From f376a43f4100441f82ea2bf7748688d6814342a1 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Sun, 19 Dec 2021 14:18:10 +0100 Subject: [PATCH] Major refactoring - Improved IRQ handling, which makes most unsafe unmask operations in user code absolete - Add first UART RX handlers which use an IRQ --- Cargo.toml | 16 --- examples/blinky.rs | 10 +- examples/cascade.rs | 33 ++--- examples/pwm.rs | 25 +--- examples/tests.rs | 13 +- examples/timer-ticks.rs | 15 +- examples/uart-irq.rs | 44 ++++++ src/gpio/dynpins.rs | 16 +-- src/gpio/pins.rs | 56 ++++---- src/timer.rs | 66 ++++++--- src/uart.rs | 299 +++++++++++++++++++++++++++++++++++++++- src/utility.rs | 31 +++++ 12 files changed, 491 insertions(+), 133 deletions(-) create mode 100644 examples/uart-irq.rs diff --git a/Cargo.toml b/Cargo.toml index 1102a4e..a6dfde1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,19 +45,3 @@ opt-level = "s" # [profile.release-lto] # inherits = "release" # lto = true - -[[example]] -name = "timer-ticks" -required-features = ["rt"] - -[[example]] -name = "tests" -required-features = ["rt"] - -[[example]] -name = "pwm" -required-features = ["rt"] - -[[example]] -name = "cascade" -required-features = ["rt"] diff --git a/examples/blinky.rs b/examples/blinky.rs index 5c0cbf2..eaa67b6 100644 --- a/examples/blinky.rs +++ b/examples/blinky.rs @@ -9,7 +9,7 @@ use cortex_m_rt::entry; use embedded_hal::digital::v2::ToggleableOutputPin; use panic_halt as _; -use va108xx_hal::{gpio::PinsA, pac, prelude::*, timer::set_up_ms_timer}; +use va108xx_hal::{gpio::PinsA, pac, prelude::*, timer::CountDownTimer}; #[entry] fn main() -> ! { @@ -18,13 +18,7 @@ fn main() -> ! { let mut led1 = porta.pa10.into_push_pull_output(); let mut led2 = porta.pa7.into_push_pull_output(); let mut led3 = porta.pa6.into_push_pull_output(); - let mut delay = set_up_ms_timer( - &mut dp.SYSCONFIG, - &mut dp.IRQSEL, - 50.mhz().into(), - dp.TIM0, - pac::Interrupt::OC0, - ); + let mut delay = CountDownTimer::new(&mut dp.SYSCONFIG, 50.mhz(), dp.TIM0); for _ in 0..10 { led1.set_low().ok(); led2.set_low().ok(); diff --git a/examples/cascade.rs b/examples/cascade.rs index 5c9ca21..3b1c363 100644 --- a/examples/cascade.rs +++ b/examples/cascade.rs @@ -14,8 +14,8 @@ use va108xx_hal::{ pac::{self, interrupt, TIM4, TIM5}, prelude::*, timer::{ - default_ms_irq_handler, set_up_ms_timer, CascadeCtrl, CascadeSource, CountDownTimer, Delay, - Event, + default_ms_irq_handler, set_up_ms_delay_provider, CascadeCtrl, CascadeSource, + CountDownTimer, Event, IrqCfg, }, }; @@ -28,23 +28,16 @@ fn main() -> ! { rprintln!("-- VA108xx Cascade example application--"); let mut dp = pac::Peripherals::take().unwrap(); - let timer = set_up_ms_timer( - &mut dp.SYSCONFIG, - &mut dp.IRQSEL, - 50.mhz().into(), - dp.TIM0, - pac::Interrupt::OC0, - ); - let mut delay = Delay::new(timer); + let mut delay = set_up_ms_delay_provider(&mut dp.SYSCONFIG, 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, - &mut dp.SYSCONFIG, - &mut dp.IRQSEL, - va108xx::Interrupt::OC1, + IrqCfg::new(va108xx::Interrupt::OC1, true, false), + Some(&mut dp.IRQSEL), + Some(&mut dp.SYSCONFIG), ); // First target for cascade @@ -63,9 +56,9 @@ fn main() -> ! { // the timer expires cascade_target_1.listen( Event::TimeOut, - &mut dp.SYSCONFIG, - &mut dp.IRQSEL, - va108xx::Interrupt::OC2, + IrqCfg::new(va108xx::Interrupt::OC2, true, false), + Some(&mut dp.IRQSEL), + Some(&mut dp.SYSCONFIG), ); // 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 @@ -89,9 +82,9 @@ fn main() -> ! { // the timer expires cascade_target_2.listen( Event::TimeOut, - &mut dp.SYSCONFIG, - &mut dp.IRQSEL, - va108xx::Interrupt::OC3, + IrqCfg::new(va108xx::Interrupt::OC3, true, false), + Some(&mut dp.IRQSEL), + Some(&mut dp.SYSCONFIG), ); // 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 @@ -112,7 +105,7 @@ fn main() -> ! { loop { rprintln!("-- Triggering cascade in 0.5 seconds --"); cascade_triggerer.start(2.hz()); - delay.delay_ms(5000); + delay.delay_ms(5000_u16); } } diff --git a/examples/pwm.rs b/examples/pwm.rs index b09c190..e181611 100644 --- a/examples/pwm.rs +++ b/examples/pwm.rs @@ -8,10 +8,10 @@ use panic_rtt_target as _; use rtt_target::{rprintln, rtt_init_print}; use va108xx_hal::{ gpio::PinsA, - pac::{self, interrupt}, + pac, prelude::*, pwm::{self, get_duty_from_percent, ReducedPwmPin, PWMA, PWMB}, - timer::{default_ms_irq_handler, set_up_ms_timer, Delay}, + timer::set_up_ms_delay_provider, }; #[entry] @@ -26,17 +26,7 @@ fn main() -> ! { &mut dp.SYSCONFIG, 10.hz(), ); - let timer = set_up_ms_timer( - &mut dp.SYSCONFIG, - &mut dp.IRQSEL, - 50.mhz().into(), - dp.TIM0, - pac::Interrupt::OC0, - ); - let mut delay = Delay::new(timer); - unsafe { - cortex_m::peripheral::NVIC::unmask(pac::Interrupt::OC0); - } + let mut delay = set_up_ms_delay_provider(&mut dp.SYSCONFIG, 50.mhz(), dp.TIM0); let mut current_duty_cycle = 0.0; PwmPin::set_duty(&mut pwm, get_duty_from_percent(current_duty_cycle)); PwmPin::enable(&mut pwm); @@ -46,7 +36,7 @@ fn main() -> ! { loop { // Increase duty cycle continuously while current_duty_cycle < 1.0 { - delay.delay_ms(200); + delay.delay_ms(200_u16); current_duty_cycle += 0.02; PwmPin::set_duty(&mut reduced_pin, get_duty_from_percent(current_duty_cycle)); } @@ -60,7 +50,7 @@ fn main() -> ! { 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 { - delay.delay_ms(200); + delay.delay_ms(200_u16); lower_limit += 0.01; upper_limit -= 0.01; pwmb.set_pwmb_lower_limit(get_duty_from_percent(lower_limit)); @@ -71,8 +61,3 @@ fn main() -> ! { reduced_pin = ReducedPwmPin::::from(pwmb); } } - -#[interrupt] -fn OC0() { - default_ms_irq_handler() -} diff --git a/examples/tests.rs b/examples/tests.rs index 17ce4fc..2826c25 100644 --- a/examples/tests.rs +++ b/examples/tests.rs @@ -14,7 +14,7 @@ use va108xx_hal::{ pac::{self, interrupt}, prelude::*, time::Hertz, - timer::{default_ms_irq_handler, set_up_ms_timer, CountDownTimer, Delay}, + timer::{default_ms_irq_handler, set_up_ms_timer, CountDownTimer, Delay, IrqCfg}, }; #[allow(dead_code)] @@ -146,15 +146,12 @@ fn main() -> ! { } TestCase::DelayMs => { let ms_timer = set_up_ms_timer( + IrqCfg::new(pac::Interrupt::OC0, true, true), &mut dp.SYSCONFIG, - &mut dp.IRQSEL, - 50.mhz().into(), + Some(&mut dp.IRQSEL), + 50.mhz(), dp.TIM0, - pac::Interrupt::OC0, ); - unsafe { - cortex_m::peripheral::NVIC::unmask(pac::Interrupt::OC0); - } let mut delay = Delay::new(ms_timer); for _ in 0..5 { led1.toggle().ok(); @@ -163,7 +160,7 @@ fn main() -> ! { delay.delay_ms(500); } - let mut delay_timer = CountDownTimer::new(&mut dp.SYSCONFIG, 50.mhz().into(), dp.TIM1); + let mut delay_timer = CountDownTimer::new(&mut dp.SYSCONFIG, 50.mhz(), dp.TIM1); let mut pa0 = pinsa.pa0.into_push_pull_output(); for _ in 0..5 { led1.toggle().ok(); diff --git a/examples/timer-ticks.rs b/examples/timer-ticks.rs index 7cb66e7..6cb1a78 100644 --- a/examples/timer-ticks.rs +++ b/examples/timer-ticks.rs @@ -12,7 +12,7 @@ use va108xx_hal::{ pac::{self, interrupt}, prelude::*, time::Hertz, - timer::{default_ms_irq_handler, set_up_ms_timer, CountDownTimer, Event, MS_COUNTER}, + timer::{default_ms_irq_handler, set_up_ms_timer, CountDownTimer, Event, IrqCfg, MS_COUNTER}, }; #[allow(dead_code)] @@ -65,22 +65,21 @@ fn main() -> ! { } LibType::Hal => { set_up_ms_timer( + IrqCfg::new(interrupt::OC0, true, true), &mut dp.SYSCONFIG, - &mut dp.IRQSEL, - 50.mhz().into(), + Some(&mut dp.IRQSEL), + 50.mhz(), dp.TIM0, - interrupt::OC0, ); let mut second_timer = CountDownTimer::new(&mut dp.SYSCONFIG, get_sys_clock().unwrap(), dp.TIM1); second_timer.listen( Event::TimeOut, - &mut dp.SYSCONFIG, - &mut dp.IRQSEL, - interrupt::OC1, + IrqCfg::new(interrupt::OC1, true, true), + Some(&mut dp.IRQSEL), + Some(&mut dp.SYSCONFIG), ); second_timer.start(1.hz()); - unmask_irqs(); } } loop { diff --git a/examples/uart-irq.rs b/examples/uart-irq.rs new file mode 100644 index 0000000..8f6ed31 --- /dev/null +++ b/examples/uart-irq.rs @@ -0,0 +1,44 @@ +//! UART example application. Sends a test string over a UART and then enters +//! echo mode +#![no_main] +#![no_std] + +use core::fmt::Write; +use cortex_m_rt::entry; +use panic_rtt_target as _; +use rtt_target::{rprintln, rtt_init_print}; +use va108xx_hal::{gpio::PinsB, pac, prelude::*, uart}; + +#[entry] +fn main() -> ! { + rtt_init_print!(); + rprintln!("-- VA108xx UART example application--"); + + let mut dp = pac::Peripherals::take().unwrap(); + + let gpiob = PinsB::new(&mut dp.SYSCONFIG, Some(dp.IOCONFIG), dp.PORTB); + let tx = gpiob.pb21.into_funsel_1(); + let rx = gpiob.pb20.into_funsel_1(); + + let uartb = uart::Uart::uartb( + dp.UARTB, + (tx, rx), + 115200.bps(), + &mut dp.SYSCONFIG, + 50.mhz(), + ); + let (mut tx, mut rx) = uartb.split(); + writeln!(tx, "Hello World\r").unwrap(); + loop { + // Echo what is received on the serial link. + match rx.read() { + Ok(recv) => { + nb::block!(tx.write(recv)).expect("TX send error"); + } + Err(nb::Error::WouldBlock) => (), + Err(nb::Error::Other(uart_error)) => { + rprintln!("UART receive error {:?}", uart_error); + } + } + } +} diff --git a/src/gpio/dynpins.rs b/src/gpio/dynpins.rs index f3b2122..5513fa7 100644 --- a/src/gpio/dynpins.rs +++ b/src/gpio/dynpins.rs @@ -66,8 +66,8 @@ use super::{ }; use crate::{ clock::FilterClkSel, - pac::{self, IRQSEL, SYSCONFIG}, - utility::Funsel, + pac::{IRQSEL, SYSCONFIG}, + utility::{IrqCfg, Funsel}, }; use embedded_hal::digital::v2::{InputPin, OutputPin, ToggleableOutputPin}; use paste::paste; @@ -344,14 +344,14 @@ impl DynPin { pub fn interrupt_edge( mut self, edge_type: InterruptEdge, + irq_cfg: IrqCfg, syscfg: Option<&mut SYSCONFIG>, - irqsel: &mut IRQSEL, - interrupt: pac::Interrupt, + irqsel: Option<&mut IRQSEL> ) -> Result { match self.mode { DynPinMode::Input(_) | DynPinMode::Output(_) => { - self._irq_enb(syscfg, irqsel, interrupt); self.regs.interrupt_edge(edge_type); + self.irq_enb(irq_cfg, syscfg, irqsel); Ok(self) } _ => Err(PinError::InvalidPinType), @@ -361,14 +361,14 @@ impl DynPin { pub fn interrupt_level( mut self, level_type: InterruptLevel, + irq_cfg: IrqCfg, syscfg: Option<&mut SYSCONFIG>, - irqsel: &mut IRQSEL, - interrupt: crate::pac::Interrupt, + irqsel: Option<&mut IRQSEL> ) -> Result { match self.mode { DynPinMode::Input(_) | DynPinMode::Output(_) => { - self._irq_enb(syscfg, irqsel, interrupt); self.regs.interrupt_level(level_type); + self.irq_enb(irq_cfg, syscfg, irqsel); Ok(self) } _ => Err(PinError::InvalidPinType), diff --git a/src/gpio/pins.rs b/src/gpio/pins.rs index f2b7c7c..d11c04e 100644 --- a/src/gpio/pins.rs +++ b/src/gpio/pins.rs @@ -92,9 +92,10 @@ use super::dynpins::{DynAlternate, DynGroup, DynInput, DynOutput, DynPinId, DynPinMode}; use super::reg::RegisterInterface; use crate::{ - pac::{self, IOCONFIG, IRQSEL, PORTA, PORTB, SYSCONFIG}, + pac::{IOCONFIG, IRQSEL, PORTA, PORTB, SYSCONFIG}, typelevel::Is, Sealed, + utility::IrqCfg }; use core::convert::Infallible; use core::marker::PhantomData; @@ -360,6 +361,7 @@ impl AnyPin for Pin { macro_rules! common_reg_if_functions { () => { paste!( + #[inline] pub fn datamask(&self) -> bool { self.regs.datamask() @@ -397,11 +399,11 @@ macro_rules! common_reg_if_functions { self.regs.write_pin_masked(false) } - fn _irq_enb( + fn irq_enb( &mut self, + irq_cfg: crate::utility::IrqCfg, syscfg: Option<&mut va108xx::SYSCONFIG>, - irqsel: &mut va108xx::IRQSEL, - interrupt: va108xx::Interrupt, + irqsel: Option<&mut va108xx::IRQSEL> ) { if syscfg.is_some() { crate::clock::enable_peripheral_clock( @@ -410,15 +412,19 @@ macro_rules! common_reg_if_functions { ); } self.regs.enable_irq(); - match self.regs.id().group { - // Set the correct interrupt number in the IRQSEL register - DynGroup::A => { - irqsel.porta[self.regs.id().num as usize] - .write(|w| unsafe { w.bits(interrupt as u32) }); - } - DynGroup::B => { - irqsel.portb[self.regs.id().num as usize] - .write(|w| unsafe { w.bits(interrupt as u32) }); + if let Some(irqsel) = irqsel { + if irq_cfg.route { + match self.regs.id().group { + // Set the correct interrupt number in the IRQSEL register + DynGroup::A => { + irqsel.porta[self.regs.id().num as usize] + .write(|w| unsafe { w.bits(irq_cfg.irq as u32) }); + } + DynGroup::B => { + irqsel.portb[self.regs.id().num as usize] + .write(|w| unsafe { w.bits(irq_cfg.irq as u32) }); + } + } } } } @@ -577,24 +583,24 @@ impl Pin> { pub fn interrupt_edge( mut self, edge_type: InterruptEdge, + irq_cfg: IrqCfg, syscfg: Option<&mut SYSCONFIG>, - irqsel: &mut IRQSEL, - interrupt: pac::Interrupt, + irqsel: Option<&mut IRQSEL> ) -> Self { - self._irq_enb(syscfg, irqsel, interrupt); self.regs.interrupt_edge(edge_type); + self.irq_enb(irq_cfg, syscfg, irqsel); self } pub fn interrupt_level( mut self, level_type: InterruptLevel, + irq_cfg: IrqCfg, syscfg: Option<&mut SYSCONFIG>, - irqsel: &mut IRQSEL, - interrupt: pac::Interrupt, + irqsel: Option<&mut IRQSEL> ) -> Self { - self._irq_enb(syscfg, irqsel, interrupt); self.regs.interrupt_level(level_type); + self.irq_enb(irq_cfg, syscfg, irqsel); self } } @@ -622,24 +628,24 @@ impl Pin> { pub fn interrupt_edge( mut self, edge_type: InterruptEdge, + irq_cfg: IrqCfg, syscfg: Option<&mut SYSCONFIG>, - irqsel: &mut IRQSEL, - interrupt: pac::Interrupt, + irqsel: Option<&mut IRQSEL> ) -> Self { - self._irq_enb(syscfg, irqsel, interrupt); self.regs.interrupt_edge(edge_type); + self.irq_enb(irq_cfg, syscfg, irqsel); self } pub fn interrupt_level( mut self, level_type: InterruptLevel, + irq_cfg: IrqCfg, syscfg: Option<&mut SYSCONFIG>, - irqsel: &mut IRQSEL, - interrupt: pac::Interrupt, + irqsel: Option<&mut IRQSEL> ) -> Self { - self._irq_enb(syscfg, irqsel, interrupt); self.regs.interrupt_level(level_type); + self.irq_enb(irq_cfg, syscfg, irqsel); self } } diff --git a/src/timer.rs b/src/timer.rs index 917b21d..19b3607 100644 --- a/src/timer.rs +++ b/src/timer.rs @@ -4,8 +4,10 @@ //! //! - [MS and second tick implementation](https://egit.irs.uni-stuttgart.de/rust/va108xx-hal/src/branch/main/examples/timer-ticks.rs) //! - [Cascade feature example](https://egit.irs.uni-stuttgart.de/rust/va108xx-hal/src/branch/main/examples/cascade.rs) +pub use crate::utility::IrqCfg; use crate::{ clock::{enable_peripheral_clock, PeripheralClocks}, + utility::unmask_irq, 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, @@ -27,7 +29,7 @@ use embedded_hal::{ blocking::delay, timer::{Cancel, CountDown, Periodic}, }; -use va108xx::{Interrupt, IRQSEL, SYSCONFIG}; +use va108xx::{IRQSEL, SYSCONFIG}; use void::Void; const IRQ_DST_NONE: u32 = 0xffffffff; @@ -386,6 +388,7 @@ unsafe impl TimPinInterface for TimDynRegister { pub struct CountDownTimer { tim: TimRegister, curr_freq: Hertz, + irq_cfg: Option, sys_clk: Hertz, rst_val: u32, last_cnt: u32, @@ -482,6 +485,7 @@ impl CountDownTimer { let cd_timer = CountDownTimer { tim: unsafe { TimRegister::new(tim) }, sys_clk: sys_clk.into(), + irq_cfg: None, rst_val: 0, curr_freq: 0.hz(), listening: false, @@ -491,21 +495,28 @@ impl CountDownTimer { cd_timer } - /// Listen for events. This also actives the IRQ in the IRQSEL register - /// for the provided interrupt. It also actives the peripheral clock for - /// IRQSEL + /// 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, - syscfg: &mut SYSCONFIG, - irqsel: &mut IRQSEL, - interrupt: Interrupt, + irq_cfg: IrqCfg, + irq_sel: Option<&mut IRQSEL>, + sys_cfg: Option<&mut SYSCONFIG>, ) { match event { Event::TimeOut => { - enable_peripheral_clock(syscfg, PeripheralClocks::Irqsel); - irqsel.tim[TIM::TIM_ID as usize].write(|w| unsafe { w.bits(interrupt as u32) }); - self.enable_interrupt(); + cortex_m::peripheral::NVIC::mask(irq_cfg.irq); + 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.tim[TIM::TIM_ID as usize] + .write(|w| unsafe { w.bits(irq_cfg.irq as u32) }); + } + } self.listening = true; } } @@ -554,6 +565,12 @@ impl CountDownTimer { #[inline(always)] pub fn enable(&mut self) { self.tim.reg().ctrl.modify(|_, w| w.enable().set_bit()); + if let Some(irq_cfg) = self.irq_cfg { + self.enable_interrupt(); + if irq_cfg.enable { + unmask_irq(irq_cfg.irq); + } + } } #[inline(always)] @@ -720,19 +737,29 @@ impl embedded_hal::blocking::delay::DelayMs for CountDownTime // Set up a millisecond timer on TIM0. Please note that you still need to unmask the related IRQ // and provide an IRQ handler yourself -pub fn set_up_ms_timer( - syscfg: &mut pac::SYSCONFIG, - irqsel: &mut pac::IRQSEL, - sys_clk: Hertz, - tim0: TIM0, - irq: pac::Interrupt, -) -> CountDownTimer { - let mut ms_timer = CountDownTimer::new(syscfg, sys_clk, tim0); - ms_timer.listen(timer::Event::TimeOut, syscfg, irqsel, irq); +pub fn set_up_ms_timer( + irq_cfg: IrqCfg, + 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() { @@ -763,6 +790,7 @@ impl Delay { } /// 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::blocking::delay::DelayMs for Delay { fn delay_ms(&mut self, ms: u32) { if self.cd_tim.curr_freq() != 1000.hz() || !self.cd_tim.listening() { diff --git a/src/uart.rs b/src/uart.rs index fbb430d..225ac81 100644 --- a/src/uart.rs +++ b/src/uart.rs @@ -8,19 +8,25 @@ use core::{marker::PhantomData, ops::Deref}; use libm::floorf; use crate::clock::enable_peripheral_clock; +pub use crate::utility::IrqCfg; use crate::{ clock, gpio::pins::{ 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::{uarta as uart_base, SYSCONFIG, UARTA, UARTB}, + pac::{uarta as uart_base, IRQSEL, SYSCONFIG, UARTA, UARTB}, + utility::unmask_irq, prelude::*, time::{Bps, Hertz}, }; use embedded_hal::{blocking, serial}; +//================================================================================================== +// Type-Level support +//================================================================================================== + pub trait Pins {} impl Pins for (Pin, Pin) {} @@ -38,12 +44,18 @@ impl Pins for (Pin, Pin) {} impl Pins for (Pin, Pin) {} impl Pins for (Pin, Pin) {} +//================================================================================================== +// Regular Definitions +//================================================================================================== + #[derive(Debug)] pub enum Error { Overrun, FramingError, ParityError, BreakCondition, + TransferPending, + BufferTooShort, } #[derive(Debug, PartialEq, Copy, Clone)] @@ -160,6 +172,106 @@ impl From for Config { } } +//================================================================================================== +// IRQ Definitions +//================================================================================================== + +pub struct IrqInfo { + rx_len: usize, + rx_idx: usize, + irq_cfg: IrqCfg, + mode: IrqReceptionMode, +} + +pub enum IrqResultMask { + Complete = 0, + Overflow = 1, + FramingError = 2, + ParityError = 3, + Break = 4, + Timeout = 5, + Addr9 = 6, +} +pub struct IrqResult { + raw_res: u32, + pub bytes_read: usize, +} + +impl IrqResult { + #[inline] + pub fn raw_result(&self) -> u32 { + self.raw_res + } + + #[inline] + pub(crate) fn clear_result(&mut self) { + self.raw_res = 0; + } + #[inline] + pub(crate) fn set_result(&mut self, flag: IrqResultMask) { + self.raw_res |= 1 << flag as u32; + } + + #[inline] + pub fn complete(&self) -> bool { + if ((self.raw_res >> IrqResultMask::Complete as u32) & 0x01) == 0x00 { + return true; + } + false + } + + #[inline] + pub fn error(&self) -> bool { + if self.overflow_error() || self.framing_error() || self.parity_error() { + return true; + } + false + } + + #[inline] + pub fn overflow_error(&self) -> bool { + if ((self.raw_res >> IrqResultMask::Overflow as u32) & 0x01) == 0x01 { + return true; + } + false + } + + #[inline] + pub fn framing_error(&self) -> bool { + if ((self.raw_res >> IrqResultMask::FramingError as u32) & 0x01) == 0x01 { + return true; + } + false + } + + #[inline] + pub fn parity_error(&self) -> bool { + if ((self.raw_res >> IrqResultMask::ParityError as u32) & 0x01) == 0x01 { + return true; + } + false + } + + #[inline] + pub fn timeout(&self) -> bool { + if ((self.raw_res >> IrqResultMask::Timeout as u32) & 0x01) == 0x01 { + return true; + } + false + } +} + +#[derive(Debug, PartialEq)] +pub enum IrqReceptionMode { + Idle, + FixedLen, + VarLen, +} + +//================================================================================================== +// UART implementation +//================================================================================================== + /// Serial abstraction pub struct Uart { uart: UART, @@ -168,6 +280,11 @@ pub struct Uart { rx: Rx, } +pub struct UartWithIrq { + uart_base: Uart, + irq_info: IrqInfo, +} + /// Serial receiver pub struct Rx { _usart: PhantomData, @@ -247,6 +364,46 @@ where self } + #[inline] + pub fn enable_rx(&mut self) { + self.uart.enable.write(|w| w.rxenable().set_bit()); + } + + #[inline] + pub fn disable_rx(&mut self) { + self.uart.enable.write(|w| w.rxenable().set_bit()); + } + + #[inline] + pub fn enable_tx(&mut self) { + self.uart.enable.write(|w| w.txenable().set_bit()); + } + + #[inline] + pub fn disable_tx(&mut self) { + self.uart.enable.write(|w| w.txenable().set_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 { self.uart.irq_enb.modify(|_, w| match event { Event::RxError => w.irq_rx_status().set_bit(), @@ -319,6 +476,146 @@ macro_rules! uart_impl { } } +impl Uart { + pub fn into_uart_with_irq(self, irq_cfg: IrqCfg) -> UartWithIrq { + UartWithIrq { + uart_base: self, + irq_info: IrqInfo { + rx_len: 0, + rx_idx: 0, + irq_cfg, + mode: IrqReceptionMode::Idle, + }, + } + } +} + +impl UartWithIrq { + pub fn read_fixed_len_using_irq( + &mut self, + max_len: usize, + enb_timeout_irq: bool, + irqsel: Option<&mut IRQSEL>, + ) -> Result<(), Error> { + if self.irq_info.mode != IrqReceptionMode::Idle { + return Err(Error::TransferPending); + } + self.irq_info.rx_idx = 0; + self.irq_info.rx_len = max_len; + self.enable_rx_irq_sources(enb_timeout_irq); + if let Some(irqsel) = irqsel { + if self.irq_info.irq_cfg.route { + irqsel.uart[0].write(|w| unsafe { w.bits(self.irq_info.irq_cfg.irq as u32) }); + } + } + if self.irq_info.irq_cfg.enable { + unmask_irq(self.irq_info.irq_cfg.irq); + } + Ok(()) + } + + #[inline] + fn enable_rx_irq_sources(&mut self, timeout: bool) { + self.uart_base.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_base.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) { + // Disable IRQ + cortex_m::peripheral::NVIC::mask(self.irq_info.irq_cfg.irq); + self.disable_rx_irq_sources(); + self.uart_base.clear_tx_fifo(); + self.irq_info.rx_idx = 0; + self.irq_info.rx_len = 0; + } + + pub fn irq_handler(&mut self, res: &mut IrqResult, buf: &mut [u8]) -> Result<(), Error> { + if buf.len() < self.irq_info.rx_len { + return Err(Error::BufferTooShort); + } + + let mut rx_status = self.uart_base.uart.rxstatus.read(); + let _tx_status = self.uart_base.uart.txstatus.read(); + let irq_end = self.uart_base.uart.irq_end.read(); + + let enb_status = self.uart_base.uart.enable.read(); + let rx_enabled = enb_status.rxenable().bit_is_set(); + let _tx_enabled = enb_status.txenable().bit_is_set(); + res.clear_result(); + if irq_end.irq_rx().bit_is_set() && rx_status.rdavl().bit_is_set() { + let mut rd_avl = rx_status.rdavl(); + // While there is data in the FIFO, write it into the reception buffer + while self.irq_info.rx_idx < self.irq_info.rx_len && rd_avl.bit_is_set() { + buf[self.irq_info.rx_idx] = nb::block!(self.uart_base.read()).unwrap(); + rd_avl = self.uart_base.uart.rxstatus.read().rdavl(); + self.irq_info.rx_idx += 1; + if self.irq_info.rx_idx == self.irq_info.rx_len { + self.disable_rx_irq_sources(); + res.bytes_read = self.irq_info.rx_len; + res.set_result(IrqResultMask::Complete); + self.irq_info.rx_idx = 0; + self.irq_info.rx_len = 0; + return Ok(()); + } + } + } + + // RX transfer not complete, check for RX errors + if (self.irq_info.rx_idx < self.irq_info.rx_len) && rx_enabled { + // Read status register again, might have changed since reading received data + rx_status = self.uart_base.uart.rxstatus.read(); + if rx_status.rxovr().bit_is_set() { + res.set_result(IrqResultMask::Overflow); + } + if rx_status.rxfrm().bit_is_set() { + res.set_result(IrqResultMask::FramingError); + } + if rx_status.rxpar().bit_is_set() { + res.set_result(IrqResultMask::ParityError); + } + if rx_status.rxbrk().bit_is_set() { + res.set_result(IrqResultMask::Break); + } + if rx_status.rxto().bit_is_set() { + // A timeout has occured but there might be some leftover data in the FIFO, + // so read that data as well + while self.irq_info.rx_idx < self.irq_info.rx_len && rx_status.rdavl().bit_is_set() + { + buf[self.irq_info.rx_idx] = nb::block!(self.uart_base.read()).unwrap(); + self.irq_info.rx_idx += 1; + } + res.bytes_read = self.irq_info.rx_idx; + res.set_result(IrqResultMask::Timeout); + res.set_result(IrqResultMask::Complete); + } + + if res.raw_res != 0 { + self.disable_rx_irq_sources(); + } + } + + self.uart_base + .uart + .irq_clr + .write(|w| unsafe { w.bits(irq_end.bits()) }); + Ok(()) + } +} + uart_impl! { UARTA: (uarta, clock::PeripheralClocks::Uart0), UARTB: (uartb, clock::PeripheralClocks::Uart1), diff --git a/src/utility.rs b/src/utility.rs index edbe74d..8101a32 100644 --- a/src/utility.rs +++ b/src/utility.rs @@ -3,6 +3,7 @@ //! Some more information about the recommended scrub rates can be found on the //! [Vorago White Paper website](https://www.voragotech.com/resources) in the //! application note AN1212 +use crate::pac; use va108xx::{IOCONFIG, SYSCONFIG}; #[derive(PartialEq, Debug)] @@ -41,6 +42,26 @@ pub enum PeripheralSelect { Gpio = 24, } +/// Generic IRQ 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 perform +/// this steps themselves +#[derive(Debug, PartialEq, Clone, Copy)] +pub struct IrqCfg { + /// Interrupt target vector. Should always be set, might be required for disabling IRQs + pub irq: 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 + pub enable: bool, +} + +impl IrqCfg { + pub fn new(irq: pac::Interrupt, route: bool, enable: bool) -> Self { + IrqCfg { irq, route, enable } + } +} + /// Enable scrubbing for the ROM /// /// Returns [`UtilityError::InvalidCounterResetVal`] if the scrub rate is 0 @@ -111,3 +132,13 @@ pub fn port_mux( } } } + +/// Unmask and enable an IRQ with the given interrupt number +/// +/// ## Safety +/// +/// The unmask function can break mask-based critical sections +#[inline] +pub (crate) fn unmask_irq(irq: pac::Interrupt) { + unsafe { cortex_m::peripheral::NVIC::unmask(irq) }; +}