UART, WDT and CLKGEN

This commit is contained in:
Robin Müller 2024-06-13 17:27:27 +02:00
parent 0e395d3747
commit 46dcad1c10
26 changed files with 855 additions and 128 deletions

View File

@ -1,6 +1,6 @@
[workspace] [workspace]
resolver = "2" resolver = "2"
members = [ members = [ "examples/simple",
"va416xx", "va416xx",
"va416xx-hal", "va416xx-hal",
"vorago-peb1" "vorago-peb1"

View File

@ -30,25 +30,28 @@ to conveniently flash with `cargo run`.
Use the following command to have a starting configuration for VS Code: Use the following command to have a starting configuration for VS Code:
```sh ```sh
cp vscode .vscode -r cp -rT vscode .vscode
``` ```
You can then adapt the files in `.vscode` to your needs. 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 1. [SEGGER J-Link tools](https://www.segger.com/downloads/jlink/) installed
2. [gdb-multiarch](https://packages.debian.org/sid/gdb-multiarch) or similar 2. [gdb-multiarch](https://packages.debian.org/sid/gdb-multiarch) or similar
cross-architecture debugger installed. All commands here assume `gdb-multiarch`. 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 You can build the blinky example application with the following command
```sh ```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 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" 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. 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"`

View File

@ -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"

View File

@ -4,13 +4,16 @@
use cortex_m_rt::entry; use cortex_m_rt::entry;
use embedded_hal::digital::StatefulOutputPin; 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}; use va416xx_hal::{gpio::PinsG, pac};
#[entry] #[entry]
fn main() -> ! { fn main() -> ! {
// SAFETY: Peripherals are only stolen once here. rtt_init_print!();
let mut dp = unsafe { pac::Peripherals::steal() }; 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 portg = PinsG::new(&mut dp.sysconfig, Some(dp.ioconfig), dp.portg);
let mut led = portg.pg5.into_readable_push_pull_output(); let mut led = portg.pg5.into_readable_push_pull_output();
//let mut delay = CountDownTimer::new(&mut dp.SYSCONFIG, 50.mhz(), dp.TIM0); //let mut delay = CountDownTimer::new(&mut dp.SYSCONFIG, 50.mhz(), dp.TIM0);

View File

@ -11,8 +11,7 @@ const LED_PG5: u32 = 1 << 5;
#[entry] #[entry]
fn main() -> ! { fn main() -> ! {
// SAFETY: Peripherals are only stolen once here. let dp = pac::Peripherals::take().unwrap();
let dp = unsafe { pac::Peripherals::steal() };
// Enable all peripheral clocks // Enable all peripheral clocks
dp.sysconfig dp.sysconfig
.peripheral_clk_enable() .peripheral_clk_enable()

View File

@ -1,11 +1,10 @@
//! Code to test RTT logger functionality. // Code to test RTT logger functionality.
#![no_main] #![no_main]
#![no_std] #![no_std]
use cortex_m_rt::entry; use cortex_m_rt::entry;
use panic_rtt_target as _; use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print}; use rtt_target::{rprintln, rtt_init_print};
use va416xx as _;
use va416xx_hal::pac; use va416xx_hal::pac;
// Mask for the LED // Mask for the LED
@ -13,8 +12,7 @@ const LED_PG5: u32 = 1 << 5;
#[entry] #[entry]
fn main() -> ! { fn main() -> ! {
// SAFETY: Peripherals are only stolen once here. let dp = pac::Peripherals::take().unwrap();
let dp = unsafe { pac::Peripherals::steal() };
// Enable all peripheral clocks // Enable all peripheral clocks
dp.sysconfig dp.sysconfig
.peripheral_clk_enable() .peripheral_clk_enable()
@ -25,7 +23,7 @@ fn main() -> ! {
.modify(|_, w| unsafe { w.bits(LED_PG5) }); .modify(|_, w| unsafe { w.bits(LED_PG5) });
rtt_init_print!(); rtt_init_print!();
rprintln!("-- RTT Demo --"); rprintln!("VA416xx RTT Demo");
let mut counter = 0; let mut counter = 0;
loop { loop {
rprintln!("{}: Hello, world!", counter); rprintln!("{}: Hello, world!", counter);

View File

@ -1,13 +1,16 @@
//! UART example application. Sends a test string over a UART and then enters // UART example application. Sends a test string over a UART and then enters
//! echo mode // echo mode
#![no_main] #![no_main]
#![no_std] #![no_std]
use cortex_m_rt::entry; 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 panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print}; 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}; use va416xx_hal::{gpio::PinsG, pac, uart};
#[entry] #[entry]
@ -15,28 +18,34 @@ fn main() -> ! {
rtt_init_print!(); rtt_init_print!();
rprintln!("-- VA416xx UART example application--"); rprintln!("-- VA416xx UART example application--");
// SAFETY: Peripherals are only stolen once here. let mut dp = pac::Peripherals::take().unwrap();
let mut dp = unsafe { pac::Peripherals::steal() };
// 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 gpiob = PinsG::new(&mut dp.sysconfig, Some(dp.ioconfig), dp.portg);
let tx = gpiob.pg0.into_funsel_1(); let tx = gpiob.pg0.into_funsel_1();
let rx = gpiob.pg1.into_funsel_1(); let rx = gpiob.pg1.into_funsel_1();
let uartb = uart::Uart::uart0( let uart0 = uart::Uart::uart0(
dp.uart0, dp.uart0,
(tx, rx), (tx, rx),
Hertz::from_raw(115200), Hertz::from_raw(115200),
&mut dp.sysconfig, &mut dp.sysconfig,
Hertz::from_raw(MegaHertz::from_raw(20).to_Hz()), &clocks,
); );
let (mut tx, mut rx) = uartb.split(); let (mut tx, mut rx) = uart0.split();
let mut recv_buf: [u8; 32] = [0; 32]; writeln!(tx, "Hello World\n\r").unwrap();
writeln!(tx, "Hello World").unwrap();
loop { loop {
// Echo what is received on the serial link. // Echo what is received on the serial link.
match rx.read(&mut recv_buf) { match nb::block!(rx.read()) {
Ok(recvd) => { 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); rprintln!("UART TX error: {:?}", e);
} }
} }

View File

@ -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<Cell<u32>> = 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) });
}
}

View File

@ -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);
}

View File

@ -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();
}
}

View File

@ -11,28 +11,24 @@ keywords = ["no-std", "hal", "cortex-m", "vorago", "va416xx"]
categories = ["embedded", "no-std", "hardware-support"] categories = ["embedded", "no-std", "hardware-support"]
[dependencies] [dependencies]
cortex-m = "0.7" cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
cortex-m-rt = "0.7"
nb = "1" nb = "1"
paste = "1" paste = "1"
embedded-hal-nb = "1" embedded-hal-nb = "1"
embedded-hal = "1" embedded-hal = "1"
embedded-io = "0.6" embedded-io = "0.6"
typenum = "1.12.0" typenum = "1"
defmt = { version = "0.3", optional = true } defmt = { version = "0.3", optional = true }
fugit = "0.3" fugit = "0.3"
delegate = "0.12" delegate = "0.12"
[dependencies.va416xx] [dependencies.va416xx]
path = "../va416xx" path = "../va416xx"
default-features = false
version = "0.1.0" version = "0.1.0"
features = ["critical-section"]
[features] [features]
default = ["rt", "revb"]
rt = ["va416xx/rt"] rt = ["va416xx/rt"]
defmt = ["dep:defmt"] revb = []
[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"]}

View File

@ -1,5 +1,11 @@
//! This also includes functionality to enable the peripheral clocks //! 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)] #[derive(Copy, Clone, PartialEq)]
pub enum PeripheralSelect { pub enum PeripheralSelect {
@ -50,14 +56,470 @@ pub enum FilterClkSel {
Clk7 = 7, 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 syscfg
.peripheral_clk_enable() .peripheral_clk_enable()
.modify(|r, w| unsafe { w.bits(r.bits() | (1 << clock as u8)) }); .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 syscfg
.peripheral_clk_enable() .peripheral_clk_enable()
.modify(|r, w| unsafe { w.bits(r.bits() & !(1 << clock as u8)) }); .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<Hertz>,
pll_cfg: Option<PllCfg>,
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<Clocks, ClkCfgError> {
// 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)
);
}
}

View File

@ -24,7 +24,7 @@
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct IsMaskedError(pub(crate) ()); pub struct IsMaskedError;
macro_rules! common_reg_if_functions { macro_rules! common_reg_if_functions {
() => { () => {

View File

@ -554,6 +554,22 @@ where
} }
} }
impl<I, C> InputPin for Pin<I, Output<C>>
where
I: PinId,
C: OutputConfig + ReadableOutput,
{
#[inline]
fn is_high(&mut self) -> Result<bool, Self::Error> {
Ok(self._is_high())
}
#[inline]
fn is_low(&mut self) -> Result<bool, Self::Error> {
Ok(self._is_low())
}
}
//================================================================================================== //==================================================================================================
// Registers // Registers
//================================================================================================== //==================================================================================================

View File

@ -209,7 +209,7 @@ pub(super) unsafe trait RegisterInterface {
#[inline(always)] #[inline(always)]
fn read_pin_masked(&self) -> Result<bool, IsMaskedError> { fn read_pin_masked(&self) -> Result<bool, IsMaskedError> {
if !self.datamask() { if !self.datamask() {
Err(IsMaskedError(())) Err(IsMaskedError)
} else { } else {
Ok(((self.port_reg().datain().read().bits() >> self.id().num) & 0x01) == 1) Ok(((self.port_reg().datain().read().bits() >> self.id().num) & 0x01) == 1)
} }
@ -234,7 +234,7 @@ pub(super) unsafe trait RegisterInterface {
#[inline] #[inline]
fn write_pin_masked(&mut self, bit: bool) -> Result<(), IsMaskedError> { fn write_pin_masked(&mut self, bit: bool) -> Result<(), IsMaskedError> {
if !self.datamask() { if !self.datamask() {
Err(IsMaskedError(())) Err(IsMaskedError)
} else { } else {
// Safety: SETOUT is a "mask" register, and we only write the bit for // Safety: SETOUT is a "mask" register, and we only write the bit for
// this pin ID // this pin ID

View File

@ -1,7 +1,10 @@
#![no_std] #![no_std]
#[cfg(test)]
extern crate std;
pub use va416xx; pub use va416xx as device;
pub use va416xx as pac; pub use va416xx as pac;
pub mod prelude; pub mod prelude;
pub mod clock; pub mod clock;
@ -9,6 +12,7 @@ pub mod gpio;
pub mod time; pub mod time;
pub mod typelevel; pub mod typelevel;
pub mod uart; pub mod uart;
pub mod wdt;
#[derive(Debug, Eq, Copy, Clone, PartialEq)] #[derive(Debug, Eq, Copy, Clone, PartialEq)]
pub enum FunSel { pub enum FunSel {

View File

@ -1 +1,4 @@
//! Prelude
pub use crate::clock::{ClkgenExt, SyscfgExt};
pub use fugit::ExtU32 as _;
pub use fugit::RateExtU32 as _;

View File

@ -60,14 +60,14 @@ impl Sealed for NoneT {}
/// specific type. Stated differently, it guarantees that `T == Specific` in /// specific type. Stated differently, it guarantees that `T == Specific` in
/// the following example. /// the following example.
/// ///
/// ``` /// ```ignore
/// where T: Is<Type = Specific> /// where T: Is<Type = Specific>
/// ``` /// ```
/// ///
/// Moreover, the super traits guarantee that any instance of or reference to a /// Moreover, the super traits guarantee that any instance of or reference to a
/// type `T` can be converted into the `Specific` type. /// type `T` can be converted into the `Specific` type.
/// ///
/// ``` /// ```ignore
/// fn example<T>(mut any: T) /// fn example<T>(mut any: T)
/// where /// where
/// T: Is<Type = Specific>, /// T: Is<Type = Specific>,

View File

@ -4,7 +4,7 @@ use core::ops::Deref;
use embedded_hal_nb::serial::Read; use embedded_hal_nb::serial::Read;
use fugit::RateExtU32; 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::gpio::{AltFunc1, Pin, PD11, PD12, PE2, PE3, PF11, PF12, PF8, PF9, PG0, PG1};
use crate::time::Hertz; use crate::time::Hertz;
use crate::{ use crate::{
@ -337,20 +337,33 @@ pub trait Instance: Deref<Target = uart_base::RegisterBlock> {
} }
impl<Uart: Instance> UartBase<Uart> { impl<Uart: Instance> UartBase<Uart> {
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 /// This function assumes that the peripheral clock was alredy enabled
/// in the SYSCONFIG register /// 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 { let baud_multiplier = match config.baud8 {
false => 16, false => 16,
true => 8, 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. // 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 integer_part = x as u32;
let frac = (64.0 * (x - integer_part as f32) + 0.5) as u32; self.uart.clkscale().write(|w| unsafe {
self.uart w.frac().bits(frac as u8);
.clkscale() w.int().bits(integer_part)
.write(|w| unsafe { w.bits(integer_part * 64 + frac) }); });
let (paren, pareven) = match config.parity { let (paren, pareven) = match config.parity {
Parity::None => (false, false), Parity::None => (false, false),
@ -459,10 +472,16 @@ impl<UartInstance, Pins> Uart<UartInstance, Pins>
where where
UartInstance: Instance, 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 /// This function assumes that the peripheral clock was already enabled
/// in the SYSCONFIG register /// in the SYSCONFIG register
fn init(mut self, config: Config, sys_clk: Hertz) -> Self { #[allow(dead_code)]
self.inner = self.inner.init(config, sys_clk); 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 self
} }
@ -798,7 +817,7 @@ macro_rules! uart_impl {
pins: Pins, pins: Pins,
config: impl Into<Config>, config: impl Into<Config>,
syscfg: &mut va416xx::Sysconfig, syscfg: &mut va416xx::Sysconfig,
sys_clk: impl Into<Hertz> clocks: &Clocks
) -> Self ) -> Self
{ {
crate::clock::enable_peripheral_clock(syscfg, $clk_enb_enum); crate::clock::enable_peripheral_clock(syscfg, $clk_enb_enum);
@ -809,7 +828,29 @@ macro_rules! uart_impl {
rx: Rx::new(), rx: Rx::new(),
}, },
pins, 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<Config>,
syscfg: &mut va416xx::Sysconfig,
clock: impl Into<Hertz>,
) -> 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())
}
} }
} }

114
va416xx-hal/src/wdt.rs Normal file
View File

@ -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
}
}

