Merge pull request 'UART with IRQ + Embassy example' (#32) from add-uart-embassy-echo-example into main
All checks were successful
Rust/va416xx-rs/pipeline/head This commit looks good
All checks were successful
Rust/va416xx-rs/pipeline/head This commit looks good
Reviewed-on: #32
This commit is contained in:
commit
a50f7a947a
@ -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
|
||||||
|
164
examples/embassy/src/bin/uart-echo-with-irq.rs
Normal file
164
examples/embassy/src/bin/uart-echo-with-irq.rs
Normal 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");
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
@ -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) {
|
||||||
|
@ -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;
|
||||||
|
@ -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::*,
|
||||||
|
@ -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]
|
||||||
|
@ -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,
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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",
|
||||||
|
@ -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",
|
||||||
|
Loading…
Reference in New Issue
Block a user