From 0e395d37479e0a1fa5f8a94d1c734dc9cd03b89f Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Thu, 13 Jun 2024 16:45:56 +0200 Subject: [PATCH 1/3] completed UART HAL, added first example --- va416xx-hal/Cargo.toml | 6 + va416xx-hal/examples/uart.rs | 48 ++ va416xx-hal/src/lib.rs | 1 + va416xx-hal/src/time.rs | 160 +----- va416xx-hal/src/uart.rs | 967 +++++++++++++++++++++++++++++++++++ 5 files changed, 1037 insertions(+), 145 deletions(-) create mode 100644 va416xx-hal/examples/uart.rs create mode 100644 va416xx-hal/src/uart.rs diff --git a/va416xx-hal/Cargo.toml b/va416xx-hal/Cargo.toml index ae093dd..e192432 100644 --- a/va416xx-hal/Cargo.toml +++ b/va416xx-hal/Cargo.toml @@ -15,8 +15,13 @@ cortex-m = "0.7" cortex-m-rt = "0.7" nb = "1" paste = "1" +embedded-hal-nb = "1" embedded-hal = "1" +embedded-io = "0.6" typenum = "1.12.0" +defmt = { version = "0.3", optional = true } +fugit = "0.3" +delegate = "0.12" [dependencies.va416xx] path = "../va416xx" @@ -24,6 +29,7 @@ version = "0.1.0" [features] rt = ["va416xx/rt"] +defmt = ["dep:defmt"] [dev-dependencies] panic-rtt-target = { version = "0.1.3" } diff --git a/va416xx-hal/examples/uart.rs b/va416xx-hal/examples/uart.rs new file mode 100644 index 0000000..672e3b4 --- /dev/null +++ b/va416xx-hal/examples/uart.rs @@ -0,0 +1,48 @@ +//! UART example application. Sends a test string over a UART and then enters +//! echo mode +#![no_main] +#![no_std] + +use cortex_m_rt::entry; +use embedded_io::{Read, Write}; +use panic_rtt_target as _; +use rtt_target::{rprintln, rtt_init_print}; +use va416xx_hal::time::{Hertz, MegaHertz}; +use va416xx_hal::{gpio::PinsG, pac, uart}; + +#[entry] +fn main() -> ! { + rtt_init_print!(); + rprintln!("-- VA416xx UART example application--"); + + // SAFETY: Peripherals are only stolen once here. + let mut dp = unsafe { pac::Peripherals::steal() }; + + let gpiob = PinsG::new(&mut dp.sysconfig, Some(dp.ioconfig), dp.portg); + let tx = gpiob.pg0.into_funsel_1(); + let rx = gpiob.pg1.into_funsel_1(); + + let uartb = uart::Uart::uart0( + dp.uart0, + (tx, rx), + Hertz::from_raw(115200), + &mut dp.sysconfig, + Hertz::from_raw(MegaHertz::from_raw(20).to_Hz()), + ); + let (mut tx, mut rx) = uartb.split(); + let mut recv_buf: [u8; 32] = [0; 32]; + writeln!(tx, "Hello World").unwrap(); + loop { + // Echo what is received on the serial link. + match rx.read(&mut recv_buf) { + Ok(recvd) => { + if let Err(e) = tx.write(&recv_buf[0..recvd]) { + rprintln!("UART TX error: {:?}", e); + } + } + Err(e) => { + rprintln!("UART RX error {:?}", e); + } + } + } +} diff --git a/va416xx-hal/src/lib.rs b/va416xx-hal/src/lib.rs index 6aad93a..0f54b47 100644 --- a/va416xx-hal/src/lib.rs +++ b/va416xx-hal/src/lib.rs @@ -8,6 +8,7 @@ pub mod clock; pub mod gpio; pub mod time; pub mod typelevel; +pub mod uart; #[derive(Debug, Eq, Copy, Clone, PartialEq)] pub enum FunSel { diff --git a/va416xx-hal/src/time.rs b/va416xx-hal/src/time.rs index 6b0200e..9808028 100644 --- a/va416xx-hal/src/time.rs +++ b/va416xx-hal/src/time.rs @@ -1,156 +1,26 @@ //! Time units -//! -//! See [`Hertz`], [`KiloHertz`] and [`MegaHertz`] for creating increasingly higher frequencies. -//! -//! The [`U32Ext`] trait adds various methods like `.hz()`, `.mhz()`, etc to the `u32` primitive type, -//! allowing it to be converted into frequencies. -/// Bits per second -#[derive(Clone, Copy, PartialEq, PartialOrd, Debug)] -pub struct Bps(pub u32); +// Frequency based /// Hertz -/// -/// Create a frequency specified in [Hertz](https://en.wikipedia.org/wiki/Hertz). -/// -/// See also [`KiloHertz`] and [`MegaHertz`] for semantically correct ways of creating higher -/// frequencies. -/// -/// # Examples -/// -/// ## Create an 60 Hz frequency -/// -/// ```rust -/// use stm32f1xx_hal::time::Hertz; -/// -/// let freq = 60.hz(); -/// ``` -#[derive(Clone, Copy, PartialEq, PartialOrd, Debug)] -pub struct Hertz(pub u32); +pub type Hertz = fugit::HertzU32; -/// Kilohertz -/// -/// Create a frequency specified in kilohertz. -/// -/// See also [`Hertz`] and [`MegaHertz`] for semantically correct ways of creating lower or higher -/// frequencies. -/// -/// # Examples -/// -/// ## Create a 100 Khz frequency -/// -/// This example creates a 100 KHz frequency. This could be used to set an I2C data rate or PWM -/// frequency, etc. -/// -/// ```rust -/// use stm32f1xx_hal::time::Hertz; -/// -/// let freq = 100.khz(); -/// ``` -#[derive(Clone, Copy, PartialEq, PartialOrd, Debug)] -pub struct KiloHertz(pub u32); +/// KiloHertz +pub type KiloHertz = fugit::KilohertzU32; -/// Megahertz -/// -/// Create a frequency specified in megahertz. -/// -/// See also [`Hertz`] and [`KiloHertz`] for semantically correct ways of creating lower -/// frequencies. -/// -/// # Examples -/// -/// ## Create a an 8 MHz frequency -/// -/// This example creates an 8 MHz frequency that could be used to configure an SPI peripheral, etc. -/// -/// ```rust -/// use stm32f1xx_hal::time::Hertz; -/// -/// let freq = 8.mhz(); -/// ``` -#[derive(Clone, Copy, PartialEq, PartialOrd, Debug)] -pub struct MegaHertz(pub u32); +/// MegaHertz +pub type MegaHertz = fugit::MegahertzU32; -/// Time unit -#[derive(PartialEq, PartialOrd, Clone, Copy)] -pub struct MilliSeconds(pub u32); +// Period based -#[derive(PartialEq, PartialOrd, Clone, Copy)] -pub struct MicroSeconds(pub u32); +/// Seconds +pub type Seconds = fugit::SecsDurationU32; -/// Extension trait that adds convenience methods to the `u32` type -pub trait U32Ext { - /// Wrap in `Bps` - fn bps(self) -> Bps; +/// Milliseconds +pub type Milliseconds = fugit::MillisDurationU32; - /// Wrap in `Hertz` - fn hz(self) -> Hertz; +/// Microseconds +pub type Microseconds = fugit::MicrosDurationU32; - /// Wrap in `KiloHertz` - fn khz(self) -> KiloHertz; - - /// Wrap in `MegaHertz` - fn mhz(self) -> MegaHertz; - - /// Wrap in `MilliSeconds` - fn ms(self) -> MilliSeconds; - - /// Wrap in `MicroSeconds` - fn us(self) -> MicroSeconds; -} - -impl U32Ext for u32 { - fn bps(self) -> Bps { - Bps(self) - } - - fn hz(self) -> Hertz { - Hertz(self) - } - - fn khz(self) -> KiloHertz { - KiloHertz(self) - } - - fn mhz(self) -> MegaHertz { - MegaHertz(self) - } - - fn ms(self) -> MilliSeconds { - MilliSeconds(self) - } - - fn us(self) -> MicroSeconds { - MicroSeconds(self) - } -} - -impl From for Hertz { - fn from(val: KiloHertz) -> Self { - Self(val.0 * 1_000) - } -} - -impl From for Hertz { - fn from(val: MegaHertz) -> Self { - Self(val.0 * 1_000_000) - } -} - -impl From for KiloHertz { - fn from(val: MegaHertz) -> Self { - Self(val.0 * 1_000) - } -} - -impl From for Hertz { - fn from(val: MilliSeconds) -> Self { - Self(1_000 / val.0) - } -} - -impl From for Hertz { - fn from(val: MicroSeconds) -> Self { - Self(1_000_000 / val.0) - } -} +/// Nanoseconds +pub type Nanoseconds = fugit::NanosDurationU32; diff --git a/va416xx-hal/src/uart.rs b/va416xx-hal/src/uart.rs new file mode 100644 index 0000000..0beb7b0 --- /dev/null +++ b/va416xx-hal/src/uart.rs @@ -0,0 +1,967 @@ +use core::marker::PhantomData; +use core::ops::Deref; + +use embedded_hal_nb::serial::Read; +use fugit::RateExtU32; + +use crate::clock::{self}; +use crate::gpio::{AltFunc1, Pin, PD11, PD12, PE2, PE3, PF11, PF12, PF8, PF9, PG0, PG1}; +use crate::time::Hertz; +use crate::{ + gpio::{AltFunc2, AltFunc3, PA2, PA3, PB14, PB15, PC14, PC15, PC4, PC5}, + pac::{uart0 as uart_base, Uart0, Uart1, Uart2}, +}; + +//================================================================================================== +// Type-Level support +//================================================================================================== + +pub trait TxRxPins {} + +impl TxRxPins for (Pin, Pin) {} +impl TxRxPins for (Pin, Pin) {} +impl TxRxPins for (Pin, Pin) {} +impl TxRxPins for (Pin, Pin) {} + +impl TxRxPins for (Pin, Pin) {} +impl TxRxPins for (Pin, Pin) {} +impl TxRxPins for (Pin, Pin) {} + +impl TxRxPins for (Pin, Pin) {} +impl TxRxPins for (Pin, Pin) {} + +//================================================================================================== +// Regular Definitions +//================================================================================================== + +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + Overrun, + FramingError, + ParityError, + BreakCondition, + TransferPending, + BufferTooShort, +} + +#[derive(Debug, PartialEq, Eq, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +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(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Parity { + None, + Odd, + Even, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum StopBits { + One = 0, + Two = 1, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum WordSize { + Five = 0, + Six = 1, + Seven = 2, + Eight = 3, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Config { + pub baudrate: Hertz, + 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: Hertz) -> 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 { + Config { + baudrate: 115200_u32.Hz(), + parity: Parity::None, + stopbits: StopBits::One, + baud8: false, + wordsize: WordSize::Eight, + enable_tx: true, + enable_rx: true, + } + } +} + +impl From for Config { + fn from(value: Hertz) -> Self { + Config::default().baudrate(value) + } +} + +//================================================================================================== +// IRQ Definitions +//================================================================================================== + +struct IrqInfo { + rx_len: usize, + rx_idx: usize, + mode: IrqReceptionMode, +} + +pub enum IrqResultMask { + Complete = 0, + Overflow = 1, + FramingError = 2, + ParityError = 3, + Break = 4, + Timeout = 5, + Addr9 = 6, + /// Should not happen + Unknown = 7, +} + +/// This struct is used to return the default IRQ handler result to the user +#[derive(Debug, Default)] +pub struct IrqResult { + raw_res: u32, + pub bytes_read: usize, +} + +impl IrqResult { + pub const fn new() -> Self { + IrqResult { + raw_res: 0, + bytes_read: 0, + } + } +} + +impl IrqResult { + #[inline] + pub fn raw_result(&self) -> u32 { + self.raw_res + } + + #[inline] + pub(crate) fn clear_result(&mut self) { + self.raw_res = 0; + } + #[inline] + pub(crate) fn set_result(&mut self, flag: IrqResultMask) { + self.raw_res |= 1 << flag as u32; + } + + #[inline] + pub fn complete(&self) -> bool { + if ((self.raw_res >> IrqResultMask::Complete as u32) & 0x01) == 0x01 { + return true; + } + false + } + + #[inline] + pub fn error(&self) -> bool { + if self.overflow_error() || self.framing_error() || self.parity_error() { + return true; + } + false + } + + #[inline] + pub fn overflow_error(&self) -> bool { + if ((self.raw_res >> IrqResultMask::Overflow as u32) & 0x01) == 0x01 { + return true; + } + false + } + + #[inline] + pub fn framing_error(&self) -> bool { + if ((self.raw_res >> IrqResultMask::FramingError as u32) & 0x01) == 0x01 { + return true; + } + false + } + + #[inline] + pub fn parity_error(&self) -> bool { + if ((self.raw_res >> IrqResultMask::ParityError as u32) & 0x01) == 0x01 { + return true; + } + false + } + + #[inline] + pub fn timeout(&self) -> bool { + if ((self.raw_res >> IrqResultMask::Timeout as u32) & 0x01) == 0x01 { + return true; + } + false + } +} + +#[derive(Debug, PartialEq)] +enum IrqReceptionMode { + Idle, + Pending, +} + +//================================================================================================== +// UART implementation +//================================================================================================== + +/// Type erased variant of a UART. Can be created with the [`Uart::downgrade`] function. +pub struct UartBase { + uart: Uart, + tx: Tx, + rx: Rx, +} +/// Serial abstraction. Entry point to create a new UART +pub struct Uart { + inner: UartBase, + pins: Pins, +} + +/// UART using the IRQ capabilities of the peripheral. Can be created with the +/// [`Uart::into_uart_with_irq`] function. Currently, only the RX side for IRQ based reception +/// is implemented. +pub struct UartWithIrq { + base: UartWithIrqBase, + pins: Pins, +} + +/// Type-erased UART using the IRQ capabilities of the peripheral. Can be created with the +/// [`UartWithIrq::downgrade`] function. Currently, only the RX side for IRQ based reception +/// is implemented. +pub struct UartWithIrqBase { + pub inner: UartBase, + irq_info: IrqInfo, +} + +/// 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 { + const IDX: u8; + + fn ptr() -> *const uart_base::RegisterBlock; +} + +impl UartBase { + /// 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, + }; + // Calculations here are derived from chapter 10.4.4 (p.74) of the datasheet. + let x = sys_clk.raw() as f32 / (config.baudrate.raw() * baud_multiplier) as f32; + let integer_part = x as u32; + let frac = (64.0 * (x - integer_part as f32) + 0.5) 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 + } + + #[inline] + pub fn enable_rx(&mut self) { + self.uart.enable().modify(|_, w| w.rxenable().set_bit()); + } + + #[inline] + pub fn disable_rx(&mut self) { + self.uart.enable().modify(|_, w| w.rxenable().clear_bit()); + } + + #[inline] + pub fn enable_tx(&mut self) { + self.uart.enable().modify(|_, w| w.txenable().set_bit()); + } + + #[inline] + pub fn disable_tx(&mut self) { + self.uart.enable().modify(|_, w| w.txenable().clear_bit()); + } + + #[inline] + pub fn clear_rx_fifo(&mut self) { + self.uart.fifo_clr().write(|w| w.rxfifo().set_bit()); + } + + #[inline] + pub fn clear_tx_fifo(&mut self) { + self.uart.fifo_clr().write(|w| w.txfifo().set_bit()); + } + + pub fn listen(&self, event: Event) { + 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(), + }); + } + + pub fn unlisten(&self, event: Event) { + 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(), + }); + } + + pub fn release(self) -> Uart { + // 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 + } + + pub fn split(self) -> (Tx, Rx) { + (self.tx, self.rx) + } +} + +impl Uart +where + UartInstance: Instance, +{ + /// This function assumes that the peripheral clock was already enabled + /// in the SYSCONFIG register + fn init(mut self, config: Config, sys_clk: Hertz) -> Self { + self.inner = self.inner.init(config, sys_clk); + self + } + + /// If the IRQ capabilities of the peripheral are used, the UART needs to be converted + /// with this function + pub fn into_uart_with_irq(self) -> UartWithIrq { + let (inner, pins) = self.downgrade_internal(); + UartWithIrq { + pins, + base: UartWithIrqBase { + inner, + irq_info: IrqInfo { + rx_len: 0, + rx_idx: 0, + mode: IrqReceptionMode::Idle, + }, + }, + } + } + + delegate::delegate! { + to self.inner { + #[inline] + pub fn enable_rx(&mut self); + #[inline] + pub fn disable_rx(&mut self); + + #[inline] + pub fn enable_tx(&mut self); + #[inline] + pub fn disable_tx(&mut self); + + #[inline] + pub fn clear_rx_fifo(&mut self); + #[inline] + pub fn clear_tx_fifo(&mut self); + + #[inline] + pub fn listen(&self, event: Event); + #[inline] + pub fn unlisten(&self, event: Event); + #[inline] + pub fn split(self) -> (Tx, Rx); + } + } + + fn downgrade_internal(self) -> (UartBase, Pins) { + let base = UartBase { + uart: self.inner.uart, + tx: self.inner.tx, + rx: self.inner.rx, + }; + (base, self.pins) + } + + pub fn release(self) -> (UartInstance, Pins) { + (self.inner.release(), self.pins) + } +} + +#[derive(Default, Debug)] +pub struct IrqUartError { + overflow: bool, + framing: bool, + parity: bool, +} + +impl IrqUartError { + pub fn error(&self) -> bool { + self.overflow || self.framing || self.parity + } +} + +#[derive(Debug)] +pub enum IrqError { + BufferTooShort { found: usize, expected: usize }, + Uart(IrqUartError), +} + +impl UartWithIrqBase { + /// This initializes a non-blocking read transfer using the IRQ capabilities of the UART + /// peripheral. + /// + /// The only required information is the maximum length for variable sized reception + /// or the expected length for fixed length reception. If variable sized packets are expected, + /// the timeout functionality of the IRQ should be enabled as well. After calling this function, + /// the [`irq_handler`](Self::irq_handler) function should be called in the user interrupt + /// handler to read the received packets and reinitiate another transfer if desired. + pub fn read_fixed_len_using_irq( + &mut self, + max_len: usize, + enb_timeout_irq: bool, + ) -> Result<(), Error> { + if self.irq_info.mode != IrqReceptionMode::Idle { + return Err(Error::TransferPending); + } + self.irq_info.mode = IrqReceptionMode::Pending; + self.irq_info.rx_idx = 0; + self.irq_info.rx_len = max_len; + self.inner.enable_rx(); + self.inner.enable_tx(); + self.enable_rx_irq_sources(enb_timeout_irq); + Ok(()) + } + + #[inline] + fn enable_rx_irq_sources(&mut self, timeout: bool) { + self.inner.uart.irq_enb().modify(|_, w| { + if timeout { + w.irq_rx_to().set_bit(); + } + w.irq_rx_status().set_bit(); + w.irq_rx().set_bit() + }); + } + + #[inline] + fn disable_rx_irq_sources(&mut self) { + self.inner.uart.irq_enb().modify(|_, w| { + w.irq_rx_to().clear_bit(); + w.irq_rx_status().clear_bit(); + w.irq_rx().clear_bit() + }); + } + + #[inline] + pub fn enable_tx(&mut self) { + self.inner.enable_tx() + } + + #[inline] + pub fn disable_tx(&mut self) { + self.inner.disable_tx() + } + + pub fn cancel_transfer(&mut self) { + self.disable_rx_irq_sources(); + self.inner.clear_tx_fifo(); + self.irq_info.rx_idx = 0; + self.irq_info.rx_len = 0; + } + + /// Default IRQ handler which can be used to read the packets arriving on the UART peripheral. + /// + /// If passed buffer is equal to or larger than the specified maximum length, an + /// [`Error::BufferTooShort`] will be returned + pub fn irq_handler(&mut self, buf: &mut [u8]) -> Result { + if buf.len() < self.irq_info.rx_len { + return Err(IrqError::BufferTooShort { + found: buf.len(), + expected: self.irq_info.rx_len, + }); + } + let mut res = IrqResult::default(); + let mut possible_error = IrqUartError::default(); + + let rx_status = self.inner.uart.rxstatus().read(); + res.raw_res = rx_status.bits(); + let irq_end = self.inner.uart.irq_end().read(); + let enb_status = self.inner.uart.enable().read(); + let rx_enabled = enb_status.rxenable().bit_is_set(); + let _tx_enabled = enb_status.txenable().bit_is_set(); + let read_handler = |res: &mut IrqResult, + possible_error: &mut IrqUartError, + read_res: nb::Result| + -> Option { + match read_res { + Ok(byte) => Some(byte), + Err(nb::Error::WouldBlock) => None, + Err(nb::Error::Other(e)) => { + match e { + Error::Overrun => { + possible_error.overflow = true; + } + Error::FramingError => { + possible_error.framing = true; + } + Error::ParityError => { + possible_error.parity = true; + } + _ => { + res.set_result(IrqResultMask::Unknown); + } + } + None + } + } + }; + if irq_end.irq_rx().bit_is_set() { + // If this interrupt bit is set, the trigger level is available at the very least. + // Read everything as fast as possible + for _ in 0..core::cmp::min( + self.inner.uart.rxfifoirqtrg().read().bits() as usize, + self.irq_info.rx_len, + ) { + buf[self.irq_info.rx_idx] = (self.inner.uart.data().read().bits() & 0xff) as u8; + self.irq_info.rx_idx += 1; + } + + // While there is data in the FIFO, write it into the reception buffer + loop { + if self.irq_info.rx_idx == self.irq_info.rx_len { + self.irq_completion_handler(&mut res); + return Ok(res); + } + if let Some(byte) = read_handler(&mut res, &mut possible_error, self.inner.read()) { + buf[self.irq_info.rx_idx] = byte; + self.irq_info.rx_idx += 1; + } else { + break; + } + } + } + + // RX transfer not complete, check for RX errors + if (self.irq_info.rx_idx < self.irq_info.rx_len) && rx_enabled { + // Read status register again, might have changed since reading received data + let rx_status = self.inner.uart.rxstatus().read(); + res.raw_res = rx_status.bits(); + if rx_status.rxovr().bit_is_set() { + possible_error.overflow = true; + } + if rx_status.rxfrm().bit_is_set() { + possible_error.framing = true; + } + if rx_status.rxpar().bit_is_set() { + possible_error.parity = true; + } + if rx_status.rxto().bit_is_set() { + // A timeout has occured but there might be some leftover data in the FIFO, + // so read that data as well + while let Some(byte) = + read_handler(&mut res, &mut possible_error, self.inner.read()) + { + buf[self.irq_info.rx_idx] = byte; + self.irq_info.rx_idx += 1; + } + self.irq_completion_handler(&mut res); + res.set_result(IrqResultMask::Timeout); + return Ok(res); + } + + // If it is not a timeout, it's an error + if possible_error.error() { + self.disable_rx_irq_sources(); + return Err(IrqError::Uart(possible_error)); + } + } + + // Clear the interrupt status bits + self.inner + .uart + .irq_clr() + .write(|w| unsafe { w.bits(irq_end.bits()) }); + Ok(res) + } + + fn irq_completion_handler(&mut self, res: &mut IrqResult) { + self.disable_rx_irq_sources(); + self.inner.disable_rx(); + res.bytes_read = self.irq_info.rx_idx; + res.clear_result(); + res.set_result(IrqResultMask::Complete); + self.irq_info.mode = IrqReceptionMode::Idle; + self.irq_info.rx_idx = 0; + self.irq_info.rx_len = 0; + } + + pub fn release(self) -> Uart { + self.inner.release() + } +} + +impl UartWithIrq { + /// See [`UartWithIrqBase::read_fixed_len_using_irq`] doc + pub fn read_fixed_len_using_irq( + &mut self, + max_len: usize, + enb_timeout_irq: bool, + ) -> Result<(), Error> { + self.base.read_fixed_len_using_irq(max_len, enb_timeout_irq) + } + + pub fn cancel_transfer(&mut self) { + self.base.cancel_transfer() + } + + /// See [`UartWithIrqBase::irq_handler`] doc + pub fn irq_handler(&mut self, buf: &mut [u8]) -> Result { + self.base.irq_handler(buf) + } + + pub fn release(self) -> (Uart, Pins) { + (self.base.release(), self.pins) + } + + pub fn downgrade(self) -> (UartWithIrqBase, Pins) { + (self.base, self.pins) + } +} + +impl Instance for Uart0 { + const IDX: u8 = 0; + + fn ptr() -> *const uart_base::RegisterBlock { + Uart0::ptr() as *const _ + } +} + +impl Instance for Uart1 { + const IDX: u8 = 1; + + fn ptr() -> *const uart_base::RegisterBlock { + Uart1::ptr() as *const _ + } +} + +impl Instance for Uart2 { + const IDX: u8 = 2; + + fn ptr() -> *const uart_base::RegisterBlock { + Uart2::ptr() as *const _ + } +} + +macro_rules! uart_impl { + ($($Uartx:ident: ($uartx:ident, $clk_enb_enum:path),)+) => { + $( + + impl> Uart<$Uartx, Pins> { + pub fn $uartx( + uart: $Uartx, + pins: Pins, + config: impl Into, + syscfg: &mut va416xx::Sysconfig, + sys_clk: impl Into + ) -> Self + { + crate::clock::enable_peripheral_clock(syscfg, $clk_enb_enum); + Uart { + inner: UartBase { + uart, + tx: Tx::new(), + rx: Rx::new(), + }, + pins, + }.init(config.into(), sys_clk.into()) + } + } + + )+ + } +} + +uart_impl! { + Uart0: (uart0, clock::PeripheralClocks::Uart0), + Uart1: (uart1, clock::PeripheralClocks::Uart1), + Uart2: (uart2, clock::PeripheralClocks::Uart2), +} + +impl embedded_io::Error for Error { + fn kind(&self) -> embedded_io::ErrorKind { + embedded_io::ErrorKind::Other + } +} + +impl embedded_hal_nb::serial::Error for Error { + fn kind(&self) -> embedded_hal_nb::serial::ErrorKind { + embedded_hal_nb::serial::ErrorKind::Other + } +} + +impl embedded_io::ErrorType for Rx { + type Error = Error; +} + +impl embedded_hal_nb::serial::ErrorType for Rx { + type Error = Error; +} + +impl embedded_hal_nb::serial::Read for Rx { + 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 embedded_io::Read for Rx { + fn read(&mut self, buf: &mut [u8]) -> Result { + if buf.is_empty() { + return Ok(0); + } + + for byte in buf.iter_mut() { + let w = nb::block!(>::read(self))?; + *byte = w; + } + + Ok(buf.len()) + } +} + +impl embedded_io::ErrorType for Tx { + type Error = Error; +} + +impl embedded_hal_nb::serial::ErrorType for Tx { + type Error = Error; +} + +impl embedded_hal_nb::serial::Write for Tx { + 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 + (*Uart::ptr()).data().write(|w| w.bits(word as u32)); + } + } + Ok(()) + } + + fn flush(&mut self) -> nb::Result<(), Self::Error> { + // SAFETY: Only TX related registers are used. + let reader = unsafe { &(*Uart::ptr()) }.txstatus().read(); + if reader.wrbusy().bit_is_set() { + return Err(nb::Error::WouldBlock); + } + Ok(()) + } +} + +impl embedded_io::Write for Tx { + fn write(&mut self, buf: &[u8]) -> Result { + if buf.is_empty() { + return Ok(0); + } + + for byte in buf.iter() { + nb::block!(>::write( + self, *byte + ))?; + } + + Ok(buf.len()) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + nb::block!(>::flush(self)) + } +} + +impl embedded_io::ErrorType for UartBase { + type Error = Error; +} + +impl embedded_hal_nb::serial::ErrorType for UartBase { + type Error = Error; +} + +impl embedded_hal_nb::serial::Read for UartBase { + fn read(&mut self) -> nb::Result { + self.rx.read() + } +} + +impl embedded_hal_nb::serial::Write for UartBase { + 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() + } +} -- 2.43.0 From 46dcad1c10749073a62fee199c50d95a8431494c Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Thu, 13 Jun 2024 17:27:27 +0200 Subject: [PATCH 2/3] UART, WDT and CLKGEN --- Cargo.toml | 2 +- README.md | 32 +- examples/simple/Cargo.toml | 16 + .../simple/examples/blinky-hal.rs | 9 +- .../simple}/examples/blinky-pac.rs | 3 +- .../simple}/examples/rtt-log.rs | 8 +- .../simple}/examples/uart.rs | 35 +- examples/simple/examples/wdt.rs | 79 +++ examples/simple/src/lib.rs | 10 + examples/simple/src/main.rs | 13 + va416xx-hal/Cargo.toml | 16 +- va416xx-hal/src/clock.rs | 468 +++++++++++++++++- va416xx-hal/src/gpio/mod.rs | 2 +- va416xx-hal/src/gpio/pin.rs | 16 + va416xx-hal/src/gpio/reg.rs | 4 +- va416xx-hal/src/lib.rs | 6 +- va416xx-hal/src/prelude.rs | 5 +- va416xx-hal/src/typelevel.rs | 4 +- va416xx-hal/src/uart.rs | 63 ++- va416xx-hal/src/wdt.rs | 114 +++++ va416xx/Cargo.toml | 5 + va416xx/README.md | 1 - va416xx/gen-helper.sh | 2 +- va416xx/src/lib.rs | 6 + va416xx/src/porta.rs | 2 +- vorago-peb1/examples/blinky.rs | 62 --- 26 files changed, 855 insertions(+), 128 deletions(-) create mode 100644 examples/simple/Cargo.toml rename va416xx-hal/examples/blinky.rs => examples/simple/examples/blinky-hal.rs (72%) rename {va416xx-hal => examples/simple}/examples/blinky-pac.rs (88%) rename {va416xx-hal => examples/simple}/examples/rtt-log.rs (81%) rename {va416xx-hal => examples/simple}/examples/uart.rs (51%) create mode 100644 examples/simple/examples/wdt.rs create mode 100644 examples/simple/src/lib.rs create mode 100644 examples/simple/src/main.rs create mode 100644 va416xx-hal/src/wdt.rs delete mode 100644 vorago-peb1/examples/blinky.rs diff --git a/Cargo.toml b/Cargo.toml index 51d3076..17fa3df 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace] resolver = "2" -members = [ +members = [ "examples/simple", "va416xx", "va416xx-hal", "vorago-peb1" diff --git a/README.md b/README.md index 87252cb..6b5461b 100644 --- a/README.md +++ b/README.md @@ -30,25 +30,28 @@ to conveniently flash with `cargo run`. Use the following command to have a starting configuration for VS Code: ```sh -cp vscode .vscode -r +cp -rT vscode .vscode ``` You can then adapt the files in `.vscode` to your needs. -## Flashing, running and debugging with the command line +## Flashing, running and debugging the software -### Prerequisites +You can use CLI or VS Code for flashing, running and debugging. In any case, take +care of installing the pre-requisites first. + +### Pre-Requisites 1. [SEGGER J-Link tools](https://www.segger.com/downloads/jlink/) installed 2. [gdb-multiarch](https://packages.debian.org/sid/gdb-multiarch) or similar cross-architecture debugger installed. All commands here assume `gdb-multiarch`. -### Flashing and debugging the blinky application +### Using CLI You can build the blinky example application with the following command ```sh -cargo build -p va416xx-hal --example blinky +cargo build --example blinky ``` Start the GDB server first. The server needs to be started with a certain configuration and with @@ -75,9 +78,22 @@ runner configuration, for example with the following lines in your `.cargo/confi runner = "gdb-multiarch -q -x jlink/jlink.gdb" ``` -After that, you can simply use `cargo run -p va416xx-hal --example blinky` to flash the blinky +After that, you can simply use `cargo run --example blinky` to flash the blinky example. -## Flashing, running and debugging with VS Code +### Using VS Code -TODO +Assuming a working debug connection to your VA108xx board, you can debug using VS Code with +the [`Cortex-Debug` plugin](https://marketplace.visualstudio.com/items?itemName=marus25.cortex-debug). + +Some sample configuration files for VS code were provided and can be used by running +`cp -rT vscode .vscode` like specified above. After that, you can use `Run and Debug` +to automatically rebuild and flash your application. + +If you would like to use a custom GDB application, you can specify the gdb binary in the following +configuration variables in your `settings.json`: + +- `"cortex-debug.gdbPath"` +- `"cortex-debug.gdbPath.linux"` +- `"cortex-debug.gdbPath.windows"` +- `"cortex-debug.gdbPath.osx"` diff --git a/examples/simple/Cargo.toml b/examples/simple/Cargo.toml new file mode 100644 index 0000000..bbad5ea --- /dev/null +++ b/examples/simple/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "simple_examples" +version = "0.1.0" +edition = "2021" + +[dependencies] +cortex-m-rt = "0.7" +va416xx-hal = { path = "../../va416xx-hal" } +panic-rtt-target = { version = "0.1.3" } +rtt-target = { version = "0.5" } +cortex-m = { version = "0.7", features = ["critical-section-single-core"]} +embedded-hal = "1" +embedded-hal-nb = "1" +nb = "1" +embedded-io = "0.6" +panic-halt = "0.2" diff --git a/va416xx-hal/examples/blinky.rs b/examples/simple/examples/blinky-hal.rs similarity index 72% rename from va416xx-hal/examples/blinky.rs rename to examples/simple/examples/blinky-hal.rs index 5ed2c39..13eab01 100644 --- a/va416xx-hal/examples/blinky.rs +++ b/examples/simple/examples/blinky-hal.rs @@ -4,13 +4,16 @@ use cortex_m_rt::entry; use embedded_hal::digital::StatefulOutputPin; -use panic_halt as _; +use panic_rtt_target as _; +use rtt_target::{rprintln, rtt_init_print}; use va416xx_hal::{gpio::PinsG, pac}; #[entry] fn main() -> ! { - // SAFETY: Peripherals are only stolen once here. - let mut dp = unsafe { pac::Peripherals::steal() }; + rtt_init_print!(); + rprintln!("VA416xx HAL blinky example"); + + let mut dp = pac::Peripherals::take().unwrap(); let portg = PinsG::new(&mut dp.sysconfig, Some(dp.ioconfig), dp.portg); let mut led = portg.pg5.into_readable_push_pull_output(); //let mut delay = CountDownTimer::new(&mut dp.SYSCONFIG, 50.mhz(), dp.TIM0); diff --git a/va416xx-hal/examples/blinky-pac.rs b/examples/simple/examples/blinky-pac.rs similarity index 88% rename from va416xx-hal/examples/blinky-pac.rs rename to examples/simple/examples/blinky-pac.rs index 8db2fa1..1f99f28 100644 --- a/va416xx-hal/examples/blinky-pac.rs +++ b/examples/simple/examples/blinky-pac.rs @@ -11,8 +11,7 @@ const LED_PG5: u32 = 1 << 5; #[entry] fn main() -> ! { - // SAFETY: Peripherals are only stolen once here. - let dp = unsafe { pac::Peripherals::steal() }; + let dp = pac::Peripherals::take().unwrap(); // Enable all peripheral clocks dp.sysconfig .peripheral_clk_enable() diff --git a/va416xx-hal/examples/rtt-log.rs b/examples/simple/examples/rtt-log.rs similarity index 81% rename from va416xx-hal/examples/rtt-log.rs rename to examples/simple/examples/rtt-log.rs index a62c369..8eb2475 100644 --- a/va416xx-hal/examples/rtt-log.rs +++ b/examples/simple/examples/rtt-log.rs @@ -1,11 +1,10 @@ -//! Code to test RTT logger functionality. +// Code to test RTT logger functionality. #![no_main] #![no_std] use cortex_m_rt::entry; use panic_rtt_target as _; use rtt_target::{rprintln, rtt_init_print}; -use va416xx as _; use va416xx_hal::pac; // Mask for the LED @@ -13,8 +12,7 @@ const LED_PG5: u32 = 1 << 5; #[entry] fn main() -> ! { - // SAFETY: Peripherals are only stolen once here. - let dp = unsafe { pac::Peripherals::steal() }; + let dp = pac::Peripherals::take().unwrap(); // Enable all peripheral clocks dp.sysconfig .peripheral_clk_enable() @@ -25,7 +23,7 @@ fn main() -> ! { .modify(|_, w| unsafe { w.bits(LED_PG5) }); rtt_init_print!(); - rprintln!("-- RTT Demo --"); + rprintln!("VA416xx RTT Demo"); let mut counter = 0; loop { rprintln!("{}: Hello, world!", counter); diff --git a/va416xx-hal/examples/uart.rs b/examples/simple/examples/uart.rs similarity index 51% rename from va416xx-hal/examples/uart.rs rename to examples/simple/examples/uart.rs index 672e3b4..51242b1 100644 --- a/va416xx-hal/examples/uart.rs +++ b/examples/simple/examples/uart.rs @@ -1,13 +1,16 @@ -//! UART example application. Sends a test string over a UART and then enters -//! echo mode +// UART example application. Sends a test string over a UART and then enters +// echo mode #![no_main] #![no_std] use cortex_m_rt::entry; -use embedded_io::{Read, Write}; +use embedded_hal_nb::serial::Read; +use embedded_io::Write; use panic_rtt_target as _; use rtt_target::{rprintln, rtt_init_print}; -use va416xx_hal::time::{Hertz, MegaHertz}; +use simple_examples::peb1; +use va416xx_hal::clock::ClkgenExt; +use va416xx_hal::time::Hertz; use va416xx_hal::{gpio::PinsG, pac, uart}; #[entry] @@ -15,28 +18,34 @@ fn main() -> ! { rtt_init_print!(); rprintln!("-- VA416xx UART example application--"); - // SAFETY: Peripherals are only stolen once here. - let mut dp = unsafe { pac::Peripherals::steal() }; + let mut dp = pac::Peripherals::take().unwrap(); + + // Use the external clock connected to XTAL_N. + let clocks = dp + .clkgen + .constrain() + .xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ) + .freeze(&mut dp.sysconfig) + .unwrap(); let gpiob = PinsG::new(&mut dp.sysconfig, Some(dp.ioconfig), dp.portg); let tx = gpiob.pg0.into_funsel_1(); let rx = gpiob.pg1.into_funsel_1(); - let uartb = uart::Uart::uart0( + let uart0 = uart::Uart::uart0( dp.uart0, (tx, rx), Hertz::from_raw(115200), &mut dp.sysconfig, - Hertz::from_raw(MegaHertz::from_raw(20).to_Hz()), + &clocks, ); - let (mut tx, mut rx) = uartb.split(); - let mut recv_buf: [u8; 32] = [0; 32]; - writeln!(tx, "Hello World").unwrap(); + let (mut tx, mut rx) = uart0.split(); + writeln!(tx, "Hello World\n\r").unwrap(); loop { // Echo what is received on the serial link. - match rx.read(&mut recv_buf) { + match nb::block!(rx.read()) { Ok(recvd) => { - if let Err(e) = tx.write(&recv_buf[0..recvd]) { + if let Err(e) = embedded_hal_nb::serial::Write::write(&mut tx, recvd) { rprintln!("UART TX error: {:?}", e); } } diff --git a/examples/simple/examples/wdt.rs b/examples/simple/examples/wdt.rs new file mode 100644 index 0000000..5e989fc --- /dev/null +++ b/examples/simple/examples/wdt.rs @@ -0,0 +1,79 @@ +// Code to test the watchdog timer. +#![no_main] +#![no_std] + +use core::cell::Cell; +use cortex_m::interrupt::Mutex; +use cortex_m_rt::entry; +use panic_rtt_target as _; +use rtt_target::{rprintln, rtt_init_print}; +use simple_examples::peb1; +use va416xx_hal::pac::{self, interrupt}; +use va416xx_hal::prelude::*; +use va416xx_hal::wdt::WdtController; + +static WDT_INTRPT_COUNT: Mutex> = Mutex::new(Cell::new(0)); + +#[derive(Debug, PartialEq, Eq)] +#[allow(dead_code)] +enum TestMode { + // Watchdog is fed by main loop, which runs with high period. + FedByMain, + // Watchdog is fed by watchdog IRQ. + FedByIrq, + AllowReset, +} +const TEST_MODE: TestMode = TestMode::FedByMain; +const WDT_ROLLOVER_MS: u32 = 100; + +#[entry] +fn main() -> ! { + rtt_init_print!(); + rprintln!("-- VA416xx WDT example application--"); + let cp = cortex_m::Peripherals::take().unwrap(); + let mut dp = pac::Peripherals::take().unwrap(); + + // Use the external clock connected to XTAL_N. + let clocks = dp + .clkgen + .constrain() + .xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ) + .freeze(&mut dp.sysconfig) + .unwrap(); + let mut delay_sysclk = cortex_m::delay::Delay::new(cp.SYST, clocks.apb0().raw()); + + let mut last_interrupt_counter = 0; + let mut wdt_ctrl = + WdtController::start(&mut dp.sysconfig, dp.watch_dog, &clocks, WDT_ROLLOVER_MS); + wdt_ctrl.enable_reset(); + loop { + if TEST_MODE != TestMode::AllowReset { + wdt_ctrl.feed(); + } + let interrupt_counter = cortex_m::interrupt::free(|cs| WDT_INTRPT_COUNT.borrow(cs).get()); + if interrupt_counter > last_interrupt_counter { + rprintln!("interrupt counter has increased to {}", interrupt_counter); + last_interrupt_counter = interrupt_counter; + } + match TEST_MODE { + TestMode::FedByMain => delay_sysclk.delay_ms(WDT_ROLLOVER_MS / 5), + TestMode::FedByIrq => delay_sysclk.delay_ms(WDT_ROLLOVER_MS), + _ => (), + } + } +} + +#[interrupt] +#[allow(non_snake_case)] +fn WATCHDOG() { + cortex_m::interrupt::free(|cs| { + WDT_INTRPT_COUNT + .borrow(cs) + .set(WDT_INTRPT_COUNT.borrow(cs).get() + 1); + }); + let wdt = unsafe { pac::WatchDog::steal() }; + // Clear interrupt. + if TEST_MODE != TestMode::AllowReset { + wdt.wdogintclr().write(|w| unsafe { w.bits(1) }); + } +} diff --git a/examples/simple/src/lib.rs b/examples/simple/src/lib.rs new file mode 100644 index 0000000..cea324a --- /dev/null +++ b/examples/simple/src/lib.rs @@ -0,0 +1,10 @@ +#![no_std] + +pub mod peb1 { + use va416xx_hal::time::Hertz; + + // The clock on the PEB1 board has a 20 MHz clock which is increased to 40 MHz with a configurable + // PLL by default. + pub const EXTCLK_FREQ: Hertz = Hertz::from_raw(40_000_000); + pub const XTAL_FREQ: Hertz = Hertz::from_raw(10_000_000); +} diff --git a/examples/simple/src/main.rs b/examples/simple/src/main.rs new file mode 100644 index 0000000..ddb240e --- /dev/null +++ b/examples/simple/src/main.rs @@ -0,0 +1,13 @@ +//! Dummy app which does not do anything. +#![no_main] +#![no_std] + +use cortex_m_rt::entry; +use panic_rtt_target as _; + +#[entry] +fn main() -> ! { + loop { + cortex_m::asm::nop(); + } +} diff --git a/va416xx-hal/Cargo.toml b/va416xx-hal/Cargo.toml index e192432..8f0ffb2 100644 --- a/va416xx-hal/Cargo.toml +++ b/va416xx-hal/Cargo.toml @@ -11,28 +11,24 @@ keywords = ["no-std", "hal", "cortex-m", "vorago", "va416xx"] categories = ["embedded", "no-std", "hardware-support"] [dependencies] -cortex-m = "0.7" -cortex-m-rt = "0.7" +cortex-m = { version = "0.7", features = ["critical-section-single-core"] } nb = "1" paste = "1" embedded-hal-nb = "1" embedded-hal = "1" embedded-io = "0.6" -typenum = "1.12.0" +typenum = "1" defmt = { version = "0.3", optional = true } fugit = "0.3" delegate = "0.12" [dependencies.va416xx] path = "../va416xx" +default-features = false version = "0.1.0" +features = ["critical-section"] [features] +default = ["rt", "revb"] rt = ["va416xx/rt"] -defmt = ["dep:defmt"] - -[dev-dependencies] -panic-rtt-target = { version = "0.1.3" } -rtt-target = { version = "0.5" } -panic-halt = "0.2" -cortex-m = { version = "0.7.6", features = ["critical-section-single-core"]} +revb = [] diff --git a/va416xx-hal/src/clock.rs b/va416xx-hal/src/clock.rs index a08d1ab..c3864d2 100644 --- a/va416xx-hal/src/clock.rs +++ b/va416xx-hal/src/clock.rs @@ -1,5 +1,11 @@ //! This also includes functionality to enable the peripheral clocks -use va416xx::Sysconfig; +//use va416xx::{, Sysconfig}; +use crate::pac; + +use crate::time::Hertz; + +pub const HBO_FREQ: Hertz = Hertz::from_raw(20_000_000); +pub const XTAL_OSC_TSTART_MS: u32 = 15; #[derive(Copy, Clone, PartialEq)] pub enum PeripheralSelect { @@ -50,14 +56,470 @@ pub enum FilterClkSel { Clk7 = 7, } -pub fn enable_peripheral_clock(syscfg: &mut Sysconfig, clock: PeripheralSelect) { +#[inline(always)] +pub fn enable_peripheral_clock(syscfg: &mut pac::Sysconfig, clock: PeripheralSelect) { syscfg .peripheral_clk_enable() .modify(|r, w| unsafe { w.bits(r.bits() | (1 << clock as u8)) }); } -pub fn disable_peripheral_clock(syscfg: &mut Sysconfig, clock: PeripheralSelect) { +#[inline(always)] +pub fn disable_peripheral_clock(syscfg: &mut pac::Sysconfig, clock: PeripheralSelect) { syscfg .peripheral_clk_enable() .modify(|r, w| unsafe { w.bits(r.bits() & !(1 << clock as u8)) }); } + +#[inline(always)] +pub fn assert_periph_reset(syscfg: &mut pac::Sysconfig, periph: PeripheralSelect) { + syscfg + .peripheral_reset() + .modify(|r, w| unsafe { w.bits(r.bits() & !(1 << periph as u8)) }); +} + +#[inline(always)] +pub fn deassert_periph_reset(syscfg: &mut pac::Sysconfig, periph: PeripheralSelect) { + syscfg + .peripheral_reset() + .modify(|r, w| unsafe { w.bits(r.bits() | (1 << periph as u8)) }); +} + +pub trait SyscfgExt { + fn enable_peripheral_clock(&mut self, clock: PeripheralClocks); + + fn disable_peripheral_clock(&mut self, clock: PeripheralClocks); + + fn assert_periph_reset(&mut self, clock: PeripheralSelect); + + fn deassert_periph_reset(&mut self, clock: PeripheralSelect); +} + +impl SyscfgExt for pac::Sysconfig { + #[inline(always)] + fn enable_peripheral_clock(&mut self, clock: PeripheralClocks) { + enable_peripheral_clock(self, clock) + } + + #[inline(always)] + fn disable_peripheral_clock(&mut self, clock: PeripheralClocks) { + disable_peripheral_clock(self, clock) + } + + #[inline(always)] + fn assert_periph_reset(&mut self, clock: PeripheralSelect) { + assert_periph_reset(self, clock) + } + + #[inline(always)] + fn deassert_periph_reset(&mut self, clock: PeripheralSelect) { + deassert_periph_reset(self, clock) + } +} + +/// Refer to chapter 8 (p.57) of the programmers guide for detailed information. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum ClkselSys { + // Internal Heart-Beat Osciallator. Not tightly controlled (+/-20 %). Not recommended as the regular clock! + Hbo = 0b00, + // External clock signal on XTAL_N line, 1-100 MHz + XtalN = 0b01, + // Internal Phase-Locked Loop. + Pll = 0b10, + // Crystal oscillator amplified, 4-10 MHz. + XtalOsc = 0b11, +} + +/// This selects the input clock to the the CLKGEN peripheral in addition to the HBO clock. +/// +/// This can either be a clock connected directly on the XTAL_N line or a chrystal on the XTAL_P +/// line which goes through an oscillator amplifier. +/// +/// Refer to chapter 8 (p.57) of the programmers guide for detailed information. +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum RefClkSel { + #[default] + None = 0b00, + XtalOsc = 0b01, + XtalN = 0b10, +} + +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum ClkDivSel { + #[default] + Div1 = 0b00, + Div2 = 0b01, + Div4 = 0b10, + Div8 = 0b11, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum AdcClkDivSel { + Div8 = 0b00, + Div4 = 0b01, + Div2 = 0b10, + Div1 = 0b11, +} + +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct PllCfg { + /// Reference clock divider. + pub clkr: u8, + /// Clock divider on feedback path + pub clkf: u8, + // Output clock divider. + pub clkod: u8, + /// Bandwidth adjustment + pub bwadj: u8, +} + +pub fn clk_after_div(clk: Hertz, div_sel: ClkDivSel) -> Hertz { + match div_sel { + ClkDivSel::Div1 => clk, + ClkDivSel::Div2 => clk / 2, + ClkDivSel::Div4 => clk / 4, + ClkDivSel::Div8 => clk / 8, + } +} + +/// Wait for 500 reference clock cycles like specified in the datasheet. +pub fn pll_setup_delay() { + for _ in 0..500 { + cortex_m::asm::nop() + } +} + +pub trait ClkgenExt { + fn constrain(self) -> ClkgenCfgr; +} + +impl ClkgenExt for pac::Clkgen { + fn constrain(self) -> ClkgenCfgr { + ClkgenCfgr { + source_clk: None, + ref_clk_sel: RefClkSel::None, + clksel_sys: ClkselSys::Hbo, + clk_div_sel: ClkDivSel::Div1, + clk_lost_detection: false, + pll_lock_lost_detection: false, + pll_cfg: None, + clkgen: self, + } + } +} + +pub struct ClkgenCfgr { + ref_clk_sel: RefClkSel, + clksel_sys: ClkselSys, + clk_div_sel: ClkDivSel, + /// The source clock frequency which is either an external clock connected to XTAL_N, or a + /// crystal connected to the XTAL_OSC input. + source_clk: Option, + pll_cfg: Option, + clk_lost_detection: bool, + /// Feature only works on revision B of the board. + #[cfg(feature = "revb")] + pll_lock_lost_detection: bool, + clkgen: pac::Clkgen, +} + +#[derive(Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct ClkSourceFreqNotSet; + +#[derive(Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum ClkCfgError { + ClkSourceFreqNotSet, + PllConfigNotSet, + PllInitError, + InconsistentCfg, +} + +/// Delays a given amount of milliseconds. +/// +/// Taken from the HAL implementation. This implementation is probably not precise and it +/// also blocks! +pub fn hbo_clock_delay_ms(ms: u32) { + let wdt = unsafe { pac::WatchDog::steal() }; + for _ in 0..ms { + for _ in 0..10_000 { + cortex_m::asm::nop(); + } + wdt.wdogintclr().write(|w| unsafe { w.bits(1) }); + } +} + +impl ClkgenCfgr { + #[inline] + pub fn source_clk(mut self, src_clk: Hertz) -> Self { + self.source_clk = Some(src_clk); + self + } + + /// This function can be used to utilize the XTAL_N clock input directly without the + /// oscillator. + /// + /// It sets the internal configuration to [ClkselSys::XtalN] and [RefClkSel::XtalN]. + #[inline] + pub fn xtal_n_clk(mut self) -> Self { + self.clksel_sys = ClkselSys::XtalN; + self.ref_clk_sel = RefClkSel::XtalN; + self + } + + #[inline] + pub fn xtal_n_clk_with_src_freq(mut self, src_clk: Hertz) -> Self { + self = self.xtal_n_clk(); + self.source_clk(src_clk) + } + + #[inline] + pub fn clksel_sys(mut self, clksel_sys: ClkselSys) -> Self { + self.clksel_sys = clksel_sys; + self + } + + #[inline] + pub fn ref_clk_sel(mut self, ref_clk_sel: RefClkSel) -> Self { + self.ref_clk_sel = ref_clk_sel; + self + } + + /// Configures all clocks and return a clock configuration structure containing the final + /// frozen clock. + /// + /// Internal implementation details: This implementation is based on the HAL implementation + /// which performs a lot of delays. I do not know if all of those are necessary, but + /// I am going to be conservative here and assume that the vendor has tested though and + /// might have had a reason for those, so I am going to keep them. Chances are, this + /// process only has to be performed once, and it does not matter if it takes a few + /// microseconds or milliseconds longer. + pub fn freeze(self, syscfg: &mut pac::Sysconfig) -> Result { + // Sanitize configuration. + if self.source_clk.is_none() { + return Err(ClkCfgError::ClkSourceFreqNotSet); + } + if self.clksel_sys == ClkselSys::XtalOsc && self.ref_clk_sel != RefClkSel::XtalOsc { + return Err(ClkCfgError::InconsistentCfg); + } + if self.clksel_sys == ClkselSys::XtalN && self.ref_clk_sel != RefClkSel::XtalN { + return Err(ClkCfgError::InconsistentCfg); + } + if self.clksel_sys == ClkselSys::Pll && self.pll_cfg.is_none() { + return Err(ClkCfgError::PllConfigNotSet); + } + + syscfg.enable_peripheral_clock(PeripheralSelect::Clkgen); + let mut final_sysclk = self.source_clk.unwrap(); + // The HAL forces back the HBO clock here with a delay.. Even though this is + // not stricly necessary when coming from a fresh start, it could be still become relevant + // later if the clock lost detection mechanism require a re-configuration of the clocks. + // Therefore, we do it here as well. + self.clkgen + .ctrl0() + .modify(|_, w| unsafe { w.clksel_sys().bits(ClkselSys::Hbo as u8) }); + pll_setup_delay(); + self.clkgen + .ctrl0() + .modify(|_, w| unsafe { w.clk_div_sel().bits(ClkDivSel::Div1 as u8) }); + + // Set up oscillator and PLL input clock. + self.clkgen + .ctrl0() + .modify(|_, w| unsafe { w.ref_clk_sel().bits(self.ref_clk_sel as u8) }); + self.clkgen.ctrl1().modify(|_, w| { + w.xtal_en().clear_bit(); + w.xtal_n_en().clear_bit(); + w + }); + match self.ref_clk_sel { + RefClkSel::None => pll_setup_delay(), + RefClkSel::XtalOsc => { + self.clkgen.ctrl1().modify(|_, w| w.xtal_en().set_bit()); + hbo_clock_delay_ms(XTAL_OSC_TSTART_MS); + } + RefClkSel::XtalN => { + self.clkgen.ctrl1().modify(|_, w| w.xtal_n_en().set_bit()); + pll_setup_delay() + } + } + + // Set up PLL configuration. + match self.pll_cfg { + Some(cfg) => { + self.clkgen.ctrl0().modify(|_, w| w.pll_pwdn().clear_bit()); + // Done in C HAL. I guess this gives the PLL some time to power down properly. + cortex_m::asm::nop(); + cortex_m::asm::nop(); + self.clkgen.ctrl0().modify(|_, w| { + unsafe { + w.pll_clkf().bits(cfg.clkf); + } + unsafe { + w.pll_clkr().bits(cfg.clkr); + } + unsafe { + w.pll_clkod().bits(cfg.clkod); + } + unsafe { + w.pll_bwadj().bits(cfg.bwadj); + } + w.pll_test().clear_bit(); + w.pll_bypass().clear_bit(); + w.pll_intfb().set_bit() + }); + // Taken from SystemCoreClockUpdate implementation from Vorago. + final_sysclk /= cfg.clkr as u32 + 1; + final_sysclk *= cfg.clkf as u32 + 1; + final_sysclk /= cfg.clkod as u32 + 1; + + // Reset PLL. + self.clkgen.ctrl0().modify(|_, w| w.pll_reset().set_bit()); + // The HAL does this, the datasheet specifies a delay of 5 us. I guess it does not + // really matter because the PLL lock detect is used later.. + pll_setup_delay(); + self.clkgen.ctrl0().modify(|_, w| w.pll_reset().clear_bit()); + pll_setup_delay(); + + // check for lock + let stat = self.clkgen.stat().read(); + if stat.fbslip().bit() || stat.rfslip().bit() { + pll_setup_delay(); + if stat.fbslip().bit() || stat.rfslip().bit() { + // This is what the HAL does. We could continue, but then we would at least + // have to somehow report a partial error.. Chances are, the user does not + // want to continue with a broken PLL clock. + return Err(ClkCfgError::PllInitError); + } + } + } + None => self.clkgen.ctrl0().modify(|_, w| w.pll_pwdn().set_bit()), + } + + if self.clk_lost_detection { + rearm_sysclk_lost_with_periph(&self.clkgen) + } + #[cfg(feature = "revb")] + if self.pll_lock_lost_detection { + rearm_pll_lock_lost_with_periph(&self.clkgen) + } + + self.clkgen + .ctrl0() + .modify(|_, w| unsafe { w.clk_div_sel().bits(self.clk_div_sel as u8) }); + final_sysclk = clk_after_div(final_sysclk, self.clk_div_sel); + + // The HAL does this. I don't know why.. + pll_setup_delay(); + + self.clkgen + .ctrl0() + .modify(|_, w| unsafe { w.clksel_sys().bits(self.clksel_sys as u8) }); + + // I will just do the ADC stuff like Vorago does it. + // ADC clock (must be 2-12.5 MHz) + // NOTE: Not using divide by 1 or /2 ratio in REVA silicon because of triggering issue + // For this reason, keep SYSCLK above 8MHz to have the ADC /4 ratio in range) + if final_sysclk.raw() <= 50_000_000 { + self.clkgen + .ctrl1() + .modify(|_, w| unsafe { w.adc_clk_div_sel().bits(AdcClkDivSel::Div4 as u8) }); + } else { + self.clkgen + .ctrl1() + .modify(|_, w| unsafe { w.adc_clk_div_sel().bits(AdcClkDivSel::Div8 as u8) }); + } + + Ok(Clocks { + sysclk: final_sysclk, + apb1: final_sysclk / 2, + apb2: final_sysclk / 4, + }) + } +} + +/// Frozen clock frequencies +/// +/// The existence of this value indicates that the clock configuration can no longer be changed +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Clocks { + sysclk: Hertz, + apb1: Hertz, + apb2: Hertz, +} + +impl Clocks { + /// Returns the frequency of the HBO clock + pub fn hbo(&self) -> Hertz { + HBO_FREQ + } + + /// Returns the frequency of the APB0 which is equal to the system clock. + pub fn apb0(&self) -> Hertz { + self.sysclk() + } + + /// Returns system clock divied by 2. + pub fn apb1(&self) -> Hertz { + self.apb1 + } + + /// Returns system clock divied by 4. + pub fn apb2(&self) -> Hertz { + self.apb2 + } + + /// Returns the system (core) frequency + pub fn sysclk(&self) -> Hertz { + self.sysclk + } +} + +pub fn rearm_sysclk_lost() { + rearm_sysclk_lost_with_periph(&unsafe { pac::Clkgen::steal() }) +} + +fn rearm_sysclk_lost_with_periph(clkgen: &pac::Clkgen) { + clkgen + .ctrl0() + .modify(|_, w| w.sys_clk_lost_det_en().set_bit()); + clkgen + .ctrl1() + .write(|w| w.sys_clk_lost_det_rearm().set_bit()); + clkgen + .ctrl1() + .write(|w| w.sys_clk_lost_det_rearm().clear_bit()); +} + +#[cfg(feature = "revb")] +pub fn rearm_pll_lock_lost() { + rearm_pll_lock_lost_with_periph(&unsafe { pac::Clkgen::steal() }) +} + +fn rearm_pll_lock_lost_with_periph(clkgen: &pac::Clkgen) { + clkgen + .ctrl1() + .modify(|_, w| w.pll_lost_lock_det_en().set_bit()); + clkgen.ctrl1().write(|w| w.pll_lck_det_rearm().set_bit()); + clkgen.ctrl1().write(|w| w.pll_lck_det_rearm().clear_bit()); +} + +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + fn test_basic_div() { + assert_eq!( + clk_after_div(Hertz::from_raw(10_000_000), super::ClkDivSel::Div2), + Hertz::from_raw(5_000_000) + ); + } +} diff --git a/va416xx-hal/src/gpio/mod.rs b/va416xx-hal/src/gpio/mod.rs index ce4d202..bfe6aa7 100644 --- a/va416xx-hal/src/gpio/mod.rs +++ b/va416xx-hal/src/gpio/mod.rs @@ -24,7 +24,7 @@ #[derive(Debug, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct IsMaskedError(pub(crate) ()); +pub struct IsMaskedError; macro_rules! common_reg_if_functions { () => { diff --git a/va416xx-hal/src/gpio/pin.rs b/va416xx-hal/src/gpio/pin.rs index ddd73b6..7dc88b8 100644 --- a/va416xx-hal/src/gpio/pin.rs +++ b/va416xx-hal/src/gpio/pin.rs @@ -554,6 +554,22 @@ where } } +impl InputPin for Pin> +where + I: PinId, + C: OutputConfig + ReadableOutput, +{ + #[inline] + fn is_high(&mut self) -> Result { + Ok(self._is_high()) + } + + #[inline] + fn is_low(&mut self) -> Result { + Ok(self._is_low()) + } +} + //================================================================================================== // Registers //================================================================================================== diff --git a/va416xx-hal/src/gpio/reg.rs b/va416xx-hal/src/gpio/reg.rs index 2067fb9..d8a9022 100644 --- a/va416xx-hal/src/gpio/reg.rs +++ b/va416xx-hal/src/gpio/reg.rs @@ -209,7 +209,7 @@ pub(super) unsafe trait RegisterInterface { #[inline(always)] fn read_pin_masked(&self) -> Result { if !self.datamask() { - Err(IsMaskedError(())) + Err(IsMaskedError) } else { Ok(((self.port_reg().datain().read().bits() >> self.id().num) & 0x01) == 1) } @@ -234,7 +234,7 @@ pub(super) unsafe trait RegisterInterface { #[inline] fn write_pin_masked(&mut self, bit: bool) -> Result<(), IsMaskedError> { if !self.datamask() { - Err(IsMaskedError(())) + Err(IsMaskedError) } else { // Safety: SETOUT is a "mask" register, and we only write the bit for // this pin ID diff --git a/va416xx-hal/src/lib.rs b/va416xx-hal/src/lib.rs index 0f54b47..4c438c1 100644 --- a/va416xx-hal/src/lib.rs +++ b/va416xx-hal/src/lib.rs @@ -1,7 +1,10 @@ #![no_std] +#[cfg(test)] +extern crate std; -pub use va416xx; +pub use va416xx as device; pub use va416xx as pac; + pub mod prelude; pub mod clock; @@ -9,6 +12,7 @@ pub mod gpio; pub mod time; pub mod typelevel; pub mod uart; +pub mod wdt; #[derive(Debug, Eq, Copy, Clone, PartialEq)] pub enum FunSel { diff --git a/va416xx-hal/src/prelude.rs b/va416xx-hal/src/prelude.rs index 8b13789..e67a9ed 100644 --- a/va416xx-hal/src/prelude.rs +++ b/va416xx-hal/src/prelude.rs @@ -1 +1,4 @@ - +//! Prelude +pub use crate::clock::{ClkgenExt, SyscfgExt}; +pub use fugit::ExtU32 as _; +pub use fugit::RateExtU32 as _; diff --git a/va416xx-hal/src/typelevel.rs b/va416xx-hal/src/typelevel.rs index 991593b..7803c20 100644 --- a/va416xx-hal/src/typelevel.rs +++ b/va416xx-hal/src/typelevel.rs @@ -60,14 +60,14 @@ impl Sealed for NoneT {} /// specific type. Stated differently, it guarantees that `T == Specific` in /// the following example. /// -/// ``` +/// ```ignore /// where T: Is /// ``` /// /// Moreover, the super traits guarantee that any instance of or reference to a /// type `T` can be converted into the `Specific` type. /// -/// ``` +/// ```ignore /// fn example(mut any: T) /// where /// T: Is, diff --git a/va416xx-hal/src/uart.rs b/va416xx-hal/src/uart.rs index 0beb7b0..b798e23 100644 --- a/va416xx-hal/src/uart.rs +++ b/va416xx-hal/src/uart.rs @@ -4,7 +4,7 @@ use core::ops::Deref; use embedded_hal_nb::serial::Read; use fugit::RateExtU32; -use crate::clock::{self}; +use crate::clock::{self, Clocks}; use crate::gpio::{AltFunc1, Pin, PD11, PD12, PE2, PE3, PF11, PF12, PF8, PF9, PG0, PG1}; use crate::time::Hertz; use crate::{ @@ -337,20 +337,33 @@ pub trait Instance: Deref { } impl UartBase { + fn init(self, config: Config, clocks: &Clocks) -> Self { + if Uart::IDX == 2 { + self.init_with_clock_freq(config, clocks.apb1()) + } else { + self.init_with_clock_freq(config, clocks.apb2()) + } + } + /// This function assumes that the peripheral clock was alredy enabled /// in the SYSCONFIG register - fn init(self, config: Config, sys_clk: Hertz) -> Self { + fn init_with_clock_freq(self, config: Config, apb_clk: Hertz) -> Self { let baud_multiplier = match config.baud8 { false => 16, true => 8, }; + // This is the calculation: (64.0 * (x - integer_part as f32) + 0.5) as u32 without floating + // point calculations. + let frac = ((apb_clk.raw() % (config.baudrate.raw() * 16)) * 64 + + (config.baudrate.raw() * 8)) + / (config.baudrate.raw() * 16); // Calculations here are derived from chapter 10.4.4 (p.74) of the datasheet. - let x = sys_clk.raw() as f32 / (config.baudrate.raw() * baud_multiplier) as f32; + let x = apb_clk.raw() as f32 / (config.baudrate.raw() * baud_multiplier) as f32; let integer_part = x as u32; - let frac = (64.0 * (x - integer_part as f32) + 0.5) as u32; - self.uart - .clkscale() - .write(|w| unsafe { w.bits(integer_part * 64 + frac) }); + self.uart.clkscale().write(|w| unsafe { + w.frac().bits(frac as u8); + w.int().bits(integer_part) + }); let (paren, pareven) = match config.parity { Parity::None => (false, false), @@ -459,10 +472,16 @@ impl Uart where UartInstance: Instance, { + fn init(mut self, config: Config, clocks: &Clocks) -> Self { + self.inner = self.inner.init(config, clocks); + self + } + /// This function assumes that the peripheral clock was already enabled /// in the SYSCONFIG register - fn init(mut self, config: Config, sys_clk: Hertz) -> Self { - self.inner = self.inner.init(config, sys_clk); + #[allow(dead_code)] + fn init_with_clock_freq(mut self, config: Config, sys_clk: Hertz) -> Self { + self.inner = self.inner.init_with_clock_freq(config, sys_clk); self } @@ -798,7 +817,7 @@ macro_rules! uart_impl { pins: Pins, config: impl Into, syscfg: &mut va416xx::Sysconfig, - sys_clk: impl Into + clocks: &Clocks ) -> Self { crate::clock::enable_peripheral_clock(syscfg, $clk_enb_enum); @@ -809,7 +828,29 @@ macro_rules! uart_impl { rx: Rx::new(), }, pins, - }.init(config.into(), sys_clk.into()) + }.init(config.into(), clocks) + } + + paste::paste! { + pub fn [ <$uartx _with_clock_freq> ]( + uart: $Uartx, + pins: Pins, + config: impl Into, + syscfg: &mut va416xx::Sysconfig, + clock: impl Into, + ) -> Self + { + crate::clock::enable_peripheral_clock(syscfg, $clk_enb_enum); + Uart { + inner: UartBase { + uart, + tx: Tx::new(), + rx: Rx::new(), + }, + pins, + }.init_with_clock_freq(config.into(), clock.into()) + } + } } diff --git a/va416xx-hal/src/wdt.rs b/va416xx-hal/src/wdt.rs new file mode 100644 index 0000000..320c029 --- /dev/null +++ b/va416xx-hal/src/wdt.rs @@ -0,0 +1,114 @@ +use crate::time::Hertz; +use crate::{ + clock::{Clocks, PeripheralSelect}, + pac, + prelude::SyscfgExt, +}; + +pub const WDT_UNLOCK_VALUE: u32 = 0x1ACC_E551; + +pub struct WdtController { + clock_freq: Hertz, + wdt: pac::WatchDog, +} + +/// Enable the watchdog interrupt +/// +/// # Safety +/// +/// This function is `unsafe` because it can break mask-based critical sections. +#[inline] +pub unsafe fn enable_wdt_interrupts() { + unsafe { + cortex_m::peripheral::NVIC::unmask(pac::Interrupt::WATCHDOG); + } +} + +#[inline] +pub fn disable_wdt_interrupts() { + cortex_m::peripheral::NVIC::mask(pac::Interrupt::WATCHDOG); +} + +impl WdtController { + pub fn new( + &self, + syscfg: &mut pac::Sysconfig, + wdt: pac::WatchDog, + clocks: &Clocks, + wdt_freq_ms: u32, + ) -> Self { + Self::start(syscfg, wdt, clocks, wdt_freq_ms) + } + + pub fn start( + syscfg: &mut pac::Sysconfig, + wdt: pac::WatchDog, + clocks: &Clocks, + wdt_freq_ms: u32, + ) -> Self { + syscfg.enable_peripheral_clock(PeripheralSelect::Watchdog); + // It's done like that in Vorago examples. Not exactly sure why the reset is necessary + // though.. + syscfg.assert_periph_reset(PeripheralSelect::Watchdog); + cortex_m::asm::nop(); + cortex_m::asm::nop(); + syscfg.deassert_periph_reset(PeripheralSelect::Watchdog); + + let wdt_clock = clocks.apb2(); + let mut wdt_ctrl = Self { + clock_freq: wdt_clock, + wdt, + }; + wdt_ctrl.set_freq(wdt_freq_ms); + wdt_ctrl.wdt.wdogcontrol().write(|w| w.inten().set_bit()); + wdt_ctrl.feed(); + // Unmask the watchdog interrupt + unsafe { + enable_wdt_interrupts(); + } + wdt_ctrl + } + + #[inline] + pub fn set_freq(&mut self, freq_ms: u32) { + let counter = (self.clock_freq.raw() / 1000) * freq_ms; + self.wdt.wdogload().write(|w| unsafe { w.bits(counter) }); + } + + #[inline] + pub fn disable_reset(&mut self) { + self.wdt.wdogcontrol().modify(|_, w| w.resen().clear_bit()) + } + + #[inline] + pub fn enable_reset(&mut self) { + self.wdt.wdogcontrol().modify(|_, w| w.resen().set_bit()) + } + + #[inline] + pub fn counter(&self) -> u32 { + self.wdt.wdogvalue().read().bits() + } + + #[inline] + pub fn feed(&self) { + self.wdt.wdogintclr().write(|w| unsafe { w.bits(1) }); + } + + #[inline] + pub fn lock(&self) { + self.wdt.wdoglock().write(|w| unsafe { w.bits(0) }); + } + + #[inline] + pub fn unlock(&self) { + self.wdt + .wdoglock() + .write(|w| unsafe { w.bits(WDT_UNLOCK_VALUE) }); + } + + #[inline] + pub fn is_locked(&self) -> bool { + self.wdt.wdogload().read().bits() == 1 + } +} diff --git a/va416xx/Cargo.toml b/va416xx/Cargo.toml index b510ff2..fff48de 100644 --- a/va416xx/Cargo.toml +++ b/va416xx/Cargo.toml @@ -15,6 +15,7 @@ categories = ["embedded", "no-std", "hardware-support"] [dependencies] cortex-m = "0.7" vcell = "0.1.3" +critical-section = { version = "1", optional = true } [dependencies.cortex-m-rt] optional = true @@ -22,3 +23,7 @@ version = ">=0.6.15,<0.8" [features] rt = ["cortex-m-rt/device"] + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docs_rs", "--generate-link-to-definition"] diff --git a/va416xx/README.md b/va416xx/README.md index c8c31b5..0737264 100644 --- a/va416xx/README.md +++ b/va416xx/README.md @@ -1,5 +1,4 @@ [![Crates.io](https://img.shields.io/crates/v/va416xx)](https://crates.io/crates/va416xx) -[![build](https://github.com/us-irs/va416xx-rs/actions/workflows/ci.yml/badge.svg)](https://github.com/us-irs/va416xx-rs/actions/workflows/ci.yml) [![docs.rs](https://img.shields.io/docsrs/va416xx)](https://docs.rs/va416xx) # PAC for the Vorago VA416xx microcontroller family diff --git a/va416xx/gen-helper.sh b/va416xx/gen-helper.sh index 8407751..60a9a59 100755 --- a/va416xx/gen-helper.sh +++ b/va416xx/gen-helper.sh @@ -29,7 +29,7 @@ then fi svdtools patch svd/va416xx-patch.yml -${svd2rust_bin} -i svd/va416xx.svd.patched +${svd2rust_bin} --reexport-interrupt -i svd/va416xx.svd.patched result=$? if [ $result -ne 0 ]; then diff --git a/va416xx/src/lib.rs b/va416xx/src/lib.rs index 2ee184f..9fbc88f 100644 --- a/va416xx/src/lib.rs +++ b/va416xx/src/lib.rs @@ -3,10 +3,16 @@ svd2rust release can be generated by cloning the svd2rust [repository], checking #![allow(non_camel_case_types)] #![allow(non_snake_case)] #![no_std] +// Manually inserted. +#![cfg_attr(docs_rs, feature(doc_auto_cfg))] use core::marker::PhantomData; use core::ops::Deref; #[doc = r"Number available in the NVIC for configuring priority"] pub const NVIC_PRIO_BITS: u8 = 4; +#[cfg(feature = "rt")] +pub use self::Interrupt as interrupt; +#[cfg(feature = "rt")] +pub use cortex_m_rt::interrupt; #[allow(unused_imports)] use generic::*; #[doc = r"Common register and bit access and modify traits"] diff --git a/va416xx/src/porta.rs b/va416xx/src/porta.rs index 7d18568..2a15691 100644 --- a/va416xx/src/porta.rs +++ b/va416xx/src/porta.rs @@ -1,4 +1,4 @@ -// Manually inserted. +// Added manually. #![allow(clippy::identity_op)] #[repr(C)] diff --git a/vorago-peb1/examples/blinky.rs b/vorago-peb1/examples/blinky.rs deleted file mode 100644 index a261fcb..0000000 --- a/vorago-peb1/examples/blinky.rs +++ /dev/null @@ -1,62 +0,0 @@ -//! Simple blinky example -#![no_main] -#![no_std] - -use cortex_m_rt::entry; -use embedded_hal::digital::v2::{OutputPin, ToggleableOutputPin}; -use panic_halt as _; -use va416xx_hal::{gpio::PinsG, pac}; - -// Mask for the LED -const LED_PG5: u32 = 1 << 5; - -#[allow(dead_code)] -enum LibType { - Pac, - Hal, - Bsp, -} - -#[entry] -fn main() -> ! { - let mut dp = pac::Peripherals::take().unwrap(); - let lib_type = LibType::Hal; - - match lib_type { - LibType::Pac => { - // Enable all peripheral clocks - dp.SYSCONFIG - .peripheral_clk_enable - .modify(|_, w| w.portg().set_bit()); - dp.PORTG.dir().modify(|_, w| unsafe { w.bits(LED_PG5) }); - dp.PORTG - .datamask() - .modify(|_, w| unsafe { w.bits(LED_PG5) }); - for _ in 0..10 { - dp.PORTG.clrout().write(|w| unsafe { w.bits(LED_PG5) }); - cortex_m::asm::delay(5_000_000); - dp.PORTG.setout().write(|w| unsafe { w.bits(LED_PG5) }); - cortex_m::asm::delay(5_000_000); - } - loop { - dp.PORTG.togout().write(|w| unsafe { w.bits(LED_PG5) }); - cortex_m::asm::delay(25_000_000); - } - } - LibType::Hal => { - let portg = PinsG::new(&mut dp.SYSCONFIG, Some(dp.IOCONFIG), dp.PORTG); - // Enable all peripheral clocks - let mut led = portg.pg5.into_push_pull_output(); - for _ in 0..10 { - led.set_low().ok(); - cortex_m::asm::delay(5_000_000); - led.set_high().ok(); - } - loop { - led.toggle().ok(); - cortex_m::asm::delay(25_000_000); - } - } - LibType::Bsp => loop {}, - } -} -- 2.43.0 From df38ba8e5bb5da9b3eb6f08393d72e3cb928284f Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Thu, 20 Jun 2024 20:04:05 +0200 Subject: [PATCH 3/3] smaller workspace file tweaks --- Cargo.toml | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 17fa3df..d189036 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,17 +1,26 @@ [workspace] resolver = "2" -members = [ "examples/simple", +members = [ + "examples/simple", "va416xx", "va416xx-hal", "vorago-peb1" ] [profile.dev] -lto = false -debug = true +codegen-units = 1 +debug = 2 +debug-assertions = true # <- +incremental = false +opt-level = 'z' # <- +overflow-checks = true # <- +# cargo build/run --release [profile.release] -# Can be problematic for debugging -lto = true -debug = true -opt-level = 's' +codegen-units = 1 +debug = 2 +debug-assertions = false # <- +incremental = false +lto = 'fat' +opt-level = 3 # <- +overflow-checks = false # <- -- 2.43.0