From f376a43f4100441f82ea2bf7748688d6814342a1 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Sun, 19 Dec 2021 14:18:10 +0100 Subject: [PATCH 1/7] 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) }; +} From d5b12c83434666d9c5e9ce99fd7dced65da661a1 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Sun, 19 Dec 2021 14:25:15 +0100 Subject: [PATCH 2/7] some fixes for manifest file --- Cargo.toml | 12 ++++++++++++ examples/cascade.rs | 2 +- src/gpio/dynpins.rs | 6 +++--- src/gpio/pins.rs | 13 ++++++------- src/timer.rs | 2 +- src/uart.rs | 2 +- src/utility.rs | 2 +- 7 files changed, 25 insertions(+), 14 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a6dfde1..8072074 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,3 +45,15 @@ 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 = "cascade" +required-features = ["rt"] diff --git a/examples/cascade.rs b/examples/cascade.rs index 3b1c363..03e5c09 100644 --- a/examples/cascade.rs +++ b/examples/cascade.rs @@ -11,7 +11,7 @@ use cortex_m_rt::entry; use panic_rtt_target as _; use rtt_target::{rprintln, rtt_init_print}; use va108xx_hal::{ - pac::{self, interrupt, TIM4, TIM5}, + pac::{self, Interrupt, TIM4, TIM5}, prelude::*, timer::{ default_ms_irq_handler, set_up_ms_delay_provider, CascadeCtrl, CascadeSource, diff --git a/src/gpio/dynpins.rs b/src/gpio/dynpins.rs index 5513fa7..3d91504 100644 --- a/src/gpio/dynpins.rs +++ b/src/gpio/dynpins.rs @@ -67,7 +67,7 @@ use super::{ use crate::{ clock::FilterClkSel, pac::{IRQSEL, SYSCONFIG}, - utility::{IrqCfg, Funsel}, + utility::{Funsel, IrqCfg}, }; use embedded_hal::digital::v2::{InputPin, OutputPin, ToggleableOutputPin}; use paste::paste; @@ -346,7 +346,7 @@ impl DynPin { edge_type: InterruptEdge, irq_cfg: IrqCfg, syscfg: Option<&mut SYSCONFIG>, - irqsel: Option<&mut IRQSEL> + irqsel: Option<&mut IRQSEL>, ) -> Result { match self.mode { DynPinMode::Input(_) | DynPinMode::Output(_) => { @@ -363,7 +363,7 @@ impl DynPin { level_type: InterruptLevel, irq_cfg: IrqCfg, syscfg: Option<&mut SYSCONFIG>, - irqsel: Option<&mut IRQSEL> + irqsel: Option<&mut IRQSEL>, ) -> Result { match self.mode { DynPinMode::Input(_) | DynPinMode::Output(_) => { diff --git a/src/gpio/pins.rs b/src/gpio/pins.rs index d11c04e..ea9dba4 100644 --- a/src/gpio/pins.rs +++ b/src/gpio/pins.rs @@ -94,8 +94,8 @@ use super::reg::RegisterInterface; use crate::{ pac::{IOCONFIG, IRQSEL, PORTA, PORTB, SYSCONFIG}, typelevel::Is, + utility::IrqCfg, Sealed, - utility::IrqCfg }; use core::convert::Infallible; use core::marker::PhantomData; @@ -361,7 +361,6 @@ impl AnyPin for Pin { macro_rules! common_reg_if_functions { () => { paste!( - #[inline] pub fn datamask(&self) -> bool { self.regs.datamask() @@ -403,7 +402,7 @@ macro_rules! common_reg_if_functions { &mut self, irq_cfg: crate::utility::IrqCfg, syscfg: Option<&mut va108xx::SYSCONFIG>, - irqsel: Option<&mut va108xx::IRQSEL> + irqsel: Option<&mut va108xx::IRQSEL>, ) { if syscfg.is_some() { crate::clock::enable_peripheral_clock( @@ -585,7 +584,7 @@ impl Pin> { edge_type: InterruptEdge, irq_cfg: IrqCfg, syscfg: Option<&mut SYSCONFIG>, - irqsel: Option<&mut IRQSEL> + irqsel: Option<&mut IRQSEL>, ) -> Self { self.regs.interrupt_edge(edge_type); self.irq_enb(irq_cfg, syscfg, irqsel); @@ -597,7 +596,7 @@ impl Pin> { level_type: InterruptLevel, irq_cfg: IrqCfg, syscfg: Option<&mut SYSCONFIG>, - irqsel: Option<&mut IRQSEL> + irqsel: Option<&mut IRQSEL>, ) -> Self { self.regs.interrupt_level(level_type); self.irq_enb(irq_cfg, syscfg, irqsel); @@ -630,7 +629,7 @@ impl Pin> { edge_type: InterruptEdge, irq_cfg: IrqCfg, syscfg: Option<&mut SYSCONFIG>, - irqsel: Option<&mut IRQSEL> + irqsel: Option<&mut IRQSEL>, ) -> Self { self.regs.interrupt_edge(edge_type); self.irq_enb(irq_cfg, syscfg, irqsel); @@ -642,7 +641,7 @@ impl Pin> { level_type: InterruptLevel, irq_cfg: IrqCfg, syscfg: Option<&mut SYSCONFIG>, - irqsel: Option<&mut IRQSEL> + irqsel: Option<&mut IRQSEL>, ) -> Self { self.regs.interrupt_level(level_type); self.irq_enb(irq_cfg, syscfg, irqsel); diff --git a/src/timer.rs b/src/timer.rs index 19b3607..99d0c80 100644 --- a/src/timer.rs +++ b/src/timer.rs @@ -7,7 +7,6 @@ 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, @@ -22,6 +21,7 @@ use crate::{ private::Sealed, time::Hertz, timer, + utility::unmask_irq, }; use core::cell::Cell; use cortex_m::interrupt::Mutex; diff --git a/src/uart.rs b/src/uart.rs index 225ac81..6882418 100644 --- a/src/uart.rs +++ b/src/uart.rs @@ -16,9 +16,9 @@ use crate::{ PA31, PA8, PA9, PB18, PB19, PB20, PB21, PB22, PB23, PB6, PB7, PB8, PB9, }, pac::{uarta as uart_base, IRQSEL, SYSCONFIG, UARTA, UARTB}, - utility::unmask_irq, prelude::*, time::{Bps, Hertz}, + utility::unmask_irq, }; use embedded_hal::{blocking, serial}; diff --git a/src/utility.rs b/src/utility.rs index 8101a32..2f8a609 100644 --- a/src/utility.rs +++ b/src/utility.rs @@ -139,6 +139,6 @@ pub fn port_mux( /// /// The unmask function can break mask-based critical sections #[inline] -pub (crate) fn unmask_irq(irq: pac::Interrupt) { +pub(crate) fn unmask_irq(irq: pac::Interrupt) { unsafe { cortex_m::peripheral::NVIC::unmask(irq) }; } From d458a816354a403511a8169c3dcf57376fb25913 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Sun, 19 Dec 2021 14:38:11 +0100 Subject: [PATCH 3/7] RTIC additions - Use RTIC in UART IRQ example - Add RTIC template --- Cargo.toml | 5 +++ examples/rtic-empty.rs | 29 ++++++++++++++++++ examples/uart-irq-rtic.rs | 64 +++++++++++++++++++++++++++++++++++++++ examples/uart-irq.rs | 44 --------------------------- 4 files changed, 98 insertions(+), 44 deletions(-) create mode 100644 examples/rtic-empty.rs create mode 100644 examples/uart-irq-rtic.rs delete mode 100644 examples/uart-irq.rs diff --git a/Cargo.toml b/Cargo.toml index 8072074..e295305 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,7 @@ version = "0.2.4" rt = ["va108xx/rt"] [dev-dependencies] +cortex-m-rtic = "0.6.0-rc.4" panic-rtt-target = { version = "0.1", features = ["cortex-m"] } rtt-target = { version = "0.3", features = ["cortex-m"] } panic-halt = "0.2" @@ -57,3 +58,7 @@ required-features = ["rt"] [[example]] name = "cascade" required-features = ["rt"] + +[[example]] +name = "uart-irq-rtic" +required-features = ["rt"] diff --git a/examples/rtic-empty.rs b/examples/rtic-empty.rs new file mode 100644 index 0000000..db1f174 --- /dev/null +++ b/examples/rtic-empty.rs @@ -0,0 +1,29 @@ +//! Empty RTIC project template +#![no_main] +#![no_std] + +#[rtic::app(device = pac)] +mod app { + use panic_rtt_target as _; + use rtt_target::{rprintln, rtt_init_default}; + use va108xx_hal::pac; + + #[local] + struct Local {} + + #[shared] + struct Shared {} + + #[init] + fn init(_ctx: init::Context) -> (Shared, Local, init::Monotonics) { + rtt_init_default!(); + rprintln!("-- Vorago RTIC template --"); + (Shared {}, Local {}, init::Monotonics()) + } + + // `shared` cannot be accessed from this context + #[idle] + fn idle(_cx: idle::Context) -> ! { + loop {} + } +} diff --git a/examples/uart-irq-rtic.rs b/examples/uart-irq-rtic.rs new file mode 100644 index 0000000..b46133e --- /dev/null +++ b/examples/uart-irq-rtic.rs @@ -0,0 +1,64 @@ +//! UART example application. Sends a test string over a UART and then enters +//! echo mode +#![no_main] +#![no_std] + +#[rtic::app(device = pac)] +mod app { + use core::fmt::Write; + use panic_rtt_target as _; + use rtt_target::{rprintln, rtt_init_print}; + use va108xx_hal::{gpio::PinsB, pac, prelude::*, uart}; + + #[local] + struct Local {} + + #[shared] + struct Shared {} + + #[init] + fn init(_ctx: init::Context) -> (Shared, Local, init::Monotonics) { + 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(); + (Shared {}, Local {}, init::Monotonics()) + } + + // `shared` cannot be accessed from this context + #[idle] + fn idle(_cx: idle::Context) -> ! { + loop {} + } +} + +// #[entry] +// fn main() -> ! { + +// 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/examples/uart-irq.rs b/examples/uart-irq.rs deleted file mode 100644 index 8f6ed31..0000000 --- a/examples/uart-irq.rs +++ /dev/null @@ -1,44 +0,0 @@ -//! 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); - } - } - } -} From a8b484d66f31b1b540d5d131da17b8f7b968300b Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Sun, 19 Dec 2021 15:45:46 +0100 Subject: [PATCH 4/7] UART reception and echo reply now working - Important bugfix in UART: use `modify` instead of `write` when enabling or disabling TX or RX - Extend RTIC example application. Reply handling is dispatched to lower priority interrupt --- Cargo.toml | 1 + examples/cascade.rs | 2 +- examples/uart-irq-rtic.rs | 121 ++++++++--- src/uart.rs | 414 ++++++++++++++++++++++++++++++-------- 4 files changed, 423 insertions(+), 115 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e295305..b47a53b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ rt = ["va108xx/rt"] [dev-dependencies] cortex-m-rtic = "0.6.0-rc.4" +arrayvec = { version = "0.7.2", default-features = false } panic-rtt-target = { version = "0.1", features = ["cortex-m"] } rtt-target = { version = "0.3", features = ["cortex-m"] } panic-halt = "0.2" diff --git a/examples/cascade.rs b/examples/cascade.rs index 03e5c09..3b1c363 100644 --- a/examples/cascade.rs +++ b/examples/cascade.rs @@ -11,7 +11,7 @@ use cortex_m_rt::entry; use panic_rtt_target as _; use rtt_target::{rprintln, rtt_init_print}; use va108xx_hal::{ - pac::{self, Interrupt, TIM4, TIM5}, + pac::{self, interrupt, TIM4, TIM5}, prelude::*, timer::{ default_ms_irq_handler, set_up_ms_delay_provider, CascadeCtrl, CascadeSource, diff --git a/examples/uart-irq-rtic.rs b/examples/uart-irq-rtic.rs index b46133e..cf16b7a 100644 --- a/examples/uart-irq-rtic.rs +++ b/examples/uart-irq-rtic.rs @@ -3,40 +3,52 @@ #![no_main] #![no_std] -#[rtic::app(device = pac)] +#[rtic::app(device = pac, dispatchers = [OC4])] mod app { use core::fmt::Write; use panic_rtt_target as _; - use rtt_target::{rprintln, rtt_init_print}; - use va108xx_hal::{gpio::PinsB, pac, prelude::*, uart}; + use rtt_target::{rprintln, rtt_init_default, set_print_channel}; + use va108xx_hal::{ + gpio::PinsB, + pac, + prelude::*, + uart::{self, IrqCfg, IrqResult, UartWithIrqBase}, + }; #[local] struct Local {} #[shared] - struct Shared {} + struct Shared { + irq_uart: UartWithIrqBase, + rx_buf: [u8; 64], + } #[init] - fn init(_ctx: init::Context) -> (Shared, Local, init::Monotonics) { - rtt_init_print!(); - rprintln!("-- VA108xx UART example application--"); - - let mut dp = pac::Peripherals::take().unwrap(); - + fn init(ctx: init::Context) -> (Shared, Local, init::Monotonics) { + let channels = rtt_init_default!(); + set_print_channel(channels.up.0); + rprintln!("-- VA108xx UART IRQ example application--"); + let mut dp = ctx.device; 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( + + let irq_cfg = IrqCfg::new(pac::interrupt::OC3, true, true); + let (mut irq_uart, _) = 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(); - (Shared {}, Local {}, init::Monotonics()) + ) + .into_uart_with_irq(irq_cfg, Some(&mut dp.SYSCONFIG), Some(&mut dp.IRQSEL)) + .downgrade(); + irq_uart + .read_fixed_len_using_irq(64, true) + .expect("Read initialization failed"); + let rx_buf: [u8; 64] = [0; 64]; + (Shared { irq_uart, rx_buf }, Local {}, init::Monotonics()) } // `shared` cannot be accessed from this context @@ -44,21 +56,64 @@ mod app { fn idle(_cx: idle::Context) -> ! { loop {} } + + #[task( + binds = OC3, + shared = [irq_uart, rx_buf], + local = [cnt: u32 = 0, result: IrqResult = IrqResult::new()], + priority = 4 + )] + fn reception_task(cx: reception_task::Context) { + let result = cx.local.result; + let cnt: &mut u32 = cx.local.cnt; + let irq_uart = cx.shared.irq_uart; + let rx_buf = cx.shared.rx_buf; + let (completed, end_idx) = (irq_uart, rx_buf).lock(|irq_uart, rx_buf| { + match irq_uart.irq_handler(result, rx_buf) { + Ok(_) => { + if result.complete() { + // Initiate next transfer immediately + irq_uart + .read_fixed_len_using_irq(64, true) + .expect("Read operation init failed"); + + let mut end_idx = 0; + for idx in 0..rx_buf.len() { + if (rx_buf[idx] as char) == '\n' { + end_idx = idx; + break; + } + } + (true, end_idx) + } else { + (false, 0) + } + } + Err(e) => { + rprintln!("Reception Error {:?}", e); + (false, 0) + } + } + }); + if completed { + rprintln!("Counter: {}", cnt); + reply_handler::spawn(result.bytes_read, end_idx, result.timeout()).unwrap(); + } + *cnt += 1; + } + + #[task(shared = [irq_uart, rx_buf], priority = 3)] + fn reply_handler(cx: reply_handler::Context, bytes_read: usize, end_idx: usize, timeout: bool) { + let irq_uart = cx.shared.irq_uart; + let rx_buf = cx.shared.rx_buf; + (irq_uart, rx_buf).lock(|irq_uart, rx_buf| { + rprintln!("Reception success, {} bytes read", bytes_read); + if timeout { + rprintln!("Timeout occured"); + } + let string = core::str::from_utf8(&rx_buf[0..end_idx]).expect("Invalid string format"); + rprintln!("Read string: {}", string); + writeln!(irq_uart.uart, "{}", string).expect("Sending reply failed"); + }); + } } - -// #[entry] -// fn main() -> ! { - -// 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/uart.rs b/src/uart.rs index 6882418..1644976 100644 --- a/src/uart.rs +++ b/src/uart.rs @@ -7,10 +7,9 @@ use core::{convert::Infallible, ptr}; use core::{marker::PhantomData, ops::Deref}; use libm::floorf; -use crate::clock::enable_peripheral_clock; pub use crate::utility::IrqCfg; use crate::{ - clock, + clock::{self, enable_peripheral_clock, PeripheralClocks}, 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, @@ -56,6 +55,8 @@ pub enum Error { BreakCondition, TransferPending, BufferTooShort, + /// Can be a combination of overrun, framing or parity error + IrqError, } #[derive(Debug, PartialEq, Copy, Clone)] @@ -191,12 +192,25 @@ pub enum IrqResultMask { Break = 4, Timeout = 5, Addr9 = 6, + /// Should not happen + Unknown = 7, } + +#[derive(Debug, Default)] pub struct IrqResult { raw_res: u32, pub bytes_read: usize, } +impl IrqResult { + pub const fn new() -> Self { + IrqResult { + raw_res: 0, + bytes_read: 0, + } + } +} + impl IrqResult { #[inline] pub fn raw_result(&self) -> u32 { @@ -214,7 +228,7 @@ impl IrqResult { #[inline] pub fn complete(&self) -> bool { - if ((self.raw_res >> IrqResultMask::Complete as u32) & 0x01) == 0x00 { + if ((self.raw_res >> IrqResultMask::Complete as u32) & 0x01) == 0x01 { return true; } false @@ -272,16 +286,24 @@ pub enum IrqReceptionMode { // UART implementation //================================================================================================== -/// Serial abstraction -pub struct Uart { +pub struct UartBase { uart: UART, - pins: PINS, tx: Tx, rx: Rx, } +/// Serial abstraction +pub struct Uart { + uart_base: UartBase, + pins: PINS, +} pub struct UartWithIrq { - uart_base: Uart, + irq_base: UartWithIrqBase, + pins: PINS, +} + +pub struct UartWithIrqBase { + pub uart: UartBase, irq_info: IrqInfo, } @@ -313,12 +335,10 @@ impl Tx { pub trait Instance: Deref { fn ptr() -> *const uart_base::RegisterBlock; + const IDX: u8; } -impl Uart -where - UART: Instance, -{ +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 { @@ -366,22 +386,22 @@ where #[inline] pub fn enable_rx(&mut self) { - self.uart.enable.write(|w| w.rxenable().set_bit()); + self.uart.enable.modify(|_, w| w.rxenable().set_bit()); } #[inline] pub fn disable_rx(&mut self) { - self.uart.enable.write(|w| w.rxenable().set_bit()); + self.uart.enable.modify(|_, w| w.rxenable().clear_bit()); } #[inline] pub fn enable_tx(&mut self) { - self.uart.enable.write(|w| w.txenable().set_bit()); + self.uart.enable.modify(|_, w| w.txenable().set_bit()); } #[inline] pub fn disable_tx(&mut self) { - self.uart.enable.write(|w| w.txenable().set_bit()); + self.uart.enable.modify(|_, w| w.txenable().clear_bit()); } #[inline] @@ -404,7 +424,7 @@ where self.uart.fifo_clr.write(|w| w.txsts().set_bit()); } - pub fn listen(self, event: Event) -> Self { + 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(), @@ -414,10 +434,9 @@ where Event::TxFifoHalfFull => w.irq_tx().set_bit(), Event::TxCts => w.irq_tx_cts().set_bit(), }); - self } - pub fn unlisten(self, event: Event) -> Self { + 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(), @@ -427,10 +446,9 @@ where Event::TxFifoHalfFull => w.irq_tx().clear_bit(), Event::TxCts => w.irq_tx_cts().clear_bit(), }); - self } - pub fn release(self) -> (UART, PINS) { + pub fn release(self) -> UART { // Clear the FIFO self.uart.fifo_clr.write(|w| { w.rxfifo().set_bit(); @@ -440,7 +458,7 @@ where w.rxenable().clear_bit(); w.txenable().clear_bit() }); - (self.uart, self.pins) + self.uart } pub fn split(self) -> (Tx, Rx) { @@ -448,14 +466,130 @@ where } } +impl Uart +where + UART: Instance, +{ + /// 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.uart_base = self.uart_base.init(config, sys_clk); + self + } + + pub fn into_uart_with_irq( + self, + irq_cfg: IrqCfg, + sys_cfg: Option<&mut SYSCONFIG>, + irq_sel: Option<&mut IRQSEL>, + ) -> UartWithIrq { + let (uart, pins) = self.downgrade_internal(); + UartWithIrq { + pins, + irq_base: UartWithIrqBase { + uart, + irq_info: IrqInfo { + rx_len: 0, + rx_idx: 0, + irq_cfg, + mode: IrqReceptionMode::Idle, + }, + } + .init(sys_cfg, irq_sel), + } + } + + #[inline] + pub fn enable_rx(&mut self) { + self.uart_base.enable_rx(); + } + + #[inline] + pub fn disable_rx(&mut self) { + self.uart_base.enable_rx(); + } + + #[inline] + pub fn enable_tx(&mut self) { + self.uart_base.enable_tx(); + } + + #[inline] + pub fn disable_tx(&mut self) { + self.uart_base.disable_tx(); + } + + #[inline] + pub fn clear_rx_fifo(&mut self) { + self.uart_base.clear_rx_fifo(); + } + + #[inline] + pub fn clear_tx_fifo(&mut self) { + self.uart_base.clear_tx_fifo(); + } + + #[inline] + pub fn clear_rx_status(&mut self) { + self.uart_base.clear_rx_status(); + } + + #[inline] + pub fn clear_tx_status(&mut self) { + self.uart_base.clear_tx_status(); + } + + pub fn listen(&self, event: Event) { + self.uart_base.listen(event); + } + + pub fn unlisten(&self, event: Event) { + self.uart_base.unlisten(event); + } + + pub fn release(self) -> (UART, PINS) { + (self.uart_base.release(), self.pins) + } + + fn downgrade_internal(self) -> (UartBase, PINS) { + let base = UartBase { + uart: self.uart_base.uart, + tx: self.uart_base.tx, + rx: self.uart_base.rx, + }; + (base, self.pins) + } + + pub fn downgrade(self) -> UartBase { + UartBase { + uart: self.uart_base.uart, + tx: self.uart_base.tx, + rx: self.uart_base.rx, + } + } + + pub fn split(self) -> (Tx, Rx) { + self.uart_base.split() + } +} + +impl Instance for UARTA { + fn ptr() -> *const uart_base::RegisterBlock { + UARTA::ptr() as *const _ + } + const IDX: u8 = 0; +} + +impl Instance for UARTB { + fn ptr() -> *const uart_base::RegisterBlock { + UARTB::ptr() as *const _ + } + const IDX: u8 = 1; +} + macro_rules! uart_impl { ($($UARTX:ident: ($uartx:ident, $clk_enb_enum:path),)+) => { $( - impl Instance for $UARTX { - fn ptr() -> *const uart_base::RegisterBlock { - $UARTX::ptr() as *const _ - } - } impl> Uart<$UARTX, PINS> { pub fn $uartx( @@ -467,47 +601,49 @@ macro_rules! uart_impl { ) -> Self { enable_peripheral_clock(syscfg, $clk_enb_enum); - Uart { uart, pins, tx: Tx::new(), rx: Rx::new() }.init( - config.into(), sys_clk.into() - ) + Uart { + uart_base: UartBase { + uart, + tx: Tx::new(), + rx: Rx::new(), + }, + pins, + } + .init(config.into(), sys_clk.into()) } } + )+ } } -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 UartWithIrqBase { + fn init(self, sys_cfg: Option<&mut SYSCONFIG>, irq_sel: Option<&mut IRQSEL>) -> Self { + if let Some(sys_cfg) = sys_cfg { + enable_peripheral_clock(sys_cfg, PeripheralClocks::Irqsel) } + if let Some(irq_sel) = irq_sel { + if self.irq_info.irq_cfg.route { + irq_sel.uart[UART::IDX as usize] + .write(|w| unsafe { w.bits(self.irq_info.irq_cfg.irq as u32) }); + } + } + self } -} -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.uart.enable_rx(); + self.uart.enable_tx(); 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); } @@ -516,7 +652,7 @@ impl UartWithIrq { #[inline] fn enable_rx_irq_sources(&mut self, timeout: bool) { - self.uart_base.uart.irq_enb.modify(|_, w| { + self.uart.uart.irq_enb.modify(|_, w| { if timeout { w.irq_rx_to().set_bit(); } @@ -527,18 +663,28 @@ impl UartWithIrq { #[inline] fn disable_rx_irq_sources(&mut self) { - self.uart_base.uart.irq_enb.modify(|_, w| { + self.uart.uart.irq_enb.modify(|_, w| { w.irq_rx_to().clear_bit(); w.irq_rx_status().clear_bit(); w.irq_rx().clear_bit() }); } + #[inline] + pub fn enable_tx(&mut self) { + self.uart.enable_tx() + } + + #[inline] + pub fn disable_tx(&mut self) { + self.uart.disable_tx() + } + 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.uart.clear_tx_fifo(); self.irq_info.rx_idx = 0; self.irq_info.rx_len = 0; } @@ -548,36 +694,68 @@ impl UartWithIrq { 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 irq_end = self.uart.uart.irq_end.read(); + let enb_status = self.uart.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(); + let read_handler = + |res: &mut IrqResult, read_res: nb::Result| -> Result, Error> { + match read_res { + Ok(byte) => return Ok(Some(byte)), + Err(nb::Error::WouldBlock) => { + return Ok(None); + } + Err(nb::Error::Other(e)) => match e { + Error::Overrun => { + res.set_result(IrqResultMask::Overflow); + return Err(Error::IrqError); + } + Error::FramingError => { + res.set_result(IrqResultMask::FramingError); + return Err(Error::IrqError); + } + Error::ParityError => { + res.set_result(IrqResultMask::ParityError); + return Err(Error::IrqError); + } + _ => { + res.set_result(IrqResultMask::Unknown); + return Err(Error::IrqError); + } + }, + }; + }; + if irq_end.irq_rx().bit_is_set() { + // If this interrupt bit is set, the trigger level is available at the very least. + // Read everything as fast as possible + for _ in 0..core::cmp::min( + self.uart.uart.rxfifoirqtrg.read().bits() as usize, + self.irq_info.rx_len, + ) { + buf[self.irq_info.rx_idx] = (self.uart.uart.data.read().bits() & 0xff) as u8; self.irq_info.rx_idx += 1; + } + + // While there is data in the FIFO, write it into the reception buffer + loop { 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; + self.irq_completion_handler(res); return Ok(()); } + if let Some(byte) = read_handler(res, self.uart.read())? { + buf[self.irq_info.rx_idx] = byte; + self.irq_info.rx_idx += 1; + } else { + break; + } } } // 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(); + let rx_status = self.uart.uart.rxstatus.read(); + res.clear_result(); if rx_status.rxovr().bit_is_set() { res.set_result(IrqResultMask::Overflow); } @@ -593,27 +771,74 @@ impl UartWithIrq { 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; + loop { + if let Some(byte) = read_handler(res, self.uart.read())? { + buf[self.irq_info.rx_idx] = byte; + self.irq_info.rx_idx += 1; + } else { + break; + } } - res.bytes_read = self.irq_info.rx_idx; + self.irq_completion_handler(res); res.set_result(IrqResultMask::Timeout); - res.set_result(IrqResultMask::Complete); + return Ok(()); } + // If it is not a timeout, it's an error if res.raw_res != 0 { self.disable_rx_irq_sources(); + return Err(Error::IrqError); } } - self.uart_base + // Clear the interrupt status bits + self.uart .uart .irq_clr .write(|w| unsafe { w.bits(irq_end.bits()) }); Ok(()) } + + fn irq_completion_handler(&mut self, res: &mut IrqResult) { + self.disable_rx_irq_sources(); + self.uart.disable_rx(); + res.bytes_read = self.irq_info.rx_idx; + res.clear_result(); + res.set_result(IrqResultMask::Complete); + self.irq_info.rx_idx = 0; + self.irq_info.rx_len = 0; + } + + pub fn release(self) -> UART { + self.uart.release() + } +} + +impl UartWithIrq { + pub fn read_fixed_len_using_irq( + &mut self, + max_len: usize, + enb_timeout_irq: bool, + ) -> Result<(), Error> { + self.irq_base + .read_fixed_len_using_irq(max_len, enb_timeout_irq) + } + + pub fn cancel_transfer(&mut self) { + self.irq_base.cancel_transfer() + } + + pub fn irq_handler(&mut self, res: &mut IrqResult, buf: &mut [u8]) -> Result<(), Error> { + self.irq_base.irq_handler(res, buf) + } + + pub fn release(self) -> (UART, PINS) { + (self.irq_base.release(), self.pins) + } + + pub fn downgrade(self) -> (UartWithIrqBase, PINS) { + (self.irq_base, self.pins) + } } uart_impl! { @@ -623,10 +848,7 @@ uart_impl! { impl Tx where UART: Instance {} -impl serial::Write for Uart -where - UART: Instance, -{ +impl serial::Write for UartBase { type Error = Infallible; fn write(&mut self, word: u8) -> nb::Result<(), Self::Error> { self.tx.write(word) @@ -636,6 +858,19 @@ where } } +impl serial::Write for Uart +where + UART: Instance, +{ + type Error = Infallible; + fn write(&mut self, word: u8) -> nb::Result<(), Self::Error> { + self.uart_base.write(word) + } + fn flush(&mut self) -> nb::Result<(), Self::Error> { + self.uart_base.flush() + } +} + impl blocking::serial::write::Default for Uart {} impl serial::Write for Tx { @@ -667,7 +902,7 @@ impl serial::Write for Tx { } } -impl serial::Read for Uart { +impl serial::Read for UartBase { type Error = Error; fn read(&mut self) -> nb::Result { @@ -675,6 +910,14 @@ impl serial::Read for Uart { } } +impl serial::Read for Uart { + type Error = Error; + + fn read(&mut self) -> nb::Result { + self.uart_base.read() + } +} + impl serial::Read for Rx { type Error = Error; @@ -706,7 +949,7 @@ impl serial::Read for Rx { } } -impl core::fmt::Write for Tx +impl core::fmt::Write for Tx where Tx: embedded_hal::serial::Write, { @@ -717,3 +960,12 @@ where .map_err(|_| core::fmt::Error) } } + +impl core::fmt::Write for UartBase +where + UartBase: embedded_hal::serial::Write, +{ + fn write_str(&mut self, s: &str) -> core::fmt::Result { + self.tx.write_str(s) + } +} From 9a5c9ac53c031be04a2d72ac5a1a29dea045b6ce Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Mon, 20 Dec 2021 11:36:02 +0100 Subject: [PATCH 5/7] update changelog and manifest - Clippy fixes --- CHANGELOG.md | 7 ++++++- Cargo.toml | 3 +-- src/uart.rs | 26 ++++++++++---------------- 3 files changed, 17 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b0a43e2..b8f4fcc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). -## [unreleased] +## [v0.5.0] + +- Reactored IRQ handling, so that `unmask` operations can be moved to HAL +- Added UART IRQ handler. Right now, can only perform reception, TX still needs to be done in + a blocking manner +- Added RTIC template and RTIC UART IRQ application ## [v0.4.3] diff --git a/Cargo.toml b/Cargo.toml index b47a53b..3ff4360 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "va108xx-hal" -version = "0.4.3" +version = "0.5.0" authors = ["Robin Mueller "] edition = "2021" description = "HAL for the Vorago VA108xx family of microcontrollers" @@ -28,7 +28,6 @@ rt = ["va108xx/rt"] [dev-dependencies] cortex-m-rtic = "0.6.0-rc.4" -arrayvec = { version = "0.7.2", default-features = false } panic-rtt-target = { version = "0.1", features = ["cortex-m"] } rtt-target = { version = "0.3", features = ["cortex-m"] } panic-halt = "0.2" diff --git a/src/uart.rs b/src/uart.rs index 1644976..675e257 100644 --- a/src/uart.rs +++ b/src/uart.rs @@ -701,29 +701,27 @@ impl UartWithIrqBase { let read_handler = |res: &mut IrqResult, read_res: nb::Result| -> Result, Error> { match read_res { - Ok(byte) => return Ok(Some(byte)), - Err(nb::Error::WouldBlock) => { - return Ok(None); - } + Ok(byte) => Ok(Some(byte)), + Err(nb::Error::WouldBlock) => Ok(None), Err(nb::Error::Other(e)) => match e { Error::Overrun => { res.set_result(IrqResultMask::Overflow); - return Err(Error::IrqError); + Err(Error::IrqError) } Error::FramingError => { res.set_result(IrqResultMask::FramingError); - return Err(Error::IrqError); + Err(Error::IrqError) } Error::ParityError => { res.set_result(IrqResultMask::ParityError); - return Err(Error::IrqError); + Err(Error::IrqError) } _ => { res.set_result(IrqResultMask::Unknown); - return Err(Error::IrqError); + Err(Error::IrqError) } }, - }; + } }; if irq_end.irq_rx().bit_is_set() { // If this interrupt bit is set, the trigger level is available at the very least. @@ -771,13 +769,9 @@ impl UartWithIrqBase { 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 - loop { - if let Some(byte) = read_handler(res, self.uart.read())? { - buf[self.irq_info.rx_idx] = byte; - self.irq_info.rx_idx += 1; - } else { - break; - } + while let Some(byte) = read_handler(res, self.uart.read())? { + buf[self.irq_info.rx_idx] = byte; + self.irq_info.rx_idx += 1; } self.irq_completion_handler(res); res.set_result(IrqResultMask::Timeout); From 5cbec366bcf2b8069259f84cdfffcd1c8503051c Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Mon, 20 Dec 2021 23:34:55 +0100 Subject: [PATCH 6/7] added Arduino Due test script --- test/DueSerialTest/.gitignore | 5 ++ test/DueSerialTest/.vscode/extensions.json | 7 ++ test/DueSerialTest/README.md | 2 + test/DueSerialTest/include/README | 39 +++++++++++ test/DueSerialTest/lib/README | 46 +++++++++++++ test/DueSerialTest/platformio.ini | 15 +++++ test/DueSerialTest/src/main.cpp | 78 ++++++++++++++++++++++ test/DueSerialTest/test/README | 11 +++ 8 files changed, 203 insertions(+) create mode 100644 test/DueSerialTest/.gitignore create mode 100644 test/DueSerialTest/.vscode/extensions.json create mode 100644 test/DueSerialTest/README.md create mode 100644 test/DueSerialTest/include/README create mode 100644 test/DueSerialTest/lib/README create mode 100644 test/DueSerialTest/platformio.ini create mode 100644 test/DueSerialTest/src/main.cpp create mode 100644 test/DueSerialTest/test/README diff --git a/test/DueSerialTest/.gitignore b/test/DueSerialTest/.gitignore new file mode 100644 index 0000000..89cc49c --- /dev/null +++ b/test/DueSerialTest/.gitignore @@ -0,0 +1,5 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/test/DueSerialTest/.vscode/extensions.json b/test/DueSerialTest/.vscode/extensions.json new file mode 100644 index 0000000..e80666b --- /dev/null +++ b/test/DueSerialTest/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "platformio.platformio-ide" + ] +} diff --git a/test/DueSerialTest/README.md b/test/DueSerialTest/README.md new file mode 100644 index 0000000..c53cfa1 --- /dev/null +++ b/test/DueSerialTest/README.md @@ -0,0 +1,2 @@ +This is a Platform IO test script for the Arduino Due which can be used to sent different kind +of strings via the serial interface (RX1 and TX1) to the Vorago board. diff --git a/test/DueSerialTest/include/README b/test/DueSerialTest/include/README new file mode 100644 index 0000000..194dcd4 --- /dev/null +++ b/test/DueSerialTest/include/README @@ -0,0 +1,39 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the usual convention is to give header files names that end with `.h'. +It is most portable to use only letters, digits, dashes, and underscores in +header file names, and at most one dot. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/test/DueSerialTest/lib/README b/test/DueSerialTest/lib/README new file mode 100644 index 0000000..6debab1 --- /dev/null +++ b/test/DueSerialTest/lib/README @@ -0,0 +1,46 @@ + +This directory is intended for project specific (private) libraries. +PlatformIO will compile them to static libraries and link into executable file. + +The source code of each library should be placed in a an own separate directory +("lib/your_library_name/[here are source files]"). + +For example, see a structure of the following two libraries `Foo` and `Bar`: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- README --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +and a contents of `src/main.c`: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +PlatformIO Library Dependency Finder will find automatically dependent +libraries scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/test/DueSerialTest/platformio.ini b/test/DueSerialTest/platformio.ini new file mode 100644 index 0000000..a448357 --- /dev/null +++ b/test/DueSerialTest/platformio.ini @@ -0,0 +1,15 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env:due] +platform = atmelsam +board = due +framework = arduino +monitor_speed = 115200 diff --git a/test/DueSerialTest/src/main.cpp b/test/DueSerialTest/src/main.cpp new file mode 100644 index 0000000..2fbed5f --- /dev/null +++ b/test/DueSerialTest/src/main.cpp @@ -0,0 +1,78 @@ +#include + +enum SendModes { + ECHO, + ONLY_WRITE, + ONLY_READ, + WRITE_READ +}; + +enum StringModes { + FIXED, + VARIABLE +}; + +// Configure the test application here +SendModes SEND_MODE = SendModes::WRITE_READ; +StringModes STRING_MODE = StringModes::VARIABLE; +uint8_t STRING_IDX = 0; + +String STRINGS[4] = { + "$Hi\n", + "$Hello\n", + "$Hello World\n", + "$Hello and Merry Christmas to all of you!\n" +}; + +void setup() { + // put your setup code here, to run once: + Serial.begin(115200); + Serial.println("Starting Arduino Serial Test script.."); + Serial1.begin(115200); + if(STRING_MODE == StringModes::VARIABLE) { + STRING_IDX = 0; + } + pinMode(LED_BUILTIN, OUTPUT); +} + +void loop() { + static byte ICOMING_BYTE = 0; + static uint32_t GLOBAL_IDX = 0; + digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); + // put your main code here, to run repeatedly: + // send data only when you receive data: + if (SEND_MODE == SendModes::ONLY_WRITE or SEND_MODE == SendModes::WRITE_READ) { + Serial.println("Sending string.."); + Serial1.write(STRINGS[STRING_IDX].c_str()); + if(STRING_MODE == StringModes::VARIABLE) { + STRING_IDX += 1; + if(STRING_IDX > 3) { + STRING_IDX = 0; + } + } + } + if( + SEND_MODE == SendModes::WRITE_READ or + SEND_MODE == SendModes::ONLY_READ or + SEND_MODE == SendModes::ECHO + ) { + if (Serial1.available() > 0) { + // read the incoming byte: + String readString = Serial1.readStringUntil('\n'); + + Serial.print(GLOBAL_IDX); + Serial.print(" - "); + GLOBAL_IDX++; + // say what you got: + Serial.print("I received: "); + Serial.println(readString); + if(SEND_MODE == SendModes::ECHO) { + delay(200); + Serial.println("Sending back echo message"); + String sendBack = readString + '\n'; + Serial1.write(sendBack.c_str()); + } + } + } + delay(3000); +} diff --git a/test/DueSerialTest/test/README b/test/DueSerialTest/test/README new file mode 100644 index 0000000..b94d089 --- /dev/null +++ b/test/DueSerialTest/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PlatformIO Unit Testing and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PlatformIO Unit Testing: +- https://docs.platformio.org/page/plus/unit-testing.html From 9f5a31c5b6adb57cd7afabfdbafc925c8976cab3 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Mon, 20 Dec 2021 23:48:50 +0100 Subject: [PATCH 7/7] small changelog update --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b8f4fcc..34aa7a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,11 +8,17 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [v0.5.0] +### Added + - Reactored IRQ handling, so that `unmask` operations can be moved to HAL - Added UART IRQ handler. Right now, can only perform reception, TX still needs to be done in a blocking manner - Added RTIC template and RTIC UART IRQ application +### Fixed + +- Bugfix in UART code where RX and TX could not be enabled or disabled independently + ## [v0.4.3] - Various smaller fixes for READMEs, update of links in documentation