Rework library structure

Changed:

- Move most library components to new [`vorago-shared-periphs`](https://egit.irs.uni-stuttgart.de/rust/vorago-shared-periphs)
  which is mostly re-exported in this crate.
- All HAL API constructors now have a more consistent argument order: PAC structures and resource
  management structures first, then clock configuration, then any other configuration.
- Overhaul and simplification of several HAL APIs. The system configuration and IRQ router
  peripheral instance generally does not need to be passed to HAL API anymore.
- All HAL drivers are now type erased. The constructors will still expect and consume the PAC
  singleton component for resource management purposes, but are not cached anymore.
- Refactoring of GPIO library to be more inline with embassy GPIO API.

Added:

- I2C clock timeout feature support.
This commit is contained in:
Robin Müller 2025-04-12 17:01:53 +02:00
parent 5b2ded51f9
commit 64a7cef47a
63 changed files with 717 additions and 9119 deletions

View File

@ -76,7 +76,7 @@ probe-rs run --chip VA108xx_RAM --protocol jtag target/thumbv6m-none-eabi/debug/
to flash and run the blinky program on the RAM. There is also a `VA108xx` chip target
available for persistent flashing.
Runner configuration avilable in the `.cargo/def-config.toml` file to use `probe-rs` for
Runner configuration is available in the `.cargo/def-config.toml` file to use `probe-rs` for
convenience. `probe-rs` is also able to process and display `defmt` strings directly.
### Using VS Code
@ -150,11 +150,10 @@ address for the RTT block placement is 0x10000000. It is recommended to use a se
0x1000 around that base address when using the RTT viewer.
The RTT viewer will not be able to process `defmt` printouts. However, you can view the defmt
logs by [installing defmt-print](https://crates.io/crates/defmt-print) first and then piping
the output on telnet port 19021 into `defmt-print`, for example by running
logs by [installing defmt-print](https://crates.io/crates/defmt-print) first and then running
```sh
telnet localhost 19021 | defmt-print -e <pathToElfFile>
defmt-print -e <pathToElfFile> tcp
```
The path of the ELF file which is being debugged needs to be specified for this to work.

View File

@ -7,18 +7,21 @@ edition = "2021"
cortex-m = "0.7"
cortex-m-rt = "0.7"
embedded-hal = "1"
panic-rtt-target = "0.2"
panic-halt = "1"
rtt-target = "0.6"
defmt-rtt = "0.4"
defmt = "1"
panic-probe = { version = "1", features = ["defmt"] }
crc = "3"
num_enum = { version = "0.7", default-features = false }
static_assertions = "1"
[dependencies.va108xx-hal]
version = "0.11"
path = "../va108xx-hal"
features = ["defmt"]
[dependencies.vorago-reb1]
version = "0.8"
path = "../vorago-reb1"
[features]
default = []

View File

@ -6,17 +6,16 @@ use cortex_m_rt::entry;
use crc::{Crc, CRC_16_IBM_3740};
use embedded_hal::delay::DelayNs;
use num_enum::TryFromPrimitive;
#[cfg(not(feature = "rtt-panic"))]
use panic_halt as _;
#[cfg(feature = "rtt-panic")]
use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print};
use va108xx_hal::{pac, time::Hertz, timer::CountdownTimer};
// Import panic provider.
use panic_probe as _;
// Import logger.
use defmt_rtt as _;
use va108xx_hal::{pac, spi::SpiClkConfig, time::Hertz, timer::CountdownTimer};
use vorago_reb1::m95m01::M95M01;
// Useful for debugging and see what the bootloader is doing. Enabled currently, because
// the binary stays small enough.
const RTT_PRINTOUT: bool = true;
const DEFMT_PRINTOUT: bool = true;
const DEBUG_PRINTOUTS: bool = true;
// Small delay, allows RTT printout to catch up.
const BOOT_DELAY_MS: u32 = 2000;
@ -74,7 +73,7 @@ pub const PREFERRED_SLOT_OFFSET: u32 = 0x20000 - 1;
const CRC_ALGO: Crc<u16> = Crc::<u16>::new(&CRC_16_IBM_3740);
#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, defmt::Format)]
#[repr(u8)]
enum AppSel {
A = 0,
@ -100,15 +99,15 @@ impl NvmInterface for NvmWrapper {
#[entry]
fn main() -> ! {
if RTT_PRINTOUT {
rtt_init_print!();
rprintln!("-- VA108xx bootloader --");
if DEFMT_PRINTOUT {
defmt::println!("-- VA108xx bootloader --");
}
let mut dp = pac::Peripherals::take().unwrap();
let dp = pac::Peripherals::take().unwrap();
let cp = cortex_m::Peripherals::take().unwrap();
let mut timer = CountdownTimer::new(&mut dp.sysconfig, CLOCK_FREQ, dp.tim0);
let mut timer = CountdownTimer::new(dp.tim0, CLOCK_FREQ);
let mut nvm = M95M01::new(&mut dp.sysconfig, CLOCK_FREQ, dp.spic);
let clk_config = SpiClkConfig::new(2, 4);
let mut nvm = M95M01::new(dp.spic, clk_config);
if FLASH_SELF {
let mut first_four_bytes: [u8; 4] = [0; 4];
@ -131,21 +130,21 @@ fn main() -> ! {
nvm.write(0x4, bootloader_data)
.expect("writing to NVM failed");
if let Err(e) = nvm.verify(0x0, &first_four_bytes) {
if RTT_PRINTOUT {
rprintln!("verification of self-flash to NVM failed: {:?}", e);
if DEFMT_PRINTOUT {
defmt::error!("verification of self-flash to NVM failed: {:?}", e);
}
}
if let Err(e) = nvm.verify(0x4, bootloader_data) {
if RTT_PRINTOUT {
rprintln!("verification of self-flash to NVM failed: {:?}", e);
if DEFMT_PRINTOUT {
defmt::error!("verification of self-flash to NVM failed: {:?}", e);
}
}
nvm.write(BOOTLOADER_CRC_ADDR as usize, &bootloader_crc.to_be_bytes())
.expect("writing CRC failed");
if let Err(e) = nvm.verify(BOOTLOADER_CRC_ADDR as usize, &bootloader_crc.to_be_bytes()) {
if RTT_PRINTOUT {
rprintln!(
if DEFMT_PRINTOUT {
defmt::error!(
"error: CRC verification for bootloader self-flash failed: {:?}",
e
);
@ -173,8 +172,8 @@ fn main() -> ! {
} else if check_app_crc(other_app) {
boot_app(&dp.sysconfig, &cp, other_app, &mut timer)
} else {
if DEBUG_PRINTOUTS && RTT_PRINTOUT {
rprintln!("both images corrupt! booting image A");
if DEBUG_PRINTOUTS && DEFMT_PRINTOUT {
defmt::error!("both images corrupt! booting image A");
}
// TODO: Shift a CCSDS packet out to inform host/OBC about image corruption.
// Both images seem to be corrupt. Boot default image A.
@ -186,7 +185,7 @@ fn check_own_crc(
sysconfig: &pac::Sysconfig,
cp: &cortex_m::Peripherals,
nvm: &mut NvmWrapper,
timer: &mut CountdownTimer<pac::Tim0>,
timer: &mut CountdownTimer,
) {
let crc_exp = unsafe { (BOOTLOADER_CRC_ADDR as *const u16).read_unaligned().to_be() };
// I'd prefer to use [core::slice::from_raw_parts], but that is problematic
@ -204,8 +203,8 @@ fn check_own_crc(
});
let crc_calc = digest.finalize();
if crc_exp == 0x0000 || crc_exp == 0xffff {
if DEBUG_PRINTOUTS && RTT_PRINTOUT {
rprintln!("BL CRC blank - prog new CRC");
if DEBUG_PRINTOUTS && DEFMT_PRINTOUT {
defmt::info!("BL CRC blank - prog new CRC");
}
// Blank CRC, write it to NVM.
nvm.write(BOOTLOADER_CRC_ADDR as usize, &crc_calc.to_be_bytes())
@ -215,8 +214,8 @@ fn check_own_crc(
// cortex_m::peripheral::SCB::sys_reset();
} else if crc_exp != crc_calc {
// Bootloader is corrupted. Try to run App A.
if DEBUG_PRINTOUTS && RTT_PRINTOUT {
rprintln!(
if DEBUG_PRINTOUTS && DEFMT_PRINTOUT {
defmt::warn!(
"bootloader CRC corrupt, read {} and expected {}. booting image A immediately",
crc_calc,
crc_exp
@ -241,8 +240,8 @@ fn read_four_bytes_at_addr_zero(buf: &mut [u8; 4]) {
}
}
fn check_app_crc(app_sel: AppSel) -> bool {
if DEBUG_PRINTOUTS && RTT_PRINTOUT {
rprintln!("Checking image {:?}", app_sel);
if DEBUG_PRINTOUTS && DEFMT_PRINTOUT {
defmt::info!("Checking image {:?}", app_sel);
}
if app_sel == AppSel::A {
check_app_given_addr(APP_A_CRC_ADDR, APP_A_START_ADDR, APP_A_SIZE_ADDR)
@ -256,8 +255,8 @@ fn check_app_given_addr(crc_addr: u32, start_addr: u32, image_size_addr: u32) ->
let image_size = unsafe { (image_size_addr as *const u32).read_unaligned().to_be() };
// Sanity check.
if image_size > APP_A_END_ADDR - APP_A_START_ADDR - 8 {
if RTT_PRINTOUT {
rprintln!("detected invalid app size {}", image_size);
if DEFMT_PRINTOUT {
defmt::error!("detected invalid app size {}", image_size);
}
return false;
}
@ -276,10 +275,10 @@ fn boot_app(
syscfg: &pac::Sysconfig,
cp: &cortex_m::Peripherals,
app_sel: AppSel,
timer: &mut CountdownTimer<pac::Tim0>,
timer: &mut CountdownTimer,
) -> ! {
if DEBUG_PRINTOUTS && RTT_PRINTOUT {
rprintln!("booting app {:?}", app_sel);
if DEBUG_PRINTOUTS && DEFMT_PRINTOUT {
defmt::info!("booting app {:?}", app_sel);
}
timer.delay_ms(BOOT_DELAY_MS);

View File

@ -29,7 +29,7 @@ embassy-executor = { version = "0.7", features = [
"executor-interrupt"
]}
va108xx-hal = { version = "0.11", features = ["defmt"] }
va108xx-hal = { version = "0.11", path = "../../va108xx-hal", features = ["defmt"] }
va108xx-embassy = { version = "0.2" }
[features]

View File

@ -12,11 +12,10 @@ use embassy_sync::channel::{Receiver, Sender};
use embassy_sync::{blocking_mutex::raw::ThreadModeRawMutex, channel::Channel};
use embassy_time::{Duration, Instant, Timer};
use embedded_hal_async::digital::Wait;
use va108xx_hal::gpio::{
on_interrupt_for_async_gpio_for_port, InputDynPinAsync, InputPinAsync, PinsB, Port,
};
use va108xx_hal::gpio::asynch::{on_interrupt_for_async_gpio_for_port, InputPinAsync};
use va108xx_hal::gpio::{Input, Output, PinState, Port};
use va108xx_hal::pins::{PinsA, PinsB};
use va108xx_hal::{
gpio::{DynPin, PinsA},
pac::{self, interrupt},
prelude::*,
};
@ -71,30 +70,28 @@ async fn main(spawner: Spawner) {
dp.tim22,
);
let porta = PinsA::new(&mut dp.sysconfig, dp.porta);
let portb = PinsB::new(&mut dp.sysconfig, dp.portb);
let mut led0 = porta.pa10.into_readable_push_pull_output();
let out_pa0 = porta.pa0.into_readable_push_pull_output();
let in_pa1 = porta.pa1.into_floating_input();
let out_pb22 = portb.pb22.into_readable_push_pull_output();
let in_pb23 = portb.pb23.into_floating_input();
let porta = PinsA::new(dp.porta);
let portb = PinsB::new(dp.portb);
let mut led0 = Output::new(porta.pa10, PinState::Low);
let out_pa0 = Output::new(porta.pa0, PinState::Low);
let in_pa1 = Input::new_floating(porta.pa1);
let out_pb22 = Output::new(portb.pb22, PinState::Low);
let in_pb23 = Input::new_floating(portb.pb23);
let in_pa1_async = InputPinAsync::new(in_pa1, pac::Interrupt::OC10);
let out_pa0_dyn = out_pa0.downgrade();
let in_pb23_async = InputDynPinAsync::new(in_pb23.downgrade(), PB22_TO_PB23_IRQ).unwrap();
let out_pb22_dyn = out_pb22.downgrade();
let in_pb23_async = InputPinAsync::new(in_pb23, PB22_TO_PB23_IRQ);
spawner
.spawn(output_task(
"PA0 to PA1",
out_pa0_dyn,
out_pa0,
CHANNEL_PA0_PA1.receiver(),
))
.unwrap();
spawner
.spawn(output_task(
"PB22 to PB23",
out_pb22_dyn,
out_pb22,
CHANNEL_PB22_TO_PB23.receiver(),
))
.unwrap();
@ -207,7 +204,7 @@ async fn check_pin_to_pin_async_ops(
#[embassy_executor::task(pool_size = 2)]
async fn output_task(
ctx: &'static str,
mut out: DynPin,
mut out: Output,
receiver: Receiver<'static, ThreadModeRawMutex, GpioCmd, 3>,
) {
loop {
@ -216,25 +213,25 @@ async fn output_task(
match next_cmd.cmd_type {
GpioCmdType::SetHigh => {
defmt::info!("{}: Set output high", ctx);
out.set_high().unwrap();
out.set_high();
}
GpioCmdType::SetLow => {
defmt::info!("{}: Set output low", ctx);
out.set_low().unwrap();
out.set_low();
}
GpioCmdType::RisingEdge => {
defmt::info!("{}: Rising edge", ctx);
if !out.is_low().unwrap() {
out.set_low().unwrap();
if !out.is_set_low() {
out.set_low();
}
out.set_high().unwrap();
out.set_high();
}
GpioCmdType::FallingEdge => {
defmt::info!("{}: Falling edge", ctx);
if !out.is_high().unwrap() {
out.set_high().unwrap();
if !out.is_set_high() {
out.set_high();
}
out.set_low().unwrap();
out.set_low();
}
}
}

View File

@ -24,8 +24,9 @@ use embedded_io::Write;
use embedded_io_async::Read;
use heapless::spsc::{Consumer, Producer, Queue};
use va108xx_hal::{
gpio::PinsA,
gpio::{Output, PinState},
pac::{self, interrupt},
pins::PinsA,
prelude::*,
uart::{
self, on_interrupt_rx_overwriting,
@ -62,34 +63,36 @@ async fn main(spawner: Spawner) {
dp.tim22,
);
let porta = PinsA::new(&mut dp.sysconfig, dp.porta);
let mut led0 = porta.pa10.into_readable_push_pull_output();
let mut led1 = porta.pa7.into_readable_push_pull_output();
let mut led2 = porta.pa6.into_readable_push_pull_output();
let porta = PinsA::new(dp.porta);
let mut led0 = Output::new(porta.pa10, PinState::Low);
let mut led1 = Output::new(porta.pa7, PinState::Low);
let mut led2 = Output::new(porta.pa6, PinState::Low);
let tx_uart_a = porta.pa9.into_funsel_2();
let rx_uart_a = porta.pa8.into_funsel_2();
let tx_uart_a = porta.pa9;
let rx_uart_a = porta.pa8;
let uarta = uart::Uart::new_with_interrupt(
&mut dp.sysconfig,
50.MHz(),
dp.uarta,
(tx_uart_a, rx_uart_a),
115200.Hz(),
tx_uart_a,
rx_uart_a,
50.MHz(),
115200.Hz().into(),
InterruptConfig::new(pac::Interrupt::OC2, true, true),
);
)
.unwrap();
let tx_uart_b = porta.pa3.into_funsel_2();
let rx_uart_b = porta.pa2.into_funsel_2();
let tx_uart_b = porta.pa3;
let rx_uart_b = porta.pa2;
let uartb = uart::Uart::new_with_interrupt(
&mut dp.sysconfig,
50.MHz(),
dp.uartb,
(tx_uart_b, rx_uart_b),
115200.Hz(),
tx_uart_b,
rx_uart_b,
50.MHz(),
115200.Hz().into(),
InterruptConfig::new(pac::Interrupt::OC3, true, true),
);
)
.unwrap();
let (mut tx_uart_a, rx_uart_a) = uarta.split();
let (tx_uart_b, rx_uart_b) = uartb.split();
let (prod_uart_a, cons_uart_a) = QUEUE_UART_A.take().split();
@ -123,7 +126,7 @@ async fn main(spawner: Spawner) {
}
#[embassy_executor::task]
async fn uart_b_task(mut async_rx: RxAsyncOverwriting<pac::Uartb, 256>, mut tx: Tx<pac::Uartb>) {
async fn uart_b_task(mut async_rx: RxAsyncOverwriting<256>, mut tx: Tx) {
let mut buf = [0u8; 256];
loop {
defmt::info!("Current time UART B: {}", Instant::now().as_secs());
@ -144,7 +147,7 @@ async fn uart_b_task(mut async_rx: RxAsyncOverwriting<pac::Uartb, 256>, mut tx:
fn OC2() {
let mut prod =
critical_section::with(|cs| PRODUCER_UART_A.borrow(cs).borrow_mut().take().unwrap());
let errors = on_interrupt_rx(Bank::A, &mut prod);
let errors = on_interrupt_rx(Bank::Uart0, &mut prod);
critical_section::with(|cs| *PRODUCER_UART_A.borrow(cs).borrow_mut() = Some(prod));
// In a production app, we could use a channel to send the errors to the main task.
if let Err(errors) = errors {
@ -157,7 +160,7 @@ fn OC2() {
fn OC3() {
let mut prod =
critical_section::with(|cs| PRODUCER_UART_B.borrow(cs).borrow_mut().take().unwrap());
let errors = on_interrupt_rx_overwriting(Bank::B, &mut prod, &CONSUMER_UART_B);
let errors = on_interrupt_rx_overwriting(Bank::Uart1, &mut prod, &CONSUMER_UART_B);
critical_section::with(|cs| *PRODUCER_UART_B.borrow(cs).borrow_mut() = Some(prod));
// In a production app, we could use a channel to send the errors to the main task.
if let Err(errors) = errors {

View File

@ -17,8 +17,9 @@ use embassy_executor::Spawner;
use embassy_time::{Duration, Instant, Ticker};
use embedded_io_async::Write;
use va108xx_hal::{
gpio::PinsA,
gpio::{Output, PinState},
pac::{self, interrupt},
pins::PinsA,
prelude::*,
uart::{self, on_interrupt_tx, Bank, TxAsync},
InterruptConfig,
@ -49,22 +50,24 @@ async fn main(_spawner: Spawner) {
dp.tim22,
);
let porta = PinsA::new(&mut dp.sysconfig, dp.porta);
let mut led0 = porta.pa10.into_readable_push_pull_output();
let mut led1 = porta.pa7.into_readable_push_pull_output();
let mut led2 = porta.pa6.into_readable_push_pull_output();
let porta = PinsA::new(dp.porta);
let tx = porta.pa9.into_funsel_2();
let rx = porta.pa8.into_funsel_2();
let mut led0 = Output::new(porta.pa10, PinState::Low);
let mut led1 = Output::new(porta.pa7, PinState::Low);
let mut led2 = Output::new(porta.pa6, PinState::Low);
let tx = porta.pa9;
let rx = porta.pa8;
let uarta = uart::Uart::new_with_interrupt(
&mut dp.sysconfig,
50.MHz(),
dp.uarta,
(tx, rx),
115200.Hz(),
tx,
rx,
50.MHz(),
115200.Hz().into(),
InterruptConfig::new(pac::Interrupt::OC2, true, true),
);
)
.unwrap();
let (tx, _rx) = uarta.split();
let mut async_tx = TxAsync::new(tx);
let mut ticker = Ticker::every(Duration::from_secs(1));
@ -89,5 +92,5 @@ async fn main(_spawner: Spawner) {
#[interrupt]
#[allow(non_snake_case)]
fn OC2() {
on_interrupt_tx(Bank::A);
on_interrupt_tx(Bank::Uart0);
}

View File

@ -12,7 +12,12 @@ cfg_if::cfg_if! {
}
}
use va108xx_hal::{gpio::PinsA, pac, prelude::*};
use va108xx_hal::{
gpio::{Output, PinState},
pac,
pins::PinsA,
prelude::*,
};
const SYSCLK_FREQ: Hertz = Hertz::from_raw(50_000_000);
@ -35,8 +40,6 @@ async fn main(_spawner: Spawner) {
);
} else {
va108xx_embassy::init_with_custom_irqs(
&mut dp.sysconfig,
&dp.irqsel,
SYSCLK_FREQ,
dp.tim23,
dp.tim22,
@ -46,10 +49,10 @@ async fn main(_spawner: Spawner) {
}
}
let porta = PinsA::new(&mut dp.sysconfig, dp.porta);
let mut led0 = porta.pa10.into_readable_push_pull_output();
let mut led1 = porta.pa7.into_readable_push_pull_output();
let mut led2 = porta.pa6.into_readable_push_pull_output();
let porta = PinsA::new(dp.porta);
let mut led0 = Output::new(porta.pa10, PinState::Low);
let mut led1 = Output::new(porta.pa7, PinState::Low);
let mut led2 = Output::new(porta.pa6, PinState::Low);
let mut ticker = Ticker::every(Duration::from_secs(1));
loop {
ticker.next().await;

View File

@ -8,8 +8,9 @@ cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
cortex-m-rt = "0.7"
embedded-hal = "1"
embedded-io = "0.6"
rtt-target = "0.6"
panic-rtt-target = "0.2"
defmt-rtt = "0.4"
defmt = "1"
panic-probe = { version = "1", features = ["defmt"] }
# Even though we do not use this directly, we need to activate this feature explicitely
# so that RTIC compiles because thumv6 does not have CAS operations natively.
@ -22,5 +23,5 @@ rtic-sync = { version = "1.3", features = ["defmt-03"] }
once_cell = {version = "1", default-features = false, features = ["critical-section"]}
ringbuf = { version = "0.4.7", default-features = false, features = ["portable-atomic"] }
va108xx-hal = { version = "0.11" }
vorago-reb1 = { version = "0.8" }
va108xx-hal = { version = "0.11", path = "../../va108xx-hal" }
vorago-reb1 = { version = "0.8", path = "../../vorago-reb1" }

View File

@ -4,34 +4,29 @@
#[rtic::app(device = pac)]
mod app {
use panic_rtt_target as _;
use rtic_example::SYSCLK_FREQ;
use rtt_target::{rprintln, rtt_init_default, set_print_channel};
// Import panic provider.
use panic_probe as _;
// Import global logger.
use defmt_rtt as _;
use va108xx_hal::{
clock::{set_clk_div_register, FilterClkSel},
gpio::{FilterType, InterruptEdge, PinsA},
gpio::{FilterType, InterruptEdge},
pac,
prelude::*,
timer::{default_ms_irq_handler, set_up_ms_tick, InterruptConfig},
pins::PinsA,
timer::InterruptConfig,
};
use vorago_reb1::button::Button;
use vorago_reb1::leds::Leds;
rtic_monotonics::systick_monotonic!(Mono, 1_000);
#[derive(Debug, PartialEq)]
#[derive(Debug, PartialEq, defmt::Format)]
pub enum PressMode {
Toggle,
Keep,
}
#[derive(Debug, PartialEq)]
pub enum CfgMode {
Prompt,
Fixed,
}
const CFG_MODE: CfgMode = CfgMode::Fixed;
// You can change the press mode here
const DEFAULT_MODE: PressMode = PressMode::Toggle;
@ -47,53 +42,35 @@ mod app {
#[init]
fn init(cx: init::Context) -> (Shared, Local) {
let channels = rtt_init_default!();
set_print_channel(channels.up.0);
rprintln!("-- Vorago Button IRQ Example --");
defmt::println!("-- Vorago Button IRQ Example --");
Mono::start(cx.core.SYST, SYSCLK_FREQ.raw());
let mode = match CFG_MODE {
// Ask mode from user via RTT
CfgMode::Prompt => prompt_mode(channels.down.0),
// Use mode hardcoded in `DEFAULT_MODE`
CfgMode::Fixed => DEFAULT_MODE,
};
rprintln!("Using {:?} mode", mode);
let mode = DEFAULT_MODE;
defmt::info!("Using {:?} mode", mode);
let mut dp = cx.device;
let pinsa = PinsA::new(&mut dp.sysconfig, dp.porta);
let pinsa = PinsA::new(dp.porta);
let edge_irq = match mode {
PressMode::Toggle => InterruptEdge::HighToLow,
PressMode::Keep => InterruptEdge::BothEdges,
};
// Configure an edge interrupt on the button and route it to interrupt vector 15
let mut button = Button::new(pinsa.pa11.into_floating_input());
let mut button = Button::new(pinsa.pa11);
if mode == PressMode::Toggle {
// This filter debounces the switch for edge based interrupts
button.configure_filter_type(FilterType::FilterFourClockCycles, FilterClkSel::Clk1);
button.configure_filter_type(FilterType::FilterFourCycles, FilterClkSel::Clk1);
set_clk_div_register(&mut dp.sysconfig, FilterClkSel::Clk1, 50_000);
}
button.configure_and_enable_edge_interrupt(
edge_irq,
InterruptConfig::new(pac::interrupt::OC15, true, true),
);
let mut leds = Leds::new(
pinsa.pa10.into_push_pull_output(),
pinsa.pa7.into_push_pull_output(),
pinsa.pa6.into_push_pull_output(),
);
let mut leds = Leds::new(pinsa.pa10, pinsa.pa7, pinsa.pa6);
for led in leds.iter_mut() {
led.off();
}
set_up_ms_tick(
InterruptConfig::new(pac::Interrupt::OC0, true, true),
&mut dp.sysconfig,
Some(&mut dp.irqsel),
50.MHz(),
dp.tim0,
);
(Shared {}, Local { leds, button, mode })
}
@ -118,26 +95,4 @@ mod app {
leds[0].on();
}
}
#[task(binds = OC0)]
fn ms_tick(_cx: ms_tick::Context) {
default_ms_irq_handler();
}
fn prompt_mode(mut down_channel: rtt_target::DownChannel) -> PressMode {
rprintln!("Using prompt mode");
rprintln!("Please enter the mode [0: Toggle, 1: Keep]");
let mut read_buf: [u8; 16] = [0; 16];
let mut read;
loop {
read = down_channel.read(&mut read_buf);
for &byte in &read_buf[..read] {
match byte as char {
'0' => return PressMode::Toggle,
'1' => return PressMode::Keep,
_ => continue, // Ignore other characters
}
}
}
}
}

View File

@ -4,8 +4,10 @@
#[rtic::app(device = pac)]
mod app {
use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_default};
// Import panic provider.
use panic_probe as _;
// Import global logger.
use defmt_rtt as _;
use va108xx_hal::pac;
#[local]
@ -16,8 +18,7 @@ mod app {
#[init]
fn init(_ctx: init::Context) -> (Shared, Local) {
rtt_init_default!();
rprintln!("-- Vorago RTIC template --");
defmt::println!("-- Vorago RTIC template --");
(Shared {}, Local {})
}

View File

@ -1,4 +1,4 @@
//! More complex UART application
//! More complex UART application on UART PA8 (TX) and PA9 (RX).
//!
//! Uses the IRQ capabilities of the VA10820 peripheral and the RTIC framework to poll the UART in
//! a non-blocking way. All received data will be sent back to the sender.
@ -14,14 +14,16 @@ const RX_RING_BUF_SIZE: usize = 1024;
mod app {
use super::*;
use embedded_io::Write;
use panic_rtt_target as _;
use ringbuf::traits::{Consumer, Observer, Producer};
use rtic_example::SYSCLK_FREQ;
// Import panic provider.
use panic_probe as _;
// Import global logger.
use defmt_rtt as _;
use rtic_monotonics::Monotonic;
use rtt_target::{rprintln, rtt_init_print};
use va108xx_hal::{
gpio::PinsA,
pac,
pins::PinsA,
prelude::*,
uart::{self, RxWithInterrupt, Tx},
InterruptConfig,
@ -29,8 +31,8 @@ mod app {
#[local]
struct Local {
rx: RxWithInterrupt<pac::Uarta>,
tx: Tx<pac::Uarta>,
rx: RxWithInterrupt,
tx: Tx,
}
#[shared]
@ -42,24 +44,24 @@ mod app {
#[init]
fn init(cx: init::Context) -> (Shared, Local) {
rtt_init_print!();
rprintln!("-- VA108xx UART Echo with IRQ example application--");
defmt::println!("-- VA108xx UART Echo with IRQ example application--");
Mono::start(cx.core.SYST, SYSCLK_FREQ.raw());
let mut dp = cx.device;
let gpioa = PinsA::new(&mut dp.sysconfig, dp.porta);
let tx = gpioa.pa9.into_funsel_2();
let rx = gpioa.pa8.into_funsel_2();
let dp = cx.device;
let gpioa = PinsA::new(dp.porta);
let tx = gpioa.pa9;
let rx = gpioa.pa8;
let irq_uart = uart::Uart::new_with_interrupt(
&mut dp.sysconfig,
SYSCLK_FREQ,
dp.uarta,
(tx, rx),
115200.Hz(),
tx,
rx,
SYSCLK_FREQ,
115200.Hz().into(),
InterruptConfig::new(pac::Interrupt::OC3, true, true),
);
)
.unwrap();
let (tx, rx) = irq_uart.split();
let mut rx = rx.into_rx_with_irq();
@ -104,7 +106,7 @@ mod app {
}
if ringbuf_full {
// Could also drop oldest data, but that would require the consumer to be shared.
rprintln!("buffer full, data was dropped");
defmt::println!("buffer full, data was dropped");
}
}

View File

@ -5,21 +5,24 @@
#[rtic::app(device = pac, dispatchers = [OC31, OC30, OC29])]
mod app {
use cortex_m::asm;
use panic_rtt_target as _;
use rtic_example::SYSCLK_FREQ;
use rtic_monotonics::systick::prelude::*;
use rtic_monotonics::Monotonic;
use rtt_target::{rprintln, rtt_init_print};
// Import panic provider.
use panic_probe as _;
// Import global logger.
use defmt_rtt as _;
use va108xx_hal::{
gpio::{OutputReadablePushPull, Pin, PinsA, PA10, PA6, PA7},
gpio::{Output, PinState},
pac,
pins::PinsA,
};
#[local]
struct Local {
led0: Pin<PA10, OutputReadablePushPull>,
led1: Pin<PA7, OutputReadablePushPull>,
led2: Pin<PA6, OutputReadablePushPull>,
led0: Output,
led1: Output,
led2: Output,
}
#[shared]
@ -28,16 +31,15 @@ mod app {
rtic_monotonics::systick_monotonic!(Mono, 1_000);
#[init]
fn init(mut cx: init::Context) -> (Shared, Local) {
rtt_init_print!();
rprintln!("-- Vorago VA108xx RTIC template --");
fn init(cx: init::Context) -> (Shared, Local) {
defmt::println!("-- Vorago VA108xx RTIC template --");
Mono::start(cx.core.SYST, SYSCLK_FREQ.raw());
let porta = PinsA::new(&mut cx.device.sysconfig, cx.device.porta);
let led0 = porta.pa10.into_readable_push_pull_output();
let led1 = porta.pa7.into_readable_push_pull_output();
let led2 = porta.pa6.into_readable_push_pull_output();
let porta = PinsA::new(cx.device.porta);
let led0 = Output::new(porta.pa10, PinState::Low);
let led1 = Output::new(porta.pa7, PinState::Low);
let led2 = Output::new(porta.pa6, PinState::Low);
blinky::spawn().ok();
(Shared {}, Local { led0, led1, led2 })
}
@ -56,7 +58,7 @@ mod app {
)]
async fn blinky(cx: blinky::Context) {
loop {
rprintln!("toggling LEDs");
defmt::println!("toggling LEDs");
cx.local.led0.toggle();
cx.local.led1.toggle();
cx.local.led2.toggle();

View File

@ -7,17 +7,21 @@ edition = "2021"
cortex-m = {version = "0.7", features = ["critical-section-single-core"]}
cortex-m-rt = "0.7"
panic-halt = "1"
panic-rtt-target = "0.2"
critical-section = "1"
rtt-target = "0.6"
defmt-rtt = "0.4"
defmt = "1"
panic-probe = { version = "1", features = ["defmt"] }
embedded-hal = "1"
embedded-hal-nb = "1"
embedded-io = "0.6"
cortex-m-semihosting = "0.5.0"
portable-atomic = { version = "1", features = ["unsafe-assume-single-core"] }
[dependencies.va108xx-hal]
version = "0.11"
path = "../../va108xx-hal"
features = ["defmt"]
[dependencies.vorago-reb1]
path = "../../vorago-reb1"
version = "0.8"

View File

@ -10,52 +10,37 @@ use cortex_m_rt::entry;
use embedded_hal::delay::DelayNs;
use panic_halt as _;
use va108xx_hal::{
gpio::PinsA,
pac::{self, interrupt},
gpio::{Output, PinState},
pac::{self},
pins::PinsA,
prelude::*,
timer::DelayMs,
timer::{default_ms_irq_handler, set_up_ms_tick, CountdownTimer},
InterruptConfig,
timer::CountdownTimer,
};
#[entry]
fn main() -> ! {
let mut dp = pac::Peripherals::take().unwrap();
let mut delay_ms = DelayMs::new(set_up_ms_tick(
InterruptConfig::new(interrupt::OC0, true, true),
&mut dp.sysconfig,
Some(&mut dp.irqsel),
50.MHz(),
dp.tim0,
))
.unwrap();
let mut delay_tim1 = CountdownTimer::new(&mut dp.sysconfig, 50.MHz(), dp.tim1);
let porta = PinsA::new(&mut dp.sysconfig, dp.porta);
let mut led1 = porta.pa10.into_readable_push_pull_output();
let mut led2 = porta.pa7.into_readable_push_pull_output();
let mut led3 = porta.pa6.into_readable_push_pull_output();
let dp = pac::Peripherals::take().unwrap();
let mut delay = CountdownTimer::new(dp.tim1, 50.MHz());
let porta = PinsA::new(dp.porta);
let mut led1 = Output::new(porta.pa10, PinState::Low);
let mut led2 = Output::new(porta.pa7, PinState::Low);
let mut led3 = Output::new(porta.pa6, PinState::Low);
for _ in 0..10 {
led1.set_low();
led2.set_low();
led3.set_low();
delay_ms.delay_ms(200);
delay.delay_ms(200);
led1.set_high();
led2.set_high();
led3.set_high();
delay_tim1.delay_ms(200);
delay.delay_ms(200);
}
loop {
led1.toggle();
delay_ms.delay_ms(200);
delay.delay_ms(200);
led2.toggle();
delay_tim1.delay_ms(200);
delay.delay_ms(200);
led3.toggle();
delay_ms.delay_ms(200);
delay.delay_ms(200);
}
}
#[interrupt]
#[allow(non_snake_case)]
fn OC0() {
default_ms_irq_handler()
}

View File

@ -6,92 +6,68 @@
#![no_std]
#![allow(non_snake_case)]
use core::cell::RefCell;
use cortex_m::interrupt::Mutex;
use cortex_m_rt::entry;
use embedded_hal::delay::DelayNs;
use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print};
// Import panic provider.
use panic_probe as _;
// Import logger.
use defmt_rtt as _;
use va108xx_hal::{
pac::{self, interrupt},
prelude::*,
timer::{
default_ms_irq_handler, set_up_ms_delay_provider, CascadeCtrl, CascadeSource,
CountdownTimer, Event, InterruptConfig,
},
timer::{CascadeControl, CascadeSelect, CascadeSource, CountdownTimer, InterruptConfig},
};
static CSD_TGT_1: Mutex<RefCell<Option<CountdownTimer<pac::Tim4>>>> =
Mutex::new(RefCell::new(None));
static CSD_TGT_2: Mutex<RefCell<Option<CountdownTimer<pac::Tim5>>>> =
Mutex::new(RefCell::new(None));
#[entry]
fn main() -> ! {
rtt_init_print!();
rprintln!("-- VA108xx Cascade example application--");
defmt::println!("-- VA108xx Cascade example application--");
let mut dp = pac::Peripherals::take().unwrap();
let mut delay = set_up_ms_delay_provider(&mut dp.sysconfig, 50.MHz(), dp.tim0);
let dp = pac::Peripherals::take().unwrap();
let mut delay = CountdownTimer::new(dp.tim0, 50.MHz());
// Will be started periodically to trigger a cascade
let mut cascade_triggerer =
CountdownTimer::new(&mut dp.sysconfig, 50.MHz(), dp.tim3).auto_disable(true);
cascade_triggerer.listen(
Event::TimeOut,
InterruptConfig::new(pac::Interrupt::OC1, true, false),
Some(&mut dp.irqsel),
Some(&mut dp.sysconfig),
);
let mut cascade_triggerer = CountdownTimer::new(dp.tim3, 50.MHz());
cascade_triggerer.auto_disable(true);
cascade_triggerer.enable_interrupt(InterruptConfig::new(pac::Interrupt::OC1, true, false));
cascade_triggerer.enable();
// First target for cascade
let mut cascade_target_1 =
CountdownTimer::new(&mut dp.sysconfig, 50.MHz(), dp.tim4).auto_deactivate(true);
let mut cascade_target_1 = CountdownTimer::new(dp.tim4, 50.MHz());
cascade_target_1.auto_deactivate(true);
cascade_target_1
.cascade_0_source(CascadeSource::Tim(3))
.expect("Configuring cascade source for TIM4 failed");
let mut csd_cfg = CascadeCtrl {
enb_start_src_csd0: true,
.cascade_source(CascadeSelect::Csd0, CascadeSource::Tim(3))
.unwrap();
let mut csd_cfg = CascadeControl {
enable_src_0: true,
trigger_mode_0: true,
..Default::default()
};
// Use trigger mode here
csd_cfg.trg_csd0 = true;
cascade_target_1.cascade_control(csd_cfg);
// Normally it should already be sufficient to activate IRQ in the CTRL
// register but a full interrupt is use here to display print output when
// the timer expires
cascade_target_1.listen(
Event::TimeOut,
InterruptConfig::new(pac::Interrupt::OC2, true, false),
Some(&mut dp.irqsel),
Some(&mut dp.sysconfig),
);
cascade_target_1.enable_interrupt(InterruptConfig::new(pac::Interrupt::OC2, true, false));
// The counter will only activate when the cascade signal is coming in so
// it is okay to call start here to set the reset value
cascade_target_1.start(1.Hz());
// Activated by first cascade target
let mut cascade_target_2 =
CountdownTimer::new(&mut dp.sysconfig, 50.MHz(), dp.tim5).auto_deactivate(true);
let mut cascade_target_2 = CountdownTimer::new(dp.tim5, 50.MHz());
cascade_target_2.auto_deactivate(true);
// Set TIM4 as cascade source
cascade_target_2
.cascade_1_source(CascadeSource::Tim(4))
.expect("Configuring cascade source for TIM5 failed");
.cascade_source(CascadeSelect::Csd1, CascadeSource::Tim(4))
.unwrap();
csd_cfg = CascadeCtrl::default();
csd_cfg.enb_start_src_csd1 = true;
csd_cfg = CascadeControl::default();
csd_cfg.enable_src_1 = true;
// Use trigger mode here
csd_cfg.trg_csd1 = true;
csd_cfg.trigger_mode_1 = true;
cascade_target_2.cascade_control(csd_cfg);
// Normally it should already be sufficient to activate IRQ in the CTRL
// register but a full interrupt is use here to display print output when
// the timer expires
cascade_target_2.listen(
Event::TimeOut,
InterruptConfig::new(pac::Interrupt::OC3, true, false),
Some(&mut dp.irqsel),
Some(&mut dp.sysconfig),
);
cascade_target_2.enable_interrupt(InterruptConfig::new(pac::Interrupt::OC3, true, false));
// The counter will only activate when the cascade signal is coming in so
// it is okay to call start here to set the reset value
cascade_target_2.start(1.Hz());
@ -103,40 +79,31 @@ fn main() -> ! {
cortex_m::peripheral::NVIC::unmask(pac::Interrupt::OC2);
cortex_m::peripheral::NVIC::unmask(pac::Interrupt::OC3);
}
// Make both cascade targets accessible from the IRQ handler with the Mutex dance
cortex_m::interrupt::free(|cs| {
CSD_TGT_1.borrow(cs).replace(Some(cascade_target_1));
CSD_TGT_2.borrow(cs).replace(Some(cascade_target_2));
});
loop {
rprintln!("-- Triggering cascade in 0.5 seconds --");
defmt::info!("-- Triggering cascade in 0.5 seconds --");
cascade_triggerer.start(2.Hz());
delay.delay_ms(5000);
}
}
#[interrupt]
fn OC0() {
default_ms_irq_handler()
}
#[interrupt]
fn OC1() {
static mut IDX: u32 = 0;
rprintln!("{}: Cascade triggered timed out", &IDX);
defmt::info!("{}: Cascade trigger timed out", &IDX);
*IDX += 1;
}
#[interrupt]
fn OC2() {
static mut IDX: u32 = 0;
rprintln!("{}: First cascade target timed out", &IDX);
defmt::info!("{}: First cascade target timed out", &IDX);
*IDX += 1;
}
#[interrupt]
fn OC3() {
static mut IDX: u32 = 0;
rprintln!("{}: Second cascade target timed out", &IDX);
defmt::info!("{}: Second cascade target timed out", &IDX);
*IDX += 1;
}

View File

@ -1,39 +1,36 @@
//! Simple PWM example
//!
//! Outputs a PWM waveform on pin PA3.
#![no_main]
#![no_std]
use cortex_m_rt::entry;
use embedded_hal::{delay::DelayNs, pwm::SetDutyCycle};
use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print};
// Import panic provider.
use panic_probe as _;
// Import logger.
use defmt_rtt as _;
use va108xx_hal::{
gpio::PinsA,
pac,
pins::PinsA,
prelude::*,
pwm::{self, get_duty_from_percent, PwmA, PwmB, ReducedPwmPin},
timer::set_up_ms_delay_provider,
pwm::{self, get_duty_from_percent, PwmA, PwmB, PwmPin},
timer::CountdownTimer,
};
#[entry]
fn main() -> ! {
rtt_init_print!();
rprintln!("-- VA108xx PWM example application--");
let mut dp = pac::Peripherals::take().unwrap();
let pinsa = PinsA::new(&mut dp.sysconfig, dp.porta);
let mut pwm = pwm::PwmPin::new(
&mut dp.sysconfig,
50.MHz(),
(pinsa.pa3.into_funsel_1(), dp.tim3),
10.Hz(),
);
let mut delay = set_up_ms_delay_provider(&mut dp.sysconfig, 50.MHz(), dp.tim0);
defmt::println!("-- VA108xx PWM example application--");
let dp = pac::Peripherals::take().unwrap();
let pinsa = PinsA::new(dp.porta);
let mut pwm = pwm::PwmPin::new(pinsa.pa3, dp.tim3, 50.MHz(), 10.Hz()).unwrap();
let mut delay = CountdownTimer::new(dp.tim0, 50.MHz());
let mut current_duty_cycle = 0.0;
pwm.set_duty_cycle(get_duty_from_percent(current_duty_cycle))
.unwrap();
pwm.enable();
// Delete type information, increased code readibility for the rest of the code
let mut reduced_pin = ReducedPwmPin::from(pwm);
loop {
let mut counter = 0;
// Increase duty cycle continuously
@ -42,11 +39,10 @@ fn main() -> ! {
current_duty_cycle += 0.02;
counter += 1;
if counter % 10 == 0 {
rprintln!("current duty cycle: {}", current_duty_cycle);
defmt::info!("current duty cycle: {}", current_duty_cycle);
}
reduced_pin
.set_duty_cycle(get_duty_from_percent(current_duty_cycle))
pwm.set_duty_cycle(get_duty_from_percent(current_duty_cycle))
.unwrap();
}
@ -55,7 +51,7 @@ fn main() -> ! {
current_duty_cycle = 0.0;
let mut upper_limit = 1.0;
let mut lower_limit = 0.0;
let mut pwmb: ReducedPwmPin<PwmB> = ReducedPwmPin::from(reduced_pin);
let mut pwmb: PwmPin<PwmB> = PwmPin::from(pwm);
pwmb.set_pwmb_lower_limit(get_duty_from_percent(lower_limit));
pwmb.set_pwmb_upper_limit(get_duty_from_percent(upper_limit));
while lower_limit < 0.5 {
@ -64,9 +60,9 @@ fn main() -> ! {
upper_limit -= 0.01;
pwmb.set_pwmb_lower_limit(get_duty_from_percent(lower_limit));
pwmb.set_pwmb_upper_limit(get_duty_from_percent(upper_limit));
rprintln!("Lower limit: {}", pwmb.pwmb_lower_limit());
rprintln!("Upper limit: {}", pwmb.pwmb_upper_limit());
defmt::info!("Lower limit: {}", pwmb.pwmb_lower_limit());
defmt::info!("Upper limit: {}", pwmb.pwmb_upper_limit());
}
reduced_pin = ReducedPwmPin::<PwmA>::from(pwmb);
pwm = PwmPin::<PwmA>::from(pwmb);
}
}

View File

@ -1,20 +0,0 @@
//! Code to test RTT logger functionality
#![no_main]
#![no_std]
use cortex_m_rt::entry;
use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print};
use va108xx_hal as _;
#[entry]
fn main() -> ! {
rtt_init_print!();
rprintln!("-- VA108XX RTT example --");
let mut counter = 0;
loop {
rprintln!("{}: Hello, world!", counter);
counter += 1;
cortex_m::asm::delay(25_000_000);
}
}

View File

@ -1,23 +1,21 @@
//! SPI example application
#![no_main]
#![no_std]
use core::cell::RefCell;
use cortex_m_rt::entry;
use embedded_hal::{
delay::DelayNs,
spi::{Mode, SpiBus, MODE_0},
};
use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print};
// Import panic provider.
use panic_probe as _;
// Import logger.
use defmt_rtt as _;
use va108xx_hal::{
gpio::{PinsA, PinsB},
pac::{self, interrupt},
pac,
pins::{PinsA, PinsB},
prelude::*,
spi::{self, Spi, SpiBase, SpiClkConfig, TransferConfigWithHwcs},
timer::{default_ms_irq_handler, set_up_ms_tick},
InterruptConfig,
spi::{self, configure_pin_as_hw_cs_pin, Spi, SpiClkConfig, TransferConfig},
timer::CountdownTimer,
};
#[derive(PartialEq, Debug)]
@ -43,23 +41,14 @@ const FILL_WORD: u8 = 0x0f;
#[entry]
fn main() -> ! {
rtt_init_print!();
rprintln!("-- VA108xx SPI example application--");
let mut dp = pac::Peripherals::take().unwrap();
let mut delay = set_up_ms_tick(
InterruptConfig::new(interrupt::OC0, true, true),
&mut dp.sysconfig,
Some(&mut dp.irqsel),
50.MHz(),
dp.tim0,
);
defmt::println!("-- VA108xx SPI example application--");
let dp = pac::Peripherals::take().unwrap();
let mut delay = CountdownTimer::new(dp.tim0, 50.MHz());
let spi_clk_cfg = SpiClkConfig::from_clk(50.MHz(), SPI_SPEED_KHZ.kHz())
.expect("creating SPI clock config failed");
let spia_ref: RefCell<Option<SpiBase<pac::Spia, u8>>> = RefCell::new(None);
let spib_ref: RefCell<Option<SpiBase<pac::Spib, u8>>> = RefCell::new(None);
let pinsa = PinsA::new(&mut dp.sysconfig, dp.porta);
let pinsb = PinsB::new(&mut dp.sysconfig, dp.portb);
let pinsa = PinsA::new(dp.porta);
let pinsb = PinsB::new(dp.portb);
let mut spi_cfg = spi::SpiConfig::default();
if EXAMPLE_SEL == ExampleSelect::Loopback {
@ -67,158 +56,82 @@ fn main() -> ! {
}
// Set up the SPI peripheral
match SPI_BUS_SEL {
let mut spi = match SPI_BUS_SEL {
SpiBusSelect::SpiAPortA => {
let (sck, mosi, miso) = (
pinsa.pa31.into_funsel_1(),
pinsa.pa30.into_funsel_1(),
pinsa.pa29.into_funsel_1(),
);
let mut spia = Spi::new(
&mut dp.sysconfig,
50.MHz(),
dp.spia,
(sck, miso, mosi),
spi_cfg,
);
let (sck, mosi, miso) = (pinsa.pa31, pinsa.pa30, pinsa.pa29);
let mut spia = Spi::new(dp.spia, (sck, miso, mosi), spi_cfg).unwrap();
spia.set_fill_word(FILL_WORD);
spia_ref.borrow_mut().replace(spia.downgrade());
spia
}
SpiBusSelect::SpiAPortB => {
let (sck, mosi, miso) = (
pinsb.pb9.into_funsel_2(),
pinsb.pb8.into_funsel_2(),
pinsb.pb7.into_funsel_2(),
);
let mut spia = Spi::new(
&mut dp.sysconfig,
50.MHz(),
dp.spia,
(sck, miso, mosi),
spi_cfg,
);
let (sck, mosi, miso) = (pinsb.pb9, pinsb.pb8, pinsb.pb7);
let mut spia = Spi::new(dp.spia, (sck, miso, mosi), spi_cfg).unwrap();
spia.set_fill_word(FILL_WORD);
spia_ref.borrow_mut().replace(spia.downgrade());
spia
}
SpiBusSelect::SpiBPortB => {
let (sck, mosi, miso) = (
pinsb.pb5.into_funsel_1(),
pinsb.pb4.into_funsel_1(),
pinsb.pb3.into_funsel_1(),
);
let mut spib = Spi::new(
&mut dp.sysconfig,
50.MHz(),
dp.spib,
(sck, miso, mosi),
spi_cfg,
);
let (sck, mosi, miso) = (pinsb.pb5, pinsb.pb4, pinsb.pb3);
let mut spib = Spi::new(dp.spib, (sck, miso, mosi), spi_cfg).unwrap();
spib.set_fill_word(FILL_WORD);
spib_ref.borrow_mut().replace(spib.downgrade());
spib
}
}
};
// Configure transfer specific properties here
match SPI_BUS_SEL {
SpiBusSelect::SpiAPortA | SpiBusSelect::SpiAPortB => {
if let Some(ref mut spi) = *spia_ref.borrow_mut() {
let transfer_cfg = TransferConfigWithHwcs::new_no_hw_cs(
Some(spi_clk_cfg),
Some(SPI_MODE),
BLOCKMODE,
true,
false,
);
spi.cfg_transfer(&transfer_cfg);
}
let transfer_cfg = TransferConfig {
clk_cfg: Some(spi_clk_cfg),
mode: Some(SPI_MODE),
sod: true,
blockmode: BLOCKMODE,
bmstall: true,
hw_cs: None,
};
spi.cfg_transfer(&transfer_cfg);
}
SpiBusSelect::SpiBPortB => {
if let Some(ref mut spi) = *spib_ref.borrow_mut() {
let hw_cs_pin = pinsb.pb2.into_funsel_1();
let transfer_cfg = TransferConfigWithHwcs::new(
Some(spi_clk_cfg),
Some(SPI_MODE),
Some(hw_cs_pin),
BLOCKMODE,
true,
false,
);
spi.cfg_transfer(&transfer_cfg);
}
let hw_cs_pin = configure_pin_as_hw_cs_pin(pinsb.pb2);
let transfer_cfg = TransferConfig {
clk_cfg: Some(spi_clk_cfg),
mode: Some(SPI_MODE),
sod: false,
blockmode: BLOCKMODE,
bmstall: true,
hw_cs: Some(hw_cs_pin),
};
spi.cfg_transfer(&transfer_cfg);
}
}
// Application logic
loop {
let mut reply_buf: [u8; 8] = [0; 8];
match SPI_BUS_SEL {
SpiBusSelect::SpiAPortA | SpiBusSelect::SpiAPortB => {
if let Some(ref mut spi) = *spia_ref.borrow_mut() {
// Can't really verify correct reply here.
spi.write(&[0x42]).expect("write failed");
// Because of the loopback mode, we should get back the fill word here.
spi.read(&mut reply_buf[0..1]).unwrap();
assert_eq!(reply_buf[0], FILL_WORD);
delay.delay_ms(500_u32);
// Can't really verify correct reply here.
spi.write(&[0x42]).expect("write failed");
// Because of the loopback mode, we should get back the fill word here.
spi.read(&mut reply_buf[0..1]).unwrap();
assert_eq!(reply_buf[0], FILL_WORD);
delay.delay_ms(500_u32);
let tx_buf: [u8; 3] = [0x01, 0x02, 0x03];
spi.transfer(&mut reply_buf[0..3], &tx_buf).unwrap();
assert_eq!(tx_buf, reply_buf[0..3]);
rprintln!(
"Received reply: {}, {}, {}",
reply_buf[0],
reply_buf[1],
reply_buf[2]
);
delay.delay_ms(500_u32);
let tx_buf: [u8; 3] = [0x01, 0x02, 0x03];
spi.transfer(&mut reply_buf[0..3], &tx_buf).unwrap();
assert_eq!(tx_buf, reply_buf[0..3]);
defmt::info!(
"Received reply: {}, {}, {}",
reply_buf[0],
reply_buf[1],
reply_buf[2]
);
delay.delay_ms(500_u32);
let mut tx_rx_buf: [u8; 3] = [0x03, 0x02, 0x01];
spi.transfer_in_place(&mut tx_rx_buf).unwrap();
rprintln!(
"Received reply: {}, {}, {}",
tx_rx_buf[0],
tx_rx_buf[1],
tx_rx_buf[2]
);
assert_eq!(&tx_rx_buf[0..3], &[0x03, 0x02, 0x01]);
}
}
SpiBusSelect::SpiBPortB => {
if let Some(ref mut spi) = *spib_ref.borrow_mut() {
// Can't really verify correct reply here.
spi.write(&[0x42]).expect("write failed");
// Because of the loopback mode, we should get back the fill word here.
spi.read(&mut reply_buf[0..1]).unwrap();
assert_eq!(reply_buf[0], FILL_WORD);
delay.delay_ms(500_u32);
let tx_buf: [u8; 3] = [0x01, 0x02, 0x03];
spi.transfer(&mut reply_buf[0..3], &tx_buf).unwrap();
assert_eq!(tx_buf, reply_buf[0..3]);
rprintln!(
"Received reply: {}, {}, {}",
reply_buf[0],
reply_buf[1],
reply_buf[2]
);
delay.delay_ms(500_u32);
let mut tx_rx_buf: [u8; 3] = [0x03, 0x02, 0x01];
spi.transfer_in_place(&mut tx_rx_buf).unwrap();
rprintln!(
"Received reply: {}, {}, {}",
tx_rx_buf[0],
tx_rx_buf[1],
tx_rx_buf[2]
);
assert_eq!(&tx_rx_buf[0..3], &[0x03, 0x02, 0x01]);
}
}
}
let mut tx_rx_buf: [u8; 3] = [0x03, 0x02, 0x01];
spi.transfer_in_place(&mut tx_rx_buf).unwrap();
defmt::info!(
"Received reply: {}, {}, {}",
tx_rx_buf[0],
tx_rx_buf[1],
tx_rx_buf[2]
);
assert_eq!(&tx_rx_buf[0..3], &[0x03, 0x02, 0x01]);
}
}
#[interrupt]
#[allow(non_snake_case)]
fn OC0() {
default_ms_irq_handler()
}

View File

@ -2,19 +2,19 @@
#![no_main]
#![no_std]
use core::cell::Cell;
use cortex_m_rt::entry;
use critical_section::Mutex;
use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print};
use embedded_hal::delay::DelayNs;
// Import panic provider.
use panic_probe as _;
// Import logger.
use defmt_rtt as _;
use portable_atomic::AtomicU32;
use va108xx_hal::{
clock::{get_sys_clock, set_sys_clock},
pac::{self, interrupt},
prelude::*,
time::Hertz,
timer::{
default_ms_irq_handler, set_up_ms_tick, CountdownTimer, Event, InterruptConfig, MS_COUNTER,
},
timer::{CountdownTimer, InterruptConfig},
};
#[allow(dead_code)]
@ -23,14 +23,15 @@ enum LibType {
Hal,
}
static SEC_COUNTER: Mutex<Cell<u32>> = Mutex::new(Cell::new(0));
static MS_COUNTER: AtomicU32 = AtomicU32::new(0);
static SEC_COUNTER: AtomicU32 = AtomicU32::new(0);
#[entry]
fn main() -> ! {
rtt_init_print!();
let mut dp = pac::Peripherals::take().unwrap();
let dp = pac::Peripherals::take().unwrap();
let mut delay = CountdownTimer::new(dp.tim2, 50.MHz());
let mut last_ms = 0;
rprintln!("-- Vorago system ticks using timers --");
defmt::info!("-- Vorago system ticks using timers --");
set_sys_clock(50.MHz());
let lib_type = LibType::Hal;
match lib_type {
@ -66,34 +67,24 @@ fn main() -> ! {
}
}
LibType::Hal => {
set_up_ms_tick(
InterruptConfig::new(interrupt::OC0, true, true),
&mut dp.sysconfig,
Some(&mut dp.irqsel),
50.MHz(),
dp.tim0,
);
let mut second_timer =
CountdownTimer::new(&mut dp.sysconfig, get_sys_clock().unwrap(), dp.tim1);
second_timer.listen(
Event::TimeOut,
InterruptConfig::new(interrupt::OC1, true, true),
Some(&mut dp.irqsel),
Some(&mut dp.sysconfig),
);
let mut ms_timer = CountdownTimer::new(dp.tim0, get_sys_clock().unwrap());
ms_timer.enable_interrupt(InterruptConfig::new(interrupt::OC0, true, true));
ms_timer.start(1.kHz());
let mut second_timer = CountdownTimer::new(dp.tim1, get_sys_clock().unwrap());
second_timer.enable_interrupt(InterruptConfig::new(interrupt::OC1, true, true));
second_timer.start(1.Hz());
}
}
loop {
let current_ms = critical_section::with(|cs| MS_COUNTER.borrow(cs).get());
let current_ms = MS_COUNTER.load(portable_atomic::Ordering::Relaxed);
if current_ms - last_ms >= 1000 {
// To prevent drift.
last_ms += 1000;
rprintln!("MS counter: {}", current_ms);
let second = critical_section::with(|cs| SEC_COUNTER.borrow(cs).get());
rprintln!("Second counter: {}", second);
defmt::info!("MS counter: {}", current_ms);
let second = SEC_COUNTER.load(portable_atomic::Ordering::Relaxed);
defmt::info!("Second counter: {}", second);
}
cortex_m::asm::delay(10000);
delay.delay_ms(50);
}
}
@ -107,15 +98,11 @@ fn unmask_irqs() {
#[interrupt]
#[allow(non_snake_case)]
fn OC0() {
default_ms_irq_handler()
MS_COUNTER.fetch_add(1, portable_atomic::Ordering::Relaxed);
}
#[interrupt]
#[allow(non_snake_case)]
fn OC1() {
critical_section::with(|cs| {
let mut sec = SEC_COUNTER.borrow(cs).get();
sec += 1;
SEC_COUNTER.borrow(cs).set(sec);
});
SEC_COUNTER.fetch_add(1, portable_atomic::Ordering::Relaxed);
}

View File

@ -13,27 +13,23 @@
use cortex_m_rt::entry;
use embedded_hal_nb::{nb, serial::Read};
use embedded_io::Write as _;
use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print};
use va108xx_hal::{gpio::PinsA, pac, prelude::*, uart};
// Import panic provider.
use panic_probe as _;
// Import logger.
use defmt_rtt as _;
use va108xx_hal::{pac, pins::PinsA, prelude::*, uart};
#[entry]
fn main() -> ! {
rtt_init_print!();
rprintln!("-- VA108xx UART example application--");
defmt::println!("-- VA108xx UART example application--");
let mut dp = pac::Peripherals::take().unwrap();
let dp = pac::Peripherals::take().unwrap();
let gpioa = PinsA::new(&mut dp.sysconfig, dp.porta);
let tx = gpioa.pa9.into_funsel_2();
let rx = gpioa.pa8.into_funsel_2();
let uart = uart::Uart::new_without_interrupt(
&mut dp.sysconfig,
50.MHz(),
dp.uarta,
(tx, rx),
115200.Hz(),
);
let gpioa = PinsA::new(dp.porta);
let tx = gpioa.pa9;
let rx = gpioa.pa8;
let uart =
uart::Uart::new_without_interrupt(dp.uarta, tx, rx, 50.MHz(), 115200.Hz().into()).unwrap();
let (mut tx, mut rx) = uart.split();
writeln!(tx, "Hello World\r").unwrap();

View File

@ -3,7 +3,7 @@
#![no_std]
use cortex_m_rt::entry;
use panic_rtt_target as _;
use panic_probe as _;
use va108xx_hal as _;
#[entry]

View File

@ -29,6 +29,7 @@ rtic-sync = {version = "1", features = ["defmt-03"]}
[dependencies.va108xx-hal]
version = "0.11"
path = "../va108xx-hal"
features = ["defmt"]
[dependencies.vorago-reb1]

View File

@ -2,16 +2,15 @@
from typing import List, Tuple
from spacepackets.ecss.defs import PusService
from spacepackets.ecss.tm import PusTm
from tmtccmd.com import ComInterface
import toml
import struct
import logging
import argparse
import time
import enum
from tmtccmd.com.serial_base import SerialCfg
from tmtccmd.com.serial_cobs import SerialCobsComIF
from tmtccmd.com.ser_utils import prompt_com_port
from com_interface import ComInterface
from com_interface.serial_base import SerialCfg
from com_interface.serial_cobs import SerialCobsComIF
from crcmod.predefined import PredefinedCrc
from spacepackets.ecss.tc import PusTc
from spacepackets.ecss.pus_verificator import PusVerificator, StatusField
@ -101,15 +100,7 @@ class ImageLoader:
)
self.verificator.add_tc(action_tc)
self.com_if.send(bytes(action_tc.pack()))
data_available = self.com_if.data_available(0.4)
if not data_available:
_LOGGER.warning("no reply received for boot image selection command")
for reply in self.com_if.receive():
result = self.verificator.add_tm(
Service1Tm.from_tm(PusTm.unpack(reply, 0), UnpackParams(0))
)
if result is not None and result.completed:
_LOGGER.info("received boot image selection command confirmation")
self.await_for_command_copletion("boot image selection command")
def handle_ping_cmd(self):
_LOGGER.info("Sending ping command")
@ -122,16 +113,26 @@ class ImageLoader:
)
self.verificator.add_tc(ping_tc)
self.com_if.send(bytes(ping_tc.pack()))
self.await_for_command_copletion("ping command")
data_available = self.com_if.data_available(0.4)
if not data_available:
_LOGGER.warning("no ping reply received")
for reply in self.com_if.receive():
result = self.verificator.add_tm(
Service1Tm.from_tm(PusTm.unpack(reply, 0), UnpackParams(0))
)
if result is not None and result.completed:
_LOGGER.info("received ping completion reply")
def await_for_command_copletion(self, context: str):
done = False
now = time.time()
while time.time() - now < 2.0:
if not self.com_if.data_available():
time.sleep(0.2)
continue
for reply in self.com_if.receive():
result = self.verificator.add_tm(
Service1Tm.from_tm(PusTm.unpack(reply, 0), UnpackParams(0))
)
if result is not None and result.completed:
_LOGGER.info(f"received {context} reply")
done = True
if done:
break
if not done:
_LOGGER.warning(f"no {context} reply received")
def handle_corruption_cmd(self, target: Target):
if target == Target.BOOTLOADER:
@ -300,12 +301,12 @@ def main() -> int:
if "serial_port" in parsed_toml:
serial_port = parsed_toml["serial_port"]
if serial_port is None:
serial_port = prompt_com_port()
serial_port = input("Please specify the serial port manually: ")
serial_cfg = SerialCfg(
com_if_id="ser_cobs",
serial_port=serial_port,
baud_rate=BAUD_RATE,
serial_timeout=0.1,
polling_frequency=0.1,
)
verificator = PusVerificator()
com_if = SerialCobsComIF(serial_cfg)

View File

@ -1 +1 @@
serial_port = "/dev/ttyUSB0"
serial_port = "/dev/ttyUSB1"

View File

@ -1,5 +1,5 @@
spacepackets == 0.24
tmtccmd == 8.0.2
spacepackets == 0.28
com-interface == 0.1
toml == 0.10
pyelftools == 0.31
crcmod == 1.7

View File

@ -68,7 +68,7 @@ mod app {
use spacepackets::ecss::{
tc::PusTcReader, tm::PusTmCreator, EcssEnumU8, PusPacket, WritablePusPacket,
};
use va108xx_hal::gpio::PinsA;
use va108xx_hal::pins::PinsA;
use va108xx_hal::uart::IrqContextTimeoutOrMaxSize;
use va108xx_hal::{pac, uart, InterruptConfig};
use vorago_reb1::m95m01::M95M01;
@ -83,8 +83,8 @@ mod app {
#[local]
struct Local {
uart_rx: uart::RxWithInterrupt<pac::Uarta>,
uart_tx: uart::Tx<pac::Uarta>,
uart_rx: uart::RxWithInterrupt,
uart_tx: uart::Tx,
rx_context: IrqContextTimeoutOrMaxSize,
verif_reporter: VerificationReportCreator,
nvm: M95M01,
@ -108,18 +108,19 @@ mod app {
let mut dp = cx.device;
let nvm = M95M01::new(&mut dp.sysconfig, SYSCLK_FREQ, dp.spic);
let gpioa = PinsA::new(&mut dp.sysconfig, dp.porta);
let tx = gpioa.pa9.into_funsel_2();
let rx = gpioa.pa8.into_funsel_2();
let gpioa = PinsA::new(dp.porta);
let tx = gpioa.pa9;
let rx = gpioa.pa8;
let irq_uart = uart::Uart::new_with_interrupt(
&mut dp.sysconfig,
SYSCLK_FREQ,
dp.uarta,
(tx, rx),
UART_BAUDRATE.Hz(),
tx,
rx,
SYSCLK_FREQ,
UART_BAUDRATE.Hz().into(),
InterruptConfig::new(pac::Interrupt::OC0, true, true),
);
)
.unwrap();
let (tx, rx) = irq_uart.split();
// Unwrap is okay, we explicitely set the interrupt ID.
let mut rx = rx.into_rx_with_irq();
@ -451,7 +452,7 @@ mod app {
cx.local.encoded_buf[send_size + 1] = 0;
cx.local
.uart_tx
.write(&cx.local.encoded_buf[0..send_size + 2])
.write_all(&cx.local.encoded_buf[0..send_size + 2])
.unwrap();
occupied_len -= 1;
Mono::delay(2.millis()).await;

View File

@ -11,21 +11,8 @@ keywords = ["no-std", "hal", "cortex-m", "vorago", "va108xx"]
categories = ["aerospace", "embedded", "no-std", "hardware-support"]
[dependencies]
critical-section = "1"
embassy-sync = "0.6"
embassy-executor = "0.7"
embassy-time-driver = "0.2"
embassy-time-queue-utils = "0.1"
once_cell = { version = "1", default-features = false, features = ["critical-section"] }
va108xx-hal = { version = ">=0.10, <=0.11" }
[target.'cfg(all(target_arch = "arm", target_os = "none"))'.dependencies]
portable-atomic = { version = "1", features = ["unsafe-assume-single-core"] }
[target.'cfg(not(all(target_arch = "arm", target_os = "none")))'.dependencies]
portable-atomic = "1"
vorago-shared-periphs = { git = "https://egit.irs.uni-stuttgart.de/rust/vorago-shared-periphs.git", features = ["vor1x"] }
va108xx-hal = { path = "../va108xx-hal" }
[features]
default = ["irq-oc30-oc31"]

View File

@ -32,29 +32,12 @@
//! [embassy example projects](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples/embassy)
#![no_std]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
use core::cell::{Cell, RefCell};
use critical_section::{CriticalSection, Mutex};
use portable_atomic::{AtomicU32, Ordering};
use embassy_time_driver::{time_driver_impl, Driver, TICK_HZ};
use embassy_time_queue_utils::Queue;
use once_cell::sync::OnceCell;
#[cfg(feature = "irqs-in-lib")]
use va108xx_hal::pac::interrupt;
use va108xx_hal::{
clock::enable_peripheral_clock,
enable_nvic_interrupt, pac,
prelude::*,
timer::{enable_tim_clk, get_tim_raw, TimRegInterface, ValidTim},
PeripheralSelect,
};
time_driver_impl!(
static TIME_DRIVER: TimerDriver = TimerDriver {
periods: AtomicU32::new(0),
alarms: Mutex::new(AlarmState::new()),
queue: Mutex::new(RefCell::new(Queue::new())),
});
use va108xx_hal::pac::{self, interrupt};
use va108xx_hal::time::Hertz;
use va108xx_hal::timer::TimMarker;
use vorago_shared_periphs::embassy::time_driver;
/// Macro to define the IRQ handlers for the time driver.
///
@ -99,311 +82,28 @@ embassy_time_driver_irqs!(timekeeper_irq = OC30, alarm_irq = OC29);
#[cfg(feature = "irq-oc28-oc29")]
embassy_time_driver_irqs!(timekeeper_irq = OC29, alarm_irq = OC28);
/// Expose the time driver so the user can specify the IRQ handlers themselves.
pub fn time_driver() -> &'static TimerDriver {
&TIME_DRIVER
}
/// Initialization method for embassy.
///
/// This should be used if the interrupt handler is provided by the library, which is the
/// default case.
#[cfg(feature = "irqs-in-lib")]
pub fn init<TimekeeperTim: TimRegInterface + ValidTim, AlarmTim: TimRegInterface + ValidTim>(
syscfg: &mut pac::Sysconfig,
irqsel: &pac::Irqsel,
sysclk: impl Into<Hertz>,
pub fn init<TimekeeperTim: TimMarker, AlarmTim: TimMarker>(
sysclk: Hertz,
timekeeper_tim: TimekeeperTim,
alarm_tim: AlarmTim,
) {
TIME_DRIVER.init(
syscfg,
irqsel,
sysclk,
timekeeper_tim,
alarm_tim,
TIMEKEEPER_IRQ,
ALARM_IRQ,
)
time_driver().__init(sysclk, timekeeper_tim, alarm_tim, TIMEKEEPER_IRQ, ALARM_IRQ)
}
/// Initialization method for embassy when using custom IRQ handlers.
///
/// Requires an explicit [pac::Interrupt] argument for the timekeeper and alarm IRQs.
pub fn init_with_custom_irqs<
TimekeeperTim: TimRegInterface + ValidTim,
AlarmTim: TimRegInterface + ValidTim,
>(
syscfg: &mut pac::Sysconfig,
irqsel: &pac::Irqsel,
sysclk: impl Into<Hertz>,
pub fn init_with_custom_irqs<TimekeeperTim: TimMarker, AlarmTim: TimMarker>(
sysclk: Hertz,
timekeeper_tim: TimekeeperTim,
alarm_tim: AlarmTim,
timekeeper_irq: pac::Interrupt,
alarm_irq: pac::Interrupt,
) {
TIME_DRIVER.init(
syscfg,
irqsel,
sysclk,
timekeeper_tim,
alarm_tim,
timekeeper_irq,
alarm_irq,
)
}
struct AlarmState {
timestamp: Cell<u64>,
}
impl AlarmState {
const fn new() -> Self {
Self {
timestamp: Cell::new(u64::MAX),
}
}
}
unsafe impl Send for AlarmState {}
static SCALE: OnceCell<u64> = OnceCell::new();
static TIMEKEEPER_TIM: OnceCell<u8> = OnceCell::new();
static ALARM_TIM: OnceCell<u8> = OnceCell::new();
pub struct TimerDriver {
periods: AtomicU32,
/// Timestamp at which to fire alarm. u64::MAX if no alarm is scheduled.
alarms: Mutex<AlarmState>,
queue: Mutex<RefCell<Queue>>,
}
impl TimerDriver {
#[allow(clippy::too_many_arguments)]
fn init<TimekeeperTim: TimRegInterface + ValidTim, AlarmTim: TimRegInterface + ValidTim>(
&self,
syscfg: &mut pac::Sysconfig,
irqsel: &pac::Irqsel,
sysclk: impl Into<Hertz>,
timekeeper_tim: TimekeeperTim,
alarm_tim: AlarmTim,
timekeeper_irq: pac::Interrupt,
alarm_irq: pac::Interrupt,
) {
if ALARM_TIM.get().is_some() || TIMEKEEPER_TIM.get().is_some() {
return;
}
ALARM_TIM.set(AlarmTim::TIM_ID).ok();
TIMEKEEPER_TIM.set(TimekeeperTim::TIM_ID).ok();
enable_peripheral_clock(syscfg, PeripheralSelect::Irqsel);
enable_tim_clk(syscfg, timekeeper_tim.tim_id());
let timekeeper_reg_block = timekeeper_tim.reg_block();
let alarm_tim_reg_block = alarm_tim.reg_block();
let sysclk = sysclk.into();
// Initiate scale value here. This is required to convert timer ticks back to a timestamp.
SCALE.set((sysclk.raw() / TICK_HZ as u32) as u64).unwrap();
timekeeper_reg_block
.rst_value()
.write(|w| unsafe { w.bits(u32::MAX) });
// Decrementing counter.
timekeeper_reg_block
.cnt_value()
.write(|w| unsafe { w.bits(u32::MAX) });
// Switch on. Timekeeping should always be done.
irqsel
.tim0(timekeeper_tim.tim_id() as usize)
.write(|w| unsafe { w.bits(timekeeper_irq as u32) });
unsafe {
enable_nvic_interrupt(timekeeper_irq);
}
timekeeper_reg_block
.ctrl()
.modify(|_, w| w.irq_enb().set_bit());
timekeeper_reg_block
.enable()
.write(|w| unsafe { w.bits(1) });
enable_tim_clk(syscfg, alarm_tim.tim_id());
// Explicitely disable alarm timer until needed.
alarm_tim_reg_block.ctrl().modify(|_, w| {
w.irq_enb().clear_bit();
w.enable().clear_bit()
});
// Enable general interrupts. The IRQ enable of the peripheral remains cleared.
unsafe {
enable_nvic_interrupt(alarm_irq);
}
irqsel
.tim0(alarm_tim.tim_id() as usize)
.write(|w| unsafe { w.bits(alarm_irq as u32) });
}
/// Should be called inside the IRQ of the timekeeper timer.
///
/// # Safety
///
/// This function has to be called once by the TIM IRQ used for the timekeeping.
pub unsafe fn on_interrupt_timekeeping(&self) {
self.next_period();
}
/// Should be called inside the IRQ of the alarm timer.
///
/// # Safety
///
///This function has to be called once by the TIM IRQ used for the timekeeping.
pub unsafe fn on_interrupt_alarm(&self) {
critical_section::with(|cs| {
if self.alarms.borrow(cs).timestamp.get() <= self.now() {
self.trigger_alarm(cs)
}
})
}
fn timekeeper_tim() -> &'static pac::tim0::RegisterBlock {
TIMEKEEPER_TIM
.get()
.map(|idx| unsafe { get_tim_raw(*idx as usize) })
.unwrap()
}
fn alarm_tim() -> &'static pac::tim0::RegisterBlock {
ALARM_TIM
.get()
.map(|idx| unsafe { get_tim_raw(*idx as usize) })
.unwrap()
}
fn next_period(&self) {
let period = self.periods.fetch_add(1, Ordering::AcqRel) + 1;
let t = (period as u64) << 32;
critical_section::with(|cs| {
let alarm = &self.alarms.borrow(cs);
let at = alarm.timestamp.get();
if at < t {
self.trigger_alarm(cs);
} else {
let alarm_tim = Self::alarm_tim();
let remaining_ticks = (at - t).checked_mul(*SCALE.get().unwrap());
if remaining_ticks.is_some_and(|v| v <= u32::MAX as u64) {
alarm_tim.enable().write(|w| unsafe { w.bits(0) });
alarm_tim
.cnt_value()
.write(|w| unsafe { w.bits(remaining_ticks.unwrap() as u32) });
alarm_tim.ctrl().modify(|_, w| w.irq_enb().set_bit());
alarm_tim.enable().write(|w| unsafe { w.bits(1) });
}
}
})
}
fn trigger_alarm(&self, cs: CriticalSection) {
Self::alarm_tim().ctrl().modify(|_, w| {
w.irq_enb().clear_bit();
w.enable().clear_bit()
});
let alarm = &self.alarms.borrow(cs);
// Setting the maximum value disables the alarm.
alarm.timestamp.set(u64::MAX);
// Call after clearing alarm, so the callback can set another alarm.
let mut next = self
.queue
.borrow(cs)
.borrow_mut()
.next_expiration(self.now());
while !self.set_alarm(cs, next) {
next = self
.queue
.borrow(cs)
.borrow_mut()
.next_expiration(self.now());
}
}
fn set_alarm(&self, cs: CriticalSection, timestamp: u64) -> bool {
if SCALE.get().is_none() {
return false;
}
let alarm_tim = Self::alarm_tim();
alarm_tim.ctrl().modify(|_, w| {
w.irq_enb().clear_bit();
w.enable().clear_bit()
});
let alarm = self.alarms.borrow(cs);
alarm.timestamp.set(timestamp);
let t = self.now();
if timestamp <= t {
alarm.timestamp.set(u64::MAX);
return false;
}
// If it hasn't triggered yet, setup the relevant reset value, regardless of whether
// the interrupts are enabled or not. When they are enabled at a later point, the
// right value is already set.
// If the timestamp is in the next few ticks, add a bit of buffer to be sure the alarm
// is not missed.
//
// This means that an alarm can be delayed for up to 2 ticks (from t+1 to t+3), but this is allowed
// by the Alarm trait contract. What's not allowed is triggering alarms *before* their scheduled time,
// and we don't do that here.
let safe_timestamp = timestamp.max(t + 3);
let timer_ticks = (safe_timestamp - t).checked_mul(*SCALE.get().unwrap());
alarm_tim.rst_value().write(|w| unsafe { w.bits(u32::MAX) });
if timer_ticks.is_some_and(|v| v <= u32::MAX as u64) {
alarm_tim
.cnt_value()
.write(|w| unsafe { w.bits(timer_ticks.unwrap() as u32) });
alarm_tim.ctrl().modify(|_, w| w.irq_enb().set_bit());
alarm_tim.enable().write(|w| unsafe { w.bits(1) });
}
// If it's too far in the future, don't enable timer yet.
// It will be enabled later by `next_period`.
true
}
}
impl Driver for TimerDriver {
fn now(&self) -> u64 {
if SCALE.get().is_none() {
return 0;
}
let mut period1: u32;
let mut period2: u32;
let mut counter_val: u32;
loop {
// Acquire ensures that we get the latest value of `periods` and
// no instructions can be reordered before the load.
period1 = self.periods.load(Ordering::Acquire);
counter_val = u32::MAX - Self::timekeeper_tim().cnt_value().read().bits();
// Double read to protect against race conditions when the counter is overflowing.
period2 = self.periods.load(Ordering::Relaxed);
if period1 == period2 {
let now = (((period1 as u64) << 32) | counter_val as u64) / *SCALE.get().unwrap();
return now;
}
}
}
fn schedule_wake(&self, at: u64, waker: &core::task::Waker) {
critical_section::with(|cs| {
let mut queue = self.queue.borrow(cs).borrow_mut();
if queue.schedule_wake(at, waker) {
let mut next = queue.next_expiration(self.now());
while !self.set_alarm(cs, next) {
next = queue.next_expiration(self.now());
}
}
})
}
time_driver().__init(sysclk, timekeeper_tim, alarm_tim, timekeeper_irq, alarm_irq)
}

View File

@ -8,6 +8,20 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
## [unreleased]
## Changed
- Move most library components to new [`vorago-shared-periphs`](https://egit.irs.uni-stuttgart.de/rust/vorago-shared-periphs)
which is mostly re-exported in this crate.
- Overhaul and simplification of several HAL APIs. The system configuration and IRQ router
peripheral instance generally does not need to be passed to HAL API anymore.
- All HAL drivers are now type erased. The constructors will still expect and consume the PAC
singleton component for resource management purposes, but are not cached anymore.
- Refactoring of GPIO library to be more inline with embassy GPIO API.
## Added
- I2C clock timeout feature support.
## [v0.11.1] 2025-03-10
## Fixed

View File

@ -15,6 +15,7 @@ cortex-m = { version = "0.7", features = ["critical-section-single-core"]}
cortex-m-rt = "0.7"
nb = "1"
paste = "1"
vorago-shared-periphs = { git = "https://egit.irs.uni-stuttgart.de/rust/vorago-shared-periphs.git", features = ["vor1x"] }
embedded-hal = "1"
embedded-hal-async = "1"
embedded-hal-nb = "1"
@ -42,7 +43,7 @@ portable-atomic = "1"
[features]
default = ["rt"]
rt = ["va108xx/rt"]
defmt = ["dep:defmt", "fugit/defmt", "embedded-hal/defmt-03"]
defmt = ["dep:defmt", "fugit/defmt", "embedded-hal/defmt-03", "vorago-shared-periphs/defmt"]
[package.metadata.docs.rs]
all-features = true

View File

@ -2,27 +2,14 @@
//!
//! This also includes functionality to enable the peripheral clocks
use crate::time::Hertz;
use crate::PeripheralSelect;
use cortex_m::interrupt::{self, Mutex};
use once_cell::unsync::OnceCell;
pub use vorago_shared_periphs::gpio::FilterClkSel;
pub use vorago_shared_periphs::sysconfig::{disable_peripheral_clock, enable_peripheral_clock};
static SYS_CLOCK: Mutex<OnceCell<Hertz>> = Mutex::new(OnceCell::new());
pub type PeripheralClocks = PeripheralSelect;
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum FilterClkSel {
SysClk = 0,
Clk1 = 1,
Clk2 = 2,
Clk3 = 3,
Clk4 = 4,
Clk5 = 5,
Clk6 = 6,
Clk7 = 7,
}
/// The Vorago in powered by an external clock which might have different frequencies.
/// The clock can be set here so it can be used by other software components as well.
/// The clock can be set exactly once
@ -63,17 +50,3 @@ pub fn set_clk_div_register(syscfg: &mut va108xx::Sysconfig, clk_sel: FilterClkS
}
}
}
#[inline]
pub fn enable_peripheral_clock(syscfg: &mut va108xx::Sysconfig, clock: PeripheralClocks) {
syscfg
.peripheral_clk_enable()
.modify(|r, w| unsafe { w.bits(r.bits() | (1 << clock as u8)) });
}
#[inline]
pub fn disable_peripheral_clock(syscfg: &mut va108xx::Sysconfig, clock: PeripheralClocks) {
syscfg
.peripheral_clk_enable()
.modify(|r, w| unsafe { w.bits(r.bits() & !(1 << clock as u8)) });
}

View File

@ -1,366 +0,0 @@
//! # Async GPIO functionality for the VA108xx family.
//!
//! This module provides the [InputPinAsync] and [InputDynPinAsync] which both implement
//! the [embedded_hal_async::digital::Wait] trait. These types allow for asynchronous waiting
//! on GPIO pins. Please note that this module does not specify/declare the interrupt handlers
//! which must be provided for async support to work. However, it provides the
//! [on_interrupt_for_async_gpio_for_port] generic interrupt handler. This should be called in all
//! IRQ functions which handle any GPIO interrupts with the corresponding [Port] argument.
//!
//! # Example
//!
//! - [Async GPIO example](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples/embassy/src/bin/async-gpio.rs)
use core::future::Future;
use embassy_sync::waitqueue::AtomicWaker;
use embedded_hal_async::digital::Wait;
use portable_atomic::AtomicBool;
use va108xx::{self as pac};
use crate::InterruptConfig;
use super::{
pin, DynPin, DynPinId, InputConfig, InterruptEdge, InvalidPinTypeError, Pin, PinId, Port,
NUM_PINS_PORT_A, NUM_PINS_PORT_B,
};
static WAKERS_FOR_PORT_A: [AtomicWaker; NUM_PINS_PORT_A] =
[const { AtomicWaker::new() }; NUM_PINS_PORT_A];
static WAKERS_FOR_PORT_B: [AtomicWaker; NUM_PINS_PORT_B] =
[const { AtomicWaker::new() }; NUM_PINS_PORT_B];
static EDGE_DETECTION_PORT_A: [AtomicBool; NUM_PINS_PORT_A] =
[const { AtomicBool::new(false) }; NUM_PINS_PORT_A];
static EDGE_DETECTION_PORT_B: [AtomicBool; NUM_PINS_PORT_B] =
[const { AtomicBool::new(false) }; NUM_PINS_PORT_B];
/// Generic interrupt handler for GPIO interrupts on a specific port to support async functionalities
///
/// This function should be called in all interrupt handlers which handle any GPIO interrupts
/// matching the [Port] argument.
/// The handler will wake the corresponding wakers for the pins that triggered an interrupts
/// as well as update the static edge detection structures. This allows the pin future tocomplete
/// complete async operations.
pub fn on_interrupt_for_async_gpio_for_port(port: Port) {
let periphs = unsafe { pac::Peripherals::steal() };
let (irq_enb, edge_status, wakers, edge_detection) = match port {
Port::A => (
periphs.porta.irq_enb().read().bits(),
periphs.porta.edge_status().read().bits(),
WAKERS_FOR_PORT_A.as_ref(),
EDGE_DETECTION_PORT_A.as_ref(),
),
Port::B => (
periphs.portb.irq_enb().read().bits(),
periphs.portb.edge_status().read().bits(),
WAKERS_FOR_PORT_B.as_ref(),
EDGE_DETECTION_PORT_B.as_ref(),
),
};
on_interrupt_for_port(irq_enb, edge_status, wakers, edge_detection);
}
#[inline]
fn on_interrupt_for_port(
mut irq_enb: u32,
edge_status: u32,
wakers: &'static [AtomicWaker],
edge_detection: &'static [AtomicBool],
) {
while irq_enb != 0 {
let bit_pos = irq_enb.trailing_zeros() as usize;
let bit_mask = 1 << bit_pos;
wakers[bit_pos].wake();
if edge_status & bit_mask != 0 {
edge_detection[bit_pos].store(true, core::sync::atomic::Ordering::Relaxed);
// Clear the processed bit
irq_enb &= !bit_mask;
}
}
}
/// Input pin future which implements the [Future] trait.
///
/// Generally, you want to use the [InputPinAsync] or [InputDynPinAsync] types instead of this
/// which also implements the [embedded_hal_async::digital::Wait] trait. However, access to this
/// struture is granted to allow writing custom async structures.
pub struct InputPinFuture {
pin_id: DynPinId,
waker_group: &'static [AtomicWaker],
edge_detection_group: &'static [AtomicBool],
}
impl InputPinFuture {
#[inline]
pub fn pin_group_to_waker_and_edge_detection_group(
group: Port,
) -> (&'static [AtomicWaker], &'static [AtomicBool]) {
match group {
Port::A => (WAKERS_FOR_PORT_A.as_ref(), EDGE_DETECTION_PORT_A.as_ref()),
Port::B => (WAKERS_FOR_PORT_B.as_ref(), EDGE_DETECTION_PORT_B.as_ref()),
}
}
pub fn new_with_dyn_pin(
pin: &mut DynPin,
irq: pac::Interrupt,
edge: InterruptEdge,
) -> Result<Self, InvalidPinTypeError> {
if !pin.is_input_pin() {
return Err(InvalidPinTypeError(pin.mode()));
}
let (waker_group, edge_detection_group) =
Self::pin_group_to_waker_and_edge_detection_group(pin.id().port());
edge_detection_group[pin.id().num() as usize]
.store(false, core::sync::atomic::Ordering::Relaxed);
pin.configure_edge_interrupt(edge).unwrap();
pin.enable_interrupt(InterruptConfig::new(irq, true, true));
Ok(Self {
pin_id: pin.id(),
waker_group,
edge_detection_group,
})
}
pub fn new_with_pin<I: PinId, C: InputConfig>(
pin: &mut Pin<I, pin::Input<C>>,
irq: pac::Interrupt,
edge: InterruptEdge,
) -> Self {
let (waker_group, edge_detection_group) =
Self::pin_group_to_waker_and_edge_detection_group(pin.id().port());
edge_detection_group[pin.id().num() as usize]
.store(false, core::sync::atomic::Ordering::Relaxed);
pin.configure_edge_interrupt(edge);
pin.enable_interrupt(InterruptConfig::new(irq, true, true));
Self {
pin_id: pin.id(),
edge_detection_group,
waker_group,
}
}
}
impl Drop for InputPinFuture {
fn drop(&mut self) {
// The API ensures that we actually own the pin, so stealing it here is okay.
unsafe { DynPin::steal(self.pin_id) }.disable_interrupt(false);
}
}
impl Future for InputPinFuture {
type Output = ();
fn poll(
self: core::pin::Pin<&mut Self>,
cx: &mut core::task::Context<'_>,
) -> core::task::Poll<Self::Output> {
let idx = self.pin_id.num() as usize;
self.waker_group[idx].register(cx.waker());
if self.edge_detection_group[idx].swap(false, core::sync::atomic::Ordering::Relaxed) {
return core::task::Poll::Ready(());
}
core::task::Poll::Pending
}
}
pub struct InputDynPinAsync {
pin: DynPin,
irq: pac::Interrupt,
}
impl InputDynPinAsync {
/// Create a new asynchronous input pin from a [DynPin]. The interrupt ID to be used must be
/// passed as well and is used to route and enable the interrupt.
///
/// Please note that the interrupt handler itself must be provided by the user and the
/// generic [on_interrupt_for_async_gpio_for_port] function must be called inside that function
/// for the asynchronous functionality to work.
pub fn new(pin: DynPin, irq: pac::Interrupt) -> Result<Self, InvalidPinTypeError> {
if !pin.is_input_pin() {
return Err(InvalidPinTypeError(pin.mode()));
}
Ok(Self { pin, irq })
}
/// Asynchronously wait until the pin is high.
///
/// This returns immediately if the pin is already high.
pub async fn wait_for_high(&mut self) {
// Unwrap okay, checked pin in constructor.
let fut =
InputPinFuture::new_with_dyn_pin(&mut self.pin, self.irq, InterruptEdge::LowToHigh)
.unwrap();
if self.pin.is_high().unwrap() {
return;
}
fut.await;
}
/// Asynchronously wait until the pin is low.
///
/// This returns immediately if the pin is already high.
pub async fn wait_for_low(&mut self) {
// Unwrap okay, checked pin in constructor.
let fut =
InputPinFuture::new_with_dyn_pin(&mut self.pin, self.irq, InterruptEdge::HighToLow)
.unwrap();
if self.pin.is_low().unwrap() {
return;
}
fut.await;
}
/// Asynchronously wait until the pin sees a falling edge.
pub async fn wait_for_falling_edge(&mut self) {
// Unwrap okay, checked pin in constructor.
InputPinFuture::new_with_dyn_pin(&mut self.pin, self.irq, InterruptEdge::HighToLow)
.unwrap()
.await;
}
/// Asynchronously wait until the pin sees a rising edge.
pub async fn wait_for_rising_edge(&mut self) {
// Unwrap okay, checked pin in constructor.
InputPinFuture::new_with_dyn_pin(&mut self.pin, self.irq, InterruptEdge::LowToHigh)
.unwrap()
.await;
}
/// Asynchronously wait until the pin sees any edge (either rising or falling).
pub async fn wait_for_any_edge(&mut self) {
// Unwrap okay, checked pin in constructor.
InputPinFuture::new_with_dyn_pin(&mut self.pin, self.irq, InterruptEdge::BothEdges)
.unwrap()
.await;
}
pub fn release(self) -> DynPin {
self.pin
}
}
impl embedded_hal::digital::ErrorType for InputDynPinAsync {
type Error = core::convert::Infallible;
}
impl Wait for InputDynPinAsync {
async fn wait_for_high(&mut self) -> Result<(), Self::Error> {
self.wait_for_high().await;
Ok(())
}
async fn wait_for_low(&mut self) -> Result<(), Self::Error> {
self.wait_for_low().await;
Ok(())
}
async fn wait_for_rising_edge(&mut self) -> Result<(), Self::Error> {
self.wait_for_rising_edge().await;
Ok(())
}
async fn wait_for_falling_edge(&mut self) -> Result<(), Self::Error> {
self.wait_for_falling_edge().await;
Ok(())
}
async fn wait_for_any_edge(&mut self) -> Result<(), Self::Error> {
self.wait_for_any_edge().await;
Ok(())
}
}
pub struct InputPinAsync<I: PinId, C: InputConfig> {
pin: Pin<I, pin::Input<C>>,
irq: pac::Interrupt,
}
impl<I: PinId, C: InputConfig> InputPinAsync<I, C> {
/// Create a new asynchronous input pin from a typed [Pin]. The interrupt ID to be used must be
/// passed as well and is used to route and enable the interrupt.
///
/// Please note that the interrupt handler itself must be provided by the user and the
/// generic [on_interrupt_for_async_gpio_for_port] function must be called inside that function
/// for the asynchronous functionality to work.
pub fn new(pin: Pin<I, pin::Input<C>>, irq: pac::Interrupt) -> Self {
Self { pin, irq }
}
/// Asynchronously wait until the pin is high.
///
/// This returns immediately if the pin is already high.
pub async fn wait_for_high(&mut self) {
let fut = InputPinFuture::new_with_pin(&mut self.pin, self.irq, InterruptEdge::LowToHigh);
if self.pin.is_high() {
return;
}
fut.await;
}
/// Asynchronously wait until the pin is low.
///
/// This returns immediately if the pin is already high.
pub async fn wait_for_low(&mut self) {
let fut = InputPinFuture::new_with_pin(&mut self.pin, self.irq, InterruptEdge::HighToLow);
if self.pin.is_low() {
return;
}
fut.await;
}
/// Asynchronously wait until the pin sees falling edge.
pub async fn wait_for_falling_edge(&mut self) {
// Unwrap okay, checked pin in constructor.
InputPinFuture::new_with_pin(&mut self.pin, self.irq, InterruptEdge::HighToLow).await;
}
/// Asynchronously wait until the pin sees rising edge.
pub async fn wait_for_rising_edge(&mut self) {
// Unwrap okay, checked pin in constructor.
InputPinFuture::new_with_pin(&mut self.pin, self.irq, InterruptEdge::LowToHigh).await;
}
/// Asynchronously wait until the pin sees any edge (either rising or falling).
pub async fn wait_for_any_edge(&mut self) {
InputPinFuture::new_with_pin(&mut self.pin, self.irq, InterruptEdge::BothEdges).await;
}
pub fn release(self) -> Pin<I, pin::Input<C>> {
self.pin
}
}
impl<I: PinId, C: InputConfig> embedded_hal::digital::ErrorType for InputPinAsync<I, C> {
type Error = core::convert::Infallible;
}
impl<I: PinId, C: InputConfig> Wait for InputPinAsync<I, C> {
async fn wait_for_high(&mut self) -> Result<(), Self::Error> {
self.wait_for_high().await;
Ok(())
}
async fn wait_for_low(&mut self) -> Result<(), Self::Error> {
self.wait_for_low().await;
Ok(())
}
async fn wait_for_rising_edge(&mut self) -> Result<(), Self::Error> {
self.wait_for_rising_edge().await;
Ok(())
}
async fn wait_for_falling_edge(&mut self) -> Result<(), Self::Error> {
self.wait_for_falling_edge().await;
Ok(())
}
async fn wait_for_any_edge(&mut self) -> Result<(), Self::Error> {
self.wait_for_any_edge().await;
Ok(())
}
}

View File

@ -1,947 +0,0 @@
//! # Type-erased, value-level module for GPIO pins
//!
//! Although the type-level API is generally preferred, it is not suitable in
//! all cases. Because each pin is represented by a distinct type, it is not
//! possible to store multiple pins in a homogeneous data structure. The
//! value-level API solves this problem by erasing the type information and
//! tracking the pin at run-time.
//!
//! Value-level pins are represented by the [`DynPin`] type. [`DynPin`] has two
//! fields, `id` and `mode` with types [`DynPinId`] and [`DynPinMode`]
//! respectively. The implementation of these types closely mirrors the
//! type-level API.
//!
//! Instances of [`DynPin`] cannot be created directly. Rather, they must be
//! created from their type-level equivalents using [`From`]/[`Into`].
//!
//! ```
//! // Move a pin out of the Pins struct and convert to a DynPin
//! let pa0: DynPin = pins.pa0.into();
//! ```
//!
//! Conversions between pin modes use a value-level version of the type-level
//! API.
//!
//! ```
//! // Use one of the literal function names
//! pa0.into_floating_input();
//! // Use a method and a DynPinMode variant
//! pa0.into_mode(DYN_FLOATING_INPUT);
//! ```
//!
//! Because the pin state cannot be tracked at compile-time, many [`DynPin`]
//! operations become fallible. Run-time checks are inserted to ensure that
//! users don't try to, for example, set the output level of an input pin.
//!
//! Users may try to convert value-level pins back to their type-level
//! equivalents. However, this option is fallible, because the compiler cannot
//! guarantee the pin has the correct ID or is in the correct mode at
//! compile-time. Use [TryFrom]/[TryInto] for this conversion.
//!
//! ```
//! // Convert to a `DynPin`
//! let pa0: DynPin = pins.pa0.into();
//! // Change pin mode
//! pa0.into_floating_input();
//! // Convert back to a `Pin`
//! let pa0: Pin<PA0, FloatingInput> = pa0.try_into().unwrap();
//! ```
//!
//! # Embedded HAL traits
//!
//! This module implements all of the embedded HAL GPIO traits for [`DynPin`].
//! However, whereas the type-level API uses
//! `Error = core::convert::Infallible`, the value-level API can return a real
//! error. If the [`DynPin`] is not in the correct [`DynPinMode`] for the
//! operation, the trait functions will return
//! [InvalidPinTypeError].
use super::{
pin::{FilterType, Pin, PinId, PinMode},
InputDynPinAsync, InterruptEdge, InterruptLevel, IsMaskedError, PinState, Port,
};
use crate::{clock::FilterClkSel, enable_nvic_interrupt, pac, FunSel};
//==================================================================================================
// DynPinMode configurations
//==================================================================================================
/// Value-level `enum` for disabled configurations
#[derive(PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum DynDisabled {
Floating,
PullDown,
PullUp,
}
/// Value-level `enum` for input configurations
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum DynInput {
Floating,
PullDown,
PullUp,
}
/// Value-level `enum` for output configurations
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum DynOutput {
PushPull,
OpenDrain,
ReadablePushPull,
ReadableOpenDrain,
}
pub type DynAlternate = FunSel;
//==============================================================================
// Error
//==============================================================================
/// GPIO error type
///
/// [`DynPin`]s are not tracked and verified at compile-time, so run-time
/// operations are fallible. This `enum` represents the corresponding errors.
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[error("Invalid pin type for operation: {0:?}")]
pub struct InvalidPinTypeError(pub DynPinMode);
impl embedded_hal::digital::Error for InvalidPinTypeError {
fn kind(&self) -> embedded_hal::digital::ErrorKind {
embedded_hal::digital::ErrorKind::Other
}
}
//==================================================================================================
// DynPinMode
//==================================================================================================
/// Value-level `enum` representing pin modes
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum DynPinMode {
Input(DynInput),
Output(DynOutput),
Alternate(DynAlternate),
}
/// Value-level variant of [`DynPinMode`] for floating input mode
pub const DYN_FLOATING_INPUT: DynPinMode = DynPinMode::Input(DynInput::Floating);
/// Value-level variant of [`DynPinMode`] for pull-down input mode
pub const DYN_PULL_DOWN_INPUT: DynPinMode = DynPinMode::Input(DynInput::PullDown);
/// Value-level variant of [`DynPinMode`] for pull-up input mode
pub const DYN_PULL_UP_INPUT: DynPinMode = DynPinMode::Input(DynInput::PullUp);
/// Value-level variant of [`DynPinMode`] for push-pull output mode
pub const DYN_PUSH_PULL_OUTPUT: DynPinMode = DynPinMode::Output(DynOutput::PushPull);
/// Value-level variant of [`DynPinMode`] for open-drain output mode
pub const DYN_OPEN_DRAIN_OUTPUT: DynPinMode = DynPinMode::Output(DynOutput::OpenDrain);
/// Value-level variant of [`DynPinMode`] for readable push-pull output mode
pub const DYN_RD_PUSH_PULL_OUTPUT: DynPinMode = DynPinMode::Output(DynOutput::ReadablePushPull);
/// Value-level variant of [`DynPinMode`] for readable opendrain output mode
pub const DYN_RD_OPEN_DRAIN_OUTPUT: DynPinMode = DynPinMode::Output(DynOutput::ReadableOpenDrain);
/// Value-level variant of [`DynPinMode`] for function select 1
pub const DYN_ALT_FUNC_1: DynPinMode = DynPinMode::Alternate(DynAlternate::Sel1);
/// Value-level variant of [`DynPinMode`] for function select 2
pub const DYN_ALT_FUNC_2: DynPinMode = DynPinMode::Alternate(DynAlternate::Sel2);
/// Value-level variant of [`DynPinMode`] for function select 3
pub const DYN_ALT_FUNC_3: DynPinMode = DynPinMode::Alternate(DynAlternate::Sel3);
//==================================================================================================
// DynGroup & DynPinId
//==================================================================================================
pub type DynGroup = Port;
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct DynPinId {
port: Port,
num: u8,
}
impl DynPinId {
pub const fn new(port: Port, num: u8) -> Self {
DynPinId { port, num }
}
pub const fn port(&self) -> Port {
self.port
}
pub const fn num(&self) -> u8 {
self.num
}
}
//==================================================================================================
// ModeFields
//==================================================================================================
/// Collect all fields needed to set the [`PinMode`](super::PinMode)
#[derive(Default)]
struct ModeFields {
dir: bool,
opendrn: bool,
pull_en: bool,
/// true for pullup, false for pulldown
pull_dir: bool,
funsel: u8,
enb_input: bool,
}
impl From<DynPinMode> for ModeFields {
#[inline]
fn from(mode: DynPinMode) -> Self {
let mut fields = Self::default();
match mode {
DynPinMode::Input(config) => {
fields.dir = false;
fields.funsel = FunSel::Sel0 as u8;
match config {
DynInput::Floating => (),
DynInput::PullUp => {
fields.pull_en = true;
fields.pull_dir = true;
}
DynInput::PullDown => {
fields.pull_en = true;
}
}
}
DynPinMode::Output(config) => {
fields.dir = true;
fields.funsel = FunSel::Sel0 as u8;
match config {
DynOutput::PushPull => (),
DynOutput::OpenDrain => {
fields.opendrn = true;
}
DynOutput::ReadableOpenDrain => {
fields.enb_input = true;
fields.opendrn = true;
}
DynOutput::ReadablePushPull => {
fields.enb_input = true;
}
}
}
DynPinMode::Alternate(config) => {
fields.funsel = config as u8;
}
}
fields
}
}
/// Type definition to avoid confusion: These register blocks are identical
type PortRegisterBlock = pac::porta::RegisterBlock;
pub type PortReg = pac::ioconfig::Porta;
//==================================================================================================
// DynPin
//==================================================================================================
/// A value-level pin, parameterized by [`DynPinId`] and [`DynPinMode`]
///
/// This type acts as a type-erased version of [`Pin`]. Every pin is represented
/// by the same type, and pins are tracked and distinguished at run-time.
#[derive(Debug)]
pub struct DynPin {
id: DynPinId,
mode: DynPinMode,
}
impl DynPin {
/// Create a new [DynPin]
///
/// # Safety
///
/// Each [DynPin] must be a singleton. For a given [DynPinId], there
/// must be at most one corresponding [`DynPin`] in existence at any given
/// time. Violating this requirement is `unsafe`.
#[inline]
pub(crate) const unsafe fn new(id: DynPinId, mode: DynPinMode) -> Self {
DynPin { id, mode }
}
/// Steals a new [DynPin].
///
/// This function will simply set the internal mode to [DYN_FLOATING_INPUT] pin without
/// modifying any registers related to the behaviour of the pin. The user should call
/// [Self::into_mode] to ensure the correct mode of the pin.
///
/// # Safety
///
/// Circumvents the HAL's safety guarantees. The caller must ensure that the pin is not
/// used cocurrently somewhere else. The caller might also want to call [Self::into_mode]
/// to ensure the correct desired state of the pin. It is recommended to create the pin using
/// [Pin::downgrade] instead.
pub const unsafe fn steal(id: DynPinId) -> Self {
DynPin {
id,
mode: DYN_FLOATING_INPUT,
}
}
/// Return a copy of the pin ID
#[inline]
pub const fn id(&self) -> DynPinId {
self.id
}
/// Return a copy of the pin mode
#[inline]
pub const fn mode(&self) -> DynPinMode {
self.mode
}
/// Convert the pin to the requested [`DynPinMode`]
#[inline]
pub fn into_mode(&mut self, mode: DynPinMode) {
self.change_mode(mode);
self.mode = mode;
}
#[inline]
pub fn is_input_pin(&self) -> bool {
matches!(self.mode, DynPinMode::Input(_))
}
#[inline]
pub fn is_output_pin(&self) -> bool {
matches!(self.mode, DynPinMode::Output(_))
}
#[inline]
pub fn into_funsel_1(&mut self) {
self.into_mode(DYN_ALT_FUNC_1);
}
#[inline]
pub fn into_funsel_2(&mut self) {
self.into_mode(DYN_ALT_FUNC_2);
}
#[inline]
pub fn into_funsel_3(&mut self) {
self.into_mode(DYN_ALT_FUNC_3);
}
/// Configure the pin to operate as a floating input
#[inline]
pub fn into_floating_input(&mut self) {
self.into_mode(DYN_FLOATING_INPUT);
}
/// Configure the pin to operate as a pulled down input
#[inline]
pub fn into_pull_down_input(&mut self) {
self.into_mode(DYN_PULL_DOWN_INPUT);
}
/// Configure the pin to operate as a pulled up input
#[inline]
pub fn into_pull_up_input(&mut self) {
self.into_mode(DYN_PULL_UP_INPUT);
}
/// Configure the pin to operate as a push-pull output
#[inline]
pub fn into_push_pull_output(&mut self) {
self.into_mode(DYN_PUSH_PULL_OUTPUT);
}
/// Configure the pin to operate as a push-pull output
#[inline]
pub fn into_open_drain_output(&mut self) {
self.into_mode(DYN_OPEN_DRAIN_OUTPUT);
}
/// Configure the pin to operate as a push-pull output
#[inline]
pub fn into_readable_push_pull_output(&mut self) {
self.into_mode(DYN_RD_PUSH_PULL_OUTPUT);
}
/// Configure the pin to operate as a push-pull output
#[inline]
pub fn into_readable_open_drain_output(&mut self) {
self.into_mode(DYN_RD_OPEN_DRAIN_OUTPUT);
}
#[inline(always)]
pub fn is_low(&self) -> Result<bool, InvalidPinTypeError> {
self.read_internal().map(|v| !v)
}
#[inline(always)]
pub fn is_high(&self) -> Result<bool, InvalidPinTypeError> {
self.read_internal()
}
#[inline(always)]
pub fn set_low(&mut self) -> Result<(), InvalidPinTypeError> {
self.write_internal(false)
}
#[inline(always)]
pub fn set_high(&mut self) -> Result<(), InvalidPinTypeError> {
self.write_internal(true)
}
/// Toggle the logic level of an output pin
#[inline(always)]
pub fn toggle(&mut self) -> Result<(), InvalidPinTypeError> {
if !self.is_output_pin() {
return Err(InvalidPinTypeError(self.mode));
}
// Safety: TOGOUT is a "mask" register, and we only write the bit for
// this pin ID
unsafe { self.port_reg().togout().write(|w| w.bits(self.mask_32())) };
Ok(())
}
pub fn enable_interrupt(&mut self, irq_cfg: crate::InterruptConfig) {
if irq_cfg.route {
self.configure_irqsel(irq_cfg.id);
}
if irq_cfg.enable_in_nvic {
unsafe { enable_nvic_interrupt(irq_cfg.id) };
}
// We only manipulate our own bit.
self.port_reg()
.irq_enb()
.modify(|r, w| unsafe { w.bits(r.bits() | self.mask_32()) });
}
pub fn disable_interrupt(&mut self, reset_irqsel: bool) {
if reset_irqsel {
self.reset_irqsel();
}
// We only manipulate our own bit.
self.port_reg()
.irq_enb()
.modify(|r, w| unsafe { w.bits(r.bits() & !self.mask_32()) });
}
/// Try to recreate a type-level [`Pin`] from a value-level [`DynPin`]
///
/// There is no way for the compiler to know if the conversion will be
/// successful at compile-time. We must verify the conversion at run-time
/// or refuse to perform it.
#[inline]
pub fn upgrade<I: PinId, M: PinMode>(self) -> Result<Pin<I, M>, InvalidPinTypeError> {
if self.id == I::DYN && self.mode == M::DYN {
// The `DynPin` is consumed, so it is safe to replace it with the
// corresponding `Pin`
return Ok(unsafe { Pin::new() });
}
Err(InvalidPinTypeError(self.mode))
}
/// Convert the pin into an async pin. The pin can be converted back by calling
/// [InputDynPinAsync::release]
pub fn into_async_input(
self,
irq: crate::pac::Interrupt,
) -> Result<InputDynPinAsync, InvalidPinTypeError> {
InputDynPinAsync::new(self, irq)
}
/// Configure the IRQSEL peripheral for this particular pin with the given interrupt ID.
pub fn configure_irqsel(&mut self, id: pac::Interrupt) {
let mut syscfg = unsafe { pac::Sysconfig::steal() };
let irqsel = unsafe { pac::Irqsel::steal() };
crate::clock::enable_peripheral_clock(&mut syscfg, crate::clock::PeripheralClocks::Irqsel);
match self.id().port() {
// Set the correct interrupt number in the IRQSEL register
super::Port::A => {
irqsel
.porta0(self.id().num() as usize)
.write(|w| unsafe { w.bits(id as u32) });
}
super::Port::B => {
irqsel
.portb0(self.id().num as usize)
.write(|w| unsafe { w.bits(id as u32) });
}
}
}
/// Reset the IRQSEL peripheral value for this particular pin.
pub fn reset_irqsel(&mut self) {
let mut syscfg = unsafe { pac::Sysconfig::steal() };
let irqsel = unsafe { pac::Irqsel::steal() };
crate::clock::enable_peripheral_clock(&mut syscfg, crate::clock::PeripheralClocks::Irqsel);
match self.id().port() {
// Set the correct interrupt number in the IRQSEL register
super::Port::A => {
irqsel
.porta0(self.id().num() as usize)
.write(|w| unsafe { w.bits(u32::MAX) });
}
super::Port::B => {
irqsel
.portb0(self.id().num as usize)
.write(|w| unsafe { w.bits(u32::MAX) });
}
}
}
// Get DATAMASK bit for this particular pin
#[inline(always)]
pub fn datamask(&self) -> bool {
(self.port_reg().datamask().read().bits() >> self.id().num) == 1
}
/// Clear DATAMASK bit for this particular pin. This prevents access
/// of the corresponding bit for output and input operations
#[inline(always)]
pub fn clear_datamask(&self) {
self.port_reg()
.datamask()
.modify(|r, w| unsafe { w.bits(r.bits() & !self.mask_32()) });
}
/// Set DATAMASK bit for this particular pin. 1 is the default
/// state of the bit and allows access of the corresponding bit
#[inline(always)]
pub fn set_datamask(&self) {
self.port_reg()
.datamask()
.modify(|r, w| unsafe { w.bits(r.bits() | self.mask_32()) });
}
#[inline]
pub fn is_high_masked(&self) -> Result<bool, crate::gpio::IsMaskedError> {
self.read_pin_masked()
}
#[inline]
pub fn is_low_masked(&self) -> Result<bool, crate::gpio::IsMaskedError> {
self.read_pin_masked().map(|v| !v)
}
#[inline]
pub fn set_high_masked(&mut self) -> Result<(), crate::gpio::IsMaskedError> {
self.write_pin_masked(true)
}
#[inline]
pub fn set_low_masked(&mut self) -> Result<(), crate::gpio::IsMaskedError> {
self.write_pin_masked(false)
}
/// Possible delays in clock cycles:
/// - Delay 1: 1
/// - Delay 2: 2
/// - Delay 1 + Delay 2: 3
#[inline]
pub fn configure_delay(
&mut self,
delay_1: bool,
delay_2: bool,
) -> Result<(), InvalidPinTypeError> {
match self.mode {
DynPinMode::Output(_) => {
self.configure_delay_internal(delay_1, delay_2);
Ok(())
}
_ => Err(InvalidPinTypeError(self.mode)),
}
}
/// When configured for pulse mode, a given pin will set the non-default state for exactly
/// one clock cycle before returning to the configured default state
#[inline]
pub fn configure_pulse_mode(
&mut self,
enable: bool,
default_state: PinState,
) -> Result<(), InvalidPinTypeError> {
match self.mode {
DynPinMode::Output(_) => {
self.configure_pulse_mode_internal(enable, default_state);
Ok(())
}
_ => Err(InvalidPinTypeError(self.mode)),
}
}
/// See p.37 and p.38 of the programmers guide for more information.
#[inline]
pub fn configure_filter_type(
&mut self,
filter: FilterType,
clksel: FilterClkSel,
) -> Result<(), InvalidPinTypeError> {
match self.mode {
DynPinMode::Input(_) => {
self.configure_filter_type_internal(filter, clksel);
Ok(())
}
_ => Err(InvalidPinTypeError(self.mode)),
}
}
pub fn configure_edge_interrupt(
&mut self,
edge_type: InterruptEdge,
) -> Result<(), InvalidPinTypeError> {
match self.mode {
DynPinMode::Input(_) | DynPinMode::Output(_) => {
self.configure_edge_interrupt_internal(edge_type);
Ok(())
}
_ => Err(InvalidPinTypeError(self.mode)),
}
}
pub fn configure_level_interrupt(
&mut self,
level_type: InterruptLevel,
) -> Result<(), InvalidPinTypeError> {
match self.mode {
DynPinMode::Input(_) | DynPinMode::Output(_) => {
self.configure_level_interrupt_internal(level_type);
Ok(())
}
_ => Err(InvalidPinTypeError(self.mode)),
}
}
/// Change the pin mode
#[inline]
pub(crate) fn change_mode(&mut self, mode: DynPinMode) {
let ModeFields {
dir,
funsel,
opendrn,
pull_dir,
pull_en,
enb_input,
} = mode.into();
let (portreg, iocfg) = (self.port_reg(), self.iocfg_port());
iocfg.write(|w| {
w.opendrn().bit(opendrn);
w.pen().bit(pull_en);
w.plevel().bit(pull_dir);
w.iewo().bit(enb_input);
unsafe { w.funsel().bits(funsel) }
});
let mask = self.mask_32();
unsafe {
if dir {
portreg.dir().modify(|r, w| w.bits(r.bits() | mask));
// Clear output
portreg.clrout().write(|w| w.bits(mask));
} else {
portreg.dir().modify(|r, w| w.bits(r.bits() & !mask));
}
}
}
#[inline]
const fn port_reg(&self) -> &PortRegisterBlock {
match self.id().port() {
Port::A => unsafe { &(*pac::Porta::ptr()) },
Port::B => unsafe { &(*pac::Portb::ptr()) },
}
}
#[inline]
const fn iocfg_port(&self) -> &PortReg {
let ioconfig = unsafe { va108xx::Ioconfig::ptr().as_ref().unwrap() };
match self.id().port() {
Port::A => ioconfig.porta(self.id().num() as usize),
Port::B => ioconfig.portb0(self.id().num() as usize),
}
}
#[inline(always)]
fn read_internal(&self) -> Result<bool, InvalidPinTypeError> {
match self.mode {
DynPinMode::Input(_) | DYN_RD_OPEN_DRAIN_OUTPUT | DYN_RD_PUSH_PULL_OUTPUT => {
Ok(self.read_pin())
}
_ => Err(InvalidPinTypeError(self.mode)),
}
}
#[inline(always)]
fn write_internal(&mut self, bit: bool) -> Result<(), InvalidPinTypeError> {
match self.mode {
DynPinMode::Output(_) => {
self.write_pin(bit);
Ok(())
}
_ => Err(InvalidPinTypeError(self.mode)),
}
}
#[inline]
/// Read the logic level of an output pin
pub(crate) fn read_pin(&self) -> bool {
let portreg = self.port_reg();
((portreg.datainraw().read().bits() >> self.id().num) & 0x01) == 1
}
/// Read a pin but use the masked version but check whether the datamask for the pin is
/// cleared as well
#[inline(always)]
fn read_pin_masked(&self) -> Result<bool, IsMaskedError> {
if !self.datamask() {
Err(IsMaskedError)
} else {
Ok(((self.port_reg().datain().read().bits() >> self.id().num) & 0x01) == 1)
}
}
/// Write the logic level of an output pin
#[inline(always)]
pub(crate) fn write_pin(&mut self, bit: bool) {
// Safety: SETOUT is a "mask" register, and we only write the bit for
// this pin ID
unsafe {
if bit {
self.port_reg().setout().write(|w| w.bits(self.mask_32()));
} else {
self.port_reg().clrout().write(|w| w.bits(self.mask_32()));
}
}
}
/// Write the logic level of an output pin but check whether the datamask for the pin is
/// cleared as well
#[inline]
fn write_pin_masked(&mut self, bit: bool) -> Result<(), IsMaskedError> {
if !self.datamask() {
Err(IsMaskedError)
} else {
// Safety: SETOUT is a "mask" register, and we only write the bit for
// this pin ID
unsafe {
if bit {
self.port_reg().setout().write(|w| w.bits(self.mask_32()));
} else {
self.port_reg().clrout().write(|w| w.bits(self.mask_32()));
}
Ok(())
}
}
}
/// Toggle the logic level of an output pin
#[inline(always)]
pub fn toggle_with_togout_reg(&mut self) {
// Safety: TOGOUT is a "mask" register, and we only write the bit for
// this pin ID
unsafe { self.port_reg().togout().write(|w| w.bits(self.mask_32())) };
}
/// Only useful for interrupt pins. Configure whether to use edges or level as interrupt soure
/// When using edge mode, it is possible to generate interrupts on both edges as well
#[inline]
fn configure_edge_interrupt_internal(&mut self, edge_type: InterruptEdge) {
unsafe {
self.port_reg()
.irq_sen()
.modify(|r, w| w.bits(r.bits() & !self.mask_32()));
match edge_type {
InterruptEdge::HighToLow => {
self.port_reg()
.irq_evt()
.modify(|r, w| w.bits(r.bits() & !self.mask_32()));
}
InterruptEdge::LowToHigh => {
self.port_reg()
.irq_evt()
.modify(|r, w| w.bits(r.bits() | self.mask_32()));
}
InterruptEdge::BothEdges => {
self.port_reg()
.irq_edge()
.modify(|r, w| w.bits(r.bits() | self.mask_32()));
}
}
}
}
/// Configure which edge or level type triggers an interrupt
#[inline]
fn configure_level_interrupt_internal(&mut self, level: InterruptLevel) {
unsafe {
self.port_reg()
.irq_sen()
.modify(|r, w| w.bits(r.bits() | self.mask_32()));
if level == InterruptLevel::Low {
self.port_reg()
.irq_evt()
.modify(|r, w| w.bits(r.bits() & !self.mask_32()));
} else {
self.port_reg()
.irq_evt()
.modify(|r, w| w.bits(r.bits() | self.mask_32()));
}
}
}
/// Only useful for input pins
#[inline]
fn configure_filter_type_internal(&mut self, filter: FilterType, clksel: FilterClkSel) {
self.iocfg_port().modify(|_, w| {
// Safety: Only write to register for this Pin ID
unsafe {
w.flttype().bits(filter as u8);
w.fltclk().bits(clksel as u8)
}
});
}
#[inline]
fn configure_pulse_mode_internal(&mut self, enable: bool, default_state: PinState) {
let portreg = self.port_reg();
unsafe {
if enable {
portreg
.pulse()
.modify(|r, w| w.bits(r.bits() | self.mask_32()));
} else {
portreg
.pulse()
.modify(|r, w| w.bits(r.bits() & !self.mask_32()));
}
if default_state == PinState::Low {
portreg
.pulsebase()
.modify(|r, w| w.bits(r.bits() & !self.mask_32()));
} else {
portreg
.pulsebase()
.modify(|r, w| w.bits(r.bits() | self.mask_32()));
}
}
}
/// Only useful for output pins
#[inline]
fn configure_delay_internal(&mut self, delay_1: bool, delay_2: bool) {
let portreg = self.port_reg();
unsafe {
if delay_1 {
portreg
.delay1()
.modify(|r, w| w.bits(r.bits() | self.mask_32()));
} else {
portreg
.delay1()
.modify(|r, w| w.bits(r.bits() & !self.mask_32()));
}
if delay_2 {
portreg
.delay2()
.modify(|r, w| w.bits(r.bits() | self.mask_32()));
} else {
portreg
.delay2()
.modify(|r, w| w.bits(r.bits() & !self.mask_32()));
}
}
}
// Only serves disambiguation purposes for the Embedded HAL impl
#[inline(always)]
fn is_low_mut(&mut self) -> Result<bool, InvalidPinTypeError> {
self.is_low()
}
// Only serves disambiguation purposes for the Embedded HAL impl
#[inline(always)]
fn is_high_mut(&mut self) -> Result<bool, InvalidPinTypeError> {
self.is_high()
}
#[inline(always)]
const fn mask_32(&self) -> u32 {
1 << self.id().num()
}
}
//==================================================================================================
// Convert between Pin and DynPin
//==================================================================================================
impl<I: PinId, M: PinMode> From<Pin<I, M>> for DynPin {
/// Erase the type-level information in a [`Pin`] and return a value-level
/// [`DynPin`]
#[inline]
fn from(pin: Pin<I, M>) -> Self {
pin.downgrade()
}
}
impl<I: PinId, M: PinMode> TryFrom<DynPin> for Pin<I, M> {
type Error = InvalidPinTypeError;
/// Try to recreate a type-level [`Pin`] from a value-level [`DynPin`]
///
/// There is no way for the compiler to know if the conversion will be
/// successful at compile-time. We must verify the conversion at run-time
/// or refuse to perform it.
#[inline]
fn try_from(pin: DynPin) -> Result<Self, Self::Error> {
pin.upgrade()
}
}
//==================================================================================================
// Embedded HAL traits
//==================================================================================================
impl embedded_hal::digital::ErrorType for DynPin {
type Error = InvalidPinTypeError;
}
impl embedded_hal::digital::OutputPin for DynPin {
#[inline]
fn set_high(&mut self) -> Result<(), Self::Error> {
self.set_high()
}
#[inline]
fn set_low(&mut self) -> Result<(), Self::Error> {
self.set_low()
}
}
impl embedded_hal::digital::InputPin for DynPin {
#[inline]
fn is_high(&mut self) -> Result<bool, Self::Error> {
self.is_high_mut()
}
#[inline]
fn is_low(&mut self) -> Result<bool, Self::Error> {
self.is_low_mut()
}
}
impl embedded_hal::digital::StatefulOutputPin for DynPin {
#[inline]
fn is_set_high(&mut self) -> Result<bool, Self::Error> {
self.is_high_mut()
}
#[inline]
fn is_set_low(&mut self) -> Result<bool, Self::Error> {
self.is_low_mut()
}
#[inline]
fn toggle(&mut self) -> Result<(), Self::Error> {
self.toggle()
}
}

View File

@ -1,74 +1,20 @@
//! # API for the GPIO peripheral
//! GPIO support module.
//!
//! The implementation of this GPIO module is heavily based on the
//! [ATSAMD HAL implementation](https://docs.rs/atsamd-hal/latest/atsamd_hal/gpio/index.html).
//! Contains abstractions to use the pins provided by the [crate::pins] module as GPIO or
//! IO peripheral pins.
//!
//! This API provides two different submodules, [pin] and [dynpin],
//! representing two different ways to handle GPIO pins. The default, [pin],
//! is a type-level API that tracks the state of each pin at compile-time. The
//! alternative, [dynpin] is a type-erased, value-level API that tracks the
//! state of each pin at run-time.
//! The core data structures provided for this are the
//!
//! The type-level API is strongly preferred. By representing the state of each
//! pin within the type system, the compiler can detect logic errors at
//! compile-time. Furthermore, the type-level API has absolutely zero run-time
//! cost.
//! - [Output] for push-pull output pins.
//! - [Input] for input pins.
//! - [Flex] for pins with flexible configuration requirements.
//! - [IoPeriphPin] for IO peripheral pins.
//!
//! If needed, [dynpin] can be used to erase the type-level differences
//! between pins. However, by doing so, pins must now be tracked at run-time,
//! and each pin has a non-zero memory footprint.
//! The [crate::pins] module exposes singletons to access the [Pin]s required by this module
//! in a type-safe way.
//!
//! ## Examples
//!
//! - [Blinky example](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples/simple/examples/blinky.rs)
//==================================================================================================
// Errors, Definitions and Constants
//==================================================================================================
pub const NUM_PINS_PORT_A: usize = 32;
pub const NUM_PINS_PORT_B: usize = 24;
pub const NUM_GPIO_PINS: usize = NUM_PINS_PORT_A + NUM_PINS_PORT_B;
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[error("The pin is masked")]
pub struct IsMaskedError;
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Port {
A,
B,
}
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum InterruptEdge {
HighToLow,
LowToHigh,
BothEdges,
}
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum InterruptLevel {
Low = 0,
High = 1,
}
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum PinState {
Low = 0,
High = 1,
}
pub mod dynpin;
pub use dynpin::*;
pub mod pin;
pub use pin::*;
pub mod asynch;
pub use asynch::*;
//! - [Async GPIO example](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples/embassy/src/bin/async-gpio.rs)
pub use vorago_shared_periphs::gpio::*;

View File

@ -1,823 +0,0 @@
//! # Type-level module for GPIO pins
//!
//! This documentation is strongly based on the
//! [atsamd documentation](https://docs.rs/atsamd-hal/latest/atsamd_hal/gpio/pin/index.html).
//!
//! This module provides a type-level API for GPIO pins. It uses the type system
//! to track the state of pins at compile-time. Representing GPIO pins in this
//! manner incurs no run-time overhead. Each [`Pin`] struct is zero-sized, so
//! there is no data to copy around. Instead, real code is generated as a side
//! effect of type transformations, and the resulting assembly is nearly
//! identical to the equivalent, hand-written C.
//!
//! To track the state of pins at compile-time, this module uses traits to
//! represent [type classes] and types as instances of those type classes. For
//! example, the trait [`InputConfig`] acts as a [type-level enum] of the
//! available input configurations, and the types [`Floating`], [`PullDown`] and
//! [`PullUp`] are its type-level variants.
//!
//! Type-level [`Pin`]s are parameterized by two type-level enums, [`PinId`] and
//! [`PinMode`].
//!
//! ```
//! pub struct Pin<I, M>
//! where
//! I: PinId,
//! M: PinMode,
//! {
//! // ...
//! }
//! ```
//!
//! A [PinId] identifies a pin by it's group (A or B) and pin number. Each
//! [PinId] instance is named according to its datasheet identifier, e.g.
//! [PA2].
//!
//! A [PinMode] represents the various pin modes. The available [PinMode]
//! variants are [`Input`], [`Output`] and [`Alternate`], each with its own
//! corresponding configurations.
//!
//! It is not possible for users to create new instances of a [`Pin`]. Singleton
//! instances of each pin are made available to users through the PinsX
//! struct.
//!
//! Example for the pins of PORT A:
//!
//! To create the [PinsA] struct, users must supply the PAC
//! [Port](crate::pac::Porta) peripheral. The [PinsA] struct takes
//! ownership of the [Porta] and provides the corresponding pins. Each [`Pin`]
//! within the [PinsA] struct can be moved out and used individually.
//!
//!
//! ```
//! let mut peripherals = Peripherals::take().unwrap();
//! let pinsa = PinsA::new(peripherals.PORT);
//! ```
//!
//! Pins can be converted between modes using several different methods.
//!
//! ```no_run
//! // Use one of the literal function names
//! let pa0 = pinsa.pa0.into_floating_input();
//! // Use a generic method and one of the `PinMode` variant types
//! let pa0 = pinsa.pa0.into_mode::<FloatingInput>();
//! // Specify the target type and use `From`/`Into`
//! let pa0: Pin<PA0, FloatingInput> = pinsa.pa27.into();
//! ```
//!
//! # Embedded HAL traits
//!
//! This module implements all of the embedded HAL GPIO traits for each [`Pin`]
//! in the corresponding [`PinMode`]s, namely: [embedded_hal::digital::InputPin],
//! [embedded_hal::digital::OutputPin] and [embedded_hal::digital::StatefulOutputPin].
use super::dynpin::{DynAlternate, DynInput, DynOutput, DynPinId, DynPinMode};
use super::{DynPin, InputPinAsync, InterruptEdge, InterruptLevel, PinState, Port};
use crate::{
pac::{Porta, Portb},
typelevel::Sealed,
};
use core::convert::Infallible;
use core::marker::PhantomData;
use core::mem::transmute;
use paste::paste;
//==================================================================================================
// Input configuration
//==================================================================================================
/// Type-level enum for input configurations
///
/// The valid options are [Floating], [PullDown] and [PullUp].
pub trait InputConfig: Sealed {
/// Corresponding [DynInput]
const DYN: DynInput;
}
#[derive(Debug)]
pub enum Floating {}
#[derive(Debug)]
pub enum PullDown {}
#[derive(Debug)]
pub enum PullUp {}
impl InputConfig for Floating {
const DYN: DynInput = DynInput::Floating;
}
impl InputConfig for PullDown {
const DYN: DynInput = DynInput::PullDown;
}
impl InputConfig for PullUp {
const DYN: DynInput = DynInput::PullUp;
}
impl Sealed for Floating {}
impl Sealed for PullDown {}
impl Sealed for PullUp {}
/// Type-level variant of [`PinMode`] for floating input mode
pub type InputFloating = Input<Floating>;
/// Type-level variant of [`PinMode`] for pull-down input mode
pub type InputPullDown = Input<PullDown>;
/// Type-level variant of [`PinMode`] for pull-up input mode
pub type InputPullUp = Input<PullUp>;
/// Type-level variant of [`PinMode`] for input modes
///
/// Type `C` is one of three input configurations: [`Floating`], [`PullDown`] or
/// [`PullUp`]
#[derive(Debug)]
pub struct Input<C: InputConfig> {
cfg: PhantomData<C>,
}
impl<C: InputConfig> Sealed for Input<C> {}
#[derive(Debug, PartialEq, Eq)]
pub enum FilterType {
SystemClock = 0,
DirectInputWithSynchronization = 1,
FilterOneClockCycle = 2,
FilterTwoClockCycles = 3,
FilterThreeClockCycles = 4,
FilterFourClockCycles = 5,
}
pub use crate::clock::FilterClkSel;
//==================================================================================================
// Output configuration
//==================================================================================================
pub trait OutputConfig: Sealed {
const DYN: DynOutput;
}
pub trait ReadableOutput: Sealed {}
/// Type-level variant of [`OutputConfig`] for a push-pull configuration
#[derive(Debug)]
pub enum PushPull {}
/// Type-level variant of [`OutputConfig`] for an open drain configuration
#[derive(Debug)]
pub enum OpenDrain {}
/// Type-level variant of [`OutputConfig`] for a readable push-pull configuration
#[derive(Debug)]
pub enum ReadablePushPull {}
/// Type-level variant of [`OutputConfig`] for a readable open-drain configuration
#[derive(Debug)]
pub enum ReadableOpenDrain {}
impl Sealed for PushPull {}
impl Sealed for OpenDrain {}
impl Sealed for ReadableOpenDrain {}
impl Sealed for ReadablePushPull {}
impl ReadableOutput for ReadableOpenDrain {}
impl ReadableOutput for ReadablePushPull {}
impl OutputConfig for PushPull {
const DYN: DynOutput = DynOutput::PushPull;
}
impl OutputConfig for OpenDrain {
const DYN: DynOutput = DynOutput::OpenDrain;
}
impl OutputConfig for ReadablePushPull {
const DYN: DynOutput = DynOutput::ReadablePushPull;
}
impl OutputConfig for ReadableOpenDrain {
const DYN: DynOutput = DynOutput::ReadableOpenDrain;
}
/// Type-level variant of [`PinMode`] for output modes
///
/// Type `C` is one of four output configurations: [`PushPull`], [`OpenDrain`] or
/// their respective readable versions
#[derive(Debug)]
pub struct Output<C: OutputConfig> {
cfg: PhantomData<C>,
}
impl<C: OutputConfig> Sealed for Output<C> {}
/// Type-level variant of [`PinMode`] for push-pull output mode
pub type PushPullOutput = Output<PushPull>;
/// Type-level variant of [`PinMode`] for open drain output mode
pub type OutputOpenDrain = Output<OpenDrain>;
pub type OutputReadablePushPull = Output<ReadablePushPull>;
pub type OutputReadableOpenDrain = Output<ReadableOpenDrain>;
//==================================================================================================
// Alternate configurations
//==================================================================================================
/// Type-level enum for alternate peripheral function configurations
pub trait AlternateConfig: Sealed {
const DYN: DynAlternate;
}
pub enum Funsel1 {}
pub enum Funsel2 {}
pub enum Funsel3 {}
impl AlternateConfig for Funsel1 {
const DYN: DynAlternate = DynAlternate::Sel1;
}
impl AlternateConfig for Funsel2 {
const DYN: DynAlternate = DynAlternate::Sel2;
}
impl AlternateConfig for Funsel3 {
const DYN: DynAlternate = DynAlternate::Sel3;
}
impl Sealed for Funsel1 {}
impl Sealed for Funsel2 {}
impl Sealed for Funsel3 {}
/// Type-level variant of [`PinMode`] for alternate peripheral functions
///
/// Type `C` is an [`AlternateConfig`]
pub struct Alternate<C: AlternateConfig> {
cfg: PhantomData<C>,
}
impl<C: AlternateConfig> Sealed for Alternate<C> {}
pub type AltFunc1 = Alternate<Funsel1>;
pub type AltFunc2 = Alternate<Funsel2>;
pub type AltFunc3 = Alternate<Funsel3>;
/// Type alias for the [`PinMode`] at reset
pub type Reset = InputFloating;
//==================================================================================================
// Pin modes
//==================================================================================================
/// Type-level enum representing pin modes
///
/// The valid options are [Input], [Output] and [Alternate].
pub trait PinMode: Sealed {
/// Corresponding [DynPinMode]
const DYN: DynPinMode;
}
impl<C: InputConfig> PinMode for Input<C> {
const DYN: DynPinMode = DynPinMode::Input(C::DYN);
}
impl<C: OutputConfig> PinMode for Output<C> {
const DYN: DynPinMode = DynPinMode::Output(C::DYN);
}
impl<C: AlternateConfig> PinMode for Alternate<C> {
const DYN: DynPinMode = DynPinMode::Alternate(C::DYN);
}
//==================================================================================================
// Pin IDs
//==================================================================================================
/// Type-level enum for pin IDs
pub trait PinId: Sealed {
/// Corresponding [DynPinId]
const DYN: DynPinId;
}
macro_rules! pin_id {
($Group:ident, $Id:ident, $NUM:literal) => {
// Need paste macro to use ident in doc attribute
paste! {
#[doc = "Pin ID representing pin " $Id]
#[derive(Debug)]
pub enum $Id {}
impl Sealed for $Id {}
impl PinId for $Id {
const DYN: DynPinId = DynPinId::new(Port::$Group, $NUM);
}
}
};
}
//==================================================================================================
// Pin
//==================================================================================================
/// A type-level GPIO pin, parameterized by [PinId] and [PinMode] types
#[derive(Debug)]
pub struct Pin<I: PinId, M: PinMode> {
inner: DynPin,
phantom: PhantomData<(I, M)>,
}
impl<I: PinId, M: PinMode> Pin<I, M> {
/// Create a new [Pin]
///
/// # Safety
///
/// Each [Pin] must be a singleton. For a given [PinId], there must be
/// at most one corresponding [Pin] in existence at any given time.
/// Violating this requirement is `unsafe`.
#[inline]
pub(crate) const unsafe fn new() -> Pin<I, M> {
Pin {
inner: DynPin::new(I::DYN, M::DYN),
phantom: PhantomData,
}
}
#[inline]
pub const fn id(&self) -> DynPinId {
self.inner.id()
}
/// Convert the pin to the requested [`PinMode`]
#[inline]
pub fn into_mode<N: PinMode>(mut self) -> Pin<I, N> {
// Only modify registers if we are actually changing pin mode
// This check should compile away
if N::DYN != M::DYN {
self.inner.change_mode(N::DYN);
}
// Safe because we drop the existing Pin
unsafe { Pin::new() }
}
/// Configure the pin for function select 1. See Programmer Guide p.40 for the function table
#[inline]
pub fn into_funsel_1(self) -> Pin<I, AltFunc1> {
self.into_mode()
}
/// Configure the pin for function select 2. See Programmer Guide p.40 for the function table
#[inline]
pub fn into_funsel_2(self) -> Pin<I, AltFunc2> {
self.into_mode()
}
/// Configure the pin for function select 3. See Programmer Guide p.40 for the function table
#[inline]
pub fn into_funsel_3(self) -> Pin<I, AltFunc3> {
self.into_mode()
}
/// Configure the pin to operate as a floating input
#[inline]
pub fn into_floating_input(self) -> Pin<I, InputFloating> {
self.into_mode()
}
/// Configure the pin to operate as a pulled down input
#[inline]
pub fn into_pull_down_input(self) -> Pin<I, InputPullDown> {
self.into_mode()
}
/// Configure the pin to operate as a pulled up input
#[inline]
pub fn into_pull_up_input(self) -> Pin<I, InputPullUp> {
self.into_mode()
}
/// Configure the pin to operate as a push-pull output
#[inline]
pub fn into_push_pull_output(self) -> Pin<I, PushPullOutput> {
self.into_mode()
}
/// Configure the pin to operate as a readable push-pull output
#[inline]
pub fn into_readable_push_pull_output(self) -> Pin<I, OutputReadablePushPull> {
self.into_mode()
}
/// Configure the pin to operate as a readable open-drain output
#[inline]
pub fn into_readable_open_drain_output(self) -> Pin<I, OutputReadableOpenDrain> {
self.into_mode()
}
#[inline]
pub fn is_low(&self) -> bool {
!self.inner.read_pin()
}
#[inline]
pub fn is_high(&self) -> bool {
self.inner.read_pin()
}
#[inline]
pub fn datamask(&self) -> bool {
self.inner.datamask()
}
#[inline]
pub fn clear_datamask(&mut self) {
self.inner.clear_datamask()
}
#[inline]
pub fn set_datamask(&mut self) {
self.inner.set_datamask()
}
#[inline]
pub fn is_high_masked(&self) -> Result<bool, crate::gpio::IsMaskedError> {
self.inner.is_high_masked()
}
#[inline]
pub fn is_low_masked(&self) -> Result<bool, crate::gpio::IsMaskedError> {
self.inner.is_low_masked()
}
#[inline]
pub fn downgrade(self) -> DynPin {
self.inner
}
// Those only serve for the embedded HAL implementations which have different mutability.
#[inline]
fn is_low_mut(&mut self) -> bool {
self.is_low()
}
#[inline]
fn is_high_mut(&mut self) -> bool {
self.is_high()
}
#[inline]
pub fn enable_interrupt(&mut self, irq_cfg: crate::InterruptConfig) {
self.inner.enable_interrupt(irq_cfg);
}
#[inline]
pub fn disable_interrupt(&mut self, reset_irqsel: bool) {
self.inner.disable_interrupt(reset_irqsel);
}
/// Configure the pin for an edge interrupt but does not enable the interrupt.
pub fn configure_edge_interrupt(&mut self, edge_type: InterruptEdge) {
self.inner.configure_edge_interrupt(edge_type).unwrap();
}
/// Configure the pin for a level interrupt but does not enable the interrupt.
pub fn configure_level_interrupt(&mut self, level_type: InterruptLevel) {
self.inner.configure_level_interrupt(level_type).unwrap();
}
}
//==============================================================================
// AnyPin
//==============================================================================
/// Type class for [`Pin`] types
///
/// This trait uses the [`AnyKind`] trait pattern to create a [type class] for
/// [`Pin`] types. See the `AnyKind` documentation for more details on the
/// pattern.
///
/// ## `v1` Compatibility
///
/// Normally, this trait would use `Is<Type = SpecificPin<Self>>` as a super
/// trait. But doing so would restrict implementations to only the `v2` `Pin`
/// type in this module. To aid in backwards compatibility, we want to implement
/// `AnyPin` for the `v1` `Pin` type as well. This is possible for a few
/// reasons. First, both structs are zero-sized, so there is no meaningful
/// memory layout to begin with. And even if there were, the `v1` `Pin` type is
/// a newtype wrapper around a `v2` `Pin`, and single-field structs are
/// guaranteed to have the same layout as the field, even for `repr(Rust)`.
///
/// [`AnyKind`]: crate::typelevel#anykind-trait-pattern
/// [type class]: crate::typelevel#type-classes
pub trait AnyPin
where
Self: Sealed,
Self: From<SpecificPin<Self>>,
Self: Into<SpecificPin<Self>>,
Self: AsRef<SpecificPin<Self>>,
Self: AsMut<SpecificPin<Self>>,
{
/// [`PinId`] of the corresponding [`Pin`]
type Id: PinId;
/// [`PinMode`] of the corresponding [`Pin`]
type Mode: PinMode;
}
impl<I, M> Sealed for Pin<I, M>
where
I: PinId,
M: PinMode,
{
}
impl<I, M> AnyPin for Pin<I, M>
where
I: PinId,
M: PinMode,
{
type Id = I;
type Mode = M;
}
/// Type alias to recover the specific [`Pin`] type from an implementation of
/// [`AnyPin`]
///
/// See the [`AnyKind`] documentation for more details on the pattern.
///
/// [`AnyKind`]: crate::typelevel#anykind-trait-pattern
pub type SpecificPin<P> = Pin<<P as AnyPin>::Id, <P as AnyPin>::Mode>;
impl<P: AnyPin> AsRef<P> for SpecificPin<P> {
#[inline]
fn as_ref(&self) -> &P {
// SAFETY: This is guaranteed to be safe, because P == SpecificPin<P>
// Transmuting between `v1` and `v2` `Pin` types is also safe, because
// both are zero-sized, and single-field, newtype structs are guaranteed
// to have the same layout as the field anyway, even for repr(Rust).
unsafe { transmute(self) }
}
}
impl<P: AnyPin> AsMut<P> for SpecificPin<P> {
#[inline]
fn as_mut(&mut self) -> &mut P {
// SAFETY: This is guaranteed to be safe, because P == SpecificPin<P>
// Transmuting between `v1` and `v2` `Pin` types is also safe, because
// both are zero-sized, and single-field, newtype structs are guaranteed
// to have the same layout as the field anyway, even for repr(Rust).
unsafe { transmute(self) }
}
}
//==================================================================================================
// Additional functionality
//==================================================================================================
impl<I: PinId, C: InputConfig> Pin<I, Input<C>> {
/// Convert the pin into an async pin. The pin can be converted back by calling
/// [InputPinAsync::release]
pub fn into_async_input(self, irq: crate::pac::Interrupt) -> InputPinAsync<I, C> {
InputPinAsync::new(self, irq)
}
}
impl<I: PinId, C: OutputConfig> Pin<I, Output<C>> {
#[inline]
pub fn set_high(&mut self) {
self.inner.write_pin(true)
}
#[inline]
pub fn set_low(&mut self) {
self.inner.write_pin(false)
}
#[inline]
pub fn toggle(&mut self) {
self.inner.toggle().unwrap()
}
#[inline]
pub fn set_high_masked(&mut self) -> Result<(), crate::gpio::IsMaskedError> {
self.inner.set_high_masked()
}
#[inline]
pub fn set_low_masked(&mut self) -> Result<(), crate::gpio::IsMaskedError> {
self.inner.set_low_masked()
}
/// See p.53 of the programmers guide for more information.
/// Possible delays in clock cycles:
/// - Delay 1: 1
/// - Delay 2: 2
/// - Delay 1 + Delay 2: 3
#[inline]
pub fn configure_delay(&mut self, delay_1: bool, delay_2: bool) {
self.inner.configure_delay(delay_1, delay_2).unwrap();
}
/// See p.52 of the programmers guide for more information.
///
/// When configured for pulse mode, a given pin will set the non-default state for exactly
/// one clock cycle before returning to the configured default state
pub fn configure_pulse_mode(&mut self, enable: bool, default_state: PinState) {
self.inner
.configure_pulse_mode(enable, default_state)
.unwrap();
}
}
impl<I: PinId, C: InputConfig> Pin<I, Input<C>> {
/// See p.37 and p.38 of the programmers guide for more information.
#[inline]
pub fn configure_filter_type(&mut self, filter: FilterType, clksel: FilterClkSel) {
self.inner.configure_filter_type(filter, clksel).unwrap();
}
}
//==================================================================================================
// Embedded HAL traits
//==================================================================================================
impl<I, M> embedded_hal::digital::ErrorType for Pin<I, M>
where
I: PinId,
M: PinMode,
{
type Error = Infallible;
}
impl<I: PinId, C: OutputConfig> embedded_hal::digital::OutputPin for Pin<I, Output<C>> {
#[inline]
fn set_high(&mut self) -> Result<(), Self::Error> {
self.set_high();
Ok(())
}
#[inline]
fn set_low(&mut self) -> Result<(), Self::Error> {
self.set_low();
Ok(())
}
}
impl<I, C> embedded_hal::digital::InputPin for Pin<I, Input<C>>
where
I: PinId,
C: InputConfig,
{
#[inline]
fn is_high(&mut self) -> Result<bool, Self::Error> {
Ok(self.is_high_mut())
}
#[inline]
fn is_low(&mut self) -> Result<bool, Self::Error> {
Ok(self.is_low_mut())
}
}
impl<I, C> embedded_hal::digital::StatefulOutputPin for Pin<I, Output<C>>
where
I: PinId,
C: OutputConfig + ReadableOutput,
{
#[inline]
fn is_set_high(&mut self) -> Result<bool, Self::Error> {
Ok(self.is_high())
}
#[inline]
fn is_set_low(&mut self) -> Result<bool, Self::Error> {
Ok(self.is_low())
}
#[inline]
fn toggle(&mut self) -> Result<(), Self::Error> {
self.toggle();
Ok(())
}
}
//==================================================================================================
// Pin definitions
//==================================================================================================
macro_rules! pins {
(
$Port:ident, $PinsName:ident, $($Id:ident,)+,
) => {
paste!(
/// Collection of all the individual [`Pin`]s for a given port (PORTA or PORTB)
#[derive(Debug)]
pub struct $PinsName {
port: $Port,
$(
#[doc = "Pin " $Id]
pub [<$Id:lower>]: Pin<$Id, Reset>,
)+
}
impl $PinsName {
/// Create a new struct containing all the Pins. Passing the IOCONFIG peripheral
/// is optional because it might be required to create pin definitions for both
/// ports.
#[inline]
pub fn new(
syscfg: &mut va108xx::Sysconfig,
port: $Port
) -> $PinsName {
syscfg.peripheral_clk_enable().modify(|_, w| {
w.[<$Port:lower>]().set_bit();
w.gpio().set_bit();
w.ioconfig().set_bit()
});
$PinsName {
//iocfg,
port,
// Safe because we only create one `Pin` per `PinId`
$(
[<$Id:lower>]: unsafe { Pin::new() },
)+
}
}
/// Get the peripheral ID
/// Safety: Read-only register
pub fn get_perid() -> u32 {
let port = unsafe { &(*$Port::ptr()) };
port.perid().read().bits()
}
/// Consumes the Pins struct and returns the port definitions
pub fn release(self) -> $Port {
self.port
}
}
);
}
}
macro_rules! declare_pins {
(
$Group:ident, $PinsName:ident, $Port:ident, [$(($Id:ident, $NUM:literal),)+]
) => {
pins!($Port, $PinsName, $($Id,)+,);
$(
pin_id!($Group, $Id, $NUM);
)+
}
}
declare_pins!(
A,
PinsA,
Porta,
[
(PA0, 0),
(PA1, 1),
(PA2, 2),
(PA3, 3),
(PA4, 4),
(PA5, 5),
(PA6, 6),
(PA7, 7),
(PA8, 8),
(PA9, 9),
(PA10, 10),
(PA11, 11),
(PA12, 12),
(PA13, 13),
(PA14, 14),
(PA15, 15),
(PA16, 16),
(PA17, 17),
(PA18, 18),
(PA19, 19),
(PA20, 20),
(PA21, 21),
(PA22, 22),
(PA23, 23),
(PA24, 24),
(PA25, 25),
(PA26, 26),
(PA27, 27),
(PA28, 28),
(PA29, 29),
(PA30, 30),
(PA31, 31),
]
);
declare_pins!(
B,
PinsB,
Portb,
[
(PB0, 0),
(PB1, 1),
(PB2, 2),
(PB3, 3),
(PB4, 4),
(PB5, 5),
(PB6, 6),
(PB7, 7),
(PB8, 8),
(PB9, 9),
(PB10, 10),
(PB11, 11),
(PB12, 12),
(PB13, 13),
(PB14, 14),
(PB15, 15),
(PB16, 16),
(PB17, 17),
(PB18, 18),
(PB19, 19),
(PB20, 20),
(PB21, 21),
(PB22, 22),
(PB23, 23),
]
);

View File

@ -3,899 +3,4 @@
//! ## Examples
//!
//! - [REB1 I2C temperature sensor example](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/vorago-reb1/examples/adt75-temp-sensor.rs)
use crate::{
clock::enable_peripheral_clock, pac, time::Hertz, typelevel::Sealed, PeripheralSelect,
};
use core::{marker::PhantomData, ops::Deref};
use embedded_hal::i2c::{self, Operation, SevenBitAddress, TenBitAddress};
//==================================================================================================
// Defintions
//==================================================================================================
const CLK_100K: Hertz = Hertz::from_raw(100_000);
const CLK_400K: Hertz = Hertz::from_raw(400_000);
const MIN_CLK_400K: Hertz = Hertz::from_raw(8_000_000);
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum FifoEmptyMode {
Stall = 0,
EndTransaction = 1,
}
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[error("clock too slow for fast I2C mode")]
pub struct ClockTooSlowForFastI2cError;
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
#[error("invalid timing parameters")]
pub struct InvalidTimingParamsError;
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Error {
#[error("arbitration lost")]
ArbitrationLost,
#[error("nack address")]
NackAddr,
/// Data not acknowledged in write operation
#[error("data not acknowledged in write operation")]
NackData,
/// Not enough data received in read operation
#[error("insufficient data received")]
InsufficientDataReceived,
/// Number of bytes in transfer too large (larger than 0x7fe)
#[error("data too large (larger than 0x7fe)")]
DataTooLarge,
}
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum InitError {
/// Wrong address used in constructor
#[error("wrong address mode")]
WrongAddrMode,
/// APB1 clock is too slow for fast I2C mode.
#[error("clock too slow for fast I2C mode: {0}")]
ClkTooSlow(#[from] ClockTooSlowForFastI2cError),
}
impl embedded_hal::i2c::Error for Error {
fn kind(&self) -> embedded_hal::i2c::ErrorKind {
match self {
Error::ArbitrationLost => embedded_hal::i2c::ErrorKind::ArbitrationLoss,
Error::NackAddr => {
embedded_hal::i2c::ErrorKind::NoAcknowledge(i2c::NoAcknowledgeSource::Address)
}
Error::NackData => {
embedded_hal::i2c::ErrorKind::NoAcknowledge(i2c::NoAcknowledgeSource::Data)
}
Error::DataTooLarge | Error::InsufficientDataReceived => {
embedded_hal::i2c::ErrorKind::Other
}
}
}
}
#[derive(Debug, PartialEq, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
enum I2cCmd {
Start = 0b00,
Stop = 0b10,
StartWithStop = 0b11,
Cancel = 0b100,
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum I2cSpeed {
Regular100khz = 0,
Fast400khz = 1,
}
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum I2cDirection {
Send = 0,
Read = 1,
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum I2cAddress {
Regular(u8),
TenBit(u16),
}
pub type I2cRegBlock = pac::i2ca::RegisterBlock;
/// Common trait implemented by all PAC peripheral access structures. The register block
/// format is the same for all SPI blocks.
pub trait Instance: Deref<Target = I2cRegBlock> {
const IDX: u8;
const PERIPH_SEL: PeripheralSelect;
fn ptr() -> *const I2cRegBlock;
}
impl Instance for pac::I2ca {
const IDX: u8 = 0;
const PERIPH_SEL: PeripheralSelect = PeripheralSelect::I2c0;
#[inline(always)]
fn ptr() -> *const I2cRegBlock {
Self::ptr()
}
}
impl Instance for pac::I2cb {
const IDX: u8 = 1;
const PERIPH_SEL: PeripheralSelect = PeripheralSelect::I2c1;
#[inline(always)]
fn ptr() -> *const I2cRegBlock {
Self::ptr()
}
}
//==================================================================================================
// Config
//==================================================================================================
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct TrTfThighTlow(u8, u8, u8, u8);
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct TsuStoTsuStaThdStaTBuf(u8, u8, u8, u8);
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct TimingCfg {
// 4 bit max width
tr: u8,
// 4 bit max width
tf: u8,
// 4 bit max width
thigh: u8,
// 4 bit max width
tlow: u8,
// 4 bit max width
tsu_sto: u8,
// 4 bit max width
tsu_sta: u8,
// 4 bit max width
thd_sta: u8,
// 4 bit max width
tbuf: u8,
}
impl TimingCfg {
pub fn new(
first_16_bits: TrTfThighTlow,
second_16_bits: TsuStoTsuStaThdStaTBuf,
) -> Result<Self, InvalidTimingParamsError> {
if first_16_bits.0 > 0xf
|| first_16_bits.1 > 0xf
|| first_16_bits.2 > 0xf
|| first_16_bits.3 > 0xf
|| second_16_bits.0 > 0xf
|| second_16_bits.1 > 0xf
|| second_16_bits.2 > 0xf
|| second_16_bits.3 > 0xf
{
return Err(InvalidTimingParamsError);
}
Ok(TimingCfg {
tr: first_16_bits.0,
tf: first_16_bits.1,
thigh: first_16_bits.2,
tlow: first_16_bits.3,
tsu_sto: second_16_bits.0,
tsu_sta: second_16_bits.1,
thd_sta: second_16_bits.2,
tbuf: second_16_bits.3,
})
}
pub fn reg(&self) -> u32 {
((self.tbuf as u32) << 28)
| ((self.thd_sta as u32) << 24)
| ((self.tsu_sta as u32) << 20)
| ((self.tsu_sto as u32) << 16)
| ((self.tlow as u32) << 12)
| ((self.thigh as u32) << 8)
| ((self.tf as u32) << 4)
| (self.tr as u32)
}
}
impl Default for TimingCfg {
fn default() -> Self {
TimingCfg {
tr: 0x02,
tf: 0x01,
thigh: 0x08,
tlow: 0x09,
tsu_sto: 0x8,
tsu_sta: 0x0a,
thd_sta: 0x8,
tbuf: 0xa,
}
}
}
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct MasterConfig {
pub tx_fe_mode: FifoEmptyMode,
pub rx_fe_mode: FifoEmptyMode,
/// Enable the analog delay glitch filter
pub alg_filt: bool,
/// Enable the digital glitch filter
pub dlg_filt: bool,
pub tm_cfg: Option<TimingCfg>,
// Loopback mode
// lbm: bool,
}
impl Default for MasterConfig {
fn default() -> Self {
MasterConfig {
tx_fe_mode: FifoEmptyMode::Stall,
rx_fe_mode: FifoEmptyMode::Stall,
alg_filt: false,
dlg_filt: false,
tm_cfg: None,
}
}
}
impl Sealed for MasterConfig {}
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct SlaveConfig {
pub tx_fe_mode: FifoEmptyMode,
pub rx_fe_mode: FifoEmptyMode,
/// Maximum number of words before issuing a negative acknowledge.
/// Range should be 0 to 0x7fe. Setting the value to 0x7ff has the same effect as not setting
/// the enable bit since RXCOUNT stops counting at 0x7fe.
pub max_words: Option<usize>,
/// A received address is compared to the ADDRESS register (addr) using the address mask
/// (addr_mask). Those bits with a 1 in the address mask must match for there to be an address
/// match
pub addr: I2cAddress,
/// The default address mask will be 0x3ff to only allow full matches
pub addr_mask: Option<u16>,
/// Optionally specify a second I2C address the slave interface responds to
pub addr_b: Option<I2cAddress>,
pub addr_b_mask: Option<u16>,
}
impl SlaveConfig {
/// Build a default slave config given a specified slave address to respond to
pub fn new(addr: I2cAddress) -> Self {
SlaveConfig {
tx_fe_mode: FifoEmptyMode::Stall,
rx_fe_mode: FifoEmptyMode::Stall,
max_words: None,
addr,
addr_mask: None,
addr_b: None,
addr_b_mask: None,
}
}
}
impl Sealed for SlaveConfig {}
//==================================================================================================
// I2C Base
//==================================================================================================
pub struct I2cBase<I2C> {
i2c: I2C,
sys_clk: Hertz,
}
impl<I2C> I2cBase<I2C> {
#[inline]
fn unwrap_addr(addr: I2cAddress) -> (u16, u32) {
match addr {
I2cAddress::Regular(addr) => (addr as u16, 0 << 15),
I2cAddress::TenBit(addr) => (addr, 1 << 15),
}
}
}
impl<I2c: Instance> I2cBase<I2c> {
pub fn new(
syscfg: &mut pac::Sysconfig,
sysclk: impl Into<Hertz>,
i2c: I2c,
speed_mode: I2cSpeed,
ms_cfg: Option<&MasterConfig>,
sl_cfg: Option<&SlaveConfig>,
) -> Result<Self, ClockTooSlowForFastI2cError> {
enable_peripheral_clock(syscfg, I2c::PERIPH_SEL);
let mut i2c_base = I2cBase {
i2c,
sys_clk: sysclk.into(),
};
if let Some(ms_cfg) = ms_cfg {
i2c_base.cfg_master(ms_cfg);
}
if let Some(sl_cfg) = sl_cfg {
i2c_base.cfg_slave(sl_cfg);
}
i2c_base.cfg_clk_scale(speed_mode)?;
Ok(i2c_base)
}
fn cfg_master(&mut self, ms_cfg: &MasterConfig) {
let (txfemd, rxfemd) = match (ms_cfg.tx_fe_mode, ms_cfg.rx_fe_mode) {
(FifoEmptyMode::Stall, FifoEmptyMode::Stall) => (false, false),
(FifoEmptyMode::Stall, FifoEmptyMode::EndTransaction) => (false, true),
(FifoEmptyMode::EndTransaction, FifoEmptyMode::Stall) => (true, false),
(FifoEmptyMode::EndTransaction, FifoEmptyMode::EndTransaction) => (true, true),
};
self.i2c.ctrl().modify(|_, w| {
w.txfemd().bit(txfemd);
w.rxffmd().bit(rxfemd);
w.dlgfilter().bit(ms_cfg.dlg_filt);
w.algfilter().bit(ms_cfg.alg_filt)
});
if let Some(ref tm_cfg) = ms_cfg.tm_cfg {
self.i2c
.tmconfig()
.write(|w| unsafe { w.bits(tm_cfg.reg()) });
}
self.i2c.fifo_clr().write(|w| {
w.rxfifo().set_bit();
w.txfifo().set_bit()
});
}
fn cfg_slave(&mut self, sl_cfg: &SlaveConfig) {
let (txfemd, rxfemd) = match (sl_cfg.tx_fe_mode, sl_cfg.rx_fe_mode) {
(FifoEmptyMode::Stall, FifoEmptyMode::Stall) => (false, false),
(FifoEmptyMode::Stall, FifoEmptyMode::EndTransaction) => (false, true),
(FifoEmptyMode::EndTransaction, FifoEmptyMode::Stall) => (true, false),
(FifoEmptyMode::EndTransaction, FifoEmptyMode::EndTransaction) => (true, true),
};
self.i2c.s0_ctrl().modify(|_, w| {
w.txfemd().bit(txfemd);
w.rxffmd().bit(rxfemd)
});
self.i2c.s0_fifo_clr().write(|w| {
w.rxfifo().set_bit();
w.txfifo().set_bit()
});
let max_words = sl_cfg.max_words;
if let Some(max_words) = max_words {
self.i2c
.s0_maxwords()
.write(|w| unsafe { w.bits((1 << 31) | max_words as u32) });
}
let (addr, addr_mode_mask) = Self::unwrap_addr(sl_cfg.addr);
// The first bit is the read/write value. Normally, both read and write are matched
// using the RWMASK bit of the address mask register
self.i2c
.s0_address()
.write(|w| unsafe { w.bits((addr << 1) as u32 | addr_mode_mask) });
if let Some(addr_mask) = sl_cfg.addr_mask {
self.i2c
.s0_addressmask()
.write(|w| unsafe { w.bits((addr_mask << 1) as u32) });
}
if let Some(addr_b) = sl_cfg.addr_b {
let (addr, addr_mode_mask) = Self::unwrap_addr(addr_b);
self.i2c
.s0_addressb()
.write(|w| unsafe { w.bits((addr << 1) as u32 | addr_mode_mask) });
}
if let Some(addr_b_mask) = sl_cfg.addr_b_mask {
self.i2c
.s0_addressmaskb()
.write(|w| unsafe { w.bits((addr_b_mask << 1) as u32) });
}
}
#[inline]
pub fn filters(&mut self, digital_filt: bool, analog_filt: bool) {
self.i2c.ctrl().modify(|_, w| {
w.dlgfilter().bit(digital_filt);
w.algfilter().bit(analog_filt)
});
}
#[inline]
pub fn fifo_empty_mode(&mut self, rx: FifoEmptyMode, tx: FifoEmptyMode) {
self.i2c.ctrl().modify(|_, w| {
w.txfemd().bit(tx as u8 != 0);
w.rxffmd().bit(rx as u8 != 0)
});
}
fn calc_clk_div(&self, speed_mode: I2cSpeed) -> Result<u8, ClockTooSlowForFastI2cError> {
if speed_mode == I2cSpeed::Regular100khz {
Ok(((self.sys_clk.raw() / CLK_100K.raw() / 20) - 1) as u8)
} else {
if self.sys_clk.raw() < MIN_CLK_400K.raw() {
return Err(ClockTooSlowForFastI2cError);
}
Ok(((self.sys_clk.raw() / CLK_400K.raw() / 25) - 1) as u8)
}
}
/// Configures the clock scale for a given speed mode setting
pub fn cfg_clk_scale(
&mut self,
speed_mode: I2cSpeed,
) -> Result<(), ClockTooSlowForFastI2cError> {
let clk_div = self.calc_clk_div(speed_mode)?;
self.i2c
.clkscale()
.write(|w| unsafe { w.bits(((speed_mode as u32) << 31) | clk_div as u32) });
Ok(())
}
pub fn load_address(&mut self, addr: u16) {
// Load address
self.i2c
.address()
.write(|w| unsafe { w.bits((addr << 1) as u32) });
}
#[inline]
fn stop_cmd(&mut self) {
self.i2c
.cmd()
.write(|w| unsafe { w.bits(I2cCmd::Stop as u32) });
}
}
//==================================================================================================
// I2C Master
//==================================================================================================
pub struct I2cMaster<I2c, Addr = SevenBitAddress> {
i2c_base: I2cBase<I2c>,
addr: PhantomData<Addr>,
}
impl<I2c: Instance, Addr> I2cMaster<I2c, Addr> {
pub fn new(
syscfg: &mut pac::Sysconfig,
sysclk: impl Into<Hertz>,
i2c: I2c,
cfg: MasterConfig,
speed_mode: I2cSpeed,
) -> Result<Self, ClockTooSlowForFastI2cError> {
Ok(I2cMaster {
i2c_base: I2cBase::new(syscfg, sysclk, i2c, speed_mode, Some(&cfg), None)?,
addr: PhantomData,
}
.enable_master())
}
#[inline]
pub fn cancel_transfer(&self) {
self.i2c_base
.i2c
.cmd()
.write(|w| unsafe { w.bits(I2cCmd::Cancel as u32) });
}
#[inline]
pub fn clear_tx_fifo(&self) {
self.i2c_base.i2c.fifo_clr().write(|w| w.txfifo().set_bit());
}
#[inline]
pub fn clear_rx_fifo(&self) {
self.i2c_base.i2c.fifo_clr().write(|w| w.rxfifo().set_bit());
}
#[inline]
pub fn enable_master(self) -> Self {
self.i2c_base.i2c.ctrl().modify(|_, w| w.enable().set_bit());
self
}
#[inline]
pub fn disable_master(self) -> Self {
self.i2c_base
.i2c
.ctrl()
.modify(|_, w| w.enable().clear_bit());
self
}
#[inline(always)]
fn load_fifo(&self, word: u8) {
self.i2c_base
.i2c
.data()
.write(|w| unsafe { w.bits(word as u32) });
}
#[inline(always)]
fn read_fifo(&self) -> u8 {
self.i2c_base.i2c.data().read().bits() as u8
}
fn error_handler_write(&mut self, init_cmd: &I2cCmd) {
self.clear_tx_fifo();
if *init_cmd == I2cCmd::Start {
self.i2c_base.stop_cmd()
}
}
fn write_base(
&mut self,
addr: I2cAddress,
init_cmd: I2cCmd,
bytes: impl IntoIterator<Item = u8>,
) -> Result<(), Error> {
let mut iter = bytes.into_iter();
// Load address
let (addr, addr_mode_bit) = I2cBase::<I2c>::unwrap_addr(addr);
self.i2c_base.i2c.address().write(|w| unsafe {
w.bits(I2cDirection::Send as u32 | (addr << 1) as u32 | addr_mode_bit)
});
self.i2c_base
.i2c
.cmd()
.write(|w| unsafe { w.bits(init_cmd as u32) });
let mut load_if_next_available = || {
if let Some(next_byte) = iter.next() {
self.load_fifo(next_byte);
}
};
loop {
let status_reader = self.i2c_base.i2c.status().read();
if status_reader.arblost().bit_is_set() {
self.error_handler_write(&init_cmd);
return Err(Error::ArbitrationLost);
} else if status_reader.nackaddr().bit_is_set() {
self.error_handler_write(&init_cmd);
return Err(Error::NackAddr);
} else if status_reader.nackdata().bit_is_set() {
self.error_handler_write(&init_cmd);
return Err(Error::NackData);
} else if status_reader.idle().bit_is_set() {
return Ok(());
} else {
while !status_reader.txnfull().bit_is_set() {
load_if_next_available();
}
}
}
}
fn write_from_buffer(
&mut self,
init_cmd: I2cCmd,
addr: I2cAddress,
output: &[u8],
) -> Result<(), Error> {
let len = output.len();
// It should theoretically possible to transfer larger data sizes by tracking
// the number of sent words and setting it to 0x7fe as soon as only that many
// bytes are remaining. However, large transfer like this are not common. This
// feature will therefore not be supported for now.
if len > 0x7fe {
return Err(Error::DataTooLarge);
}
// Load number of words
self.i2c_base
.i2c
.words()
.write(|w| unsafe { w.bits(len as u32) });
let mut bytes = output.iter();
// FIFO has a depth of 16. We load slightly above the trigger level
// but not all of it because the transaction might fail immediately
const FILL_DEPTH: usize = 12;
// load the FIFO
for _ in 0..core::cmp::min(FILL_DEPTH, len) {
self.load_fifo(*bytes.next().unwrap());
}
self.write_base(addr, init_cmd, output.iter().cloned())
}
fn read_internal(&mut self, addr: I2cAddress, buffer: &mut [u8]) -> Result<(), Error> {
let len = buffer.len();
// It should theoretically possible to transfer larger data sizes by tracking
// the number of sent words and setting it to 0x7fe as soon as only that many
// bytes are remaining. However, large transfer like this are not common. This
// feature will therefore not be supported for now.
if len > 0x7fe {
return Err(Error::DataTooLarge);
}
// Clear the receive FIFO
self.clear_rx_fifo();
// Load number of words
self.i2c_base
.i2c
.words()
.write(|w| unsafe { w.bits(len as u32) });
let (addr, addr_mode_bit) = match addr {
I2cAddress::Regular(addr) => (addr as u16, 0 << 15),
I2cAddress::TenBit(addr) => (addr, 1 << 15),
};
// Load address
self.i2c_base.i2c.address().write(|w| unsafe {
w.bits(I2cDirection::Read as u32 | (addr << 1) as u32 | addr_mode_bit)
});
let mut buf_iter = buffer.iter_mut();
let mut read_bytes = 0;
// Start receive transfer
self.i2c_base
.i2c
.cmd()
.write(|w| unsafe { w.bits(I2cCmd::StartWithStop as u32) });
let mut read_if_next_available = || {
if let Some(next_byte) = buf_iter.next() {
*next_byte = self.read_fifo();
}
};
loop {
let status_reader = self.i2c_base.i2c.status().read();
if status_reader.arblost().bit_is_set() {
self.clear_rx_fifo();
return Err(Error::ArbitrationLost);
} else if status_reader.nackaddr().bit_is_set() {
self.clear_rx_fifo();
return Err(Error::NackAddr);
} else if status_reader.idle().bit_is_set() {
if read_bytes != len {
return Err(Error::InsufficientDataReceived);
}
return Ok(());
} else if status_reader.rxnempty().bit_is_set() {
read_if_next_available();
read_bytes += 1;
}
}
}
}
//======================================================================================
// Embedded HAL I2C implementations
//======================================================================================
impl<I2c> embedded_hal::i2c::ErrorType for I2cMaster<I2c, SevenBitAddress> {
type Error = Error;
}
impl<I2c: Instance> embedded_hal::i2c::I2c for I2cMaster<I2c, SevenBitAddress> {
fn transaction(
&mut self,
address: SevenBitAddress,
operations: &mut [Operation<'_>],
) -> Result<(), Self::Error> {
for operation in operations {
match operation {
Operation::Read(buf) => self.read_internal(I2cAddress::Regular(address), buf)?,
Operation::Write(buf) => self.write_from_buffer(
I2cCmd::StartWithStop,
I2cAddress::Regular(address),
buf,
)?,
}
}
Ok(())
}
}
impl<I2c> embedded_hal::i2c::ErrorType for I2cMaster<I2c, TenBitAddress> {
type Error = Error;
}
impl<I2c: Instance> embedded_hal::i2c::I2c<TenBitAddress> for I2cMaster<I2c, TenBitAddress> {
fn transaction(
&mut self,
address: TenBitAddress,
operations: &mut [Operation<'_>],
) -> Result<(), Self::Error> {
for operation in operations {
match operation {
Operation::Read(buf) => self.read_internal(I2cAddress::TenBit(address), buf)?,
Operation::Write(buf) => {
self.write_from_buffer(I2cCmd::StartWithStop, I2cAddress::TenBit(address), buf)?
}
}
}
Ok(())
}
}
//==================================================================================================
// I2C Slave
//==================================================================================================
pub struct I2cSlave<I2c, Addr = SevenBitAddress> {
i2c_base: I2cBase<I2c>,
addr: PhantomData<Addr>,
}
impl<I2c: Instance, Addr> I2cSlave<I2c, Addr> {
fn new_generic(
sys_cfg: &mut pac::Sysconfig,
sys_clk: impl Into<Hertz>,
i2c: I2c,
cfg: SlaveConfig,
speed_mode: I2cSpeed,
) -> Result<Self, ClockTooSlowForFastI2cError> {
Ok(I2cSlave {
i2c_base: I2cBase::new(sys_cfg, sys_clk, i2c, speed_mode, None, Some(&cfg))?,
addr: PhantomData,
}
.enable_slave())
}
#[inline]
pub fn enable_slave(self) -> Self {
self.i2c_base
.i2c
.s0_ctrl()
.modify(|_, w| w.enable().set_bit());
self
}
#[inline]
pub fn disable_slave(self) -> Self {
self.i2c_base
.i2c
.s0_ctrl()
.modify(|_, w| w.enable().clear_bit());
self
}
#[inline(always)]
fn load_fifo(&self, word: u8) {
self.i2c_base
.i2c
.s0_data()
.write(|w| unsafe { w.bits(word as u32) });
}
#[inline(always)]
fn read_fifo(&self) -> u8 {
self.i2c_base.i2c.s0_data().read().bits() as u8
}
#[inline]
fn clear_tx_fifo(&self) {
self.i2c_base
.i2c
.s0_fifo_clr()
.write(|w| w.txfifo().set_bit());
}
#[inline]
fn clear_rx_fifo(&self) {
self.i2c_base
.i2c
.s0_fifo_clr()
.write(|w| w.rxfifo().set_bit());
}
/// Get the last address that was matched by the slave control and the corresponding
/// master direction
pub fn last_address(&self) -> (I2cDirection, u32) {
let bits = self.i2c_base.i2c.s0_lastaddress().read().bits();
match bits & 0x01 {
0 => (I2cDirection::Send, bits >> 1),
1 => (I2cDirection::Read, bits >> 1),
_ => (I2cDirection::Send, bits >> 1),
}
}
pub fn write(&mut self, output: &[u8]) -> Result<(), Error> {
let len = output.len();
// It should theoretically possible to transfer larger data sizes by tracking
// the number of sent words and setting it to 0x7fe as soon as only that many
// bytes are remaining. However, large transfer like this are not common. This
// feature will therefore not be supported for now.
if len > 0x7fe {
return Err(Error::DataTooLarge);
}
let mut bytes = output.iter();
// FIFO has a depth of 16. We load slightly above the trigger level
// but not all of it because the transaction might fail immediately
const FILL_DEPTH: usize = 12;
// load the FIFO
for _ in 0..core::cmp::min(FILL_DEPTH, len) {
self.load_fifo(*bytes.next().unwrap());
}
let status_reader = self.i2c_base.i2c.s0_status().read();
let mut load_if_next_available = || {
if let Some(next_byte) = bytes.next() {
self.load_fifo(*next_byte);
}
};
loop {
if status_reader.nackdata().bit_is_set() {
self.clear_tx_fifo();
return Err(Error::NackData);
} else if status_reader.idle().bit_is_set() {
return Ok(());
} else {
while !status_reader.txnfull().bit_is_set() {
load_if_next_available();
}
}
}
}
pub fn read(&mut self, buffer: &mut [u8]) -> Result<(), Error> {
let len = buffer.len();
// It should theoretically possible to transfer larger data sizes by tracking
// the number of sent words and setting it to 0x7fe as soon as only that many
// bytes are remaining. However, large transfer like this are not common. This
// feature will therefore not be supported for now.
if len > 0x7fe {
return Err(Error::DataTooLarge);
}
// Clear the receive FIFO
self.clear_rx_fifo();
let mut buf_iter = buffer.iter_mut();
let mut read_bytes = 0;
let mut read_if_next_available = || {
if let Some(next_byte) = buf_iter.next() {
*next_byte = self.read_fifo();
}
};
loop {
let status_reader = self.i2c_base.i2c.s0_status().read();
if status_reader.idle().bit_is_set() {
if read_bytes != len {
return Err(Error::InsufficientDataReceived);
}
return Ok(());
} else if status_reader.rxnempty().bit_is_set() {
read_bytes += 1;
read_if_next_available();
}
}
}
}
impl<I2c: Instance> I2cSlave<I2c, SevenBitAddress> {
/// Create a new I2C slave for seven bit addresses
pub fn new(
sys_cfg: &mut pac::Sysconfig,
sys_clk: impl Into<Hertz>,
i2c: I2c,
cfg: SlaveConfig,
speed_mode: I2cSpeed,
) -> Result<Self, InitError> {
if let I2cAddress::TenBit(_) = cfg.addr {
return Err(InitError::WrongAddrMode);
}
Ok(Self::new_generic(sys_cfg, sys_clk, i2c, cfg, speed_mode)?)
}
}
impl<I2c: Instance> I2cSlave<I2c, TenBitAddress> {
pub fn new_ten_bit_addr(
sys_cfg: &mut pac::Sysconfig,
sys_clk: impl Into<Hertz>,
i2c: I2c,
cfg: SlaveConfig,
speed_mode: I2cSpeed,
) -> Result<Self, ClockTooSlowForFastI2cError> {
Self::new_generic(sys_cfg, sys_clk, i2c, cfg, speed_mode)
}
}
pub use vorago_shared_periphs::i2c::*;

View File

@ -8,69 +8,21 @@ pub use va108xx as pac;
pub mod clock;
pub mod gpio;
pub mod i2c;
pub mod pins;
pub mod prelude;
pub mod pwm;
pub mod spi;
pub mod sysconfig;
pub mod time;
pub mod timer;
pub mod typelevel;
pub mod uart;
#[derive(Debug, Eq, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum FunSel {
Sel0 = 0b00,
Sel1 = 0b01,
Sel2 = 0b10,
Sel3 = 0b11,
}
pub use vorago_shared_periphs::{
disable_nvic_interrupt, enable_nvic_interrupt, FunSel, InterruptConfig, PeripheralSelect,
};
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum PeripheralSelect {
PortA = 0,
PortB = 1,
Spi0 = 4,
Spi1 = 5,
Spi2 = 6,
Uart0 = 8,
Uart1 = 9,
I2c0 = 16,
I2c1 = 17,
Irqsel = 21,
Ioconfig = 22,
Utility = 23,
Gpio = 24,
}
/// Generic interrupt config which can be used to specify whether the HAL driver will
/// use the IRQSEL register to route an interrupt, and whether the IRQ will be unmasked in the
/// Cortex-M0 NVIC. Both are generally necessary for IRQs to work, but the user might want to
/// perform those steps themselves.
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct InterruptConfig {
/// Interrupt target vector. Should always be set, might be required for disabling IRQs
pub id: pac::Interrupt,
/// Specfiy whether IRQ should be routed to an IRQ vector using the IRQSEL peripheral.
pub route: bool,
/// Specify whether the IRQ is unmasked in the Cortex-M NVIC. If an interrupt is used for
/// multiple purposes, the user can enable the interrupts themselves.
pub enable_in_nvic: bool,
}
impl InterruptConfig {
pub fn new(id: pac::Interrupt, route: bool, enable_in_nvic: bool) -> Self {
InterruptConfig {
id,
route,
enable_in_nvic,
}
}
}
pub type IrqCfg = InterruptConfig;
/// This is the NONE destination reigster value for the IRQSEL peripheral.
pub const IRQ_DST_NONE: u32 = 0xffffffff;
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
@ -101,20 +53,7 @@ pub fn port_function_select(
Ok(())
}
/// Enable a specific interrupt using the NVIC peripheral.
///
/// # Safety
///
/// This function is `unsafe` because it can break mask-based critical sections.
#[inline]
pub unsafe fn enable_nvic_interrupt(irq: pac::Interrupt) {
unsafe {
cortex_m::peripheral::NVIC::unmask(irq);
}
}
/// Disable a specific interrupt using the NVIC peripheral.
#[inline]
pub fn disable_nvic_interrupt(irq: pac::Interrupt) {
cortex_m::peripheral::NVIC::mask(irq);
#[allow(dead_code)]
pub(crate) mod sealed {
pub trait Sealed {}
}

6
va108xx-hal/src/pins.rs Normal file
View File

@ -0,0 +1,6 @@
//! Pin resource management singletons.
//!
//! This module contains the pin singletons. It allows creating those singletons
//! to access the [Pin] structures of individual ports in a safe way with checked ownership
//! rules.
pub use vorago_shared_periphs::pins::*;

View File

@ -5,459 +5,4 @@
//! ## Examples
//!
//! - [PWM example](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples/simple/examples/pwm.rs)
use core::convert::Infallible;
use core::marker::PhantomData;
use crate::pac;
use crate::time::Hertz;
use crate::timer::{TimDynRegister, TimPin, TimRegInterface, ValidTim, ValidTimAndPin};
use crate::{clock::enable_peripheral_clock, gpio::DynPinId};
const DUTY_MAX: u16 = u16::MAX;
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub(crate) struct PwmCommon {
sys_clk: Hertz,
/// For PWMB, this is the upper limit
current_duty: u16,
/// For PWMA, this value will not be used
current_lower_limit: u16,
current_period: Hertz,
current_rst_val: u32,
}
enum StatusSelPwm {
PwmA = 3,
PwmB = 4,
}
pub struct PwmA {}
pub struct PwmB {}
//==================================================================================================
// Strongly typed PWM pin
//==================================================================================================
pub struct PwmPin<Pin: TimPin, Tim: ValidTim, Mode = PwmA> {
pin_and_tim: (Pin, Tim),
inner: ReducedPwmPin<Mode>,
mode: PhantomData<Mode>,
}
impl<Pin: TimPin, Tim: ValidTim, Mode> PwmPin<Pin, Tim, Mode>
where
(Pin, Tim): ValidTimAndPin<Pin, Tim>,
{
/// Create a new stronlgy typed PWM pin
pub fn new(
sys_cfg: &mut pac::Sysconfig,
sys_clk: impl Into<Hertz> + Copy,
pin_and_tim: (Pin, Tim),
initial_period: impl Into<Hertz> + Copy,
) -> Self {
let mut pin = PwmPin {
pin_and_tim,
inner: ReducedPwmPin::<Mode>::new(
Tim::TIM_ID,
Pin::DYN,
PwmCommon {
current_duty: 0,
current_lower_limit: 0,
current_period: initial_period.into(),
current_rst_val: 0,
sys_clk: sys_clk.into(),
},
),
//unsafe { TimAndPin::new(tim_and_pin.0, tim_and_pin.1) },
mode: PhantomData,
};
enable_peripheral_clock(sys_cfg, crate::clock::PeripheralClocks::Gpio);
enable_peripheral_clock(sys_cfg, crate::clock::PeripheralClocks::Ioconfig);
sys_cfg
.tim_clk_enable()
.modify(|r, w| unsafe { w.bits(r.bits() | pin.pin_and_tim.1.mask_32()) });
pin.enable_pwm_a();
pin.set_period(initial_period);
pin
}
pub fn downgrade(self) -> ReducedPwmPin<Mode> {
self.inner
}
pub fn release(self) -> (Pin, Tim) {
self.pin_and_tim
}
#[inline]
fn enable_pwm_a(&mut self) {
self.inner.enable_pwm_a();
}
#[inline]
fn enable_pwm_b(&mut self) {
self.inner.enable_pwm_b();
}
#[inline]
pub fn get_period(&self) -> Hertz {
self.inner.get_period()
}
#[inline]
pub fn set_period(&mut self, period: impl Into<Hertz>) {
self.inner.set_period(period);
}
#[inline]
pub fn disable(&mut self) {
self.inner.disable();
}
#[inline]
pub fn enable(&mut self) {
self.inner.enable();
}
#[inline]
pub fn period(&self) -> Hertz {
self.inner.period()
}
#[inline(always)]
pub fn duty(&self) -> u16 {
self.inner.duty()
}
}
impl<Pin: TimPin, Tim: ValidTim> From<PwmPin<Pin, Tim, PwmA>> for PwmPin<Pin, Tim, PwmB>
where
(Pin, Tim): ValidTimAndPin<Pin, Tim>,
{
fn from(other: PwmPin<Pin, Tim, PwmA>) -> Self {
let mut pwmb = Self {
mode: PhantomData,
pin_and_tim: other.pin_and_tim,
inner: other.inner.into(),
};
pwmb.enable_pwm_b();
pwmb
}
}
impl<PIN: TimPin, TIM: ValidTim> From<PwmPin<PIN, TIM, PwmB>> for PwmPin<PIN, TIM, PwmA>
where
(PIN, TIM): ValidTimAndPin<PIN, TIM>,
{
fn from(other: PwmPin<PIN, TIM, PwmB>) -> Self {
let mut pwma = Self {
mode: PhantomData,
pin_and_tim: other.pin_and_tim,
inner: other.inner.into(),
};
pwma.enable_pwm_a();
pwma
}
}
impl<Pin: TimPin, Tim: ValidTim> PwmPin<Pin, Tim, PwmA>
where
(Pin, Tim): ValidTimAndPin<Pin, Tim>,
{
pub fn pwma(
sys_cfg: &mut pac::Sysconfig,
sys_clk: impl Into<Hertz> + Copy,
pin_and_tim: (Pin, Tim),
initial_period: impl Into<Hertz> + Copy,
) -> Self {
let mut pin: PwmPin<Pin, Tim, PwmA> =
Self::new(sys_cfg, sys_clk, pin_and_tim, initial_period);
pin.enable_pwm_a();
pin
}
}
impl<Pin: TimPin, Tim: ValidTim> PwmPin<Pin, Tim, PwmB>
where
(Pin, Tim): ValidTimAndPin<Pin, Tim>,
{
pub fn pwmb(
sys_cfg: &mut pac::Sysconfig,
sys_clk: impl Into<Hertz> + Copy,
pin_and_tim: (Pin, Tim),
initial_period: impl Into<Hertz> + Copy,
) -> Self {
let mut pin: PwmPin<Pin, Tim, PwmB> =
Self::new(sys_cfg, sys_clk, pin_and_tim, initial_period);
pin.enable_pwm_b();
pin
}
}
//==================================================================================================
// Reduced PWM pin
//==================================================================================================
/// Reduced version where type information is deleted
pub struct ReducedPwmPin<Mode = PwmA> {
dyn_reg: TimDynRegister,
common: PwmCommon,
mode: PhantomData<Mode>,
}
impl<Mode> ReducedPwmPin<Mode> {
pub(crate) fn new(tim_id: u8, pin_id: DynPinId, common: PwmCommon) -> Self {
Self {
dyn_reg: TimDynRegister { tim_id, pin_id },
common,
mode: PhantomData,
}
}
}
impl<Mode> ReducedPwmPin<Mode> {
#[inline]
fn enable_pwm_a(&mut self) {
self.dyn_reg
.reg_block()
.ctrl()
.modify(|_, w| unsafe { w.status_sel().bits(StatusSelPwm::PwmA as u8) });
}
#[inline]
fn enable_pwm_b(&mut self) {
self.dyn_reg
.reg_block()
.ctrl()
.modify(|_, w| unsafe { w.status_sel().bits(StatusSelPwm::PwmB as u8) });
}
#[inline]
pub fn get_period(&self) -> Hertz {
self.common.current_period
}
#[inline]
pub fn set_period(&mut self, period: impl Into<Hertz>) {
self.common.current_period = period.into();
// Avoid division by 0
if self.common.current_period.raw() == 0 {
return;
}
self.common.current_rst_val = self.common.sys_clk.raw() / self.common.current_period.raw();
self.dyn_reg
.reg_block()
.rst_value()
.write(|w| unsafe { w.bits(self.common.current_rst_val) });
}
#[inline]
pub fn disable(&mut self) {
self.dyn_reg
.reg_block()
.ctrl()
.modify(|_, w| w.enable().clear_bit());
}
#[inline]
pub fn enable(&mut self) {
self.dyn_reg
.reg_block()
.ctrl()
.modify(|_, w| w.enable().set_bit());
}
#[inline]
pub fn period(&self) -> Hertz {
self.common.current_period
}
#[inline(always)]
pub fn duty(&self) -> u16 {
self.common.current_duty
}
}
impl<Pin: TimPin, Tim: ValidTim> From<PwmPin<Pin, Tim, PwmA>> for ReducedPwmPin<PwmA>
where
(Pin, Tim): ValidTimAndPin<Pin, Tim>,
{
fn from(value: PwmPin<Pin, Tim, PwmA>) -> Self {
value.downgrade()
}
}
impl<Pin: TimPin, Tim: ValidTim> From<PwmPin<Pin, Tim, PwmB>> for ReducedPwmPin<PwmB>
where
(Pin, Tim): ValidTimAndPin<Pin, Tim>,
{
fn from(value: PwmPin<Pin, Tim, PwmB>) -> Self {
value.downgrade()
}
}
impl From<ReducedPwmPin<PwmA>> for ReducedPwmPin<PwmB> {
fn from(other: ReducedPwmPin<PwmA>) -> Self {
let mut pwmb = Self {
dyn_reg: other.dyn_reg,
common: other.common,
mode: PhantomData,
};
pwmb.enable_pwm_b();
pwmb
}
}
impl From<ReducedPwmPin<PwmB>> for ReducedPwmPin<PwmA> {
fn from(other: ReducedPwmPin<PwmB>) -> Self {
let mut pwmb = Self {
dyn_reg: other.dyn_reg,
common: other.common,
mode: PhantomData,
};
pwmb.enable_pwm_a();
pwmb
}
}
//==================================================================================================
// PWMB implementations
//==================================================================================================
impl<Pin: TimPin, Tim: ValidTim> PwmPin<Pin, Tim, PwmB>
where
(Pin, Tim): ValidTimAndPin<Pin, Tim>,
{
pub fn pwmb_lower_limit(&self) -> u16 {
self.inner.pwmb_lower_limit()
}
pub fn pwmb_upper_limit(&self) -> u16 {
self.inner.pwmb_upper_limit()
}
/// Set the lower limit for PWMB
///
/// The PWM signal will be 1 as long as the current RST counter is larger than
/// the lower limit. For example, with a lower limit of 0.5 and and an upper limit
/// of 0.7, Only a fixed period between 0.5 * period and 0.7 * period will be in a high
/// state
pub fn set_pwmb_lower_limit(&mut self, duty: u16) {
self.inner.set_pwmb_lower_limit(duty);
}
/// Set the higher limit for PWMB
///
/// The PWM signal will be 1 as long as the current RST counter is smaller than
/// the higher limit. For example, with a lower limit of 0.5 and and an upper limit
/// of 0.7, Only a fixed period between 0.5 * period and 0.7 * period will be in a high
/// state
pub fn set_pwmb_upper_limit(&mut self, duty: u16) {
self.inner.set_pwmb_upper_limit(duty);
}
}
impl ReducedPwmPin<PwmB> {
#[inline(always)]
pub fn pwmb_lower_limit(&self) -> u16 {
self.common.current_lower_limit
}
#[inline(always)]
pub fn pwmb_upper_limit(&self) -> u16 {
self.common.current_duty
}
/// Set the lower limit for PWMB
///
/// The PWM signal will be 1 as long as the current RST counter is larger than
/// the lower limit. For example, with a lower limit of 0.5 and and an upper limit
/// of 0.7, Only a fixed period between 0.5 * period and 0.7 * period will be in a high
/// state
#[inline(always)]
pub fn set_pwmb_lower_limit(&mut self, duty: u16) {
self.common.current_lower_limit = duty;
let pwmb_val: u64 = (self.common.current_rst_val as u64
* self.common.current_lower_limit as u64)
/ DUTY_MAX as u64;
self.dyn_reg
.reg_block()
.pwmb_value()
.write(|w| unsafe { w.bits(pwmb_val as u32) });
}
/// Set the higher limit for PWMB
///
/// The PWM signal will be 1 as long as the current RST counter is smaller than
/// the higher limit. For example, with a lower limit of 0.5 and and an upper limit
/// of 0.7, Only a fixed period between 0.5 * period and 0.7 * period will be in a high
/// state
pub fn set_pwmb_upper_limit(&mut self, duty: u16) {
self.common.current_duty = duty;
let pwma_val: u64 = (self.common.current_rst_val as u64 * self.common.current_duty as u64)
/ DUTY_MAX as u64;
self.dyn_reg
.reg_block()
.pwma_value()
.write(|w| unsafe { w.bits(pwma_val as u32) });
}
}
//==================================================================================================
// Embedded HAL implementation: PWMA only
//==================================================================================================
impl<Pin: TimPin, Tim: ValidTim> embedded_hal::pwm::ErrorType for PwmPin<Pin, Tim> {
type Error = Infallible;
}
impl embedded_hal::pwm::ErrorType for ReducedPwmPin {
type Error = Infallible;
}
impl embedded_hal::pwm::SetDutyCycle for ReducedPwmPin {
#[inline]
fn max_duty_cycle(&self) -> u16 {
DUTY_MAX
}
#[inline]
fn set_duty_cycle(&mut self, duty: u16) -> Result<(), Self::Error> {
self.common.current_duty = duty;
let pwma_val: u64 = (self.common.current_rst_val as u64
* (DUTY_MAX as u64 - self.common.current_duty as u64))
/ DUTY_MAX as u64;
self.dyn_reg
.reg_block()
.pwma_value()
.write(|w| unsafe { w.bits(pwma_val as u32) });
Ok(())
}
}
impl<Pin: TimPin, Tim: ValidTim> embedded_hal::pwm::SetDutyCycle for PwmPin<Pin, Tim> {
#[inline]
fn max_duty_cycle(&self) -> u16 {
DUTY_MAX
}
#[inline]
fn set_duty_cycle(&mut self, duty: u16) -> Result<(), Self::Error> {
self.inner.set_duty_cycle(duty)
}
}
/// Get the corresponding u16 duty cycle from a percent value ranging between 0.0 and 1.0.
///
/// Please note that this might load a lot of floating point code because this processor does not
/// have a FPU
pub fn get_duty_from_percent(percent: f32) -> u16 {
if percent > 1.0 {
DUTY_MAX
} else if percent <= 0.0 {
0
} else {
(percent * DUTY_MAX as f32) as u16
}
}
pub use vorago_shared_periphs::pwm::*;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,12 @@
//! API for the SPI peripheral.
//!
//! The main abstraction provided by this module is the [Spi] an structure.
//! It provides the [embedded_hal::spi] traits, but also offer a low level interface
//! via the [SpiLowLevel] trait.
//!
//! ## Examples
//!
//! - [Blocking SPI example](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples/simple/examples/spi.rs)
//! - [REB1 ADC example](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/vorago-reb1/examples/max11519-adc.rs)
//! - [REB1 EEPROM library](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/vorago-reb1/src/m95m01.rs)
pub use vorago_shared_periphs::spi::*;

View File

@ -1,5 +1,3 @@
use crate::{pac, PeripheralSelect};
#[derive(PartialEq, Eq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct InvalidCounterResetVal(pub(crate) ());
@ -8,10 +6,8 @@ pub struct InvalidCounterResetVal(pub(crate) ());
///
/// Returns [InvalidCounterResetVal] if the scrub rate is 0
/// (equivalent to disabling) or larger than 24 bits
pub fn enable_rom_scrubbing(
syscfg: &mut pac::Sysconfig,
scrub_rate: u32,
) -> Result<(), InvalidCounterResetVal> {
pub fn enable_rom_scrubbing(scrub_rate: u32) -> Result<(), InvalidCounterResetVal> {
let syscfg = unsafe { va108xx::Sysconfig::steal() };
if scrub_rate == 0 || scrub_rate > u32::pow(2, 24) {
return Err(InvalidCounterResetVal(()));
}
@ -19,7 +15,8 @@ pub fn enable_rom_scrubbing(
Ok(())
}
pub fn disable_rom_scrubbing(syscfg: &mut pac::Sysconfig) {
pub fn disable_rom_scrubbing() {
let syscfg = unsafe { va108xx::Sysconfig::steal() };
syscfg.rom_scrub().write(|w| unsafe { w.bits(0) });
}
@ -27,10 +24,8 @@ pub fn disable_rom_scrubbing(syscfg: &mut pac::Sysconfig) {
///
/// Returns [InvalidCounterResetVal] if the scrub rate is 0
/// (equivalent to disabling) or larger than 24 bits
pub fn enable_ram_scrubbing(
syscfg: &mut pac::Sysconfig,
scrub_rate: u32,
) -> Result<(), InvalidCounterResetVal> {
pub fn enable_ram_scrubbing(scrub_rate: u32) -> Result<(), InvalidCounterResetVal> {
let syscfg = unsafe { va108xx::Sysconfig::steal() };
if scrub_rate == 0 || scrub_rate > u32::pow(2, 24) {
return Err(InvalidCounterResetVal(()));
}
@ -38,20 +33,11 @@ pub fn enable_ram_scrubbing(
Ok(())
}
pub fn disable_ram_scrubbing(syscfg: &mut pac::Sysconfig) {
pub fn disable_ram_scrubbing() {
let syscfg = unsafe { va108xx::Sysconfig::steal() };
syscfg.ram_scrub().write(|w| unsafe { w.bits(0) });
}
/// Clear the reset bit. This register is active low, so doing this will hold the peripheral
/// in a reset state
pub fn clear_reset_bit(syscfg: &mut pac::Sysconfig, periph_sel: PeripheralSelect) {
syscfg
.peripheral_reset()
.modify(|r, w| unsafe { w.bits(r.bits() & !(1 << periph_sel as u8)) });
}
pub fn set_reset_bit(syscfg: &mut pac::Sysconfig, periph_sel: PeripheralSelect) {
syscfg
.peripheral_reset()
.modify(|r, w| unsafe { w.bits(r.bits() | (1 << periph_sel as u8)) });
}
pub use vorago_shared_periphs::sysconfig::{
assert_peripheral_reset, disable_peripheral_clock, enable_peripheral_clock,
};

View File

@ -1,26 +1,2 @@
//! Time units
// Frequency based
/// Hertz
pub type Hertz = fugit::HertzU32;
/// KiloHertz
pub type KiloHertz = fugit::KilohertzU32;
/// MegaHertz
pub type MegaHertz = fugit::MegahertzU32;
// Period based
/// Seconds
pub type Seconds = fugit::SecsDurationU32;
/// Milliseconds
pub type Milliseconds = fugit::MillisDurationU32;
/// Microseconds
pub type Microseconds = fugit::MicrosDurationU32;
/// Nanoseconds
pub type Nanoseconds = fugit::NanosDurationU32;
pub use vorago_shared_periphs::time::*;

View File

@ -4,786 +4,4 @@
//!
//! - [MS and second tick implementation](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples/simple/examples/timer-ticks.rs)
//! - [Cascade feature example](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples/simple/examples/cascade.rs)
pub use crate::InterruptConfig;
use crate::{
clock::{enable_peripheral_clock, PeripheralClocks},
enable_nvic_interrupt,
gpio::{
AltFunc1, AltFunc2, AltFunc3, DynPinId, Pin, PinId, PA0, PA1, PA10, PA11, PA12, PA13, PA14,
PA15, PA2, PA24, PA25, PA26, PA27, PA28, PA29, PA3, PA30, PA31, PA4, PA5, PA6, PA7, PA8,
PA9, PB0, PB1, PB10, PB11, PB12, PB13, PB14, PB15, PB16, PB17, PB18, PB19, PB2, PB20, PB21,
PB22, PB23, PB3, PB4, PB5, PB6,
},
pac::{self, tim0},
time::Hertz,
timer,
typelevel::Sealed,
};
use core::cell::Cell;
use critical_section::Mutex;
use fugit::RateExtU32;
const IRQ_DST_NONE: u32 = 0xffffffff;
pub static MS_COUNTER: Mutex<Cell<u32>> = Mutex::new(Cell::new(0));
/// Get the peripheral block of a TIM peripheral given the index.
///
/// This function panics if the given index is greater than 23.
///
/// # Safety
///
/// This returns a direct handle to the peripheral block, which allows to circumvent ownership
/// rules for the peripheral block. You have to ensure that the retrieved peripheral block is not
/// used by any other software component.
#[inline(always)]
pub const unsafe fn get_tim_raw(tim_idx: usize) -> &'static pac::tim0::RegisterBlock {
match tim_idx {
0 => unsafe { &*pac::Tim0::ptr() },
1 => unsafe { &*pac::Tim1::ptr() },
2 => unsafe { &*pac::Tim2::ptr() },
3 => unsafe { &*pac::Tim3::ptr() },
4 => unsafe { &*pac::Tim4::ptr() },
5 => unsafe { &*pac::Tim5::ptr() },
6 => unsafe { &*pac::Tim6::ptr() },
7 => unsafe { &*pac::Tim7::ptr() },
8 => unsafe { &*pac::Tim8::ptr() },
9 => unsafe { &*pac::Tim9::ptr() },
10 => unsafe { &*pac::Tim10::ptr() },
11 => unsafe { &*pac::Tim11::ptr() },
12 => unsafe { &*pac::Tim12::ptr() },
13 => unsafe { &*pac::Tim13::ptr() },
14 => unsafe { &*pac::Tim14::ptr() },
15 => unsafe { &*pac::Tim15::ptr() },
16 => unsafe { &*pac::Tim16::ptr() },
17 => unsafe { &*pac::Tim17::ptr() },
18 => unsafe { &*pac::Tim18::ptr() },
19 => unsafe { &*pac::Tim19::ptr() },
20 => unsafe { &*pac::Tim20::ptr() },
21 => unsafe { &*pac::Tim21::ptr() },
22 => unsafe { &*pac::Tim22::ptr() },
23 => unsafe { &*pac::Tim23::ptr() },
_ => {
panic!("invalid alarm timer index")
}
}
}
//==================================================================================================
// Defintions
//==================================================================================================
/// Interrupt events
pub enum Event {
/// Timer timed out / count down ended
TimeOut,
}
#[derive(Default, Debug, PartialEq, Eq, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct CascadeCtrl {
/// Enable Cascade 0 signal active as a requirement for counting
pub enb_start_src_csd0: bool,
/// Invert Cascade 0, making it active low
pub inv_csd0: bool,
/// Enable Cascade 1 signal active as a requirement for counting
pub enb_start_src_csd1: bool,
/// Invert Cascade 1, making it active low
pub inv_csd1: bool,
/// Specify required operation if both Cascade 0 and Cascade 1 are active.
/// 0 is a logical AND of both cascade signals, 1 is a logical OR
pub dual_csd_op: bool,
/// Enable trigger mode for Cascade 0. In trigger mode, couting will start with the selected
/// cascade signal active, but once the counter is active, cascade control will be ignored
pub trg_csd0: bool,
/// Trigger mode, identical to [`trg_csd0`](CascadeCtrl) but for Cascade 1
pub trg_csd1: bool,
/// Enable Cascade 2 signal active as a requirement to stop counting. This mode is similar
/// to the REQ_STOP control bit, but signalled by a Cascade source
pub enb_stop_src_csd2: bool,
/// Invert Cascade 2, making it active low
pub inv_csd2: bool,
/// The counter is automatically disabled if the corresponding Cascade 2 level-sensitive input
/// souce is active when the count reaches 0. If the counter is not 0, the cascade control is
/// ignored
pub trg_csd2: bool,
}
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum CascadeSel {
Csd0 = 0,
Csd1 = 1,
Csd2 = 2,
}
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct InvalidCascadeSourceId;
/// The numbers are the base numbers for bundles like PORTA, PORTB or TIM
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum CascadeSource {
PortA(u8),
PortB(u8),
Tim(u8),
RamSbe = 96,
RamMbe = 97,
RomSbe = 98,
RomMbe = 99,
Txev = 100,
ClockDivider(u8),
}
impl CascadeSource {
fn id(&self) -> Result<u8, InvalidCascadeSourceId> {
let port_check = |base: u8, id: u8, len: u8| {
if id > len - 1 {
return Err(InvalidCascadeSourceId);
}
Ok(base + id)
};
match self {
CascadeSource::PortA(id) => port_check(0, *id, 32),
CascadeSource::PortB(id) => port_check(32, *id, 32),
CascadeSource::Tim(id) => port_check(64, *id, 24),
CascadeSource::RamSbe => Ok(96),
CascadeSource::RamMbe => Ok(97),
CascadeSource::RomSbe => Ok(98),
CascadeSource::RomMbe => Ok(99),
CascadeSource::Txev => Ok(100),
CascadeSource::ClockDivider(id) => port_check(120, *id, 8),
}
}
}
//==================================================================================================
// Valid TIM and PIN combinations
//==================================================================================================
pub trait TimPin {
const DYN: DynPinId;
}
pub trait ValidTim {
// TIM ID ranging from 0 to 23 for 24 TIM peripherals
const TIM_ID: u8;
}
macro_rules! tim_marker {
($TIMX:path, $ID:expr) => {
impl ValidTim for $TIMX {
const TIM_ID: u8 = $ID;
}
};
}
tim_marker!(pac::Tim0, 0);
tim_marker!(pac::Tim1, 1);
tim_marker!(pac::Tim2, 2);
tim_marker!(pac::Tim3, 3);
tim_marker!(pac::Tim4, 4);
tim_marker!(pac::Tim5, 5);
tim_marker!(pac::Tim6, 6);
tim_marker!(pac::Tim7, 7);
tim_marker!(pac::Tim8, 8);
tim_marker!(pac::Tim9, 9);
tim_marker!(pac::Tim10, 10);
tim_marker!(pac::Tim11, 11);
tim_marker!(pac::Tim12, 12);
tim_marker!(pac::Tim13, 13);
tim_marker!(pac::Tim14, 14);
tim_marker!(pac::Tim15, 15);
tim_marker!(pac::Tim16, 16);
tim_marker!(pac::Tim17, 17);
tim_marker!(pac::Tim18, 18);
tim_marker!(pac::Tim19, 19);
tim_marker!(pac::Tim20, 20);
tim_marker!(pac::Tim21, 21);
tim_marker!(pac::Tim22, 22);
tim_marker!(pac::Tim23, 23);
pub trait ValidTimAndPin<PIN: TimPin, TIM: ValidTim>: Sealed {}
macro_rules! pin_and_tim {
($PAX:ident, $ALTFUNC:ident, $ID:expr, $TIMX:path) => {
impl TimPin for Pin<$PAX, $ALTFUNC>
where
$PAX: PinId,
{
const DYN: DynPinId = $PAX::DYN;
}
impl<PIN: TimPin, TIM: ValidTim> ValidTimAndPin<PIN, TIM> for (Pin<$PAX, $ALTFUNC>, $TIMX)
where
Pin<$PAX, $ALTFUNC>: TimPin,
$PAX: PinId,
{
}
impl Sealed for (Pin<$PAX, $ALTFUNC>, $TIMX) {}
};
}
pin_and_tim!(PA31, AltFunc2, 23, pac::Tim23);
pin_and_tim!(PA30, AltFunc2, 22, pac::Tim22);
pin_and_tim!(PA29, AltFunc2, 21, pac::Tim21);
pin_and_tim!(PA28, AltFunc2, 20, pac::Tim20);
pin_and_tim!(PA27, AltFunc2, 19, pac::Tim19);
pin_and_tim!(PA26, AltFunc2, 18, pac::Tim18);
pin_and_tim!(PA25, AltFunc2, 17, pac::Tim17);
pin_and_tim!(PA24, AltFunc2, 16, pac::Tim16);
pin_and_tim!(PA15, AltFunc1, 15, pac::Tim15);
pin_and_tim!(PA14, AltFunc1, 14, pac::Tim14);
pin_and_tim!(PA13, AltFunc1, 13, pac::Tim13);
pin_and_tim!(PA12, AltFunc1, 12, pac::Tim12);
pin_and_tim!(PA11, AltFunc1, 11, pac::Tim11);
pin_and_tim!(PA10, AltFunc1, 10, pac::Tim10);
pin_and_tim!(PA9, AltFunc1, 9, pac::Tim9);
pin_and_tim!(PA8, AltFunc1, 8, pac::Tim8);
pin_and_tim!(PA7, AltFunc1, 7, pac::Tim7);
pin_and_tim!(PA6, AltFunc1, 6, pac::Tim6);
pin_and_tim!(PA5, AltFunc1, 5, pac::Tim5);
pin_and_tim!(PA4, AltFunc1, 4, pac::Tim4);
pin_and_tim!(PA3, AltFunc1, 3, pac::Tim3);
pin_and_tim!(PA2, AltFunc1, 2, pac::Tim2);
pin_and_tim!(PA1, AltFunc1, 1, pac::Tim1);
pin_and_tim!(PA0, AltFunc1, 0, pac::Tim0);
pin_and_tim!(PB23, AltFunc3, 23, pac::Tim23);
pin_and_tim!(PB22, AltFunc3, 22, pac::Tim22);
pin_and_tim!(PB21, AltFunc3, 21, pac::Tim21);
pin_and_tim!(PB20, AltFunc3, 20, pac::Tim20);
pin_and_tim!(PB19, AltFunc3, 19, pac::Tim19);
pin_and_tim!(PB18, AltFunc3, 18, pac::Tim18);
pin_and_tim!(PB17, AltFunc3, 17, pac::Tim17);
pin_and_tim!(PB16, AltFunc3, 16, pac::Tim16);
pin_and_tim!(PB15, AltFunc3, 15, pac::Tim15);
pin_and_tim!(PB14, AltFunc3, 14, pac::Tim14);
pin_and_tim!(PB13, AltFunc3, 13, pac::Tim13);
pin_and_tim!(PB12, AltFunc3, 12, pac::Tim12);
pin_and_tim!(PB11, AltFunc3, 11, pac::Tim11);
pin_and_tim!(PB10, AltFunc3, 10, pac::Tim10);
pin_and_tim!(PB6, AltFunc3, 6, pac::Tim6);
pin_and_tim!(PB5, AltFunc3, 5, pac::Tim5);
pin_and_tim!(PB4, AltFunc3, 4, pac::Tim4);
pin_and_tim!(PB3, AltFunc3, 3, pac::Tim3);
pin_and_tim!(PB2, AltFunc3, 2, pac::Tim2);
pin_and_tim!(PB1, AltFunc3, 1, pac::Tim1);
pin_and_tim!(PB0, AltFunc3, 0, pac::Tim0);
//==================================================================================================
// Register Interface for TIM registers and TIM pins
//==================================================================================================
pub type TimRegBlock = tim0::RegisterBlock;
/// Register interface.
///
/// This interface provides valid TIM pins a way to access their corresponding TIM
/// registers
///
/// # Safety
///
/// Users should only implement the [Self::tim_id] function. No default function
/// implementations should be overridden. The implementing type must also have
/// "control" over the corresponding pin ID, i.e. it must guarantee that a each
/// pin ID is a singleton.
pub unsafe trait TimRegInterface {
fn tim_id(&self) -> u8;
const PORT_BASE: *const tim0::RegisterBlock = pac::Tim0::ptr() as *const _;
/// All 24 TIM blocks are identical. This helper functions returns the correct
/// memory mapped peripheral depending on the TIM ID.
#[inline(always)]
fn reg_block(&self) -> &TimRegBlock {
unsafe { &*Self::PORT_BASE.offset(self.tim_id() as isize) }
}
#[inline(always)]
fn mask_32(&self) -> u32 {
1 << self.tim_id()
}
/// Clear the reset bit of the TIM, holding it in reset
///
/// # Safety
///
/// Only the bit related to the corresponding TIM peripheral is modified
#[inline]
#[allow(dead_code)]
fn clear_tim_reset_bit(&self) {
unsafe {
va108xx::Peripherals::steal()
.sysconfig
.tim_reset()
.modify(|r, w| w.bits(r.bits() & !self.mask_32()));
}
}
#[inline]
#[allow(dead_code)]
fn set_tim_reset_bit(&self) {
unsafe {
va108xx::Peripherals::steal()
.sysconfig
.tim_reset()
.modify(|r, w| w.bits(r.bits() | self.mask_32()));
}
}
}
unsafe impl<Tim: ValidTim> TimRegInterface for Tim {
fn tim_id(&self) -> u8 {
Tim::TIM_ID
}
}
pub(crate) struct TimDynRegister {
pub(crate) tim_id: u8,
#[allow(dead_code)]
pub(crate) pin_id: DynPinId,
}
unsafe impl TimRegInterface for TimDynRegister {
#[inline(always)]
fn tim_id(&self) -> u8 {
self.tim_id
}
}
//==================================================================================================
// Timers
//==================================================================================================
/// Hardware timers
pub struct CountdownTimer<Tim: ValidTim> {
tim: Tim,
curr_freq: Hertz,
irq_cfg: Option<InterruptConfig>,
sys_clk: Hertz,
rst_val: u32,
last_cnt: u32,
listening: bool,
}
#[inline(always)]
pub fn enable_tim_clk(syscfg: &mut pac::Sysconfig, idx: u8) {
syscfg
.tim_clk_enable()
.modify(|r, w| unsafe { w.bits(r.bits() | (1 << idx)) });
}
#[inline(always)]
pub fn disable_tim_clk(syscfg: &mut pac::Sysconfig, idx: u8) {
syscfg
.tim_clk_enable()
.modify(|r, w| unsafe { w.bits(r.bits() & !(1 << idx)) });
}
unsafe impl<TIM: ValidTim> TimRegInterface for CountdownTimer<TIM> {
fn tim_id(&self) -> u8 {
TIM::TIM_ID
}
}
impl<Tim: ValidTim> CountdownTimer<Tim> {
/// Configures a TIM peripheral as a periodic count down timer
pub fn new(syscfg: &mut pac::Sysconfig, sys_clk: impl Into<Hertz>, tim: Tim) -> Self {
enable_tim_clk(syscfg, Tim::TIM_ID);
let cd_timer = CountdownTimer {
tim,
sys_clk: sys_clk.into(),
irq_cfg: None,
rst_val: 0,
curr_freq: 0.Hz(),
listening: false,
last_cnt: 0,
};
cd_timer
.tim
.reg_block()
.ctrl()
.modify(|_, w| w.enable().set_bit());
cd_timer
}
/// Listen for events. Depending on the IRQ configuration, this also activates the IRQ in the
/// IRQSEL peripheral for the provided interrupt and unmasks the interrupt
pub fn listen(
&mut self,
event: Event,
irq_cfg: InterruptConfig,
irq_sel: Option<&mut pac::Irqsel>,
sys_cfg: Option<&mut pac::Sysconfig>,
) {
match event {
Event::TimeOut => {
cortex_m::peripheral::NVIC::mask(irq_cfg.id);
self.irq_cfg = Some(irq_cfg);
if irq_cfg.route {
if let Some(sys_cfg) = sys_cfg {
enable_peripheral_clock(sys_cfg, PeripheralClocks::Irqsel);
}
if let Some(irq_sel) = irq_sel {
irq_sel
.tim0(Tim::TIM_ID as usize)
.write(|w| unsafe { w.bits(irq_cfg.id as u32) });
}
}
self.listening = true;
}
}
}
pub fn unlisten(
&mut self,
event: Event,
syscfg: &mut pac::Sysconfig,
irqsel: &mut pac::Irqsel,
) {
match event {
Event::TimeOut => {
enable_peripheral_clock(syscfg, PeripheralClocks::Irqsel);
irqsel
.tim0(Tim::TIM_ID as usize)
.write(|w| unsafe { w.bits(IRQ_DST_NONE) });
self.disable_interrupt();
self.listening = false;
}
}
}
#[inline(always)]
pub fn enable_interrupt(&mut self) {
self.tim
.reg_block()
.ctrl()
.modify(|_, w| w.irq_enb().set_bit());
}
#[inline(always)]
pub fn disable_interrupt(&mut self) {
self.tim
.reg_block()
.ctrl()
.modify(|_, w| w.irq_enb().clear_bit());
}
pub fn release(self, syscfg: &mut pac::Sysconfig) -> Tim {
self.tim
.reg_block()
.ctrl()
.write(|w| w.enable().clear_bit());
syscfg
.tim_clk_enable()
.modify(|r, w| unsafe { w.bits(r.bits() & !(1 << Tim::TIM_ID)) });
self.tim
}
/// Load the count down timer with a timeout but do not start it.
pub fn load(&mut self, timeout: impl Into<Hertz>) {
self.tim
.reg_block()
.ctrl()
.modify(|_, w| w.enable().clear_bit());
self.curr_freq = timeout.into();
self.rst_val = self.sys_clk.raw() / self.curr_freq.raw();
self.set_reload(self.rst_val);
self.set_count(self.rst_val);
}
#[inline(always)]
pub fn set_reload(&mut self, val: u32) {
self.tim
.reg_block()
.rst_value()
.write(|w| unsafe { w.bits(val) });
}
#[inline(always)]
pub fn set_count(&mut self, val: u32) {
self.tim
.reg_block()
.cnt_value()
.write(|w| unsafe { w.bits(val) });
}
#[inline(always)]
pub fn count(&self) -> u32 {
self.tim.reg_block().cnt_value().read().bits()
}
#[inline(always)]
pub fn enable(&mut self) {
if let Some(irq_cfg) = self.irq_cfg {
self.enable_interrupt();
if irq_cfg.enable_in_nvic {
unsafe { enable_nvic_interrupt(irq_cfg.id) };
}
}
self.tim
.reg_block()
.enable()
.write(|w| unsafe { w.bits(1) });
}
#[inline(always)]
pub fn disable(&mut self) {
self.tim
.reg_block()
.enable()
.write(|w| unsafe { w.bits(0) });
}
/// Disable the counter, setting both enable and active bit to 0
pub fn auto_disable(self, enable: bool) -> Self {
if enable {
self.tim
.reg_block()
.ctrl()
.modify(|_, w| w.auto_disable().set_bit());
} else {
self.tim
.reg_block()
.ctrl()
.modify(|_, w| w.auto_disable().clear_bit());
}
self
}
/// This option only applies when the Auto-Disable functionality is 0.
///
/// The active bit is changed to 0 when count reaches 0, but the counter stays
/// enabled. When Auto-Disable is 1, Auto-Deactivate is implied
pub fn auto_deactivate(self, enable: bool) -> Self {
if enable {
self.tim
.reg_block()
.ctrl()
.modify(|_, w| w.auto_deactivate().set_bit());
} else {
self.tim
.reg_block()
.ctrl()
.modify(|_, w| w.auto_deactivate().clear_bit());
}
self
}
/// Configure the cascade parameters
pub fn cascade_control(&mut self, ctrl: CascadeCtrl) {
self.tim.reg_block().csd_ctrl().write(|w| {
w.csden0().bit(ctrl.enb_start_src_csd0);
w.csdinv0().bit(ctrl.inv_csd0);
w.csden1().bit(ctrl.enb_start_src_csd1);
w.csdinv1().bit(ctrl.inv_csd1);
w.dcasop().bit(ctrl.dual_csd_op);
w.csdtrg0().bit(ctrl.trg_csd0);
w.csdtrg1().bit(ctrl.trg_csd1);
w.csden2().bit(ctrl.enb_stop_src_csd2);
w.csdinv2().bit(ctrl.inv_csd2);
w.csdtrg2().bit(ctrl.trg_csd2)
});
}
pub fn cascade_0_source(&mut self, src: CascadeSource) -> Result<(), InvalidCascadeSourceId> {
let id = src.id()?;
self.tim
.reg_block()
.cascade0()
.write(|w| unsafe { w.cassel().bits(id) });
Ok(())
}
pub fn cascade_1_source(&mut self, src: CascadeSource) -> Result<(), InvalidCascadeSourceId> {
let id = src.id()?;
self.tim
.reg_block()
.cascade1()
.write(|w| unsafe { w.cassel().bits(id) });
Ok(())
}
pub fn cascade_2_source(&mut self, src: CascadeSource) -> Result<(), InvalidCascadeSourceId> {
let id = src.id()?;
self.tim
.reg_block()
.cascade2()
.write(|w| unsafe { w.cassel().bits(id) });
Ok(())
}
pub fn curr_freq(&self) -> Hertz {
self.curr_freq
}
pub fn listening(&self) -> bool {
self.listening
}
}
/// CountDown implementation for TIMx
impl<TIM: ValidTim> CountdownTimer<TIM> {
#[inline]
pub fn start<T>(&mut self, timeout: T)
where
T: Into<Hertz>,
{
self.load(timeout);
self.enable();
}
/// Return `Ok` if the timer has wrapped. Peripheral will automatically clear the
/// flag and restart the time if configured correctly
pub fn wait(&mut self) -> nb::Result<(), void::Void> {
let cnt = self.tim.reg_block().cnt_value().read().bits();
if (cnt > self.last_cnt) || cnt == 0 {
self.last_cnt = self.rst_val;
Ok(())
} else {
self.last_cnt = cnt;
Err(nb::Error::WouldBlock)
}
}
/// Returns [false] if the timer was not active, and true otherwise.
pub fn cancel(&mut self) -> bool {
if !self.tim.reg_block().ctrl().read().enable().bit_is_set() {
return false;
}
self.tim
.reg_block()
.ctrl()
.write(|w| w.enable().clear_bit());
true
}
}
impl<TIM: ValidTim> embedded_hal::delay::DelayNs for CountdownTimer<TIM> {
fn delay_ns(&mut self, ns: u32) {
let ticks = (u64::from(ns)) * (u64::from(self.sys_clk.raw())) / 1_000_000_000;
let full_cycles = ticks >> 32;
let mut last_count;
let mut new_count;
if full_cycles > 0 {
self.set_reload(u32::MAX);
self.set_count(u32::MAX);
self.enable();
for _ in 0..full_cycles {
// Always ensure that both values are the same at the start.
new_count = self.count();
last_count = new_count;
loop {
new_count = self.count();
if new_count == 0 {
// Wait till timer has wrapped.
while self.count() == 0 {
cortex_m::asm::nop()
}
break;
}
// Timer has definitely wrapped.
if new_count > last_count {
break;
}
last_count = new_count;
}
}
}
let ticks = (ticks & u32::MAX as u64) as u32;
self.disable();
if ticks > 1 {
self.set_reload(ticks);
self.set_count(ticks);
self.enable();
last_count = ticks;
loop {
new_count = self.count();
if new_count == 0 || (new_count > last_count) {
break;
}
last_count = new_count;
}
}
self.disable();
}
}
// Set up a millisecond timer on TIM0. Please note that the user still has to provide an IRQ handler
// which should call [default_ms_irq_handler].
pub fn set_up_ms_tick<TIM: ValidTim>(
irq_cfg: InterruptConfig,
sys_cfg: &mut pac::Sysconfig,
irq_sel: Option<&mut pac::Irqsel>,
sys_clk: impl Into<Hertz>,
tim0: TIM,
) -> CountdownTimer<TIM> {
let mut ms_timer = CountdownTimer::new(sys_cfg, sys_clk, tim0);
ms_timer.listen(timer::Event::TimeOut, irq_cfg, irq_sel, Some(sys_cfg));
ms_timer.start(1000.Hz());
ms_timer
}
pub fn set_up_ms_delay_provider<TIM: ValidTim>(
sys_cfg: &mut pac::Sysconfig,
sys_clk: impl Into<Hertz>,
tim: TIM,
) -> CountdownTimer<TIM> {
let mut provider = CountdownTimer::new(sys_cfg, sys_clk, tim);
provider.start(1000.Hz());
provider
}
/// This function can be called in a specified interrupt handler to increment
/// the MS counter
pub fn default_ms_irq_handler() {
critical_section::with(|cs| {
let mut ms = MS_COUNTER.borrow(cs).get();
ms += 1;
MS_COUNTER.borrow(cs).set(ms);
});
}
/// Get the current MS tick count
pub fn get_ms_ticks() -> u32 {
critical_section::with(|cs| MS_COUNTER.borrow(cs).get())
}
//==================================================================================================
// Delay implementations
//==================================================================================================
pub struct DelayMs(CountdownTimer<pac::Tim0>);
impl DelayMs {
pub fn new(timer: CountdownTimer<pac::Tim0>) -> Option<Self> {
if timer.curr_freq() != Hertz::from_raw(1000) || !timer.listening() {
return None;
}
Some(Self(timer))
}
}
/// This assumes that the user has already set up a MS tick timer in TIM0 as a system tick
/// with [`set_up_ms_delay_provider`]
impl embedded_hal::delay::DelayNs for DelayMs {
fn delay_ns(&mut self, ns: u32) {
let ns_as_ms = ns / 1_000_000;
if self.0.curr_freq() != Hertz::from_raw(1000) || !self.0.listening() {
return;
}
let start_time = get_ms_ticks();
while get_ms_ticks() - start_time < ns_as_ms {
cortex_m::asm::nop();
}
}
}
pub use vorago_shared_periphs::timer::*;

View File

@ -1,155 +0,0 @@
//! Module supporting type-level programming
//!
//! This module is identical to the
//! [atsamd typelevel](https://docs.rs/atsamd-hal/latest/atsamd_hal/typelevel/index.html).
use core::ops::{Add, Sub};
use typenum::{Add1, Bit, Sub1, UInt, Unsigned, B1, U0};
mod private {
/// Super trait used to mark traits with an exhaustive set of
/// implementations
pub trait Sealed {}
impl Sealed for u8 {}
impl Sealed for i8 {}
impl Sealed for u16 {}
impl Sealed for i16 {}
impl Sealed for u32 {}
impl Sealed for i32 {}
impl Sealed for f32 {}
/// Mapping from an instance of a countable type to its successor
pub trait Increment {
/// Successor type of `Self`
type Inc;
/// Consume an instance of `Self` and return its successor
fn inc(self) -> Self::Inc;
}
/// Mapping from an instance of a countable type to its predecessor
pub trait Decrement {
/// Predecessor type of `Self`
type Dec;
/// Consume an instance of `Self` and return its predecessor
fn dec(self) -> Self::Dec;
}
}
pub(crate) use private::Decrement as PrivateDecrement;
pub(crate) use private::Increment as PrivateIncrement;
pub(crate) use private::Sealed;
/// Type-level version of the [`None`] variant
#[derive(Default)]
pub struct NoneT;
impl Sealed for NoneT {}
//==============================================================================
// Is
//==============================================================================
/// Marker trait for type identity
///
/// This trait is used as part of the [`AnyKind`] trait pattern. It represents
/// the concept of type identity, because all implementors have
/// `<Self as Is>::Type == Self`. When used as a trait bound with a specific
/// type, it guarantees that the corresponding type parameter is exactly the
/// specific type. Stated differently, it guarantees that `T == Specific` in
/// the following example.
///
/// ```ignore
/// where T: Is<Type = Specific>
/// ```
///
/// Moreover, the super traits guarantee that any instance of or reference to a
/// type `T` can be converted into the `Specific` type.
///
/// ```ignore
/// fn example<T>(mut any: T)
/// where
/// T: Is<Type = Specific>,
/// {
/// let specific_mut: &mut Specific = any.as_mut();
/// let specific_ref: &Specific = any.as_ref();
/// let specific: Specific = any.into();
/// }
/// ```
///
/// [`AnyKind`]: #anykind-trait-pattern
pub trait Is
where
Self: Sealed,
Self: From<IsType<Self>>,
Self: Into<IsType<Self>>,
Self: AsRef<IsType<Self>>,
Self: AsMut<IsType<Self>>,
{
type Type;
}
/// Type alias for [`Is::Type`]
pub type IsType<T> = <T as Is>::Type;
impl<T> Is for T
where
T: Sealed + AsRef<T> + AsMut<T>,
{
type Type = T;
}
//==============================================================================
// Counting
//==============================================================================
/// Implement `Sealed` for [`U0`]
impl Sealed for U0 {}
/// Implement `Sealed` for all type-level, [`Unsigned`] integers *except* [`U0`]
impl<U: Unsigned, B: Bit> Sealed for UInt<U, B> {}
/// Trait mapping each countable type to its successor
///
/// This trait maps each countable type to its corresponding successor type. The
/// actual implementation of this trait is contained within `PrivateIncrement`.
/// Access to `PrivateIncrement` is restricted, so that safe HAL APIs can be
/// built with it.
pub trait Increment: PrivateIncrement {}
impl<T: PrivateIncrement> Increment for T {}
/// Trait mapping each countable type to its predecessor
///
/// This trait maps each countable type to its corresponding predecessor type.
/// The actual implementation of this trait is contained within
/// `PrivateDecrement`. Access to `PrivateDecrement` is restricted, so that safe
/// HAL APIs can be built with it.
pub trait Decrement: PrivateDecrement {}
impl<T: PrivateDecrement> Decrement for T {}
impl<N> PrivateIncrement for N
where
N: Unsigned + Add<B1>,
Add1<N>: Unsigned,
{
type Inc = Add1<N>;
#[inline]
fn inc(self) -> Self::Inc {
Self::Inc::default()
}
}
impl<N> PrivateDecrement for N
where
N: Unsigned + Sub<B1>,
Sub1<N>: Unsigned,
{
type Dec = Sub1<N>;
#[inline]
fn dec(self) -> Self::Dec {
Self::Dec::default()
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,440 +0,0 @@
//! # Async UART reception functionality for the VA416xx family.
//!
//! This module provides the [RxAsync] and [RxAsyncOverwriting] struct which both implement the
//! [embedded_io_async::Read] trait.
//! This trait allows for asynchronous reception of data streams. Please note that this module does
//! not specify/declare the interrupt handlers which must be provided for async support to work.
//! However, it provides two interrupt handlers:
//!
//! - [on_interrupt_rx]
//! - [on_interrupt_rx_overwriting]
//!
//! The first two are used for the [RxAsync] struct, while the latter two are used with the
//! [RxAsyncOverwriting] struct. The later two will overwrite old values in the used ring buffer.
//!
//! Error handling is performed in the user interrupt handler by checking the [AsyncUartErrors]
//! structure returned by the interrupt handlers.
//!
//! # Example
//!
//! - [Async UART RX example](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples/embassy/src/bin/async-uart-rx.rs)
use core::{cell::RefCell, convert::Infallible, future::Future, sync::atomic::Ordering};
use critical_section::Mutex;
use embassy_sync::waitqueue::AtomicWaker;
use embedded_io::ErrorType;
use portable_atomic::AtomicBool;
use va108xx::uarta as uart_base;
use super::{Bank, Instance, Rx, UartErrors};
static UART_RX_WAKERS: [AtomicWaker; 2] = [const { AtomicWaker::new() }; 2];
static RX_READ_ACTIVE: [AtomicBool; 2] = [const { AtomicBool::new(false) }; 2];
static RX_HAS_DATA: [AtomicBool; 2] = [const { AtomicBool::new(false) }; 2];
struct RxFuture {
uart_idx: usize,
}
impl RxFuture {
pub fn new<Uart: Instance>(_rx: &mut Rx<Uart>) -> Self {
RX_READ_ACTIVE[Uart::IDX as usize].store(true, Ordering::Relaxed);
Self {
uart_idx: Uart::IDX as usize,
}
}
}
impl Future for RxFuture {
type Output = Result<(), Infallible>;
fn poll(
self: core::pin::Pin<&mut Self>,
cx: &mut core::task::Context<'_>,
) -> core::task::Poll<Self::Output> {
UART_RX_WAKERS[self.uart_idx].register(cx.waker());
if RX_HAS_DATA[self.uart_idx].load(Ordering::Relaxed) {
return core::task::Poll::Ready(Ok(()));
}
core::task::Poll::Pending
}
}
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct AsyncUartErrors {
/// Queue has overflowed, data might have been lost.
pub queue_overflow: bool,
/// UART errors.
pub uart_errors: UartErrors,
}
fn on_interrupt_handle_rx_errors(uart: &'static uart_base::RegisterBlock) -> Option<UartErrors> {
let rx_status = uart.rxstatus().read();
if rx_status.rxovr().bit_is_set()
|| rx_status.rxfrm().bit_is_set()
|| rx_status.rxpar().bit_is_set()
{
let mut errors_val = UartErrors::default();
if rx_status.rxovr().bit_is_set() {
errors_val.overflow = true;
}
if rx_status.rxfrm().bit_is_set() {
errors_val.framing = true;
}
if rx_status.rxpar().bit_is_set() {
errors_val.parity = true;
}
return Some(errors_val);
}
None
}
fn on_interrupt_rx_common_post_processing(
bank: Bank,
rx_enabled: bool,
read_some_data: bool,
irq_end: u32,
) -> Option<UartErrors> {
let idx = bank as usize;
if read_some_data {
RX_HAS_DATA[idx].store(true, Ordering::Relaxed);
if RX_READ_ACTIVE[idx].load(Ordering::Relaxed) {
UART_RX_WAKERS[idx].wake();
}
}
let mut errors = None;
let uart_regs = unsafe { bank.reg_block() };
// Check for RX errors
if rx_enabled {
errors = on_interrupt_handle_rx_errors(uart_regs);
}
// Clear the interrupt status bits
uart_regs.irq_clr().write(|w| unsafe { w.bits(irq_end) });
errors
}
/// Interrupt handler with overwriting behaviour when the ring buffer is full.
///
/// Should be called in the user interrupt handler to enable
/// asynchronous reception. This variant will overwrite old data in the ring buffer in case
/// the ring buffer is full.
pub fn on_interrupt_rx_overwriting<const N: usize>(
bank: Bank,
prod: &mut heapless::spsc::Producer<u8, N>,
shared_consumer: &Mutex<RefCell<Option<heapless::spsc::Consumer<'static, u8, N>>>>,
) -> Result<(), AsyncUartErrors> {
on_interrupt_rx_async_heapless_queue_overwriting(bank, prod, shared_consumer)
}
pub fn on_interrupt_rx_async_heapless_queue_overwriting<const N: usize>(
bank: Bank,
prod: &mut heapless::spsc::Producer<u8, N>,
shared_consumer: &Mutex<RefCell<Option<heapless::spsc::Consumer<'static, u8, N>>>>,
) -> Result<(), AsyncUartErrors> {
let uart_regs = unsafe { bank.reg_block() };
let irq_end = uart_regs.irq_end().read();
let enb_status = uart_regs.enable().read();
let rx_enabled = enb_status.rxenable().bit_is_set();
let mut read_some_data = false;
let mut queue_overflow = false;
// Half-Full interrupt. We have a guaranteed amount of data we can read.
if irq_end.irq_rx().bit_is_set() {
let available_bytes = uart_regs.rxfifoirqtrg().read().bits() as usize;
// If this interrupt bit is set, the trigger level is available at the very least.
// Read everything as fast as possible
for _ in 0..available_bytes {
let byte = uart_regs.data().read().bits();
if !prod.ready() {
queue_overflow = true;
critical_section::with(|cs| {
let mut cons_ref = shared_consumer.borrow(cs).borrow_mut();
cons_ref.as_mut().unwrap().dequeue();
});
}
prod.enqueue(byte as u8).ok();
}
read_some_data = true;
}
// Timeout, empty the FIFO completely.
if irq_end.irq_rx_to().bit_is_set() {
while uart_regs.rxstatus().read().rdavl().bit_is_set() {
// While there is data in the FIFO, write it into the reception buffer
let byte = uart_regs.data().read().bits();
if !prod.ready() {
queue_overflow = true;
critical_section::with(|cs| {
let mut cons_ref = shared_consumer.borrow(cs).borrow_mut();
cons_ref.as_mut().unwrap().dequeue();
});
}
prod.enqueue(byte as u8).ok();
}
read_some_data = true;
}
let uart_errors =
on_interrupt_rx_common_post_processing(bank, rx_enabled, read_some_data, irq_end.bits());
if uart_errors.is_some() || queue_overflow {
return Err(AsyncUartErrors {
queue_overflow,
uart_errors: uart_errors.unwrap_or_default(),
});
}
Ok(())
}
/// Interrupt handler for asynchronous RX operations.
///
/// Should be called in the user interrupt handler to enable asynchronous reception.
pub fn on_interrupt_rx<const N: usize>(
bank: Bank,
prod: &mut heapless::spsc::Producer<'_, u8, N>,
) -> Result<(), AsyncUartErrors> {
on_interrupt_rx_async_heapless_queue(bank, prod)
}
pub fn on_interrupt_rx_async_heapless_queue<const N: usize>(
bank: Bank,
prod: &mut heapless::spsc::Producer<'_, u8, N>,
) -> Result<(), AsyncUartErrors> {
let uart = unsafe { bank.reg_block() };
let irq_end = uart.irq_end().read();
let enb_status = uart.enable().read();
let rx_enabled = enb_status.rxenable().bit_is_set();
let mut read_some_data = false;
let mut queue_overflow = false;
// Half-Full interrupt. We have a guaranteed amount of data we can read.
if irq_end.irq_rx().bit_is_set() {
let available_bytes = uart.rxfifoirqtrg().read().bits() as usize;
// If this interrupt bit is set, the trigger level is available at the very least.
// Read everything as fast as possible
for _ in 0..available_bytes {
let byte = uart.data().read().bits();
if !prod.ready() {
queue_overflow = true;
}
prod.enqueue(byte as u8).ok();
}
read_some_data = true;
}
// Timeout, empty the FIFO completely.
if irq_end.irq_rx_to().bit_is_set() {
while uart.rxstatus().read().rdavl().bit_is_set() {
// While there is data in the FIFO, write it into the reception buffer
let byte = uart.data().read().bits();
if !prod.ready() {
queue_overflow = true;
}
prod.enqueue(byte as u8).ok();
}
read_some_data = true;
}
let uart_errors =
on_interrupt_rx_common_post_processing(bank, rx_enabled, read_some_data, irq_end.bits());
if uart_errors.is_some() || queue_overflow {
return Err(AsyncUartErrors {
queue_overflow,
uart_errors: uart_errors.unwrap_or_default(),
});
}
Ok(())
}
struct ActiveReadGuard(usize);
impl Drop for ActiveReadGuard {
fn drop(&mut self) {
RX_READ_ACTIVE[self.0].store(false, Ordering::Relaxed);
}
}
struct RxAsyncInner<Uart: Instance, const N: usize> {
rx: Rx<Uart>,
pub queue: heapless::spsc::Consumer<'static, u8, N>,
}
/// Core data structure to allow asynchronous UART reception.
///
/// If the ring buffer becomes full, data will be lost.
pub struct RxAsync<Uart: Instance, const N: usize>(Option<RxAsyncInner<Uart, N>>);
impl<Uart: Instance, const N: usize> ErrorType for RxAsync<Uart, N> {
/// Error reporting is done using the result of the interrupt functions.
type Error = Infallible;
}
fn stop_async_rx<Uart: Instance>(rx: &mut Rx<Uart>) {
rx.disable_interrupts();
rx.disable();
rx.clear_fifo();
}
impl<Uart: Instance, const N: usize> RxAsync<Uart, N> {
/// Create a new asynchronous receiver.
///
/// The passed [heapless::spsc::Consumer] will be used to asynchronously receive data which
/// is filled by the interrupt handler [on_interrupt_rx].
pub fn new(mut rx: Rx<Uart>, queue: heapless::spsc::Consumer<'static, u8, N>) -> Self {
rx.disable_interrupts();
rx.disable();
rx.clear_fifo();
// Enable those together.
critical_section::with(|_| {
rx.enable_interrupts();
rx.enable();
});
Self(Some(RxAsyncInner { rx, queue }))
}
pub fn stop(&mut self) {
stop_async_rx(&mut self.0.as_mut().unwrap().rx);
}
pub fn release(mut self) -> (Rx<Uart>, heapless::spsc::Consumer<'static, u8, N>) {
self.stop();
let inner = self.0.take().unwrap();
(inner.rx, inner.queue)
}
}
impl<Uart: Instance, const N: usize> Drop for RxAsync<Uart, N> {
fn drop(&mut self) {
self.stop();
}
}
impl<Uart: Instance, const N: usize> embedded_io_async::Read for RxAsync<Uart, N> {
async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
// Need to wait for the IRQ to read data and set this flag. If the queue is not
// empty, we can read data immediately.
if self.0.as_ref().unwrap().queue.len() == 0 {
RX_HAS_DATA[Uart::IDX as usize].store(false, Ordering::Relaxed);
}
let _guard = ActiveReadGuard(Uart::IDX as usize);
let mut handle_data_in_queue = |consumer: &mut heapless::spsc::Consumer<'static, u8, N>| {
let data_to_read = consumer.len().min(buf.len());
for byte in buf.iter_mut().take(data_to_read) {
// We own the consumer and we checked that the amount of data is guaranteed to be available.
*byte = unsafe { consumer.dequeue_unchecked() };
}
data_to_read
};
let mut_ref = self.0.as_mut().unwrap();
let fut = RxFuture::new(&mut mut_ref.rx);
// Data is available, so read that data immediately.
let read_data = handle_data_in_queue(&mut mut_ref.queue);
if read_data > 0 {
return Ok(read_data);
}
// Await data.
let _ = fut.await;
Ok(handle_data_in_queue(&mut mut_ref.queue))
}
}
struct RxAsyncOverwritingInner<Uart: Instance, const N: usize> {
rx: Rx<Uart>,
pub shared_consumer: &'static Mutex<RefCell<Option<heapless::spsc::Consumer<'static, u8, N>>>>,
}
/// Core data structure to allow asynchronous UART reception.
///
/// If the ring buffer becomes full, the oldest data will be overwritten when using the
/// [on_interrupt_rx_overwriting] interrupt handlers.
pub struct RxAsyncOverwriting<Uart: Instance, const N: usize>(
Option<RxAsyncOverwritingInner<Uart, N>>,
);
impl<Uart: Instance, const N: usize> ErrorType for RxAsyncOverwriting<Uart, N> {
/// Error reporting is done using the result of the interrupt functions.
type Error = Infallible;
}
impl<Uart: Instance, const N: usize> RxAsyncOverwriting<Uart, N> {
/// Create a new asynchronous receiver.
///
/// The passed shared [heapless::spsc::Consumer] will be used to asynchronously receive data
/// which is filled by the interrupt handler. The shared property allows using it in the
/// interrupt handler to overwrite old data.
pub fn new(
mut rx: Rx<Uart>,
shared_consumer: &'static Mutex<RefCell<Option<heapless::spsc::Consumer<'static, u8, N>>>>,
) -> Self {
rx.disable_interrupts();
rx.disable();
rx.clear_fifo();
// Enable those together.
critical_section::with(|_| {
rx.enable_interrupts();
rx.enable();
});
Self(Some(RxAsyncOverwritingInner {
rx,
shared_consumer,
}))
}
pub fn stop(&mut self) {
stop_async_rx(&mut self.0.as_mut().unwrap().rx);
}
pub fn release(mut self) -> Rx<Uart> {
self.stop();
let inner = self.0.take().unwrap();
inner.rx
}
}
impl<Uart: Instance, const N: usize> Drop for RxAsyncOverwriting<Uart, N> {
fn drop(&mut self) {
self.stop();
}
}
impl<Uart: Instance, const N: usize> embedded_io_async::Read for RxAsyncOverwriting<Uart, N> {
async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
// Need to wait for the IRQ to read data and set this flag. If the queue is not
// empty, we can read data immediately.
critical_section::with(|cs| {
let queue = self.0.as_ref().unwrap().shared_consumer.borrow(cs);
if queue.borrow().as_ref().unwrap().len() == 0 {
RX_HAS_DATA[Uart::IDX as usize].store(false, Ordering::Relaxed);
}
});
let _guard = ActiveReadGuard(Uart::IDX as usize);
let mut handle_data_in_queue = |inner: &mut RxAsyncOverwritingInner<Uart, N>| {
critical_section::with(|cs| {
let mut consumer_ref = inner.shared_consumer.borrow(cs).borrow_mut();
let consumer = consumer_ref.as_mut().unwrap();
let data_to_read = consumer.len().min(buf.len());
for byte in buf.iter_mut().take(data_to_read) {
// We own the consumer and we checked that the amount of data is guaranteed to be available.
*byte = unsafe { consumer.dequeue_unchecked() };
}
data_to_read
})
};
let fut = RxFuture::new(&mut self.0.as_mut().unwrap().rx);
// Data is available, so read that data immediately.
let read_data = handle_data_in_queue(self.0.as_mut().unwrap());
if read_data > 0 {
return Ok(read_data);
}
// Await data.
let _ = fut.await;
let read_data = handle_data_in_queue(self.0.as_mut().unwrap());
Ok(read_data)
}
}

View File

@ -1,254 +0,0 @@
//! # Async UART transmission functionality for the VA108xx family.
//!
//! This module provides the [TxAsync] struct which implements the [embedded_io_async::Write] trait.
//! This trait allows for asynchronous sending of data streams. Please note that this module does
//! not specify/declare the interrupt handlers which must be provided for async support to work.
//! However, it the [on_interrupt_tx] interrupt handler.
//!
//! This handler should be called in ALL user interrupt handlers which handle UART TX interrupts
//! for a given UART bank.
//!
//! # Example
//!
//! - [Async UART TX example](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples/embassy/src/bin/async-uart-tx.rs)
use core::{cell::RefCell, future::Future};
use critical_section::Mutex;
use embassy_sync::waitqueue::AtomicWaker;
use embedded_io_async::Write;
use portable_atomic::AtomicBool;
use super::*;
static UART_TX_WAKERS: [AtomicWaker; 2] = [const { AtomicWaker::new() }; 2];
static TX_CONTEXTS: [Mutex<RefCell<TxContext>>; 2] =
[const { Mutex::new(RefCell::new(TxContext::new())) }; 2];
// Completion flag. Kept outside of the context structure as an atomic to avoid
// critical section.
static TX_DONE: [AtomicBool; 2] = [const { AtomicBool::new(false) }; 2];
/// This is a generic interrupt handler to handle asynchronous UART TX operations for a given
/// UART bank.
///
/// The user has to call this once in the interrupt handler responsible for the TX interrupts on
/// the given UART bank.
pub fn on_interrupt_tx(bank: Bank) {
let uart = unsafe { bank.reg_block() };
let idx = bank as usize;
let irq_enb = uart.irq_enb().read();
// IRQ is not related to TX.
if irq_enb.irq_tx().bit_is_clear() || irq_enb.irq_tx_empty().bit_is_clear() {
return;
}
let tx_status = uart.txstatus().read();
let unexpected_overrun = tx_status.wrlost().bit_is_set();
let mut context = critical_section::with(|cs| {
let context_ref = TX_CONTEXTS[idx].borrow(cs);
*context_ref.borrow()
});
context.tx_overrun = unexpected_overrun;
if context.progress >= context.slice.len && !tx_status.wrbusy().bit_is_set() {
uart.irq_enb().modify(|_, w| {
w.irq_tx().clear_bit();
w.irq_tx_empty().clear_bit();
w.irq_tx_status().clear_bit()
});
uart.enable().modify(|_, w| w.txenable().clear_bit());
// Write back updated context structure.
critical_section::with(|cs| {
let context_ref = TX_CONTEXTS[idx].borrow(cs);
*context_ref.borrow_mut() = context;
});
// Transfer is done.
TX_DONE[idx].store(true, core::sync::atomic::Ordering::Relaxed);
UART_TX_WAKERS[idx].wake();
return;
}
// Safety: We documented that the user provided slice must outlive the future, so we convert
// the raw pointer back to the slice here.
let slice = unsafe { core::slice::from_raw_parts(context.slice.data, context.slice.len) };
while context.progress < context.slice.len {
let wrrdy = uart.txstatus().read().wrrdy().bit_is_set();
if !wrrdy {
break;
}
// Safety: TX structure is owned by the future which does not write into the the data
// register, so we can assume we are the only one writing to the data register.
uart.data()
.write(|w| unsafe { w.bits(slice[context.progress] as u32) });
context.progress += 1;
}
// Write back updated context structure.
critical_section::with(|cs| {
let context_ref = TX_CONTEXTS[idx].borrow(cs);
*context_ref.borrow_mut() = context;
});
}
#[derive(Debug, Copy, Clone)]
pub struct TxContext {
progress: usize,
tx_overrun: bool,
slice: RawBufSlice,
}
#[allow(clippy::new_without_default)]
impl TxContext {
pub const fn new() -> Self {
Self {
progress: 0,
tx_overrun: false,
slice: RawBufSlice::new_empty(),
}
}
}
#[derive(Debug, Copy, Clone)]
struct RawBufSlice {
data: *const u8,
len: usize,
}
/// Safety: This type MUST be used with mutex to ensure concurrent access is valid.
unsafe impl Send for RawBufSlice {}
impl RawBufSlice {
/// # Safety
///
/// This function stores the raw pointer of the passed data slice. The user MUST ensure
/// that the slice outlives the data structure.
#[allow(dead_code)]
const unsafe fn new(data: &[u8]) -> Self {
Self {
data: data.as_ptr(),
len: data.len(),
}
}
const fn new_empty() -> Self {
Self {
data: core::ptr::null(),
len: 0,
}
}
/// # Safety
///
/// This function stores the raw pointer of the passed data slice. The user MUST ensure
/// that the slice outlives the data structure.
pub unsafe fn set(&mut self, data: &[u8]) {
self.data = data.as_ptr();
self.len = data.len();
}
}
pub struct TxFuture {
uart_idx: usize,
}
impl TxFuture {
/// # Safety
///
/// This function stores the raw pointer of the passed data slice. The user MUST ensure
/// that the slice outlives the data structure.
pub unsafe fn new<Uart: Instance>(tx: &mut Tx<Uart>, data: &[u8]) -> Self {
TX_DONE[Uart::IDX as usize].store(false, core::sync::atomic::Ordering::Relaxed);
tx.disable_interrupts();
tx.disable();
tx.clear_fifo();
let uart_tx = unsafe { tx.uart() };
let init_fill_count = core::cmp::min(data.len(), 16);
// We fill the FIFO.
for data in data.iter().take(init_fill_count) {
uart_tx.data().write(|w| unsafe { w.bits(*data as u32) });
}
critical_section::with(|cs| {
let context_ref = TX_CONTEXTS[Uart::IDX as usize].borrow(cs);
let mut context = context_ref.borrow_mut();
context.slice.set(data);
context.progress = init_fill_count;
// Ensure those are enabled inside a critical section at the same time. Can lead to
// weird glitches otherwise.
tx.enable_interrupts();
tx.enable();
});
Self {
uart_idx: Uart::IDX as usize,
}
}
}
impl Future for TxFuture {
type Output = Result<usize, TxOverrunError>;
fn poll(
self: core::pin::Pin<&mut Self>,
cx: &mut core::task::Context<'_>,
) -> core::task::Poll<Self::Output> {
UART_TX_WAKERS[self.uart_idx].register(cx.waker());
if TX_DONE[self.uart_idx].swap(false, core::sync::atomic::Ordering::Relaxed) {
let progress = critical_section::with(|cs| {
TX_CONTEXTS[self.uart_idx].borrow(cs).borrow().progress
});
return core::task::Poll::Ready(Ok(progress));
}
core::task::Poll::Pending
}
}
impl Drop for TxFuture {
fn drop(&mut self) {
let reg_block = match self.uart_idx {
0 => unsafe { pac::Uarta::reg_block() },
1 => unsafe { pac::Uartb::reg_block() },
_ => unreachable!(),
};
disable_tx_interrupts(reg_block);
disable_tx(reg_block);
}
}
pub struct TxAsync<Uart: Instance> {
tx: Tx<Uart>,
}
impl<Uart: Instance> TxAsync<Uart> {
pub fn new(tx: Tx<Uart>) -> Self {
Self { tx }
}
pub fn release(self) -> Tx<Uart> {
self.tx
}
}
#[derive(Debug, thiserror::Error)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[error("TX overrun error")]
pub struct TxOverrunError;
impl embedded_io_async::Error for TxOverrunError {
fn kind(&self) -> embedded_io_async::ErrorKind {
embedded_io_async::ErrorKind::Other
}
}
impl<Uart: Instance> embedded_io::ErrorType for TxAsync<Uart> {
type Error = TxOverrunError;
}
impl<Uart: Instance> Write for TxAsync<Uart> {
/// Write a buffer asynchronously.
///
/// This implementation is not side effect free, and a started future might have already
/// written part of the passed buffer.
async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
let fut = unsafe { TxFuture::new(&mut self.tx, buf) };
fut.await
}
}

View File

@ -15,10 +15,11 @@ cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
cortex-m-rt = "0.7"
embedded-hal = "1"
nb = "1"
bitfield = ">=0.17, <=0.19"
bitbybit = "1.3"
arbitrary-int = "1.3"
max116xx-10bit = "0.3"
va108xx-hal = { version = ">=0.10, <=0.11", features = ["rt"] }
va108xx-hal = { version = ">=0.10, <=0.11", path = "../va108xx-hal", features = ["rt"] }
[features]
rt = ["va108xx-hal/rt"]

View File

@ -4,17 +4,17 @@ use cortex_m_rt::entry;
use embedded_hal::delay::DelayNs;
use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print};
use va108xx_hal::{pac, prelude::*, timer::set_up_ms_delay_provider};
use va108xx_hal::{pac, prelude::*, timer::CountdownTimer};
use vorago_reb1::temp_sensor::Adt75TempSensor;
#[entry]
fn main() -> ! {
rtt_init_print!();
rprintln!("-- Vorago Temperature Sensor and I2C Example --");
let mut dp = pac::Peripherals::take().unwrap();
let mut delay = set_up_ms_delay_provider(&mut dp.sysconfig, 50.MHz(), dp.tim0);
let mut temp_sensor = Adt75TempSensor::new(&mut dp.sysconfig, 50.MHz(), dp.i2ca)
.expect("Creating temperature sensor struct failed");
let dp = pac::Peripherals::take().unwrap();
let mut delay = CountdownTimer::new(dp.tim0, 50.MHz());
let mut temp_sensor =
Adt75TempSensor::new(50.MHz(), dp.i2ca).expect("Creating temperature sensor struct failed");
loop {
let temp = temp_sensor
.read_temperature()

View File

@ -9,13 +9,14 @@ use embedded_hal::delay::DelayNs;
use embedded_hal::spi::{SpiBus, MODE_3};
use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print};
use va108xx_hal::spi::SpiClkConfig;
use va108xx_hal::gpio::{Output, PinState};
use va108xx_hal::pins::PinsA;
use va108xx_hal::spi::{configure_pin_as_hw_cs_pin, SpiClkConfig};
use va108xx_hal::timer::CountdownTimer;
use va108xx_hal::{
gpio::PinsA,
pac,
prelude::*,
spi::{Spi, SpiConfig},
timer::set_up_ms_delay_provider,
};
const READ_MASK: u8 = 1 << 7;
@ -29,19 +30,15 @@ const PWR_MEASUREMENT_MODE_MASK: u8 = 1 << 3;
fn main() -> ! {
rtt_init_print!();
rprintln!("-- Vorago Accelerometer Example --");
let mut dp = pac::Peripherals::take().unwrap();
let mut delay = set_up_ms_delay_provider(&mut dp.sysconfig, 50.MHz(), dp.tim0);
let pinsa = PinsA::new(&mut dp.sysconfig, dp.porta);
let (sck, mosi, miso) = (
pinsa.pa20.into_funsel_2(),
pinsa.pa19.into_funsel_2(),
pinsa.pa18.into_funsel_2(),
);
let cs_pin = pinsa.pa16.into_funsel_2();
let dp = pac::Peripherals::take().unwrap();
let pinsa = PinsA::new(dp.porta);
let mut delay = CountdownTimer::new(dp.tim0, 50.MHz());
let (sck, mosi, miso) = (pinsa.pa20, pinsa.pa19, pinsa.pa18);
let cs_pin = pinsa.pa16;
let hw_cs_id = configure_pin_as_hw_cs_pin(cs_pin);
// Need to set the ADC chip select low
let mut adc_cs = pinsa.pa17.into_push_pull_output();
adc_cs.set_high();
Output::new(pinsa.pa17, PinState::Low);
let spi_cfg = SpiConfig::default()
.clk_cfg(
@ -49,14 +46,8 @@ fn main() -> ! {
)
.mode(MODE_3)
.slave_output_disable(true);
let mut spi = Spi::new(
&mut dp.sysconfig,
50.MHz(),
dp.spib,
(sck, miso, mosi),
spi_cfg,
);
spi.cfg_hw_cs_with_pin(&cs_pin);
let mut spi = Spi::new(dp.spib, (sck, miso, mosi), spi_cfg).unwrap();
spi.cfg_hw_cs(hw_cs_id);
let mut tx_rx_buf: [u8; 3] = [0; 3];
tx_rx_buf[0] = READ_MASK | DEVID_REG;

View File

@ -10,10 +10,10 @@ use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print};
use va108xx_hal::{
clock::{set_clk_div_register, FilterClkSel},
gpio::{FilterType, InterruptEdge, PinsA},
gpio::{FilterType, InterruptEdge},
pac::{self, interrupt},
prelude::*,
timer::{default_ms_irq_handler, set_up_ms_tick, InterruptConfig},
pins::PinsA,
timer::InterruptConfig,
};
use vorago_reb1::button::Button;
use vorago_reb1::leds::Leds;
@ -35,18 +35,18 @@ fn main() -> ! {
rtt_init_print!();
rprintln!("-- Vorago Button IRQ Example --");
let mut dp = pac::Peripherals::take().unwrap();
let pinsa = PinsA::new(&mut dp.sysconfig, dp.porta);
let pinsa = PinsA::new(dp.porta);
let edge_irq = match PRESS_MODE {
PressMode::Toggle => InterruptEdge::HighToLow,
PressMode::Keep => InterruptEdge::BothEdges,
};
// Configure an edge interrupt on the button and route it to interrupt vector 15
let mut button = Button::new(pinsa.pa11.into_floating_input());
let mut button = Button::new(pinsa.pa11);
if PRESS_MODE == PressMode::Toggle {
// This filter debounces the switch for edge based interrupts
button.configure_filter_type(FilterType::FilterFourClockCycles, FilterClkSel::Clk1);
button.configure_filter_type(FilterType::FilterFourCycles, FilterClkSel::Clk1);
set_clk_div_register(&mut dp.sysconfig, FilterClkSel::Clk1, 50_000);
}
button.configure_and_enable_edge_interrupt(
@ -54,18 +54,7 @@ fn main() -> ! {
InterruptConfig::new(pac::interrupt::OC15, true, true),
);
set_up_ms_tick(
InterruptConfig::new(pac::Interrupt::OC0, true, true),
&mut dp.sysconfig,
Some(&mut dp.irqsel),
50.MHz(),
dp.tim0,
);
let mut leds = Leds::new(
pinsa.pa10.into_push_pull_output(),
pinsa.pa7.into_push_pull_output(),
pinsa.pa6.into_push_pull_output(),
);
let mut leds = Leds::new(pinsa.pa10, pinsa.pa7, pinsa.pa6);
for led in leds.iter_mut() {
led.off();
}
@ -79,11 +68,6 @@ fn main() -> ! {
}
}
#[interrupt]
fn OC0() {
default_ms_irq_handler();
}
#[interrupt]
fn OC15() {
cortex_m::interrupt::free(|cs| {

View File

@ -9,7 +9,13 @@
use cortex_m_rt::entry;
use embedded_hal::delay::DelayNs;
use panic_halt as _;
use va108xx_hal::{gpio::PinsA, pac, prelude::*, timer::set_up_ms_delay_provider};
use va108xx_hal::{
gpio::{Output, PinState},
pac,
pins::PinsA,
prelude::*,
timer::CountdownTimer,
};
use vorago_reb1::leds::Leds;
// REB LED pin definitions. All on port A
@ -26,7 +32,7 @@ enum LibType {
#[entry]
fn main() -> ! {
let mut dp = pac::Peripherals::take().unwrap();
let dp = pac::Peripherals::take().unwrap();
let lib_type = LibType::Bsp;
@ -60,11 +66,11 @@ fn main() -> ! {
}
}
LibType::Hal => {
let pins = PinsA::new(&mut dp.sysconfig, dp.porta);
let mut led1 = pins.pa10.into_readable_push_pull_output();
let mut led2 = pins.pa7.into_readable_push_pull_output();
let mut led3 = pins.pa6.into_readable_push_pull_output();
let mut delay = set_up_ms_delay_provider(&mut dp.sysconfig, 50.MHz(), dp.tim0);
let pins = PinsA::new(dp.porta);
let mut led1 = Output::new(pins.pa10, PinState::Low);
let mut led2 = Output::new(pins.pa7, PinState::Low);
let mut led3 = Output::new(pins.pa6, PinState::Low);
let mut delay = CountdownTimer::new(dp.tim0, 50.MHz());
for _ in 0..10 {
led1.set_low();
led2.set_low();
@ -83,13 +89,9 @@ fn main() -> ! {
}
}
LibType::Bsp => {
let pinsa = PinsA::new(&mut dp.sysconfig, dp.porta);
let mut leds = Leds::new(
pinsa.pa10.into_push_pull_output(),
pinsa.pa7.into_push_pull_output(),
pinsa.pa6.into_push_pull_output(),
);
let mut delay = set_up_ms_delay_provider(&mut dp.sysconfig, 50.MHz(), dp.tim0);
let pinsa = PinsA::new(dp.porta);
let mut leds = Leds::new(pinsa.pa10, pinsa.pa7, pinsa.pa6);
let mut delay = CountdownTimer::new(dp.tim0, 50.MHz());
for _ in 0..10 {
// Blink all LEDs quickly
for led in leds.iter_mut() {

View File

@ -14,20 +14,19 @@ use max116xx_10bit::VoltageRefMode;
use max116xx_10bit::{AveragingConversions, AveragingResults};
use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print};
use va108xx_hal::gpio::Port;
use va108xx_hal::spi::{OptionalHwCs, SpiClkConfig};
use va108xx_hal::gpio::{Input, Output, PinState, Port};
use va108xx_hal::pins::PinsA;
use va108xx_hal::spi::{configure_pin_as_hw_cs_pin, SpiClkConfig};
use va108xx_hal::timer::CountdownTimer;
use va108xx_hal::{
gpio::PinsA,
pac::{self, interrupt},
pac,
prelude::*,
spi::{Spi, SpiBase, SpiConfig},
timer::{default_ms_irq_handler, set_up_ms_tick, DelayMs, InterruptConfig},
spi::{HwChipSelectId, Spi, SpiConfig},
};
use va108xx_hal::{port_function_select, FunSel};
use vorago_reb1::max11619::{
max11619_externally_clocked_no_wakeup, max11619_externally_clocked_with_wakeup,
max11619_internally_clocked, EocPin, AN2_CHANNEL, POTENTIOMETER_CHANNEL,
max11619_internally_clocked, AN2_CHANNEL, POTENTIOMETER_CHANNEL,
};
#[derive(Debug, PartialEq, Copy, Clone)]
@ -57,34 +56,34 @@ const MUX_MODE: MuxMode = MuxMode::None;
// This is probably more or less a re-implementation of https://docs.rs/embedded-hal-bus/latest/embedded_hal_bus/spi/struct.ExclusiveDevice.html.
// Users should look at the embedded-hal-bus crate for sharing the bus.
pub struct SpiWithHwCs<Delay, HwCs> {
inner: SpiBase<pac::Spib, u8>,
pub struct SpiWithHwCs<Delay> {
inner: Spi<u8>,
delay_provider: Delay,
hw_cs: HwCs,
hw_cs_id: HwChipSelectId,
}
impl<Delay: DelayNs, HwCs: OptionalHwCs<pac::Spib>> SpiWithHwCs<Delay, HwCs> {
pub fn new(spi: SpiBase<pac::Spib, u8>, hw_cs: HwCs, delay_provider: Delay) -> Self {
impl<Delay: DelayNs> SpiWithHwCs<Delay> {
pub fn new(spi: Spi<u8>, hw_cs_id: HwChipSelectId, delay_provider: Delay) -> Self {
Self {
inner: spi,
hw_cs,
hw_cs_id,
delay_provider,
}
}
}
impl<Delay, HwCs> embedded_hal::spi::ErrorType for SpiWithHwCs<Delay, HwCs> {
impl<Delay> embedded_hal::spi::ErrorType for SpiWithHwCs<Delay> {
type Error = Infallible;
}
impl<Delay: DelayNs, HwCs: OptionalHwCs<pac::Spib>> SpiDevice for SpiWithHwCs<Delay, HwCs> {
impl<Delay: DelayNs> SpiDevice for SpiWithHwCs<Delay> {
fn transaction(
&mut self,
operations: &mut [spi::Operation<'_, u8>],
) -> Result<(), Self::Error> {
// Only the HW CS is configured here. This is not really necessary, but showcases
// that we could scale this multiple SPI devices.
self.inner.cfg_hw_cs_with_pin(&self.hw_cs);
self.inner.cfg_hw_cs(self.hw_cs_id);
for operation in operations {
match operation {
spi::Operation::Read(buf) => self.inner.read(buf).ok().unwrap(),
@ -111,28 +110,17 @@ fn main() -> ! {
rprintln!("-- Vorago ADC Example --");
let mut dp = pac::Peripherals::take().unwrap();
let tim0 = set_up_ms_tick(
InterruptConfig::new(pac::Interrupt::OC0, true, true),
&mut dp.sysconfig,
Some(&mut dp.irqsel),
SYS_CLK,
dp.tim0,
);
let delay = DelayMs::new(tim0).unwrap();
let mut delay = CountdownTimer::new(dp.tim0, SYS_CLK);
unsafe {
cortex_m::peripheral::NVIC::unmask(pac::Interrupt::OC0);
}
let pinsa = PinsA::new(&mut dp.sysconfig, dp.porta);
let pinsa = PinsA::new(dp.porta);
let spi_cfg = SpiConfig::default()
.clk_cfg(SpiClkConfig::from_clk(SYS_CLK, 3.MHz()).unwrap())
.mode(MODE_0)
.blockmode(true);
let (sck, mosi, miso) = (
pinsa.pa20.into_funsel_2(),
pinsa.pa19.into_funsel_2(),
pinsa.pa18.into_funsel_2(),
);
let (sck, mosi, miso) = (pinsa.pa20, pinsa.pa19, pinsa.pa18);
if MUX_MODE == MuxMode::PortB19to17 {
port_function_select(&mut dp.ioconfig, Port::B, 19, FunSel::Sel1).ok();
@ -141,39 +129,30 @@ fn main() -> ! {
port_function_select(&mut dp.ioconfig, Port::B, 16, FunSel::Sel1).ok();
}
// Set the accelerometer chip select low in case the board slot is populated
let mut accel_cs = pinsa.pa16.into_push_pull_output();
accel_cs.set_high();
Output::new(pinsa.pa16, PinState::Low);
let spi = Spi::new(
&mut dp.sysconfig,
50.MHz(),
dp.spib,
(sck, miso, mosi),
spi_cfg,
)
.downgrade();
let delay_provider = CountdownTimer::new(&mut dp.sysconfig, 50.MHz(), dp.tim1);
let spi_with_hwcs = SpiWithHwCs::new(spi, pinsa.pa17.into_funsel_2(), delay_provider);
let hw_cs_id = configure_pin_as_hw_cs_pin(pinsa.pa17);
let spi = Spi::new(dp.spib, (sck, miso, mosi), spi_cfg).unwrap();
let delay_spi = CountdownTimer::new(dp.tim1, SYS_CLK);
let spi_with_hwcs = SpiWithHwCs::new(spi, hw_cs_id, delay_spi);
match EXAMPLE_MODE {
ExampleMode::NotUsingEoc => spi_example_externally_clocked(spi_with_hwcs, delay),
ExampleMode::NotUsingEoc => spi_example_externally_clocked(spi_with_hwcs, &mut delay),
ExampleMode::UsingEoc => {
spi_example_internally_clocked(spi_with_hwcs, delay, pinsa.pa14.into_floating_input());
spi_example_internally_clocked(
spi_with_hwcs,
&mut delay,
Input::new_floating(pinsa.pa14),
);
}
ExampleMode::NotUsingEocWithDelay => {
let delay_us = CountdownTimer::new(&mut dp.sysconfig, 50.MHz(), dp.tim2);
spi_example_externally_clocked_with_delay(spi_with_hwcs, delay, delay_us);
spi_example_externally_clocked_with_delay(spi_with_hwcs, &mut delay);
}
}
}
#[interrupt]
#[allow(non_snake_case)]
fn OC0() {
default_ms_irq_handler();
}
/// Use the SPI clock as the conversion clock
fn spi_example_externally_clocked(spi: impl SpiDevice, mut delay: DelayMs) -> ! {
fn spi_example_externally_clocked(spi: impl SpiDevice, delay: &mut impl DelayNs) -> ! {
let mut adc = max11619_externally_clocked_no_wakeup(spi)
.expect("Creating externally clocked MAX11619 device failed");
if READ_MODE == ReadMode::AverageN {
@ -228,11 +207,7 @@ fn spi_example_externally_clocked(spi: impl SpiDevice, mut delay: DelayMs) -> !
}
}
fn spi_example_externally_clocked_with_delay(
spi: impl SpiDevice,
mut delay: DelayMs,
mut delay_us: impl DelayNs,
) -> ! {
fn spi_example_externally_clocked_with_delay(spi: impl SpiDevice, delay: &mut impl DelayNs) -> ! {
let mut adc =
max11619_externally_clocked_with_wakeup(spi).expect("Creating MAX116xx device failed");
let mut cmd_buf: [u8; 32] = [0; 32];
@ -244,7 +219,7 @@ fn spi_example_externally_clocked_with_delay(
ReadMode::Single => {
rprintln!("Reading single potentiometer channel");
let pot_val = adc
.read_single_channel(&mut cmd_buf, POTENTIOMETER_CHANNEL, &mut delay_us)
.read_single_channel(&mut cmd_buf, POTENTIOMETER_CHANNEL, delay)
.expect("Creating externally clocked MAX11619 ADC failed");
rprintln!("Single channel read:");
rprintln!("\tPotentiometer value: {}", pot_val);
@ -255,7 +230,7 @@ fn spi_example_externally_clocked_with_delay(
&mut cmd_buf,
&mut res_buf.iter_mut(),
POTENTIOMETER_CHANNEL,
&mut delay_us,
delay,
)
.expect("Multi-Channel read failed");
print_res_buf(&res_buf);
@ -266,7 +241,7 @@ fn spi_example_externally_clocked_with_delay(
&mut cmd_buf,
&mut res_buf.iter_mut(),
AN2_CHANNEL,
&mut delay_us,
delay,
)
.expect("Multi-Channel read failed");
rprintln!("Multi channel read from 2 to 3:");
@ -283,7 +258,11 @@ fn spi_example_externally_clocked_with_delay(
}
/// This function uses the EOC pin to determine whether the conversion finished
fn spi_example_internally_clocked(spi: impl SpiDevice, mut delay: DelayMs, eoc_pin: EocPin) -> ! {
fn spi_example_internally_clocked(
spi: impl SpiDevice,
delay: &mut impl DelayNs,
eoc_pin: Input,
) -> ! {
let mut adc = max11619_internally_clocked(
spi,
eoc_pin,

View File

@ -5,7 +5,7 @@ use cortex_m_rt::entry;
use embedded_hal::delay::DelayNs;
use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print};
use va108xx_hal::{pac, time::Hertz, timer::CountdownTimer};
use va108xx_hal::{pac, spi::SpiClkConfig, time::Hertz, timer::CountdownTimer};
use vorago_reb1::m95m01::{M95M01, PAGE_SIZE};
const CLOCK_FREQ: Hertz = Hertz::from_raw(50_000_000);
@ -15,12 +15,13 @@ fn main() -> ! {
rtt_init_print!();
rprintln!("-- VA108XX REB1 NVM example --");
let mut dp = pac::Peripherals::take().unwrap();
let dp = pac::Peripherals::take().unwrap();
let mut timer = CountdownTimer::new(&mut dp.sysconfig, CLOCK_FREQ, dp.tim0);
let mut nvm = M95M01::new(&mut dp.sysconfig, CLOCK_FREQ, dp.spic);
let mut delay = CountdownTimer::new(dp.tim0, CLOCK_FREQ);
let clk_config = SpiClkConfig::new(2, 4);
let mut nvm = M95M01::new(dp.spic, clk_config);
let status_reg = nvm.read_status_reg().expect("reading status reg failed");
if status_reg.zero_segment() == 0b111 {
if status_reg.zero_segment().value() == 0b111 {
panic!("status register unexpected values");
}
@ -51,6 +52,6 @@ fn main() -> ! {
nvm.write(0, &orig_content).unwrap();
loop {
timer.delay_ms(500);
delay.delay_ms(500);
}
}

View File

@ -5,16 +5,18 @@
//! - [Button Blinky with IRQs](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/vorago-reb1/examples/blinky-button-irq.rs)
//! - [Button Blinky with IRQs and RTIC](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/vorago-reb1/examples/blinky-button-rtic.rs)
use va108xx_hal::{
gpio::{FilterClkSel, FilterType, InputFloating, InterruptEdge, InterruptLevel, Pin, PA11},
clock::FilterClkSel,
gpio::{FilterType, Input, InterruptEdge, InterruptLevel, Pin},
pins::Pa11,
InterruptConfig,
};
#[derive(Debug)]
pub struct Button(pub Pin<PA11, InputFloating>);
pub struct Button(pub Input);
impl Button {
pub fn new(pin: Pin<PA11, InputFloating>) -> Button {
Button(pin)
pub fn new(pin: Pin<Pa11>) -> Button {
Button(Input::new_floating(pin))
}
#[inline]

View File

@ -6,20 +6,20 @@
//! - [Button Blinky using IRQs](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/vorago-reb1/examples/blinky-button-irq.rs)
//! - [Button Blinky using IRQs and RTIC](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/vorago-reb1/examples/blinky-button-rtic.rs)
use va108xx_hal::{
gpio::dynpin::DynPin,
gpio::pin::{Pin, PushPullOutput, PA10, PA6, PA7},
gpio::{Output, PinState},
pins::{Pa10, Pa6, Pa7, Pin},
};
pub type LD2 = Pin<PA10, PushPullOutput>;
pub type LD3 = Pin<PA7, PushPullOutput>;
pub type LD4 = Pin<PA6, PushPullOutput>;
#[derive(Debug)]
pub struct Leds(pub [Led; 3]);
impl Leds {
pub fn new(led_pin1: LD2, led_pin2: LD3, led_pin3: LD4) -> Leds {
Leds([led_pin1.into(), led_pin2.into(), led_pin3.into()])
pub fn new(led_pin1: Pin<Pa10>, led_pin2: Pin<Pa7>, led_pin3: Pin<Pa6>) -> Leds {
Leds([
Led(Output::new(led_pin1, PinState::Low)),
Led(Output::new(led_pin2, PinState::Low)),
Led(Output::new(led_pin3, PinState::Low)),
])
}
}
@ -52,38 +52,24 @@ impl core::ops::IndexMut<usize> for Leds {
}
#[derive(Debug)]
pub struct Led(pub DynPin);
macro_rules! ctor {
($($ldx:ident),+) => {
$(
impl From<$ldx> for Led {
fn from(led: $ldx) -> Self {
Led(led.into())
}
}
)+
}
}
ctor!(LD2, LD3, LD4);
pub struct Led(Output);
impl Led {
/// Turns the LED off. Setting the pin high actually turns the LED off
#[inline]
pub fn off(&mut self) {
self.0.set_high().ok();
self.0.set_high();
}
/// Turns the LED on. Setting the pin low actually turns the LED on
#[inline]
pub fn on(&mut self) {
self.0.set_low().ok();
self.0.set_low();
}
/// Toggles the LED
#[inline]
pub fn toggle(&mut self) {
self.0.toggle().ok();
self.0.toggle();
}
}

View File

@ -7,20 +7,25 @@
//! # Example
//!
//! - [REB1 EEPROM example](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/vorago-reb1/examples/nvm.rs)
use arbitrary_int::{u2, u3};
use core::convert::Infallible;
use embedded_hal::spi::SpiBus;
pub const PAGE_SIZE: usize = 256;
bitfield::bitfield! {
pub struct StatusReg(u8);
impl Debug;
u8;
pub status_register_write_protect, _: 7;
pub zero_segment, _: 6, 4;
pub block_protection_bits, set_block_protection_bits: 3, 2;
pub write_enable_latch, _: 1;
pub write_in_progress, _: 0;
#[bitbybit::bitfield(u8)]
#[derive(Debug)]
pub struct StatusReg {
#[bit(7, r)]
status_register_write_protect: bool,
#[bits(4..=6, r)]
zero_segment: u3,
#[bits(2..=3, rw)]
block_protection_bits: u2,
#[bit(1, r)]
write_enable_latch: bool,
#[bit(0, r)]
write_in_progress: bool,
}
// Registers.
@ -42,11 +47,10 @@ pub mod regs {
use regs::*;
use va108xx_hal::{
pac,
prelude::*,
spi::{RomMiso, RomMosi, RomSck, Spi, SpiClkConfig, SpiConfig, BMSTART_BMSTOP_MASK},
spi::{Spi, SpiClkConfig, SpiConfig, SpiLowLevel, BMSTART_BMSTOP_MASK},
};
pub type RomSpi = Spi<pac::Spic, (RomSck, RomMiso, RomMosi), u8>;
pub type RomSpi = Spi<u8>;
/// Driver for the ST device M95M01 EEPROM memory.
///
@ -59,14 +63,8 @@ pub struct M95M01 {
pub struct PageBoundaryExceededError;
impl M95M01 {
pub fn new(syscfg: &mut pac::Sysconfig, sys_clk: impl Into<Hertz>, spi: pac::Spic) -> Self {
let spi = RomSpi::new(
syscfg,
sys_clk,
spi,
(RomSck, RomMiso, RomMosi),
SpiConfig::default().clk_cfg(SpiClkConfig::new(2, 4)),
);
pub fn new(spi: pac::Spic, clk_config: SpiClkConfig) -> Self {
let spi = RomSpi::new_for_rom(spi, SpiConfig::default().clk_cfg(clk_config)).unwrap();
let mut spi_dev = Self { spi };
spi_dev.clear_block_protection().unwrap();
spi_dev
@ -74,7 +72,7 @@ impl M95M01 {
pub fn release(mut self) -> pac::Spic {
self.set_block_protection().unwrap();
self.spi.release().0
unsafe { pac::Spic::steal() }
}
// Wait until the write-in-progress state is cleared. This exposes a [nb] API, so this function
@ -90,7 +88,7 @@ impl M95M01 {
pub fn read_status_reg(&mut self) -> Result<StatusReg, Infallible> {
let mut write_read: [u8; 2] = [regs::RDSR, 0x00];
self.spi.transfer_in_place(&mut write_read)?;
Ok(StatusReg(write_read[1]))
Ok(StatusReg::new_with_raw_value(write_read[1]))
}
pub fn write_enable(&mut self) -> Result<(), Infallible> {
@ -104,10 +102,10 @@ impl M95M01 {
}
pub fn set_block_protection(&mut self) -> Result<(), Infallible> {
let mut reg = StatusReg(0);
reg.set_block_protection_bits(0b11);
let mut reg = StatusReg::new_with_raw_value(0);
reg.set_block_protection_bits(u2::new(0b11));
self.write_enable()?;
self.spi.write(&[WRSR, reg.0])
self.spi.write(&[WRSR, reg.raw_value()])
}
fn common_init_write_and_read(&mut self, address: usize, reg: u8) -> Result<(), Infallible> {

View File

@ -9,7 +9,7 @@ use max116xx_10bit::{
Error, ExternallyClocked, InternallyClockedInternallyTimedSerialInterface, Max116xx10Bit,
Max116xx10BitEocExt, VoltageRefMode, WithWakeupDelay, WithoutWakeupDelay,
};
use va108xx_hal::gpio::{Floating, Input, Pin, PA14};
use va108xx_hal::gpio::Input;
pub type Max11619ExternallyClockedNoWakeup<Spi> =
Max116xx10Bit<Spi, ExternallyClocked, WithoutWakeupDelay>;
@ -17,7 +17,6 @@ pub type Max11619ExternallyClockedWithWakeup<Spi> =
Max116xx10Bit<Spi, ExternallyClocked, WithWakeupDelay>;
pub type Max11619InternallyClocked<Spi, Eoc> =
Max116xx10BitEocExt<Spi, Eoc, InternallyClockedInternallyTimedSerialInterface>;
pub type EocPin = Pin<PA14, Input<Floating>>;
pub const AN0_CHANNEL: u8 = 0;
pub const AN1_CHANNEL: u8 = 1;
@ -44,9 +43,9 @@ pub fn max11619_externally_clocked_with_wakeup<Spi: SpiDevice>(
pub fn max11619_internally_clocked<Spi: SpiDevice>(
spi: Spi,
eoc: EocPin,
eoc: Input,
v_ref: VoltageRefMode,
) -> Result<Max11619InternallyClocked<Spi, EocPin>, Error<Spi::Error, Infallible>> {
) -> Result<Max11619InternallyClocked<Spi, Input>, Error<Spi::Error, Infallible>> {
let mut adc = Max116xx10Bit::max11619(spi)?
.into_int_clkd_int_timed_through_ser_if_without_wakeup(v_ref, eoc)?;
adc.reset(false)?;

View File

@ -15,7 +15,7 @@ use va108xx_hal::{
const ADT75_I2C_ADDR: u8 = 0b1001000;
pub struct Adt75TempSensor {
sensor_if: I2cMaster<pac::I2ca, SevenBitAddress>,
sensor_if: I2cMaster<SevenBitAddress>,
cmd_buf: [u8; 1],
current_reg: RegAddresses,
}
@ -48,17 +48,12 @@ impl From<Error> for AdtInitError {
}
impl Adt75TempSensor {
pub fn new(
sys_cfg: &mut pac::Sysconfig,
sys_clk: impl Into<Hertz> + Copy,
i2ca: pac::I2ca,
) -> Result<Self, Error> {
pub fn new(sys_clk: Hertz, i2ca: pac::I2ca) -> Result<Self, Error> {
let mut sensor = Adt75TempSensor {
// The master construction can not fail for regular I2C speed.
sensor_if: I2cMaster::new(
sys_cfg,
sys_clk,
i2ca,
sys_clk,
MasterConfig::default(),
I2cSpeed::Regular100khz,
)

View File

@ -15,6 +15,10 @@
"device": "Cortex-M0",
"svdFile": "./va108xx/svd/va108xx.svd.patched",
"preLaunchTask": "rust: cargo build led blinky",
"serverArgs": [
"-jtagconf",
"-1,-1"
],
"executable": "${workspaceFolder}/target/thumbv6m-none-eabi/debug/examples/blinky-leds",
"interface": "jtag",
"runToEntryPoint": "main",
@ -39,6 +43,10 @@
"device": "Cortex-M0",
"svdFile": "./va108xx/svd/va108xx.svd.patched",
"preLaunchTask": "rust: cargo build hal tests",
"serverArgs": [
"-jtagconf",
"-1,-1"
],
"executable": "${workspaceFolder}/target/thumbv6m-none-eabi/debug/board-tests",
"interface": "jtag",
"runToEntryPoint": "main",
@ -54,30 +62,6 @@
]
}
},
{
"type": "cortex-debug",
"request": "launch",
"name": "Debug RTT",
"servertype": "jlink",
"cwd": "${workspaceRoot}",
"device": "Cortex-M0",
"svdFile": "./va108xx/svd/va108xx.svd.patched",
"preLaunchTask": "rust: cargo build rtt",
"executable": "${workspaceFolder}/target/thumbv6m-none-eabi/debug/examples/rtt-log",
"interface": "jtag",
"runToEntryPoint": "main",
"rttConfig": {
"enabled": true,
"address": "auto",
"decoders": [
{
"port": 0,
"timestamp": true,
"type": "console"
}
]
}
},
{
"type": "cortex-debug",
"request": "launch",
@ -87,6 +71,10 @@
"device": "Cortex-M0",
"svdFile": "./va108xx/svd/va108xx.svd.patched",
"preLaunchTask": "rust: cargo build button blinky",
"serverArgs": [
"-jtagconf",
"-1,-1"
],
"executable": "${workspaceFolder}/target/thumbv6m-none-eabi/debug/examples/blinky-button-irq",
"interface": "jtag",
"runToEntryPoint": "main",
@ -113,6 +101,10 @@
"preLaunchTask": "rust: cargo build systick",
"executable": "${workspaceFolder}/target/thumbv6m-none-eabi/debug/examples/timer-ticks",
"interface": "jtag",
"serverArgs": [
"-jtagconf",
"-1,-1"
],
"runToEntryPoint": "main",
"rttConfig": {
"enabled": true,
@ -133,7 +125,11 @@
"servertype": "jlink",
"cwd": "${workspaceRoot}",
"device": "Cortex-M0",
"svdFile": "./va108xx/svd/va108xx-base.svd.patched",
"svdFile": "./va108xx/svd/va108xx.svd.patched",
"serverArgs": [
"-jtagconf",
"-1,-1"
],
"preLaunchTask": "rust: cargo build uart",
"executable": "${workspaceFolder}/target/thumbv6m-none-eabi/debug/examples/uart",
"interface": "jtag",
@ -158,6 +154,10 @@
"cwd": "${workspaceRoot}",
"device": "Cortex-M0",
"svdFile": "./va108xx/svd/va108xx.svd.patched",
"serverArgs": [
"-jtagconf",
"-1,-1"
],
"preLaunchTask": "rust: cargo build spi",
"executable": "${workspaceFolder}/target/thumbv6m-none-eabi/debug/examples/spi",
"interface": "jtag",
@ -182,6 +182,10 @@
"cwd": "${workspaceRoot}",
"device": "Cortex-M0",
"svdFile": "./va108xx/svd/va108xx-base.svd.patched",
"serverArgs": [
"-jtagconf",
"-1,-1"
],
"preLaunchTask": "rust: cargo build temp sensor",
"executable": "${workspaceFolder}/target/thumbv6m-none-eabi/debug/examples/adt75-temp-sensor",
"interface": "jtag",
@ -206,6 +210,10 @@
"cwd": "${workspaceRoot}",
"device": "Cortex-M0",
"svdFile": "./va108xx/svd/va108xx-base.svd.patched",
"serverArgs": [
"-jtagconf",
"-1,-1"
],
"preLaunchTask": "rust: cargo build button blinky rtic",
"executable": "${workspaceFolder}/target/thumbv6m-none-eabi/debug/examples/blinky-button-rtic",
"interface": "jtag",
@ -230,6 +238,10 @@
"cwd": "${workspaceRoot}",
"device": "Cortex-M0",
"svdFile": "./va108xx/svd/va108xx-base.svd.patched",
"serverArgs": [
"-jtagconf",
"-1,-1"
],
"preLaunchTask": "uart-echo-rtic-example",
"executable": "${workspaceFolder}/target/thumbv6m-none-eabi/debug/uart-echo-rtic",
"interface": "jtag",
@ -254,6 +266,10 @@
"cwd": "${workspaceRoot}",
"device": "Cortex-M0",
"svdFile": "./va108xx/svd/va108xx.svd.patched",
"serverArgs": [
"-jtagconf",
"-1,-1"
],
"preLaunchTask": "rust: cargo build pwm",
"executable": "${workspaceFolder}/target/thumbv6m-none-eabi/debug/examples/pwm",
"interface": "jtag",
@ -278,6 +294,10 @@
"cwd": "${workspaceRoot}",
"device": "Cortex-M0",
"svdFile": "./va108xx/svd/va108xx.svd.patched",
"serverArgs": [
"-jtagconf",
"-1,-1"
],
"preLaunchTask": "rust: cargo build cascade",
"executable": "${workspaceFolder}/target/thumbv6m-none-eabi/debug/examples/cascade",
"interface": "jtag",
@ -302,6 +322,10 @@
"cwd": "${workspaceRoot}",
"device": "Cortex-M0",
"svdFile": "./va108xx/svd/va108xx.svd.patched",
"serverArgs": [
"-jtagconf",
"-1,-1"
],
"preLaunchTask": "rust: cargo build accelerometer",
"executable": "${workspaceFolder}/target/thumbv6m-none-eabi/debug/examples/adxl343-accelerometer",
"interface": "jtag",
@ -318,19 +342,6 @@
]
}
},
{
"type": "cortex-debug",
"request": "launch",
"name": "Blinky HAL",
"servertype": "jlink",
"cwd": "${workspaceRoot}",
"device": "Cortex-M0",
"svdFile": "./va108xx/svd/va108xx.svd.patched",
"preLaunchTask": "blinky-hal",
"executable": "${workspaceFolder}/target/thumbv6m-none-eabi/debug/examples/blinky",
"interface": "jtag",
"runToEntryPoint": "main",
},
{
"type": "cortex-debug",
"request": "launch",
@ -339,6 +350,10 @@
"cwd": "${workspaceRoot}",
"device": "Cortex-M0",
"svdFile": "./va108xx/svd/va108xx.svd.patched",
"serverArgs": [
"-jtagconf",
"-1,-1"
],
"preLaunchTask": "rust: cargo build adc",
"executable": "${workspaceFolder}/target/thumbv6m-none-eabi/debug/examples/max11619-adc",
"interface": "jtag",
@ -363,6 +378,10 @@
"cwd": "${workspaceRoot}",
"device": "Cortex-M0",
"svdFile": "./va108xx/svd/va108xx.svd.patched",
"serverArgs": [
"-jtagconf",
"-1,-1"
],
"preLaunchTask": "uart-echo-rtic-example",
"executable": "${workspaceFolder}/target/thumbv6m-none-eabi/debug/uart-echo-rtic",
"interface": "jtag",
@ -387,6 +406,10 @@
"cwd": "${workspaceRoot}",
"device": "Cortex-M0",
"svdFile": "./va108xx/svd/va108xx.svd.patched",
"serverArgs": [
"-jtagconf",
"-1,-1"
],
"preLaunchTask": "reb1-nvm",
"executable": "${workspaceFolder}/target/thumbv6m-none-eabi/debug/examples/nvm",
"interface": "jtag",
@ -411,6 +434,10 @@
"cwd": "${workspaceRoot}",
"device": "Cortex-M0",
"svdFile": "./va108xx/svd/va108xx.svd.patched",
"serverArgs": [
"-jtagconf",
"-1,-1"
],
"preLaunchTask": "rtic-example",
"executable": "${workspaceFolder}/target/thumbv6m-none-eabi/debug/rtic-example",
"interface": "jtag",
@ -435,6 +462,10 @@
"cwd": "${workspaceRoot}",
"device": "Cortex-M0",
"svdFile": "./va108xx/svd/va108xx.svd.patched",
"serverArgs": [
"-jtagconf",
"-1,-1"
],
"preLaunchTask": "embassy-example",
"executable": "${workspaceFolder}/target/thumbv6m-none-eabi/debug/embassy-example",
"interface": "jtag",
@ -459,6 +490,10 @@
"cwd": "${workspaceRoot}",
"device": "Cortex-M0",
"svdFile": "./va108xx/svd/va108xx.svd.patched",
"serverArgs": [
"-jtagconf",
"-1,-1"
],
"preLaunchTask": "bootloader",
"executable": "${workspaceFolder}/target/thumbv6m-none-eabi/release/bootloader",
"interface": "jtag",
@ -483,6 +518,10 @@
"cwd": "${workspaceRoot}",
"device": "Cortex-M0",
"svdFile": "./va108xx/svd/va108xx.svd.patched",
"serverArgs": [
"-jtagconf",
"-1,-1"
],
"preLaunchTask": "flashloader",
"executable": "${workspaceFolder}/target/thumbv6m-none-eabi/release/flashloader",
"interface": "jtag",
@ -507,6 +546,10 @@
"cwd": "${workspaceRoot}",
"device": "Cortex-M0",
"svdFile": "./va108xx/svd/va108xx.svd.patched",
"serverArgs": [
"-jtagconf",
"-1,-1"
],
"preLaunchTask": "async-gpio",
"executable": "${workspaceFolder}/target/thumbv6m-none-eabi/debug/async-gpio",
"interface": "jtag",
@ -531,6 +574,10 @@
"cwd": "${workspaceRoot}",
"device": "Cortex-M0",
"svdFile": "./va108xx/svd/va108xx.svd.patched",
"serverArgs": [
"-jtagconf",
"-1,-1"
],
"preLaunchTask": "async-uart-tx",
"executable": "${workspaceFolder}/target/thumbv6m-none-eabi/debug/async-uart-tx",
"interface": "jtag",
@ -555,6 +602,10 @@
"cwd": "${workspaceRoot}",
"device": "Cortex-M0",
"svdFile": "./va108xx/svd/va108xx.svd.patched",
"serverArgs": [
"-jtagconf",
"-1,-1"
],
"preLaunchTask": "async-uart-rx",
"executable": "${workspaceFolder}/target/thumbv6m-none-eabi/debug/async-uart-rx",
"interface": "jtag",
@ -572,4 +623,4 @@
}
},
]
}
}

View File

@ -31,20 +31,6 @@
"isDefault": true
}
},
{
"label": "rust: cargo build rtt",
"type": "shell",
"command": "~/.cargo/bin/cargo", // note: full path to the cargo
"args": [
"build",
"--example",
"rtt-log"
],
"group": {
"kind": "build",
"isDefault": true
}
},
{
"label": "rust: cargo build systick",
"type": "shell",
@ -319,4 +305,4 @@
]
}
]
}
}