View File

@ -15,6 +15,7 @@ categories = ["embedded", "no-std", "hardware-support"]
[dependencies] [dependencies]
cortex-m = "0.7" cortex-m = "0.7"
vcell = "0.1.3" vcell = "0.1.3"
critical-section = { version = "1", optional = true }
[dependencies.cortex-m-rt] [dependencies.cortex-m-rt]
optional = true optional = true
@ -22,3 +23,7 @@ version = ">=0.6.15,<0.8"
[features] [features]
rt = ["cortex-m-rt/device"] rt = ["cortex-m-rt/device"]
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docs_rs", "--generate-link-to-definition"]

View File

@ -1,5 +1,4 @@
[![Crates.io](https://img.shields.io/crates/v/va416xx)](https://crates.io/crates/va416xx) [![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) [![docs.rs](https://img.shields.io/docsrs/va416xx)](https://docs.rs/va416xx)
# PAC for the Vorago VA416xx microcontroller family # PAC for the Vorago VA416xx microcontroller family

View File

@ -29,7 +29,7 @@ then
fi fi
svdtools patch svd/va416xx-patch.yml svdtools patch svd/va416xx-patch.yml
${svd2rust_bin} -i svd/va416xx.svd.patched ${svd2rust_bin} --reexport-interrupt -i svd/va416xx.svd.patched
result=$? result=$?
if [ $result -ne 0 ]; then if [ $result -ne 0 ]; then

View File

@ -3,10 +3,16 @@ svd2rust release can be generated by cloning the svd2rust [repository], checking
#![allow(non_camel_case_types)] #![allow(non_camel_case_types)]
#![allow(non_snake_case)] #![allow(non_snake_case)]
#![no_std] #![no_std]
// Manually inserted.
#![cfg_attr(docs_rs, feature(doc_auto_cfg))]
use core::marker::PhantomData; use core::marker::PhantomData;
use core::ops::Deref; use core::ops::Deref;
#[doc = r"Number available in the NVIC for configuring priority"] #[doc = r"Number available in the NVIC for configuring priority"]
pub const NVIC_PRIO_BITS: u8 = 4; 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)] #[allow(unused_imports)]
use generic::*; use generic::*;
#[doc = r"Common register and bit access and modify traits"] #[doc = r"Common register and bit access and modify traits"]

View File

@ -1,4 +1,4 @@
// Manually inserted. // Added manually.
#![allow(clippy::identity_op)] #![allow(clippy::identity_op)]
#[repr(C)] #[repr(C)]

View File

@ -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 {},
}
}