UART with IRQ + Embassy example #32

Merged
muellerr merged 1 commits from add-uart-embassy-echo-example into main 2024-09-24 11:07:01 +02:00
12 changed files with 238 additions and 22 deletions

View File

@ -7,6 +7,7 @@ edition = "2021"
cortex-m = { version = "0.7", features = ["critical-section-single-core"] } cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
cortex-m-rt = "0.7" cortex-m-rt = "0.7"
embedded-hal = "1" embedded-hal = "1"
embedded-io = "0.6"
rtt-target = { version = "0.5" } rtt-target = { version = "0.5" }
panic-rtt-target = { version = "0.1" } panic-rtt-target = { version = "0.1" }
@ -16,6 +17,10 @@ embassy-sync = { version = "0.6.0" }
embassy-time = { version = "0.3.2" } embassy-time = { version = "0.3.2" }
embassy-time-driver = { version = "0.1" } embassy-time-driver = { version = "0.1" }
[dependencies.ringbuf]
version = "0.4"
default-features = false
[dependencies.once_cell] [dependencies.once_cell]
version = "1" version = "1"
default-features = false default-features = false

View File

@ -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<CriticalSectionRawMutex, RefCell<Option<uart::RxWithIrq<pac::Uart0>>>>;
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<CriticalSectionRawMutex, RefCell<Option<StaticRb<u8, RING_BUF_SIZE>>>>;
// 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<PG5, OutputReadablePushPull>) {
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");
}
}

View File

@ -1,4 +1,6 @@
#![no_std] #![no_std]
pub mod time_driver; pub mod time_driver;
pub const EXTCLK_FREQ: u32 = 40_000_000;
pub use time_driver::init; pub use time_driver::init;

View File

