From abede6057e16032739b293c2e4469b4b67597a05 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Mon, 23 Sep 2024 17:13:49 +0200 Subject: [PATCH] UART with IRQ + Embassy example --- examples/embassy/Cargo.toml | 5 + .../embassy/src/bin/uart-echo-with-irq.rs | 164 ++++++++++++++++++ examples/embassy/src/lib.rs | 2 + examples/embassy/src/main.rs | 3 +- examples/embassy/src/time_driver.rs | 2 +- examples/simple/examples/dma.rs | 2 +- examples/simple/examples/pwm.rs | 3 +- flashloader/src/main.rs | 6 +- va416xx-hal/src/pwm.rs | 4 +- va416xx-hal/src/uart.rs | 24 +-- vscode/launch.json | 30 ++++ vscode/tasks.json | 15 +- 12 files changed, 238 insertions(+), 22 deletions(-) create mode 100644 examples/embassy/src/bin/uart-echo-with-irq.rs diff --git a/examples/embassy/Cargo.toml b/examples/embassy/Cargo.toml index 49eafe5..9239590 100644 --- a/examples/embassy/Cargo.toml +++ b/examples/embassy/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" cortex-m = { version = "0.7", features = ["critical-section-single-core"] } cortex-m-rt = "0.7" embedded-hal = "1" +embedded-io = "0.6" rtt-target = { version = "0.5" } panic-rtt-target = { version = "0.1" } @@ -16,6 +17,10 @@ embassy-sync = { version = "0.6.0" } embassy-time = { version = "0.3.2" } embassy-time-driver = { version = "0.1" } +[dependencies.ringbuf] +version = "0.4" +default-features = false + [dependencies.once_cell] version = "1" default-features = false diff --git a/examples/embassy/src/bin/uart-echo-with-irq.rs b/examples/embassy/src/bin/uart-echo-with-irq.rs new file mode 100644 index 0000000..cc8a599 --- /dev/null +++ b/examples/embassy/src/bin/uart-echo-with-irq.rs @@ -0,0 +1,164 @@ +//! This is an example of using the UART HAL abstraction with the IRQ support and embassy. +//! +//! It uses the UART0 for communication with another MCU or a host computer (recommended). +//! You can connect a USB-to-Serial converter to the UART0 pins and then use a serial terminal +//! application like picocom to send data to the microcontroller, which should be echoed +//! back to the sender. +//! +//! This application uses the interrupt support of the VA416xx to read the data arriving +//! on the UART without requiring polling. +#![no_std] +#![no_main] +use core::cell::RefCell; + +use embassy_example::EXTCLK_FREQ; +use embassy_executor::Spawner; +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +use embassy_sync::blocking_mutex::Mutex; +use embassy_time::{Duration, Ticker}; +use embedded_hal::digital::StatefulOutputPin; +use embedded_io::Write; +use panic_rtt_target as _; +use ringbuf::{ + traits::{Consumer, Observer, Producer}, + StaticRb, +}; +use rtt_target::{rprintln, rtt_init_print}; +use va416xx_hal::{ + gpio::{OutputReadablePushPull, Pin, PinsG, PG5}, + pac::{self, interrupt}, + prelude::*, + time::Hertz, + uart, +}; + +pub type SharedUart = Mutex>>>; +static RX: SharedUart = Mutex::new(RefCell::new(None)); + +const BAUDRATE: u32 = 115200; + +// Ring buffer size. +const RING_BUF_SIZE: usize = 2048; + +pub type SharedRingBuf = + Mutex>>>; +// Ring buffers to handling variable sized telemetry +static RINGBUF: SharedRingBuf = Mutex::new(RefCell::new(None)); + +// See https://embassy.dev/book/#_sharing_using_a_mutex for background information about sharing +// a peripheral with embassy. +#[embassy_executor::main] +async fn main(spawner: Spawner) { + rtt_init_print!(); + rprintln!("VA416xx UART-Embassy Example"); + + let mut dp = pac::Peripherals::take().unwrap(); + + // Initialize the systick interrupt & obtain the token to prove that we did + // Use the external clock connected to XTAL_N. + let clocks = dp + .clkgen + .constrain() + .xtal_n_clk_with_src_freq(Hertz::from_raw(EXTCLK_FREQ)) + .freeze(&mut dp.sysconfig) + .unwrap(); + // Safety: Only called once here. + unsafe { + embassy_example::init( + &mut dp.sysconfig, + &dp.irq_router, + dp.tim15, + dp.tim14, + &clocks, + ) + }; + + let portg = PinsG::new(&mut dp.sysconfig, dp.portg); + + let tx = portg.pg0.into_funsel_1(); + let rx = portg.pg1.into_funsel_1(); + + let uart0 = uart::Uart::new( + dp.uart0, + (tx, rx), + Hertz::from_raw(BAUDRATE), + &mut dp.sysconfig, + &clocks, + ); + let (mut tx, rx) = uart0.split(); + let mut rx = rx.to_rx_with_irq(); + rx.start(); + RX.lock(|static_rx| { + static_rx.borrow_mut().replace(rx); + }); + RINGBUF.lock(|static_rb| { + static_rb.borrow_mut().replace(StaticRb::default()); + }); + + let led = portg.pg5.into_readable_push_pull_output(); + let mut ticker = Ticker::every(Duration::from_millis(50)); + let mut processing_buf: [u8; RING_BUF_SIZE] = [0; RING_BUF_SIZE]; + let mut read_bytes = 0; + spawner.spawn(blinky(led)).expect("failed to spawn blinky"); + loop { + RINGBUF.lock(|static_rb| { + let mut rb_borrow = static_rb.borrow_mut(); + let rb_mut = rb_borrow.as_mut().unwrap(); + read_bytes = rb_mut.occupied_len(); + rb_mut.pop_slice(&mut processing_buf[0..read_bytes]); + }); + // Simply send back all received data. + tx.write_all(&processing_buf[0..read_bytes]) + .expect("sending back read data failed"); + ticker.next().await; + } +} + +#[embassy_executor::task] +async fn blinky(mut led: Pin) { + let mut ticker = Ticker::every(Duration::from_millis(500)); + loop { + led.toggle().ok(); + ticker.next().await; + } +} + +#[interrupt] +#[allow(non_snake_case)] +fn UART0_RX() { + let mut buf: [u8; 16] = [0; 16]; + let mut read_len: usize = 0; + let mut irq_error = None; + RX.lock(|static_rx| { + let mut rx_borrow = static_rx.borrow_mut(); + let rx_mut_ref = rx_borrow.as_mut().unwrap(); + match rx_mut_ref.irq_handler(&mut buf) { + Ok(result) => { + read_len = result.bytes_read; + } + Err(e) => { + irq_error = Some(e); + } + } + }); + let mut ringbuf_full = false; + if read_len > 0 { + // Send the received buffer to the main thread for processing via a ring buffer. + RINGBUF.lock(|static_rb| { + let mut rb_borrow = static_rb.borrow_mut(); + let rb_mut_ref = rb_borrow.as_mut().unwrap(); + if rb_mut_ref.vacant_len() < read_len { + ringbuf_full = true; + for _ in rb_mut_ref.pop_iter() {} + } + rb_mut_ref.push_slice(&buf[0..read_len]); + }); + } + + if irq_error.is_some() { + rprintln!("error in IRQ handler: {:?}", irq_error); + } + if ringbuf_full { + rprintln!("ringbuffer is full, deleted oldest data"); + } +} diff --git a/examples/embassy/src/lib.rs b/examples/embassy/src/lib.rs index b934a40..d00ea29 100644 --- a/examples/embassy/src/lib.rs +++ b/examples/embassy/src/lib.rs @@ -1,4 +1,6 @@ #![no_std] pub mod time_driver; +pub const EXTCLK_FREQ: u32 = 40_000_000; + pub use time_driver::init; diff --git a/examples/embassy/src/main.rs b/examples/embassy/src/main.rs index 6f75702..d8345a2 100644 --- a/examples/embassy/src/main.rs +++ b/examples/embassy/src/main.rs @@ -1,5 +1,6 @@ #![no_std] #![no_main] +use embassy_example::EXTCLK_FREQ; use embassy_executor::Spawner; use embassy_time::{Duration, Instant, Ticker}; use embedded_hal::digital::StatefulOutputPin; @@ -7,8 +8,6 @@ use panic_rtt_target as _; use rtt_target::{rprintln, rtt_init_print}; use va416xx_hal::{gpio::PinsG, pac, prelude::*, time::Hertz}; -const EXTCLK_FREQ: u32 = 40_000_000; - // main is itself an async function. #[embassy_executor::main] async fn main(_spawner: Spawner) { diff --git a/examples/embassy/src/time_driver.rs b/examples/embassy/src/time_driver.rs index bf88171..6e56a79 100644 --- a/examples/embassy/src/time_driver.rs +++ b/examples/embassy/src/time_driver.rs @@ -17,7 +17,7 @@ use va416xx_hal::{ enable_interrupt, irq_router::enable_and_init_irq_router, pac::{self, interrupt}, - pwm::{assert_tim_reset_for_two_cycles, enable_tim_clk, ValidTim}, + timer::{assert_tim_reset_for_two_cycles, enable_tim_clk, ValidTim}, }; pub type TimekeeperClk = pac::Tim15; diff --git a/examples/simple/examples/dma.rs b/examples/simple/examples/dma.rs index baf933e..4cf34bd 100644 --- a/examples/simple/examples/dma.rs +++ b/examples/simple/examples/dma.rs @@ -12,7 +12,7 @@ use rtt_target::{rprintln, rtt_init_print}; use simple_examples::peb1; use va416xx_hal::dma::{Dma, DmaCfg, DmaChannel, DmaCtrlBlock}; use va416xx_hal::irq_router::enable_and_init_irq_router; -use va416xx_hal::pwm::CountdownTimer; +use va416xx_hal::timer::CountdownTimer; use va416xx_hal::{ pac::{self, interrupt}, prelude::*, diff --git a/examples/simple/examples/pwm.rs b/examples/simple/examples/pwm.rs index 922ae00..2d94ec3 100644 --- a/examples/simple/examples/pwm.rs +++ b/examples/simple/examples/pwm.rs @@ -11,7 +11,8 @@ use va416xx_hal::{ gpio::PinsA, pac, prelude::*, - pwm::{self, get_duty_from_percent, CountdownTimer, PwmA, PwmB, ReducedPwmPin}, + pwm::{self, get_duty_from_percent, PwmA, PwmB, ReducedPwmPin}, + timer::CountdownTimer, }; #[entry] diff --git a/flashloader/src/main.rs b/flashloader/src/main.rs index 68210dd..96d546d 100644 --- a/flashloader/src/main.rs +++ b/flashloader/src/main.rs @@ -169,9 +169,9 @@ mod app { enable_and_init_irq_router(&mut cx.device.sysconfig, &cx.device.irq_router); setup_edac(&mut cx.device.sysconfig); - let gpiob = PinsG::new(&mut cx.device.sysconfig, cx.device.portg); - let tx = gpiob.pg0.into_funsel_1(); - let rx = gpiob.pg1.into_funsel_1(); + let gpiog = PinsG::new(&mut cx.device.sysconfig, cx.device.portg); + let tx = gpiog.pg0.into_funsel_1(); + let rx = gpiog.pg1.into_funsel_1(); let uart0 = Uart::new( cx.device.uart0, diff --git a/va416xx-hal/src/pwm.rs b/va416xx-hal/src/pwm.rs index deb5db4..3716dfa 100644 --- a/va416xx-hal/src/pwm.rs +++ b/va416xx-hal/src/pwm.rs @@ -9,8 +9,10 @@ use core::convert::Infallible; use core::marker::PhantomData; use crate::pac; +use crate::time::Hertz; +pub use crate::timer::ValidTim; +use crate::timer::{TimAndPinRegister, TimDynRegister, TimPin, TimRegInterface, ValidTimAndPin}; use crate::{clock::Clocks, gpio::DynPinId}; -pub use crate::{gpio::PinId, time::Hertz, timer::*}; const DUTY_MAX: u16 = u16::MAX; diff --git a/va416xx-hal/src/uart.rs b/va416xx-hal/src/uart.rs index 859aa5c..bd2f9c8 100644 --- a/va416xx-hal/src/uart.rs +++ b/va416xx-hal/src/uart.rs @@ -7,6 +7,7 @@ //! ## Examples //! //! - [UART simple example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/uart.rs) +//! - [UART echo with IRQ and Embassy](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/embassy/src/bin/uart-echo-with-irq.rs) //! - [Flashloader app using UART with IRQs](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/flashloader) use core::ops::Deref; @@ -806,7 +807,6 @@ impl RxWithIrq { /// API after calling this function to continue emptying the FIFO. pub fn irq_handler(&mut self, buf: &mut [u8; 16]) -> Result { let mut result = IrqResult::default(); - let mut current_idx = 0; let irq_end = self.uart().irq_end().read(); let enb_status = self.uart().enable().read(); @@ -814,27 +814,27 @@ impl RxWithIrq { // Half-Full interrupt. We have a guaranteed amount of data we can read. if irq_end.irq_rx().bit_is_set() { - // Determine the number of bytes to read, ensuring we leave 1 byte in the FIFO. - // We use this trick/hack because the timeout feature of the peripheral relies on data - // being in the RX FIFO. If data continues arriving, another half-full IRQ will fire. - // If not, the last byte(s) is/are emptied by the timeout interrupt. let available_bytes = self.uart().rxfifoirqtrg().read().bits() as usize; // If this interrupt bit is set, the trigger level is available at the very least. // Read everything as fast as possible for _ in 0..available_bytes { - buf[current_idx] = (self.uart().data().read().bits() & 0xff) as u8; - current_idx += 1; + buf[result.bytes_read] = (self.uart().data().read().bits() & 0xff) as u8; + result.bytes_read += 1; } } // Timeout, empty the FIFO completely. if irq_end.irq_rx_to().bit_is_set() { - let read_result = self.0.read(); - // While there is data in the FIFO, write it into the reception buffer - while let Some(byte) = self.read_handler(&mut result.errors, &read_result) { - buf[current_idx] = byte; - current_idx += 1; + loop { + // While there is data in the FIFO, write it into the reception buffer + let read_result = self.0.read(); + if let Some(byte) = self.read_handler(&mut result.errors, &read_result) { + buf[result.bytes_read] = byte; + result.bytes_read += 1; + } else { + break; + } } } diff --git a/vscode/launch.json b/vscode/launch.json index 0a41f77..d942236 100644 --- a/vscode/launch.json +++ b/vscode/launch.json @@ -350,6 +350,36 @@ ] } }, + { + "type": "cortex-debug", + "request": "launch", + "name": "UART Echo with IRQ", + "servertype": "jlink", + "jlinkscript": "${workspaceFolder}/jlink/JLinkSettings.JLinkScript", + "cwd": "${workspaceRoot}", + "device": "Cortex-M4", + "svdFile": "${workspaceFolder}/va416xx/svd/va416xx.svd.patched", + "preLaunchTask": "uart-echo-with-irq", + "overrideLaunchCommands": [ + "monitor halt", + "monitor reset", + "load", + ], + "executable": "${workspaceFolder}/target/thumbv7em-none-eabihf/debug/uart-echo-with-irq", + "interface": "swd", + "runToEntryPoint": "main", + "rttConfig": { + "enabled": true, + "address": "auto", + "decoders": [ + { + "port": 0, + "timestamp": true, + "type": "console" + } + ] + } + }, { "type": "cortex-debug", "request": "launch", diff --git a/vscode/tasks.json b/vscode/tasks.json index 4bc6c05..d20cc8f 100644 --- a/vscode/tasks.json +++ b/vscode/tasks.json @@ -95,6 +95,19 @@ "kind": "build", } }, + { + "label": "uart-echo-with-irq", + "type": "shell", + "command": "~/.cargo/bin/cargo", // note: full path to the cargo + "args": [ + "build", + "--bin", + "uart-echo-with-irq" + ], + "group": { + "kind": "build", + } + }, { "label": "pwm-example", "type": "shell", @@ -200,4 +213,4 @@ } }, ] -} +} \ No newline at end of file