diff --git a/CHANGELOG.md b/CHANGELOG.md index b0a43e2..34aa7a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,18 @@ 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] + +### 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] diff --git a/Cargo.toml b/Cargo.toml index 1102a4e..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" @@ -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" @@ -55,9 +56,9 @@ name = "tests" required-features = ["rt"] [[example]] -name = "pwm" +name = "cascade" required-features = ["rt"] [[example]] -name = "cascade" +name = "uart-irq-rtic" 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/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/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-rtic.rs b/examples/uart-irq-rtic.rs new file mode 100644 index 0000000..cf16b7a --- /dev/null +++ b/examples/uart-irq-rtic.rs @@ -0,0 +1,119 @@ +//! UART example application. Sends a test string over a UART and then enters +//! echo mode +#![no_main] +#![no_std] + +#[rtic::app(device = pac, dispatchers = [OC4])] +mod app { + use core::fmt::Write; + use panic_rtt_target as _; + 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 { + irq_uart: UartWithIrqBase, + rx_buf: [u8; 64], + } + + #[init] + 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 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(), + ) + .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 + #[idle] + 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"); + }); + } +} diff --git a/src/gpio/dynpins.rs b/src/gpio/dynpins.rs index f3b2122..3d91504 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::{Funsel, IrqCfg}, }; 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..ea9dba4 100644 --- a/src/gpio/pins.rs +++ b/src/gpio/pins.rs @@ -92,8 +92,9 @@ 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, + utility::IrqCfg, Sealed, }; use core::convert::Infallible; @@ -397,11 +398,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 +411,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 +582,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 +627,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..99d0c80 100644 --- a/src/timer.rs +++ b/src/timer.rs @@ -4,6 +4,7 @@ //! //! - [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}, gpio::{ @@ -20,6 +21,7 @@ use crate::{ private::Sealed, time::Hertz, timer, + utility::unmask_irq, }; use core::cell::Cell; use cortex_m::interrupt::Mutex; @@ -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..675e257 100644 --- a/src/uart.rs +++ b/src/uart.rs @@ -7,20 +7,25 @@ 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, }, - pac::{uarta as uart_base, SYSCONFIG, UARTA, UARTB}, + pac::{uarta as uart_base, IRQSEL, SYSCONFIG, UARTA, UARTB}, prelude::*, time::{Bps, Hertz}, + utility::unmask_irq, }; use embedded_hal::{blocking, serial}; +//================================================================================================== +// Type-Level support +//================================================================================================== + pub trait Pins {} impl Pins for (Pin, Pin) {} @@ -38,12 +43,20 @@ 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, + /// Can be a combination of overrun, framing or parity error + IrqError, } #[derive(Debug, PartialEq, Copy, Clone)] @@ -160,13 +173,139 @@ impl From for Config { } } -/// Serial abstraction -pub struct Uart { +//================================================================================================== +// 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, + /// 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 { + 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) == 0x01 { + 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 +//================================================================================================== + +pub struct UartBase { uart: UART, - pins: PINS, tx: Tx, rx: Rx, } +/// Serial abstraction +pub struct Uart { + uart_base: UartBase, + pins: PINS, +} + +pub struct UartWithIrq { + irq_base: UartWithIrqBase, + pins: PINS, +} + +pub struct UartWithIrqBase { + pub uart: UartBase, + irq_info: IrqInfo, +} /// Serial receiver pub struct Rx { @@ -196,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 { @@ -247,7 +384,47 @@ where self } - pub fn listen(self, event: Event) -> Self { + #[inline] + pub fn enable_rx(&mut self) { + self.uart.enable.modify(|_, w| w.rxenable().set_bit()); + } + + #[inline] + pub fn disable_rx(&mut self) { + self.uart.enable.modify(|_, w| w.rxenable().clear_bit()); + } + + #[inline] + pub fn enable_tx(&mut self) { + self.uart.enable.modify(|_, w| w.txenable().set_bit()); + } + + #[inline] + pub fn disable_tx(&mut self) { + self.uart.enable.modify(|_, w| w.txenable().clear_bit()); + } + + #[inline] + pub fn clear_rx_fifo(&mut self) { + self.uart.fifo_clr.write(|w| w.rxfifo().set_bit()); + } + + #[inline] + pub fn clear_tx_fifo(&mut self) { + self.uart.fifo_clr.write(|w| w.txfifo().set_bit()); + } + + #[inline] + pub fn clear_rx_status(&mut self) { + self.uart.fifo_clr.write(|w| w.rxsts().set_bit()); + } + + #[inline] + pub fn clear_tx_status(&mut self) { + self.uart.fifo_clr.write(|w| w.txsts().set_bit()); + } + + pub fn listen(&self, event: Event) { self.uart.irq_enb.modify(|_, w| match event { Event::RxError => w.irq_rx_status().set_bit(), Event::RxFifoHalfFull => w.irq_rx().set_bit(), @@ -257,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(), @@ -270,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(); @@ -283,7 +458,7 @@ where w.rxenable().clear_bit(); w.txenable().clear_bit() }); - (self.uart, self.pins) + self.uart } pub fn split(self) -> (Tx, Rx) { @@ -291,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( @@ -310,15 +601,240 @@ 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 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 + } + + pub fn read_fixed_len_using_irq( + &mut self, + max_len: usize, + enb_timeout_irq: bool, + ) -> 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 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.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.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.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 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(); + let read_handler = + |res: &mut IrqResult, read_res: nb::Result| -> Result, Error> { + match read_res { + 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); + Err(Error::IrqError) + } + Error::FramingError => { + res.set_result(IrqResultMask::FramingError); + Err(Error::IrqError) + } + Error::ParityError => { + res.set_result(IrqResultMask::ParityError); + Err(Error::IrqError) + } + _ => { + res.set_result(IrqResultMask::Unknown); + 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.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 + let rx_status = self.uart.uart.rxstatus.read(); + res.clear_result(); + 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 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); + 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); + } + } + + // 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! { UARTA: (uarta, clock::PeripheralClocks::Uart0), UARTB: (uartb, clock::PeripheralClocks::Uart1), @@ -326,10 +842,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) @@ -339,6 +852,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 { @@ -370,7 +896,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 { @@ -378,6 +904,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; @@ -409,7 +943,7 @@ impl serial::Read for Rx { } } -impl core::fmt::Write for Tx +impl core::fmt::Write for Tx where Tx: embedded_hal::serial::Write, { @@ -420,3 +954,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) + } +} diff --git a/src/utility.rs b/src/utility.rs index edbe74d..2f8a609 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) }; +} 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