mspm0: add buffered uart driver

And tests for G3507.
This commit is contained in:
i509VCB
2025-07-06 17:35:19 -05:00
parent 209b64a80b
commit a1867f0d74
6 changed files with 1288 additions and 41 deletions

View File

@@ -36,7 +36,10 @@ embassy-embedded-hal = { version = "0.3.1", path = "../embassy-embedded-hal", de
embassy-executor = { version = "0.7.0", path = "../embassy-executor", optional = true }
embedded-hal = { version = "1.0" }
embedded-hal-nb = { version = "1.0" }
embedded-hal-async = { version = "1.0" }
embedded-io = "0.6.1"
embedded-io-async = "0.6.1"
defmt = { version = "1.0.1", optional = true }
fixed = "1.29"
@@ -51,6 +54,7 @@ mspm0-metapac = { git = "https://github.com/mspm0-rs/mspm0-data-generated/", tag
[build-dependencies]
proc-macro2 = "1.0.94"
quote = "1.0.40"
cfg_aliases = "0.2.1"
# mspm0-metapac = { version = "", default-features = false, features = ["metadata"] }
mspm0-metapac = { git = "https://github.com/mspm0-rs/mspm0-data-generated/", tag = "mspm0-data-235158ac2865d8aac3a1eceb2d62026eb12bf38f", default-features = false, features = ["metadata"] }

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,11 @@
#![macro_use]
mod buffered;
use core::marker::PhantomData;
use core::sync::atomic::{compiler_fence, AtomicU32, Ordering};
pub use buffered::*;
use embassy_embedded_hal::SetConfig;
use embassy_hal_internal::PeripheralType;
@@ -128,8 +131,8 @@ pub struct Config {
// pub manchester: bool,
// TODO: majority voting
// TODO: fifo level select - need power domain info in metapac
/// If true: the built-in FIFO is enabled.
pub fifo_enable: bool,
// TODO: glitch suppression
/// If true: invert TX pin signal values (V<sub>DD</sub> = 0/mark, Gnd = 1/idle).
@@ -169,6 +172,7 @@ impl Default for Config {
msb_order: BitOrder::LsbFirst,
loop_back_enable: false,
// manchester: false,
fifo_enable: false,
invert_tx: false,
invert_rx: false,
invert_rts: false,
@@ -185,7 +189,7 @@ impl Default for Config {
///
/// ### Notes on [`embedded_io::Read`]
///
/// `embedded_io::Read` requires guarantees that the base [`UartRx`] cannot provide.
/// [`embedded_io::Read`] requires guarantees that the base [`UartRx`] cannot provide.
///
/// See [`UartRx`] for more details, and see [`BufferedUart`] and [`RingBufferedUartRx`]
/// as alternatives that do provide the necessary guarantees for `embedded_io::Read`.
@@ -199,8 +203,7 @@ impl<'d, M: Mode> SetConfig for Uart<'d, M> {
type ConfigError = ConfigError;
fn set_config(&mut self, config: &Self::Config) -> Result<(), Self::ConfigError> {
self.tx.set_config(config)?;
self.rx.set_config(config)
self.set_config(config)
}
}
@@ -236,6 +239,12 @@ impl core::fmt::Display for Error {
impl core::error::Error for Error {}
impl embedded_io::Error for Error {
fn kind(&self) -> embedded_io::ErrorKind {
embedded_io::ErrorKind::Other
}
}
/// Rx-only UART Driver.
///
/// Can be obtained from [`Uart::split`], or can be constructed independently,
@@ -260,7 +269,7 @@ impl<'d, M: Mode> SetConfig for UartRx<'d, M> {
impl<'d> UartRx<'d, Blocking> {
/// Create a new rx-only UART with no hardware flow control.
///
/// Useful if you only want Uart Rx. It saves 1 pin .
/// Useful if you only want Uart Rx. It saves 1 pin.
pub fn new_blocking<T: Instance>(
peri: Peri<'d, T>,
rx: Peri<'d, impl RxPin<T>>,
@@ -286,19 +295,6 @@ impl<'d> UartRx<'d, Blocking> {
}
impl<'d, M: Mode> UartRx<'d, M> {
/// Reconfigure the driver
pub fn set_config(&mut self, config: &Config) -> Result<(), ConfigError> {
if let Some(ref rx) = self.rx {
rx.update_pf(config.rx_pf());
}
if let Some(ref rts) = self.rts {
rts.update_pf(config.rts_pf());
}
reconfigure(self.info, self.state, config)
}
/// Perform a blocking read into `buffer`
pub fn blocking_read(&mut self, buffer: &mut [u8]) -> Result<(), Error> {
let r = self.info.regs;
@@ -315,6 +311,19 @@ impl<'d, M: Mode> UartRx<'d, M> {
Ok(())
}
/// Reconfigure the driver
pub fn set_config(&mut self, config: &Config) -> Result<(), ConfigError> {
if let Some(ref rx) = self.rx {
rx.update_pf(config.rx_pf());
}
if let Some(ref rts) = self.rts {
rts.update_pf(config.rts_pf());
}
reconfigure(self.info, self.state, config)
}
/// Set baudrate
pub fn set_baudrate(&self, baudrate: u32) -> Result<(), ConfigError> {
set_baudrate(&self.info, self.state.clock.load(Ordering::Relaxed), baudrate)
@@ -378,19 +387,6 @@ impl<'d> UartTx<'d, Blocking> {
}
impl<'d, M: Mode> UartTx<'d, M> {
/// Reconfigure the driver
pub fn set_config(&mut self, config: &Config) -> Result<(), ConfigError> {
if let Some(ref tx) = self.tx {
tx.update_pf(config.tx_pf());
}
if let Some(ref cts) = self.cts {
cts.update_pf(config.cts_pf());
}
reconfigure(self.info, self.state, config)
}
/// Perform a blocking UART write
pub fn blocking_write(&mut self, buffer: &[u8]) -> Result<(), Error> {
let r = self.info.regs;
@@ -427,6 +423,24 @@ impl<'d, M: Mode> UartTx<'d, M> {
});
}
/// Check if UART is busy.
pub fn busy(&self) -> bool {
busy(self.info.regs)
}
/// Reconfigure the driver
pub fn set_config(&mut self, config: &Config) -> Result<(), ConfigError> {
if let Some(ref tx) = self.tx {
tx.update_pf(config.tx_pf());
}
if let Some(ref cts) = self.cts {
cts.update_pf(config.cts_pf());
}
reconfigure(self.info, self.state, config)
}
/// Set baudrate
pub fn set_baudrate(&self, baudrate: u32) -> Result<(), ConfigError> {
set_baudrate(&self.info, self.state.clock.load(Ordering::Relaxed), baudrate)
@@ -489,6 +503,11 @@ impl<'d, M: Mode> Uart<'d, M> {
self.tx.blocking_flush()
}
/// Check if UART is busy.
pub fn busy(&self) -> bool {
self.tx.busy()
}
/// Perform a blocking read into `buffer`
pub fn blocking_read(&mut self, buffer: &mut [u8]) -> Result<(), Error> {
self.rx.blocking_read(buffer)
@@ -508,6 +527,12 @@ impl<'d, M: Mode> Uart<'d, M> {
(&mut self.tx, &mut self.rx)
}
/// Reconfigure the driver
pub fn set_config(&mut self, config: &Config) -> Result<(), ConfigError> {
self.tx.set_config(config)?;
self.rx.set_config(config)
}
/// Send break character
pub fn send_break(&self) {
self.tx.send_break();
@@ -557,8 +582,16 @@ pub(crate) struct Info {
}
pub(crate) struct State {
/// The clock rate of the UART. This might be configured.
pub(crate) clock: AtomicU32,
/// The clock rate of the UART in Hz.
clock: AtomicU32,
}
impl State {
pub const fn new() -> Self {
Self {
clock: AtomicU32::new(0),
}
}
}
impl<'d, M: Mode> UartRx<'d, M> {
@@ -746,7 +779,8 @@ fn configure(
info.regs.ctl0().modify(|w| {
w.set_lbe(config.loop_back_enable);
w.set_rxe(enable_rx);
// Errata UART_ERR_02, must set RXE to allow use of EOT.
w.set_rxe(enable_rx | enable_tx);
w.set_txe(enable_tx);
// RXD_OUT_EN and TXD_OUT_EN?
w.set_menc(false);
@@ -754,13 +788,18 @@ fn configure(
w.set_rtsen(enable_rts);
w.set_ctsen(enable_cts);
// oversampling is set later
// TODO: config
w.set_fen(false);
w.set_fen(config.fifo_enable);
// TODO: config
w.set_majvote(false);
w.set_msbfirst(matches!(config.msb_order, BitOrder::MsbFirst));
});
info.regs.ifls().modify(|w| {
// TODO: Need power domain info for other options.
w.set_txiflsel(vals::Iflssel::AT_LEAST_ONE);
w.set_rxiflsel(vals::Iflssel::AT_LEAST_ONE);
});
info.regs.lcrh().modify(|w| {
let eps = if matches!(config.parity, Parity::ParityEven) {
vals::Eps::EVEN
@@ -1021,9 +1060,29 @@ fn read_with_error(r: Regs) -> Result<u8, Error> {
Ok(rx.data())
}
/// This function assumes CTL0.ENABLE is set (for errata cases).
fn busy(r: Regs) -> bool {
// Errata UART_ERR_08
if cfg!(any(
mspm0g151x, mspm0g351x, mspm0l110x, mspm0l130x, mspm0l134x, mspm0c110x,
)) {
let stat = r.stat().read();
// "Poll TXFIFO status and the CTL0.ENABLE register bit to identify BUSY status."
!stat.txfe()
} else {
r.stat().read().busy()
}
}
// TODO: Implement when dma uart is implemented.
fn dma_enabled(_r: Regs) -> bool {
false
}
pub(crate) trait SealedInstance {
fn info() -> &'static Info;
fn state() -> &'static State;
fn buffered_state() -> &'static BufferedState;
}
macro_rules! impl_uart_instance {
@@ -1041,12 +1100,16 @@ macro_rules! impl_uart_instance {
}
fn state() -> &'static crate::uart::State {
use crate::interrupt::typelevel::Interrupt;
use crate::uart::State;
static STATE: State = State {
clock: core::sync::atomic::AtomicU32::new(0),
};
static STATE: State = State::new();
&STATE
}
fn buffered_state() -> &'static crate::uart::BufferedState {
use crate::uart::BufferedState;
static STATE: BufferedState = BufferedState::new();
&STATE
}
}

View File

@@ -17,5 +17,7 @@ defmt-rtt = "1.0.0"
panic-probe = { version = "1.0.0", features = ["print-defmt"] }
panic-semihosting = "0.6.0"
embedded-io-async = "0.6.1"
[profile.release]
debug = 2

View File

@@ -13,6 +13,7 @@ teleprobe-meta = "1.1"
embassy-sync = { version = "0.7.0", path = "../../embassy-sync", features = [ "defmt" ] }
embassy-executor = { version = "0.7.0", path = "../../embassy-executor", features = [ "arch-cortex-m", "executor-thread", "defmt" ] }
embassy-futures = { version = "0.1.0", path = "../../embassy-futures" }
embassy-time = { version = "0.4.0", path = "../../embassy-time", features = [ "defmt" ] }
embassy-mspm0 = { version = "0.1.0", path = "../../embassy-mspm0", features = [ "rt", "defmt", "unstable-pac", "time-driver-any" ] }
embassy-embedded-hal = { version = "0.3.1", path = "../../embassy-embedded-hal/"}
@@ -24,6 +25,8 @@ cortex-m = { version = "0.7.6", features = [ "inline-asm", "critical-section-sin
cortex-m-rt = "0.7.0"
embedded-hal = { package = "embedded-hal", version = "1.0" }
embedded-hal-async = { version = "1.0" }
embedded-io = { version = "0.6.1", features = ["defmt-03"] }
embedded-io-async = { version = "0.6.1", features = ["defmt-03"] }
panic-probe = { version = "1.0.0", features = ["print-defmt"] }
static_cell = "2"
portable-atomic = { version = "1.5", features = ["critical-section"] }

View File

@@ -0,0 +1,115 @@
#![no_std]
#![no_main]
#[cfg(feature = "mspm0g3507")]
teleprobe_meta::target!(b"lp-mspm0g3507");
use defmt::{assert_eq, unwrap, *};
use embassy_executor::Spawner;
use embassy_mspm0::uart::{BufferedInterruptHandler, BufferedUart, Config};
use embassy_mspm0::{bind_interrupts, peripherals};
use {defmt_rtt as _, panic_probe as _};
bind_interrupts!(struct Irqs {
UART1 => BufferedInterruptHandler<peripherals::UART1>;
});
#[embassy_executor::main]
async fn main(_spawner: Spawner) {
let p = embassy_mspm0::init(Default::default());
info!("Hello World!");
// TODO: Allow creating a looped-back UART (so pins are not needed).
// Do not select default UART since the virtual COM port is attached to UART0.
#[cfg(any(feature = "mspm0g3507"))]
let (mut tx, mut rx, mut uart) = (p.PA8, p.PA9, p.UART1);
{
use embedded_io_async::{Read, Write};
let mut config = Config::default();
config.loop_back_enable = true;
config.fifo_enable = false;
let tx_buf = &mut [0u8; 16];
let rx_buf = &mut [0u8; 16];
let mut uart = unwrap!(BufferedUart::new(
uart.reborrow(),
tx.reborrow(),
rx.reborrow(),
Irqs,
tx_buf,
rx_buf,
config
));
let mut buf = [0; 16];
for (j, b) in buf.iter_mut().enumerate() {
*b = j as u8;
}
unwrap!(uart.write_all(&buf).await);
unwrap!(uart.flush().await);
unwrap!(uart.read_exact(&mut buf).await);
for (j, b) in buf.iter().enumerate() {
assert_eq!(*b, j as u8);
}
// Buffer is unclogged, should be able to write again.
unwrap!(uart.write_all(&buf).await);
unwrap!(uart.flush().await);
unwrap!(uart.read_exact(&mut buf).await);
for (j, b) in buf.iter().enumerate() {
assert_eq!(*b, j as u8);
}
}
info!("Blocking buffered");
{
use embedded_io::{Read, Write};
let mut config = Config::default();
config.loop_back_enable = true;
config.fifo_enable = false;
let tx_buf = &mut [0u8; 16];
let rx_buf = &mut [0u8; 16];
let mut uart = unwrap!(BufferedUart::new(
uart.reborrow(),
tx.reborrow(),
rx.reborrow(),
Irqs,
tx_buf,
rx_buf,
config
));
let mut buf = [0; 16];
for (j, b) in buf.iter_mut().enumerate() {
*b = j as u8;
}
unwrap!(uart.write_all(&buf));
unwrap!(uart.blocking_flush());
unwrap!(uart.read_exact(&mut buf));
for (j, b) in buf.iter().enumerate() {
assert_eq!(*b, j as u8);
}
// Buffer is unclogged, should be able to write again.
unwrap!(uart.write_all(&buf));
unwrap!(uart.blocking_flush());
unwrap!(uart.read_exact(&mut buf));
for (j, b) in buf.iter().enumerate() {
assert_eq!(*b, j as u8, "at {}", j);
}
}
info!("Test OK");
cortex_m::asm::bkpt();
}