diff --git a/Cargo.toml b/Cargo.toml index 966472f..86e1701 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ members = [ "board-tests", "bootloader", "flashloader", + "vorago-shared-periphs", ] exclude = [ "flashloader/slot-a-blinky", diff --git a/bootloader/Cargo.toml b/bootloader/Cargo.toml index 1aaf734..13ab933 100644 --- a/bootloader/Cargo.toml +++ b/bootloader/Cargo.toml @@ -16,9 +16,11 @@ static_assertions = "1" [dependencies.va108xx-hal] version = "0.11" +path = "../va108xx-hal" [dependencies.vorago-reb1] version = "0.8" +path = "../vorago-reb1" [features] default = [] diff --git a/bootloader/src/main.rs b/bootloader/src/main.rs index dd8a028..b55ef6f 100644 --- a/bootloader/src/main.rs +++ b/bootloader/src/main.rs @@ -104,11 +104,11 @@ fn main() -> ! { rtt_init_print!(); rprintln!("-- 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(CLOCK_FREQ, dp.tim0); - let mut nvm = M95M01::new(&mut dp.sysconfig, CLOCK_FREQ, dp.spic); + let mut nvm = M95M01::new(CLOCK_FREQ, dp.spic); if FLASH_SELF { let mut first_four_bytes: [u8; 4] = [0; 4]; @@ -186,7 +186,7 @@ fn check_own_crc( sysconfig: &pac::Sysconfig, cp: &cortex_m::Peripherals, nvm: &mut NvmWrapper, - timer: &mut CountdownTimer, + 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 @@ -276,7 +276,7 @@ fn boot_app( syscfg: &pac::Sysconfig, cp: &cortex_m::Peripherals, app_sel: AppSel, - timer: &mut CountdownTimer, + timer: &mut CountdownTimer, ) -> ! { if DEBUG_PRINTOUTS && RTT_PRINTOUT { rprintln!("booting app {:?}", app_sel); diff --git a/examples/embassy/Cargo.toml b/examples/embassy/Cargo.toml index 039e18a..6356d17 100644 --- a/examples/embassy/Cargo.toml +++ b/examples/embassy/Cargo.toml @@ -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] diff --git a/examples/embassy/src/bin/async-gpio.rs b/examples/embassy/src/bin/async-gpio.rs index e06f774..fcb81dd 100644 --- a/examples/embassy/src/bin/async-gpio.rs +++ b/examples/embassy/src/bin/async-gpio.rs @@ -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(); } } } diff --git a/examples/embassy/src/bin/async-uart-rx.rs b/examples/embassy/src/bin/async-uart-rx.rs index 33a974d..8bf7baf 100644 --- a/examples/embassy/src/bin/async-uart-rx.rs +++ b/examples/embassy/src/bin/async-uart-rx.rs @@ -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,34 @@ 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(), + 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(), + 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 +124,7 @@ async fn main(spawner: Spawner) { } #[embassy_executor::task] -async fn uart_b_task(mut async_rx: RxAsyncOverwriting, mut tx: Tx) { +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 +145,7 @@ async fn uart_b_task(mut async_rx: RxAsyncOverwriting, 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 +158,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 { diff --git a/examples/embassy/src/bin/async-uart-tx.rs b/examples/embassy/src/bin/async-uart-tx.rs index 29ee434..8c073c7 100644 --- a/examples/embassy/src/bin/async-uart-tx.rs +++ b/examples/embassy/src/bin/async-uart-tx.rs @@ -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,23 @@ 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(), + 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 +91,5 @@ async fn main(_spawner: Spawner) { #[interrupt] #[allow(non_snake_case)] fn OC2() { - on_interrupt_tx(Bank::A); + on_interrupt_tx(Bank::Uart0); } diff --git a/examples/embassy/src/main.rs b/examples/embassy/src/main.rs index 35e0411..7e38a1c 100644 --- a/examples/embassy/src/main.rs +++ b/examples/embassy/src/main.rs @@ -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; diff --git a/examples/rtic/Cargo.toml b/examples/rtic/Cargo.toml index 30ec487..5792e94 100644 --- a/examples/rtic/Cargo.toml +++ b/examples/rtic/Cargo.toml @@ -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" } diff --git a/examples/rtic/src/bin/blinky-button-rtic.rs b/examples/rtic/src/bin/blinky-button-rtic.rs index bba2d4c..c927327 100644 --- a/examples/rtic/src/bin/blinky-button-rtic.rs +++ b/examples/rtic/src/bin/blinky-button-rtic.rs @@ -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 - } - } - } - } } diff --git a/examples/rtic/src/bin/rtic-empty.rs b/examples/rtic/src/bin/rtic-empty.rs index 0950065..294e382 100644 --- a/examples/rtic/src/bin/rtic-empty.rs +++ b/examples/rtic/src/bin/rtic-empty.rs @@ -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 {}) } diff --git a/examples/rtic/src/bin/uart-echo-rtic.rs b/examples/rtic/src/bin/uart-echo-rtic.rs index 296e66e..616c195 100644 --- a/examples/rtic/src/bin/uart-echo-rtic.rs +++ b/examples/rtic/src/bin/uart-echo-rtic.rs @@ -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, - tx: Tx, + rx: RxWithInterrupt, + tx: Tx, } #[shared] @@ -42,24 +44,23 @@ 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(), + 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 +105,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"); } } diff --git a/examples/rtic/src/main.rs b/examples/rtic/src/main.rs index ad6ccd9..833a751 100644 --- a/examples/rtic/src/main.rs +++ b/examples/rtic/src/main.rs @@ -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, - led1: Pin, - led2: Pin, + 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(); diff --git a/examples/simple/Cargo.toml b/examples/simple/Cargo.toml index 158cbff..9407c5f 100644 --- a/examples/simple/Cargo.toml +++ b/examples/simple/Cargo.toml @@ -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" diff --git a/examples/simple/examples/blinky.rs b/examples/simple/examples/blinky.rs index bcdfe0c..1d30b82 100644 --- a/examples/simple/examples/blinky.rs +++ b/examples/simple/examples/blinky.rs @@ -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(50.MHz(), dp.tim1); + 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() -} diff --git a/examples/simple/examples/cascade.rs b/examples/simple/examples/cascade.rs index bac4745..0d5e7c0 100644 --- a/examples/simple/examples/cascade.rs +++ b/examples/simple/examples/cascade.rs @@ -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>>> = - Mutex::new(RefCell::new(None)); -static CSD_TGT_2: Mutex>>> = - 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(50.MHz(), dp.tim0); // 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(50.MHz(), dp.tim3); + 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(50.MHz(), dp.tim4); + 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(50.MHz(), dp.tim5); + 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; } diff --git a/examples/simple/examples/pwm.rs b/examples/simple/examples/pwm.rs index 57acac5..237a511 100644 --- a/examples/simple/examples/pwm.rs +++ b/examples/simple/examples/pwm.rs @@ -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(50.MHz(), (pinsa.pa3, dp.tim3), 10.Hz()).unwrap(); + let mut delay = CountdownTimer::new(50.MHz(), dp.tim0); 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 = ReducedPwmPin::from(reduced_pin); + let mut pwmb: PwmPin = 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::::from(pwmb); + pwm = PwmPin::::from(pwmb); } } diff --git a/examples/simple/examples/rtt-log.rs b/examples/simple/examples/rtt-log.rs deleted file mode 100644 index cbd4187..0000000 --- a/examples/simple/examples/rtt-log.rs +++ /dev/null @@ -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); - } -} diff --git a/examples/simple/examples/spi.rs b/examples/simple/examples/spi.rs index 7869b5a..9424855 100644 --- a/examples/simple/examples/spi.rs +++ b/examples/simple/examples/spi.rs @@ -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(50.MHz(), dp.tim0); let spi_clk_cfg = SpiClkConfig::from_clk(50.MHz(), SPI_SPEED_KHZ.kHz()) .expect("creating SPI clock config failed"); - let spia_ref: RefCell>> = RefCell::new(None); - let spib_ref: RefCell>> = 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(50.MHz(), 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(50.MHz(), 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(50.MHz(), 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() -} diff --git a/examples/simple/examples/timer-ticks.rs b/examples/simple/examples/timer-ticks.rs index 3b4b275..48c177e 100644 --- a/examples/simple/examples/timer-ticks.rs +++ b/examples/simple/examples/timer-ticks.rs @@ -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> = 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(50.MHz(), dp.tim2); 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(get_sys_clock().unwrap(), dp.tim0); + ms_timer.enable_interrupt(InterruptConfig::new(interrupt::OC0, true, true)); + ms_timer.start(1.kHz()); + let mut second_timer = CountdownTimer::new(get_sys_clock().unwrap(), dp.tim1); + 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); } diff --git a/examples/simple/examples/uart.rs b/examples/simple/examples/uart.rs index 5869a03..45e88d3 100644 --- a/examples/simple/examples/uart.rs +++ b/examples/simple/examples/uart.rs @@ -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(50.MHz(), dp.uarta, (tx, rx), 115200.Hz().into()) + .unwrap(); let (mut tx, mut rx) = uart.split(); writeln!(tx, "Hello World\r").unwrap(); diff --git a/examples/simple/src/main.rs b/examples/simple/src/main.rs index 55f4c3c..25c46d4 100644 --- a/examples/simple/src/main.rs +++ b/examples/simple/src/main.rs @@ -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] diff --git a/flashloader/Cargo.toml b/flashloader/Cargo.toml index a979b11..2215825 100644 --- a/flashloader/Cargo.toml +++ b/flashloader/Cargo.toml @@ -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] diff --git a/flashloader/image-loader.py b/flashloader/image-loader.py index e260502..0c22c4e 100755 --- a/flashloader/image-loader.py +++ b/flashloader/image-loader.py @@ -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) diff --git a/flashloader/loader.toml b/flashloader/loader.toml index bfcf1ac..008f3de 100644 --- a/flashloader/loader.toml +++ b/flashloader/loader.toml @@ -1 +1 @@ -serial_port = "/dev/ttyUSB0" +serial_port = "/dev/ttyUSB1" diff --git a/flashloader/requirements.txt b/flashloader/requirements.txt index 5a2f67b..dbcb091 100644 --- a/flashloader/requirements.txt +++ b/flashloader/requirements.txt @@ -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 diff --git a/flashloader/src/main.rs b/flashloader/src/main.rs index 4928bfa..641a612 100644 --- a/flashloader/src/main.rs +++ b/flashloader/src/main.rs @@ -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, - uart_tx: uart::Tx, + uart_rx: uart::RxWithInterrupt, + uart_tx: uart::Tx, rx_context: IrqContextTimeoutOrMaxSize, verif_reporter: VerificationReportCreator, nvm: M95M01, @@ -108,18 +108,18 @@ 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(), + 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 +451,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; diff --git a/va108xx-embassy/Cargo.toml b/va108xx-embassy/Cargo.toml index e7520ba..e1ebb69 100644 --- a/va108xx-embassy/Cargo.toml +++ b/va108xx-embassy/Cargo.toml @@ -20,7 +20,7 @@ embassy-time-queue-utils = "0.1" once_cell = { version = "1", default-features = false, features = ["critical-section"] } -va108xx-hal = { version = ">=0.10, <=0.11" } +va108xx-hal = { version = ">=0.10, <=0.11", path = "../va108xx-hal" } [target.'cfg(all(target_arch = "arm", target_os = "none"))'.dependencies] portable-atomic = { version = "1", features = ["unsafe-assume-single-core"] } diff --git a/va108xx-embassy/src/lib.rs b/va108xx-embassy/src/lib.rs index dce3ef7..fe900b5 100644 --- a/va108xx-embassy/src/lib.rs +++ b/va108xx-embassy/src/lib.rs @@ -45,7 +45,11 @@ use va108xx_hal::{ clock::enable_peripheral_clock, enable_nvic_interrupt, pac, prelude::*, - timer::{enable_tim_clk, get_tim_raw, TimRegInterface, ValidTim}, + timer::{ + enable_tim_clk, + regs::{EnableControl, MmioTimer}, + TimId, TimMarker, + }, PeripheralSelect, }; @@ -109,48 +113,25 @@ pub fn time_driver() -> &'static TimerDriver { /// 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( - syscfg: &mut pac::Sysconfig, - irqsel: &pac::Irqsel, - sysclk: impl Into, +pub fn init( + 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, +pub fn init_with_custom_irqs( + 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, - ) + TIME_DRIVER.init(sysclk, timekeeper_tim, alarm_tim, timekeeper_irq, alarm_irq) } struct AlarmState { @@ -168,8 +149,8 @@ impl AlarmState { unsafe impl Send for AlarmState {} static SCALE: OnceCell = OnceCell::new(); -static TIMEKEEPER_TIM: OnceCell = OnceCell::new(); -static ALARM_TIM: OnceCell = OnceCell::new(); +static TIMEKEEPER_TIM: OnceCell = OnceCell::new(); +static ALARM_TIM: OnceCell = OnceCell::new(); pub struct TimerDriver { periods: AtomicU32, @@ -180,62 +161,56 @@ pub struct TimerDriver { impl TimerDriver { #[allow(clippy::too_many_arguments)] - fn init( + fn init( &self, - syscfg: &mut pac::Sysconfig, - irqsel: &pac::Irqsel, - sysclk: impl Into, - timekeeper_tim: TimekeeperTim, - alarm_tim: AlarmTim, + sysclk: 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(); + ALARM_TIM.set(AlarmTim::ID).ok(); + TIMEKEEPER_TIM.set(TimekeeperTim::ID).ok(); + enable_peripheral_clock(PeripheralSelect::Irqsel); + enable_tim_clk(TimekeeperTim::ID); + let mut timekeeper_reg_block = unsafe { TimekeeperTim::ID.steal_regs() }; + let mut alarm_tim_reg_block = unsafe { AlarmTim::ID.steal_regs() }; // 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) }); + timekeeper_reg_block.write_reset_value(u32::MAX); // Decrementing counter. - timekeeper_reg_block - .cnt_value() - .write(|w| unsafe { w.bits(u32::MAX) }); + timekeeper_reg_block.write_count_value(u32::MAX); + let irqsel = unsafe { va108xx_hal::pac::Irqsel::steal() }; // Switch on. Timekeeping should always be done. irqsel - .tim0(timekeeper_tim.tim_id() as usize) + .tim0(TimekeeperTim::ID.value() 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) }); + timekeeper_reg_block.modify_control(|mut value| { + value.set_irq_enable(true); + value + }); + timekeeper_reg_block.write_enable_control(EnableControl::new_enable()); - enable_tim_clk(syscfg, alarm_tim.tim_id()); + enable_tim_clk(AlarmTim::ID); // Explicitely disable alarm timer until needed. - alarm_tim_reg_block.ctrl().modify(|_, w| { - w.irq_enb().clear_bit(); - w.enable().clear_bit() + alarm_tim_reg_block.modify_control(|mut value| { + value.set_irq_enable(false); + value.set_enable(false); + value }); // 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) + .tim0(AlarmTim::ID.value() as usize) .write(|w| unsafe { w.bits(alarm_irq as u32) }); } @@ -261,16 +236,16 @@ impl TimerDriver { }) } - fn timekeeper_tim() -> &'static pac::tim0::RegisterBlock { + fn timekeeper_tim() -> MmioTimer<'static> { TIMEKEEPER_TIM .get() - .map(|idx| unsafe { get_tim_raw(*idx as usize) }) + .map(|id| unsafe { id.steal_regs() }) .unwrap() } - fn alarm_tim() -> &'static pac::tim0::RegisterBlock { + fn alarm_tim() -> MmioTimer<'static> { ALARM_TIM .get() - .map(|idx| unsafe { get_tim_raw(*idx as usize) }) + .map(|id| unsafe { id.steal_regs() }) .unwrap() } @@ -283,25 +258,27 @@ impl TimerDriver { if at < t { self.trigger_alarm(cs); } else { - let alarm_tim = Self::alarm_tim(); + let mut 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) }); + alarm_tim.write_enable_control(EnableControl::new_disable()); + alarm_tim.write_count_value(remaining_ticks.unwrap() as u32); + alarm_tim.modify_control(|mut value| { + value.set_irq_enable(true); + value + }); + alarm_tim.write_enable_control(EnableControl::new_enable()); } } }) } fn trigger_alarm(&self, cs: CriticalSection) { - Self::alarm_tim().ctrl().modify(|_, w| { - w.irq_enb().clear_bit(); - w.enable().clear_bit() + Self::alarm_tim().modify_control(|mut value| { + value.set_irq_enable(false); + value.set_enable(false); + value }); let alarm = &self.alarms.borrow(cs); @@ -327,10 +304,11 @@ impl TimerDriver { 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 mut alarm_tim = Self::alarm_tim(); + alarm_tim.modify_control(|mut value| { + value.set_irq_enable(false); + value.set_enable(false); + value }); let alarm = self.alarms.borrow(cs); @@ -354,13 +332,14 @@ impl TimerDriver { // 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) }); + alarm_tim.write_reset_value(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) }); + alarm_tim.write_count_value(timer_ticks.unwrap() as u32); + alarm_tim.modify_control(|mut value| { + value.set_irq_enable(true); + value.set_enable(true); + value + }); } // If it's too far in the future, don't enable timer yet. // It will be enabled later by `next_period`. @@ -383,7 +362,7 @@ impl Driver for TimerDriver { // 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(); + counter_val = u32::MAX - Self::timekeeper_tim().read_count_value(); // Double read to protect against race conditions when the counter is overflowing. period2 = self.periods.load(Ordering::Relaxed); diff --git a/va108xx-hal/Cargo.toml b/va108xx-hal/Cargo.toml index 82449b9..7f3881c 100644 --- a/va108xx-hal/Cargo.toml +++ b/va108xx-hal/Cargo.toml @@ -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 = { path = "../vorago-shared-periphs", 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 diff --git a/va108xx-hal/src/clock.rs b/va108xx-hal/src/clock.rs index 3383145..6e9970a 100644 --- a/va108xx-hal/src/clock.rs +++ b/va108xx-hal/src/clock.rs @@ -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> = 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)) }); -} diff --git a/va108xx-hal/src/gpio/asynch.rs b/va108xx-hal/src/gpio/asynch.rs deleted file mode 100644 index 36c4ab8..0000000 --- a/va108xx-hal/src/gpio/asynch.rs +++ /dev/null @@ -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 { - 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( - pin: &mut Pin>, - 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 { - 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 { - 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 { - pin: Pin>, - irq: pac::Interrupt, -} - -impl InputPinAsync { - /// 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>, 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> { - self.pin - } -} -impl embedded_hal::digital::ErrorType for InputPinAsync { - type Error = core::convert::Infallible; -} - -impl Wait for InputPinAsync { - 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(()) - } -} diff --git a/va108xx-hal/src/gpio/dynpin.rs b/va108xx-hal/src/gpio/dynpin.rs deleted file mode 100644 index 993150c..0000000 --- a/va108xx-hal/src/gpio/dynpin.rs +++ /dev/null @@ -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.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 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 { - self.read_internal().map(|v| !v) - } - - #[inline(always)] - pub fn is_high(&self) -> Result { - 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(self) -> Result, 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::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 { - self.read_pin_masked() - } - - #[inline] - pub fn is_low_masked(&self) -> Result { - 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 { - 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 { - 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 { - self.is_low() - } - - // Only serves disambiguation purposes for the Embedded HAL impl - #[inline(always)] - fn is_high_mut(&mut self) -> Result { - self.is_high() - } - - #[inline(always)] - const fn mask_32(&self) -> u32 { - 1 << self.id().num() - } -} - -//================================================================================================== -// Convert between Pin and DynPin -//================================================================================================== - -impl From> for DynPin { - /// Erase the type-level information in a [`Pin`] and return a value-level - /// [`DynPin`] - #[inline] - fn from(pin: Pin) -> Self { - pin.downgrade() - } -} - -impl TryFrom for Pin { - 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 { - 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 { - self.is_high_mut() - } - #[inline] - fn is_low(&mut self) -> Result { - self.is_low_mut() - } -} - -impl embedded_hal::digital::StatefulOutputPin for DynPin { - #[inline] - fn is_set_high(&mut self) -> Result { - self.is_high_mut() - } - - #[inline] - fn is_set_low(&mut self) -> Result { - self.is_low_mut() - } - - #[inline] - fn toggle(&mut self) -> Result<(), Self::Error> { - self.toggle() - } -} diff --git a/va108xx-hal/src/gpio/mod.rs b/va108xx-hal/src/gpio/mod.rs index 6c95ae0..46bcd4c 100644 --- a/va108xx-hal/src/gpio/mod.rs +++ b/va108xx-hal/src/gpio/mod.rs @@ -1,74 +1,27 @@ -//! # 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) +//! - [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::*; -//================================================================================================== -// Errors, Definitions and Constants -//================================================================================================== +pub use vorago_shared_periphs::gpio::asynch; +/// Low-level GPIO access. +pub use vorago_shared_periphs::gpio::ll; -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::*; +/// GPIO register definitions. +pub use vorago_shared_periphs::gpio::regs; diff --git a/va108xx-hal/src/gpio/pin.rs b/va108xx-hal/src/gpio/pin.rs deleted file mode 100644 index 8575f2e..0000000 --- a/va108xx-hal/src/gpio/pin.rs +++ /dev/null @@ -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 -//! 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::(); -//! // Specify the target type and use `From`/`Into` -//! let pa0: Pin = 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; -/// Type-level variant of [`PinMode`] for pull-down input mode -pub type InputPullDown = Input; -/// Type-level variant of [`PinMode`] for pull-up input mode -pub type InputPullUp = Input; - -/// 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 { - cfg: PhantomData, -} - -impl Sealed for Input {} - -#[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 { - cfg: PhantomData, -} - -impl Sealed for Output {} - -/// Type-level variant of [`PinMode`] for push-pull output mode -pub type PushPullOutput = Output; -/// Type-level variant of [`PinMode`] for open drain output mode -pub type OutputOpenDrain = Output; - -pub type OutputReadablePushPull = Output; -pub type OutputReadableOpenDrain = Output; - -//================================================================================================== -// 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 { - cfg: PhantomData, -} - -impl Sealed for Alternate {} - -pub type AltFunc1 = Alternate; -pub type AltFunc2 = Alternate; -pub type AltFunc3 = Alternate; - -/// 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 PinMode for Input { - const DYN: DynPinMode = DynPinMode::Input(C::DYN); -} -impl PinMode for Output { - const DYN: DynPinMode = DynPinMode::Output(C::DYN); -} -impl PinMode for Alternate { - 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 { - inner: DynPin, - phantom: PhantomData<(I, M)>, -} - -impl Pin { - /// 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 { - 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(mut self) -> Pin { - // 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 { - 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 { - 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 { - self.into_mode() - } - - /// Configure the pin to operate as a floating input - #[inline] - pub fn into_floating_input(self) -> Pin { - self.into_mode() - } - - /// Configure the pin to operate as a pulled down input - #[inline] - pub fn into_pull_down_input(self) -> Pin { - self.into_mode() - } - - /// Configure the pin to operate as a pulled up input - #[inline] - pub fn into_pull_up_input(self) -> Pin { - self.into_mode() - } - - /// Configure the pin to operate as a push-pull output - #[inline] - pub fn into_push_pull_output(self) -> Pin { - self.into_mode() - } - - /// Configure the pin to operate as a readable push-pull output - #[inline] - pub fn into_readable_push_pull_output(self) -> Pin { - self.into_mode() - } - - /// Configure the pin to operate as a readable open-drain output - #[inline] - pub fn into_readable_open_drain_output(self) -> Pin { - 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 { - self.inner.is_high_masked() - } - - #[inline] - pub fn is_low_masked(&self) -> Result { - 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>` 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>, - Self: Into>, - Self: AsRef>, - Self: AsMut>, -{ - /// [`PinId`] of the corresponding [`Pin`] - type Id: PinId; - /// [`PinMode`] of the corresponding [`Pin`] - type Mode: PinMode; -} - -impl Sealed for Pin -where - I: PinId, - M: PinMode, -{ -} - -impl AnyPin for Pin -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

= Pin<

::Id,

::Mode>; - -impl AsRef

for SpecificPin

{ - #[inline] - fn as_ref(&self) -> &P { - // SAFETY: This is guaranteed to be safe, because P == SpecificPin

- // 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 AsMut

for SpecificPin

{ - #[inline] - fn as_mut(&mut self) -> &mut P { - // SAFETY: This is guaranteed to be safe, because P == SpecificPin

- // 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 Pin> { - /// 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 { - InputPinAsync::new(self, irq) - } -} - -impl Pin> { - #[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 Pin> { - /// 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 embedded_hal::digital::ErrorType for Pin -where - I: PinId, - M: PinMode, -{ - type Error = Infallible; -} - -impl embedded_hal::digital::OutputPin for Pin> { - #[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 embedded_hal::digital::InputPin for Pin> -where - I: PinId, - C: InputConfig, -{ - #[inline] - fn is_high(&mut self) -> Result { - Ok(self.is_high_mut()) - } - #[inline] - fn is_low(&mut self) -> Result { - Ok(self.is_low_mut()) - } -} - -impl embedded_hal::digital::StatefulOutputPin for Pin> -where - I: PinId, - C: OutputConfig + ReadableOutput, -{ - #[inline] - fn is_set_high(&mut self) -> Result { - Ok(self.is_high()) - } - #[inline] - fn is_set_low(&mut self) -> Result { - 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), - ] -); diff --git a/va108xx-hal/src/i2c.rs b/va108xx-hal/src/i2c.rs index 1dd53e8..eef3838 100644 --- a/va108xx-hal/src/i2c.rs +++ b/va108xx-hal/src/i2c.rs @@ -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 { - 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 { - 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, - // 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, - /// 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, - /// Optionally specify a second I2C address the slave interface responds to - pub addr_b: Option, - pub addr_b_mask: Option, -} - -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, - sys_clk: Hertz, -} - -impl I2cBase { - #[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 I2cBase { - pub fn new( - syscfg: &mut pac::Sysconfig, - sysclk: impl Into, - i2c: I2c, - speed_mode: I2cSpeed, - ms_cfg: Option<&MasterConfig>, - sl_cfg: Option<&SlaveConfig>, - ) -> Result { - 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 { - 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_base: I2cBase, - addr: PhantomData, -} - -impl I2cMaster { - pub fn new( - syscfg: &mut pac::Sysconfig, - sysclk: impl Into, - i2c: I2c, - cfg: MasterConfig, - speed_mode: I2cSpeed, - ) -> Result { - 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, - ) -> Result<(), Error> { - let mut iter = bytes.into_iter(); - // Load address - let (addr, addr_mode_bit) = I2cBase::::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 embedded_hal::i2c::ErrorType for I2cMaster { - type Error = Error; -} - -impl embedded_hal::i2c::I2c for I2cMaster { - 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 embedded_hal::i2c::ErrorType for I2cMaster { - type Error = Error; -} - -impl embedded_hal::i2c::I2c for I2cMaster { - 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_base: I2cBase, - addr: PhantomData, -} - -impl I2cSlave { - fn new_generic( - sys_cfg: &mut pac::Sysconfig, - sys_clk: impl Into, - i2c: I2c, - cfg: SlaveConfig, - speed_mode: I2cSpeed, - ) -> Result { - 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 I2cSlave { - /// Create a new I2C slave for seven bit addresses - pub fn new( - sys_cfg: &mut pac::Sysconfig, - sys_clk: impl Into, - i2c: I2c, - cfg: SlaveConfig, - speed_mode: I2cSpeed, - ) -> Result { - if let I2cAddress::TenBit(_) = cfg.addr { - return Err(InitError::WrongAddrMode); - } - Ok(Self::new_generic(sys_cfg, sys_clk, i2c, cfg, speed_mode)?) - } -} - -impl I2cSlave { - pub fn new_ten_bit_addr( - sys_cfg: &mut pac::Sysconfig, - sys_clk: impl Into, - i2c: I2c, - cfg: SlaveConfig, - speed_mode: I2cSpeed, - ) -> Result { - Self::new_generic(sys_cfg, sys_clk, i2c, cfg, speed_mode) - } -} +pub use vorago_shared_periphs::i2c::*; diff --git a/va108xx-hal/src/lib.rs b/va108xx-hal/src/lib.rs index 1b28470..ca23b3a 100644 --- a/va108xx-hal/src/lib.rs +++ b/va108xx-hal/src/lib.rs @@ -8,69 +8,23 @@ 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::FunSel; -#[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, -} +/// This is the NONE destination reigster value for the IRQSEL peripheral. +pub const IRQ_DST_NONE: u32 = 0xffffffff; -/// 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; +pub use vorago_shared_periphs::{ + disable_nvic_interrupt, enable_nvic_interrupt, InterruptConfig, PeripheralSelect, +}; #[derive(Debug, PartialEq, Eq, thiserror::Error)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -101,20 +55,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 {} } diff --git a/va108xx-hal/src/pins.rs b/va108xx-hal/src/pins.rs new file mode 100644 index 0000000..8500714 --- /dev/null +++ b/va108xx-hal/src/pins.rs @@ -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::*; diff --git a/va108xx-hal/src/pwm.rs b/va108xx-hal/src/pwm.rs index b3f1de0..22ad292 100644 --- a/va108xx-hal/src/pwm.rs +++ b/va108xx-hal/src/pwm.rs @@ -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_and_tim: (Pin, Tim), - inner: ReducedPwmPin, - mode: PhantomData, -} - -impl PwmPin -where - (Pin, Tim): ValidTimAndPin, -{ - /// Create a new stronlgy typed PWM pin - pub fn new( - sys_cfg: &mut pac::Sysconfig, - sys_clk: impl Into + Copy, - pin_and_tim: (Pin, Tim), - initial_period: impl Into + Copy, - ) -> Self { - let mut pin = PwmPin { - pin_and_tim, - inner: ReducedPwmPin::::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 { - 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) { - 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 From> for PwmPin -where - (Pin, Tim): ValidTimAndPin, -{ - fn from(other: PwmPin) -> Self { - let mut pwmb = Self { - mode: PhantomData, - pin_and_tim: other.pin_and_tim, - inner: other.inner.into(), - }; - pwmb.enable_pwm_b(); - pwmb - } -} - -impl From> for PwmPin -where - (PIN, TIM): ValidTimAndPin, -{ - fn from(other: PwmPin) -> Self { - let mut pwma = Self { - mode: PhantomData, - pin_and_tim: other.pin_and_tim, - inner: other.inner.into(), - }; - pwma.enable_pwm_a(); - pwma - } -} - -impl PwmPin -where - (Pin, Tim): ValidTimAndPin, -{ - pub fn pwma( - sys_cfg: &mut pac::Sysconfig, - sys_clk: impl Into + Copy, - pin_and_tim: (Pin, Tim), - initial_period: impl Into + Copy, - ) -> Self { - let mut pin: PwmPin = - Self::new(sys_cfg, sys_clk, pin_and_tim, initial_period); - pin.enable_pwm_a(); - pin - } -} - -impl PwmPin -where - (Pin, Tim): ValidTimAndPin, -{ - pub fn pwmb( - sys_cfg: &mut pac::Sysconfig, - sys_clk: impl Into + Copy, - pin_and_tim: (Pin, Tim), - initial_period: impl Into + Copy, - ) -> Self { - let mut pin: PwmPin = - 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 { - dyn_reg: TimDynRegister, - common: PwmCommon, - mode: PhantomData, -} - -impl ReducedPwmPin { - 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 ReducedPwmPin { - #[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) { - 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 From> for ReducedPwmPin -where - (Pin, Tim): ValidTimAndPin, -{ - fn from(value: PwmPin) -> Self { - value.downgrade() - } -} - -impl From> for ReducedPwmPin -where - (Pin, Tim): ValidTimAndPin, -{ - fn from(value: PwmPin) -> Self { - value.downgrade() - } -} - -impl From> for ReducedPwmPin { - fn from(other: ReducedPwmPin) -> Self { - let mut pwmb = Self { - dyn_reg: other.dyn_reg, - common: other.common, - mode: PhantomData, - }; - pwmb.enable_pwm_b(); - pwmb - } -} - -impl From> for ReducedPwmPin { - fn from(other: ReducedPwmPin) -> Self { - let mut pwmb = Self { - dyn_reg: other.dyn_reg, - common: other.common, - mode: PhantomData, - }; - pwmb.enable_pwm_a(); - pwmb - } -} - -//================================================================================================== -// PWMB implementations -//================================================================================================== - -impl PwmPin -where - (Pin, Tim): ValidTimAndPin, -{ - 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 { - #[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 embedded_hal::pwm::ErrorType for PwmPin { - 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 embedded_hal::pwm::SetDutyCycle for PwmPin { - #[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::*; diff --git a/va108xx-hal/src/spi.rs b/va108xx-hal/src/spi.rs deleted file mode 100644 index afc8b5c..0000000 --- a/va108xx-hal/src/spi.rs +++ /dev/null @@ -1,1260 +0,0 @@ -//! API for the SPI peripheral. -//! -//! The main abstraction provided by this module are the [Spi] and the [SpiBase] structure. -//! These provide 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) -use crate::{ - clock::enable_peripheral_clock, - gpio::pin::{ - AltFunc1, AltFunc2, AltFunc3, Pin, PA10, PA11, PA12, PA13, PA14, PA15, PA16, PA17, PA18, - PA19, PA20, PA21, PA22, PA23, PA24, PA25, PA26, PA27, PA28, PA29, PA30, PA31, PB0, PB1, - PB10, PB11, PB12, PB13, PB14, PB15, PB16, PB17, PB18, PB19, PB2, PB22, PB23, PB3, PB4, PB5, - PB6, PB7, PB8, PB9, - }, - pac, - time::Hertz, - typelevel::Sealed, - PeripheralSelect, -}; -use core::{convert::Infallible, fmt::Debug, marker::PhantomData, ops::Deref}; -use embedded_hal::spi::{Mode, MODE_0}; - -//================================================================================================== -// Defintions -//================================================================================================== - -// FIFO has a depth of 16. -const FILL_DEPTH: usize = 12; - -pub const BMSTART_BMSTOP_MASK: u32 = 1 << 31; -pub const BMSKIPDATA_MASK: u32 = 1 << 30; - -pub const DEFAULT_CLK_DIV: u16 = 2; - -#[derive(Debug, PartialEq, Eq, Copy, Clone)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum HwChipSelectId { - Id0 = 0, - Id1 = 1, - Id2 = 2, - Id3 = 3, - Id4 = 4, - Id5 = 5, - Id6 = 6, - Id7 = 7, - Invalid = 0xff, -} - -#[derive(Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum SpiPort { - Porta = 0, - Portb = 1, - Portc = 2, - Invalid = 3, -} - -#[derive(Debug, PartialEq, Eq, Copy, Clone)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum WordSize { - OneBit = 0x00, - FourBits = 0x03, - EightBits = 0x07, - SixteenBits = 0x0f, -} - -pub type SpiRegBlock = pac::spia::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 { - const IDX: u8; - const PERIPH_SEL: PeripheralSelect; - - fn ptr() -> *const SpiRegBlock; -} - -impl Instance for pac::Spia { - const IDX: u8 = 0; - const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Spi0; - - #[inline(always)] - fn ptr() -> *const SpiRegBlock { - Self::ptr() - } -} - -impl Instance for pac::Spib { - const IDX: u8 = 1; - const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Spi1; - - #[inline(always)] - fn ptr() -> *const SpiRegBlock { - Self::ptr() - } -} - -impl Instance for pac::Spic { - const IDX: u8 = 2; - const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Spi2; - - #[inline(always)] - fn ptr() -> *const SpiRegBlock { - Self::ptr() - } -} - -//================================================================================================== -// Pin type definitions -//================================================================================================== - -pub trait PinSck: Sealed {} -pub trait PinMosi: Sealed {} -pub trait PinMiso: Sealed {} - -pub trait HwCsProvider: Sealed { - const CS_ID: HwChipSelectId; - const SPI_PORT: SpiPort; -} - -pub trait OptionalHwCs: HwCsProvider + Sealed {} - -macro_rules! hw_cs_pins { - ($SPIx:path, $portId: path: - $( - ($PXx:ident, $AFx:ident, $HwCsIdent:path, $typedef:ident), - )+ - ) => { - $( - impl HwCsProvider for Pin<$PXx, $AFx> { - const CS_ID: HwChipSelectId = $HwCsIdent; - const SPI_PORT: SpiPort = $portId; - } - impl OptionalHwCs<$SPIx> for Pin<$PXx, $AFx> {} - pub type $typedef = Pin<$PXx, $AFx>; - )+ - }; -} - -impl HwCsProvider for NoneT { - const CS_ID: HwChipSelectId = HwChipSelectId::Invalid; - const SPI_PORT: SpiPort = SpiPort::Invalid; -} - -impl OptionalHwCs for NoneT {} -impl OptionalHwCs for NoneT {} - -// SPIA - -impl PinSck for Pin {} -impl PinMosi for Pin {} -impl PinMiso for Pin {} - -pub type SpiAPortASck = Pin; -pub type SpiAPortAMosi = Pin; -pub type SpiAPortAMiso = Pin; - -impl PinSck for Pin {} -impl PinMosi for Pin {} -impl PinMiso for Pin {} - -pub type SpiAPortBSck = Pin; -pub type SpiAPortBMosi = Pin; -pub type SpiAPortBMiso = Pin; - -hw_cs_pins!( - pac::Spia, SpiPort::Porta: - (PA28, AltFunc1, HwChipSelectId::Id0, HwCs0SpiAPortA), - (PA27, AltFunc1, HwChipSelectId::Id1, HwCs1SpiAPortA), - (PA26, AltFunc1, HwChipSelectId::Id2, HwCs2SpiAPortA), - (PA25, AltFunc1, HwChipSelectId::Id3, HwCs3SpiAPortA), - (PA24, AltFunc1, HwChipSelectId::Id4, HwCs4SpiAPortA), - (PA23, AltFunc1, HwChipSelectId::Id5, HwCs5SpiAPortA), - (PA22, AltFunc1, HwChipSelectId::Id6, HwCs6SpiAPortA), - (PA21, AltFunc1, HwChipSelectId::Id7, HwCs7SpiAPortA), - (PB6, AltFunc2, HwChipSelectId::Id0, HwCs0SpiAPortB), - (PB5, AltFunc2, HwChipSelectId::Id6, HwCs6SpiAPortB), - (PB4, AltFunc2, HwChipSelectId::Id5, HwCs5SpiAPortB), - (PB3, AltFunc2, HwChipSelectId::Id4, HwCs4SpiAPortB), - (PB2, AltFunc2, HwChipSelectId::Id3, HwCs3SpiAPortB), - (PB1, AltFunc2, HwChipSelectId::Id2, HwCs2SpiAPortB), - (PB0, AltFunc2, HwChipSelectId::Id1, HwCs1SpiAPortB), -); - -// SPIB - -impl PinSck for Pin {} -impl PinMosi for Pin {} -impl PinMiso for Pin {} - -pub type SpiBPortASck = Pin; -pub type SpiBPortAMosi = Pin; -pub type SpiBPortAMiso = Pin; - -impl PinSck for Pin {} -impl PinMosi for Pin {} -impl PinMiso for Pin {} - -impl PinSck for Pin {} -impl PinMosi for Pin {} -impl PinMiso for Pin {} - -hw_cs_pins!( - pac::Spib, SpiPort::Portb: - (PB16, AltFunc1, HwChipSelectId::Id0, HwCs0SpiBPortB0), - (PB15, AltFunc1, HwChipSelectId::Id1, HwCs1SpiBPortB0), - (PB14, AltFunc1, HwChipSelectId::Id2, HwCs2SpiBPortB0), - (PB13, AltFunc1, HwChipSelectId::Id3, HwCs3SpiBPortB), - (PB12, AltFunc1, HwChipSelectId::Id4, HwCs4SpiBPortB), - (PB11, AltFunc1, HwChipSelectId::Id5, HwCs5SpiBPortB), - (PB12, AltFunc2, HwChipSelectId::Id0, HwCs0SpiBPortB2), - (PB11, AltFunc2, HwChipSelectId::Id1, HwCs1SpiBPortB2), - (PB10, AltFunc1, HwChipSelectId::Id6, HwCs6SpiBPortB), - (PB10, AltFunc2, HwChipSelectId::Id2, HwCs2SpiBPortB2), - (PB2, AltFunc1, HwChipSelectId::Id0, HwCs0SpiBPortB1), - (PB1, AltFunc1, HwChipSelectId::Id1, HwCs1SpiBPortB1), - (PB0, AltFunc1, HwChipSelectId::Id2, HwCs2SpiBPortB1), - (PA17, AltFunc2, HwChipSelectId::Id0, HwCs0SpiBPortA), - (PA16, AltFunc2, HwChipSelectId::Id1, HwCs1SpiBPortA), - (PA15, AltFunc2, HwChipSelectId::Id2, HwCs2SpiBPortA), - (PA14, AltFunc2, HwChipSelectId::Id3, HwCs3SpiBPortA), - (PA13, AltFunc2, HwChipSelectId::Id4, HwCs4SpiBPortA), - (PA12, AltFunc2, HwChipSelectId::Id5, HwCs5SpiBPortA0), - (PA11, AltFunc2, HwChipSelectId::Id6, HwCs6SpiBPortA0), - (PA10, AltFunc2, HwChipSelectId::Id7, HwCs7SpiBPortA0), - (PA23, AltFunc2, HwChipSelectId::Id5, HwCs5SpiBPortA1), - (PA22, AltFunc2, HwChipSelectId::Id6, HwCs6SpiBPortA1), - (PA21, AltFunc2, HwChipSelectId::Id7, HwCs7SpiBPortA1), -); - -// SPIC - -// Dummy pin defintion for the ROM SCK. -pub struct RomSck; -// Dummy pin defintion for the ROM MOSI. -pub struct RomMosi; -// Dummy pin defintion for the ROM MISO. -pub struct RomMiso; -// Dummy pin defintion for the ROM chip select. -pub struct RomCs; - -impl Sealed for RomSck {} -impl PinSck for RomSck {} -impl Sealed for RomMosi {} -impl PinMosi for RomMosi {} -impl Sealed for RomMiso {} -impl PinMiso for RomMiso {} -impl Sealed for RomCs {} - -hw_cs_pins!( - pac::Spic, SpiPort::Portc: - (PB9, AltFunc3, HwChipSelectId::Id1, HwCs1SpiCPortB0), - (PB8, AltFunc3, HwChipSelectId::Id2, HwCs2SpiCPortB0), - (PB7, AltFunc3, HwChipSelectId::Id3, HwCs3SpiCPortB), - (PB23, AltFunc3, HwChipSelectId::Id2, HwCs2SpiCPortB1), - (PB22, AltFunc3, HwChipSelectId::Id1, HwCs1SpiCPortB1), - (PA20, AltFunc1, HwChipSelectId::Id1, HwCs1SpiCPortA0), - (PA19, AltFunc1, HwChipSelectId::Id2, HwCs2SpiCPortA0), - (PB18, AltFunc1, HwChipSelectId::Id3, HwCs3SpiCPortA0), - (PA23, AltFunc3, HwChipSelectId::Id1, HwCs1SpiCPortA1), - (PA22, AltFunc3, HwChipSelectId::Id2, HwCs2SpiCPortA1), - (PA21, AltFunc3, HwChipSelectId::Id3, HwCs3SpiCPortA1), - (PA20, AltFunc3, HwChipSelectId::Id4, HwCs4SpiCPortA), -); - -impl HwCsProvider for RomCs { - const CS_ID: HwChipSelectId = HwChipSelectId::Id0; - const SPI_PORT: SpiPort = SpiPort::Portc; -} -impl OptionalHwCs for RomCs {} - -//================================================================================================== -// Config -//================================================================================================== - -pub trait TransferConfigProvider { - fn sod(&mut self, sod: bool); - fn blockmode(&mut self, blockmode: bool); - fn mode(&mut self, mode: Mode); - fn clk_cfg(&mut self, clk_cfg: SpiClkConfig); - fn hw_cs_id(&self) -> u8; -} - -/// This struct contains all configuration parameter which are transfer specific -/// and might change for transfers to different SPI slaves -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct TransferConfigWithHwcs { - pub hw_cs: Option, - pub cfg: TransferConfig, -} - -/// Type erased variant of the transfer configuration. This is required to avoid generics in -/// the SPI constructor. -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct TransferConfig { - pub clk_cfg: Option, - pub mode: Option, - pub sod: bool, - /// If this is enabled, all data in the FIFO is transmitted in a single frame unless - /// the BMSTOP bit is set on a dataword. A frame is defined as CSn being active for the - /// duration of multiple data words - pub blockmode: bool, - /// Only used when blockmode is used. The SCK will be stalled until an explicit stop bit - /// is set on a written word. - pub bmstall: bool, - pub hw_cs: HwChipSelectId, -} - -impl TransferConfigWithHwcs { - pub fn new_no_hw_cs( - clk_cfg: Option, - mode: Option, - blockmode: bool, - bmstall: bool, - sod: bool, - ) -> Self { - TransferConfigWithHwcs { - hw_cs: None, - cfg: TransferConfig { - clk_cfg, - mode, - sod, - blockmode, - bmstall, - hw_cs: HwChipSelectId::Invalid, - }, - } - } -} - -impl TransferConfigWithHwcs { - pub fn new( - clk_cfg: Option, - mode: Option, - hw_cs: Option, - blockmode: bool, - bmstall: bool, - sod: bool, - ) -> Self { - TransferConfigWithHwcs { - hw_cs, - cfg: TransferConfig { - clk_cfg, - mode, - sod, - blockmode, - bmstall, - hw_cs: HwCs::CS_ID, - }, - } - } - - pub fn downgrade(self) -> TransferConfig { - self.cfg - } -} - -impl TransferConfigProvider for TransferConfigWithHwcs { - /// Slave Output Disable - fn sod(&mut self, sod: bool) { - self.cfg.sod = sod; - } - - fn blockmode(&mut self, blockmode: bool) { - self.cfg.blockmode = blockmode; - } - - fn mode(&mut self, mode: Mode) { - self.cfg.mode = Some(mode); - } - - fn clk_cfg(&mut self, clk_cfg: SpiClkConfig) { - self.cfg.clk_cfg = Some(clk_cfg); - } - - fn hw_cs_id(&self) -> u8 { - HwCs::CS_ID as u8 - } -} - -/// Configuration options for the whole SPI bus. See Programmer Guide p.92 for more details -#[derive(Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct SpiConfig { - clk: SpiClkConfig, - // SPI mode configuration - pub init_mode: Mode, - /// If this is enabled, all data in the FIFO is transmitted in a single frame unless - /// the BMSTOP bit is set on a dataword. A frame is defined as CSn being active for the - /// duration of multiple data words. Defaults to true. - pub blockmode: bool, - /// This enables the stalling of the SPI SCK if in blockmode and the FIFO is empty. - /// Currently enabled by default. - pub bmstall: bool, - /// By default, configure SPI for master mode (ms == false) - ms: bool, - /// Slave output disable. Useful if separate GPIO pins or decoders are used for CS control - pub slave_output_disable: bool, - /// Loopback mode. If you use this, don't connect MISO to MOSI, they will be tied internally - pub loopback_mode: bool, - /// Enable Master Delayer Capture Mode. See Programmers Guide p.92 for more details - pub master_delayer_capture: bool, -} - -impl Default for SpiConfig { - fn default() -> Self { - Self { - init_mode: MODE_0, - blockmode: true, - bmstall: true, - // Default value is definitely valid. - clk: SpiClkConfig::from_div(DEFAULT_CLK_DIV).unwrap(), - ms: Default::default(), - slave_output_disable: Default::default(), - loopback_mode: Default::default(), - master_delayer_capture: Default::default(), - } - } -} - -impl SpiConfig { - pub fn loopback(mut self, enable: bool) -> Self { - self.loopback_mode = enable; - self - } - - pub fn blockmode(mut self, enable: bool) -> Self { - self.blockmode = enable; - self - } - - pub fn bmstall(mut self, enable: bool) -> Self { - self.bmstall = enable; - self - } - - pub fn mode(mut self, mode: Mode) -> Self { - self.init_mode = mode; - self - } - - pub fn clk_cfg(mut self, clk_cfg: SpiClkConfig) -> Self { - self.clk = clk_cfg; - self - } - - pub fn master_mode(mut self, master: bool) -> Self { - self.ms = !master; - self - } - - pub fn slave_output_disable(mut self, sod: bool) -> Self { - self.slave_output_disable = sod; - self - } -} - -//================================================================================================== -// Word Size -//================================================================================================== - -/// Configuration trait for the Word Size -/// used by the SPI peripheral -pub trait WordProvider: Copy + Default + Into + TryFrom + 'static { - const MASK: u32; - fn word_reg() -> u8; -} - -impl WordProvider for u8 { - const MASK: u32 = 0xff; - fn word_reg() -> u8 { - 0x07 - } -} - -impl WordProvider for u16 { - const MASK: u32 = 0xffff; - fn word_reg() -> u8 { - 0x0f - } -} - -//================================================================================================== -// Spi -//================================================================================================== - -/// Low level access trait for the SPI peripheral. -pub trait SpiLowLevel { - /// Low level function to write a word to the SPI FIFO but also checks whether - /// there is actually data in the FIFO. - /// - /// Uses the [nb] API to allow usage in blocking and non-blocking contexts. - fn write_fifo(&self, data: u32) -> nb::Result<(), Infallible>; - - /// Low level function to write a word to the SPI FIFO without checking whether - /// there FIFO is full. - /// - /// This does not necesarily mean there is a space in the FIFO available. - /// Use [Self::write_fifo] function to write a word into the FIFO reliably. - fn write_fifo_unchecked(&self, data: u32); - - /// Low level function to read a word from the SPI FIFO. Must be preceeded by a - /// [Self::write_fifo] call. - /// - /// Uses the [nb] API to allow usage in blocking and non-blocking contexts. - fn read_fifo(&self) -> nb::Result; - - /// Low level function to read a word from from the SPI FIFO. - /// - /// This does not necesarily mean there is a word in the FIFO available. - /// Use the [Self::read_fifo] function to read a word from the FIFO reliably using the [nb] - /// API. - /// You might also need to mask the value to ignore the BMSTART/BMSTOP bit. - fn read_fifo_unchecked(&self) -> u32; -} - -pub struct SpiBase { - spi: SpiInstance, - cfg: SpiConfig, - sys_clk: Hertz, - /// Fill word for read-only SPI transactions. - pub fill_word: Word, - blockmode: bool, - bmstall: bool, - word: PhantomData, -} - -pub struct Spi { - inner: SpiBase, - pins: Pins, -} - -#[inline(always)] -pub fn mode_to_cpo_cph_bit(mode: embedded_hal::spi::Mode) -> (bool, bool) { - match mode { - embedded_hal::spi::MODE_0 => (false, false), - embedded_hal::spi::MODE_1 => (false, true), - embedded_hal::spi::MODE_2 => (true, false), - embedded_hal::spi::MODE_3 => (true, true), - } -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct SpiClkConfig { - prescale_val: u16, - scrdv: u8, -} - -impl SpiClkConfig { - pub fn prescale_val(&self) -> u16 { - self.prescale_val - } - pub fn scrdv(&self) -> u8 { - self.scrdv - } -} - -impl SpiClkConfig { - pub fn new(prescale_val: u16, scrdv: u8) -> Self { - Self { - prescale_val, - scrdv, - } - } - - pub fn from_div(div: u16) -> Result { - spi_clk_config_from_div(div) - } - - pub fn from_clk(sys_clk: impl Into, spi_clk: impl Into) -> Option { - clk_div_for_target_clock(sys_clk, spi_clk).map(|div| spi_clk_config_from_div(div).unwrap()) - } -} - -#[derive(Debug, thiserror::Error)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum SpiClkConfigError { - #[error("division by zero")] - DivIsZero, - #[error("divide value is not even")] - DivideValueNotEven, - #[error("scrdv value is too large")] - ScrdvValueTooLarge, -} - -#[inline] -pub fn spi_clk_config_from_div(mut div: u16) -> Result { - if div == 0 { - return Err(SpiClkConfigError::DivIsZero); - } - if div % 2 != 0 { - return Err(SpiClkConfigError::DivideValueNotEven); - } - let mut prescale_val = 0; - - // find largest (even) prescale value that divides into div - for i in (2..=0xfe).rev().step_by(2) { - if div % i == 0 { - prescale_val = i; - break; - } - } - - if prescale_val == 0 { - return Err(SpiClkConfigError::DivideValueNotEven); - } - - div /= prescale_val; - if div > u8::MAX as u16 + 1 { - return Err(SpiClkConfigError::ScrdvValueTooLarge); - } - Ok(SpiClkConfig { - prescale_val, - scrdv: (div - 1) as u8, - }) -} - -#[inline] -pub fn clk_div_for_target_clock( - sys_clk: impl Into, - spi_clk: impl Into, -) -> Option { - let spi_clk = spi_clk.into(); - let sys_clk = sys_clk.into(); - if spi_clk > sys_clk { - return None; - } - - // Step 1: Calculate raw divider. - let raw_div = sys_clk.raw() / spi_clk.raw(); - let remainder = sys_clk.raw() % spi_clk.raw(); - - // Step 2: Round up if necessary. - let mut rounded_div = if remainder * 2 >= spi_clk.raw() { - raw_div + 1 - } else { - raw_div - }; - - if rounded_div % 2 != 0 { - // Take slower clock conservatively. - rounded_div += 1; - } - if rounded_div > u16::MAX as u32 { - return None; - } - Some(rounded_div as u16) -} - -// Re-export this so it can be used for the constructor -pub use crate::typelevel::NoneT; - -impl SpiBase -where - >::Error: core::fmt::Debug, -{ - #[inline] - pub fn spi(&self) -> &SpiInstance { - &self.spi - } - - #[inline] - pub fn cfg_clock(&mut self, cfg: SpiClkConfig) { - self.spi - .ctrl0() - .modify(|_, w| unsafe { w.scrdv().bits(cfg.scrdv) }); - self.spi - .clkprescale() - .write(|w| unsafe { w.bits(cfg.prescale_val as u32) }); - } - - #[inline] - pub fn cfg_clock_from_div(&mut self, div: u16) -> Result<(), SpiClkConfigError> { - let val = spi_clk_config_from_div(div)?; - self.cfg_clock(val); - Ok(()) - } - - #[inline] - pub fn cfg_mode(&mut self, mode: Mode) { - let (cpo_bit, cph_bit) = mode_to_cpo_cph_bit(mode); - self.spi.ctrl0().modify(|_, w| { - w.spo().bit(cpo_bit); - w.sph().bit(cph_bit) - }); - } - - #[inline] - pub fn fill_word(&self) -> Word { - self.fill_word - } - - #[inline] - pub fn clear_tx_fifo(&self) { - self.spi.fifo_clr().write(|w| w.txfifo().set_bit()); - } - - #[inline] - pub fn clear_rx_fifo(&self) { - self.spi.fifo_clr().write(|w| w.rxfifo().set_bit()); - } - - #[inline] - pub fn perid(&self) -> u32 { - self.spi.perid().read().bits() - } - - /// Configure the hardware chip select given a hardware chip select ID. - #[inline] - pub fn cfg_hw_cs(&mut self, hw_cs: HwChipSelectId) { - if hw_cs == HwChipSelectId::Invalid { - return; - } - self.spi.ctrl1().modify(|_, w| { - w.sod().clear_bit(); - unsafe { - w.ss().bits(hw_cs as u8); - } - w - }); - } - - /// Configure the hardware chip select given a physical hardware CS pin. - #[inline] - pub fn cfg_hw_cs_with_pin>(&mut self, _: &HwCs) { - self.cfg_hw_cs(HwCs::CS_ID); - } - - /// Disables the hardware chip select functionality. This can be used when performing - /// external chip select handling, for example with GPIO pins. - #[inline] - pub fn cfg_hw_cs_disable(&mut self) { - self.spi.ctrl1().modify(|_, w| { - w.sod().set_bit(); - w - }); - } - - /// Utility function to configure all relevant transfer parameters in one go. - /// This is useful if multiple devices with different clock and mode configurations - /// are connected to one bus. - pub fn cfg_transfer>( - &mut self, - transfer_cfg: &TransferConfigWithHwcs, - ) { - if let Some(trans_clk_div) = transfer_cfg.cfg.clk_cfg { - self.cfg_clock(trans_clk_div); - } - if let Some(mode) = transfer_cfg.cfg.mode { - self.cfg_mode(mode); - } - self.blockmode = transfer_cfg.cfg.blockmode; - self.spi.ctrl1().modify(|_, w| { - if transfer_cfg.cfg.sod { - w.sod().set_bit(); - } else if transfer_cfg.hw_cs.is_some() { - w.sod().clear_bit(); - unsafe { - w.ss().bits(HwCs::CS_ID as u8); - } - } else { - w.sod().clear_bit(); - } - w.blockmode().bit(transfer_cfg.cfg.blockmode); - w.bmstall().bit(transfer_cfg.cfg.bmstall) - }); - } - - fn flush_internal(&self) { - let mut status_reg = self.spi.status().read(); - while status_reg.tfe().bit_is_clear() - || status_reg.rne().bit_is_set() - || status_reg.busy().bit_is_set() - { - if status_reg.rne().bit_is_set() { - self.read_fifo_unchecked(); - } - status_reg = self.spi.status().read(); - } - } - - fn transfer_preparation(&self, words: &[Word]) -> Result<(), Infallible> { - if words.is_empty() { - return Ok(()); - } - self.flush_internal(); - Ok(()) - } - - // The FIFO can hold a guaranteed amount of data, so we can pump it on transfer - // initialization. Returns the amount of written bytes. - fn initial_send_fifo_pumping_with_words(&self, words: &[Word]) -> usize { - if self.blockmode { - self.spi.ctrl1().modify(|_, w| w.mtxpause().set_bit()); - } - // Fill the first half of the write FIFO - let mut current_write_idx = 0; - let smaller_idx = core::cmp::min(FILL_DEPTH, words.len()); - for _ in 0..smaller_idx { - if current_write_idx == smaller_idx.saturating_sub(1) && self.bmstall { - self.write_fifo_unchecked(words[current_write_idx].into() | BMSTART_BMSTOP_MASK); - } else { - self.write_fifo_unchecked(words[current_write_idx].into()); - } - current_write_idx += 1; - } - if self.blockmode { - self.spi.ctrl1().modify(|_, w| w.mtxpause().clear_bit()); - } - current_write_idx - } - - // The FIFO can hold a guaranteed amount of data, so we can pump it on transfer - // initialization. - fn initial_send_fifo_pumping_with_fill_words(&self, send_len: usize) -> usize { - if self.blockmode { - self.spi.ctrl1().modify(|_, w| w.mtxpause().set_bit()); - } - // Fill the first half of the write FIFO - let mut current_write_idx = 0; - let smaller_idx = core::cmp::min(FILL_DEPTH, send_len); - for _ in 0..smaller_idx { - if current_write_idx == smaller_idx.saturating_sub(1) && self.bmstall { - self.write_fifo_unchecked(self.fill_word.into() | BMSTART_BMSTOP_MASK); - } else { - self.write_fifo_unchecked(self.fill_word.into()); - } - current_write_idx += 1; - } - if self.blockmode { - self.spi.ctrl1().modify(|_, w| w.mtxpause().clear_bit()); - } - current_write_idx - } -} - -impl SpiLowLevel for SpiBase -where - >::Error: core::fmt::Debug, -{ - #[inline(always)] - fn write_fifo(&self, data: u32) -> nb::Result<(), Infallible> { - if self.spi.status().read().tnf().bit_is_clear() { - return Err(nb::Error::WouldBlock); - } - self.write_fifo_unchecked(data); - Ok(()) - } - - #[inline(always)] - fn write_fifo_unchecked(&self, data: u32) { - self.spi.data().write(|w| unsafe { w.bits(data) }); - } - - #[inline(always)] - fn read_fifo(&self) -> nb::Result { - if self.spi.status().read().rne().bit_is_clear() { - return Err(nb::Error::WouldBlock); - } - Ok(self.read_fifo_unchecked()) - } - - #[inline(always)] - fn read_fifo_unchecked(&self) -> u32 { - self.spi.data().read().bits() - } -} - -impl embedded_hal::spi::ErrorType for SpiBase { - type Error = Infallible; -} - -impl embedded_hal::spi::SpiBus for SpiBase -where - >::Error: core::fmt::Debug, -{ - fn read(&mut self, words: &mut [Word]) -> Result<(), Self::Error> { - self.transfer_preparation(words)?; - let mut current_read_idx = 0; - let mut current_write_idx = self.initial_send_fifo_pumping_with_fill_words(words.len()); - loop { - if current_read_idx < words.len() { - words[current_read_idx] = (nb::block!(self.read_fifo())? & Word::MASK) - .try_into() - .unwrap(); - current_read_idx += 1; - } - if current_write_idx < words.len() { - if current_write_idx == words.len() - 1 && self.bmstall { - nb::block!(self.write_fifo(self.fill_word.into() | BMSTART_BMSTOP_MASK))?; - } else { - nb::block!(self.write_fifo(self.fill_word.into()))?; - } - current_write_idx += 1; - } - if current_read_idx >= words.len() && current_write_idx >= words.len() { - break; - } - } - Ok(()) - } - - fn write(&mut self, words: &[Word]) -> Result<(), Self::Error> { - self.transfer_preparation(words)?; - let mut current_write_idx = self.initial_send_fifo_pumping_with_words(words); - while current_write_idx < words.len() { - if current_write_idx == words.len() - 1 && self.bmstall { - nb::block!(self.write_fifo(words[current_write_idx].into() | BMSTART_BMSTOP_MASK))?; - } else { - nb::block!(self.write_fifo(words[current_write_idx].into()))?; - } - current_write_idx += 1; - // Ignore received words. - if self.spi.status().read().rne().bit_is_set() { - self.clear_rx_fifo(); - } - } - Ok(()) - } - - fn transfer(&mut self, read: &mut [Word], write: &[Word]) -> Result<(), Self::Error> { - self.transfer_preparation(write)?; - let mut current_read_idx = 0; - let mut current_write_idx = self.initial_send_fifo_pumping_with_words(write); - while current_read_idx < read.len() || current_write_idx < write.len() { - if current_write_idx < write.len() { - if current_write_idx == write.len() - 1 && self.bmstall { - nb::block!( - self.write_fifo(write[current_write_idx].into() | BMSTART_BMSTOP_MASK) - )?; - } else { - nb::block!(self.write_fifo(write[current_write_idx].into()))?; - } - current_write_idx += 1; - } - if current_read_idx < read.len() { - read[current_read_idx] = (nb::block!(self.read_fifo())? & Word::MASK) - .try_into() - .unwrap(); - current_read_idx += 1; - } - } - - Ok(()) - } - - fn transfer_in_place(&mut self, words: &mut [Word]) -> Result<(), Self::Error> { - self.transfer_preparation(words)?; - let mut current_read_idx = 0; - let mut current_write_idx = self.initial_send_fifo_pumping_with_words(words); - - while current_read_idx < words.len() || current_write_idx < words.len() { - if current_write_idx < words.len() { - if current_write_idx == words.len() - 1 && self.bmstall { - nb::block!( - self.write_fifo(words[current_write_idx].into() | BMSTART_BMSTOP_MASK) - )?; - } else { - nb::block!(self.write_fifo(words[current_write_idx].into()))?; - } - current_write_idx += 1; - } - if current_read_idx < words.len() && current_read_idx < current_write_idx { - words[current_read_idx] = (nb::block!(self.read_fifo())? & Word::MASK) - .try_into() - .unwrap(); - current_read_idx += 1; - } - } - Ok(()) - } - - fn flush(&mut self) -> Result<(), Self::Error> { - self.flush_internal(); - Ok(()) - } -} - -impl< - SpiI: Instance, - Sck: PinSck, - Miso: PinMiso, - Mosi: PinMosi, - Word: WordProvider, - > Spi -where - >::Error: core::fmt::Debug, -{ - /// Create a new SPI struct - /// - /// You can delete the pin type information by calling the - /// [`downgrade`](Self::downgrade) function - /// - /// ## Arguments - /// * `syscfg` - Can be passed optionally to enable the peripheral clock - /// * `sys_clk` - System clock - /// * `spi` - SPI bus to use - /// * `pins` - Pins to be used for SPI transactions. These pins are consumed - /// to ensure the pins can not be used for other purposes anymore - /// * `spi_cfg` - Configuration specific to the SPI bus - pub fn new( - syscfg: &mut pac::Sysconfig, - sys_clk: impl Into, - spi: SpiI, - pins: (Sck, Miso, Mosi), - spi_cfg: SpiConfig, - ) -> Self { - enable_peripheral_clock(syscfg, SpiI::PERIPH_SEL); - let SpiConfig { - clk, - init_mode, - blockmode, - bmstall, - ms, - slave_output_disable, - loopback_mode, - master_delayer_capture, - } = spi_cfg; - - let (cpo_bit, cph_bit) = mode_to_cpo_cph_bit(init_mode); - spi.ctrl0().write(|w| { - unsafe { - w.size().bits(Word::word_reg()); - w.scrdv().bits(clk.scrdv); - // Clear clock phase and polarity. Will be set to correct value for each - // transfer - w.spo().bit(cpo_bit); - w.sph().bit(cph_bit) - } - }); - - spi.ctrl1().write(|w| { - w.lbm().bit(loopback_mode); - w.sod().bit(slave_output_disable); - w.ms().bit(ms); - w.mdlycap().bit(master_delayer_capture); - w.blockmode().bit(blockmode); - w.bmstall().bit(bmstall); - unsafe { w.ss().bits(0) } - }); - spi.clkprescale() - .write(|w| unsafe { w.bits(clk.prescale_val as u32) }); - - spi.fifo_clr().write(|w| { - w.rxfifo().set_bit(); - w.txfifo().set_bit() - }); - // Enable the peripheral as the last step as recommended in the - // programmers guide - spi.ctrl1().modify(|_, w| w.enable().set_bit()); - Spi { - inner: SpiBase { - spi, - cfg: spi_cfg, - sys_clk: sys_clk.into(), - fill_word: Default::default(), - bmstall, - blockmode, - word: PhantomData, - }, - pins, - } - } - - delegate::delegate! { - to self.inner { - #[inline] - pub fn cfg_clock(&mut self, cfg: SpiClkConfig); - - #[inline] - pub fn cfg_clock_from_div(&mut self, div: u16) -> Result<(), SpiClkConfigError>; - - #[inline] - pub fn cfg_mode(&mut self, mode: Mode); - - #[inline] - pub fn perid(&self) -> u32; - - #[inline] - pub fn fill_word(&self) -> Word; - - #[inline] - pub fn spi(&self) -> &SpiI; - - /// Configure the hardware chip select given a hardware chip select ID. - #[inline] - pub fn cfg_hw_cs(&mut self, hw_cs: HwChipSelectId); - - /// Configure the hardware chip select given a physical hardware CS pin. - #[inline] - pub fn cfg_hw_cs_with_pin>(&mut self, _hwcs: &HwCs); - - /// Disables the hardware chip select functionality. This can be used when performing - /// external chip select handling, for example with GPIO pins. - #[inline] - pub fn cfg_hw_cs_disable(&mut self); - - /// Utility function to configure all relevant transfer parameters in one go. - /// This is useful if multiple devices with different clock and mode configurations - /// are connected to one bus. - pub fn cfg_transfer>( - &mut self, transfer_cfg: &TransferConfigWithHwcs - ); - - /// Low level function to write a word to the SPI FIFO but also checks whether - /// there is actually data in the FIFO. - /// - /// Uses the [nb] API to allow usage in blocking and non-blocking contexts. - #[inline(always)] - pub fn write_fifo(&self, data: u32) -> nb::Result<(), Infallible>; - - /// Low level function to write a word to the SPI FIFO. - /// - /// This does not necesarily mean there is a space in the FIFO available. - /// Use [Self::write_fifo] function to write a word into the FIFO reliably using the - /// [nb] API. - #[inline(always)] - pub fn write_fifo_unchecked(&self, data: u32); - - /// Low level function to read a word from the SPI FIFO. Must be preceeded by a - /// [Self::write_fifo] call. - /// - /// Uses the [nb] API to allow usage in blocking and non-blocking contexts. - #[inline(always)] - pub fn read_fifo(&self) -> nb::Result; - - /// Low level function to read a word from from the SPI FIFO. - /// - /// This does not necesarily mean there is a word in the FIFO available. - /// Use the [Self::read_fifo] function to read a word from the FIFO reliably using the [nb] - /// API. - /// You might also need to mask the value to ignore the BMSTART/BMSTOP bit. - #[inline(always)] - pub fn read_fifo_unchecked(&self) -> u32; - } - } - - pub fn set_fill_word(&mut self, fill_word: Word) { - self.inner.fill_word = fill_word; - } - - /// Releases the SPI peripheral and associated pins - pub fn release(self) -> (SpiI, (Sck, Miso, Mosi), SpiConfig) { - (self.inner.spi, self.pins, self.inner.cfg) - } - - pub fn downgrade(self) -> SpiBase { - self.inner - } -} - -impl< - SpiI: Instance, - Sck: PinSck, - Miso: PinMiso, - Mosi: PinMosi, - Word: WordProvider, - > SpiLowLevel for Spi -where - >::Error: core::fmt::Debug, -{ - delegate::delegate! { - to self.inner { - fn write_fifo(&self, data: u32) -> nb::Result<(), Infallible>; - fn write_fifo_unchecked(&self, data: u32); - fn read_fifo(&self) -> nb::Result; - fn read_fifo_unchecked(&self) -> u32; - } - } -} - -impl< - SpiI: Instance, - Word: WordProvider, - Sck: PinSck, - Miso: PinMiso, - Mosi: PinMosi, - > embedded_hal::spi::ErrorType for Spi -{ - type Error = Infallible; -} - -impl< - SpiI: Instance, - Word: WordProvider, - Sck: PinSck, - Miso: PinMiso, - Mosi: PinMosi, - > embedded_hal::spi::SpiBus for Spi -where - >::Error: core::fmt::Debug, -{ - delegate::delegate! { - to self.inner { - fn read(&mut self, words: &mut [Word]) -> Result<(), Self::Error>; - fn write(&mut self, words: &[Word]) -> Result<(), Self::Error>; - fn transfer(&mut self, read: &mut [Word], write: &[Word]) -> Result<(), Self::Error>; - fn transfer_in_place(&mut self, words: &mut [Word]) -> Result<(), Self::Error>; - fn flush(&mut self) -> Result<(), Self::Error>; - } - } -} - -/// Changing the word size also requires a type conversion -impl, Miso: PinMiso, Mosi: PinMosi> - From> for Spi -{ - fn from(old_spi: Spi) -> Self { - old_spi - .inner - .spi - .ctrl0() - .modify(|_, w| unsafe { w.size().bits(WordSize::SixteenBits as u8) }); - Spi { - inner: SpiBase { - spi: old_spi.inner.spi, - cfg: old_spi.inner.cfg, - blockmode: old_spi.inner.blockmode, - fill_word: Default::default(), - bmstall: old_spi.inner.bmstall, - sys_clk: old_spi.inner.sys_clk, - word: PhantomData, - }, - pins: old_spi.pins, - } - } -} - -/// Changing the word size also requires a type conversion -impl, Miso: PinMiso, Mosi: PinMosi> - From> for Spi -{ - fn from(old_spi: Spi) -> Self { - old_spi - .inner - .spi - .ctrl0() - .modify(|_, w| unsafe { w.size().bits(WordSize::EightBits as u8) }); - Spi { - inner: SpiBase { - spi: old_spi.inner.spi, - cfg: old_spi.inner.cfg, - blockmode: old_spi.inner.blockmode, - bmstall: old_spi.inner.bmstall, - sys_clk: old_spi.inner.sys_clk, - fill_word: Default::default(), - word: PhantomData, - }, - pins: old_spi.pins, - } - } -} diff --git a/va108xx-hal/src/spi/mod.rs b/va108xx-hal/src/spi/mod.rs new file mode 100644 index 0000000..0b2cb69 --- /dev/null +++ b/va108xx-hal/src/spi/mod.rs @@ -0,0 +1,14 @@ +//! 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::*; + +pub use vorago_shared_periphs::spi::pins_vor1x as pins; diff --git a/va108xx-hal/src/sysconfig.rs b/va108xx-hal/src/sysconfig.rs index 5d04355..b664761 100644 --- a/va108xx-hal/src/sysconfig.rs +++ b/va108xx-hal/src/sysconfig.rs @@ -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, +}; diff --git a/va108xx-hal/src/time.rs b/va108xx-hal/src/time.rs index 9808028..1663b95 100644 --- a/va108xx-hal/src/time.rs +++ b/va108xx-hal/src/time.rs @@ -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::*; diff --git a/va108xx-hal/src/timer.rs b/va108xx-hal/src/timer.rs index d356671..a4fc39c 100644 --- a/va108xx-hal/src/timer.rs +++ b/va108xx-hal/src/timer.rs @@ -4,786 +4,6 @@ //! //! - [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; +pub use vorago_shared_periphs::timer::*; -const IRQ_DST_NONE: u32 = 0xffffffff; -pub static MS_COUNTER: Mutex> = 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 { - 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: 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 ValidTimAndPin 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 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: Tim, - curr_freq: Hertz, - irq_cfg: Option, - 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 TimRegInterface for CountdownTimer { - fn tim_id(&self) -> u8 { - TIM::TIM_ID - } -} - -impl CountdownTimer { - /// Configures a TIM peripheral as a periodic count down timer - pub fn new(syscfg: &mut pac::Sysconfig, sys_clk: impl Into, 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) { - 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 CountdownTimer { - #[inline] - pub fn start(&mut self, timeout: T) - where - T: Into, - { - 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 embedded_hal::delay::DelayNs for CountdownTimer { - 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( - irq_cfg: InterruptConfig, - sys_cfg: &mut pac::Sysconfig, - irq_sel: Option<&mut pac::Irqsel>, - sys_clk: impl Into, - tim0: TIM, -) -> CountdownTimer { - 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( - sys_cfg: &mut pac::Sysconfig, - sys_clk: impl Into, - tim: TIM, -) -> CountdownTimer { - 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); - -impl DelayMs { - pub fn new(timer: CountdownTimer) -> Option { - 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::regs; diff --git a/va108xx-hal/src/typelevel.rs b/va108xx-hal/src/typelevel.rs deleted file mode 100644 index 7803c20..0000000 --- a/va108xx-hal/src/typelevel.rs +++ /dev/null @@ -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 -/// `::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 -/// ``` -/// -/// 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(mut any: T) -/// where -/// T: Is, -/// { -/// 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>, - Self: Into>, - Self: AsRef>, - Self: AsMut>, -{ - type Type; -} - -/// Type alias for [`Is::Type`] -pub type IsType = ::Type; - -impl Is for T -where - T: Sealed + AsRef + AsMut, -{ - type Type = T; -} - -//============================================================================== -// Counting -//============================================================================== - -/// Implement `Sealed` for [`U0`] -impl Sealed for U0 {} - -/// Implement `Sealed` for all type-level, [`Unsigned`] integers *except* [`U0`] -impl Sealed for UInt {} - -/// 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 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 Decrement for T {} - -impl PrivateIncrement for N -where - N: Unsigned + Add, - Add1: Unsigned, -{ - type Inc = Add1; - #[inline] - fn inc(self) -> Self::Inc { - Self::Inc::default() - } -} - -impl PrivateDecrement for N -where - N: Unsigned + Sub, - Sub1: Unsigned, -{ - type Dec = Sub1; - #[inline] - fn dec(self) -> Self::Dec { - Self::Dec::default() - } -} diff --git a/va108xx-hal/src/uart/mod.rs b/va108xx-hal/src/uart/mod.rs index 929b6c2..49303f3 100644 --- a/va108xx-hal/src/uart/mod.rs +++ b/va108xx-hal/src/uart/mod.rs @@ -1,6 +1,6 @@ //! # API for the UART peripheral //! -//! The core of this API are the [Uart], [UartBase], [Rx] and [Tx] structures. +//! The core of this API are the [Uart], [Rx] and [Tx] structures. //! The RX structure also has a dedicated [RxWithInterrupt] variant which allows reading the receiver //! using interrupts. //! @@ -12,1345 +12,9 @@ //! - [UART simple example](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples/simple/examples/uart.rs) //! - [UART with IRQ and RTIC](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples/rtic/src/bin/uart-echo-rtic.rs) //! - [Flashloader exposing a CCSDS interface via UART](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/flashloader) -use core::{convert::Infallible, ops::Deref}; -use fugit::RateExtU32; +//! - [Async UART RX example](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples/embassy/src/bin/async-uart-rx.rs) +//! - [Async UART TX example](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples/embassy/src/bin/async-uart-tx.rs) +pub use vorago_shared_periphs::uart::*; -pub use crate::InterruptConfig; -use crate::{ - clock::enable_peripheral_clock, - enable_nvic_interrupt, - gpio::pin::{ - AltFunc1, AltFunc2, AltFunc3, Pin, PA16, PA17, PA18, PA19, PA2, PA26, PA27, PA3, PA30, - PA31, PA8, PA9, PB18, PB19, PB20, PB21, PB22, PB23, PB6, PB7, PB8, PB9, - }, - pac::{self, uarta as uart_base}, - time::Hertz, - PeripheralSelect, -}; -use embedded_hal_nb::serial::Read; - -#[derive(Debug, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum Bank { - A = 0, - B = 1, -} - -//================================================================================================== -// Type-Level support -//================================================================================================== - -pub trait Pins {} - -impl Pins for (Pin, Pin) {} -impl Pins for (Pin, Pin) {} -impl Pins for (Pin, Pin) {} - -impl Pins for (Pin, Pin) {} -impl Pins for (Pin, Pin) {} - -impl Pins for (Pin, Pin) {} -impl Pins for (Pin, Pin) {} -impl Pins for (Pin, Pin) {} - -impl Pins for (Pin, Pin) {} -impl Pins for (Pin, Pin) {} -impl Pins for (Pin, Pin) {} - -//================================================================================================== -// Regular Definitions -//================================================================================================== - -#[derive(Debug, PartialEq, Eq, thiserror::Error)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[error("no interrupt ID was set")] -pub struct NoInterruptIdWasSet; - -#[derive(Debug, PartialEq, Eq, thiserror::Error)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[error("transer is pending")] -pub struct TransferPendingError; - -#[derive(Debug, PartialEq, Eq, Copy, Clone)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum Event { - // Receiver FIFO interrupt enable. Generates interrupt - // when FIFO is at least half full. Half full is defined as FIFO - // count >= RXFIFOIRQTRG - RxFifoHalfFull, - // Framing error, Overrun error, Parity Error and Break error - RxError, - // Event for timeout condition: Data in the FIFO and no receiver - // FIFO activity for 4 character times - RxTimeout, - - // Transmitter FIFO interrupt enable. Generates interrupt - // when FIFO is at least half full. Half full is defined as FIFO - // count >= TXFIFOIRQTRG - TxFifoHalfFull, - // FIFO overflow error - TxError, - // Generate interrupt when transmit FIFO is empty and TXBUSY is 0 - TxEmpty, - // Interrupt when CTSn changes value - TxCts, -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum Parity { - None, - Odd, - Even, -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum StopBits { - One = 0, - Two = 1, -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum WordSize { - Five = 0, - Six = 1, - Seven = 2, - Eight = 3, -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct Config { - pub baudrate: Hertz, - pub parity: Parity, - pub stopbits: StopBits, - // When false, use standard 16x baud clock, other 8x baud clock - pub baud8: bool, - pub wordsize: WordSize, - pub enable_tx: bool, - pub enable_rx: bool, -} - -impl Config { - pub fn baudrate(mut self, baudrate: Hertz) -> Self { - self.baudrate = baudrate; - self - } - - pub fn parity_none(mut self) -> Self { - self.parity = Parity::None; - self - } - - pub fn parity_even(mut self) -> Self { - self.parity = Parity::Even; - self - } - - pub fn parity_odd(mut self) -> Self { - self.parity = Parity::Odd; - self - } - - pub fn stopbits(mut self, stopbits: StopBits) -> Self { - self.stopbits = stopbits; - self - } - - pub fn wordsize(mut self, wordsize: WordSize) -> Self { - self.wordsize = wordsize; - self - } - - pub fn baud8(mut self, baud: bool) -> Self { - self.baud8 = baud; - self - } -} - -impl Default for Config { - fn default() -> Config { - let baudrate = 115_200_u32.Hz(); - Config { - baudrate, - parity: Parity::None, - stopbits: StopBits::One, - baud8: false, - wordsize: WordSize::Eight, - enable_tx: true, - enable_rx: true, - } - } -} - -impl From for Config { - fn from(baud: Hertz) -> Self { - Config::default().baudrate(baud) - } -} - -//================================================================================================== -// IRQ Definitions -//================================================================================================== - -#[derive(Debug, Copy, Clone)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct IrqContextTimeoutOrMaxSize { - rx_idx: usize, - mode: IrqReceptionMode, - pub max_len: usize, -} - -impl IrqContextTimeoutOrMaxSize { - pub fn new(max_len: usize) -> Self { - IrqContextTimeoutOrMaxSize { - rx_idx: 0, - max_len, - mode: IrqReceptionMode::Idle, - } - } -} - -impl IrqContextTimeoutOrMaxSize { - pub fn reset(&mut self) { - self.rx_idx = 0; - self.mode = IrqReceptionMode::Idle; - } -} - -/// This struct is used to return the default IRQ handler result to the user -#[derive(Debug, Default)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct IrqResult { - pub bytes_read: usize, - pub errors: Option, -} - -/// This struct is used to return the default IRQ handler result to the user -#[derive(Debug, Default)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct IrqResultMaxSizeOrTimeout { - complete: bool, - timeout: bool, - pub errors: Option, - pub bytes_read: usize, -} - -impl IrqResultMaxSizeOrTimeout { - pub fn new() -> Self { - IrqResultMaxSizeOrTimeout { - complete: false, - timeout: false, - errors: None, - bytes_read: 0, - } - } -} -impl IrqResultMaxSizeOrTimeout { - #[inline] - pub fn has_errors(&self) -> bool { - self.errors.is_some() - } - - #[inline] - pub fn overflow_error(&self) -> bool { - self.errors.is_some_and(|e| e.overflow) - } - - #[inline] - pub fn framing_error(&self) -> bool { - self.errors.is_some_and(|e| e.framing) - } - - #[inline] - pub fn parity_error(&self) -> bool { - self.errors.is_some_and(|e| e.parity) - } - - #[inline] - pub fn timeout(&self) -> bool { - self.timeout - } - - #[inline] - pub fn complete(&self) -> bool { - self.complete - } -} - -#[derive(Debug, PartialEq, Copy, Clone)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -enum IrqReceptionMode { - Idle, - Pending, -} - -#[derive(Default, Debug, Copy, Clone)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct UartErrors { - overflow: bool, - framing: bool, - parity: bool, - other: bool, -} - -impl UartErrors { - #[inline(always)] - pub fn overflow(&self) -> bool { - self.overflow - } - - #[inline(always)] - pub fn framing(&self) -> bool { - self.framing - } - - #[inline(always)] - pub fn parity(&self) -> bool { - self.parity - } - - #[inline(always)] - pub fn other(&self) -> bool { - self.other - } -} - -impl UartErrors { - #[inline(always)] - pub fn error(&self) -> bool { - self.overflow || self.framing || self.parity || self.other - } -} - -#[derive(Debug, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct BufferTooShortError { - found: usize, - expected: usize, -} - -//================================================================================================== -// UART peripheral wrapper -//================================================================================================== - -pub trait Instance: Deref { - const IDX: u8; - const PERIPH_SEL: PeripheralSelect; - const PTR: *const uart_base::RegisterBlock; - - /// Retrieve the peripheral structure. - /// - /// # Safety - /// - /// This circumvents the safety guarantees of the HAL. - unsafe fn steal() -> Self; - - #[inline(always)] - fn ptr() -> *const uart_base::RegisterBlock { - Self::PTR - } - - /// Retrieve the type erased peripheral register block. - /// - /// # Safety - /// - /// This circumvents the safety guarantees of the HAL. - #[inline(always)] - unsafe fn reg_block() -> &'static uart_base::RegisterBlock { - unsafe { &(*Self::ptr()) } - } -} - -impl Instance for pac::Uarta { - const IDX: u8 = 0; - - const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Uart0; - const PTR: *const uart_base::RegisterBlock = Self::PTR; - - #[inline(always)] - unsafe fn steal() -> Self { - Self::steal() - } -} - -impl Instance for pac::Uartb { - const IDX: u8 = 1; - - const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Uart1; - const PTR: *const uart_base::RegisterBlock = Self::PTR; - - #[inline(always)] - unsafe fn steal() -> Self { - Self::steal() - } -} - -impl Bank { - /// Retrieve the peripheral register block. - /// - /// # Safety - /// - /// Circumvents the HAL safety guarantees. - pub unsafe fn reg_block(&self) -> &'static uart_base::RegisterBlock { - match self { - Bank::A => unsafe { pac::Uarta::reg_block() }, - Bank::B => unsafe { pac::Uartb::reg_block() }, - } - } -} - -//================================================================================================== -// UART implementation -//================================================================================================== - -/// Type erased variant of a UART. Can be created with the [`Uart::downgrade`] function. -pub struct UartBase { - uart: Uart, - tx: Tx, - rx: Rx, -} - -impl UartBase { - /// This function assumes that the peripheral clock was alredy enabled - /// in the SYSCONFIG register - fn init(self, config: Config, sys_clk: Hertz) -> Self { - let baud_multiplier = match config.baud8 { - false => 16, - true => 8, - }; - - // This is the calculation: (64.0 * (x - integer_part as f32) + 0.5) as u32 without floating - // point calculations. - let frac = ((sys_clk.raw() % (config.baudrate.raw() * 16)) * 64 - + (config.baudrate.raw() * 8)) - / (config.baudrate.raw() * 16); - // Calculations here are derived from chapter 4.8.5 (p.79) of the datasheet. - let x = sys_clk.raw() as f32 / (config.baudrate.raw() * baud_multiplier) as f32; - let integer_part = x as u32; - self.uart.clkscale().write(|w| unsafe { - w.frac().bits(frac as u8); - w.int().bits(integer_part) - }); - - let (paren, pareven) = match config.parity { - Parity::None => (false, false), - Parity::Odd => (true, false), - Parity::Even => (true, true), - }; - let stopbits = match config.stopbits { - StopBits::One => false, - StopBits::Two => true, - }; - let wordsize = config.wordsize as u8; - let baud8 = config.baud8; - self.uart.ctrl().write(|w| { - w.paren().bit(paren); - w.pareven().bit(pareven); - w.stopbits().bit(stopbits); - w.baud8().bit(baud8); - unsafe { w.wordsize().bits(wordsize) } - }); - let (txenb, rxenb) = (config.enable_tx, config.enable_rx); - // Clear the FIFO - self.uart.fifo_clr().write(|w| { - w.rxfifo().set_bit(); - w.txfifo().set_bit() - }); - self.uart.enable().write(|w| { - w.rxenable().bit(rxenb); - w.txenable().bit(txenb) - }); - self - } - - #[inline] - pub fn enable_rx(&mut self) { - self.uart.enable().modify(|_, w| w.rxenable().set_bit()); - } - - #[inline] - pub fn disable_rx(&mut self) { - self.uart.enable().modify(|_, w| w.rxenable().clear_bit()); - } - - #[inline] - pub fn enable_tx(&mut self) { - self.uart.enable().modify(|_, w| w.txenable().set_bit()); - } - - #[inline] - pub fn disable_tx(&mut self) { - self.uart.enable().modify(|_, w| w.txenable().clear_bit()); - } - - #[inline] - pub fn clear_rx_fifo(&mut self) { - self.uart.fifo_clr().write(|w| w.rxfifo().set_bit()); - } - - #[inline] - pub fn clear_tx_fifo(&mut self) { - self.uart.fifo_clr().write(|w| w.txfifo().set_bit()); - } - - #[inline] - pub fn clear_rx_status(&mut self) { - self.uart.fifo_clr().write(|w| w.rxsts().set_bit()); - } - - #[inline] - pub fn clear_tx_status(&mut self) { - self.uart.fifo_clr().write(|w| w.txsts().set_bit()); - } - - pub fn listen(&self, event: Event) { - self.uart.irq_enb().modify(|_, w| match event { - Event::RxError => w.irq_rx_status().set_bit(), - Event::RxFifoHalfFull => w.irq_rx().set_bit(), - Event::RxTimeout => w.irq_rx_to().set_bit(), - Event::TxEmpty => w.irq_tx_empty().set_bit(), - Event::TxError => w.irq_tx_status().set_bit(), - Event::TxFifoHalfFull => w.irq_tx().set_bit(), - Event::TxCts => w.irq_tx_cts().set_bit(), - }); - } - - pub fn unlisten(&self, event: Event) { - self.uart.irq_enb().modify(|_, w| match event { - Event::RxError => w.irq_rx_status().clear_bit(), - Event::RxFifoHalfFull => w.irq_rx().clear_bit(), - Event::RxTimeout => w.irq_rx_to().clear_bit(), - Event::TxEmpty => w.irq_tx_empty().clear_bit(), - Event::TxError => w.irq_tx_status().clear_bit(), - Event::TxFifoHalfFull => w.irq_tx().clear_bit(), - Event::TxCts => w.irq_tx_cts().clear_bit(), - }); - } - - pub fn release(self) -> Uart { - // Clear the FIFO - self.uart.fifo_clr().write(|w| { - w.rxfifo().set_bit(); - w.txfifo().set_bit() - }); - self.uart.enable().write(|w| { - w.rxenable().clear_bit(); - w.txenable().clear_bit() - }); - self.uart - } - - /// Poll receiver errors. - pub fn poll_rx_errors(&self) -> Option { - self.rx.poll_errors() - } - - pub fn split(self) -> (Tx, Rx) { - (self.tx, self.rx) - } -} - -impl embedded_io::ErrorType for UartBase { - type Error = Infallible; -} - -impl embedded_hal_nb::serial::ErrorType for UartBase { - type Error = Infallible; -} - -impl embedded_hal_nb::serial::Read for UartBase { - fn read(&mut self) -> nb::Result { - self.rx.read() - } -} - -impl embedded_hal_nb::serial::Write for UartBase { - fn write(&mut self, word: u8) -> nb::Result<(), Self::Error> { - self.tx.write(word).map_err(|e| { - if let nb::Error::Other(_) = e { - unreachable!() - } - nb::Error::WouldBlock - }) - } - - fn flush(&mut self) -> nb::Result<(), Self::Error> { - self.tx.flush().map_err(|e| { - if let nb::Error::Other(_) = e { - unreachable!() - } - nb::Error::WouldBlock - }) - } -} - -/// Serial abstraction. Entry point to create a new UART -pub struct Uart { - inner: UartBase, - pins: Pins, -} - -impl Uart -where - UartInstance: Instance, - PinsInstance: Pins, -{ - /// Calls [Self::new] with the interrupt configuration to some valid value. - pub fn new_with_interrupt( - syscfg: &mut va108xx::Sysconfig, - sys_clk: impl Into, - uart: UartInstance, - pins: PinsInstance, - config: impl Into, - irq_cfg: InterruptConfig, - ) -> Self { - Self::new(syscfg, sys_clk, uart, pins, config, Some(irq_cfg)) - } - - /// Calls [Self::new] with the interrupt configuration to [None]. - pub fn new_without_interrupt( - syscfg: &mut va108xx::Sysconfig, - sys_clk: impl Into, - uart: UartInstance, - pins: PinsInstance, - config: impl Into, - ) -> Self { - Self::new(syscfg, sys_clk, uart, pins, config, None) - } - - /// Create a new UART peripheral with an interrupt configuration. - /// - /// # Arguments - /// - /// - `syscfg`: The system configuration register block - /// - `sys_clk`: The system clock frequency - /// - `uart`: The concrete UART peripheral instance. - /// - `pins`: UART TX and RX pin tuple. - /// - `config`: UART specific configuration parameters like baudrate. - /// - `irq_cfg`: Optional interrupt configuration. This should be a valid value if the plan - /// is to use TX or RX functionality relying on interrupts. If only the blocking API without - /// any interrupt support is used, this can be [None]. - pub fn new( - syscfg: &mut va108xx::Sysconfig, - sys_clk: impl Into, - uart: UartInstance, - pins: PinsInstance, - config: impl Into, - opt_irq_cfg: Option, - ) -> Self { - crate::clock::enable_peripheral_clock(syscfg, UartInstance::PERIPH_SEL); - let uart = Uart { - inner: UartBase { - uart, - tx: Tx::new(unsafe { UartInstance::steal() }), - rx: Rx::new(unsafe { UartInstance::steal() }), - }, - pins, - } - .init(config.into(), sys_clk.into()); - - if let Some(irq_cfg) = opt_irq_cfg { - if irq_cfg.route { - enable_peripheral_clock(syscfg, PeripheralSelect::Irqsel); - unsafe { pac::Irqsel::steal() } - .uart0(UartInstance::IDX as usize) - .write(|w| unsafe { w.bits(irq_cfg.id as u32) }); - } - if irq_cfg.enable_in_nvic { - // Safety: User has specifically configured this. - unsafe { enable_nvic_interrupt(irq_cfg.id) }; - } - } - uart - } - - /// This function assumes that the peripheral clock was alredy enabled - /// in the SYSCONFIG register - fn init(mut self, config: Config, sys_clk: Hertz) -> Self { - self.inner = self.inner.init(config, sys_clk); - self - } - - /// Poll receiver errors. - pub fn poll_rx_errors(&self) -> Option { - self.inner.poll_rx_errors() - } - - #[inline] - pub fn enable_rx(&mut self) { - self.inner.enable_rx(); - } - - #[inline] - pub fn disable_rx(&mut self) { - self.inner.enable_rx(); - } - - #[inline] - pub fn enable_tx(&mut self) { - self.inner.enable_tx(); - } - - #[inline] - pub fn disable_tx(&mut self) { - self.inner.disable_tx(); - } - - #[inline] - pub fn clear_rx_fifo(&mut self) { - self.inner.clear_rx_fifo(); - } - - #[inline] - pub fn clear_tx_fifo(&mut self) { - self.inner.clear_tx_fifo(); - } - - #[inline] - pub fn clear_rx_status(&mut self) { - self.inner.clear_rx_status(); - } - - #[inline] - pub fn clear_tx_status(&mut self) { - self.inner.clear_tx_status(); - } - - pub fn listen(&self, event: Event) { - self.inner.listen(event); - } - - pub fn unlisten(&self, event: Event) { - self.inner.unlisten(event); - } - - pub fn release(self) -> (UartInstance, PinsInstance) { - (self.inner.release(), self.pins) - } - - pub fn downgrade(self) -> UartBase { - UartBase { - uart: self.inner.uart, - tx: self.inner.tx, - rx: self.inner.rx, - } - } - - pub fn split(self) -> (Tx, Rx) { - self.inner.split() - } -} - -#[inline(always)] -pub fn enable_rx(uart: &uart_base::RegisterBlock) { - uart.enable().modify(|_, w| w.rxenable().set_bit()); -} - -#[inline(always)] -pub fn disable_rx(uart: &uart_base::RegisterBlock) { - uart.enable().modify(|_, w| w.rxenable().clear_bit()); -} - -#[inline(always)] -pub fn enable_rx_interrupts(uart: &uart_base::RegisterBlock) { - uart.irq_enb().modify(|_, w| { - w.irq_rx().set_bit(); - w.irq_rx_to().set_bit(); - w.irq_rx_status().set_bit() - }); -} - -#[inline(always)] -pub fn disable_rx_interrupts(uart: &uart_base::RegisterBlock) { - uart.irq_enb().modify(|_, w| { - w.irq_rx().clear_bit(); - w.irq_rx_to().clear_bit(); - w.irq_rx_status().clear_bit() - }); -} - -/// Serial receiver. -/// -/// Can be created by using the [Uart::split] or [UartBase::split] API. -pub struct Rx(Uart); - -impl Rx { - #[inline(always)] - const fn new(uart: Uart) -> Self { - Self(uart) - } - - /// Direct access to the peripheral structure. - /// - /// # Safety - /// - /// You must ensure that only registers related to the operation of the RX side are used. - #[inline(always)] - pub const unsafe fn uart(&self) -> &Uart { - &self.0 - } - - pub fn poll_errors(&self) -> Option { - let mut errors = UartErrors::default(); - - let uart = unsafe { &(*Uart::ptr()) }; - let status_reader = uart.rxstatus().read(); - if status_reader.rxovr().bit_is_set() { - errors.overflow = true; - } else if status_reader.rxfrm().bit_is_set() { - errors.framing = true; - } else if status_reader.rxpar().bit_is_set() { - errors.parity = true; - } else { - return None; - }; - Some(errors) - } - - #[inline] - pub fn clear_fifo(&self) { - self.0.fifo_clr().write(|w| w.rxfifo().set_bit()); - } - - #[inline] - pub fn disable_interrupts(&mut self) { - disable_rx_interrupts(unsafe { Uart::reg_block() }); - } - #[inline] - pub fn enable_interrupts(&mut self) { - enable_rx_interrupts(unsafe { Uart::reg_block() }); - } - - #[inline] - pub fn enable(&mut self) { - enable_rx(unsafe { Uart::reg_block() }); - } - - #[inline] - pub fn disable(&mut self) { - disable_rx(unsafe { Uart::reg_block() }); - } - - /// Low level function to read a word from the UART FIFO. - /// - /// Uses the [nb] API to allow usage in blocking and non-blocking contexts. - /// - /// Please note that you might have to mask the returned value with 0xff to retrieve the actual - /// value if you use the manual parity mode. See chapter 4.6.2 for more information. - #[inline(always)] - pub fn read_fifo(&self) -> nb::Result { - if self.0.rxstatus().read().rdavl().bit_is_clear() { - return Err(nb::Error::WouldBlock); - } - Ok(self.read_fifo_unchecked()) - } - - /// Low level function to read a word from from the UART FIFO. - /// - /// This does not necesarily mean there is a word in the FIFO available. - /// Use the [Self::read_fifo] function to read a word from the FIFO reliably using the [nb] - /// API. - /// - /// Please note that you might have to mask the returned value with 0xff to retrieve the actual - /// value if you use the manual parity mode. See chapter 4.6.2 for more information. - #[inline(always)] - pub fn read_fifo_unchecked(&self) -> u32 { - self.0.data().read().bits() - } - - pub fn into_rx_with_irq(self) -> RxWithInterrupt { - RxWithInterrupt::new(self) - } - - #[inline(always)] - pub fn release(self) -> Uart { - self.0 - } -} - -impl embedded_io::ErrorType for Rx { - type Error = Infallible; -} - -impl embedded_hal_nb::serial::ErrorType for Rx { - type Error = Infallible; -} - -impl embedded_hal_nb::serial::Read for Rx { - fn read(&mut self) -> nb::Result { - self.read_fifo().map(|val| (val & 0xff) as u8).map_err(|e| { - if let nb::Error::Other(_) = e { - unreachable!() - } - nb::Error::WouldBlock - }) - } -} - -impl embedded_io::Read for Rx { - fn read(&mut self, buf: &mut [u8]) -> Result { - if buf.is_empty() { - return Ok(0); - } - let mut read = 0; - loop { - if self.0.rxstatus().read().rdavl().bit_is_set() { - break; - } - } - for byte in buf.iter_mut() { - match >::read(self) { - Ok(w) => { - *byte = w; - read += 1; - } - Err(nb::Error::WouldBlock) => break, - } - } - - Ok(read) - } -} - -#[inline(always)] -pub fn enable_tx(uart: &uart_base::RegisterBlock) { - uart.enable().modify(|_, w| w.txenable().set_bit()); -} - -#[inline(always)] -pub fn disable_tx(uart: &uart_base::RegisterBlock) { - uart.enable().modify(|_, w| w.txenable().clear_bit()); -} - -#[inline(always)] -pub fn enable_tx_interrupts(uart: &uart_base::RegisterBlock) { - uart.irq_enb().modify(|_, w| { - w.irq_tx().set_bit(); - w.irq_tx_status().set_bit(); - w.irq_tx_empty().set_bit() - }); -} - -#[inline(always)] -pub fn disable_tx_interrupts(uart: &uart_base::RegisterBlock) { - uart.irq_enb().modify(|_, w| { - w.irq_tx().clear_bit(); - w.irq_tx_status().clear_bit(); - w.irq_tx_empty().clear_bit() - }); -} - -/// Serial transmitter -/// -/// Can be created by using the [Uart::split] or [UartBase::split] API. -pub struct Tx(Uart); - -impl Tx { - /// Retrieve a TX pin without expecting an explicit UART structure - /// - /// # Safety - /// - /// Circumvents the HAL safety guarantees. - #[inline(always)] - pub unsafe fn steal() -> Self { - Self(Uart::steal()) - } - - #[inline(always)] - fn new(uart: Uart) -> Self { - Self(uart) - } - - /// Direct access to the peripheral structure. - /// - /// # Safety - /// - /// You must ensure that only registers related to the operation of the TX side are used. - #[inline(always)] - pub const unsafe fn uart(&self) -> &Uart { - &self.0 - } - - #[inline] - pub fn clear_fifo(&self) { - self.0.fifo_clr().write(|w| w.txfifo().set_bit()); - } - - #[inline] - pub fn enable(&mut self) { - self.0.enable().modify(|_, w| w.txenable().set_bit()); - } - - #[inline] - pub fn disable(&mut self) { - self.0.enable().modify(|_, w| w.txenable().clear_bit()); - } - - /// Enables the IRQ_TX, IRQ_TX_STATUS and IRQ_TX_EMPTY interrupts. - /// - /// - The IRQ_TX interrupt is generated when the TX FIFO is at least half empty. - /// - The IRQ_TX_STATUS interrupt is generated when write data is lost due to a FIFO overflow - /// - The IRQ_TX_EMPTY interrupt is generated when the TX FIFO is empty and the TXBUSY signal - /// is 0 - #[inline] - pub fn enable_interrupts(&self) { - // Safety: We own the UART structure - enable_tx_interrupts(unsafe { Uart::reg_block() }); - } - - /// Disables the IRQ_TX, IRQ_TX_STATUS and IRQ_TX_EMPTY interrupts. - /// - /// [Self::enable_interrupts] documents the interrupts. - #[inline] - pub fn disable_interrupts(&self) { - // Safety: We own the UART structure - disable_tx_interrupts(unsafe { Uart::reg_block() }); - } - - /// Low level function to write a word to the UART FIFO. - /// - /// Uses the [nb] API to allow usage in blocking and non-blocking contexts. - /// - /// Please note that you might have to mask the returned value with 0xff to retrieve the actual - /// value if you use the manual parity mode. See chapter 11.4.1 for more information. - #[inline(always)] - pub fn write_fifo(&self, data: u32) -> nb::Result<(), Infallible> { - if self.0.txstatus().read().wrrdy().bit_is_clear() { - return Err(nb::Error::WouldBlock); - } - self.write_fifo_unchecked(data); - Ok(()) - } - - /// Low level function to write a word to the UART FIFO. - /// - /// This does not necesarily mean that the FIFO can process another word because it might be - /// full. - /// Use the [Self::write_fifo] function to write a word to the FIFO reliably using the [nb] - /// API. - #[inline(always)] - pub fn write_fifo_unchecked(&self, data: u32) { - self.0.data().write(|w| unsafe { w.bits(data) }); - } - - pub fn into_async(self) -> TxAsync { - TxAsync::new(self) - } -} - -impl embedded_io::ErrorType for Tx { - type Error = Infallible; -} - -impl embedded_hal_nb::serial::ErrorType for Tx { - type Error = Infallible; -} - -impl embedded_hal_nb::serial::Write for Tx { - fn write(&mut self, word: u8) -> nb::Result<(), Self::Error> { - self.write_fifo(word as u32) - } - - fn flush(&mut self) -> nb::Result<(), Self::Error> { - // SAFETY: Only TX related registers are used. - let reader = unsafe { &(*Uart::ptr()) }.txstatus().read(); - if reader.wrbusy().bit_is_set() { - return Err(nb::Error::WouldBlock); - } - Ok(()) - } -} - -impl embedded_io::Write for Tx { - fn write(&mut self, buf: &[u8]) -> Result { - if buf.is_empty() { - return Ok(0); - } - loop { - if self.0.txstatus().read().wrrdy().bit_is_set() { - break; - } - } - let mut written = 0; - for byte in buf.iter() { - match >::write(self, *byte) { - Ok(_) => written += 1, - Err(nb::Error::WouldBlock) => return Ok(written), - } - } - - Ok(written) - } - - fn flush(&mut self) -> Result<(), Self::Error> { - nb::block!(>::flush(self)) - } -} - -/// Serial receiver, using interrupts to offload reading to the hardware. -/// -/// You can use [Rx::into_rx_with_irq] to convert a normal [Rx] structure into this structure. -/// This structure provides two distinct ways to read the UART RX using interrupts. It should -/// be noted that the interrupt service routine (ISR) still has to be provided by the user. However, -/// this structure provides API calls which can be used inside the ISRs to simplify the reading -/// of the UART. -/// -/// 1. The first way simply empties the FIFO on an interrupt into a user provided buffer. You -/// can simply use [Self::start] to prepare the peripheral and then call the -/// [Self::on_interrupt] in the interrupt service routine. -/// 2. The second way reads packets bounded by a maximum size or a baudtick based timeout. You -/// can use [Self::read_fixed_len_or_timeout_based_using_irq] to prepare the peripheral and -/// then call the [Self::on_interrupt_max_size_or_timeout_based] in the interrupt service -/// routine. You have to call [Self::read_fixed_len_or_timeout_based_using_irq] in the ISR to -/// start reading the next packet. -pub struct RxWithInterrupt(Rx); - -impl RxWithInterrupt { - pub fn new(rx: Rx) -> Self { - Self(rx) - } - - /// This function should be called once at initialization time if the regular - /// [Self::on_interrupt] is used to read the UART receiver to enable and start the receiver. - pub fn start(&mut self) { - self.0.enable(); - self.enable_rx_irq_sources(true); - } - - #[inline(always)] - pub fn uart(&self) -> &Uart { - &self.0 .0 - } - - /// This function is used together with the [Self::on_interrupt_max_size_or_timeout_based] - /// function to read packets with a maximum size or variable sized packets by using the - /// receive timeout of the hardware. - /// - /// This function should be called once at initialization to initiate the context state - /// and to [Self::start] the receiver. After that, it should be called after each - /// completed [Self::on_interrupt_max_size_or_timeout_based] call to restart the reception - /// of a packet. - pub fn read_fixed_len_or_timeout_based_using_irq( - &mut self, - context: &mut IrqContextTimeoutOrMaxSize, - ) -> Result<(), TransferPendingError> { - if context.mode != IrqReceptionMode::Idle { - return Err(TransferPendingError); - } - context.mode = IrqReceptionMode::Pending; - context.rx_idx = 0; - self.start(); - Ok(()) - } - - #[inline] - fn enable_rx_irq_sources(&mut self, timeout: bool) { - self.uart().irq_enb().modify(|_, w| { - if timeout { - w.irq_rx_to().set_bit(); - } - w.irq_rx_status().set_bit(); - w.irq_rx().set_bit() - }); - } - - #[inline] - fn disable_rx_irq_sources(&mut self) { - self.uart().irq_enb().modify(|_, w| { - w.irq_rx_to().clear_bit(); - w.irq_rx_status().clear_bit(); - w.irq_rx().clear_bit() - }); - } - - pub fn cancel_transfer(&mut self) { - self.disable_rx_irq_sources(); - self.0.clear_fifo(); - } - - /// This function should be called in the user provided UART interrupt handler. - /// - /// It simply empties any bytes in the FIFO into the user provided buffer and returns the - /// result of the operation. - /// - /// This function will not disable the RX interrupts, so you don't need to call any other - /// API after calling this function to continue emptying the FIFO. RX errors are handled - /// as partial errors and are returned as part of the [IrqResult]. - pub fn on_interrupt(&mut self, buf: &mut [u8; 16]) -> IrqResult { - let mut result = IrqResult::default(); - - let irq_end = self.uart().irq_end().read(); - let enb_status = self.uart().enable().read(); - let rx_enabled = enb_status.rxenable().bit_is_set(); - - // Half-Full interrupt. We have a guaranteed amount of data we can read. - if irq_end.irq_rx().bit_is_set() { - let available_bytes = self.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 { - buf[result.bytes_read] = (self.uart().data().read().bits() & 0xff) as u8; - result.bytes_read += 1; - } - } - - // Timeout, empty the FIFO completely. - if irq_end.irq_rx_to().bit_is_set() { - // While there is data in the FIFO, write it into the reception buffer - while let Ok(byte) = self.0.read_fifo() { - buf[result.bytes_read] = byte as u8; - result.bytes_read += 1; - } - } - - // RX transfer not complete, check for RX errors - if rx_enabled { - self.check_for_errors(&mut result.errors); - } - - // Clear the interrupt status bits - self.uart() - .irq_clr() - .write(|w| unsafe { w.bits(irq_end.bits()) }); - result - } - - /// This function should be called in the user provided UART interrupt handler. - /// - /// This function is used to read packets which either have a maximum size or variable sized - /// packet which are bounded by sufficient delays between them, triggering a hardware timeout. - /// - /// If either the maximum number of packets have been read or a timeout occured, the transfer - /// will be deemed completed. The state information of the transfer is tracked in the - /// [IrqContextTimeoutOrMaxSize] structure. - /// - /// If passed buffer is equal to or larger than the specified maximum length, an - /// [BufferTooShortError] will be returned. Other RX errors are treated as partial errors - /// and returned inside the [IrqResultMaxSizeOrTimeout] structure. - pub fn on_interrupt_max_size_or_timeout_based( - &mut self, - context: &mut IrqContextTimeoutOrMaxSize, - buf: &mut [u8], - ) -> Result { - if buf.len() < context.max_len { - return Err(BufferTooShortError { - found: buf.len(), - expected: context.max_len, - }); - } - let mut result = IrqResultMaxSizeOrTimeout::default(); - - let irq_end = self.uart().irq_end().read(); - let enb_status = self.uart().enable().read(); - let rx_enabled = enb_status.rxenable().bit_is_set(); - - // Half-Full interrupt. We have a guaranteed amount of data we can read. - if irq_end.irq_rx().bit_is_set() { - // Determine the number of bytes to read, ensuring we leave 1 byte in the FIFO. - // We use this trick/hack because the timeout feature of the peripheral relies on data - // being in the RX FIFO. If data continues arriving, another half-full IRQ will fire. - // If not, the last byte(s) is/are emptied by the timeout interrupt. - let available_bytes = self.uart().rxfifoirqtrg().read().bits() as usize; - - let bytes_to_read = core::cmp::min( - available_bytes.saturating_sub(1), - context.max_len - context.rx_idx, - ); - - // If this interrupt bit is set, the trigger level is available at the very least. - // Read everything as fast as possible - for _ in 0..bytes_to_read { - buf[context.rx_idx] = (self.uart().data().read().bits() & 0xff) as u8; - context.rx_idx += 1; - } - - // On high-baudrates, data might be available immediately, and we possible have to - // read continuosly? Then again, the CPU should always be faster than that. I'd rather - // rely on the hardware firing another IRQ. I have not tried baudrates higher than - // 115200 so far. - } - // Timeout, empty the FIFO completely. - if irq_end.irq_rx_to().bit_is_set() { - // While there is data in the FIFO, write it into the reception buffer - loop { - if context.rx_idx == context.max_len { - break; - } - // While there is data in the FIFO, write it into the reception buffer - match self.0.read() { - Ok(byte) => { - buf[result.bytes_read] = byte; - result.bytes_read += 1; - } - Err(_) => break, - } - } - self.irq_completion_handler_max_size_timeout(&mut result, context); - return Ok(result); - } - - // RX transfer not complete, check for RX errors - if (context.rx_idx < context.max_len) && rx_enabled { - self.check_for_errors(&mut result.errors); - } - - // Clear the interrupt status bits - self.uart() - .irq_clr() - .write(|w| unsafe { w.bits(irq_end.bits()) }); - Ok(result) - } - - fn check_for_errors(&self, errors: &mut Option) { - let rx_status = self.uart().rxstatus().read(); - - if rx_status.rxovr().bit_is_set() - || rx_status.rxfrm().bit_is_set() - || rx_status.rxpar().bit_is_set() - { - let err = errors.get_or_insert(UartErrors::default()); - - if rx_status.rxovr().bit_is_set() { - err.overflow = true; - } - if rx_status.rxfrm().bit_is_set() { - err.framing = true; - } - if rx_status.rxpar().bit_is_set() { - err.parity = true; - } - } - } - - fn irq_completion_handler_max_size_timeout( - &mut self, - res: &mut IrqResultMaxSizeOrTimeout, - context: &mut IrqContextTimeoutOrMaxSize, - ) { - self.disable_rx_irq_sources(); - self.0.disable(); - res.bytes_read = context.rx_idx; - res.complete = true; - context.mode = IrqReceptionMode::Idle; - context.rx_idx = 0; - } - - /// # Safety - /// - /// This API allows creating multiple UART instances when releasing the TX structure as well. - /// The user must ensure that these instances are not used to create multiple overlapping - /// UART drivers. - pub unsafe fn release(self) -> Uart { - self.0.release() - } -} - -pub mod tx_asynch; -pub use tx_asynch::*; - -pub mod rx_asynch; -pub use rx_asynch::*; +pub use vorago_shared_periphs::uart::rx_asynch; +pub use vorago_shared_periphs::uart::tx_asynch; diff --git a/vorago-reb1/Cargo.toml b/vorago-reb1/Cargo.toml index ff242e0..c32c151 100644 --- a/vorago-reb1/Cargo.toml +++ b/vorago-reb1/Cargo.toml @@ -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"] diff --git a/vorago-reb1/examples/adt75-temp-sensor.rs b/vorago-reb1/examples/adt75-temp-sensor.rs index 40187bd..bf02c53 100644 --- a/vorago-reb1/examples/adt75-temp-sensor.rs +++ b/vorago-reb1/examples/adt75-temp-sensor.rs @@ -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(50.MHz(), dp.tim0); + let mut temp_sensor = + Adt75TempSensor::new(50.MHz(), dp.i2ca).expect("Creating temperature sensor struct failed"); loop { let temp = temp_sensor .read_temperature() diff --git a/vorago-reb1/examples/adxl343-accelerometer.rs b/vorago-reb1/examples/adxl343-accelerometer.rs index ce7be6c..f23355e 100644 --- a/vorago-reb1/examples/adxl343-accelerometer.rs +++ b/vorago-reb1/examples/adxl343-accelerometer.rs @@ -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(50.MHz(), dp.tim0); + 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(50.MHz(), 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; diff --git a/vorago-reb1/examples/blinky-button-irq.rs b/vorago-reb1/examples/blinky-button-irq.rs index bc113fc..36fac07 100644 --- a/vorago-reb1/examples/blinky-button-irq.rs +++ b/vorago-reb1/examples/blinky-button-irq.rs @@ -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| { diff --git a/vorago-reb1/examples/blinky-leds.rs b/vorago-reb1/examples/blinky-leds.rs index de9fbd5..b5aea2c 100644 --- a/vorago-reb1/examples/blinky-leds.rs +++ b/vorago-reb1/examples/blinky-leds.rs @@ -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(50.MHz(), dp.tim0); 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(50.MHz(), dp.tim0); for _ in 0..10 { // Blink all LEDs quickly for led in leds.iter_mut() { diff --git a/vorago-reb1/examples/max11619-adc.rs b/vorago-reb1/examples/max11619-adc.rs index 14c03c5..636479d 100644 --- a/vorago-reb1/examples/max11619-adc.rs +++ b/vorago-reb1/examples/max11619-adc.rs @@ -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 { - inner: SpiBase, +pub struct SpiWithHwCs { + inner: Spi, delay_provider: Delay, - hw_cs: HwCs, + hw_cs_id: HwChipSelectId, } -impl> SpiWithHwCs { - pub fn new(spi: SpiBase, hw_cs: HwCs, delay_provider: Delay) -> Self { +impl SpiWithHwCs { + pub fn new(spi: Spi, hw_cs_id: HwChipSelectId, delay_provider: Delay) -> Self { Self { inner: spi, - hw_cs, + hw_cs_id, delay_provider, } } } -impl embedded_hal::spi::ErrorType for SpiWithHwCs { +impl embedded_hal::spi::ErrorType for SpiWithHwCs { type Error = Infallible; } -impl> SpiDevice for SpiWithHwCs { +impl SpiDevice for SpiWithHwCs { 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(SYS_CLK, dp.tim0); 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(50.MHz(), dp.spib, (sck, miso, mosi), spi_cfg).unwrap(); + + let delay_spi = CountdownTimer::new(SYS_CLK, dp.tim1); + 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, diff --git a/vorago-reb1/examples/nvm.rs b/vorago-reb1/examples/nvm.rs index ee7c3d7..38434b7 100644 --- a/vorago-reb1/examples/nvm.rs +++ b/vorago-reb1/examples/nvm.rs @@ -15,12 +15,12 @@ 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 timer = CountdownTimer::new(CLOCK_FREQ, dp.tim0); + let mut nvm = M95M01::new(CLOCK_FREQ, dp.spic); 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"); } diff --git a/vorago-reb1/src/button.rs b/vorago-reb1/src/button.rs index a16ff7f..1d261f7 100644 --- a/vorago-reb1/src/button.rs +++ b/vorago-reb1/src/button.rs @@ -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); +pub struct Button(pub Input); impl Button { - pub fn new(pin: Pin) -> Button { - Button(pin) + pub fn new(pin: Pin) -> Button { + Button(Input::new_floating(pin)) } #[inline] diff --git a/vorago-reb1/src/leds.rs b/vorago-reb1/src/leds.rs index dfa9ec1..560e5a2 100644 --- a/vorago-reb1/src/leds.rs +++ b/vorago-reb1/src/leds.rs @@ -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; -pub type LD3 = Pin; -pub type LD4 = Pin; - #[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, led_pin2: Pin, led_pin3: Pin) -> 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 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(); } } diff --git a/vorago-reb1/src/m95m01.rs b/vorago-reb1/src/m95m01.rs index 528b9b3..c83a2ef 100644 --- a/vorago-reb1/src/m95m01.rs +++ b/vorago-reb1/src/m95m01.rs @@ -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. @@ -43,10 +48,10 @@ 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; +pub type RomSpi = Spi; /// Driver for the ST device M95M01 EEPROM memory. /// @@ -59,14 +64,13 @@ pub struct M95M01 { pub struct PageBoundaryExceededError; impl M95M01 { - pub fn new(syscfg: &mut pac::Sysconfig, sys_clk: impl Into, spi: pac::Spic) -> Self { - let spi = RomSpi::new( - syscfg, + pub fn new(sys_clk: Hertz, spi: pac::Spic) -> Self { + let spi = RomSpi::new_for_rom( sys_clk, spi, - (RomSck, RomMiso, RomMosi), SpiConfig::default().clk_cfg(SpiClkConfig::new(2, 4)), - ); + ) + .unwrap(); let mut spi_dev = Self { spi }; spi_dev.clear_block_protection().unwrap(); spi_dev @@ -74,7 +78,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 +94,7 @@ impl M95M01 { pub fn read_status_reg(&mut self) -> Result { 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 +108,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> { diff --git a/vorago-reb1/src/max11619.rs b/vorago-reb1/src/max11619.rs index c4805ed..eb15b82 100644 --- a/vorago-reb1/src/max11619.rs +++ b/vorago-reb1/src/max11619.rs @@ -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 = Max116xx10Bit; @@ -17,7 +17,6 @@ pub type Max11619ExternallyClockedWithWakeup = Max116xx10Bit; pub type Max11619InternallyClocked = Max116xx10BitEocExt; -pub type EocPin = Pin>; pub const AN0_CHANNEL: u8 = 0; pub const AN1_CHANNEL: u8 = 1; @@ -44,9 +43,9 @@ pub fn max11619_externally_clocked_with_wakeup( pub fn max11619_internally_clocked( spi: Spi, - eoc: EocPin, + eoc: Input, v_ref: VoltageRefMode, -) -> Result, Error> { +) -> Result, Error> { let mut adc = Max116xx10Bit::max11619(spi)? .into_int_clkd_int_timed_through_ser_if_without_wakeup(v_ref, eoc)?; adc.reset(false)?; diff --git a/vorago-reb1/src/temp_sensor.rs b/vorago-reb1/src/temp_sensor.rs index 25b20df..3bc2a4a 100644 --- a/vorago-reb1/src/temp_sensor.rs +++ b/vorago-reb1/src/temp_sensor.rs @@ -15,7 +15,7 @@ use va108xx_hal::{ const ADT75_I2C_ADDR: u8 = 0b1001000; pub struct Adt75TempSensor { - sensor_if: I2cMaster, + sensor_if: I2cMaster, cmd_buf: [u8; 1], current_reg: RegAddresses, } @@ -48,15 +48,10 @@ impl From for AdtInitError { } impl Adt75TempSensor { - pub fn new( - sys_cfg: &mut pac::Sysconfig, - sys_clk: impl Into + Copy, - i2ca: pac::I2ca, - ) -> Result { + pub fn new(sys_clk: Hertz, i2ca: pac::I2ca) -> Result { let mut sensor = Adt75TempSensor { // The master construction can not fail for regular I2C speed. sensor_if: I2cMaster::new( - sys_cfg, sys_clk, i2ca, MasterConfig::default(), diff --git a/vorago-shared-periphs/Cargo.toml b/vorago-shared-periphs/Cargo.toml new file mode 100644 index 0000000..5d35d5a --- /dev/null +++ b/vorago-shared-periphs/Cargo.toml @@ -0,0 +1,40 @@ +[package] +name = "vorago-shared-periphs" +version = "0.1.0" +description = "Peripheral drivers shared between Vorago families" +edition = "2024" + +[dependencies] +cortex-m = { version = "0.7" } +cfg-if = "1" +derive-mmio = { path = "../../../ROMEO/derive-mmio" } +bitbybit = "1.3" +arbitrary-int = "1.3" +static_assertions = "1.1" +nb = "1" +heapless = "0.8" +critical-section = "1" +embedded-hal = { version = "1.0" } +embedded-hal-async = "1" +embedded-hal-nb = "1" +embedded-io = "0.6" +embedded-io-async = "0.6" +raw-slicee = "0.1" +thiserror = { version = "2", default-features = false } +paste = "1" +fugit = "0.3" +embassy-sync = "0.6" +defmt = { version = "1", optional = true } +va108xx = { version = "0.5", default-features = false, optional = true } + +[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" + +[features] +vor1x = ["_family-selected", "dep:va108xx"] +vor4x = ["_family-selected"] +defmt = ["dep:defmt", "arbitrary-int/defmt"] + +_family-selected = [] diff --git a/vorago-shared-periphs/src/gpio/asynch.rs b/vorago-shared-periphs/src/gpio/asynch.rs new file mode 100644 index 0000000..ea32286 --- /dev/null +++ b/vorago-shared-periphs/src/gpio/asynch.rs @@ -0,0 +1,231 @@ +//! # Async GPIO functionality for the Vorago GPIO peripherals. +//! +//! This module provides the [InputPinAsync] which implements +//! 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. +use core::future::Future; + +use embassy_sync::waitqueue::AtomicWaker; +use embedded_hal_async::digital::Wait; +use portable_atomic::AtomicBool; + +use crate::{InterruptConfig, NUM_PORT_A, NUM_PORT_B}; + +pub use super::ll::InterruptEdge; +use super::{ + Input, Port, + ll::{LowLevelGpio, PinId}, +}; + +static WAKERS_FOR_PORT_A: [AtomicWaker; NUM_PORT_A] = [const { AtomicWaker::new() }; NUM_PORT_A]; +static WAKERS_FOR_PORT_B: [AtomicWaker; NUM_PORT_B] = [const { AtomicWaker::new() }; NUM_PORT_B]; +static EDGE_DETECTION_PORT_A: [AtomicBool; NUM_PORT_A] = + [const { AtomicBool::new(false) }; NUM_PORT_A]; +static EDGE_DETECTION_PORT_B: [AtomicBool; NUM_PORT_B] = + [const { AtomicBool::new(false) }; NUM_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 gpio = unsafe { port.steal_gpio() }; + + let irq_enb = gpio.read_irq_enable(); + let edge_status = gpio.read_edge_status(); + let (wakers, edge_detection) = match port { + 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()), + }; + + 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] 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 { + id: PinId, + 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_input_pin( + pin: &mut Input, + irq: va108xx::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().offset()].store(false, core::sync::atomic::Ordering::Relaxed); + pin.configure_edge_interrupt(edge); + #[cfg(feature = "vor1x")] + pin.enable_interrupt(InterruptConfig::new(irq, true, true)); + Self { + id: pin.id(), + waker_group, + edge_detection_group, + } + } +} + +impl Drop for InputPinFuture { + fn drop(&mut self) { + let mut ll = LowLevelGpio::new(self.id); + ll.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 { + let idx = self.id.offset(); + 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 InputPinAsync { + pin: Input, + #[cfg(feature = "vor1x")] + irq: va108xx::Interrupt, +} + +impl InputPinAsync { + /// Create a new asynchronous input pin from an [Input] 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. + #[cfg(feature = "vor1x")] + pub fn new(pin: Input, irq: va108xx::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) { + // Unwrap okay, checked pin in constructor. + let fut = + InputPinFuture::new_with_input_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) { + // Unwrap okay, checked pin in constructor. + let fut = + InputPinFuture::new_with_input_pin(&mut self.pin, self.irq, InterruptEdge::HighToLow); + if self.pin.is_low() { + 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_input_pin(&mut self.pin, self.irq, InterruptEdge::HighToLow).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_input_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) { + // Unwrap okay, checked pin in constructor. + InputPinFuture::new_with_input_pin(&mut self.pin, self.irq, InterruptEdge::BothEdges).await; + } + + pub fn release(self) -> Input { + self.pin + } +} + +impl embedded_hal::digital::ErrorType for InputPinAsync { + type Error = core::convert::Infallible; +} + +impl Wait for InputPinAsync { + 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(()) + } +} diff --git a/vorago-shared-periphs/src/gpio/ll.rs b/vorago-shared-periphs/src/gpio/ll.rs new file mode 100644 index 0000000..e1c2b96 --- /dev/null +++ b/vorago-shared-periphs/src/gpio/ll.rs @@ -0,0 +1,424 @@ +pub use embedded_hal::digital::PinState; + +use crate::ioconfig::FilterClkSel; +use crate::ioconfig::FilterType; +#[cfg(feature = "vor1x")] +use crate::{PeripheralSelect, sysconfig::enable_peripheral_clock}; + +pub use crate::InvalidOffsetError; +pub use crate::Port; +pub use crate::ioconfig::regs::Pull; +use crate::ioconfig::regs::{FunSel, IoConfig, MmioIoConfig}; + +use super::Pin; +use super::PinIdProvider; + +#[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, +} + +/// Pin identifier for all physical pins exposed by Vorago MCUs. +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct PinId { + port: Port, + /// Offset within the port. + offset: u8, +} + +impl PinId { + /// Unchecked constructor which panics on invalid offsets. + pub const fn new_unchecked(port: Port, offset: usize) -> Self { + if offset >= port.max_offset() { + panic!("Pin ID construction: offset is out of range"); + } + PinId { + port, + offset: offset as u8, + } + } + + pub const fn new(port: Port, offset: usize) -> Result { + if offset >= port.max_offset() { + return Err(InvalidOffsetError { offset, port }); + } + Ok(PinId { + port, + offset: offset as u8, + }) + } + + pub const fn port(&self) -> Port { + self.port + } + + pub const fn offset(&self) -> usize { + self.offset as usize + } +} + +/// Low-level driver structure for GPIO pins. +pub struct LowLevelGpio { + gpio: super::regs::MmioGpio<'static>, + ioconfig: MmioIoConfig<'static>, + id: PinId, +} + +impl core::fmt::Debug for LowLevelGpio { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("LowLevelGpio") + .field("gpio", &self.gpio.port()) + .field("id", &self.id) + .finish() + } +} + +impl LowLevelGpio { + /// Create a new low-level GPIO pin instance from a given [Pin]. + /// + /// Can be used for performing resource management of the [Pin]s. + pub fn new_with_pin(_pin: Pin) -> Self { + Self::new(I::ID) + } + + /// Create a new low-level GPIO pin instance using only the [PinId]. + pub fn new(id: PinId) -> Self { + LowLevelGpio { + gpio: super::regs::Gpio::new_mmio(id.port), + ioconfig: IoConfig::new_mmio(), + id, + } + } + + #[inline] + pub fn id(&self) -> PinId { + self.id + } + + #[inline] + pub fn port(&self) -> Port { + self.id.port() + } + + #[inline] + pub fn offset(&self) -> usize { + self.id.offset() + } + + pub fn configure_as_input_floating(&mut self) { + self.ioconfig.modify_pin_config(self.id, |mut config| { + config.set_funsel(FunSel::Sel0); + config.set_io_disable(false); + config.set_invert_input(false); + config.set_open_drain(false); + config.set_pull_enable(false); + config.set_pull_when_output_active(false); + config.set_invert_output(false); + config.set_input_enable_when_output(false); + config + }); + self.gpio.modify_dir(|mut dir| { + dir &= !(1 << self.id.offset()); + dir + }); + } + + pub fn configure_as_input_with_pull(&mut self, pull: Pull) { + self.ioconfig.modify_pin_config(self.id, |mut config| { + config.set_funsel(FunSel::Sel0); + config.set_io_disable(false); + config.set_invert_input(false); + config.set_open_drain(false); + config.set_pull_enable(true); + config.set_pull_dir(pull); + config.set_pull_when_output_active(false); + config.set_invert_output(false); + config.set_input_enable_when_output(false); + config + }); + self.gpio.modify_dir(|mut dir| { + dir &= !(1 << self.id.offset()); + dir + }); + } + + pub fn configure_as_output_push_pull(&mut self, init_level: PinState) { + self.ioconfig.modify_pin_config(self.id, |mut config| { + config.set_funsel(FunSel::Sel0); + config.set_io_disable(false); + config.set_invert_input(false); + config.set_open_drain(false); + config.set_pull_enable(false); + config.set_pull_when_output_active(false); + config.set_invert_output(false); + config.set_input_enable_when_output(true); + config + }); + match init_level { + PinState::Low => self.gpio.write_clr_out(self.mask_32()), + PinState::High => self.gpio.write_set_out(self.mask_32()), + } + self.gpio.modify_dir(|mut dir| { + dir |= 1 << self.id.offset(); + dir + }); + } + + pub fn configure_as_output_open_drain(&mut self, init_level: PinState) { + self.ioconfig.modify_pin_config(self.id, |mut config| { + config.set_funsel(FunSel::Sel0); + config.set_io_disable(false); + config.set_invert_input(false); + config.set_open_drain(true); + config.set_pull_enable(true); + config.set_pull_dir(Pull::Up); + config.set_pull_when_output_active(false); + config.set_invert_output(false); + config.set_input_enable_when_output(true); + config + }); + let mask32 = self.mask_32(); + match init_level { + PinState::Low => self.gpio.write_clr_out(mask32), + PinState::High => self.gpio.write_set_out(mask32), + } + self.gpio.modify_dir(|mut dir| { + dir |= mask32; + dir + }); + } + + pub fn configure_as_peripheral_pin(&mut self, fun_sel: FunSel, pull: Option) { + self.ioconfig.modify_pin_config(self.id, |mut config| { + config.set_funsel(fun_sel); + config.set_io_disable(false); + config.set_invert_input(false); + config.set_open_drain(false); + config.set_pull_enable(pull.is_some()); + config.set_pull_dir(pull.unwrap_or(Pull::Up)); + config.set_invert_output(false); + config + }); + } + + #[inline] + pub fn is_high(&self) -> bool { + (self.gpio.read_data_in() >> self.offset()) & 1 == 1 + } + + #[inline] + pub fn is_low(&self) -> bool { + !self.is_high() + } + + #[inline] + pub fn set_high(&mut self) { + self.gpio.write_set_out(self.mask_32()); + } + + #[inline] + pub fn set_low(&mut self) { + self.gpio.write_clr_out(self.mask_32()); + } + + #[inline] + pub fn is_set_high(&self) -> bool { + (self.gpio.read_data_out() >> self.offset()) & 1 == 1 + } + + #[inline] + pub fn is_set_low(&self) -> bool { + !self.is_set_high() + } + + #[inline] + pub fn toggle(&mut self) { + self.gpio.write_tog_out(self.mask_32()); + } + + #[cfg(feature = "vor1x")] + 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 { crate::enable_nvic_interrupt(irq_cfg.id) }; + } + self.gpio.modify_irq_enable(|mut value| { + value |= 1 << self.id.offset; + value + }); + } + + #[cfg(feature = "vor1x")] + pub fn disable_interrupt(&mut self, reset_irqsel: bool) { + if reset_irqsel { + self.reset_irqsel(); + } + // We only manipulate our own bit. + self.gpio.modify_irq_enable(|mut value| { + value &= !(1 << self.id.offset); + value + }); + } + + /// 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] + pub fn configure_edge_interrupt(&mut self, edge_type: InterruptEdge) { + let mask32 = self.mask_32(); + self.gpio.modify_irq_sen(|mut value| { + value &= !mask32; + value + }); + match edge_type { + InterruptEdge::HighToLow => { + self.gpio.modify_irq_evt(|mut value| { + value &= !mask32; + value + }); + } + InterruptEdge::LowToHigh => { + self.gpio.modify_irq_evt(|mut value| { + value |= mask32; + value + }); + } + InterruptEdge::BothEdges => { + self.gpio.modify_irq_edge(|mut value| { + value |= mask32; + value + }); + } + } + } + + /// Configure which edge or level type triggers an interrupt + #[inline] + pub fn configure_level_interrupt(&mut self, level: InterruptLevel) { + let mask32 = self.mask_32(); + self.gpio.modify_irq_sen(|mut value| { + value |= mask32; + value + }); + if level == InterruptLevel::Low { + self.gpio.modify_irq_evt(|mut value| { + value &= !mask32; + value + }); + } else { + self.gpio.modify_irq_evt(|mut value| { + value |= mask32; + value + }); + } + } + + /// Only useful for input pins + #[inline] + pub fn configure_filter_type(&mut self, filter: FilterType, clksel: FilterClkSel) { + self.ioconfig.modify_pin_config(self.id, |mut config| { + config.set_filter_type(filter); + config.set_filter_clk_sel(clksel); + config + }); + } + + /// Only useful for output pins. + #[inline] + pub fn configure_pulse_mode(&mut self, enable: bool, default_state: PinState) { + self.gpio.modify_pulse(|mut value| { + if enable { + value |= 1 << self.id.offset; + } else { + value &= !(1 << self.id.offset); + } + value + }); + self.gpio.modify_pulsebase(|mut value| { + if default_state == PinState::High { + value |= 1 << self.id.offset; + } else { + value &= !(1 << self.id.offset); + } + value + }); + } + + /// Only useful for output pins + #[inline] + pub fn configure_delay(&mut self, delay_1: bool, delay_2: bool) { + self.gpio.modify_delay1(|mut value| { + if delay_1 { + value |= 1 << self.id.offset; + } else { + value &= !(1 << self.id.offset); + } + value + }); + self.gpio.modify_delay2(|mut value| { + if delay_2 { + value |= 1 << self.id.offset; + } else { + value &= !(1 << self.id.offset); + } + value + }); + } + + #[cfg(feature = "vor1x")] + /// Configure the IRQSEL peripheral for this particular pin with the given interrupt ID. + pub fn configure_irqsel(&mut self, id: va108xx::Interrupt) { + let irqsel = unsafe { va108xx::Irqsel::steal() }; + enable_peripheral_clock(PeripheralSelect::Irqsel); + match self.id().port() { + // Set the correct interrupt number in the IRQSEL register + super::Port::A => { + irqsel + .porta0(self.id().offset()) + .write(|w| unsafe { w.bits(id as u32) }); + } + super::Port::B => { + irqsel + .portb0(self.id().offset()) + .write(|w| unsafe { w.bits(id as u32) }); + } + } + } + + #[cfg(feature = "vor1x")] + /// Reset the IRQSEL peripheral value for this particular pin. + pub fn reset_irqsel(&mut self) { + let irqsel = unsafe { va108xx::Irqsel::steal() }; + enable_peripheral_clock(PeripheralSelect::Irqsel); + match self.id().port() { + // Set the correct interrupt number in the IRQSEL register + super::Port::A => { + irqsel + .porta0(self.id().offset()) + .write(|w| unsafe { w.bits(u32::MAX) }); + } + super::Port::B => { + irqsel + .portb0(self.id().offset()) + .write(|w| unsafe { w.bits(u32::MAX) }); + } + } + } + + #[inline(always)] + pub const fn mask_32(&self) -> u32 { + 1 << self.id.offset() + } +} diff --git a/vorago-shared-periphs/src/gpio/mod.rs b/vorago-shared-periphs/src/gpio/mod.rs new file mode 100644 index 0000000..4d15e72 --- /dev/null +++ b/vorago-shared-periphs/src/gpio/mod.rs @@ -0,0 +1,374 @@ +//! GPIO support module. +use core::convert::Infallible; + +pub use crate::ioconfig::{FilterClkSel, FilterType, regs::FunSel}; +pub use embedded_hal::digital::PinState; +pub use ll::{InterruptEdge, InterruptLevel, PinId, Port, Pull}; + +pub mod asynch; +pub mod ll; +pub mod regs; + +/// Trait implemented by data structures assocaited with pin identifiacation. +pub trait PinIdProvider { + const ID: ll::PinId; +} + +/// Primary Pin structure for the physical pins exposed by Vorago MCUs. +/// +/// This pin structure is only used for resource management and does not do anything on its +/// own. +pub struct Pin { + phantom: core::marker::PhantomData, +} + +impl Pin { + #[allow(clippy::new_without_default)] + #[doc(hidden)] + pub const fn __new() -> Self { + Self { + phantom: core::marker::PhantomData, + } + } + + /// Create a new pin instance. + /// + /// # Safety + /// + /// This circumvents ownership rules of the HAL and allows creating multiple instances + /// of the same pin. + pub const unsafe fn steal() -> Self { + Self::__new() + } +} + +/// Push-Pull output pin. +#[derive(Debug)] +pub struct Output(ll::LowLevelGpio); + +impl Output { + pub fn new(_pin: Pin, init_level: PinState) -> Self { + let mut ll = ll::LowLevelGpio::new(I::ID); + ll.configure_as_output_push_pull(init_level); + Output(ll) + } + + #[inline] + pub fn port(&self) -> Port { + self.0.port() + } + + #[inline] + pub fn offset(&self) -> usize { + self.0.offset() + } + + #[inline] + pub fn set_high(&mut self) { + self.0.set_high(); + } + + #[inline] + pub fn set_low(&mut self) { + self.0.set_low(); + } + + #[inline] + pub fn is_set_high(&self) -> bool { + self.0.is_set_high() + } + + #[inline] + pub fn is_set_low(&self) -> bool { + self.0.is_set_low() + } + + /// Toggle pin output with dedicated HW feature. + #[inline] + pub fn toggle(&mut self) { + self.0.toggle(); + } +} + +impl embedded_hal::digital::ErrorType for Output { + type Error = Infallible; +} + +impl embedded_hal::digital::OutputPin for Output { + fn set_low(&mut self) -> Result<(), Self::Error> { + self.0.set_low(); + Ok(()) + } + + fn set_high(&mut self) -> Result<(), Self::Error> { + self.0.set_high(); + Ok(()) + } +} + +impl embedded_hal::digital::StatefulOutputPin for Output { + fn is_set_high(&mut self) -> Result { + Ok(self.0.is_set_high()) + } + + fn is_set_low(&mut self) -> Result { + Ok(self.0.is_set_low()) + } + + /// Toggle pin output with dedicated HW feature. + fn toggle(&mut self) -> Result<(), Self::Error> { + self.0.toggle(); + Ok(()) + } +} + +/// Input pin. +/// +/// Can be created as a floating input pin or as an input pin with pull-up or pull-down. +#[derive(Debug)] +pub struct Input(ll::LowLevelGpio); + +impl Input { + pub fn new_floating(_pin: Pin) -> Self { + let mut ll = ll::LowLevelGpio::new(I::ID); + ll.configure_as_input_floating(); + Input(ll) + } + + pub fn new_with_pull(_pin: Pin, pull: Pull) -> Self { + let mut ll = ll::LowLevelGpio::new(I::ID); + ll.configure_as_input_with_pull(pull); + Input(ll) + } + + #[inline] + pub fn id(&self) -> PinId { + self.0.id() + } + + #[cfg(feature = "vor1x")] + #[inline] + pub fn enable_interrupt(&mut self, irq_cfg: crate::InterruptConfig) { + self.0.enable_interrupt(irq_cfg); + } + + #[inline] + pub fn configure_edge_interrupt(&mut self, edge: InterruptEdge) { + self.0.configure_edge_interrupt(edge); + } + + #[inline] + pub fn configure_level_interrupt(&mut self, edge: InterruptLevel) { + self.0.configure_level_interrupt(edge); + } + + #[inline] + pub fn configure_delay(&mut self, delay_1: bool, delay_2: bool) { + self.0.configure_delay(delay_1, delay_2); + } + + #[inline] + pub fn configure_filter_type(&mut self, filter: FilterType, clksel: FilterClkSel) { + self.0.configure_filter_type(filter, clksel); + } + + #[inline] + pub fn is_low(&self) -> bool { + self.0.is_low() + } + + #[inline] + pub fn is_high(&self) -> bool { + self.0.is_high() + } +} + +impl embedded_hal::digital::ErrorType for Input { + type Error = Infallible; +} + +impl embedded_hal::digital::InputPin for Input { + fn is_low(&mut self) -> Result { + Ok(self.0.is_low()) + } + + fn is_high(&mut self) -> Result { + Ok(self.0.is_high()) + } +} + +#[derive(Debug)] +pub enum PinMode { + InputFloating, + InputWithPull(Pull), + OutputPushPull, + OutputOpenDrain, +} + +impl PinMode { + pub fn is_input(&self) -> bool { + matches!(self, PinMode::InputFloating | PinMode::InputWithPull(_)) + } + + pub fn is_output(&self) -> bool { + !self.is_input() + } +} + +/// Flex pin abstraction which can be dynamically re-configured. +/// +/// The following functions can be configured at run-time: +/// +/// - Input Floating +/// - Input with Pull-Up +/// - Output Push-Pull +/// - Output Open-Drain. +/// +/// Flex pins are always floating input pins after construction. +#[derive(Debug)] +pub struct Flex { + ll: ll::LowLevelGpio, + mode: PinMode, +} + +impl Flex { + pub fn new(_pin: Pin) -> Self { + let mut ll = ll::LowLevelGpio::new(I::ID); + ll.configure_as_input_floating(); + Flex { + ll, + mode: PinMode::InputFloating, + } + } + + #[inline] + pub fn port(&self) -> Port { + self.ll.port() + } + + #[inline] + pub fn offset(&self) -> usize { + self.ll.offset() + } + + /// Reads the input state of the pin, regardless of configured mode. + #[inline] + pub fn is_low(&self) -> bool { + self.ll.is_low() + } + + /// Reads the input state of the pin, regardless of configured mode. + #[inline] + pub fn is_high(&self) -> bool { + self.ll.is_high() + } + + /// If the pin is configured as an input pin, this function does nothing. + #[inline] + pub fn set_low(&mut self) { + if !self.mode.is_input() { + return; + } + self.ll.set_low(); + } + + /// If the pin is configured as an input pin, this function does nothing. + #[inline] + pub fn set_high(&mut self) { + if !self.mode.is_input() { + return; + } + self.ll.set_high(); + } +} + +impl embedded_hal::digital::ErrorType for Flex { + type Error = Infallible; +} + +impl embedded_hal::digital::InputPin for Flex { + /// Reads the input state of the pin, regardless of configured mode. + fn is_low(&mut self) -> Result { + Ok(self.ll.is_low()) + } + + /// Reads the input state of the pin, regardless of configured mode. + fn is_high(&mut self) -> Result { + Ok(self.ll.is_high()) + } +} + +impl embedded_hal::digital::OutputPin for Flex { + /// If the pin is configured as an input pin, this function does nothing. + fn set_low(&mut self) -> Result<(), Self::Error> { + self.set_low(); + Ok(()) + } + + /// If the pin is configured as an input pin, this function does nothing. + fn set_high(&mut self) -> Result<(), Self::Error> { + self.set_high(); + Ok(()) + } +} + +impl embedded_hal::digital::StatefulOutputPin for Flex { + /// If the pin is not configured as a stateful output pin like Output Push-Pull, the result + /// of this function is undefined. + fn is_set_high(&mut self) -> Result { + Ok(self.ll.is_set_high()) + } + + /// If the pin is not configured as a stateful output pin like Output Push-Pull, the result + /// of this function is undefined. + fn is_set_low(&mut self) -> Result { + Ok(self.ll.is_set_low()) + } + + /// Toggle pin output. + /// + /// If the pin is not configured as a stateful output pin like Output Push-Pull, the result + /// of this function is undefined. + fn toggle(&mut self) -> Result<(), Self::Error> { + self.ll.toggle(); + Ok(()) + } +} + +/// IO peripheral pin structure. +/// +/// Can be used to configure pins as IO peripheral pins. +pub struct IoPeriphPin { + ll: ll::LowLevelGpio, + fun_sel: FunSel, +} + +impl IoPeriphPin { + pub fn new_with_pin( + _pin: Pin, + fun_sel: FunSel, + pull: Option, + ) -> Self { + let mut ll = ll::LowLevelGpio::new(I::ID); + ll.configure_as_peripheral_pin(fun_sel, pull); + IoPeriphPin { ll, fun_sel } + } + + pub fn new(pin_id: PinId, fun_sel: FunSel, pull: Option) -> Self { + let mut ll = ll::LowLevelGpio::new(pin_id); + ll.configure_as_peripheral_pin(fun_sel, pull); + IoPeriphPin { ll, fun_sel } + } + + pub fn port(&self) -> Port { + self.ll.port() + } + + pub fn offset(&self) -> usize { + self.ll.offset() + } + + pub fn fun_sel(&self) -> FunSel { + self.fun_sel + } +} diff --git a/vorago-shared-periphs/src/gpio/regs.rs b/vorago-shared-periphs/src/gpio/regs.rs new file mode 100644 index 0000000..e0ae9db --- /dev/null +++ b/vorago-shared-periphs/src/gpio/regs.rs @@ -0,0 +1,126 @@ +use crate::Port; + +cfg_if::cfg_if! { + if #[cfg(feature = "vor1x")] { + /// PORT A base address. + pub const GPIO_0_BASE: usize = 0x5000_0000; + /// PORT B base address. + pub const GPIO_1_BASE: usize = 0x5000_1000; + } else if #[cfg(feature = "vor4x")] { + /// PORT A base address. + pub const GPIO_0_BASE: usize = 0x4001_2000; + /// PORT B base address. + pub const GPIO_1_BASE: usize = 0x4001_2400; + /// PORT C base address. + pub const GPIO_2_BASE: usize = 0x4001_2800; + /// PORT D base address. + pub const GPIO_3_BASE: usize = 0x4001_2C00; + /// PORT E base address. + pub const GPIO_4_BASE: usize = 0x4001_3000; + /// PORT F base address. + pub const GPIO_5_BASE: usize = 0x4001_3400; + /// PORT G base address. + pub const GPIO_6_BASE: usize = 0x4001_3800; + } +} + +#[derive(derive_mmio::Mmio)] +#[mmio(no_ctors)] +#[repr(C)] +pub struct Gpio { + #[mmio(PureRead)] + data_in: u32, + #[mmio(PureRead)] + data_in_raw: u32, + data_out: u32, + data_out_raw: u32, + #[mmio(Write)] + set_out: u32, + #[mmio(Write)] + clr_out: u32, + #[mmio(Write)] + tog_out: u32, + data_mask: u32, + /// Direction bits. 1 for output, 0 for input. + dir: u32, + pulse: u32, + pulsebase: u32, + delay1: u32, + delay2: u32, + irq_sen: u32, + irq_edge: u32, + irq_evt: u32, + irq_enable: u32, + /// Raw interrupt status. This register is not latched and may not indicated edge sensitive + /// events. + #[mmio(PureRead)] + irq_raw: u32, + /// Read-only register which shows enabled and active interrupts. Called IRQ_end by Vorago. + #[mmio(PureRead)] + irq_status: u32, + #[mmio(PureRead)] + edge_status: u32, + + #[cfg(feature = "vor1x")] + _reserved: [u32; 0x3eb], + #[cfg(feature = "vor4x")] + _reserved: [u32; 0xeb], + + /// Peripheral ID. Vorago 1x reset value: 0x0040_07e1. Vorago 4x reset value: 0x0210_07E9. + perid: u32, +} + +cfg_if::cfg_if! { + if #[cfg(feature = "vor1x")] { + static_assertions::const_assert_eq!(core::mem::size_of::(), 0x1000); + } else if #[cfg(feature = "vor4x")] { + static_assertions::const_assert_eq!(core::mem::size_of::(), 0x400); + } +} + +impl Gpio { + const fn new_mmio_at(base: usize) -> MmioGpio<'static> { + MmioGpio { + ptr: base as *mut _, + phantom: core::marker::PhantomData, + } + } + + pub const fn new_mmio(port: Port) -> MmioGpio<'static> { + match port { + Port::A => Self::new_mmio_at(GPIO_0_BASE), + Port::B => Self::new_mmio_at(GPIO_1_BASE), + #[cfg(feature = "vor4x")] + Port::C => Self::new_mmio_at(GPIO_2_BASE), + #[cfg(feature = "vor4x")] + Port::D => Self::new_mmio_at(GPIO_3_BASE), + #[cfg(feature = "vor4x")] + Port::E => Self::new_mmio_at(GPIO_4_BASE), + #[cfg(feature = "vor4x")] + Port::F => Self::new_mmio_at(GPIO_5_BASE), + #[cfg(feature = "vor4x")] + Port::G => Self::new_mmio_at(GPIO_6_BASE), + } + } +} + +impl MmioGpio<'_> { + pub fn port(&self) -> Port { + match unsafe { self.ptr() } as usize { + GPIO_0_BASE => Port::A, + GPIO_1_BASE => Port::B, + #[cfg(feature = "vor4x")] + GPIO_2_BASE => Port::C, + #[cfg(feature = "vor4x")] + GPIO_3_BASE => Port::D, + #[cfg(feature = "vor4x")] + GPIO_4_BASE => Port::E, + #[cfg(feature = "vor4x")] + GPIO_5_BASE => Port::F, + #[cfg(feature = "vor4x")] + GPIO_6_BASE => Port::G, + // Constructors were disabled, so this should really not happen. + _ => panic!("unexpected base address of GPIO register block"), + } + } +} diff --git a/vorago-shared-periphs/src/i2c/mod.rs b/vorago-shared-periphs/src/i2c/mod.rs new file mode 100644 index 0000000..758a6e7 --- /dev/null +++ b/vorago-shared-periphs/src/i2c/mod.rs @@ -0,0 +1,665 @@ +pub mod regs; + +use crate::{ + enable_peripheral_clock, sealed::Sealed, sysconfig::reset_peripheral_for_cycles, time::Hertz, + PeripheralSelect, +}; +use arbitrary_int::{u10, u11, u20, u4}; +use core::marker::PhantomData; +use embedded_hal::i2c::{self, Operation, SevenBitAddress, TenBitAddress}; +use regs::ClkTimeoutLimit; +pub use regs::{Bank, I2cSpeed, RxFifoFullMode, TxFifoEmptyMode}; + +#[cfg(feature = "vor1x")] +use va108xx as pac; + +//================================================================================================== +// 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, 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, + #[error("clock timeout, SCL was low for {0} clock cycles")] + ClockTimeout(u20), +} + +#[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 | Error::ClockTimeout(_) => { + embedded_hal::i2c::ErrorKind::Other + } + } + } +} + +#[derive(Debug, PartialEq, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum I2cCmd { + Start = 0b01, + Stop = 0b10, + StartWithStop = 0b11, + Cancel = 0b100, +} + +#[derive(Debug, PartialEq, Eq, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum I2cAddress { + Regular(u8), + TenBit(u16), +} + +impl I2cAddress { + pub fn ten_bit_addr(&self) -> bool { + match self { + I2cAddress::Regular(_) => false, + I2cAddress::TenBit(_) => true, + } + } + pub fn raw(&self) -> u16 { + match self { + I2cAddress::Regular(addr) => *addr as u16, + I2cAddress::TenBit(addr) => *addr, + } + } +} + +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 I2cMarker: Sealed { + const ID: Bank; + const PERIPH_SEL: PeripheralSelect; +} + +impl I2cMarker for pac::I2ca { + const ID: Bank = Bank::I2c0; + const PERIPH_SEL: PeripheralSelect = PeripheralSelect::I2c0; +} +impl Sealed for pac::I2ca {} + +impl I2cMarker for pac::I2cb { + const ID: Bank = Bank::I2c1; + const PERIPH_SEL: PeripheralSelect = PeripheralSelect::I2c1; +} +impl Sealed for pac::I2cb {} + +//================================================================================================== +// Config +//================================================================================================== + +fn calc_clk_div(sys_clk: Hertz, speed_mode: I2cSpeed) -> Result { + if speed_mode == I2cSpeed::Regular100khz { + Ok(((sys_clk.raw() / CLK_100K.raw() / 20) - 1) as u8) + } else { + if sys_clk.raw() < MIN_CLK_400K.raw() { + return Err(ClockTooSlowForFastI2cError); + } + Ok(((sys_clk.raw() / CLK_400K.raw() / 25) - 1) as u8) + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct TimingConfig { + pub t_rise: u4, + pub t_fall: u4, + pub t_high: u4, + pub t_low: u4, + pub tsu_stop: u4, + pub tsu_start: u4, + pub thd_start: u4, + pub t_buf: u4, +} + +/// Default configuration are the register reset value which are used by default. +impl Default for TimingConfig { + fn default() -> Self { + TimingConfig { + t_rise: u4::new(0b0010), + t_fall: u4::new(0b0001), + t_high: u4::new(0b1000), + t_low: u4::new(0b1001), + tsu_stop: u4::new(0b1000), + tsu_start: u4::new(0b1010), + thd_start: u4::new(0b1000), + t_buf: u4::new(0b1010), + } + } +} + +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct MasterConfig { + pub tx_empty_mode: TxFifoEmptyMode, + pub rx_full_mode: RxFifoFullMode, + /// Enable the analog delay glitch filter + pub alg_filt: bool, + /// Enable the digital glitch filter + pub dlg_filt: bool, + pub timing_config: Option, + /// See [I2cMaster::set_clock_low_timeout] documentation. + pub timeout: Option, + // Loopback mode + // lbm: bool, +} + +impl Default for MasterConfig { + fn default() -> Self { + MasterConfig { + tx_empty_mode: TxFifoEmptyMode::Stall, + rx_full_mode: RxFifoFullMode::Stall, + alg_filt: false, + dlg_filt: false, + timeout: None, + timing_config: None, + } + } +} + +impl Sealed for MasterConfig {} + +#[derive(Debug, PartialEq, Eq)] +enum WriteCompletionCondition { + Idle, + Waiting, +} + +struct TimeoutGuard { + clk_timeout_enabled: bool, + regs: regs::MmioI2c<'static>, +} + +impl TimeoutGuard { + fn new(regs: ®s::MmioI2c<'static>) -> Self { + let clk_timeout_enabled = regs.read_clk_timeout_limit().value().value() > 0; + let mut guard = TimeoutGuard { + clk_timeout_enabled, + regs: unsafe { regs.clone() }, + }; + if clk_timeout_enabled { + // Clear any interrupts which might be pending. + guard.regs.write_irq_clear( + regs::InterruptClear::builder() + .with_clock_timeout(true) + .with_tx_overflow(false) + .with_rx_overflow(false) + .build(), + ); + guard.regs.modify_irq_enb(|mut value| { + value.set_clock_timeout(true); + value + }); + } + guard + } + + fn timeout_enabled(&self) -> bool { + self.clk_timeout_enabled + } +} + +impl Drop for TimeoutGuard { + fn drop(&mut self) { + if self.clk_timeout_enabled { + self.regs.modify_irq_enb(|mut value| { + value.set_clock_timeout(false); + value + }); + } + } +} +//================================================================================================== +// I2C Master +//================================================================================================== + +pub struct I2cMaster { + id: Bank, + regs: regs::MmioI2c<'static>, + addr: PhantomData, +} + +impl I2cMaster { + pub fn new( + sysclk: Hertz, + _i2c: I2c, + cfg: MasterConfig, + speed_mode: I2cSpeed, + ) -> Result { + reset_peripheral_for_cycles(I2c::PERIPH_SEL, 2); + enable_peripheral_clock(I2c::PERIPH_SEL); + let mut regs = regs::I2c::new_mmio(I2c::ID); + + let clk_div = calc_clk_div(sysclk, speed_mode)?; + regs.write_clkscale( + regs::ClkScale::builder() + .with_div(clk_div) + .with_fastmode(speed_mode) + .build(), + ); + regs.modify_control(|mut value| { + value.set_tx_fifo_empty_mode(cfg.tx_empty_mode); + value.set_rx_fifo_full_mode(cfg.rx_full_mode); + value.set_analog_filter(cfg.alg_filt); + value.set_digital_filter(cfg.dlg_filt); + value + }); + + if let Some(ref timing_cfg) = cfg.timing_config { + regs.modify_control(|mut value| { + value.set_enable_timing_config(true); + value + }); + regs.write_timing_config( + regs::TimingConfig::builder() + .with_t_rise(timing_cfg.t_rise) + .with_t_fall(timing_cfg.t_fall) + .with_t_high(timing_cfg.t_high) + .with_t_low(timing_cfg.t_low) + .with_tsu_stop(timing_cfg.tsu_stop) + .with_tsu_start(timing_cfg.tsu_start) + .with_thd_start(timing_cfg.thd_start) + .with_t_buf(timing_cfg.t_buf) + .build(), + ); + } + regs.write_fifo_clear( + regs::FifoClear::builder() + .with_tx_fifo(true) + .with_rx_fifo(true) + .build(), + ); + if let Some(timeout) = cfg.timeout { + regs.write_clk_timeout_limit(ClkTimeoutLimit::new(timeout)); + } + let mut i2c_master = I2cMaster { + addr: PhantomData, + id: I2c::ID, + regs, + }; + i2c_master.enable(); + Ok(i2c_master) + } + + pub fn id(&self) -> Bank { + self.id + } + + /// Configures the clock scale for a given speed mode setting + pub fn set_clk_scale( + &mut self, + sys_clk: Hertz, + speed_mode: I2cSpeed, + ) -> Result<(), ClockTooSlowForFastI2cError> { + self.disable(); + let clk_div = calc_clk_div(sys_clk, speed_mode)?; + self.regs.write_clkscale( + regs::ClkScale::builder() + .with_div(clk_div) + .with_fastmode(speed_mode) + .build(), + ); + self.enable(); + Ok(()) + } + + #[inline] + pub fn cancel_transfer(&mut self) { + self.regs.write_cmd( + regs::Command::builder() + .with_start(false) + .with_stop(false) + .with_cancel(true) + .build(), + ); + } + + #[inline] + pub fn clear_tx_fifo(&mut self) { + self.regs.write_fifo_clear( + regs::FifoClear::builder() + .with_tx_fifo(true) + .with_rx_fifo(false) + .build(), + ); + } + + #[inline] + pub fn clear_rx_fifo(&mut self) { + self.regs.write_fifo_clear( + regs::FifoClear::builder() + .with_tx_fifo(false) + .with_rx_fifo(true) + .build(), + ); + } + + /// Configure a timeout limit on the amount of time the I2C clock is seen to be low. + /// The timeout is specified as I2C clock cycles. + /// + /// If the timeout is enabled, the blocking transaction handlers provided by the [I2cMaster] + /// will poll the interrupt status register to check for timeouts. This can be used to avoid + /// hang-ups of the I2C bus. + #[inline] + pub fn set_clock_low_timeout(&mut self, clock_cycles: u20) { + self.regs.write_clk_timeout_limit(ClkTimeoutLimit::new(clock_cycles)); + } + + #[inline] + pub fn disable_clock_low_timeout(&mut self) { + self.regs.write_clk_timeout_limit(ClkTimeoutLimit::new(u20::new(0))); + } + + #[inline] + pub fn enable(&mut self) { + self.regs.modify_control(|mut value| { + value.set_enable(true); + value + }); + } + + #[inline] + pub fn disable(&mut self) { + self.regs.modify_control(|mut value| { + value.set_enable(false); + value + }); + } + + #[inline(always)] + fn write_fifo_unchecked(&mut self, word: u8) { + self.regs.write_data(regs::Data::new(word)); + } + + #[inline(always)] + fn read_fifo_unchecked(&self) -> u8 { + self.regs.read_data().data() + } + + #[inline] + pub fn read_status(&mut self) -> regs::Status { + self.regs.read_status() + } + + #[inline] + pub fn write_command(&mut self, cmd: I2cCmd) { + self.regs + .write_cmd(regs::Command::new_with_raw_value(cmd as u32)); + } + + #[inline] + pub fn write_address(&mut self, addr: I2cAddress, dir: regs::Direction) { + self.regs.write_address( + regs::Address::builder() + .with_direction(dir) + .with_address(u10::new(addr.raw())) + .with_a10_mode(addr.ten_bit_addr()) + .build(), + ); + } + + fn error_handler_write(&mut self, init_cmd: I2cCmd) { + if init_cmd == I2cCmd::Start { + self.write_command(I2cCmd::Stop); + } + // The other case is start with stop where, so a CANCEL command should not be necessary + // because the hardware takes care of it. + self.clear_tx_fifo(); + } + + /// Blocking write transaction on the I2C bus. + pub fn write_blocking(&mut self, addr: I2cAddress, output: &[u8]) -> Result<(), Error> { + self.write_blocking_generic( + I2cCmd::StartWithStop, + addr, + output, + WriteCompletionCondition::Idle, + ) + } + + /// Blocking read transaction on the I2C bus. + pub fn read_blocking(&mut self, addr: I2cAddress, buffer: &mut [u8]) -> Result<(), Error> { + let len = buffer.len(); + if len > 0x7fe { + return Err(Error::DataTooLarge); + } + // Clear the receive FIFO + self.clear_rx_fifo(); + + let timeout_guard = TimeoutGuard::new(&self.regs); + + // Load number of words + self.regs + .write_words(regs::Words::new(u11::new(len as u16))); + // Load address + self.write_address(addr, regs::Direction::Receive); + + let mut buf_iter = buffer.iter_mut(); + let mut read_bytes = 0; + // Start receive transfer + self.write_command(I2cCmd::StartWithStop); + loop { + let status = self.read_status(); + if status.arb_lost() { + self.clear_rx_fifo(); + return Err(Error::ArbitrationLost); + } + if status.nack_addr() { + self.clear_rx_fifo(); + return Err(Error::NackAddr); + } + if status.idle() { + if read_bytes != len { + return Err(Error::InsufficientDataReceived); + } + return Ok(()); + } + if timeout_guard.timeout_enabled() && self.regs.read_irq_status().clock_timeout() { + return Err(Error::ClockTimeout( + self.regs.read_clk_timeout_limit().value(), + )); + } + if status.rx_not_empty() { + if let Some(next_byte) = buf_iter.next() { + *next_byte = self.read_fifo_unchecked(); + } + read_bytes += 1; + } + } + } + + fn write_blocking_generic( + &mut self, + init_cmd: I2cCmd, + addr: I2cAddress, + output: &[u8], + end_condition: WriteCompletionCondition, + ) -> Result<(), Error> { + let len = output.len(); + if len > 0x7fe { + return Err(Error::DataTooLarge); + } + // Clear the send FIFO + self.clear_tx_fifo(); + + let timeout_guard = TimeoutGuard::new(&self.regs); + + // Load number of words + self.regs + .write_words(regs::Words::new(u11::new(len as u16))); + 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; + + let mut current_index = core::cmp::min(FILL_DEPTH, len); + // load the FIFO + for _ in 0..current_index { + self.write_fifo_unchecked(*bytes.next().unwrap()); + } + self.write_address(addr, regs::Direction::Send); + self.write_command(init_cmd); + loop { + let status = self.regs.read_status(); + if status.arb_lost() { + self.error_handler_write(init_cmd); + return Err(Error::ArbitrationLost); + } + if status.nack_addr() { + self.error_handler_write(init_cmd); + return Err(Error::NackAddr); + } + if status.nack_data() { + self.error_handler_write(init_cmd); + return Err(Error::NackData); + } + match end_condition { + WriteCompletionCondition::Idle => { + if status.idle() { + return Ok(()); + } + } + + WriteCompletionCondition::Waiting => { + if status.waiting() { + return Ok(()); + } + } + } + if timeout_guard.timeout_enabled() && self.regs.read_irq_status().clock_timeout() { + return Err(Error::ClockTimeout( + self.regs.read_clk_timeout_limit().value(), + )); + } + if status.tx_not_full() && current_index < len { + self.write_fifo_unchecked(output[current_index]); + current_index += 1; + } + } + } + + /// Blocking write-read transaction on the I2C bus. + pub fn write_read_blocking( + &mut self, + address: I2cAddress, + write: &[u8], + read: &mut [u8], + ) -> Result<(), Error> { + self.write_blocking_generic( + I2cCmd::Start, + address, + write, + WriteCompletionCondition::Waiting, + )?; + self.read_blocking(address, read) + } +} + +//====================================================================================== +// Embedded HAL I2C implementations +//====================================================================================== + +impl embedded_hal::i2c::ErrorType for I2cMaster { + type Error = Error; +} + +impl embedded_hal::i2c::I2c for I2cMaster { + fn transaction( + &mut self, + address: SevenBitAddress, + operations: &mut [Operation<'_>], + ) -> Result<(), Self::Error> { + for operation in operations { + match operation { + Operation::Read(buf) => self.read_blocking(I2cAddress::Regular(address), buf)?, + Operation::Write(buf) => self.write_blocking(I2cAddress::Regular(address), buf)?, + } + } + Ok(()) + } + + fn write_read( + &mut self, + address: u8, + write: &[u8], + read: &mut [u8], + ) -> Result<(), Self::Error> { + let addr = I2cAddress::Regular(address); + self.write_read_blocking(addr, write, read) + } +} + +impl embedded_hal::i2c::ErrorType for I2cMaster { + type Error = Error; +} + +impl embedded_hal::i2c::I2c for I2cMaster { + fn transaction( + &mut self, + address: TenBitAddress, + operations: &mut [Operation<'_>], + ) -> Result<(), Self::Error> { + for operation in operations { + match operation { + Operation::Read(buf) => self.read_blocking(I2cAddress::TenBit(address), buf)?, + Operation::Write(buf) => self.write_blocking(I2cAddress::TenBit(address), buf)?, + } + } + Ok(()) + } + + fn write_read( + &mut self, + address: TenBitAddress, + write: &[u8], + read: &mut [u8], + ) -> Result<(), Self::Error> { + let addr = I2cAddress::TenBit(address); + self.write_read_blocking(addr, write, read) + } +} diff --git a/vorago-shared-periphs/src/i2c/regs.rs b/vorago-shared-periphs/src/i2c/regs.rs new file mode 100644 index 0000000..7ce774e --- /dev/null +++ b/vorago-shared-periphs/src/i2c/regs.rs @@ -0,0 +1,671 @@ +use core::marker::PhantomData; + +use arbitrary_int::{u10, u11, u20, u4, u5, u9}; + +pub use crate::shared::{FifoClear, TriggerLevel}; + +cfg_if::cfg_if! { + if #[cfg(feature = "vor1x")] { + /// I2C A base address + pub const BASE_ADDR_0: usize = 0x4006_0000; + /// I2C B base address + pub const BASE_ADDR_1: usize = 0x4006_1000; + } else if #[cfg(feature = "vor4x")] { + /// I2C 0 base address + pub const BASE_ADDR_0: usize = 0x4001_6000; + /// I2C 1 base address + pub const BASE_ADDR_1: usize = 0x4001_6400; + /// I2C 2 base address + pub const BASE_ADDR_2: usize = 0x4001_6800; + } +} + +#[derive(Debug, PartialEq, Eq, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Bank { + I2c0 = 0, + I2c1 = 1, + #[cfg(feature = "vor4x")] + I2c2 = 2, +} + +impl Bank { + /// Unsafely steal the I2C peripheral block for the given port. + /// + /// # Safety + /// + /// Circumvents ownership and safety guarantees by the HAL. + pub unsafe fn steal_regs(&self) -> MmioI2c<'static> { + I2c::new_mmio(*self) + } +} + +#[bitbybit::bitenum(u1, exhaustive = true)] +#[derive(Default, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum TxFifoEmptyMode { + /// I2C clock is stretched until data is available. + #[default] + Stall = 0, + EndTransaction = 1, +} + +#[bitbybit::bitenum(u1, exhaustive = true)] +#[derive(Default, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum RxFifoFullMode { + /// I2C clock is stretched until data is available. + #[default] + Stall = 0, + Nack = 1, +} + +#[bitbybit::bitfield(u32)] +#[derive(Debug)] +pub struct Control { + #[bit(0, r)] + clk_enabled: bool, + #[bit(1, r)] + enabled: bool, + #[bit(2, rw)] + enable: bool, + #[bit(3, rw)] + tx_fifo_empty_mode: TxFifoEmptyMode, + #[bit(4, rw)] + rx_fifo_full_mode: RxFifoFullMode, + /// Enables the analog delay glitch filter. + #[bit(5, rw)] + analog_filter: bool, + /// Enables the digital glitch filter. + #[bit(6, rw)] + digital_filter: bool, + #[bit(8, rw)] + loopback: bool, + #[bit(9, rw)] + enable_timing_config: bool, +} + +#[derive(Debug, PartialEq, Eq)] +#[bitbybit::bitenum(u1, exhaustive = true)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum I2cSpeed { + Regular100khz = 0, + Fast400khz = 1, +} + +#[bitbybit::bitfield(u32, default = 0x0)] +#[derive(Debug)] +pub struct ClkScale { + /// Clock divide value. Reset value: 0x18. + #[bits(0..=7, rw)] + div: u8, + #[bit(31, rw)] + fastmode: I2cSpeed, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct Words(arbitrary_int::UInt); + +impl Words { + pub const fn new(value: u11) -> Self { + Words(arbitrary_int::UInt::::new(value.value() as u32)) + } + pub const fn value(&self) -> u11 { + u11::new(self.0.value() as u16) + } +} + +#[bitbybit::bitenum(u1, exhaustive = true)] +#[derive(Default, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Direction { + #[default] + Send = 0, + Receive = 1, +} + +#[bitbybit::bitfield(u32, default = 0x0)] +#[derive(Debug)] +pub struct Address { + #[bit(0, rw)] + direction: Direction, + #[bits(1..=10, rw)] + address: u10, + /// Enables 10-bit addressing mode. + #[bit(15, rw)] + a10_mode: bool, +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub struct Data(arbitrary_int::UInt); + +impl Data { + pub const fn new(value: u8) -> Self { + Data(arbitrary_int::UInt::::new(value as u32)) + } + + pub const fn data(&self) -> u8 { + self.0.value() as u8 + } +} + +#[bitbybit::bitfield(u32, default = 0x0)] +#[derive(Debug)] +pub struct Command { + #[bit(0, w)] + start: bool, + #[bit(1, w)] + stop: bool, + #[bit(2, w)] + cancel: bool, +} + +#[bitbybit::bitfield(u32)] +#[derive(Debug)] +pub struct Status { + #[bit(0, r)] + i2c_idle: bool, + #[bit(1, r)] + idle: bool, + #[bit(2, r)] + waiting: bool, + #[bit(3, r)] + stalled: bool, + #[bit(4, r)] + arb_lost: bool, + #[bit(5, r)] + nack_addr: bool, + #[bit(6, r)] + nack_data: bool, + #[bit(8, r)] + rx_not_empty: bool, + #[bit(9, r)] + rx_full: bool, + #[bit(11, r)] + rx_trigger: bool, + #[bit(12, r)] + tx_empty: bool, + #[bit(13, r)] + tx_not_full: bool, + #[bit(15, r)] + tx_trigger: bool, + #[bit(30, r)] + raw_sda: bool, + #[bit(31, r)] + raw_scl: bool, +} + +#[bitbybit::bitfield(u32)] +#[derive(Debug)] +pub struct State { + #[bits(0..=3, rw)] + state: u4, + #[bits(4..=7, rw)] + step: u4, + #[bits(8..=12, rw)] + rx_fifo: u5, + #[bits(14..=18, rw)] + tx_fifo: u5, + #[bits(20..=28, rw)] + bitstate: u9, +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct DataCount(arbitrary_int::UInt); + +#[bitbybit::bitfield(u32)] +#[derive(Debug)] +pub struct InterruptControl { + #[bit(0, rw)] + i2c_idle: bool, + #[bit(1, rw)] + idle: bool, + #[bit(2, rw)] + waiting: bool, + #[bit(3, rw)] + stalled: bool, + #[bit(4, rw)] + arb_lost: bool, + #[bit(5, rw)] + nack_addr: bool, + #[bit(6, rw)] + nack_data: bool, + #[bit(7, rw)] + clock_timeout: bool, + #[bit(10, rw)] + tx_overflow: bool, + #[bit(11, rw)] + rx_overflow: bool, + #[bit(12, rw)] + tx_ready: bool, + #[bit(13, rw)] + rx_ready: bool, + #[bit(14, rw)] + tx_empty: bool, + #[bit(15, rw)] + rx_full: bool, +} + +#[bitbybit::bitfield(u32)] +#[derive(Debug)] +pub struct InterruptStatus { + #[bit(0, r)] + i2c_idle: bool, + #[bit(1, r)] + idle: bool, + #[bit(2, r)] + waiting: bool, + #[bit(3, r)] + stalled: bool, + #[bit(4, r)] + arb_lost: bool, + #[bit(5, r)] + nack_addr: bool, + #[bit(6, r)] + nack_data: bool, + #[bit(7, r)] + clock_timeout: bool, + #[bit(10, r)] + tx_overflow: bool, + #[bit(11, r)] + rx_overflow: bool, + #[bit(12, r)] + tx_ready: bool, + #[bit(13, r)] + rx_ready: bool, + #[bit(14, r)] + tx_empty: bool, + #[bit(15, r)] + rx_full: bool, +} + +#[bitbybit::bitfield(u32, default = 0x0)] +#[derive(Debug)] +pub struct InterruptClear { + #[bit(7, w)] + clock_timeout: bool, + #[bit(10, w)] + tx_overflow: bool, + #[bit(11, w)] + rx_overflow: bool, +} + +#[bitbybit::bitfield(u32)] +#[derive(Debug)] +pub struct TimingConfig { + /// Rise time. + #[bits(0..=3, rw)] + t_rise: u4, + /// Fall time. + #[bits(4..=7, rw)] + t_fall: u4, + /// Duty cycle high time of SCL. + #[bits(8..=11, rw)] + t_high: u4, + /// Duty cycle low time of SCL. + #[bits(12..=15, rw)] + t_low: u4, + /// Setup time for STOP. + #[bits(16..=19, rw)] + tsu_stop: u4, + /// Setup time for START. + #[bits(20..=23, rw)] + tsu_start: u4, + /// Data hold time. + #[bits(24..=27, rw)] + thd_start: u4, + /// TBus free time between STOP and START. + #[bits(28..=31, rw)] + t_buf: u4, +} + +pub struct ClkTimeoutLimit(pub arbitrary_int::UInt); + +impl ClkTimeoutLimit { + pub fn new(value: u20) -> Self { + ClkTimeoutLimit(arbitrary_int::UInt::::new(value.value())) + } + pub fn value(&self) -> u20 { + self.0 + } +} + +pub mod slave { + use super::{Data, DataCount, FifoClear, RxFifoFullMode, TriggerLevel, TxFifoEmptyMode}; + use arbitrary_int::{u10, u11, u3, u4, u5}; + + #[bitbybit::bitfield(u32)] + #[derive(Debug)] + pub struct Control { + #[bit(0, r)] + clk_enabled: bool, + #[bit(1, r)] + enabled: bool, + #[bit(2, rw)] + enable: bool, + #[bit(3, rw)] + tx_fifo_empty_mode: TxFifoEmptyMode, + #[bit(4, rw)] + rx_fifo_full_mode: RxFifoFullMode, + } + + #[bitbybit::bitfield(u32)] + #[derive(Debug)] + pub struct Maxwords { + #[bits(0..=10, rw)] + maxwords: u11, + #[bit(31, rw)] + enable: bool, + } + + #[bitbybit::bitfield(u32)] + #[derive(Debug)] + pub struct Address { + #[bit(0, rw)] + rw: bool, + #[bits(1..=10, rw)] + address: u10, + #[bit(15, rw)] + a10_mode: bool, + } + + #[bitbybit::bitfield(u32)] + #[derive(Debug)] + pub struct AddressMask { + /// Will normally be 0 to match both read and write addresses. + #[bit(0, rw)] + rw_mask: bool, + /// Reset value 0x3FF. + #[bits(1..=10, rw)] + mask: u10, + } + + #[bitbybit::bitenum(u1, exhaustive = true)] + #[derive(Default, Debug, PartialEq, Eq)] + pub enum Direction { + #[default] + MasterSend = 0, + MasterReceive = 1, + } + + #[bitbybit::bitfield(u32)] + #[derive(Debug)] + pub struct LastAddress { + #[bit(0, rw)] + direction: Direction, + #[bits(1..=10, rw)] + address: u10, + } + + #[bitbybit::bitfield(u32)] + #[derive(Debug)] + pub struct Status { + #[bit(0, r)] + completed: bool, + #[bit(1, r)] + idle: bool, + #[bit(2, r)] + waiting: bool, + #[bit(3, r)] + tx_stalled: bool, + #[bit(4, r)] + rx_stalled: bool, + #[bit(5, r)] + address_match: bool, + #[bit(6, r)] + nack_data: bool, + #[bit(7, r)] + rx_data_first: bool, + #[bit(8, r)] + rx_not_empty: bool, + #[bit(9, r)] + rx_full: bool, + #[bit(11, r)] + rx_trigger: bool, + #[bit(12, r)] + tx_empty: bool, + #[bit(13, r)] + tx_not_full: bool, + #[bit(15, r)] + tx_trigger: bool, + #[bit(28, r)] + raw_busy: bool, + #[bit(30, r)] + raw_sda: bool, + #[bit(31, r)] + raw_scl: bool, + } + + #[bitbybit::bitfield(u32)] + #[derive(Debug)] + pub struct State { + #[bits(0..=2, rw)] + state: u3, + #[bits(4..=7, rw)] + step: u4, + #[bits(8..=12, rw)] + rx_fifo: u5, + #[bits(14..=18, rw)] + tx_fifo: u5, + } + + #[bitbybit::bitfield(u32)] + #[derive(Debug)] + pub struct InterruptControl { + #[bit(0, rw)] + completed: bool, + #[bit(1, rw)] + idle: bool, + #[bit(2, rw)] + waiting: bool, + #[bit(3, rw)] + tx_stalled: bool, + #[bit(4, rw)] + rx_stalled: bool, + #[bit(5, rw)] + address_match: bool, + #[bit(6, rw)] + nack_data: bool, + #[bit(7, rw)] + rx_data_first: bool, + + #[bit(8, rw)] + i2c_start: bool, + #[bit(9, rw)] + i2c_stop: bool, + #[bit(10, rw)] + tx_underflow: bool, + #[bit(11, rw)] + rx_underflow: bool, + #[bit(12, rw)] + tx_ready: bool, + #[bit(13, rw)] + rx_ready: bool, + #[bit(14, rw)] + tx_empty: bool, + #[bit(15, rw)] + rx_full: bool, + } + + #[bitbybit::bitfield(u32)] + #[derive(Debug)] + pub struct InterruptStatus { + #[bit(0, r)] + completed: bool, + #[bit(1, r)] + idle: bool, + #[bit(2, r)] + waiting: bool, + #[bit(3, r)] + tx_stalled: bool, + #[bit(4, r)] + rx_stalled: bool, + #[bit(5, r)] + address_match: bool, + #[bit(6, r)] + nack_data: bool, + #[bit(7, r)] + rx_data_first: bool, + + #[bit(8, r)] + i2c_start: bool, + #[bit(9, r)] + i2c_stop: bool, + #[bit(10, r)] + tx_underflow: bool, + #[bit(11, r)] + rx_underflow: bool, + #[bit(12, r)] + tx_ready: bool, + #[bit(13, r)] + rx_ready: bool, + #[bit(14, r)] + tx_empty: bool, + #[bit(15, r)] + rx_full: bool, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + #[derive(Debug)] + pub struct InterruptClear { + #[bit(0, w)] + completed: bool, + #[bit(1, w)] + idle: bool, + #[bit(2, w)] + waiting: bool, + #[bit(3, w)] + tx_stalled: bool, + #[bit(4, w)] + rx_stalled: bool, + #[bit(5, w)] + address_match: bool, + #[bit(6, w)] + nack_data: bool, + #[bit(7, w)] + rx_data_first: bool, + + #[bit(8, w)] + i2c_start: bool, + #[bit(9, w)] + i2c_stop: bool, + #[bit(10, w)] + tx_underflow: bool, + #[bit(11, w)] + rx_underflow: bool, + #[bit(12, w)] + tx_ready: bool, + #[bit(13, w)] + rx_ready: bool, + #[bit(14, w)] + tx_empty: bool, + #[bit(15, w)] + rx_full: bool, + } + + #[derive(derive_mmio::Mmio)] + #[repr(C)] + pub struct I2cSlave { + s0_ctrl: Control, + s0_maxwords: Maxwords, + s0_address: Address, + s0_addressmask: AddressMask, + s0_data: Data, + s0_lastaddress: LastAddress, + #[mmio(PureRead)] + s0_status: Status, + #[mmio(PureRead)] + s0_state: State, + #[mmio(PureRead)] + s0_tx_count: DataCount, + #[mmio(PureRead)] + s0_rx_count: DataCount, + s0_irq_enb: InterruptControl, + #[mmio(PureRead)] + s0_irq_raw: InterruptStatus, + #[mmio(PureRead)] + s0_irq_status: InterruptStatus, + #[mmio(Write)] + s0_irq_clear: InterruptClear, + s0_rx_fifo_trigger: TriggerLevel, + s0_tx_fifo_trigger: TriggerLevel, + #[mmio(Write)] + s0_fifo_clear: FifoClear, + s0_address_b: Address, + s0_addressmask_b: AddressMask, + } +} +#[derive(derive_mmio::Mmio)] +#[mmio(no_ctors)] +#[repr(C)] +pub struct I2c { + control: Control, + clkscale: ClkScale, + words: Words, + address: Address, + data: Data, + #[mmio(Write)] + cmd: Command, + #[mmio(PureRead)] + status: Status, + #[mmio(PureRead)] + state: State, + #[mmio(PureRead)] + tx_count: DataCount, + #[mmio(PureRead)] + rx_count: DataCount, + irq_enb: InterruptControl, + #[mmio(PureRead)] + irq_raw: InterruptStatus, + #[mmio(PureRead)] + irq_status: InterruptStatus, + #[mmio(Write)] + irq_clear: InterruptClear, + rx_fifo_trigger: TriggerLevel, + tx_fifo_trigger: TriggerLevel, + #[mmio(Write)] + fifo_clear: FifoClear, + timing_config: TimingConfig, + clk_timeout_limit: ClkTimeoutLimit, + + _reserved_0: [u32; 0x2D], + + #[mmio(inner)] + slave: slave::I2cSlave, + + #[cfg(feature = "vor1x")] + _reserved_1: [u32; 0x3AC], + #[cfg(feature = "vor4x")] + _reserved_1: [u32; 0xAC], + + /// Vorago 4x: 0x0214_07E9. Vorago 1x: 0x0014_07E1. + #[mmio(PureRead)] + perid: u32, +} + +cfg_if::cfg_if! { + if #[cfg(feature = "vor1x")] { + static_assertions::const_assert_eq!(core::mem::size_of::(), 0x1000); + } else if #[cfg(feature = "vor4x")] { + static_assertions::const_assert_eq!(core::mem::size_of::(), 0x400); + } +} + +impl I2c { + fn new_mmio_at(base: usize) -> MmioI2c<'static> { + MmioI2c { + ptr: base as *mut _, + phantom: PhantomData, + } + } + + pub fn new_mmio(bank: Bank) -> MmioI2c<'static> { + match bank { + Bank::I2c0 => Self::new_mmio_at(BASE_ADDR_0), + Bank::I2c1 => Self::new_mmio_at(BASE_ADDR_1), + #[cfg(feature = "vor4x")] + Bank::Spi3 => Self::new_mmio_at(BASE_ADDR_2), + } + } +} diff --git a/vorago-shared-periphs/src/ioconfig/mod.rs b/vorago-shared-periphs/src/ioconfig/mod.rs new file mode 100644 index 0000000..d5b7d8e --- /dev/null +++ b/vorago-shared-periphs/src/ioconfig/mod.rs @@ -0,0 +1,3 @@ +pub use regs::{FilterClkSel, FilterType}; + +pub mod regs; diff --git a/vorago-shared-periphs/src/ioconfig/regs.rs b/vorago-shared-periphs/src/ioconfig/regs.rs new file mode 100644 index 0000000..943eb3d --- /dev/null +++ b/vorago-shared-periphs/src/ioconfig/regs.rs @@ -0,0 +1,177 @@ +use core::marker::PhantomData; + +use crate::{NUM_PORT_A, NUM_PORT_B, gpio::PinId}; +#[cfg(feature = "vor4x")] +use crate::{NUM_PORT_DEFAULT, NUM_PORT_G}; + +#[cfg(feature = "vor1x")] +pub const BASE_ADDR: usize = 0x4000_2000; +#[cfg(feature = "vor4x")] +pub const BASE_ADDR: usize = 0x4001_1000; + +#[bitbybit::bitenum(u3)] +pub enum FilterType { + SysClk = 0, + DirectInput = 1, + FilterOneCycle = 2, + FilterTwoCycles = 3, + FilterThreeCycles = 4, + FilterFourCycles = 5, +} + +#[derive(Debug, PartialEq, Eq)] +#[bitbybit::bitenum(u3, exhaustive = true)] +#[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, +} + +#[derive(Debug, PartialEq, Eq)] +#[bitbybit::bitenum(u1, exhaustive = true)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Pull { + Up = 0, + Down = 1, +} + +#[derive(Debug, Eq, PartialEq)] +#[bitbybit::bitenum(u2, exhaustive = true)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum FunSel { + Sel0 = 0b00, + Sel1 = 0b01, + Sel2 = 0b10, + Sel3 = 0b11, +} + +#[bitbybit::bitfield(u32)] +pub struct Config { + #[bit(16, rw)] + io_disable: bool, + #[bits(13..=14, rw)] + funsel: FunSel, + #[bit(12, rw)] + pull_when_output_active: bool, + #[bit(11, rw)] + pull_enable: bool, + #[bit(10, rw)] + pull_dir: Pull, + #[bit(9, rw)] + invert_output: bool, + #[bit(8, rw)] + open_drain: bool, + /// IEWO bit. Allows monitoring of output values. + #[bit(7, rw)] + input_enable_when_output: bool, + #[bit(6, rw)] + invert_input: bool, + #[bits(3..=5, rw)] + filter_clk_sel: FilterClkSel, + #[bits(0..=2, rw)] + filter_type: Option, +} + +#[derive(derive_mmio::Mmio)] +#[mmio(no_ctors)] +#[repr(C)] +pub struct IoConfig { + port_a: [Config; NUM_PORT_A], + port_b: [Config; NUM_PORT_B], + #[cfg(feature = "vor4x")] + port_c: [Config; NUM_PORT_DEFAULT], + #[cfg(feature = "vor4x")] + port_d: [Config; NUM_PORT_DEFAULT], + #[cfg(feature = "vor4x")] + port_e: [Config; NUM_PORT_DEFAULT], + #[cfg(feature = "vor4x")] + port_f: [Config; NUM_PORT_DEFAULT], + #[cfg(feature = "vor4x")] + port_g: [Config; NUM_PORT_G], + #[cfg(feature = "vor4x")] + _reserved0: [u32; 0x8], + #[cfg(feature = "vor4x")] + #[mmio(PureRead)] + clk_div_0: u32, + #[cfg(feature = "vor4x")] + clk_div_1: u32, + #[cfg(feature = "vor4x")] + clk_div_2: u32, + #[cfg(feature = "vor4x")] + clk_div_3: u32, + #[cfg(feature = "vor4x")] + clk_div_4: u32, + #[cfg(feature = "vor4x")] + clk_div_5: u32, + #[cfg(feature = "vor4x")] + clk_div_6: u32, + #[cfg(feature = "vor4x")] + clk_div_7: u32, + #[cfg(feature = "vor4x")] + _reserved1: [u32; 0x387], + #[cfg(feature = "vor1x")] + _reserved1: [u32; 0x3c7], + #[mmio(PureRead)] + /// Reset value: 0x0282_07E9 for Vorago 4x, and 0x0182_07E1 for Vorago 1x + perid: u32, +} + +static_assertions::const_assert_eq!(core::mem::size_of::(), 0x1000); + +impl IoConfig { + pub const fn new_mmio() -> MmioIoConfig<'static> { + MmioIoConfig { + ptr: BASE_ADDR as *mut _, + phantom: PhantomData, + } + } +} + +impl MmioIoConfig<'_> { + pub fn read_pin_config(&self, id: PinId) -> Config { + let offset = id.offset(); + match id.port() { + crate::Port::A => unsafe { self.read_port_a_unchecked(offset) }, + crate::Port::B => unsafe { self.read_port_b_unchecked(offset) }, + #[cfg(feature = "vor4x")] + crate::Port::C => unsafe { self.read_port_c_unchecked(offset) }, + #[cfg(feature = "vor4x")] + crate::Port::D => unsafe { self.read_port_d_unchecked(offset) }, + #[cfg(feature = "vor4x")] + crate::Port::E => unsafe { self.read_port_e_unchecked(offset) }, + #[cfg(feature = "vor4x")] + crate::Port::F => unsafe { self.read_port_f_unchecked(offset) }, + #[cfg(feature = "vor4x")] + crate::Port::G => unsafe { self.read_port_g_unchecked(offset) }, + } + } + + pub fn modify_pin_config Config>(&mut self, id: PinId, f: F) { + let config = self.read_pin_config(id); + self.write_pin_config(id, f(config)) + } + + pub fn write_pin_config(&mut self, id: PinId, config: Config) { + let offset = id.offset(); + match id.port() { + crate::Port::A => unsafe { self.write_port_a_unchecked(offset, config) }, + crate::Port::B => unsafe { self.write_port_b_unchecked(offset, config) }, + #[cfg(feature = "vor4x")] + crate::Port::C => unsafe { self.write_port_c_unchecked(offset, config) }, + #[cfg(feature = "vor4x")] + crate::Port::D => unsafe { self.write_port_d_unchecked(offset, config) }, + #[cfg(feature = "vor4x")] + crate::Port::E => unsafe { self.write_port_e_unchecked(offset, config) }, + #[cfg(feature = "vor4x")] + crate::Port::F => unsafe { self.write_port_f_unchecked(offset, config) }, + #[cfg(feature = "vor4x")] + crate::Port::G => unsafe { self.write_port_g_unchecked(offset, config) }, + } + } +} diff --git a/vorago-shared-periphs/src/lib.rs b/vorago-shared-periphs/src/lib.rs new file mode 100644 index 0000000..1e10c11 --- /dev/null +++ b/vorago-shared-periphs/src/lib.rs @@ -0,0 +1,182 @@ +#![no_std] +pub mod gpio; +pub mod i2c; +pub mod ioconfig; +pub mod pins; +pub mod pwm; +pub mod spi; +pub mod sysconfig; +pub mod time; +pub mod timer; +pub mod uart; + +pub use sysconfig::{disable_peripheral_clock, enable_peripheral_clock}; + +#[cfg(not(feature = "_family-selected"))] +compile_error!("no Vorago CPU family was select. Choices: vor1x or vor4x"); + +pub use ioconfig::regs::FunSel; + +#[cfg(feature = "vor1x")] +#[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, +} + +cfg_if::cfg_if! { + if #[cfg(feature = "vor1x")] { + /// Number of GPIO ports and IOCONFIG registers for PORT A + pub const NUM_PORT_A: usize = 32; + /// Number of GPIO ports and IOCONFIG registers for PORT B + pub const NUM_PORT_B: usize = 24; + } else if #[cfg(feature = "vor4x")] { + /// Number of GPIO ports and IOCONFIG registers for PORT C to Port F + pub const NUM_PORT_DEFAULT: usize = 16; + /// Number of GPIO ports and IOCONFIG registers for PORT A + pub const NUM_PORT_A: usize = NUM_PORT_DEFAULT; + /// Number of GPIO ports and IOCONFIG registers for PORT B + pub const NUM_PORT_B: usize = NUM_PORT_DEFAULT; + /// Number of GPIO ports and IOCONFIG registers for PORT G + pub const NUM_PORT_G: usize = 8; + } +} + +/// GPIO port enumeration. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Port { + A = 0, + B = 1, + #[cfg(feature = "vor4x")] + C = 2, + #[cfg(feature = "vor4x")] + D = 3, + #[cfg(feature = "vor4x")] + E = 4, + #[cfg(feature = "vor4x")] + F = 5, + #[cfg(feature = "vor4x")] + G = 6, +} + +impl Port { + pub const fn max_offset(&self) -> usize { + match self { + Port::A => NUM_PORT_A, + Port::B => NUM_PORT_B, + #[cfg(feature = "vor4x")] + Port::C | Port::D | Port::E | Port::F => NUM_PORT_DEFAULT, + #[cfg(feature = "vor4x")] + Port::G => NUM_PORT_G, + } + } + + /// Unsafely steal the GPIO peripheral block for the given port. + /// + /// # Safety + /// + /// Circumvents ownership and safety guarantees by the HAL. + pub unsafe fn steal_gpio(&self) -> gpio::regs::MmioGpio<'static> { + gpio::regs::Gpio::new_mmio(*self) + } +} + +#[derive(Debug, thiserror::Error)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[error("invalid GPIO offset {offset} for port {port:?}")] +pub struct InvalidOffsetError { + offset: usize, + port: Port, +} + +/// 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. +#[cfg(feature = "vor1x")] +#[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: va108xx::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, +} + +#[cfg(feature = "vor1x")] +impl InterruptConfig { + pub fn new(id: va108xx::Interrupt, route: bool, enable_in_nvic: bool) -> Self { + InterruptConfig { + id, + route, + enable_in_nvic, + } + } +} + +/// Enable a specific interrupt using the NVIC peripheral. +/// +/// # Safety +/// +/// This function is `unsafe` because it can break mask-based critical sections. +#[cfg(feature = "vor1x")] +#[inline] +pub unsafe fn enable_nvic_interrupt(irq: va108xx::Interrupt) { + unsafe { + cortex_m::peripheral::NVIC::unmask(irq); + } +} + +/// Disable a specific interrupt using the NVIC peripheral. +#[cfg(feature = "vor1x")] +#[inline] +pub fn disable_nvic_interrupt(irq: va108xx::Interrupt) { + cortex_m::peripheral::NVIC::mask(irq); +} + +#[allow(dead_code)] +pub(crate) mod sealed { + pub trait Sealed {} +} + +pub(crate) mod shared { + use arbitrary_int::u5; + + #[derive(Debug)] + pub struct TriggerLevel(arbitrary_int::UInt); + + impl TriggerLevel { + pub const fn new(value: u5) -> Self { + TriggerLevel(arbitrary_int::UInt::::new(value.value() as u32)) + } + + pub const fn value(&self) -> u5 { + u5::new(self.0.value() as u8) + } + } + + #[bitbybit::bitfield(u32, default = 0x0)] + #[derive(Debug)] + pub struct FifoClear { + #[bit(1, w)] + tx_fifo: bool, + #[bit(0, w)] + rx_fifo: bool, + } +} diff --git a/vorago-shared-periphs/src/pins.rs b/vorago-shared-periphs/src/pins.rs new file mode 100644 index 0000000..940c35d --- /dev/null +++ b/vorago-shared-periphs/src/pins.rs @@ -0,0 +1,236 @@ +use crate::sysconfig::reset_peripheral_for_cycles; + +pub use crate::gpio::{Pin, PinId, PinIdProvider, Port}; + +use crate::PeripheralSelect; +use crate::sealed::Sealed; + +pub trait PinMarker: Sealed { + const ID: PinId; +} + +macro_rules! pin_id { + ($Id:ident, $Port:path, $num:literal) => { + // Need paste macro to use ident in doc attribute + paste::paste! { + #[doc = "Pin ID representing pin " $Id] + #[derive(Debug)] + pub enum $Id {} + + impl $crate::sealed::Sealed for $Id {} + impl PinIdProvider for $Id { + const ID: PinId = PinId::new_unchecked($Port, $num); + } + + impl PinMarker for Pin<$Id> { + const ID: PinId = $Id::ID; + } + } + }; +} + +impl Sealed for Pin {} + +pin_id!(Pa0, Port::A, 0); +pin_id!(Pa1, Port::A, 1); +pin_id!(Pa2, Port::A, 2); +pin_id!(Pa3, Port::A, 3); +pin_id!(Pa4, Port::A, 4); +pin_id!(Pa5, Port::A, 5); +pin_id!(Pa6, Port::A, 6); +pin_id!(Pa7, Port::A, 7); +pin_id!(Pa8, Port::A, 8); +pin_id!(Pa9, Port::A, 9); +pin_id!(Pa10, Port::A, 10); +pin_id!(Pa11, Port::A, 11); +pin_id!(Pa12, Port::A, 12); +pin_id!(Pa13, Port::A, 13); +pin_id!(Pa14, Port::A, 14); +pin_id!(Pa15, Port::A, 15); +pin_id!(Pa16, Port::A, 16); +pin_id!(Pa17, Port::A, 17); +pin_id!(Pa18, Port::A, 18); +pin_id!(Pa19, Port::A, 19); +pin_id!(Pa20, Port::A, 20); +pin_id!(Pa21, Port::A, 21); +pin_id!(Pa22, Port::A, 22); +pin_id!(Pa23, Port::A, 23); +pin_id!(Pa24, Port::A, 24); +pin_id!(Pa25, Port::A, 25); +pin_id!(Pa26, Port::A, 26); +pin_id!(Pa27, Port::A, 27); +pin_id!(Pa28, Port::A, 28); +pin_id!(Pa29, Port::A, 29); +pin_id!(Pa30, Port::A, 30); +pin_id!(Pa31, Port::A, 31); + +pin_id!(Pb0, Port::B, 0); +pin_id!(Pb1, Port::B, 1); +pin_id!(Pb2, Port::B, 2); +pin_id!(Pb3, Port::B, 3); +pin_id!(Pb4, Port::B, 4); +pin_id!(Pb5, Port::B, 5); +pin_id!(Pb6, Port::B, 6); +pin_id!(Pb7, Port::B, 7); +pin_id!(Pb8, Port::B, 8); +pin_id!(Pb9, Port::B, 9); +pin_id!(Pb10, Port::B, 10); +pin_id!(Pb11, Port::B, 11); +pin_id!(Pb12, Port::B, 12); +pin_id!(Pb13, Port::B, 13); +pin_id!(Pb14, Port::B, 14); +pin_id!(Pb15, Port::B, 15); +pin_id!(Pb16, Port::B, 16); +pin_id!(Pb17, Port::B, 17); +pin_id!(Pb18, Port::B, 18); +pin_id!(Pb19, Port::B, 19); +pin_id!(Pb20, Port::B, 20); +pin_id!(Pb21, Port::B, 21); +pin_id!(Pb22, Port::B, 22); +pin_id!(Pb23, Port::B, 23); + +pub struct PinsA { + pub pa0: Pin, + pub pa1: Pin, + pub pa2: Pin, + pub pa3: Pin, + pub pa4: Pin, + pub pa5: Pin, + pub pa6: Pin, + pub pa7: Pin, + pub pa8: Pin, + pub pa9: Pin, + pub pa10: Pin, + pub pa11: Pin, + pub pa12: Pin, + pub pa13: Pin, + pub pa14: Pin, + pub pa15: Pin, + pub pa16: Pin, + pub pa17: Pin, + pub pa18: Pin, + pub pa19: Pin, + pub pa20: Pin, + pub pa21: Pin, + pub pa22: Pin, + pub pa23: Pin, + pub pa24: Pin, + pub pa25: Pin, + pub pa26: Pin, + pub pa27: Pin, + pub pa28: Pin, + pub pa29: Pin, + pub pa30: Pin, + pub pa31: Pin, +} + +impl PinsA { + pub fn new(_port_a: va108xx::Porta) -> Self { + let syscfg = unsafe { va108xx::Sysconfig::steal() }; + reset_peripheral_for_cycles(PeripheralSelect::PortA, 2); + syscfg.peripheral_clk_enable().modify(|_, w| { + w.porta().set_bit(); + w.gpio().set_bit(); + w.ioconfig().set_bit() + }); + Self { + pa0: Pin::__new(), + pa1: Pin::__new(), + pa2: Pin::__new(), + pa3: Pin::__new(), + pa4: Pin::__new(), + pa5: Pin::__new(), + pa6: Pin::__new(), + pa7: Pin::__new(), + pa8: Pin::__new(), + pa9: Pin::__new(), + pa10: Pin::__new(), + pa11: Pin::__new(), + pa12: Pin::__new(), + pa13: Pin::__new(), + pa14: Pin::__new(), + pa15: Pin::__new(), + pa16: Pin::__new(), + pa17: Pin::__new(), + pa18: Pin::__new(), + pa19: Pin::__new(), + pa20: Pin::__new(), + pa21: Pin::__new(), + pa22: Pin::__new(), + pa23: Pin::__new(), + pa24: Pin::__new(), + pa25: Pin::__new(), + pa26: Pin::__new(), + pa27: Pin::__new(), + pa28: Pin::__new(), + pa29: Pin::__new(), + pa30: Pin::__new(), + pa31: Pin::__new(), + } + } +} + +pub struct PinsB { + pub pb0: Pin, + pub pb1: Pin, + pub pb2: Pin, + pub pb3: Pin, + pub pb4: Pin, + pub pb5: Pin, + pub pb6: Pin, + pub pb7: Pin, + pub pb8: Pin, + pub pb9: Pin, + pub pb10: Pin, + pub pb11: Pin, + pub pb12: Pin, + pub pb13: Pin, + pub pb14: Pin, + pub pb15: Pin, + pub pb16: Pin, + pub pb17: Pin, + pub pb18: Pin, + pub pb19: Pin, + pub pb20: Pin, + pub pb21: Pin, + pub pb22: Pin, + pub pb23: Pin, +} + +impl PinsB { + pub fn new(_port_b: va108xx::Portb) -> Self { + let syscfg = unsafe { va108xx::Sysconfig::steal() }; + reset_peripheral_for_cycles(PeripheralSelect::PortB, 2); + syscfg.peripheral_clk_enable().modify(|_, w| { + w.portb().set_bit(); + w.gpio().set_bit(); + w.ioconfig().set_bit() + }); + Self { + pb0: Pin::__new(), + pb1: Pin::__new(), + pb2: Pin::__new(), + pb3: Pin::__new(), + pb4: Pin::__new(), + pb5: Pin::__new(), + pb6: Pin::__new(), + pb7: Pin::__new(), + pb8: Pin::__new(), + pb9: Pin::__new(), + pb10: Pin::__new(), + pb11: Pin::__new(), + pb12: Pin::__new(), + pb13: Pin::__new(), + pb14: Pin::__new(), + pb15: Pin::__new(), + pb16: Pin::__new(), + pb17: Pin::__new(), + pb18: Pin::__new(), + pb19: Pin::__new(), + pb20: Pin::__new(), + pb21: Pin::__new(), + pb22: Pin::__new(), + pb23: Pin::__new(), + } + } +} diff --git a/vorago-shared-periphs/src/pwm.rs b/vorago-shared-periphs/src/pwm.rs new file mode 100644 index 0000000..8db97fd --- /dev/null +++ b/vorago-shared-periphs/src/pwm.rs @@ -0,0 +1,246 @@ +use core::convert::Infallible; +use core::marker::PhantomData; + +use crate::gpio::IoPeriphPin; +use crate::timer::enable_tim_clk; +use crate::timer::regs::{EnableControl, StatusSelect}; +use crate::{PeripheralSelect, enable_peripheral_clock}; + +use crate::time::Hertz; +use crate::timer::{self, TimId, TimMarker, TimPin}; + +const DUTY_MAX: u16 = u16::MAX; + +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum PwmA {} +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum PwmB {} + +#[derive(Debug, thiserror::Error)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[error("pin tim ID {pin_tim:?} and timer tim id {tim_id:?} do not match")] +pub struct TimMissmatchError { + pin_tim: TimId, + tim_id: TimId, +} + +//================================================================================================== +// PWM pin +//================================================================================================== + +/// Reduced version where type information is deleted +pub struct PwmPin { + tim_id: TimId, + regs: timer::regs::MmioTimer<'static>, + 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, + mode: PhantomData, +} + +impl PwmPin { + /// Create a new strongly typed PWM pin + pub fn new( + sys_clk: Hertz, + _pin_and_tim: (Pin, Tim), + initial_frequency: Hertz, + ) -> Result { + if Pin::TIM_ID != Tim::ID { + return Err(TimMissmatchError { + pin_tim: Pin::TIM_ID, + tim_id: Tim::ID, + }); + } + IoPeriphPin::new(Pin::PIN_ID, Pin::FUN_SEL, None); + let mut pin = PwmPin { + tim_id: Tim::ID, + regs: timer::regs::Timer::new_mmio(Tim::ID), + current_duty: 0, + current_lower_limit: 0, + current_period: initial_frequency, + current_rst_val: 0, + sys_clk, + mode: PhantomData, + }; + enable_peripheral_clock(PeripheralSelect::Gpio); + enable_peripheral_clock(PeripheralSelect::Ioconfig); + enable_tim_clk(Tim::ID); + pin.enable_pwm_a(); + pin.set_period(initial_frequency); + Ok(pin) + } + + #[inline] + fn enable_pwm_a(&mut self) { + self.regs.modify_control(|mut value| { + value.set_status_sel(StatusSelect::PwmaOutput); + value + }); + } + + #[inline] + fn enable_pwm_b(&mut self) { + self.regs.modify_control(|mut value| { + value.set_status_sel(StatusSelect::PwmbOutput); + value + }); + } + + #[inline] + pub fn get_period(&self) -> Hertz { + self.current_period + } + + #[inline] + pub fn set_period(&mut self, period: impl Into) { + self.current_period = period.into(); + // Avoid division by 0 + if self.current_period.raw() == 0 { + return; + } + self.current_rst_val = self.sys_clk.raw() / self.current_period.raw(); + self.regs.write_reset_value(self.current_rst_val); + } + + #[inline] + pub fn disable(&mut self) { + self.regs.write_enable_control(EnableControl::new_disable()); + } + + #[inline] + pub fn enable(&mut self) { + self.regs.write_enable_control(EnableControl::new_enable()); + } + + #[inline] + pub fn period(&self) -> Hertz { + self.current_period + } + + #[inline(always)] + pub fn duty(&self) -> u16 { + self.current_duty + } +} + +impl From> for PwmPin { + fn from(other: PwmPin) -> Self { + let mut pwmb = Self { + mode: PhantomData, + regs: other.regs, + tim_id: other.tim_id, + sys_clk: other.sys_clk, + current_duty: other.current_duty, + current_lower_limit: other.current_lower_limit, + current_period: other.current_period, + current_rst_val: other.current_rst_val, + }; + pwmb.enable_pwm_b(); + pwmb + } +} + +impl From> for PwmPin { + fn from(other: PwmPin) -> Self { + let mut pwmb = Self { + mode: PhantomData, + tim_id: other.tim_id, + regs: other.regs, + sys_clk: other.sys_clk, + current_duty: other.current_duty, + current_lower_limit: other.current_lower_limit, + current_period: other.current_period, + current_rst_val: other.current_rst_val, + }; + pwmb.enable_pwm_a(); + pwmb + } +} + +//================================================================================================== +// PWMB implementations +//================================================================================================== + +impl PwmPin { + #[inline(always)] + pub fn pwmb_lower_limit(&self) -> u16 { + self.current_lower_limit + } + + #[inline(always)] + pub fn pwmb_upper_limit(&self) -> u16 { + self.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.current_lower_limit = duty; + let pwmb_val: u64 = + (self.current_rst_val as u64 * self.current_lower_limit as u64) / DUTY_MAX as u64; + self.regs.write_pwmb_value(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.current_duty = duty; + let pwma_val: u64 = + (self.current_rst_val as u64 * self.current_duty as u64) / DUTY_MAX as u64; + self.regs.write_pwma_value(pwma_val as u32); + } +} + +//================================================================================================== +// Embedded HAL implementation: PWMA only +//================================================================================================== + +impl embedded_hal::pwm::ErrorType for PwmPin { + type Error = Infallible; +} + +impl embedded_hal::pwm::SetDutyCycle for PwmPin { + #[inline] + fn max_duty_cycle(&self) -> u16 { + DUTY_MAX + } + + #[inline] + fn set_duty_cycle(&mut self, duty: u16) -> Result<(), Self::Error> { + self.current_duty = duty; + let pwma_val: u64 = (self.current_rst_val as u64 + * (DUTY_MAX as u64 - self.current_duty as u64)) + / DUTY_MAX as u64; + self.regs.write_pwma_value(pwma_val as u32); + Ok(()) + } +} + +/// 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 + } +} diff --git a/vorago-shared-periphs/src/spi/mod.rs b/vorago-shared-periphs/src/spi/mod.rs new file mode 100644 index 0000000..70eae81 --- /dev/null +++ b/vorago-shared-periphs/src/spi/mod.rs @@ -0,0 +1,868 @@ +use crate::gpio::{IoPeriphPin, PinId}; +use crate::FunSel; +use crate::{ + enable_peripheral_clock, pins::PinMarker, sealed::Sealed, time::Hertz, PeripheralSelect, +}; +use core::{convert::Infallible, fmt::Debug, marker::PhantomData}; +use embedded_hal::spi::{Mode, MODE_0}; + +use regs::{ClkPrescaler, Data, FifoClear, WordSize}; +#[cfg(feature = "vor1x")] +use va108xx as pac; +#[cfg(feature = "vor1x")] +pub mod pins_vor1x; + +pub use regs::{Bank, HwChipSelectId}; + +pub mod regs; + +pub fn configure_pin_as_hw_cs_pin(_pin: P) -> HwChipSelectId { + IoPeriphPin::new(P::ID, P::FUN_SEL, None); + P::CS_ID +} + +pub trait PinSck: PinMarker { + const SPI_ID: Bank; + const FUN_SEL: FunSel; +} + +pub trait PinMosi: PinMarker { + const SPI_ID: Bank; + const FUN_SEL: FunSel; +} + +pub trait PinMiso: PinMarker { + const SPI_ID: Bank; + const FUN_SEL: FunSel; +} + +pub trait HwCsProvider { + const PIN_ID: PinId; + const SPI_ID: Bank; + const FUN_SEL: FunSel; + const CS_ID: HwChipSelectId; +} + +//================================================================================================== +// Defintions +//================================================================================================== + +// FIFO has a depth of 16. +const FILL_DEPTH: usize = 12; + +pub const BMSTART_BMSTOP_MASK: u32 = 1 << 31; +pub const BMSKIPDATA_MASK: u32 = 1 << 30; + +pub const DEFAULT_CLK_DIV: u16 = 2; + +/// Common trait implemented by all PAC peripheral access structures. The register block +/// format is the same for all SPI blocks. +pub trait SpiMarker: Sealed { + const ID: Bank; + const PERIPH_SEL: PeripheralSelect; +} + +impl SpiMarker for pac::Spia { + const ID: Bank = Bank::Spi0; + const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Spi0; +} +impl Sealed for pac::Spia {} + +impl SpiMarker for pac::Spib { + const ID: Bank = Bank::Spi1; + const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Spi1; +} +impl Sealed for pac::Spib {} + +impl SpiMarker for pac::Spic { + const ID: Bank = Bank::Spi2; + const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Spi2; +} +impl Sealed for pac::Spic {} + +//================================================================================================== +// Config +//================================================================================================== + +pub trait TransferConfigProvider { + fn sod(&mut self, sod: bool); + fn blockmode(&mut self, blockmode: bool); + fn mode(&mut self, mode: Mode); + fn clk_cfg(&mut self, clk_cfg: SpiClkConfig); + fn hw_cs_id(&self) -> u8; +} + +/// Type erased variant of the transfer configuration. This is required to avoid generics in +/// the SPI constructor. +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct TransferConfig { + pub clk_cfg: Option, + pub mode: Option, + pub sod: bool, + /// If this is enabled, all data in the FIFO is transmitted in a single frame unless + /// the BMSTOP bit is set on a dataword. A frame is defined as CSn being active for the + /// duration of multiple data words + pub blockmode: bool, + /// Only used when blockmode is used. The SCK will be stalled until an explicit stop bit + /// is set on a written word. + pub bmstall: bool, + pub hw_cs: Option, +} + +impl TransferConfig { + pub fn new_with_hw_cs( + clk_cfg: Option, + mode: Option, + blockmode: bool, + bmstall: bool, + sod: bool, + hw_cs_id: HwChipSelectId, + ) -> Self { + TransferConfig { + clk_cfg, + mode, + sod, + blockmode, + bmstall, + hw_cs: Some(hw_cs_id), + } + } +} + +/// Configuration options for the whole SPI bus. See Programmer Guide p.92 for more details +#[derive(Debug, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct SpiConfig { + clk: SpiClkConfig, + // SPI mode configuration + pub init_mode: Mode, + /// If this is enabled, all data in the FIFO is transmitted in a single frame unless + /// the BMSTOP bit is set on a dataword. A frame is defined as CSn being active for the + /// duration of multiple data words. Defaults to true. + pub blockmode: bool, + /// This enables the stalling of the SPI SCK if in blockmode and the FIFO is empty. + /// Currently enabled by default. + pub bmstall: bool, + /// Slave output disable. Useful if separate GPIO pins or decoders are used for CS control + pub slave_output_disable: bool, + /// Loopback mode. If you use this, don't connect MISO to MOSI, they will be tied internally + pub loopback_mode: bool, + /// Enable Master Delayer Capture Mode. See Programmers Guide p.92 for more details + pub master_delayer_capture: bool, +} + +impl Default for SpiConfig { + fn default() -> Self { + Self { + init_mode: MODE_0, + blockmode: true, + bmstall: true, + // Default value is definitely valid. + clk: SpiClkConfig::from_div(DEFAULT_CLK_DIV).unwrap(), + slave_output_disable: Default::default(), + loopback_mode: Default::default(), + master_delayer_capture: Default::default(), + } + } +} + +impl SpiConfig { + pub fn loopback(mut self, enable: bool) -> Self { + self.loopback_mode = enable; + self + } + + pub fn blockmode(mut self, enable: bool) -> Self { + self.blockmode = enable; + self + } + + pub fn bmstall(mut self, enable: bool) -> Self { + self.bmstall = enable; + self + } + + pub fn mode(mut self, mode: Mode) -> Self { + self.init_mode = mode; + self + } + + pub fn clk_cfg(mut self, clk_cfg: SpiClkConfig) -> Self { + self.clk = clk_cfg; + self + } + + pub fn slave_output_disable(mut self, sod: bool) -> Self { + self.slave_output_disable = sod; + self + } +} + +//================================================================================================== +// Word Size +//================================================================================================== + +/// Configuration trait for the Word Size +/// used by the SPI peripheral +pub trait WordProvider: Copy + Default + Into + TryFrom + 'static { + const MASK: u32; + const WORD_SIZE: regs::WordSize; + fn word_reg() -> u8; +} + +impl WordProvider for u8 { + const MASK: u32 = 0xff; + const WORD_SIZE: regs::WordSize = regs::WordSize::EightBits; + fn word_reg() -> u8 { + 0x07 + } +} + +impl WordProvider for u16 { + const MASK: u32 = 0xffff; + const WORD_SIZE: regs::WordSize = regs::WordSize::SixteenBits; + fn word_reg() -> u8 { + 0x0f + } +} + +//================================================================================================== +// Spi +//================================================================================================== + +/// Low level access trait for the SPI peripheral. +pub trait SpiLowLevel { + /// Low level function to write a word to the SPI FIFO but also checks whether + /// there is actually data in the FIFO. + /// + /// Uses the [nb] API to allow usage in blocking and non-blocking contexts. + fn write_fifo(&mut self, data: u32) -> nb::Result<(), Infallible>; + + /// Low level function to write a word to the SPI FIFO without checking whether + /// there FIFO is full. + /// + /// This does not necesarily mean there is a space in the FIFO available. + /// Use [Self::write_fifo] function to write a word into the FIFO reliably. + fn write_fifo_unchecked(&mut self, data: u32); + + /// Low level function to read a word from the SPI FIFO. Must be preceeded by a + /// [Self::write_fifo] call. + /// + /// Uses the [nb] API to allow usage in blocking and non-blocking contexts. + fn read_fifo(&mut self) -> nb::Result; + + /// Low level function to read a word from from the SPI FIFO. + /// + /// This does not necesarily mean there is a word in the FIFO available. + /// Use the [Self::read_fifo] function to read a word from the FIFO reliably using the [nb] + /// API. + /// You might also need to mask the value to ignore the BMSTART/BMSTOP bit. + fn read_fifo_unchecked(&mut self) -> u32; +} + +#[inline(always)] +pub fn mode_to_cpo_cph_bit(mode: embedded_hal::spi::Mode) -> (bool, bool) { + match mode { + embedded_hal::spi::MODE_0 => (false, false), + embedded_hal::spi::MODE_1 => (false, true), + embedded_hal::spi::MODE_2 => (true, false), + embedded_hal::spi::MODE_3 => (true, true), + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct SpiClkConfig { + prescale_val: u8, + scrdv: u8, +} + +impl SpiClkConfig { + pub fn prescale_val(&self) -> u8 { + self.prescale_val + } + pub fn scrdv(&self) -> u8 { + self.scrdv + } +} + +impl SpiClkConfig { + pub fn new(prescale_val: u8, scrdv: u8) -> Self { + Self { + prescale_val, + scrdv, + } + } + + pub fn from_div(div: u16) -> Result { + spi_clk_config_from_div(div) + } + + pub fn from_clk(sys_clk: impl Into, spi_clk: impl Into) -> Option { + clk_div_for_target_clock(sys_clk, spi_clk).map(|div| spi_clk_config_from_div(div).unwrap()) + } +} + +#[derive(Debug, thiserror::Error)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum SpiClkConfigError { + #[error("division by zero")] + DivIsZero, + #[error("divide value is not even")] + DivideValueNotEven, + #[error("scrdv value is too large")] + ScrdvValueTooLarge, +} + +#[inline] +pub fn spi_clk_config_from_div(mut div: u16) -> Result { + if div == 0 { + return Err(SpiClkConfigError::DivIsZero); + } + if div % 2 != 0 { + return Err(SpiClkConfigError::DivideValueNotEven); + } + let mut prescale_val = 0; + + // find largest (even) prescale value that divides into div + for i in (2..=0xfe).rev().step_by(2) { + if div % i == 0 { + prescale_val = i; + break; + } + } + + if prescale_val == 0 { + return Err(SpiClkConfigError::DivideValueNotEven); + } + + div /= prescale_val; + if div > u8::MAX as u16 + 1 { + return Err(SpiClkConfigError::ScrdvValueTooLarge); + } + Ok(SpiClkConfig { + prescale_val: prescale_val as u8, + scrdv: (div - 1) as u8, + }) +} + +#[inline] +pub fn clk_div_for_target_clock( + sys_clk: impl Into, + spi_clk: impl Into, +) -> Option { + let spi_clk = spi_clk.into(); + let sys_clk = sys_clk.into(); + if spi_clk > sys_clk { + return None; + } + + // Step 1: Calculate raw divider. + let raw_div = sys_clk.raw() / spi_clk.raw(); + let remainder = sys_clk.raw() % spi_clk.raw(); + + // Step 2: Round up if necessary. + let mut rounded_div = if remainder * 2 >= spi_clk.raw() { + raw_div + 1 + } else { + raw_div + }; + + if rounded_div % 2 != 0 { + // Take slower clock conservatively. + rounded_div += 1; + } + if rounded_div > u16::MAX as u32 { + return None; + } + Some(rounded_div as u16) +} + +#[derive(Debug, thiserror::Error)] +#[error("peripheral or peripheral pin ID is not consistent")] +pub struct SpiIdMissmatchError; + +/// SPI peripheral driver structure. +pub struct Spi { + id: Bank, + regs: regs::MmioSpi<'static>, + cfg: SpiConfig, + sys_clk: Hertz, + /// Fill word for read-only SPI transactions. + fill_word: Word, + blockmode: bool, + bmstall: bool, + word: PhantomData, +} + +impl Spi +where + >::Error: core::fmt::Debug, +{ + /// Create a new SPI struct. + /// + /// ## Arguments + /// * `sys_clk` - System clock + /// * `spi` - SPI bus to use + /// * `pins` - Pins to be used for SPI transactions. These pins are consumed + /// to ensure the pins can not be used for other purposes anymore + /// * `spi_cfg` - Configuration specific to the SPI bus + pub fn new_for_rom( + sys_clk: Hertz, + spi: SpiI, + spi_cfg: SpiConfig, + ) -> Result { + #[cfg(feature = "vor1x")] + if SpiI::ID != Bank::Spi2 { + return Err(SpiIdMissmatchError); + } + #[cfg(feature = "vor4x")] + if SpiI::ID != Bank::Spi3 { + return Err(SpiIdMissmatchError); + } + Ok(Self::new_generic(sys_clk, spi, spi_cfg)) + } + /// Create a new SPI struct. + /// + /// ## Arguments + /// * `sys_clk` - System clock + /// * `spi` - SPI bus to use + /// * `pins` - Pins to be used for SPI transactions. These pins are consumed + /// to ensure the pins can not be used for other purposes anymore + /// * `spi_cfg` - Configuration specific to the SPI bus + pub fn new( + sys_clk: Hertz, + spi: SpiI, + _pins: (Sck, Miso, Mosi), + spi_cfg: SpiConfig, + ) -> Result { + if SpiI::ID != Sck::SPI_ID || SpiI::ID != Miso::SPI_ID || SpiI::ID != Mosi::SPI_ID { + return Err(SpiIdMissmatchError); + } + IoPeriphPin::new(Sck::ID, Sck::FUN_SEL, None); + IoPeriphPin::new(Miso::ID, Miso::FUN_SEL, None); + IoPeriphPin::new(Mosi::ID, Mosi::FUN_SEL, None); + Ok(Self::new_generic(sys_clk, spi, spi_cfg)) + } + + pub fn new_generic(sys_clk: Hertz, _spi: SpiI, spi_cfg: SpiConfig) -> Self { + enable_peripheral_clock(SpiI::PERIPH_SEL); + let mut regs = regs::Spi::new_mmio(SpiI::ID); + let (cpo_bit, cph_bit) = mode_to_cpo_cph_bit(spi_cfg.init_mode); + regs.write_ctrl0( + regs::Control0::builder() + .with_scrdv(spi_cfg.clk.scrdv) + .with_sph(cph_bit) + .with_spo(cpo_bit) + .with_word_size(Word::WORD_SIZE) + .build(), + ); + regs.write_ctrl1( + regs::Control1::builder() + .with_mtxpause(false) + .with_mdlycap(spi_cfg.master_delayer_capture) + .with_bm_stall(spi_cfg.bmstall) + .with_bm_start(false) + .with_blockmode(spi_cfg.blockmode) + .with_ss(HwChipSelectId::Id0) + .with_sod(spi_cfg.slave_output_disable) + .with_slave_mode(false) + .with_enable(false) + .with_lbm(spi_cfg.loopback_mode) + .build(), + ); + regs.write_clkprescale(ClkPrescaler::new(spi_cfg.clk.prescale_val)); + regs.write_fifo_clear( + FifoClear::builder() + .with_tx_fifo(true) + .with_rx_fifo(true) + .build(), + ); + // Enable the peripheral as the last step as recommended in the + // programmers guide + regs.modify_ctrl1(|mut value| { + value.set_enable(true); + value + }); + Spi { + id: SpiI::ID, + regs: regs::Spi::new_mmio(SpiI::ID), + cfg: spi_cfg, + sys_clk, + fill_word: Default::default(), + bmstall: spi_cfg.bmstall, + blockmode: spi_cfg.blockmode, + word: PhantomData, + } + } + + #[inline] + pub fn cfg_clock(&mut self, cfg: SpiClkConfig) { + self.regs.modify_ctrl0(|mut value| { + value.set_scrdv(cfg.scrdv); + value + }); + self.regs + .write_clkprescale(regs::ClkPrescaler::new(cfg.prescale_val)); + } + + pub fn set_fill_word(&mut self, fill_word: Word) { + self.fill_word = fill_word; + } + + #[inline] + pub fn cfg_clock_from_div(&mut self, div: u16) -> Result<(), SpiClkConfigError> { + let val = spi_clk_config_from_div(div)?; + self.cfg_clock(val); + Ok(()) + } + + #[inline] + pub fn cfg_mode(&mut self, mode: Mode) { + let (cpo_bit, cph_bit) = mode_to_cpo_cph_bit(mode); + self.regs.modify_ctrl0(|mut value| { + value.set_spo(cpo_bit); + value.set_sph(cph_bit); + value + }); + } + + #[inline] + pub fn fill_word(&self) -> Word { + self.fill_word + } + + #[inline] + pub fn clear_tx_fifo(&mut self) { + self.regs.write_fifo_clear( + regs::FifoClear::builder() + .with_tx_fifo(true) + .with_rx_fifo(false) + .build(), + ); + } + + #[inline] + pub fn clear_rx_fifo(&mut self) { + self.regs.write_fifo_clear( + regs::FifoClear::builder() + .with_tx_fifo(false) + .with_rx_fifo(true) + .build(), + ); + } + + #[inline] + pub fn perid(&self) -> u32 { + self.regs.read_perid() + } + + /// Configure the hardware chip select given a hardware chip select ID. + /// + /// The pin also needs to be configured to be used as a HW CS pin. This can be done + /// by using the [configure_pin_as_hw_cs_pin] function which also returns the + /// corresponding [HwChipSelectId]. + #[inline] + pub fn cfg_hw_cs(&mut self, hw_cs: HwChipSelectId) { + self.regs.modify_ctrl1(|mut value| { + value.set_sod(false); + value.set_ss(hw_cs); + value + }); + } + + /// Disables the hardware chip select functionality. This can be used when performing + /// external chip select handling, for example with GPIO pins. + #[inline] + pub fn cfg_hw_cs_disable(&mut self) { + self.regs.modify_ctrl1(|mut value| { + value.set_sod(true); + value + }); + } + + /// Utility function to configure all relevant transfer parameters in one go. + /// This is useful if multiple devices with different clock and mode configurations + /// are connected to one bus. + pub fn cfg_transfer(&mut self, transfer_cfg: &TransferConfig) { + if let Some(trans_clk_div) = transfer_cfg.clk_cfg { + self.cfg_clock(trans_clk_div); + } + if let Some(mode) = transfer_cfg.mode { + self.cfg_mode(mode); + } + self.blockmode = transfer_cfg.blockmode; + self.regs.modify_ctrl1(|mut value| { + if transfer_cfg.sod { + value.set_sod(transfer_cfg.sod); + } else { + value.set_sod(false); + if let Some(hw_cs) = transfer_cfg.hw_cs { + value.set_ss(hw_cs); + } + } + value.set_blockmode(transfer_cfg.blockmode); + value.set_bm_stall(transfer_cfg.bmstall); + value + }); + } + + fn flush_internal(&mut self) { + let mut status_reg = self.regs.read_status(); + while !status_reg.tx_empty() || status_reg.rx_not_empty() || status_reg.busy() { + if status_reg.rx_not_empty() { + self.read_fifo_unchecked(); + } + status_reg = self.regs.read_status(); + } + } + + fn transfer_preparation(&mut self, words: &[Word]) -> Result<(), Infallible> { + if words.is_empty() { + return Ok(()); + } + self.flush_internal(); + Ok(()) + } + + // The FIFO can hold a guaranteed amount of data, so we can pump it on transfer + // initialization. Returns the amount of written bytes. + fn initial_send_fifo_pumping_with_words(&mut self, words: &[Word]) -> usize { + //let reg_block = self.reg_block(); + if self.blockmode { + self.regs.modify_ctrl1(|mut value| { + value.set_mtxpause(true); + value + }); + } + // Fill the first half of the write FIFO + let mut current_write_idx = 0; + let smaller_idx = core::cmp::min(FILL_DEPTH, words.len()); + for _ in 0..smaller_idx { + if current_write_idx == smaller_idx.saturating_sub(1) && self.bmstall { + self.write_fifo_unchecked(words[current_write_idx].into() | BMSTART_BMSTOP_MASK); + } else { + self.write_fifo_unchecked(words[current_write_idx].into()); + } + current_write_idx += 1; + } + if self.blockmode { + self.regs.modify_ctrl1(|mut value| { + value.set_mtxpause(false); + value + }); + } + current_write_idx + } + + // The FIFO can hold a guaranteed amount of data, so we can pump it on transfer + // initialization. + fn initial_send_fifo_pumping_with_fill_words(&mut self, send_len: usize) -> usize { + if self.blockmode { + self.regs.modify_ctrl1(|mut value| { + value.set_mtxpause(true); + value + }); + } + // Fill the first half of the write FIFO + let mut current_write_idx = 0; + let smaller_idx = core::cmp::min(FILL_DEPTH, send_len); + for _ in 0..smaller_idx { + if current_write_idx == smaller_idx.saturating_sub(1) && self.bmstall { + self.write_fifo_unchecked(self.fill_word.into() | BMSTART_BMSTOP_MASK); + } else { + self.write_fifo_unchecked(self.fill_word.into()); + } + current_write_idx += 1; + } + if self.blockmode { + self.regs.modify_ctrl1(|mut value| { + value.set_mtxpause(false); + value + }); + } + current_write_idx + } +} + +impl SpiLowLevel for Spi +where + >::Error: core::fmt::Debug, +{ + #[inline(always)] + fn write_fifo(&mut self, data: u32) -> nb::Result<(), Infallible> { + if !self.regs.read_status().tx_not_full() { + return Err(nb::Error::WouldBlock); + } + self.write_fifo_unchecked(data); + Ok(()) + } + + #[inline(always)] + fn write_fifo_unchecked(&mut self, data: u32) { + self.regs.write_data(Data::new_with_raw_value(data)); + } + + #[inline(always)] + fn read_fifo(&mut self) -> nb::Result { + if !self.regs.read_status().rx_not_empty() { + return Err(nb::Error::WouldBlock); + } + Ok(self.read_fifo_unchecked()) + } + + #[inline(always)] + fn read_fifo_unchecked(&mut self) -> u32 { + self.regs.read_data().raw_value() + } +} + +impl embedded_hal::spi::ErrorType for Spi { + type Error = Infallible; +} + +impl embedded_hal::spi::SpiBus for Spi +where + >::Error: core::fmt::Debug, +{ + fn read(&mut self, words: &mut [Word]) -> Result<(), Self::Error> { + self.transfer_preparation(words)?; + let mut current_read_idx = 0; + let mut current_write_idx = self.initial_send_fifo_pumping_with_fill_words(words.len()); + loop { + if current_read_idx < words.len() { + words[current_read_idx] = (nb::block!(self.read_fifo())? & Word::MASK) + .try_into() + .unwrap(); + current_read_idx += 1; + } + if current_write_idx < words.len() { + if current_write_idx == words.len() - 1 && self.bmstall { + nb::block!(self.write_fifo(self.fill_word.into() | BMSTART_BMSTOP_MASK))?; + } else { + nb::block!(self.write_fifo(self.fill_word.into()))?; + } + current_write_idx += 1; + } + if current_read_idx >= words.len() && current_write_idx >= words.len() { + break; + } + } + Ok(()) + } + + fn write(&mut self, words: &[Word]) -> Result<(), Self::Error> { + self.transfer_preparation(words)?; + let mut current_write_idx = self.initial_send_fifo_pumping_with_words(words); + while current_write_idx < words.len() { + if current_write_idx == words.len() - 1 && self.bmstall { + nb::block!(self.write_fifo(words[current_write_idx].into() | BMSTART_BMSTOP_MASK))?; + } else { + nb::block!(self.write_fifo(words[current_write_idx].into()))?; + } + current_write_idx += 1; + // Ignore received words. + if self.regs.read_status().rx_not_empty() { + self.clear_rx_fifo(); + } + } + Ok(()) + } + + fn transfer(&mut self, read: &mut [Word], write: &[Word]) -> Result<(), Self::Error> { + self.transfer_preparation(write)?; + let mut current_read_idx = 0; + let mut current_write_idx = self.initial_send_fifo_pumping_with_words(write); + while current_read_idx < read.len() || current_write_idx < write.len() { + if current_write_idx < write.len() { + if current_write_idx == write.len() - 1 && self.bmstall { + nb::block!( + self.write_fifo(write[current_write_idx].into() | BMSTART_BMSTOP_MASK) + )?; + } else { + nb::block!(self.write_fifo(write[current_write_idx].into()))?; + } + current_write_idx += 1; + } + if current_read_idx < read.len() { + read[current_read_idx] = (nb::block!(self.read_fifo())? & Word::MASK) + .try_into() + .unwrap(); + current_read_idx += 1; + } + } + + Ok(()) + } + + fn transfer_in_place(&mut self, words: &mut [Word]) -> Result<(), Self::Error> { + self.transfer_preparation(words)?; + let mut current_read_idx = 0; + let mut current_write_idx = self.initial_send_fifo_pumping_with_words(words); + + while current_read_idx < words.len() || current_write_idx < words.len() { + if current_write_idx < words.len() { + if current_write_idx == words.len() - 1 && self.bmstall { + nb::block!( + self.write_fifo(words[current_write_idx].into() | BMSTART_BMSTOP_MASK) + )?; + } else { + nb::block!(self.write_fifo(words[current_write_idx].into()))?; + } + current_write_idx += 1; + } + if current_read_idx < words.len() && current_read_idx < current_write_idx { + words[current_read_idx] = (nb::block!(self.read_fifo())? & Word::MASK) + .try_into() + .unwrap(); + current_read_idx += 1; + } + } + Ok(()) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + self.flush_internal(); + Ok(()) + } +} + +/// Changing the word size also requires a type conversion +impl From> for Spi { + fn from(mut old_spi: Spi) -> Self { + old_spi.regs.modify_ctrl0(|mut value| { + value.set_word_size(WordSize::SixteenBits); + value + }); + Spi { + id: old_spi.id, + regs: old_spi.regs, + cfg: old_spi.cfg, + blockmode: old_spi.blockmode, + fill_word: Default::default(), + bmstall: old_spi.bmstall, + sys_clk: old_spi.sys_clk, + word: PhantomData, + } + } +} + +impl From> for Spi { + fn from(mut old_spi: Spi) -> Self { + old_spi.regs.modify_ctrl0(|mut value| { + value.set_word_size(WordSize::EightBits); + value + }); + Spi { + id: old_spi.id, + regs: old_spi.regs, + cfg: old_spi.cfg, + blockmode: old_spi.blockmode, + fill_word: Default::default(), + bmstall: old_spi.bmstall, + sys_clk: old_spi.sys_clk, + word: PhantomData, + } + } +} diff --git a/vorago-shared-periphs/src/spi/pins_vor1x.rs b/vorago-shared-periphs/src/spi/pins_vor1x.rs new file mode 100644 index 0000000..2bb8cef --- /dev/null +++ b/vorago-shared-periphs/src/spi/pins_vor1x.rs @@ -0,0 +1,355 @@ +use super::{HwCsProvider, PinMiso, PinMosi, PinSck}; +use crate::FunSel; +use crate::gpio::{PinId, PinIdProvider}; + +use crate::{ + pins::{ + Pa10, Pa11, Pa12, Pa13, Pa14, Pa15, Pa16, Pa17, Pa18, Pa19, Pa20, Pa21, Pa22, Pa23, Pa24, + Pa25, Pa26, Pa27, Pa28, Pa29, Pa30, Pa31, Pb0, Pb1, Pb2, Pb3, Pb4, Pb5, Pb6, Pb7, Pb8, Pb9, + Pb10, Pb11, Pb12, Pb13, Pb14, Pb15, Pb16, Pb17, Pb18, Pb19, Pb22, Pb23, Pin, + }, + sealed::Sealed, +}; + +use super::{Bank, HwChipSelectId}; + +macro_rules! hw_cs_pins { + ($SpiId:path, $(($Px:ident, $FunSel:path, $HwCsIdent:path),)+) => { + $( + impl HwCsProvider for Pin<$Px> { + const PIN_ID: PinId = $Px::ID; + const SPI_ID: Bank = $SpiId; + const FUN_SEL: FunSel = $FunSel; + const CS_ID: HwChipSelectId = $HwCsIdent; + } + )+ + }; +} +macro_rules! hw_cs_multi_pin { + ( + // name of the newtype wrapper struct + $name:ident, + // Pb0 + $pin_id:ident, + // SpiId::B + $spi_id:path, + // FunSel::Sel1 + $fun_sel:path, + // HwChipSelectId::Id2 + $cs_id:path + ) => { + #[doc = concat!( + "Newtype wrapper to use [Pin] [`", stringify!($pin_id), + "`] as a HW CS pin for [`", stringify!($spi_id), + "`] with [`", stringify!($cs_id), "`]." + )] + pub struct $name(Pin<$pin_id>); + + impl $name { + pub fn new(pin: Pin<$pin_id>) -> Self { + Self(pin) + } + } + + impl Sealed for $name {} + + impl HwCsProvider for $name { + const PIN_ID: PinId = <$pin_id as PinIdProvider>::ID; + const SPI_ID: Bank = $spi_id; + const FUN_SEL: FunSel = $fun_sel; + const CS_ID: HwChipSelectId = $cs_id; + } + }; +} + +// SPIA + +impl PinSck for Pin { + const SPI_ID: Bank = Bank::Spi0; + const FUN_SEL: FunSel = FunSel::Sel1; +} +impl PinMosi for Pin { + const SPI_ID: Bank = Bank::Spi0; + const FUN_SEL: FunSel = FunSel::Sel1; +} +impl PinMiso for Pin { + const SPI_ID: Bank = Bank::Spi0; + const FUN_SEL: FunSel = FunSel::Sel1; +} + +impl PinSck for Pin { + const SPI_ID: Bank = Bank::Spi0; + const FUN_SEL: FunSel = FunSel::Sel2; +} +impl PinMosi for Pin { + const SPI_ID: Bank = Bank::Spi0; + const FUN_SEL: FunSel = FunSel::Sel2; +} +impl PinMiso for Pin { + const SPI_ID: Bank = Bank::Spi0; + const FUN_SEL: FunSel = FunSel::Sel2; +} + +hw_cs_pins!( + Bank::Spi0, + (Pb0, FunSel::Sel2, HwChipSelectId::Id1), + (Pb1, FunSel::Sel2, HwChipSelectId::Id2), + (Pb2, FunSel::Sel2, HwChipSelectId::Id3), + (Pb3, FunSel::Sel2, HwChipSelectId::Id4), + (Pb4, FunSel::Sel2, HwChipSelectId::Id5), + (Pb5, FunSel::Sel2, HwChipSelectId::Id6), + (Pb6, FunSel::Sel2, HwChipSelectId::Id0), + (Pa24, FunSel::Sel1, HwChipSelectId::Id4), + (Pa25, FunSel::Sel1, HwChipSelectId::Id3), + (Pa26, FunSel::Sel1, HwChipSelectId::Id2), + (Pa27, FunSel::Sel1, HwChipSelectId::Id1), + (Pa28, FunSel::Sel1, HwChipSelectId::Id0), +); + +hw_cs_multi_pin!( + PinPb0SpiaHwCsId1, + Pb0, + Bank::Spi0, + FunSel::Sel2, + HwChipSelectId::Id1 +); +hw_cs_multi_pin!( + PinPb1SpiaHwCsId2, + Pb1, + Bank::Spi0, + FunSel::Sel2, + HwChipSelectId::Id2 +); +hw_cs_multi_pin!( + PinPb2SpiaHwCsId3, + Pb2, + Bank::Spi0, + FunSel::Sel2, + HwChipSelectId::Id3 +); + +hw_cs_multi_pin!( + PinPa21SpiaHwCsId7, + Pa21, + Bank::Spi0, + FunSel::Sel1, + HwChipSelectId::Id7 +); +hw_cs_multi_pin!( + PinPa22SpiaHwCsId6, + Pa22, + Bank::Spi0, + FunSel::Sel1, + HwChipSelectId::Id6 +); +hw_cs_multi_pin!( + PinPa23SpiaHwCsId5, + Pa23, + Bank::Spi0, + FunSel::Sel1, + HwChipSelectId::Id5 +); + +// SPIB + +impl PinSck for Pin { + const SPI_ID: Bank = Bank::Spi1; + const FUN_SEL: FunSel = FunSel::Sel2; +} +impl PinMosi for Pin { + const SPI_ID: Bank = Bank::Spi1; + const FUN_SEL: FunSel = FunSel::Sel2; +} +impl PinMiso for Pin { + const SPI_ID: Bank = Bank::Spi1; + const FUN_SEL: FunSel = FunSel::Sel2; +} + +pub type SpiBPortASck = Pin; +pub type SpiBPortAMosi = Pin; +pub type SpiBPortAMiso = Pin; + +impl PinSck for Pin { + const SPI_ID: Bank = Bank::Spi1; + const FUN_SEL: FunSel = FunSel::Sel1; +} +impl PinMosi for Pin { + const SPI_ID: Bank = Bank::Spi1; + const FUN_SEL: FunSel = FunSel::Sel1; +} +impl PinMiso for Pin { + const SPI_ID: Bank = Bank::Spi1; + const FUN_SEL: FunSel = FunSel::Sel1; +} + +impl PinSck for Pin { + const SPI_ID: Bank = Bank::Spi1; + const FUN_SEL: FunSel = FunSel::Sel1; +} +impl PinMosi for Pin { + const SPI_ID: Bank = Bank::Spi1; + const FUN_SEL: FunSel = FunSel::Sel1; +} +impl PinMiso for Pin { + const SPI_ID: Bank = Bank::Spi1; + const FUN_SEL: FunSel = FunSel::Sel1; +} + +// TODO: Need to deal with these duplications.. +hw_cs_pins!( + Bank::Spi1, + (Pb16, FunSel::Sel1, HwChipSelectId::Id0), + (Pb15, FunSel::Sel1, HwChipSelectId::Id1), + (Pb14, FunSel::Sel1, HwChipSelectId::Id2), + (Pb13, FunSel::Sel1, HwChipSelectId::Id3), + (Pa17, FunSel::Sel2, HwChipSelectId::Id0), + (Pa16, FunSel::Sel2, HwChipSelectId::Id1), + (Pa15, FunSel::Sel2, HwChipSelectId::Id2), + (Pa14, FunSel::Sel2, HwChipSelectId::Id3), + (Pa13, FunSel::Sel2, HwChipSelectId::Id4), + (Pa12, FunSel::Sel2, HwChipSelectId::Id5), + (Pa11, FunSel::Sel2, HwChipSelectId::Id6), + (Pa10, FunSel::Sel2, HwChipSelectId::Id7), + (Pa23, FunSel::Sel2, HwChipSelectId::Id5), + (Pa22, FunSel::Sel2, HwChipSelectId::Id6), + (Pa21, FunSel::Sel2, HwChipSelectId::Id7), +); + +hw_cs_multi_pin!( + PinPb0SpibHwCsId2, + Pb0, + Bank::Spi1, + FunSel::Sel1, + HwChipSelectId::Id2 +); +hw_cs_multi_pin!( + PinPb1SpibHwCsId1, + Pb1, + Bank::Spi1, + FunSel::Sel1, + HwChipSelectId::Id1 +); +hw_cs_multi_pin!( + PinPb2SpibHwCsId0, + Pb2, + Bank::Spi1, + FunSel::Sel1, + HwChipSelectId::Id0 +); + +hw_cs_multi_pin!( + PinPb10SpibHwCsId6, + Pb10, + Bank::Spi1, + FunSel::Sel1, + HwChipSelectId::Id6 +); +hw_cs_multi_pin!( + PinPb11SpibHwCsId5, + Pb11, + Bank::Spi1, + FunSel::Sel1, + HwChipSelectId::Id5 +); +hw_cs_multi_pin!( + PinPb12SpibHwCsId4, + Pb12, + Bank::Spi1, + FunSel::Sel1, + HwChipSelectId::Id4 +); + +hw_cs_multi_pin!( + PinPb10SpibHwCsId2, + Pb10, + Bank::Spi1, + FunSel::Sel2, + HwChipSelectId::Id2 +); +hw_cs_multi_pin!( + PinPb11SpibHwCsId1, + Pb11, + Bank::Spi1, + FunSel::Sel2, + HwChipSelectId::Id1 +); +hw_cs_multi_pin!( + PinPb12SpibHwCsId0, + Pb12, + Bank::Spi1, + FunSel::Sel2, + HwChipSelectId::Id0 +); + +hw_cs_multi_pin!( + PinPa21SpibHwCsId7, + Pa21, + Bank::Spi1, + FunSel::Sel2, + HwChipSelectId::Id7 +); +hw_cs_multi_pin!( + PinPa22SpibHwCsId6, + Pa22, + Bank::Spi1, + FunSel::Sel2, + HwChipSelectId::Id6 +); +hw_cs_multi_pin!( + PinPa23SpibHwCsId5, + Pa23, + Bank::Spi1, + FunSel::Sel2, + HwChipSelectId::Id5 +); + +// SPIC + +hw_cs_pins!( + Bank::Spi2, + (Pb9, FunSel::Sel3, HwChipSelectId::Id1), + (Pb8, FunSel::Sel3, HwChipSelectId::Id2), + (Pb7, FunSel::Sel3, HwChipSelectId::Id3), + (Pb23, FunSel::Sel3, HwChipSelectId::Id2), + (Pb22, FunSel::Sel3, HwChipSelectId::Id1), + (Pa20, FunSel::Sel1, HwChipSelectId::Id1), + (Pa19, FunSel::Sel1, HwChipSelectId::Id2), + (Pb18, FunSel::Sel1, HwChipSelectId::Id3), +); + +hw_cs_multi_pin!( + PinPa21SpicHwCsId3, + Pa21, + Bank::Spi2, + FunSel::Sel3, + HwChipSelectId::Id3 +); +hw_cs_multi_pin!( + PinPa22SpicHwCsId2, + Pa22, + Bank::Spi2, + FunSel::Sel3, + HwChipSelectId::Id2 +); +hw_cs_multi_pin!( + PinPa23SpicHwCsId1, + Pa23, + Bank::Spi2, + FunSel::Sel3, + HwChipSelectId::Id1 +); + +hw_cs_multi_pin!( + PinPa20SpicHwCsId1, + Pa20, + Bank::Spi2, + FunSel::Sel1, + HwChipSelectId::Id1 +); +hw_cs_multi_pin!( + PinPa20SpicHwCsId4, + Pa20, + Bank::Spi2, + FunSel::Sel3, + HwChipSelectId::Id4 +); diff --git a/vorago-shared-periphs/src/spi/regs.rs b/vorago-shared-periphs/src/spi/regs.rs new file mode 100644 index 0000000..1ca5fb0 --- /dev/null +++ b/vorago-shared-periphs/src/spi/regs.rs @@ -0,0 +1,279 @@ +use core::marker::PhantomData; + +pub use crate::shared::{FifoClear, TriggerLevel}; + +cfg_if::cfg_if! { + if #[cfg(feature = "vor1x")] { + /// SPI A base address + pub const BASE_ADDR_0: usize = 0x4005_0000; + /// SPI B base address + pub const BASE_ADDR_1: usize = 0x4005_1000; + /// SPI C base address + pub const BASE_ADDR_2: usize = 0x4005_2000; + } else if #[cfg(feature = "vor4x")] { + /// SPI 0 base address + pub const BASE_ADDR_0: usize = 0x4001_5000; + /// SPI 1 base address + pub const BASE_ADDR_1: usize = 0x4001_5400; + /// SPI 2 base address + pub const BASE_ADDR_2: usize = 0x4001_5800; + /// SPI 3 base address + pub const BASE_ADDR_3: usize = 0x4001_5C00; + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Bank { + Spi0, + Spi1, + Spi2, + #[cfg(feature = "vor4x")] + Spi3, +} + +impl Bank { + /// Unsafely steal the SPI peripheral block for the given port. + /// + /// # Safety + /// + /// Circumvents ownership and safety guarantees by the HAL. + pub unsafe fn steal_regs(&self) -> MmioSpi<'static> { + Spi::new_mmio(*self) + } +} + +#[bitbybit::bitenum(u4)] +#[derive(Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum WordSize { + OneBit = 0x00, + FourBits = 0x03, + EightBits = 0x07, + SixteenBits = 0x0f, +} + +#[derive(Debug, PartialEq, Eq)] +#[bitbybit::bitenum(u3, exhaustive = true)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum HwChipSelectId { + Id0 = 0, + Id1 = 1, + Id2 = 2, + Id3 = 3, + Id4 = 4, + Id5 = 5, + Id6 = 6, + Id7 = 7, +} + +#[bitbybit::bitfield(u32, default = 0x0)] +#[derive(Debug)] +pub struct Control0 { + #[bits(8..=15, rw)] + scrdv: u8, + #[bit(7, rw)] + sph: bool, + #[bit(6, rw)] + spo: bool, + #[bits(0..=3, rw)] + word_size: Option, +} + +#[bitbybit::bitfield(u32, default = 0x0)] +#[derive(Debug)] +pub struct Control1 { + #[bit(11, rw)] + mtxpause: bool, + #[bit(10, rw)] + mdlycap: bool, + #[bit(9, rw)] + bm_stall: bool, + #[bit(8, rw)] + bm_start: bool, + #[bit(7, rw)] + blockmode: bool, + #[bits(4..=6, rw)] + ss: HwChipSelectId, + #[bit(3, rw)] + sod: bool, + #[bit(2, rw)] + slave_mode: bool, + #[bit(1, rw)] + enable: bool, + #[bit(0, rw)] + lbm: bool, +} + +#[bitbybit::bitfield(u32)] +#[derive(Debug)] +pub struct Data { + /// Only used for BLOCKMODE. For received data, this bit indicated that the data was the first + /// word after the chip select went active. For transmitted data, setting this bit to 1 + /// will end an SPI frame (deassert CS) after the specified data word. + #[bit(31, rw)] + bm_start_stop: bool, + /// Only used for BLOCKMODE. Setting this bit to 1 along with the BMSTOP bit will end an SPI + /// frame without any additional data to be transmitted. If BMSTOP is not set, this bit is + /// ignored. + #[bit(30, rw)] + bm_skipdata: bool, + #[bits(0..=15, rw)] + data: u16, +} + +#[bitbybit::bitfield(u32)] +#[derive(Debug)] +pub struct Status { + /// TX FIFO below the trigger level. + #[bit(7, r)] + tx_trigger: bool, + /// RX FIFO above or equals the trigger level. + #[bit(6, r)] + rx_trigger: bool, + #[bit(5, r)] + rx_data_first: bool, + #[bit(4, r)] + busy: bool, + #[bit(3, r)] + rx_full: bool, + #[bit(2, r)] + rx_not_empty: bool, + #[bit(1, r)] + tx_not_full: bool, + #[bit(0, r)] + tx_empty: bool, +} + +/// Clock divisor value. Bit 0 is ignored and always 0. This means that only the even values +/// are used as clock divisor values, and uneven values are truncated to the next even value. +/// A value of 0 acts as a 1 for the divisor value. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct ClkPrescaler(arbitrary_int::UInt); + +impl ClkPrescaler { + pub const fn new(value: u8) -> Self { + ClkPrescaler(arbitrary_int::UInt::::new(value as u32)) + } + pub const fn value(&self) -> u8 { + self.0.value() as u8 + } +} + +#[bitbybit::bitfield(u32)] +#[derive(Debug)] +pub struct InterruptControl { + /// TX FIFO count <= TX FIFO trigger level. + #[bit(3, rw)] + tx: bool, + /// RX FIFO count >= RX FIFO trigger level. + #[bit(2, rw)] + rx: bool, + /// Occurs when the RX FIFO has not been read within 32 clock ticks of the SPICLKx2 clock + /// within the RX FIFO not being empty. Clearing the RX interrupt or reading data from the + /// FIFO resets the timeout counter. + #[bit(1, rw)] + rx_timeout: bool, + #[bit(0, rw)] + rx_overrun: bool, +} + +#[bitbybit::bitfield(u32)] +#[derive(Debug)] +pub struct InterruptStatus { + /// TX FIFO count <= TX FIFO trigger level. + #[bit(3, r)] + tx: bool, + /// RX FIFO count >= RX FIFO trigger level. + #[bit(2, r)] + rx: bool, + /// Occurs when the RX FIFO has not been read within 32 clock ticks of the SPICLKx2 clock + /// within the RX FIFO not being empty. Clearing the RX interrupt or reading data from the + /// FIFO resets the timeout counter. + #[bit(1, r)] + rx_timeout: bool, + #[bit(0, r)] + rx_overrun: bool, +} + +#[bitbybit::bitfield(u32)] +#[derive(Debug)] +pub struct InterruptClear { + /// Clearing the RX interrupt or reading data from the FIFO resets the timeout counter. + #[bit(1, w)] + rx_timeout: bool, + #[bit(0, w)] + rx_overrun: bool, +} + +#[bitbybit::bitfield(u32)] +#[derive(Debug)] +pub struct State { + #[bits(0..=7, r)] + rx_state: u8, + #[bits(8..=15, r)] + rx_fifo: u8, + #[bits(24..=31, r)] + tx_fifo: u8, +} + +#[derive(derive_mmio::Mmio)] +#[mmio(no_ctors)] +#[repr(C)] +pub struct Spi { + ctrl0: Control0, + ctrl1: Control1, + data: Data, + #[mmio(PureRead)] + status: Status, + clkprescale: ClkPrescaler, + irq_enb: InterruptControl, + /// Raw interrupt status. + #[mmio(PureRead)] + irq_raw: InterruptStatus, + /// Enabled interrupt status. + #[mmio(PureRead)] + irq_status: InterruptStatus, + #[mmio(Write)] + irq_clear: InterruptClear, + rx_fifo_trigger: TriggerLevel, + tx_fifo_trigger: TriggerLevel, + #[mmio(Write)] + fifo_clear: FifoClear, + #[mmio(PureRead)] + state: u32, + #[cfg(feature = "vor1x")] + _reserved: [u32; 0x3F2], + #[cfg(feature = "vor4x")] + _reserved: [u32; 0xF2], + /// Vorago 1x: 0x0113_07E1. Vorago 4x: 0x0213_07E9. + #[mmio(PureRead)] + perid: u32, +} + +cfg_if::cfg_if! { + if #[cfg(feature = "vor1x")] { + static_assertions::const_assert_eq!(core::mem::size_of::(), 0x1000); + } else if #[cfg(feature = "vor4x")] { + static_assertions::const_assert_eq!(core::mem::size_of::(), 0x400); + } +} + +impl Spi { + fn new_mmio_at(base: usize) -> MmioSpi<'static> { + MmioSpi { + ptr: base as *mut _, + phantom: PhantomData, + } + } + + pub fn new_mmio(bank: Bank) -> MmioSpi<'static> { + match bank { + Bank::Spi0 => Self::new_mmio_at(BASE_ADDR_0), + Bank::Spi1 => Self::new_mmio_at(BASE_ADDR_1), + Bank::Spi2 => Self::new_mmio_at(BASE_ADDR_2), + #[cfg(feature = "vor4x")] + Bank::Spi3 => Self::new_mmio_at(BASE_ADDR_2), + } + } +} diff --git a/vorago-shared-periphs/src/sysconfig.rs b/vorago-shared-periphs/src/sysconfig.rs new file mode 100644 index 0000000..02054b9 --- /dev/null +++ b/vorago-shared-periphs/src/sysconfig.rs @@ -0,0 +1,43 @@ +#[cfg(feature = "vor1x")] +#[inline] +pub fn enable_peripheral_clock(clock: crate::PeripheralSelect) { + let syscfg = unsafe { va108xx::Sysconfig::steal() }; + syscfg + .peripheral_clk_enable() + .modify(|r, w| unsafe { w.bits(r.bits() | (1 << clock as u8)) }); +} + +#[cfg(feature = "vor1x")] +#[inline] +pub fn disable_peripheral_clock(clock: crate::PeripheralSelect) { + let syscfg = unsafe { va108xx::Sysconfig::steal() }; + syscfg + .peripheral_clk_enable() + .modify(|r, w| unsafe { w.bits(r.bits() & !(1 << clock as u8)) }); +} + +#[cfg(feature = "vor1x")] +#[inline] +pub fn assert_peripheral_reset(periph_sel: crate::PeripheralSelect) { + let syscfg = unsafe { va108xx::Sysconfig::steal() }; + syscfg + .peripheral_reset() + .modify(|r, w| unsafe { w.bits(r.bits() & !(1 << periph_sel as u8)) }); +} + +#[cfg(feature = "vor1x")] +#[inline] +pub fn deassert_peripheral_reset(periph_sel: crate::PeripheralSelect) { + let syscfg = unsafe { va108xx::Sysconfig::steal() }; + syscfg + .peripheral_reset() + .modify(|r, w| unsafe { w.bits(r.bits() | (1 << periph_sel as u8)) }); +} + +#[cfg(feature = "vor1x")] +#[inline] +pub fn reset_peripheral_for_cycles(periph_sel: crate::PeripheralSelect, cycles: u32) { + assert_peripheral_reset(periph_sel); + cortex_m::asm::delay(cycles); + deassert_peripheral_reset(periph_sel); +} diff --git a/vorago-shared-periphs/src/time.rs b/vorago-shared-periphs/src/time.rs new file mode 100644 index 0000000..9808028 --- /dev/null +++ b/vorago-shared-periphs/src/time.rs @@ -0,0 +1,26 @@ +//! 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; diff --git a/vorago-shared-periphs/src/timer/mod.rs b/vorago-shared-periphs/src/timer/mod.rs new file mode 100644 index 0000000..d826708 --- /dev/null +++ b/vorago-shared-periphs/src/timer/mod.rs @@ -0,0 +1,466 @@ +pub mod regs; + +use core::convert::Infallible; + +#[cfg(feature = "vor1x")] +pub use crate::InterruptConfig; +pub use regs::{CascadeSource, InvalidTimerIndex, TimId}; + +use crate::{ + PeripheralSelect, + gpio::{Pin, PinId, PinIdProvider}, + ioconfig::regs::FunSel, + pins::PinMarker, + sysconfig::enable_peripheral_clock, +}; +use crate::{ + enable_nvic_interrupt, + pins::{ + Pa0, Pa1, Pa2, Pa3, Pa4, Pa5, Pa6, Pa7, Pa8, Pa9, Pa10, Pa11, Pa12, Pa13, Pa14, Pa15, Pa24, + Pa25, Pa26, Pa27, Pa28, Pa29, Pa30, Pa31, Pb0, Pb1, Pb2, Pb3, Pb4, Pb5, Pb6, Pb10, Pb11, + Pb12, Pb13, Pb14, Pb15, Pb16, Pb17, Pb18, Pb19, Pb20, Pb21, Pb22, Pb23, + }, + sealed::Sealed, + time::Hertz, +}; +use fugit::RateExtU32; + +#[cfg(feature = "vor1x")] +use va108xx as pac; +#[cfg(feature = "vor4x")] +use va416xx as pac; + +//================================================================================================== +// Defintions +//================================================================================================== + +#[derive(Default, Debug, PartialEq, Eq, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CascadeControl { + /// Enable Cascade 0 signal active as a requirement for counting + pub enable_src_0: bool, + /// Invert Cascade 0, making it active low + pub inv_src_0: regs::CascadeInvert, + /// Enable Cascade 1 signal active as a requirement for counting + pub enable_src_1: bool, + /// Invert Cascade 1, making it active low + pub inv_src_1: regs::CascadeInvert, + /// 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_operation: regs::DualCascadeOp, + /// 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 trigger_mode_0: bool, + /// Trigger mode, identical to [Self::trigger_mode_0] but for Cascade 1 + pub trigger_mode_1: 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 enable_stop_src_2: bool, + /// Invert Cascade 2, making it active low + pub inv_src_2: regs::CascadeInvert, + /// 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 trigger_mode_2: bool, +} + +#[derive(Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum CascadeSelect { + Csd0 = 0, + Csd1 = 1, + Csd2 = 2, +} + +//================================================================================================== +// Valid TIM and PIN combinations +//================================================================================================== + +pub trait TimPin: PinMarker { + const PIN_ID: PinId; + const FUN_SEL: FunSel; + const TIM_ID: TimId; +} + +pub trait TimMarker: Sealed { + // TIM ID ranging from 0 to 23 for 24 TIM peripherals + const ID: TimId; +} + +macro_rules! tim_marker { + ($TIMX:path, $ID:expr) => { + impl TimMarker for $TIMX { + const ID: TimId = TimId::new_unchecked($ID); + } + + impl Sealed for $TIMX {} + }; +} + +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: Sealed {} + +macro_rules! pin_and_tim { + ($Px:ident, $FunSel:path, $ID:expr) => { + impl TimPin for Pin<$Px> + where + $Px: PinIdProvider, + { + const PIN_ID: PinId = $Px::ID; + const FUN_SEL: FunSel = $FunSel; + const TIM_ID: TimId = TimId::new_unchecked($ID); + } + }; +} + +pin_and_tim!(Pa0, FunSel::Sel1, 0); +pin_and_tim!(Pa1, FunSel::Sel1, 1); +pin_and_tim!(Pa2, FunSel::Sel1, 2); +pin_and_tim!(Pa3, FunSel::Sel1, 3); +pin_and_tim!(Pa4, FunSel::Sel1, 4); +pin_and_tim!(Pa5, FunSel::Sel1, 5); +pin_and_tim!(Pa6, FunSel::Sel1, 6); +pin_and_tim!(Pa7, FunSel::Sel1, 7); +pin_and_tim!(Pa8, FunSel::Sel1, 8); +pin_and_tim!(Pa9, FunSel::Sel1, 9); +pin_and_tim!(Pa10, FunSel::Sel1, 10); +pin_and_tim!(Pa11, FunSel::Sel1, 11); +pin_and_tim!(Pa12, FunSel::Sel1, 12); +pin_and_tim!(Pa13, FunSel::Sel1, 13); +pin_and_tim!(Pa14, FunSel::Sel1, 14); +pin_and_tim!(Pa15, FunSel::Sel1, 15); + +pin_and_tim!(Pa24, FunSel::Sel2, 16); +pin_and_tim!(Pa25, FunSel::Sel2, 17); +pin_and_tim!(Pa26, FunSel::Sel2, 18); +pin_and_tim!(Pa27, FunSel::Sel2, 19); +pin_and_tim!(Pa28, FunSel::Sel2, 20); +pin_and_tim!(Pa29, FunSel::Sel2, 21); +pin_and_tim!(Pa30, FunSel::Sel2, 22); +pin_and_tim!(Pa31, FunSel::Sel2, 23); + +pin_and_tim!(Pb0, FunSel::Sel3, 0); +pin_and_tim!(Pb1, FunSel::Sel3, 1); +pin_and_tim!(Pb2, FunSel::Sel3, 2); +pin_and_tim!(Pb3, FunSel::Sel3, 3); +pin_and_tim!(Pb4, FunSel::Sel3, 4); +pin_and_tim!(Pb5, FunSel::Sel3, 5); +pin_and_tim!(Pb6, FunSel::Sel3, 6); + +pin_and_tim!(Pb10, FunSel::Sel3, 10); +pin_and_tim!(Pb11, FunSel::Sel3, 11); +pin_and_tim!(Pb12, FunSel::Sel3, 12); +pin_and_tim!(Pb13, FunSel::Sel3, 13); +pin_and_tim!(Pb14, FunSel::Sel3, 14); +pin_and_tim!(Pb15, FunSel::Sel3, 15); +pin_and_tim!(Pb16, FunSel::Sel3, 16); +pin_and_tim!(Pb17, FunSel::Sel3, 17); +pin_and_tim!(Pb18, FunSel::Sel3, 18); +pin_and_tim!(Pb19, FunSel::Sel3, 19); +pin_and_tim!(Pb20, FunSel::Sel3, 20); +pin_and_tim!(Pb21, FunSel::Sel3, 21); +pin_and_tim!(Pb22, FunSel::Sel3, 22); +pin_and_tim!(Pb23, FunSel::Sel3, 23); + +//================================================================================================== +// Timers +//================================================================================================== + +/// Hardware timers +pub struct CountdownTimer { + id: TimId, + regs: regs::MmioTimer<'static>, + curr_freq: Hertz, + sys_clk: Hertz, + rst_val: u32, + last_cnt: u32, +} + +impl CountdownTimer { + /// Create a countdown timer structure for a given TIM peripheral. + /// + /// This does not enable the timer. You can use the [Self::load], [Self::start], + /// [Self::enable_interrupt] and [Self::enable] API to set up and configure the countdown + /// timer. + pub fn new(sys_clk: Hertz, _tim: Tim) -> Self { + enable_tim_clk(Tim::ID); + assert_tim_reset_for_cycles(Tim::ID, 2); + CountdownTimer { + id: Tim::ID, + regs: regs::Timer::new_mmio(Tim::ID), + sys_clk, + rst_val: 0, + curr_freq: 0.Hz(), + last_cnt: 0, + } + } + + #[inline(always)] + pub fn enable(&mut self) { + self.regs + .write_enable_control(regs::EnableControl::new_enable()); + } + #[inline(always)] + pub fn disable(&mut self) { + self.regs + .write_enable_control(regs::EnableControl::new_disable()); + } + + pub fn enable_interrupt(&mut self, irq_cfg: InterruptConfig) { + if irq_cfg.route { + let irqsel = unsafe { pac::Irqsel::steal() }; + enable_peripheral_clock(PeripheralSelect::Irqsel); + irqsel + .tim0(self.id.value() as usize) + .write(|w| unsafe { w.bits(irq_cfg.id as u32) }); + } + if irq_cfg.enable_in_nvic { + unsafe { enable_nvic_interrupt(irq_cfg.id) }; + } + self.regs.modify_control(|mut value| { + value.set_irq_enable(true); + value + }); + } + + /// This function only clears the interrupt enable bit. + /// + /// It does not mask the interrupt in the NVIC or un-route the IRQ. + #[inline(always)] + pub fn disable_interrupt(&mut self) { + self.regs.modify_control(|mut value| { + value.set_irq_enable(false); + value + }); + } + + /// Calls [Self::load] to configure the specified frequency and then calls [Self::enable]. + pub fn start(&mut self, frequency: impl Into) { + self.load(frequency); + 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<(), Infallible> { + let cnt = self.counter(); + if (cnt > self.last_cnt) || cnt == 0 { + self.last_cnt = self.rst_val; + Ok(()) + } else { + self.last_cnt = cnt; + Err(nb::Error::WouldBlock) + } + } + + /// Load the count down timer with a timeout but do not start it. + pub fn load(&mut self, timeout: impl Into) { + self.disable(); + 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.regs.write_reset_value(val); + } + + #[inline(always)] + pub fn set_count(&mut self, val: u32) { + self.regs.write_count_value(val); + } + + #[inline(always)] + pub fn counter(&self) -> u32 { + self.regs.read_count_value() + } + + /// Disable the counter, setting both enable and active bit to 0 + #[inline] + pub fn auto_disable(&mut self, enable: bool) { + self.regs.modify_control(|mut value| { + value.set_auto_disable(enable); + value + }); + } + + /// 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 + #[inline] + pub fn auto_deactivate(&mut self, enable: bool) { + self.regs.modify_control(|mut value| { + value.set_auto_deactivate(enable); + value + }); + } + + /// Configure the cascade parameters + pub fn cascade_control(&mut self, ctrl: CascadeControl) { + self.regs.write_cascade_control( + regs::CascadeControl::builder() + .with_trigger2(ctrl.trigger_mode_2) + .with_inv2(ctrl.inv_src_2) + .with_en2(ctrl.enable_stop_src_2) + .with_trigger1(ctrl.trigger_mode_1) + .with_trigger0(ctrl.trigger_mode_0) + .with_dual_cascade_op(ctrl.dual_operation) + .with_inv1(ctrl.inv_src_1) + .with_en1(ctrl.enable_src_1) + .with_inv0(ctrl.inv_src_0) + .with_en0(ctrl.enable_src_0) + .build(), + ); + } + + pub fn cascade_source( + &mut self, + cascade_index: CascadeSelect, + src: regs::CascadeSource, + ) -> Result<(), regs::InvalidCascadeSourceId> { + // Safety: Index range safe by enum values. + unsafe { + self.regs + .write_cascade_unchecked(cascade_index as usize, regs::CascadeSourceReg::new(src)?); + } + Ok(()) + } + + pub fn curr_freq(&self) -> Hertz { + self.curr_freq + } + + /// Disables the TIM and the dedicated TIM clock. + pub fn stop_with_clock_disable(mut self) { + self.disable(); + unsafe { va108xx::Sysconfig::steal() } + .tim_clk_enable() + .modify(|r, w| unsafe { w.bits(r.bits() & !(1 << self.id.value())) }); + } +} + +//================================================================================================== +// Delay implementations +//================================================================================================== +// +impl embedded_hal::delay::DelayNs for CountdownTimer { + 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.counter(); + last_count = new_count; + loop { + new_count = self.counter(); + if new_count == 0 { + // Wait till timer has wrapped. + while self.counter() == 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.counter(); + if new_count == 0 || (new_count > last_count) { + break; + } + last_count = new_count; + } + } + + self.disable(); + } +} + +#[inline(always)] +pub fn enable_tim_clk(id: TimId) { + unsafe { pac::Sysconfig::steal() } + .tim_clk_enable() + .modify(|r, w| unsafe { w.bits(r.bits() | (1 << id.value())) }); +} + +#[inline(always)] +pub fn disable_tim_clk(id: TimId) { + unsafe { pac::Sysconfig::steal() } + .tim_clk_enable() + .modify(|r, w| unsafe { w.bits(r.bits() & !(1 << (id.value()))) }); +} + +/// Clear the reset bit of the TIM, holding it in reset +/// +/// # Safety +/// +/// Only the bit related to the corresponding TIM peripheral is modified +#[inline] +fn assert_tim_reset(id: TimId) { + unsafe { pac::Peripherals::steal() } + .sysconfig + .tim_reset() + .modify(|r, w| unsafe { w.bits(r.bits() & !(1 << id.value())) }); +} + +#[inline] +fn deassert_tim_reset(tim: TimId) { + unsafe { pac::Peripherals::steal() } + .sysconfig + .tim_reset() + .modify(|r, w| unsafe { w.bits(r.bits() | (1 << tim.value())) }); +} + +fn assert_tim_reset_for_cycles(tim: TimId, cycles: u32) { + assert_tim_reset(tim); + cortex_m::asm::delay(cycles); + deassert_tim_reset(tim); +} diff --git a/vorago-shared-periphs/src/timer/pins_vor1x.rs b/vorago-shared-periphs/src/timer/pins_vor1x.rs new file mode 100644 index 0000000..e69de29 diff --git a/vorago-shared-periphs/src/timer/regs.rs b/vorago-shared-periphs/src/timer/regs.rs new file mode 100644 index 0000000..c3077df --- /dev/null +++ b/vorago-shared-periphs/src/timer/regs.rs @@ -0,0 +1,309 @@ +use core::marker::PhantomData; + +use arbitrary_int::{Number, u7}; + +#[cfg(feature = "vor1x")] +const BASE_ADDR: usize = 0x4002_0000; +#[cfg(feature = "vor4x")] +const BASE_ADDR: usize = 0x4001_8000; + +#[bitbybit::bitenum(u3)] +#[derive(Debug, PartialEq, Eq)] +pub enum StatusSelect { + /// Pulse when timer reaches 0. + OneCyclePulse = 0b000, + OutputActiveBit = 0b001, + /// Creates a divide by two output clock of the timer. + ToggleOnEachCycle = 0b010, + /// 1 when count value >= PWM A value, 0 otherwise + PwmaOutput = 0b011, + /// 1 when count value < PWM A value and >= PWM B, 0 when counter value >= PWM A value or < PWM + /// B value + PwmbOutput = 0b100, + EnabledBit = 0b101, + /// 1 when counter value <= PWM A value and 0 otherwise. + PwmaActiveBit = 0b110, +} + +#[bitbybit::bitfield(u32)] +pub struct Control { + /// The counter is requested to stop on the next normal count cycle. + #[bit(9, rw)] + request_stop: bool, + #[bit(8, rw)] + status_invert: bool, + #[bits(5..=7, rw)] + status_sel: Option, + #[bit(4, rw)] + irq_enable: bool, + /// Only applies if the Auto-Disable bit is 0. The ACTIVE bit goes to 0 when the count reaches + /// 0, but the timer remains enabled. + #[bit(3, rw)] + auto_deactivate: bool, + /// Counter is fully disabled when count reaches 0, which means that both the ENABLE + /// and ACTIVE bits go to 0. + #[bit(2, rw)] + auto_disable: bool, + #[bit(1, r)] + active: bool, + #[bit(0, rw)] + enable: bool, +} + +pub struct EnableControl(arbitrary_int::UInt); + +impl EnableControl { + pub fn new_disable() -> Self { + EnableControl(arbitrary_int::UInt::::from_u32(0)) + } + + pub fn new_enable() -> Self { + EnableControl(arbitrary_int::UInt::::from_u32(1)) + } + + pub fn enabled(&self) -> bool { + self.0.value() != 0 + } +} + +#[bitbybit::bitenum(u1, exhaustive = true)] +#[derive(Default, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum CascadeInvert { + #[default] + ActiveHigh = 0, + ActiveLow = 1, +} + +/// When two cascade sources are selected, configure the required operation. +#[bitbybit::bitenum(u1, exhaustive = true)] +#[derive(Default, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum DualCascadeOp { + #[default] + LogicalAnd = 0, + LogicalOr = 1, +} + +#[bitbybit::bitfield(u32, default = 0x0)] +pub struct CascadeControl { + /// 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. + #[bit(10, rw)] + trigger2: bool, + #[bit(9, rw)] + inv2: CascadeInvert, + /// 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. + #[bit(8, rw)] + en2: bool, + /// Same as the trigger field for Cascade 0. + #[bit(7, rw)] + trigger1: 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. + #[bit(6, rw)] + trigger0: 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. + #[bit(4, rw)] + dual_cascade_op: DualCascadeOp, + /// Inversion bit for Cascade 1 + #[bit(3, rw)] + inv1: CascadeInvert, + /// Enable Cascade 1 signal active as a requirement for counting. + #[bit(2, rw)] + en1: bool, + /// Inversion bit for Cascade 0. + #[bit(1, rw)] + inv0: CascadeInvert, + /// Enable Cascade 0 signal active as a requirement for counting. + #[bit(0, rw)] + en0: bool, +} + +#[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 +#[cfg(feature = "vor1x")] +#[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 { + #[cfg(feature = "vor1x")] + pub fn id(&self) -> Result { + let port_check = |base: u8, id: u8, len: u8| -> Result { + if id > len - 1 { + return Err(InvalidCascadeSourceId); + } + Ok(u7::new(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(u7::new(96)), + CascadeSource::RamMbe => Ok(u7::new(97)), + CascadeSource::RomSbe => Ok(u7::new(98)), + CascadeSource::RomMbe => Ok(u7::new(99)), + CascadeSource::Txev => Ok(u7::new(100)), + CascadeSource::ClockDivider(id) => port_check(120, *id, 8), + } + } + + #[cfg(feature = "vor1x")] + pub fn from_raw(raw: u32) -> Result { + let id = u7::new((raw & 0x7F) as u8); + if id.value() > 127 { + return Err(InvalidCascadeSourceId); + } + let id = id.as_u8(); + if id < 32 { + return Ok(CascadeSource::PortA(id)); + } else if (32..56).contains(&id) { + return Ok(CascadeSource::PortB(id - 32)); + } else if (64..88).contains(&id) { + return Ok(CascadeSource::Tim(id - 64)); + } else if id > 120 { + return Ok(CascadeSource::ClockDivider(id - 120)); + } + match id { + 96 => Ok(CascadeSource::RamSbe), + 97 => Ok(CascadeSource::RamMbe), + 98 => Ok(CascadeSource::RomSbe), + 99 => Ok(CascadeSource::RomMbe), + 100 => Ok(CascadeSource::Txev), + _ => Err(InvalidCascadeSourceId), + } + } +} + +#[bitbybit::bitfield(u32)] +pub struct CascadeSourceReg { + #[bits(0..=6, rw)] + raw: u7, +} + +impl CascadeSourceReg { + pub fn new(source: CascadeSource) -> Result { + let id = source.id()?; + Ok(Self::new_with_raw_value(id.as_u32())) + } + + pub fn as_cascade_source(&self) -> Result { + CascadeSource::from_raw(self.raw().as_u32()) + } +} + +#[derive(derive_mmio::Mmio)] +#[mmio(no_ctors)] +#[repr(C)] +pub struct Timer { + control: Control, + reset_value: u32, + count_value: u32, + enable_control: EnableControl, + cascade_control: CascadeControl, + /// CASCADE0 and CASCADE1 are used to control the counting and activation of the counter. + /// CASCADE2 is used to request stopping of the timer. + cascade: [CascadeSourceReg; 3], + /// PWM A compare value. + pwma_value: u32, + /// PWM B compare value. + pwmb_value: u32, + #[cfg(feature = "vor1x")] + _reserved: [u32; 0x3f5], + #[cfg(feature = "vor4x")] + _reserved: [u32; 0xf5], + /// Vorago 1x: 0x0111_07E1. Vorago 4x: 0x0211_07E9 + perid: u32, +} + +cfg_if::cfg_if! { + if #[cfg(feature = "vor1x")] { + static_assertions::const_assert_eq!(core::mem::size_of::(), 0x1000); + } else if #[cfg(feature = "vor4x")] { + static_assertions::const_assert_eq!(core::mem::size_of::(), 0x400); + } +} + +#[derive(Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct InvalidTimerIndex(pub usize); + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct TimId(u8); + +impl TimId { + pub const fn new(index: usize) -> Result { + if index > 23 { + return Err(InvalidTimerIndex(index)); + } + Ok(TimId(index as u8)) + } + + pub const fn new_unchecked(index: usize) -> Self { + if index > 23 { + panic!("invalid timer index"); + } + TimId(index as u8) + } + + /// Unsafely steal the TIM peripheral block for the TIM ID. + /// + /// # Safety + /// + /// Circumvents ownership and safety guarantees by the HAL. + pub const unsafe fn steal_regs(&self) -> MmioTimer<'static> { + Timer::new_mmio(*self) + } + + pub const fn value(&self) -> u8 { + self.0 + } +} + +impl Timer { + const fn new_mmio_at(base: usize) -> MmioTimer<'static> { + MmioTimer { + ptr: base as *mut _, + phantom: PhantomData, + } + } + + pub const fn new_mmio(id: TimId) -> MmioTimer<'static> { + if cfg!(feature = "vor1x") { + Timer::new_mmio_at(BASE_ADDR + 0x1000 * id.value() as usize) + } else { + Timer::new_mmio_at(BASE_ADDR + 0x400 * id.value() as usize) + } + } + pub fn new_mmio_with_raw_index( + timer_index: usize, + ) -> Result, InvalidTimerIndex> { + if timer_index > 23 { + return Err(InvalidTimerIndex(timer_index)); + } + if cfg!(feature = "vor1x") { + Ok(Timer::new_mmio_at(BASE_ADDR + 0x1000 * timer_index)) + } else { + Ok(Timer::new_mmio_at(BASE_ADDR + 0x400 * timer_index)) + } + } +} diff --git a/vorago-shared-periphs/src/uart/mod.rs b/vorago-shared-periphs/src/uart/mod.rs new file mode 100644 index 0000000..ad66cb1 --- /dev/null +++ b/vorago-shared-periphs/src/uart/mod.rs @@ -0,0 +1,1260 @@ +//! # API for the UART peripheral +//! +//! The core of this API are the [Uart], [Rx] and [Tx] structures. +//! The RX structure also has a dedicated [RxWithInterrupt] variant which allows reading the receiver +//! using interrupts. +//! +//! The [rx_asynch] and [tx_asynch] modules provide an asynchronous non-blocking API for the UART +//! peripheral. +//! +//! ## Examples +//! +//! - [UART simple example](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples/simple/examples/uart.rs) +//! - [UART with IRQ and RTIC](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples/rtic/src/bin/uart-echo-rtic.rs) +//! - [Flashloader exposing a CCSDS interface via UART](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/flashloader) +use core::{convert::Infallible, ops::Deref}; +pub mod regs; +use crate::{FunSel, InterruptConfig, gpio::IoPeriphPin, pins::PinMarker}; +use arbitrary_int::{Number, u6, u18}; +use fugit::RateExtU32; +use regs::{ClkScale, Control, Data, Enable, FifoClear, InterruptClear, MmioUart}; + +use crate::{PeripheralSelect, enable_nvic_interrupt, enable_peripheral_clock, time::Hertz}; +use embedded_hal_nb::serial::Read; +pub use regs::{Bank, Stopbits, WordSize}; + +#[cfg(feature = "vor1x")] +mod pins_vor1x; + +//================================================================================================== +// Type-Level support +//================================================================================================== + +pub trait TxPin: PinMarker { + const BANK: Bank; + const FUN_SEL: FunSel; +} +pub trait RxPin: PinMarker { + const BANK: Bank; + const FUN_SEL: FunSel; +} + +//================================================================================================== +// Regular Definitions +//================================================================================================== + +#[derive(Debug, PartialEq, Eq, thiserror::Error)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[error("no interrupt ID was set")] +pub struct NoInterruptIdWasSet; + +#[derive(Debug, PartialEq, Eq, thiserror::Error)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[error("transer is pending")] +pub struct TransferPendingError; + +#[derive(Debug, PartialEq, Eq, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Event { + // Receiver FIFO interrupt enable. Generates interrupt + // when FIFO is at least half full. Half full is defined as FIFO + // count >= RXFIFOIRQTRG + RxFifoHalfFull, + // Framing error, Overrun error, Parity Error and Break error + RxError, + // Event for timeout condition: Data in the FIFO and no receiver + // FIFO activity for 4 character times + RxTimeout, + + // Transmitter FIFO interrupt enable. Generates interrupt + // when FIFO is at least half full. Half full is defined as FIFO + // count >= TXFIFOIRQTRG + TxFifoHalfFull, + // FIFO overflow error + TxError, + // Generate interrupt when transmit FIFO is empty and TXBUSY is 0 + TxEmpty, + // Interrupt when CTSn changes value + TxCts, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Parity { + None, + Odd, + Even, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Config { + pub baudrate: Hertz, + pub parity: Parity, + pub stopbits: Stopbits, + // When false, use standard 16x baud clock, other 8x baud clock + pub baud8: bool, + pub wordsize: WordSize, + pub enable_tx: bool, + pub enable_rx: bool, +} + +impl Config { + pub fn baudrate(mut self, baudrate: Hertz) -> Self { + self.baudrate = baudrate; + self + } + + pub fn parity_none(mut self) -> Self { + self.parity = Parity::None; + self + } + + pub fn parity_even(mut self) -> Self { + self.parity = Parity::Even; + self + } + + pub fn parity_odd(mut self) -> Self { + self.parity = Parity::Odd; + self + } + + pub fn stopbits(mut self, stopbits: Stopbits) -> Self { + self.stopbits = stopbits; + self + } + + pub fn wordsize(mut self, wordsize: WordSize) -> Self { + self.wordsize = wordsize; + self + } + + pub fn baud8(mut self, baud: bool) -> Self { + self.baud8 = baud; + self + } +} + +impl Default for Config { + fn default() -> Config { + let baudrate = 115_200_u32.Hz(); + Config { + baudrate, + parity: Parity::None, + stopbits: Stopbits::One, + baud8: false, + wordsize: WordSize::Eight, + enable_tx: true, + enable_rx: true, + } + } +} + +impl From for Config { + fn from(baud: Hertz) -> Self { + Config::default().baudrate(baud) + } +} + +//================================================================================================== +// IRQ Definitions +//================================================================================================== + +#[derive(Debug, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct IrqContextTimeoutOrMaxSize { + rx_idx: usize, + mode: IrqReceptionMode, + pub max_len: usize, +} + +impl IrqContextTimeoutOrMaxSize { + pub fn new(max_len: usize) -> Self { + IrqContextTimeoutOrMaxSize { + rx_idx: 0, + max_len, + mode: IrqReceptionMode::Idle, + } + } +} + +impl IrqContextTimeoutOrMaxSize { + pub fn reset(&mut self) { + self.rx_idx = 0; + self.mode = IrqReceptionMode::Idle; + } +} + +/// This struct is used to return the default IRQ handler result to the user +#[derive(Debug, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct IrqResult { + pub bytes_read: usize, + pub errors: Option, +} + +/// This struct is used to return the default IRQ handler result to the user +#[derive(Debug, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct IrqResultMaxSizeOrTimeout { + complete: bool, + timeout: bool, + pub errors: Option, + pub bytes_read: usize, +} + +impl IrqResultMaxSizeOrTimeout { + pub fn new() -> Self { + IrqResultMaxSizeOrTimeout { + complete: false, + timeout: false, + errors: None, + bytes_read: 0, + } + } +} +impl IrqResultMaxSizeOrTimeout { + #[inline] + pub fn has_errors(&self) -> bool { + self.errors.is_some() + } + + #[inline] + pub fn overflow_error(&self) -> bool { + self.errors.is_some_and(|e| e.overflow) + } + + #[inline] + pub fn framing_error(&self) -> bool { + self.errors.is_some_and(|e| e.framing) + } + + #[inline] + pub fn parity_error(&self) -> bool { + self.errors.is_some_and(|e| e.parity) + } + + #[inline] + pub fn timeout(&self) -> bool { + self.timeout + } + + #[inline] + pub fn complete(&self) -> bool { + self.complete + } +} + +#[derive(Debug, PartialEq, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +enum IrqReceptionMode { + Idle, + Pending, +} + +#[derive(Default, Debug, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct UartErrors { + overflow: bool, + framing: bool, + parity: bool, + other: bool, +} + +impl UartErrors { + #[inline(always)] + pub fn overflow(&self) -> bool { + self.overflow + } + + #[inline(always)] + pub fn framing(&self) -> bool { + self.framing + } + + #[inline(always)] + pub fn parity(&self) -> bool { + self.parity + } + + #[inline(always)] + pub fn other(&self) -> bool { + self.other + } +} + +impl UartErrors { + #[inline(always)] + pub fn error(&self) -> bool { + self.overflow || self.framing || self.parity || self.other + } +} + +#[derive(Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct BufferTooShortError { + found: usize, + expected: usize, +} + +//================================================================================================== +// UART peripheral wrapper +//================================================================================================== +use va108xx::uarta as uart_base; + +pub trait UartPeripheralMarker: Deref { + const ID: Bank; + const PERIPH_SEL: PeripheralSelect; + const PTR: *const uart_base::RegisterBlock; + + /* + /// Retrieve the peripheral structure. + /// + /// # Safety + /// + /// This circumvents the safety guarantees of the HAL. + unsafe fn steal() -> Self; + + #[inline(always)] + fn ptr() -> *const uart_base::RegisterBlock { + Self::PTR + } + + /// Retrieve the type erased peripheral register block. + /// + /// # Safety + /// + /// This circumvents the safety guarantees of the HAL. + #[inline(always)] + unsafe fn reg_block() -> &'static uart_base::RegisterBlock { + unsafe { &(*Self::ptr()) } + } + */ +} + +impl UartPeripheralMarker for va108xx::Uarta { + const ID: Bank = Bank::Uart0; + + const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Uart0; + const PTR: *const uart_base::RegisterBlock = Self::PTR; + + /* + #[inline(always)] + unsafe fn steal() -> Self { + Self::steal() + } + */ +} + +impl UartPeripheralMarker for va108xx::Uartb { + const ID: Bank = Bank::Uart1; + + const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Uart1; + const PTR: *const uart_base::RegisterBlock = Self::PTR; + + /* + #[inline(always)] + unsafe fn steal() -> Self { + Self::steal() + } + */ +} + +#[derive(Debug, thiserror::Error)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[error("UART ID missmatch between peripheral and pins.")] +pub struct UartIdMissmatchError; + +//================================================================================================== +// UART implementation +//================================================================================================== + +/// UART driver structure. +pub struct Uart { + tx: Tx, + rx: Rx, +} + +impl Uart { + /// Calls [Self::new] with the interrupt configuration to some valid value. + pub fn new_with_interrupt( + sys_clk: Hertz, + uart: UartI, + pins: (Tx, Rx), + config: Config, + irq_cfg: InterruptConfig, + ) -> Result { + Self::new(sys_clk, uart, pins, config, Some(irq_cfg)) + } + + /// Calls [Self::new] with the interrupt configuration to [None]. + pub fn new_without_interrupt( + sys_clk: Hertz, + uart: UartI, + pins: (Tx, Rx), + config: Config, + ) -> Result { + Self::new(sys_clk, uart, pins, config, None) + } + + /// Create a new UART peripheral with an interrupt configuration. + /// + /// # Arguments + /// + /// - `syscfg`: The system configuration register block + /// - `sys_clk`: The system clock frequency + /// - `uart`: The concrete UART peripheral instance. + /// - `pins`: UART TX and RX pin tuple. + /// - `config`: UART specific configuration parameters like baudrate. + /// - `irq_cfg`: Optional interrupt configuration. This should be a valid value if the plan + /// is to use TX or RX functionality relying on interrupts. If only the blocking API without + /// any interrupt support is used, this can be [None]. + pub fn new( + sys_clk: Hertz, + _uart: UartI, + _pins: (TxPinI, RxPinI), + config: Config, + opt_irq_cfg: Option, + ) -> Result { + if UartI::ID != TxPinI::BANK || UartI::ID != RxPinI::BANK { + return Err(UartIdMissmatchError); + } + IoPeriphPin::new(TxPinI::ID, TxPinI::FUN_SEL, None); + IoPeriphPin::new(RxPinI::ID, TxPinI::FUN_SEL, None); + enable_peripheral_clock(UartI::PERIPH_SEL); + + let mut reg_block = regs::Uart::new_mmio(UartI::ID); + let baud_multiplier = match config.baud8 { + false => 16, + true => 8, + }; + + // This is the calculation: (64.0 * (x - integer_part as f32) + 0.5) as u32 without floating + // point calculations. + let frac = ((sys_clk.raw() % (config.baudrate.raw() * 16)) * 64 + + (config.baudrate.raw() * 8)) + / (config.baudrate.raw() * 16); + // Calculations here are derived from chapter 4.8.5 (p.79) of the datasheet. + let x = sys_clk.raw() as f32 / (config.baudrate.raw() * baud_multiplier) as f32; + let integer_part = x as u32; + reg_block.write_clkscale( + ClkScale::builder() + .with_int(u18::new(integer_part)) + .with_frac(u6::new(frac as u8)) + .build(), + ); + + let (paren, pareven) = match config.parity { + Parity::None => (false, false), + Parity::Odd => (true, false), + Parity::Even => (true, true), + }; + reg_block.write_ctrl( + Control::builder() + .with_baud8(config.baud8) + .with_auto_rts(false) + .with_def_rts(false) + .with_auto_cts(false) + .with_loopback_block(false) + .with_loopback(false) + .with_wordsize(config.wordsize) + .with_stopbits(config.stopbits) + .with_parity_manual(false) + .with_parity_even(pareven) + .with_parity_enable(paren) + .build(), + ); + // Clear the FIFO + reg_block.write_fifo_clr(FifoClear::builder().with_tx(true).with_rx(true).build()); + reg_block.write_enable( + Enable::builder() + .with_tx(config.enable_tx) + .with_rx(config.enable_rx) + .build(), + ); + + // TODO: VA108xx specific + if let Some(irq_cfg) = opt_irq_cfg { + if irq_cfg.route { + enable_peripheral_clock(PeripheralSelect::Irqsel); + unsafe { va108xx::Irqsel::steal() } + .uart0(UartI::ID as usize) + .write(|w| unsafe { w.bits(irq_cfg.id as u32) }); + } + if irq_cfg.enable_in_nvic { + // Safety: User has specifically configured this. + unsafe { enable_nvic_interrupt(irq_cfg.id) }; + } + } + + Ok(Uart { + tx: Tx::new(UartI::ID), + rx: Rx::new(UartI::ID), + }) + } + + #[inline] + pub fn enable_rx(&mut self) { + self.rx.enable(); + } + + #[inline] + pub fn disable_rx(&mut self) { + self.rx.disable(); + } + + #[inline] + pub fn enable_tx(&mut self) { + self.tx.enable(); + } + + #[inline] + pub fn disable_tx(&mut self) { + self.tx.disable(); + } + + /// This also clears status conditons for the RX FIFO. + #[inline] + pub fn clear_rx_fifo(&mut self) { + self.rx.clear_fifo(); + } + + /// This also clears status conditons for the TX FIFO. + #[inline] + pub fn clear_tx_fifo(&mut self) { + self.tx.clear_fifo(); + } + + pub fn listen(&mut self, event: Event) { + self.tx.regs.modify_irq_enabled(|mut value| { + match event { + Event::RxError => value.set_rx_status(true), + Event::RxFifoHalfFull => value.set_rx(true), + Event::RxTimeout => value.set_rx_timeout(true), + Event::TxEmpty => value.set_tx_empty(true), + Event::TxError => value.set_tx_status(true), + Event::TxFifoHalfFull => value.set_tx(true), + Event::TxCts => value.set_tx_cts(true), + } + value + }); + } + + pub fn unlisten(&mut self, event: Event) { + self.tx.regs.modify_irq_enabled(|mut value| { + match event { + Event::RxError => value.set_rx_status(false), + Event::RxFifoHalfFull => value.set_rx(false), + Event::RxTimeout => value.set_rx_timeout(false), + Event::TxEmpty => value.set_tx_empty(false), + Event::TxError => value.set_tx_status(false), + Event::TxFifoHalfFull => value.set_tx(false), + Event::TxCts => value.set_tx_cts(false), + } + value + }); + } + + /// Poll receiver errors. + pub fn poll_rx_errors(&self) -> Option { + self.rx.poll_errors() + } + + pub fn split(self) -> (Tx, Rx) { + (self.tx, self.rx) + } +} + +impl embedded_io::ErrorType for Uart { + type Error = Infallible; +} + +impl embedded_hal_nb::serial::ErrorType for Uart { + type Error = Infallible; +} + +impl embedded_hal_nb::serial::Read for Uart { + fn read(&mut self) -> nb::Result { + self.rx.read() + } +} + +impl embedded_hal_nb::serial::Write for Uart { + fn write(&mut self, word: u8) -> nb::Result<(), Self::Error> { + self.tx.write(word).map_err(|e| { + if let nb::Error::Other(_) = e { + unreachable!() + } + nb::Error::WouldBlock + }) + } + + fn flush(&mut self) -> nb::Result<(), Self::Error> { + self.tx.flush().map_err(|e| { + if let nb::Error::Other(_) = e { + unreachable!() + } + nb::Error::WouldBlock + }) + } +} + +#[inline(always)] +pub fn enable_rx(uart: &mut MmioUart<'static>) { + uart.modify_enable(|mut value| { + value.set_rx(true); + value + }); +} + +#[inline(always)] +pub fn disable_rx(uart: &mut MmioUart<'static>) { + uart.modify_enable(|mut value| { + value.set_rx(false); + value + }); +} + +#[inline(always)] +pub fn enable_rx_interrupts(uart: &mut MmioUart<'static>, timeout: bool) { + uart.modify_irq_enabled(|mut value| { + value.set_rx_status(true); + value.set_rx(true); + if timeout { + value.set_rx_timeout(true); + } + value + }); +} + +#[inline(always)] +pub fn disable_rx_interrupts(uart: &mut MmioUart<'static>) { + uart.modify_irq_enabled(|mut value| { + value.set_rx_status(false); + value.set_rx(false); + value.set_rx_timeout(false); + value + }); +} + +/// Serial receiver. +/// +/// Can be created by using the [Uart::split] API. +pub struct Rx { + id: Bank, + regs: regs::MmioUart<'static>, +} + +impl Rx { + /// Retrieve a TX pin without expecting an explicit UART structure + /// + /// # Safety + /// + /// Circumvents the HAL safety guarantees. + #[inline(always)] + pub unsafe fn steal(id: Bank) -> Self { + Self::new(id) + } + + #[inline(always)] + fn new(id: Bank) -> Self { + Self { + id, + regs: regs::Uart::new_mmio(id), + } + } + + /* + /// Direct access to the peripheral structure. + /// + /// # Safety + /// + /// You must ensure that only registers related to the operation of the RX side are used. + #[inline(always)] + pub const unsafe fn regs(&self) -> &'static uart_base::RegisterBlock { + self.regs_priv() + } + + #[inline(always)] + const fn regs_priv(&self) -> &'static uart_base::RegisterBlock { + unsafe { self.0.reg_block() } + } + */ + + pub fn poll_errors(&self) -> Option { + let mut errors = UartErrors::default(); + + let status = self.regs.read_rx_status(); + if status.overrun_error() { + errors.overflow = true; + } else if status.framing_error() { + errors.framing = true; + } else if status.parity_error() { + errors.parity = true; + } else { + return None; + }; + Some(errors) + } + + #[inline] + pub fn clear_fifo(&mut self) { + self.regs + .write_fifo_clr(FifoClear::builder().with_tx(false).with_rx(true).build()); + } + + #[inline] + pub fn disable_interrupts(&mut self) { + disable_rx_interrupts(&mut self.regs); + } + #[inline] + pub fn enable_interrupts(&mut self, timeout: bool) { + enable_rx_interrupts(&mut self.regs, timeout); + } + + #[inline] + pub fn enable(&mut self) { + enable_rx(&mut self.regs); + } + + #[inline] + pub fn disable(&mut self) { + disable_rx(&mut self.regs); + } + + /// Low level function to read a word from the UART FIFO. + /// + /// Uses the [nb] API to allow usage in blocking and non-blocking contexts. + /// + /// Please note that you might have to mask the returned value with 0xff to retrieve the actual + /// value if you use the manual parity mode. See chapter 4.6.2 for more information. + #[inline(always)] + pub fn read_fifo(&mut self) -> nb::Result { + if !self.regs.read_rx_status().data_available() { + return Err(nb::Error::WouldBlock); + } + Ok(self.read_fifo_unchecked()) + } + + /// Low level function to read a word from from the UART FIFO. + /// + /// This does not necesarily mean there is a word in the FIFO available. + /// Use the [Self::read_fifo] function to read a word from the FIFO reliably using the [nb] + /// API. + /// + /// Please note that you might have to mask the returned value with 0xff to retrieve the actual + /// value if you use the manual parity mode. See chapter 4.6.2 for more information. + #[inline(always)] + pub fn read_fifo_unchecked(&mut self) -> u32 { + self.regs.read_data().raw_value() + } + + pub fn into_rx_with_irq(self) -> RxWithInterrupt { + RxWithInterrupt::new(self) + } +} + +impl embedded_io::ErrorType for Rx { + type Error = Infallible; +} + +impl embedded_hal_nb::serial::ErrorType for Rx { + type Error = Infallible; +} + +impl embedded_hal_nb::serial::Read for Rx { + fn read(&mut self) -> nb::Result { + self.read_fifo().map(|val| (val & 0xff) as u8).map_err(|e| { + if let nb::Error::Other(_) = e { + unreachable!() + } + nb::Error::WouldBlock + }) + } +} + +impl embedded_io::Read for Rx { + fn read(&mut self, buf: &mut [u8]) -> Result { + if buf.is_empty() { + return Ok(0); + } + let mut read = 0; + loop { + if self.regs.read_rx_status().data_available() { + break; + } + } + for byte in buf.iter_mut() { + match >::read(self) { + Ok(w) => { + *byte = w; + read += 1; + } + Err(nb::Error::WouldBlock) => break, + } + } + + Ok(read) + } +} + +#[inline(always)] +pub fn enable_tx(uart: &mut MmioUart<'static>) { + uart.modify_enable(|mut value| { + value.set_tx(true); + value + }); +} + +#[inline(always)] +pub fn disable_tx(uart: &mut MmioUart<'static>) { + uart.modify_enable(|mut value| { + value.set_tx(false); + value + }); +} + +#[inline(always)] +pub fn enable_tx_interrupts(uart: &mut MmioUart<'static>) { + uart.modify_irq_enabled(|mut value| { + value.set_tx(true); + value.set_tx_empty(true); + value.set_tx_status(true); + value + }); +} + +#[inline(always)] +pub fn disable_tx_interrupts(uart: &mut MmioUart<'static>) { + uart.modify_irq_enabled(|mut value| { + value.set_tx(false); + value.set_tx_empty(false); + value.set_tx_status(false); + value + }); +} + +/// Serial transmitter +/// +/// Can be created by using the [Uart::split] API. +pub struct Tx { + id: Bank, + regs: regs::MmioUart<'static>, +} + +impl Tx { + /// Retrieve a TX pin without expecting an explicit UART structure + /// + /// # Safety + /// + /// Circumvents the HAL safety guarantees. + #[inline(always)] + pub unsafe fn steal(id: Bank) -> Self { + Self::new(id) + } + + #[inline(always)] + fn new(id: Bank) -> Self { + Self { + id, + regs: regs::Uart::new_mmio(id), + } + } + + /* + /// Direct access to the peripheral structure. + /// + /// # Safety + /// + /// You must ensure that only registers related to the operation of the TX side are used. + #[inline(always)] + pub unsafe fn regs(&self) -> &'static uart_base::RegisterBlock { + self.0.reg_block() + } + + #[inline(always)] + fn regs_priv(&self) -> &'static uart_base::RegisterBlock { + unsafe { self.regs() } + } + */ + + #[inline] + pub fn clear_fifo(&mut self) { + self.regs + .write_fifo_clr(FifoClear::builder().with_tx(true).with_rx(false).build()); + } + + #[inline] + pub fn enable(&mut self) { + self.regs.modify_enable(|mut value| { + value.set_tx(true); + value + }); + } + + #[inline] + pub fn disable(&mut self) { + self.regs.modify_enable(|mut value| { + value.set_tx(false); + value + }); + } + + /// Enables the IRQ_TX, IRQ_TX_STATUS and IRQ_TX_EMPTY interrupts. + /// + /// - The IRQ_TX interrupt is generated when the TX FIFO is at least half empty. + /// - The IRQ_TX_STATUS interrupt is generated when write data is lost due to a FIFO overflow + /// - The IRQ_TX_EMPTY interrupt is generated when the TX FIFO is empty and the TXBUSY signal + /// is 0 + #[inline] + pub fn enable_interrupts(&mut self) { + // Safety: We own the UART structure + enable_tx_interrupts(&mut self.regs); + } + + /// Disables the IRQ_TX, IRQ_TX_STATUS and IRQ_TX_EMPTY interrupts. + /// + /// [Self::enable_interrupts] documents the interrupts. + #[inline] + pub fn disable_interrupts(&mut self) { + // Safety: We own the UART structure + disable_tx_interrupts(&mut self.regs); + } + + /// Low level function to write a word to the UART FIFO. + /// + /// Uses the [nb] API to allow usage in blocking and non-blocking contexts. + /// + /// Please note that you might have to mask the returned value with 0xff to retrieve the actual + /// value if you use the manual parity mode. See chapter 11.4.1 for more information. + #[inline(always)] + pub fn write_fifo(&mut self, data: u32) -> nb::Result<(), Infallible> { + if !self.regs.read_tx_status().ready() { + return Err(nb::Error::WouldBlock); + } + self.write_fifo_unchecked(data); + Ok(()) + } + + /// Low level function to write a word to the UART FIFO. + /// + /// This does not necesarily mean that the FIFO can process another word because it might be + /// full. + /// Use the [Self::write_fifo] function to write a word to the FIFO reliably using the [nb] + /// API. + #[inline(always)] + pub fn write_fifo_unchecked(&mut self, data: u32) { + self.regs.write_data(Data::new_with_raw_value(data)); + } + + pub fn into_async(self) -> TxAsync { + TxAsync::new(self) + } +} + +impl embedded_io::ErrorType for Tx { + type Error = Infallible; +} + +impl embedded_hal_nb::serial::ErrorType for Tx { + type Error = Infallible; +} + +impl embedded_hal_nb::serial::Write for Tx { + fn write(&mut self, word: u8) -> nb::Result<(), Self::Error> { + self.write_fifo(word as u32) + } + + fn flush(&mut self) -> nb::Result<(), Self::Error> { + // SAFETY: Only TX related registers are used. + if self.regs.read_tx_status().write_busy() { + return Err(nb::Error::WouldBlock); + } + Ok(()) + } +} + +impl embedded_io::Write for Tx { + fn write(&mut self, buf: &[u8]) -> Result { + if buf.is_empty() { + return Ok(0); + } + loop { + if self.regs.read_tx_status().ready() { + break; + } + } + let mut written = 0; + for byte in buf.iter() { + match >::write(self, *byte) { + Ok(_) => written += 1, + Err(nb::Error::WouldBlock) => return Ok(written), + } + } + + Ok(written) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + nb::block!(>::flush(self)) + } +} + +/// Serial receiver, using interrupts to offload reading to the hardware. +/// +/// You can use [Rx::into_rx_with_irq] to convert a normal [Rx] structure into this structure. +/// This structure provides two distinct ways to read the UART RX using interrupts. It should +/// be noted that the interrupt service routine (ISR) still has to be provided by the user. However, +/// this structure provides API calls which can be used inside the ISRs to simplify the reading +/// of the UART. +/// +/// 1. The first way simply empties the FIFO on an interrupt into a user provided buffer. You +/// can simply use [Self::start] to prepare the peripheral and then call the +/// [Self::on_interrupt] in the interrupt service routine. +/// 2. The second way reads packets bounded by a maximum size or a baudtick based timeout. You +/// can use [Self::read_fixed_len_or_timeout_based_using_irq] to prepare the peripheral and +/// then call the [Self::on_interrupt_max_size_or_timeout_based] in the interrupt service +/// routine. You have to call [Self::read_fixed_len_or_timeout_based_using_irq] in the ISR to +/// start reading the next packet. +pub struct RxWithInterrupt(Rx); + +impl RxWithInterrupt { + pub fn new(rx: Rx) -> Self { + Self(rx) + } + + /// This function should be called once at initialization time if the regular + /// [Self::on_interrupt] is used to read the UART receiver to enable and start the receiver. + pub fn start(&mut self) { + self.0.enable(); + self.enable_interrupts(true); + } + + #[inline(always)] + pub fn rx(&self) -> &Rx { + &self.0 + } + + /// This function is used together with the [Self::on_interrupt_max_size_or_timeout_based] + /// function to read packets with a maximum size or variable sized packets by using the + /// receive timeout of the hardware. + /// + /// This function should be called once at initialization to initiate the context state + /// and to [Self::start] the receiver. After that, it should be called after each + /// completed [Self::on_interrupt_max_size_or_timeout_based] call to restart the reception + /// of a packet. + pub fn read_fixed_len_or_timeout_based_using_irq( + &mut self, + context: &mut IrqContextTimeoutOrMaxSize, + ) -> Result<(), TransferPendingError> { + if context.mode != IrqReceptionMode::Idle { + return Err(TransferPendingError); + } + context.mode = IrqReceptionMode::Pending; + context.rx_idx = 0; + self.start(); + Ok(()) + } + + #[inline] + fn enable_interrupts(&mut self, timeout: bool) { + self.0.enable_interrupts(timeout); + } + + #[inline] + fn disable_interrupts(&mut self) { + self.0.disable_interrupts(); + } + + pub fn cancel_transfer(&mut self) { + self.disable_interrupts(); + self.0.clear_fifo(); + } + + /// This function should be called in the user provided UART interrupt handler. + /// + /// It simply empties any bytes in the FIFO into the user provided buffer and returns the + /// result of the operation. + /// + /// This function will not disable the RX interrupts, so you don't need to call any other + /// API after calling this function to continue emptying the FIFO. RX errors are handled + /// as partial errors and are returned as part of the [IrqResult]. + pub fn on_interrupt(&mut self, buf: &mut [u8; 16]) -> IrqResult { + let mut result = IrqResult::default(); + + let irq_status = self.0.regs.read_irq_status(); + let irq_enabled = self.0.regs.read_irq_enabled(); + let rx_enabled = irq_enabled.rx(); + + // Half-Full interrupt. We have a guaranteed amount of data we can read. + if irq_status.rx() { + let available_bytes = self.0.regs.read_rx_fifo_trigger().level().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 { + buf[result.bytes_read] = (self.0.read_fifo_unchecked() & 0xff) as u8; + result.bytes_read += 1; + } + } + + // Timeout, empty the FIFO completely. + if irq_status.rx_timeout() { + // While there is data in the FIFO, write it into the reception buffer + while let Ok(byte) = self.0.read_fifo() { + buf[result.bytes_read] = byte as u8; + result.bytes_read += 1; + } + } + + // RX transfer not complete, check for RX errors + if rx_enabled { + self.check_for_errors(&mut result.errors); + } + + // Clear the interrupt status bits + self.0.regs.write_irq_clr( + InterruptClear::builder() + .with_rx_overrun(true) + .with_tx_overrun(false) + .build(), + ); + result + } + + /// This function should be called in the user provided UART interrupt handler. + /// + /// This function is used to read packets which either have a maximum size or variable sized + /// packet which are bounded by sufficient delays between them, triggering a hardware timeout. + /// + /// If either the maximum number of packets have been read or a timeout occured, the transfer + /// will be deemed completed. The state information of the transfer is tracked in the + /// [IrqContextTimeoutOrMaxSize] structure. + /// + /// If passed buffer is equal to or larger than the specified maximum length, an + /// [BufferTooShortError] will be returned. Other RX errors are treated as partial errors + /// and returned inside the [IrqResultMaxSizeOrTimeout] structure. + pub fn on_interrupt_max_size_or_timeout_based( + &mut self, + context: &mut IrqContextTimeoutOrMaxSize, + buf: &mut [u8], + ) -> Result { + if buf.len() < context.max_len { + return Err(BufferTooShortError { + found: buf.len(), + expected: context.max_len, + }); + } + let mut result = IrqResultMaxSizeOrTimeout::default(); + + let irq_status = self.0.regs.read_irq_status(); + let rx_enabled = self.0.regs.read_enable().rx(); + + // Half-Full interrupt. We have a guaranteed amount of data we can read. + if irq_status.rx() { + // Determine the number of bytes to read, ensuring we leave 1 byte in the FIFO. + // We use this trick/hack because the timeout feature of the peripheral relies on data + // being in the RX FIFO. If data continues arriving, another half-full IRQ will fire. + // If not, the last byte(s) is/are emptied by the timeout interrupt. + let available_bytes = self.0.regs.read_rx_fifo_trigger().level().as_usize(); + + let bytes_to_read = core::cmp::min( + available_bytes.saturating_sub(1), + context.max_len - context.rx_idx, + ); + + // If this interrupt bit is set, the trigger level is available at the very least. + // Read everything as fast as possible + for _ in 0..bytes_to_read { + buf[context.rx_idx] = (self.0.read_fifo_unchecked() & 0xff) as u8; + context.rx_idx += 1; + } + + // On high-baudrates, data might be available immediately, and we possible have to + // read continuosly? Then again, the CPU should always be faster than that. I'd rather + // rely on the hardware firing another IRQ. I have not tried baudrates higher than + // 115200 so far. + } + // Timeout, empty the FIFO completely. + if irq_status.rx_timeout() { + // While there is data in the FIFO, write it into the reception buffer + loop { + if context.rx_idx == context.max_len { + break; + } + // While there is data in the FIFO, write it into the reception buffer + match self.0.read() { + Ok(byte) => { + buf[context.rx_idx] = byte; + context.rx_idx += 1; + } + Err(_) => break, + } + } + self.irq_completion_handler_max_size_timeout(&mut result, context); + return Ok(result); + } + + // RX transfer not complete, check for RX errors + if (context.rx_idx < context.max_len) && rx_enabled { + self.check_for_errors(&mut result.errors); + } + + // Clear the interrupt status bits + self.0.regs.write_irq_clr( + InterruptClear::builder() + .with_rx_overrun(true) + .with_tx_overrun(false) + .build(), + ); + Ok(result) + } + + fn check_for_errors(&self, errors: &mut Option) { + let rx_status = self.0.regs.read_rx_status(); + + if rx_status.overrun_error() || rx_status.framing_error() || rx_status.parity_error() { + let err = errors.get_or_insert(UartErrors::default()); + + if rx_status.overrun_error() { + err.overflow = true; + } + if rx_status.framing_error() { + err.framing = true; + } + if rx_status.parity_error() { + err.parity = true; + } + } + } + + fn irq_completion_handler_max_size_timeout( + &mut self, + res: &mut IrqResultMaxSizeOrTimeout, + context: &mut IrqContextTimeoutOrMaxSize, + ) { + self.disable_interrupts(); + self.0.disable(); + res.bytes_read = context.rx_idx; + res.complete = true; + context.mode = IrqReceptionMode::Idle; + context.rx_idx = 0; + } + + /// # Safety + /// + /// This API allows creating multiple UART instances when releasing the TX structure as well. + /// The user must ensure that these instances are not used to create multiple overlapping + /// UART drivers. + pub unsafe fn release(mut self) -> Rx { + self.disable_interrupts(); + self.0 + } +} + +pub mod tx_asynch; +pub use tx_asynch::*; + +pub mod rx_asynch; +pub use rx_asynch::*; diff --git a/vorago-shared-periphs/src/uart/pins_vor1x.rs b/vorago-shared-periphs/src/uart/pins_vor1x.rs new file mode 100644 index 0000000..c7c0a95 --- /dev/null +++ b/vorago-shared-periphs/src/uart/pins_vor1x.rs @@ -0,0 +1,112 @@ +// UART A pins + +use crate::{ + FunSel, + pins::{ + Pa2, Pa3, Pa8, Pa9, Pa16, Pa17, Pa18, Pa19, Pa26, Pa27, Pa30, Pa31, Pb6, Pb7, Pb8, Pb9, + Pb18, Pb19, Pb20, Pb21, Pb22, Pb23, Pin, + }, +}; + +use super::{Bank, RxPin, TxPin}; + +impl TxPin for Pin { + const BANK: Bank = Bank::Uart0; + const FUN_SEL: FunSel = FunSel::Sel2; +} +impl RxPin for Pin { + const BANK: Bank = Bank::Uart0; + const FUN_SEL: FunSel = FunSel::Sel2; +} + +impl TxPin for Pin { + const BANK: Bank = Bank::Uart0; + const FUN_SEL: FunSel = FunSel::Sel3; +} +impl RxPin for Pin { + const BANK: Bank = Bank::Uart0; + const FUN_SEL: FunSel = FunSel::Sel3; +} + +impl TxPin for Pin { + const BANK: Bank = Bank::Uart0; + const FUN_SEL: FunSel = FunSel::Sel3; +} +impl RxPin for Pin { + const BANK: Bank = Bank::Uart0; + const FUN_SEL: FunSel = FunSel::Sel3; +} + +impl TxPin for Pin { + const BANK: Bank = Bank::Uart0; + const FUN_SEL: FunSel = FunSel::Sel1; +} +impl RxPin for Pin { + const BANK: Bank = Bank::Uart0; + const FUN_SEL: FunSel = FunSel::Sel1; +} + +impl TxPin for Pin { + const BANK: Bank = Bank::Uart0; + const FUN_SEL: FunSel = FunSel::Sel1; +} +impl RxPin for Pin { + const BANK: Bank = Bank::Uart0; + const FUN_SEL: FunSel = FunSel::Sel1; +} + +// UART B pins + +impl TxPin for Pin { + const BANK: Bank = Bank::Uart1; + const FUN_SEL: FunSel = FunSel::Sel2; +} +impl RxPin for Pin { + const BANK: Bank = Bank::Uart1; + const FUN_SEL: FunSel = FunSel::Sel2; +} + +impl TxPin for Pin { + const BANK: Bank = Bank::Uart1; + const FUN_SEL: FunSel = FunSel::Sel3; +} +impl RxPin for Pin { + const BANK: Bank = Bank::Uart1; + const FUN_SEL: FunSel = FunSel::Sel3; +} + +impl TxPin for Pin { + const BANK: Bank = Bank::Uart1; + const FUN_SEL: FunSel = FunSel::Sel3; +} +impl RxPin for Pin { + const BANK: Bank = Bank::Uart1; + const FUN_SEL: FunSel = FunSel::Sel3; +} + +impl TxPin for Pin { + const BANK: Bank = Bank::Uart1; + const FUN_SEL: FunSel = FunSel::Sel1; +} +impl RxPin for Pin { + const BANK: Bank = Bank::Uart1; + const FUN_SEL: FunSel = FunSel::Sel1; +} + +impl TxPin for Pin { + const BANK: Bank = Bank::Uart1; + const FUN_SEL: FunSel = FunSel::Sel2; +} +impl RxPin for Pin { + const BANK: Bank = Bank::Uart1; + const FUN_SEL: FunSel = FunSel::Sel2; +} + +impl TxPin for Pin { + const BANK: Bank = Bank::Uart1; + const FUN_SEL: FunSel = FunSel::Sel1; +} +impl RxPin for Pin { + const BANK: Bank = Bank::Uart1; + const FUN_SEL: FunSel = FunSel::Sel1; +} diff --git a/vorago-shared-periphs/src/uart/regs.rs b/vorago-shared-periphs/src/uart/regs.rs new file mode 100644 index 0000000..878bd5f --- /dev/null +++ b/vorago-shared-periphs/src/uart/regs.rs @@ -0,0 +1,304 @@ +use core::marker::PhantomData; + +use arbitrary_int::{u5, u6, u18}; + +cfg_if::cfg_if! { + if #[cfg(feature = "vor1x")] { + /// UART A base address + pub const BASE_ADDR_0: usize = 0x4004_0000; + /// UART B base address + pub const BASE_ADDR_1: usize = 0x4004_1000; + } else if #[cfg(feature = "vor4x")] { + /// UART 0 base address + pub const BASE_ADDR_0: usize = 0x4002_4000; + /// UART 1 base address + pub const BASE_ADDR_1: usize = 0x4002_5000; + /// UART 2 base address + pub const BASE_ADDR_2: usize = 0x4001_7000; + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Bank { + Uart0 = 0, + Uart1 = 1, + #[cfg(feature = "vor4x")] + Uart2 = 2, +} + +impl Bank { + /// Unsafely steal the GPIO peripheral block for the given port. + /// + /// # Safety + /// + /// Circumvents ownership and safety guarantees by the HAL. + pub unsafe fn steal_regs(&self) -> MmioUart<'static> { + Uart::new_mmio(*self) + } +} + +#[bitbybit::bitfield(u32)] +#[derive(Debug)] +pub struct Data { + #[bit(15, rw)] + dparity: bool, + #[bits(0..=7, rw)] + value: u8, +} + +#[bitbybit::bitfield(u32, default = 0x0)] +#[derive(Debug)] +pub struct Enable { + #[bit(1, rw)] + tx: bool, + #[bit(0, rw)] + rx: bool, +} + +#[bitbybit::bitenum(u1, exhaustive = true)] +#[derive(Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Stopbits { + One = 0, + Two = 1, +} + +#[bitbybit::bitenum(u2, exhaustive = true)] +#[derive(Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum WordSize { + Five = 0b00, + Six = 0b01, + Seven = 0b10, + Eight = 0b11, +} + +#[bitbybit::bitfield(u32, default = 0x0)] +#[derive(Debug)] +pub struct Control { + #[bit(11, rw)] + baud8: bool, + #[bit(10, rw)] + auto_rts: bool, + #[bit(9, rw)] + def_rts: bool, + #[bit(8, rw)] + auto_cts: bool, + #[bit(7, rw)] + loopback_block: bool, + #[bit(6, rw)] + loopback: bool, + #[bits(4..=5, rw)] + wordsize: WordSize, + #[bit(3, rw)] + stopbits: Stopbits, + #[bit(2, rw)] + parity_manual: bool, + #[bit(1, rw)] + parity_even: bool, + #[bit(0, rw)] + parity_enable: bool, +} + +#[bitbybit::bitfield(u32, default = 0x0)] +#[derive(Debug)] +pub struct ClkScale { + #[bits(6..=23, rw)] + int: u18, + #[bits(0..=5, rw)] + frac: u6, +} + +#[bitbybit::bitfield(u32)] +#[derive(Debug)] +pub struct RxStatus { + #[bit(15, r)] + rx_rtsn: bool, + #[bit(9, r)] + rx_addr9: bool, + #[bit(8, r)] + busy_break: bool, + #[bit(7, r)] + break_error: bool, + #[bit(6, r)] + parity_error: bool, + #[bit(5, r)] + framing_error: bool, + #[bit(4, r)] + overrun_error: bool, + #[bit(3, r)] + timeout: bool, + #[bit(2, r)] + busy: bool, + #[bit(1, r)] + not_full: bool, + #[bit(0, r)] + data_available: bool, +} + +#[bitbybit::bitfield(u32)] +#[derive(Debug)] +pub struct TxStatus { + #[bit(15, r)] + tx_ctsn: bool, + #[bit(3, r)] + wr_lost: bool, + #[bit(2, r)] + tx_busy: bool, + #[bit(1, r)] + write_busy: bool, + /// There is space in the FIFO to write data. + #[bit(0, r)] + ready: bool, +} + +#[bitbybit::bitfield(u32, default = 0x0)] +#[derive(Debug)] +pub struct FifoClear { + #[bit(1, w)] + tx: bool, + #[bit(0, w)] + rx: bool, +} + +#[bitbybit::bitfield(u32)] +#[derive(Debug)] +pub struct InterruptControl { + /// Generate an interrrupt when the RX FIFO is at least half-full (FIFO count >= trigger level) + #[bit(0, rw)] + rx: bool, + /// Interrupts for status conditions (overrun, framing, parity and break) + #[bit(1, rw)] + rx_status: bool, + /// Interrupt on timeout conditions. + #[bit(2, rw)] + rx_timeout: bool, + + /// Generates an interrupt when the TX FIFO is at least half-empty (FIFO count < trigger level) + #[bit(4, rw)] + tx: bool, + /// Generates an interrupt on TX FIFO overflow. + #[bit(5, rw)] + tx_status: bool, + /// Generates an interrupt when the transmit FIFO is empty and TXBUSY is 0. + #[bit(6, rw)] + tx_empty: bool, + #[bit(7, rw)] + tx_cts: bool, +} + +#[bitbybit::bitfield(u32)] +#[derive(Debug)] +pub struct InterruptStatus { + /// Generate an interrrupt when the RX FIFO is at least half-full (FIFO count >= trigger level) + #[bit(0, r)] + rx: bool, + /// Interrupts for status conditions (overrun, framing, parity and break) + #[bit(1, r)] + rx_status: bool, + /// Interrupt on timeout conditions. + #[bit(2, r)] + rx_timeout: bool, + + /// Generates an interrupt when the TX FIFO is at least half-empty (FIFO count < trigger level) + #[bit(4, r)] + tx: bool, + /// Generates an interrupt on TX FIFO overflow. + #[bit(5, r)] + tx_status: bool, + /// Generates an interrupt when the transmit FIFO is empty and TXBUSY is 0. + #[bit(6, r)] + tx_empty: bool, + #[bit(7, r)] + tx_cts: bool, +} + +/// As specified in the VA416x0 Programmers Guide, only the RX overflow bit can be cleared. +#[bitbybit::bitfield(u32, default = 0x0)] +#[derive(Debug)] +pub struct InterruptClear { + #[bit(1, w)] + rx_overrun: bool, + /// Not sure if this does anything, the programmer guides are not consistent on this.. + #[bit(5, w)] + tx_overrun: bool, +} + +#[bitbybit::bitfield(u32)] +#[derive(Debug)] +pub struct FifoTrigger { + #[bits(0..=4, rw)] + level: u5, +} + +#[bitbybit::bitfield(u32)] +#[derive(Debug)] +pub struct State { + #[bits(0..=7, r)] + rx_state: u8, + /// Data count. + #[bits(8..=12, r)] + rx_fifo: u5, + #[bits(16..=23, r)] + tx_state: u8, + /// Data count. + #[bits(24..=28, r)] + tx_fifo: u5, +} + +#[derive(derive_mmio::Mmio)] +#[mmio(no_ctors)] +#[repr(C)] +pub struct Uart { + data: Data, + enable: Enable, + ctrl: Control, + clkscale: ClkScale, + #[mmio(PureRead)] + rx_status: RxStatus, + #[mmio(PureRead)] + tx_status: TxStatus, + #[mmio(Write)] + fifo_clr: FifoClear, + #[mmio(Write)] + txbreak: u32, + addr9: u32, + addr9mask: u32, + irq_enabled: InterruptControl, + #[mmio(PureRead)] + irq_raw: InterruptStatus, + #[mmio(PureRead)] + irq_status: InterruptStatus, + #[mmio(Write)] + irq_clr: InterruptClear, + rx_fifo_trigger: FifoTrigger, + tx_fifo_trigger: FifoTrigger, + rx_fifo_rts_trigger: u32, + #[mmio(PureRead)] + state: State, + _reserved: [u32; 0x3ED], + /// Vorago 1x value: 0x0112_07E1. Vorago 4x value: 0x0212_07E9 + #[mmio(PureRead)] + perid: u32, +} + +static_assertions::const_assert_eq!(core::mem::size_of::(), 0x1000); + +impl Uart { + fn new_mmio_at(base: usize) -> MmioUart<'static> { + MmioUart { + ptr: base as *mut _, + phantom: PhantomData, + } + } + + pub fn new_mmio(bank: Bank) -> MmioUart<'static> { + match bank { + Bank::Uart0 => Self::new_mmio_at(BASE_ADDR_0), + Bank::Uart1 => Self::new_mmio_at(BASE_ADDR_1), + #[cfg(feature = "vor4x")] + Bank::Uart2 => Self::new_mmio_at(BASE_ADDR_2), + } + } +} diff --git a/va108xx-hal/src/uart/rx_asynch.rs b/vorago-shared-periphs/src/uart/rx_asynch.rs similarity index 71% rename from va108xx-hal/src/uart/rx_asynch.rs rename to vorago-shared-periphs/src/uart/rx_asynch.rs index 215bfae..19687ad 100644 --- a/va108xx-hal/src/uart/rx_asynch.rs +++ b/vorago-shared-periphs/src/uart/rx_asynch.rs @@ -14,34 +14,31 @@ //! //! 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 arbitrary_int::Number; 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}; +use super::{ + Bank, Rx, UartErrors, + regs::{InterruptClear, MmioUart}, +}; 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, + id: Bank, } impl RxFuture { - pub fn new(_rx: &mut Rx) -> Self { - RX_READ_ACTIVE[Uart::IDX as usize].store(true, Ordering::Relaxed); - Self { - uart_idx: Uart::IDX as usize, - } + pub fn new(rx: &mut Rx) -> Self { + RX_READ_ACTIVE[rx.id as usize].store(true, Ordering::Relaxed); + Self { id: rx.id } } } @@ -52,8 +49,8 @@ impl Future for RxFuture { self: core::pin::Pin<&mut Self>, cx: &mut core::task::Context<'_>, ) -> core::task::Poll { - UART_RX_WAKERS[self.uart_idx].register(cx.waker()); - if RX_HAS_DATA[self.uart_idx].load(Ordering::Relaxed) { + UART_RX_WAKERS[self.id as usize].register(cx.waker()); + if RX_HAS_DATA[self.id as usize].load(Ordering::Relaxed) { return core::task::Poll::Ready(Ok(())); } core::task::Poll::Pending @@ -69,21 +66,18 @@ pub struct AsyncUartErrors { pub uart_errors: UartErrors, } -fn on_interrupt_handle_rx_errors(uart: &'static uart_base::RegisterBlock) -> Option { - 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() - { +fn on_interrupt_handle_rx_errors(uart: &mut MmioUart<'static>) -> Option { + let rx_status = uart.read_rx_status(); + if rx_status.overrun_error() || rx_status.framing_error() || rx_status.parity_error() { let mut errors_val = UartErrors::default(); - if rx_status.rxovr().bit_is_set() { + if rx_status.overrun_error() { errors_val.overflow = true; } - if rx_status.rxfrm().bit_is_set() { + if rx_status.framing_error() { errors_val.framing = true; } - if rx_status.rxpar().bit_is_set() { + if rx_status.parity_error() { errors_val.parity = true; } return Some(errors_val); @@ -92,12 +86,11 @@ fn on_interrupt_handle_rx_errors(uart: &'static uart_base::RegisterBlock) -> Opt } fn on_interrupt_rx_common_post_processing( - bank: Bank, + id: Bank, rx_enabled: bool, read_some_data: bool, - irq_end: u32, ) -> Option { - let idx = bank as usize; + let idx = id as usize; if read_some_data { RX_HAS_DATA[idx].store(true, Ordering::Relaxed); if RX_READ_ACTIVE[idx].load(Ordering::Relaxed) { @@ -106,14 +99,19 @@ fn on_interrupt_rx_common_post_processing( } let mut errors = None; - let uart_regs = unsafe { bank.reg_block() }; + let mut uart_regs = unsafe { id.steal_regs() }; // Check for RX errors if rx_enabled { - errors = on_interrupt_handle_rx_errors(uart_regs); + errors = on_interrupt_handle_rx_errors(&mut uart_regs); } // Clear the interrupt status bits - uart_regs.irq_clr().write(|w| unsafe { w.bits(irq_end) }); + uart_regs.write_irq_clr( + InterruptClear::builder() + .with_rx_overrun(true) + .with_tx_overrun(false) + .build(), + ); errors } @@ -135,21 +133,21 @@ pub fn on_interrupt_rx_async_heapless_queue_overwriting( prod: &mut heapless::spsc::Producer, shared_consumer: &Mutex>>>, ) -> 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 uart_regs = unsafe { bank.steal_regs() }; + let irq_status = uart_regs.read_irq_status(); + let irq_enabled = uart_regs.read_irq_enabled(); + let rx_enabled = irq_enabled.rx(); 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 irq_status.rx() { + let available_bytes = uart_regs.read_rx_fifo_trigger().level().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(); + let byte = uart_regs.read_data().value(); if !prod.ready() { queue_overflow = true; critical_section::with(|cs| { @@ -157,16 +155,16 @@ pub fn on_interrupt_rx_async_heapless_queue_overwriting( cons_ref.as_mut().unwrap().dequeue(); }); } - prod.enqueue(byte as u8).ok(); + prod.enqueue(byte).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() { + if irq_status.rx_timeout() { + while uart_regs.read_rx_status().data_available() { // While there is data in the FIFO, write it into the reception buffer - let byte = uart_regs.data().read().bits(); + let byte = uart_regs.read_data().value(); if !prod.ready() { queue_overflow = true; critical_section::with(|cs| { @@ -174,13 +172,12 @@ pub fn on_interrupt_rx_async_heapless_queue_overwriting( cons_ref.as_mut().unwrap().dequeue(); }); } - prod.enqueue(byte as u8).ok(); + prod.enqueue(byte).ok(); } read_some_data = true; } - let uart_errors = - on_interrupt_rx_common_post_processing(bank, rx_enabled, read_some_data, irq_end.bits()); + let uart_errors = on_interrupt_rx_common_post_processing(bank, rx_enabled, read_some_data); if uart_errors.is_some() || queue_overflow { return Err(AsyncUartErrors { queue_overflow, @@ -204,44 +201,43 @@ pub fn on_interrupt_rx_async_heapless_queue( 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 uart_regs = unsafe { bank.steal_regs() }; + let irq_status = uart_regs.read_irq_status(); + let irq_enabled = uart_regs.read_irq_enabled(); + let rx_enabled = irq_enabled.rx(); 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 irq_status.rx() { + let available_bytes = uart_regs.read_rx_fifo_trigger().level().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(); + let byte = uart_regs.read_data().value(); if !prod.ready() { queue_overflow = true; } - prod.enqueue(byte as u8).ok(); + prod.enqueue(byte).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() { + if irq_status.rx_timeout() { + while uart_regs.read_rx_status().data_available() { // While there is data in the FIFO, write it into the reception buffer - let byte = uart.data().read().bits(); + let byte = uart_regs.read_data().value(); if !prod.ready() { queue_overflow = true; } - prod.enqueue(byte as u8).ok(); + prod.enqueue(byte).ok(); } read_some_data = true; } - let uart_errors = - on_interrupt_rx_common_post_processing(bank, rx_enabled, read_some_data, irq_end.bits()); + let uart_errors = on_interrupt_rx_common_post_processing(bank, rx_enabled, read_some_data); if uart_errors.is_some() || queue_overflow { return Err(AsyncUartErrors { queue_overflow, @@ -259,39 +255,39 @@ impl Drop for ActiveReadGuard { } } -struct RxAsyncInner { - rx: Rx, +struct RxAsyncInner { + rx: Rx, 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(Option>); +pub struct RxAsync(Option>); -impl ErrorType for RxAsync { +impl ErrorType for RxAsync { /// Error reporting is done using the result of the interrupt functions. type Error = Infallible; } -fn stop_async_rx(rx: &mut Rx) { +fn stop_async_rx(rx: &mut Rx) { rx.disable_interrupts(); rx.disable(); rx.clear_fifo(); } -impl RxAsync { +impl RxAsync { /// 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, queue: heapless::spsc::Consumer<'static, u8, N>) -> Self { + pub fn new(mut rx: Rx, 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_interrupts(true); rx.enable(); }); Self(Some(RxAsyncInner { rx, queue })) @@ -301,27 +297,28 @@ impl RxAsync { stop_async_rx(&mut self.0.as_mut().unwrap().rx); } - pub fn release(mut self) -> (Rx, heapless::spsc::Consumer<'static, u8, N>) { + pub fn release(mut self) -> (Rx, heapless::spsc::Consumer<'static, u8, N>) { self.stop(); let inner = self.0.take().unwrap(); (inner.rx, inner.queue) } } -impl Drop for RxAsync { +impl Drop for RxAsync { fn drop(&mut self) { self.stop(); } } -impl embedded_io_async::Read for RxAsync { +impl embedded_io_async::Read for RxAsync { async fn read(&mut self, buf: &mut [u8]) -> Result { + let inner = self.0.as_ref().unwrap(); // 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); + if inner.queue.len() == 0 { + RX_HAS_DATA[inner.rx.id as usize].store(false, Ordering::Relaxed); } - let _guard = ActiveReadGuard(Uart::IDX as usize); + let _guard = ActiveReadGuard(inner.rx.id 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) { @@ -343,8 +340,8 @@ impl embedded_io_async::Read for RxAsync { - rx: Rx, +struct RxAsyncOverwritingInner { + rx: Rx, pub shared_consumer: &'static Mutex>>>, } @@ -352,23 +349,21 @@ struct RxAsyncOverwritingInner { /// /// If the ring buffer becomes full, the oldest data will be overwritten when using the /// [on_interrupt_rx_overwriting] interrupt handlers. -pub struct RxAsyncOverwriting( - Option>, -); +pub struct RxAsyncOverwriting(Option>); -impl ErrorType for RxAsyncOverwriting { +impl ErrorType for RxAsyncOverwriting { /// Error reporting is done using the result of the interrupt functions. type Error = Infallible; } -impl RxAsyncOverwriting { +impl RxAsyncOverwriting { /// 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, + mut rx: Rx, shared_consumer: &'static Mutex>>>, ) -> Self { rx.disable_interrupts(); @@ -376,7 +371,7 @@ impl RxAsyncOverwriting { rx.clear_fifo(); // Enable those together. critical_section::with(|_| { - rx.enable_interrupts(); + rx.enable_interrupts(true); rx.enable(); }); Self(Some(RxAsyncOverwritingInner { @@ -389,32 +384,34 @@ impl RxAsyncOverwriting { stop_async_rx(&mut self.0.as_mut().unwrap().rx); } - pub fn release(mut self) -> Rx { + pub fn release(mut self) -> Rx { self.stop(); let inner = self.0.take().unwrap(); inner.rx } } -impl Drop for RxAsyncOverwriting { +impl Drop for RxAsyncOverwriting { fn drop(&mut self) { self.stop(); } } -impl embedded_io_async::Read for RxAsyncOverwriting { +impl embedded_io_async::Read for RxAsyncOverwriting { async fn read(&mut self, buf: &mut [u8]) -> Result { + let inner = self.0.as_ref().unwrap(); + let id = inner.rx.id as usize; // 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); + let queue = inner.shared_consumer.borrow(cs); if queue.borrow().as_ref().unwrap().len() == 0 { - RX_HAS_DATA[Uart::IDX as usize].store(false, Ordering::Relaxed); + RX_HAS_DATA[id].store(false, Ordering::Relaxed); } }); - let _guard = ActiveReadGuard(Uart::IDX as usize); - let mut handle_data_in_queue = |inner: &mut RxAsyncOverwritingInner| { + let _guard = ActiveReadGuard(id); + let mut handle_data_in_queue = |inner: &mut RxAsyncOverwritingInner| { critical_section::with(|cs| { let mut consumer_ref = inner.shared_consumer.borrow(cs).borrow_mut(); let consumer = consumer_ref.as_mut().unwrap(); diff --git a/va108xx-hal/src/uart/tx_asynch.rs b/vorago-shared-periphs/src/uart/tx_asynch.rs similarity index 58% rename from va108xx-hal/src/uart/tx_asynch.rs rename to vorago-shared-periphs/src/uart/tx_asynch.rs index 3dc36f7..5234f76 100644 --- a/va108xx-hal/src/uart/tx_asynch.rs +++ b/vorago-shared-periphs/src/uart/tx_asynch.rs @@ -7,16 +7,13 @@ //! //! 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 raw_slice::RawBufSlice; use super::*; @@ -33,28 +30,35 @@ static TX_DONE: [AtomicBool; 2] = [const { AtomicBool::new(false) }; 2]; /// 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 mut uart = unsafe { bank.steal_regs() }; let idx = bank as usize; - let irq_enb = uart.irq_enb().read(); + let irq_enabled = uart.read_irq_enabled(); // IRQ is not related to TX. - if irq_enb.irq_tx().bit_is_clear() || irq_enb.irq_tx_empty().bit_is_clear() { + if !irq_enabled.tx() && !irq_enabled.tx_empty() { return; } - let tx_status = uart.txstatus().read(); - let unexpected_overrun = tx_status.wrlost().bit_is_set(); + let tx_status = uart.read_tx_status(); + let unexpected_overrun = tx_status.wr_lost(); 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() + // 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 { context.slice.get().unwrap() }; + if context.progress >= slice.len() && !tx_status.tx_busy() { + uart.modify_irq_enabled(|mut value| { + value.set_tx(false); + value.set_tx_empty(false); + value.set_tx_status(false); + value + }); + uart.modify_enable(|mut value| { + value.set_tx(false); + value }); - 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); @@ -65,18 +69,13 @@ pub fn on_interrupt_tx(bank: Bank) { 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 { + while context.progress < slice.len() { + if !uart.read_tx_status().ready() { 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) }); + uart.write_data(Data::new_with_raw_value(slice[context.progress] as u32)); context.progress += 1; } @@ -100,52 +99,13 @@ impl TxContext { Self { progress: 0, tx_overrun: false, - slice: RawBufSlice::new_empty(), + slice: RawBufSlice::new_nulled(), } } } -#[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, + id: Bank, } impl TxFuture { @@ -153,22 +113,21 @@ impl TxFuture { /// /// 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(tx: &mut Tx, data: &[u8]) -> Self { - TX_DONE[Uart::IDX as usize].store(false, core::sync::atomic::Ordering::Relaxed); + pub unsafe fn new(tx: &mut Tx, data: &[u8]) -> Self { + TX_DONE[tx.id 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) }); + tx.regs.write_data(Data::new_with_raw_value(*data as u32)); } critical_section::with(|cs| { - let context_ref = TX_CONTEXTS[Uart::IDX as usize].borrow(cs); + let context_ref = TX_CONTEXTS[tx.id as usize].borrow(cs); let mut context = context_ref.borrow_mut(); - context.slice.set(data); + unsafe { context.slice.set(data) }; context.progress = init_fill_count; // Ensure those are enabled inside a critical section at the same time. Can lead to @@ -176,9 +135,7 @@ impl TxFuture { tx.enable_interrupts(); tx.enable(); }); - Self { - uart_idx: Uart::IDX as usize, - } + Self { id: tx.id } } } @@ -189,10 +146,10 @@ impl Future for TxFuture { self: core::pin::Pin<&mut Self>, cx: &mut core::task::Context<'_>, ) -> core::task::Poll { - UART_TX_WAKERS[self.uart_idx].register(cx.waker()); - if TX_DONE[self.uart_idx].swap(false, core::sync::atomic::Ordering::Relaxed) { + UART_TX_WAKERS[self.id as usize].register(cx.waker()); + if TX_DONE[self.id as usize].swap(false, core::sync::atomic::Ordering::Relaxed) { let progress = critical_section::with(|cs| { - TX_CONTEXTS[self.uart_idx].borrow(cs).borrow().progress + TX_CONTEXTS[self.id as usize].borrow(cs).borrow().progress }); return core::task::Poll::Ready(Ok(progress)); } @@ -202,28 +159,22 @@ impl Future for TxFuture { 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!(), - }; + let mut reg_block = unsafe { self.id.steal_regs() }; - disable_tx_interrupts(reg_block); - disable_tx(reg_block); + disable_tx_interrupts(&mut reg_block); + disable_tx(&mut reg_block); } } -pub struct TxAsync { - tx: Tx, -} +pub struct TxAsync(Tx); -impl TxAsync { - pub fn new(tx: Tx) -> Self { - Self { tx } +impl TxAsync { + pub fn new(tx: Tx) -> Self { + Self(tx) } - pub fn release(self) -> Tx { - self.tx + pub fn release(self) -> Tx { + self.0 } } @@ -238,17 +189,17 @@ impl embedded_io_async::Error for TxOverrunError { } } -impl embedded_io::ErrorType for TxAsync { +impl embedded_io::ErrorType for TxAsync { type Error = TxOverrunError; } -impl Write for TxAsync { +impl Write for TxAsync { /// 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 { - let fut = unsafe { TxFuture::new(&mut self.tx, buf) }; + let fut = unsafe { TxFuture::new(&mut self.0, buf) }; fut.await } } diff --git a/vscode/launch.json b/vscode/launch.json index 01362f9..a57fb55 100644 --- a/vscode/launch.json +++ b/vscode/launch.json @@ -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 @@ } }, ] -} +} \ No newline at end of file diff --git a/vscode/tasks.json b/vscode/tasks.json index 595a42c..5b26aec 100644 --- a/vscode/tasks.json +++ b/vscode/tasks.json @@ -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 @@ ] } ] -} +} \ No newline at end of file