@ -1,5 +1,6 @@
#![no_std] #![no_std]
#![no_main] #![no_main]
use embassy_example::EXTCLK_FREQ;
use embassy_executor::Spawner; use embassy_executor::Spawner;
use embassy_time::{Duration, Instant, Ticker}; use embassy_time::{Duration, Instant, Ticker};
use embedded_hal::digital::StatefulOutputPin; use embedded_hal::digital::StatefulOutputPin;
@ -7,8 +8,6 @@ use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print}; use rtt_target::{rprintln, rtt_init_print};
use va416xx_hal::{gpio::PinsG, pac, prelude::*, time::Hertz}; use va416xx_hal::{gpio::PinsG, pac, prelude::*, time::Hertz};
const EXTCLK_FREQ: u32 = 40_000_000;
// main is itself an async function. // main is itself an async function.
#[embassy_executor::main] #[embassy_executor::main]
async fn main(_spawner: Spawner) { async fn main(_spawner: Spawner) {

View File

@ -17,7 +17,7 @@ use va416xx_hal::{
enable_interrupt, enable_interrupt,
irq_router::enable_and_init_irq_router, irq_router::enable_and_init_irq_router,
pac::{self, interrupt}, 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; pub type TimekeeperClk = pac::Tim15;

View File

@ -12,7 +12,7 @@ use rtt_target::{rprintln, rtt_init_print};
use simple_examples::peb1; use simple_examples::peb1;
use va416xx_hal::dma::{Dma, DmaCfg, DmaChannel, DmaCtrlBlock}; use va416xx_hal::dma::{Dma, DmaCfg, DmaChannel, DmaCtrlBlock};
use va416xx_hal::irq_router::enable_and_init_irq_router; use va416xx_hal::irq_router::enable_and_init_irq_router;
use va416xx_hal::pwm::CountdownTimer; use va416xx_hal::timer::CountdownTimer;
use va416xx_hal::{ use va416xx_hal::{
pac::{self, interrupt}, pac::{self, interrupt},
prelude::*, prelude::*,

View File

@ -11,7 +11,8 @@ use va416xx_hal::{
gpio::PinsA, gpio::PinsA,
pac, pac,
prelude::*, prelude::*,
pwm::{self, get_duty_from_percent, CountdownTimer, PwmA, PwmB, ReducedPwmPin}, pwm::{self, get_duty_from_percent, PwmA, PwmB, ReducedPwmPin},
timer::CountdownTimer,
}; };
#[entry] #[entry]

View File

@ -169,9 +169,9 @@ mod app {
enable_and_init_irq_router(&mut cx.device.sysconfig, &cx.device.irq_router); enable_and_init_irq_router(&mut cx.device.sysconfig, &cx.device.irq_router);
setup_edac(&mut cx.device.sysconfig); setup_edac(&mut cx.device.sysconfig);
let gpiob = PinsG::new(&mut cx.device.sysconfig, cx.device.portg); let gpiog = PinsG::new(&mut cx.device.sysconfig, cx.device.portg);
let tx = gpiob.pg0.into_funsel_1(); let tx = gpiog.pg0.into_funsel_1();
let rx = gpiob.pg1.into_funsel_1(); let rx = gpiog.pg1.into_funsel_1();
let uart0 = Uart::new( let uart0 = Uart::new(
cx.device.uart0, cx.device.uart0,

View File

@ -9,8 +9,10 @@ use core::convert::Infallible;
use core::marker::PhantomData; use core::marker::PhantomData;
use crate::pac; 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}; use crate::{clock::Clocks, gpio::DynPinId};
pub use crate::{gpio::PinId, time::Hertz, timer::*};
const DUTY_MAX: u16 = u16::MAX; const DUTY_MAX: u16 = u16::MAX;

View File

@ -7,6 +7,7 @@
//! ## Examples //! ## Examples
//! //!
//! - [UART simple example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/uart.rs) //! - [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) //! - [Flashloader app using UART with IRQs](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/flashloader)
use core::ops::Deref; use core::ops::Deref;
@ -806,7 +807,6 @@ impl<Uart: Instance> RxWithIrq<Uart> {
/// API after calling this function to continue emptying the FIFO. /// API after calling this function to continue emptying the FIFO.
pub fn irq_handler(&mut self, buf: &mut [u8; 16]) -> Result<IrqResult, IrqUartError> { pub fn irq_handler(&mut self, buf: &mut [u8; 16]) -> Result<IrqResult, IrqUartError> {
let mut result = IrqResult::default(); let mut result = IrqResult::default();
let mut current_idx = 0;
let irq_end = self.uart().irq_end().read(); let irq_end = self.uart().irq_end().read();
let enb_status = self.uart().enable().read(); let enb_status = self.uart().enable().read();
@ -814,27 +814,27 @@ impl<Uart: Instance> RxWithIrq<Uart> {
// Half-Full interrupt. We have a guaranteed amount of data we can read. // Half-Full interrupt. We have a guaranteed amount of data we can read.
if irq_end.irq_rx().bit_is_set() { if irq_end.irq_rx().bit_is_set() {
// Determine the number of bytes to read, ensuring we leave 1 byte in the FIFO.
// We use this trick/hack because the timeout feature of the peripheral relies on data
// being in the RX FIFO. If data continues arriving, another half-full IRQ will fire.
// If not, the last byte(s) is/are emptied by the timeout interrupt.
let available_bytes = self.uart().rxfifoirqtrg().read().bits() as usize; let available_bytes = self.uart().rxfifoirqtrg().read().bits() as usize;
// If this interrupt bit is set, the trigger level is available at the very least. // If this interrupt bit is set, the trigger level is available at the very least.
// Read everything as fast as possible // Read everything as fast as possible
for _ in 0..available_bytes { for _ in 0..available_bytes {
buf[current_idx] = (self.uart().data().read().bits() & 0xff) as u8; buf[result.bytes_read] = (self.uart().data().read().bits() & 0xff) as u8;
current_idx += 1; result.bytes_read += 1;
} }
} }
// Timeout, empty the FIFO completely. // Timeout, empty the FIFO completely.
if irq_end.irq_rx_to().bit_is_set() { if irq_end.irq_rx_to().bit_is_set() {
let read_result = self.0.read(); loop {
// While there is data in the FIFO, write it into the reception buffer // 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) { let read_result = self.0.read();
buf[current_idx] = byte; if let Some(byte) = self.read_handler(&mut result.errors, &read_result) {
current_idx += 1; buf[result.bytes_read] = byte;
result.bytes_read += 1;
} else {
break;
}
} }
} }

View File

@ -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", "type": "cortex-debug",
"request": "launch", "request": "launch",

View File

@ -95,6 +95,19 @@
"kind": "build", "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", "label": "pwm-example",
"type": "shell", "type": "shell",