From b18e32e0cc923b9aa51d22f434c9b95027d9c712 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Tue, 9 Nov 2021 18:30:46 +0100 Subject: [PATCH] Rust edition bumped & UART implementation - Also adds UART example --- CHANGELOG.md | 10 ++ Cargo.toml | 3 +- examples/tests.rs | 4 +- examples/uart.rs | 44 +++++ src/clock.rs | 3 +- src/gpio.rs | 32 ++-- src/lib.rs | 5 + src/timer.rs | 5 + src/uart.rs | 417 ++++++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 505 insertions(+), 18 deletions(-) create mode 100644 examples/uart.rs create mode 100644 src/uart.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c89129..dc598a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,16 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [unreleased] + +## [0.2.0] + +### Added + +- UART implementation +- UART example +- Some bugfixes for GPIO implementation +- Rust edition updated to 2021 + ## [0.1.0] ### Added diff --git a/Cargo.toml b/Cargo.toml index e673b71..2d1459a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "va108xx-hal" version = "0.1.0" authors = ["Robin Mueller "] -edition = "2018" +edition = "2021" description = "HAL for the Vorago VA108xx family of microcontrollers" homepage = "https://github.com/robamu-org/va108xx-hal-rs" repository = "https://github.com/robamu-org/va108xx-hal-rs" @@ -17,6 +17,7 @@ nb = "1" embedded-hal = { features = ["unproven"], version = "0.2.6" } void = { version = "1.0", default-features = false } once_cell = { version = "1.8.0", default-features = false } +libm = "0.2.1" [dependencies.va108xx] version = "0.1" diff --git a/examples/tests.rs b/examples/tests.rs index fec7915..6fca5a3 100644 --- a/examples/tests.rs +++ b/examples/tests.rs @@ -72,7 +72,9 @@ fn main() -> ! { } TestCase::TestPullup => { // Tie PORTA[0] to PORTA[1] for these tests! - let input = porta.pa1.into_pull_up_input(&mut dp.IOCONFIG); + let input = porta + .pa1 + .into_pull_up_input(&mut dp.IOCONFIG, &mut dp.PORTA); assert!(input.is_high().unwrap()); let mut out = porta .pa0 diff --git a/examples/uart.rs b/examples/uart.rs new file mode 100644 index 0000000..01a06d3 --- /dev/null +++ b/examples/uart.rs @@ -0,0 +1,44 @@ +//! UART example application. Sends a test string over a UART and then enters +//! echo mode +#![no_main] +#![no_std] + +use core::fmt::Write; +use cortex_m_rt::entry; +use panic_rtt_target as _; +use rtt_target::{rprintln, rtt_init_print}; +use va108xx_hal::{pac, prelude::*, uart}; + +#[entry] +fn main() -> ! { + rtt_init_print!(); + rprintln!("-- VA108xx UART test application--"); + + let mut dp = pac::Peripherals::take().unwrap(); + + let gpiob = dp.PORTB.split(&mut dp.SYSCONFIG).unwrap(); + let tx = gpiob.pb21.into_funsel_1(&mut dp.IOCONFIG); + let rx = gpiob.pb20.into_funsel_1(&mut dp.IOCONFIG); + + let uartb = uart::Uart::uartb( + dp.UARTB, + (tx, rx), + 115200.bps(), + &mut dp.SYSCONFIG, + 50.mhz().into(), + ); + let (mut tx, mut rx) = uartb.split(); + writeln!(tx, "Hello World\r").unwrap(); + loop { + // Echo what is received on the serial link. + match rx.read() { + Ok(recv) => { + nb::block!(tx.write(recv)).expect("TX send error"); + } + Err(nb::Error::WouldBlock) => (), + Err(nb::Error::Other(uart_error)) => { + rprintln!("UART receive error {:?}", uart_error); + } + } + } +} diff --git a/src/clock.rs b/src/clock.rs index d5a21dc..e8740ae 100644 --- a/src/clock.rs +++ b/src/clock.rs @@ -5,13 +5,14 @@ use va108xx::SYSCONFIG; static SYS_CLOCK: Mutex> = Mutex::new(OnceCell::new()); +#[derive(Copy, Clone, PartialEq)] pub enum PeripheralClocks { PortA = 0, PortB = 1, Spi0 = 4, Spi1 = 5, Spi2 = 6, - UArt0 = 8, + Uart0 = 8, Uart1 = 9, I2c0 = 16, I2c1 = 17, diff --git a/src/gpio.rs b/src/gpio.rs index 8388398..701aeef 100644 --- a/src/gpio.rs +++ b/src/gpio.rs @@ -1,3 +1,8 @@ +//! API for the GPIO pins +//! +//! ## Examples +//! +//! - [Blinky example](https://github.com/robamu-org/va108xx-hal-rs/blob/main/examples/blinky.rs) use crate::pac::SYSCONFIG; use core::convert::Infallible; use core::marker::PhantomData; @@ -71,7 +76,7 @@ pub struct FUNSEL2; pub struct FUNSEL3; /// Function select (type state) -pub struct Funsel { +pub struct AltFunc { _mode: PhantomData, } @@ -207,7 +212,7 @@ macro_rules! gpio { use core::marker::PhantomData; use core::convert::Infallible; use super::{ - FUNSEL1, FUNSEL2, FUNSEL3, Floating, Funsel, GpioExt, Input, OpenDrain, + FUNSEL1, FUNSEL2, FUNSEL3, Floating, AltFunc, GpioExt, Input, OpenDrain, PullUp, Output, FilterType, FilterClkSel, Pin, GpioRegExt, PushPull, PinModeError, PinState, PortId, singleton }; @@ -247,11 +252,9 @@ macro_rules! gpio { } fn _set_alternate_mode(iocfg: &mut IOCONFIG, index: usize, mode: u8) { - unsafe { - iocfg.$portx[index].modify(|_, w| { - w.funsel().bits(mode) - }) - } + iocfg.$portx[index].modify(|_, w| unsafe { + w.funsel().bits(mode) + }); } $( @@ -260,15 +263,15 @@ macro_rules! gpio { } impl $PXi { - pub fn into_funsel_1(self, iocfg: &mut IOCONFIG) -> $PXi> { + pub fn into_funsel_1(self, iocfg: &mut IOCONFIG) -> $PXi> { _set_alternate_mode(iocfg, $i, 1); $PXi { _mode: PhantomData } } - pub fn into_funsel_2(self, iocfg: &mut IOCONFIG) -> $PXi> { + pub fn into_funsel_2(self, iocfg: &mut IOCONFIG) -> $PXi> { _set_alternate_mode(iocfg, $i, 2); $PXi { _mode: PhantomData } } - pub fn into_funsel_3(self, iocfg: &mut IOCONFIG) -> $PXi> { + pub fn into_funsel_3(self, iocfg: &mut IOCONFIG) -> $PXi> { _set_alternate_mode(iocfg, $i, 3); $PXi { _mode: PhantomData } } @@ -312,7 +315,7 @@ macro_rules! gpio { $PXi { _mode: PhantomData } } - pub fn into_pull_up_input(self, iocfg: &mut IOCONFIG) -> $PXi> { + pub fn into_pull_up_input(self, iocfg: &mut IOCONFIG, port: &mut $PORTX) -> $PXi> { unsafe { iocfg.$portx[$i].modify(|_, w| { w.funsel().bits(0); @@ -320,14 +323,13 @@ macro_rules! gpio { w.plevel().set_bit(); w.opendrn().clear_bit() }); - let port_reg = &(*$PORTX::ptr()); - port_reg.dir().modify(|r,w| w.bits(r.bits() & !(1 << $i))); + port.dir().modify(|r,w| w.bits(r.bits() & !(1 << $i))); } $PXi { _mode: PhantomData } } pub fn into_pull_down_input( - self, iocfg: &mut IOCONFIG, port_reg: &mut $PORTX + self, iocfg: &mut IOCONFIG, port: &mut $PORTX ) -> $PXi> { unsafe { iocfg.$portx[$i].modify(|_, w| { @@ -336,7 +338,7 @@ macro_rules! gpio { w.plevel().clear_bit(); w.opendrn().clear_bit() }); - port_reg.dir().modify(|r,w| w.bits(r.bits() & !(1 << $i))); + port.dir().modify(|r,w| w.bits(r.bits() & !(1 << $i))); } $PXi { _mode: PhantomData } } diff --git a/src/lib.rs b/src/lib.rs index 5c4fb8a..8c9e872 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,5 +7,10 @@ pub mod gpio; pub mod prelude; pub mod time; pub mod timer; +pub mod uart; pub use va108xx as pac; + +mod sealed { + pub trait Sealed {} +} diff --git a/src/timer.rs b/src/timer.rs index c34e3ee..7b15c02 100644 --- a/src/timer.rs +++ b/src/timer.rs @@ -1,3 +1,8 @@ +//! API for the TIM peripherals +//! +//! ## Examples +//! +//! - [MS and second tick implementation](https://github.com/robamu-org/va108xx-hal-rs/blob/main/examples/timer-ticks.rs) use crate::{ clock::{enable_peripheral_clock, PeripheralClocks}, time::Hertz, diff --git a/src/uart.rs b/src/uart.rs new file mode 100644 index 0000000..7ceef9a --- /dev/null +++ b/src/uart.rs @@ -0,0 +1,417 @@ +//! API for the UART peripheral +use core::{convert::Infallible, ptr}; +use core::{marker::PhantomData, ops::Deref}; +use libm::floorf; + +use crate::clock::enable_peripheral_clock; +use crate::{ + clock, + gpio::porta::{PA16, PA17, PA18, PA19, PA2, PA26, PA27, PA3, PA30, PA31, PA8, PA9}, + gpio::portb::{PB18, PB19, PB20, PB21, PB22, PB23, PB6, PB7, PB8, PB9}, + gpio::{AltFunc, FUNSEL1, FUNSEL2, FUNSEL3}, + pac::{uarta as uart_base, SYSCONFIG, UARTA, UARTB}, + prelude::*, + time::{Bps, Hertz}, +}; + +use embedded_hal::{blocking, serial}; + +pub trait Pins {} + +impl Pins for (PA9>, PA8>) {} +impl Pins for (PA17>, PA16>) {} +impl Pins for (PA31>, PA30>) {} + +impl Pins for (PB9>, PB8>) {} +impl Pins for (PB23>, PB22>) {} + +impl Pins for (PA3>, PA2>) {} +impl Pins for (PA19>, PA18>) {} +impl Pins for (PA27>, PA26>) {} + +impl Pins for (PB7>, PB6>) {} +impl Pins for (PB19>, PB18>) {} +impl Pins for (PB21>, PB20>) {} + +#[derive(Debug)] +pub enum Error { + Overrun, + FramingError, + ParityError, + BreakCondition, +} + +#[derive(Copy, Clone, PartialEq)] +pub enum Event { + // Receiver FIFO interrupt enable. Generates interrupt + // when FIFO is at least half full. Half full is defined as FIFO + // count >= RXFIFOIRQTRG + RxFifoHalfFull, + // Framing error, Overrun error, Parity Error and Break error + RxError, + // Event for timeout condition: Data in the FIFO and no receiver + // FIFO activity for 4 character times + RxTimeout, + + // Transmitter FIFO interrupt enable. Generates interrupt + // when FIFO is at least half full. Half full is defined as FIFO + // count >= TXFIFOIRQTRG + TxFifoHalfFull, + // FIFO overflow error + TxError, + // Generate interrupt when transmit FIFO is empty and TXBUSY is 0 + TxEmpty, + // Interrupt when CTSn changes value + TxCts, +} + +#[derive(Copy, Clone, PartialEq)] +pub enum Parity { + None, + Odd, + Even, +} + +#[derive(Copy, Clone, PartialEq)] +pub enum StopBits { + One = 0, + Two = 1, +} + +#[derive(Copy, Clone, PartialEq)] +pub enum WordSize { + Five = 0, + Six = 1, + Seven = 2, + Eight = 3, +} + +pub struct Config { + pub baudrate: Bps, + pub parity: Parity, + pub stopbits: StopBits, + // When false, use standard 16x baud clock, other 8x baud clock + pub baud8: bool, + pub wordsize: WordSize, + pub enable_tx: bool, + pub enable_rx: bool, +} + +impl Config { + pub fn baudrate(mut self, baudrate: Bps) -> Self { + self.baudrate = baudrate; + self + } + + pub fn parity_none(mut self) -> Self { + self.parity = Parity::None; + self + } + + pub fn parity_even(mut self) -> Self { + self.parity = Parity::Even; + self + } + + pub fn parity_odd(mut self) -> Self { + self.parity = Parity::Odd; + self + } + + pub fn stopbits(mut self, stopbits: StopBits) -> Self { + self.stopbits = stopbits; + self + } + + pub fn wordsize(mut self, wordsize: WordSize) -> Self { + self.wordsize = wordsize; + self + } + + pub fn baud8(mut self, baud: bool) -> Self { + self.baud8 = baud; + self + } +} + +impl Default for Config { + fn default() -> Config { + let baudrate = 115_200_u32.bps(); + Config { + baudrate, + parity: Parity::None, + stopbits: StopBits::One, + baud8: false, + wordsize: WordSize::Eight, + enable_tx: true, + enable_rx: true, + } + } +} + +impl From for Config { + fn from(baud: Bps) -> Self { + Config::default().baudrate(baud) + } +} + +/// Serial abstraction +pub struct Uart { + uart: UART, + pins: PINS, + tx: Tx, + rx: Rx, +} + +/// Serial receiver +pub struct Rx { + _usart: PhantomData, +} + +/// Serial transmitter +pub struct Tx { + _usart: PhantomData, +} + +impl Rx { + fn new() -> Self { + Self { + _usart: PhantomData, + } + } +} + +impl Tx { + fn new() -> Self { + Self { + _usart: PhantomData, + } + } +} + +pub trait Instance: Deref { + fn ptr() -> *const uart_base::RegisterBlock; +} + +impl Uart +where + UART: Instance, +{ + /// This function assumes that the peripheral clock was alredy enabled + /// in the SYSCONFIG register + fn init(self, config: Config, sys_clk: Hertz) -> Self { + let baud_multiplier = match config.baud8 { + false => 16, + true => 8, + }; + let x = sys_clk.0 as f32 / (config.baudrate.0 * baud_multiplier) as f32; + let integer_part = floorf(x) as u32; + let frac = floorf((64.0 * (x - integer_part as f32) + 0.5) as f32) as u32; + self.uart + .clkscale + .write(|w| unsafe { w.bits(integer_part * 64 + frac) }); + + let (paren, pareven) = match config.parity { + Parity::None => (false, false), + Parity::Odd => (true, false), + Parity::Even => (true, true), + }; + let stopbits = match config.stopbits { + StopBits::One => false, + StopBits::Two => true, + }; + let wordsize = config.wordsize as u8; + let baud8 = config.baud8; + self.uart.ctrl.write(|w| { + w.paren().bit(paren); + w.pareven().bit(pareven); + w.stopbits().bit(stopbits); + w.baud8().bit(baud8); + unsafe { w.wordsize().bits(wordsize) } + }); + let (txenb, rxenb) = (config.enable_tx, config.enable_rx); + // Clear the FIFO + self.uart.fifo_clr.write(|w| { + w.rxfifo().set_bit(); + w.txfifo().set_bit() + }); + self.uart.enable.write(|w| { + w.rxenable().bit(rxenb); + w.txenable().bit(txenb) + }); + self + } + + pub fn listen(self, event: Event) -> Self { + self.uart.irq_enb.modify(|_, w| match event { + Event::RxError => w.irq_rx_status().set_bit(), + Event::RxFifoHalfFull => w.irq_rx().set_bit(), + Event::RxTimeout => w.irq_rx_to().set_bit(), + Event::TxEmpty => w.irq_tx_empty().set_bit(), + Event::TxError => w.irq_tx_status().set_bit(), + Event::TxFifoHalfFull => w.irq_tx().set_bit(), + Event::TxCts => w.irq_tx_cts().set_bit(), + }); + self + } + + pub fn unlisten(self, event: Event) -> Self { + self.uart.irq_enb.modify(|_, w| match event { + Event::RxError => w.irq_rx_status().clear_bit(), + Event::RxFifoHalfFull => w.irq_rx().clear_bit(), + Event::RxTimeout => w.irq_rx_to().clear_bit(), + Event::TxEmpty => w.irq_tx_empty().clear_bit(), + Event::TxError => w.irq_tx_status().clear_bit(), + Event::TxFifoHalfFull => w.irq_tx().clear_bit(), + Event::TxCts => w.irq_tx_cts().clear_bit(), + }); + self + } + + pub fn release(self) -> (UART, PINS) { + // Clear the FIFO + self.uart.fifo_clr.write(|w| { + w.rxfifo().set_bit(); + w.txfifo().set_bit() + }); + self.uart.enable.write(|w| { + w.rxenable().clear_bit(); + w.txenable().clear_bit() + }); + (self.uart, self.pins) + } + + pub fn split(self) -> (Tx, Rx) { + (self.tx, self.rx) + } +} + +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( + uart: $UARTX, + pins: PINS, + config: impl Into, + syscfg: &mut SYSCONFIG, + sys_clk: Hertz + ) -> Self + { + enable_peripheral_clock(syscfg, $clk_enb_enum); + Uart { uart, pins, tx: Tx::new(), rx: Rx::new() }.init( + config.into(), sys_clk + ) + } + } + )+ + } +} + +uart_impl! { + UARTA: (uarta, clock::PeripheralClocks::Uart0), + UARTB: (uartb, clock::PeripheralClocks::Uart1), +} + +impl Tx where UART: Instance {} + +impl serial::Write for Uart +where + UART: Instance, +{ + type Error = Infallible; + fn write(&mut self, word: u8) -> nb::Result<(), Self::Error> { + self.tx.write(word) + } + fn flush(&mut self) -> nb::Result<(), Self::Error> { + self.tx.flush() + } +} + +impl blocking::serial::write::Default for Uart {} + +impl serial::Write for Tx { + type Error = Infallible; + + fn write(&mut self, word: u8) -> nb::Result<(), Self::Error> { + let reader = unsafe { &(*UART::ptr()) }.txstatus.read(); + if reader.wrrdy().bit_is_clear() { + return Err(nb::Error::WouldBlock); + } else { + // DPARITY bit not supported yet + unsafe { + // NOTE(unsafe) atomic write to data register + // NOTE(write_volatile) 8-bit write that's not + // possible through the svd2rust API + ptr::write_volatile(&(*UART::ptr()).data as *const _ as *mut _, word); + } + } + Ok(()) + } + + fn flush(&mut self) -> nb::Result<(), Self::Error> { + let reader = unsafe { &(*UART::ptr()) }.txstatus.read(); + if reader.wrbusy().bit_is_clear() { + Ok(()) + } else { + Err(nb::Error::WouldBlock) + } + } +} + +impl serial::Read for Uart { + type Error = Error; + + fn read(&mut self) -> nb::Result { + self.rx.read() + } +} + +impl serial::Read for Rx { + type Error = Error; + + fn read(&mut self) -> nb::Result { + let uart = unsafe { &(*UART::ptr()) }; + let status_reader = uart.rxstatus.read(); + let err = if status_reader.rxovr().bit_is_set() { + Some(Error::Overrun) + } else if status_reader.rxfrm().bit_is_set() { + Some(Error::FramingError) + } else if status_reader.rxpar().bit_is_set() { + Some(Error::ParityError) + } else { + None + }; + if let Some(err) = err { + // The status code is always related to the next bit for the framing + // and parity status bits. We have to read the DATA register + // so that the next status reflects the next DATA word + // For overrun error, we read as well to clear the peripheral + uart.data.read().bits(); + Err(err.into()) + } else if status_reader.rdavl().bit_is_set() { + let data = uart.data.read().bits(); + Ok((data & 0xff) as u8) + } else { + Err(nb::Error::WouldBlock) + } + } +} + +impl core::fmt::Write for Tx +where + Tx: embedded_hal::serial::Write, +{ + fn write_str(&mut self, s: &str) -> core::fmt::Result { + s.as_bytes() + .iter() + .try_for_each(|c| nb::block!(self.write(*c))) + .map_err(|_| core::fmt::Error) + } +}