Rework library structure

Changed:

- Move most library components to new [`vorago-shared-periphs`](https://egit.irs.uni-stuttgart.de/rust/vorago-shared-periphs)
  which is mostly re-exported in this crate.
- Overhaul and simplification of several HAL APIs. The system configuration and IRQ router
  peripheral instance generally does not need to be passed to HAL API anymore.
- All HAL drivers are now type erased. The constructors will still expect and consume the PAC
  singleton component for resource management purposes, but are not cached anymore.
- Refactoring of GPIO library to be more inline with embassy GPIO API.

Added:

- I2C clock timeout feature support.
This commit is contained in:
Robin Müller 2025-04-24 14:05:02 +02:00
parent d641f3943f
commit 05f85bd209
52 changed files with 592 additions and 9297 deletions

View File

@ -6,7 +6,6 @@ edition = "2021"
[dependencies]
cortex-m = "0.7"
cortex-m-rt = "0.7"
embedded-hal = "1"
defmt-rtt = "0.4"
defmt = "1"
panic-probe = { version = "1", features = ["defmt"] }
@ -16,3 +15,4 @@ static_assertions = "1"
[dependencies.va416xx-hal]
version = "0.5"
features = ["va41630", "defmt"]
path = "../va416xx-hal"

View File

@ -10,11 +10,10 @@ use crc::{Crc, CRC_32_ISO_HDLC};
use defmt_rtt as _;
use panic_probe as _;
use va416xx_hal::{
clock::{pll_setup_delay, ClkDivSel, ClkselSys},
clock::{pll_setup_delay, ClkDivSel, ClkselSys, ClockConfigurator},
edac,
nvm::Nvm,
pac::{self, interrupt},
prelude::*,
time::Hertz,
wdt::Wdt,
};
@ -104,23 +103,20 @@ fn main() -> ! {
dp.sysconfig.rom_prot().write(|w| unsafe { w.bits(1) });
setup_edac(&mut dp.sysconfig);
// Use the external clock connected to XTAL_N.
let clocks = dp
.clkgen
.constrain()
let clocks = ClockConfigurator::new(dp.clkgen)
.xtal_n_clk_with_src_freq(Hertz::from_raw(EXTCLK_FREQ))
.freeze(&mut dp.sysconfig)
.freeze()
.unwrap();
let mut opt_wdt = OptWdt(None);
if WITH_WDT {
opt_wdt.0 = Some(Wdt::start(
&mut dp.sysconfig,
dp.watch_dog,
&clocks,
WDT_FREQ_MS,
));
}
let nvm = Nvm::new(&mut dp.sysconfig, dp.spi3, &clocks);
let nvm = Nvm::new(dp.spi3, &clocks);
if FLASH_SELF {
let mut first_four_bytes: [u8; 4] = [0; 4];

View File

@ -4,10 +4,8 @@ version = "0.1.0"
edition = "2021"
[dependencies]
cfg-if = "1"
cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
cortex-m-rt = "0.7"
embedded-hal = "1"
cfg-if = "1"
embedded-io = "0.6"
embedded-hal-async = "1"
embedded-io-async = "0.6"
@ -18,7 +16,6 @@ defmt = "1"
panic-probe = { version = "1", features = ["defmt"] }
static_cell = "2"
critical-section = "1"
once_cell = { version = "1", default-features = false, features = ["critical-section"] }
ringbuf = { version = "0.4", default-features = false }
embassy-sync = "0.6"
@ -29,11 +26,14 @@ embassy-executor = { version = "0.7", features = [
"executor-interrupt"
]}
va416xx-hal = { version = "0.5", features = ["defmt"] }
va416xx-embassy = { version = "0.1", default-features = false }
va416xx-hal = { version = "0.5", path = "../../va416xx-hal", features = ["defmt"] }
va416xx-embassy = { version = "0.1", path = "../../va416xx-embassy", default-features = false }
[features]
default = ["ticks-hz-1_000", "va416xx-embassy/irq-tim14-tim15"]
custom-irqs = []
ticks-hz-1_000 = ["embassy-time/tick-hz-1_000"]
ticks-hz-32_768 = ["embassy-time/tick-hz-32_768"]
[package.metadata.cargo-machete]
ignored = ["cortex-m-rt"]

View File

@ -5,6 +5,7 @@
//! [CHECK_XXX_TO_XXX] constants to true.
#![no_std]
#![no_main]
// Import panic provider.
use panic_probe as _;
// Import logger.
@ -16,23 +17,19 @@ use embassy_sync::channel::{Receiver, Sender};
use embassy_sync::{blocking_mutex::raw::ThreadModeRawMutex, channel::Channel};
use embassy_time::{Duration, Instant, Timer};
use embedded_hal_async::digital::Wait;
use va416xx_hal::clock::ClkgenExt;
use va416xx_hal::gpio::{
on_interrupt_for_async_gpio_for_port, InputDynPinAsync, InputPinAsync, PinsB, PinsC, PinsD,
PinsE, PinsF, PinsG, Port,
};
use va416xx_hal::clock::ClockConfigurator;
use va416xx_hal::gpio::asynch::{on_interrupt_for_async_gpio_for_port, InputPinAsync};
use va416xx_hal::gpio::{Input, Output, PinState, Port};
use va416xx_hal::pac::{self, interrupt};
use va416xx_hal::pins::{PinsA, PinsB, PinsC, PinsD, PinsE, PinsF, PinsG};
use va416xx_hal::time::Hertz;
use va416xx_hal::{
gpio::{DynPin, PinsA},
pac::{self, interrupt},
};
const CHECK_PA0_TO_PA1: bool = true;
const CHECK_PB0_TO_PB1: bool = true;
const CHECK_PC14_TO_PC15: bool = true;
const CHECK_PD2_TO_PD3: bool = true;
const CHECK_PE0_TO_PE1: bool = true;
const CHECK_PF0_TO_PF1: bool = true;
const CHECK_PB0_TO_PB1: bool = false;
const CHECK_PC14_TO_PC15: bool = false;
const CHECK_PD2_TO_PD3: bool = false;
const CHECK_PE0_TO_PE1: bool = false;
const CHECK_PF0_TO_PF1: bool = false;
#[derive(Clone, Copy)]
pub struct GpioCmd {
@ -70,41 +67,30 @@ static CHANNEL_PF0_TO_PF1: Channel<ThreadModeRawMutex, GpioCmd, 3> = Channel::ne
async fn main(spawner: Spawner) {
defmt::println!("-- VA416xx Async GPIO Demo --");
let mut dp = pac::Peripherals::take().unwrap();
let dp = pac::Peripherals::take().unwrap();
// Initialize the systick interrupt & obtain the token to prove that we did
// Use the external clock connected to XTAL_N.
let clocks = dp
.clkgen
.constrain()
let clocks = ClockConfigurator::new(dp.clkgen)
.xtal_n_clk_with_src_freq(Hertz::from_raw(EXTCLK_FREQ))
.freeze(&mut dp.sysconfig)
.freeze()
.unwrap();
// Safety: Only called once here.
unsafe {
va416xx_embassy::init(
&mut dp.sysconfig,
&dp.irq_router,
dp.tim15,
dp.tim14,
&clocks,
)
};
va416xx_embassy::init(dp.tim15, dp.tim14, &clocks);
let porta = PinsA::new(&mut dp.sysconfig, dp.porta);
let portb = PinsB::new(&mut dp.sysconfig, dp.portb);
let portc = PinsC::new(&mut dp.sysconfig, dp.portc);
let portd = PinsD::new(&mut dp.sysconfig, dp.portd);
let porte = PinsE::new(&mut dp.sysconfig, dp.porte);
let portf = PinsF::new(&mut dp.sysconfig, dp.portf);
let porta = PinsA::new(dp.porta);
let portb = PinsB::new(dp.portb);
let portc = PinsC::new(dp.portc);
let portd = PinsD::new(dp.portd);
let porte = PinsE::new(dp.porte);
let portf = PinsF::new(dp.portf);
let portg = PinsG::new(&mut dp.sysconfig, dp.portg);
let mut led = portg.pg5.into_readable_push_pull_output();
let portg = PinsG::new(dp.portg);
let mut led = Output::new(portg.pg5, PinState::Low);
if CHECK_PA0_TO_PA1 {
let out_pin = porta.pa0.into_readable_push_pull_output();
let in_pin = porta.pa1.into_floating_input();
let out_pin = out_pin.downgrade();
let out_pin = Output::new(porta.pa0, PinState::Low);
let in_pin = Input::new_floating(porta.pa1);
let in_pin = InputPinAsync::new(in_pin).unwrap();
spawner
@ -119,10 +105,9 @@ async fn main(spawner: Spawner) {
}
if CHECK_PB0_TO_PB1 {
let out_pin = portb.pb0.into_readable_push_pull_output();
let in_pin = portb.pb1.into_floating_input();
let out_pin = out_pin.downgrade();
let in_pin = InputDynPinAsync::new(in_pin.downgrade()).unwrap();
let out_pin = Output::new(portb.pb0, PinState::Low);
let in_pin = Input::new_floating(portb.pb1);
let in_pin = InputPinAsync::new(in_pin).unwrap();
spawner
.spawn(output_task(
@ -136,10 +121,9 @@ async fn main(spawner: Spawner) {
}
if CHECK_PC14_TO_PC15 {
let out_pin = portc.pc14.into_readable_push_pull_output();
let in_pin = portc.pc15.into_floating_input();
let out_pin = out_pin.downgrade();
let in_pin = InputDynPinAsync::new(in_pin.downgrade()).unwrap();
let out_pin = Output::new(portc.pc14, PinState::Low);
let in_pin = Input::new_floating(portc.pc15);
let in_pin = InputPinAsync::new(in_pin).unwrap();
spawner
.spawn(output_task(
"PC14 to PC15",
@ -152,10 +136,9 @@ async fn main(spawner: Spawner) {
}
if CHECK_PD2_TO_PD3 {
let out_pin = portd.pd2.into_readable_push_pull_output();
let in_pin = portd.pd3.into_floating_input();
let out_pin = out_pin.downgrade();
let in_pin = InputDynPinAsync::new(in_pin.downgrade()).unwrap();
let out_pin = Output::new(portd.pd2, PinState::Low);
let in_pin = Input::new_floating(portd.pd3);
let in_pin = InputPinAsync::new(in_pin).unwrap();
spawner
.spawn(output_task(
"PD2 to PD3",
@ -168,10 +151,9 @@ async fn main(spawner: Spawner) {
}
if CHECK_PE0_TO_PE1 {
let out_pin = porte.pe0.into_readable_push_pull_output();
let in_pin = porte.pe1.into_floating_input();
let out_pin = out_pin.downgrade();
let in_pin = InputDynPinAsync::new(in_pin.downgrade()).unwrap();
let out_pin = Output::new(porte.pe0, PinState::Low);
let in_pin = Input::new_floating(porte.pe1);
let in_pin = InputPinAsync::new(in_pin).unwrap();
spawner
.spawn(output_task(
"PE0 to PE1",
@ -184,10 +166,9 @@ async fn main(spawner: Spawner) {
}
if CHECK_PF0_TO_PF1 {
let out_pin = portf.pf0.into_readable_push_pull_output();
let in_pin = portf.pf1.into_floating_input();
let out_pin = out_pin.downgrade();
let in_pin = InputDynPinAsync::new(in_pin.downgrade()).unwrap();
let out_pin = Output::new(portf.pf0, PinState::Low);
let in_pin = Input::new_floating(portf.pf1);
let in_pin = InputPinAsync::new(in_pin).unwrap();
spawner
.spawn(output_task(
"PF0 to PF1",
@ -298,7 +279,7 @@ async fn check_pin_to_pin_async_ops(
#[embassy_executor::task(pool_size = 8)]
async fn output_task(
ctx: &'static str,
mut out: DynPin,
mut out: Output,
receiver: Receiver<'static, ThreadModeRawMutex, GpioCmd, 3>,
) {
loop {
@ -307,25 +288,25 @@ async fn output_task(
match next_cmd.cmd_type {
GpioCmdType::SetHigh => {
defmt::info!("{}: Set output high", ctx);
out.set_high().unwrap();
out.set_high();
}
GpioCmdType::SetLow => {
defmt::info!("{}: Set output low", ctx);
out.set_low().unwrap();
out.set_low();
}
GpioCmdType::RisingEdge => {
defmt::info!("{}: Rising edge", ctx);
if !out.is_low().unwrap() {
out.set_low().unwrap();
if !out.is_set_low() {
out.set_low();
}
out.set_high().unwrap();
out.set_high();
}
GpioCmdType::FallingEdge => {
defmt::info!("{}: Falling edge", ctx);
if !out.is_high().unwrap() {
out.set_high().unwrap();
if !out.is_set_high() {
out.set_high();
}
out.set_low().unwrap();
out.set_low();
}
GpioCmdType::CloseTask => {
defmt::info!("{}: Closing task", ctx);

View File

@ -27,8 +27,10 @@ use embedded_io::Write;
use embedded_io_async::Read;
use heapless::spsc::{Producer, Queue};
use va416xx_hal::{
gpio::PinsG,
clock::ClockConfigurator,
gpio::{Output, PinState},
pac::{self, interrupt},
pins::PinsG,
prelude::*,
time::Hertz,
uart::{
@ -46,34 +48,22 @@ static PRODUCER_UART_A: Mutex<RefCell<Option<Producer<u8, 256>>>> = Mutex::new(R
async fn main(_spawner: Spawner) {
defmt::println!("-- VA108xx Async UART RX Demo --");
let mut dp = pac::Peripherals::take().unwrap();
let dp = pac::Peripherals::take().unwrap();
// Initialize the systick interrupt & obtain the token to prove that we did
// Use the external clock connected to XTAL_N.
let clocks = dp
.clkgen
.constrain()
let clocks = ClockConfigurator::new(dp.clkgen)
.xtal_n_clk_with_src_freq(Hertz::from_raw(EXTCLK_FREQ))
.freeze(&mut dp.sysconfig)
.freeze()
.unwrap();
// Safety: Only called once here.
unsafe {
va416xx_embassy::init(
&mut dp.sysconfig,
&dp.irq_router,
dp.tim15,
dp.tim14,
&clocks,
);
}
va416xx_embassy::init(dp.tim15, dp.tim14, &clocks);
let portg = PinsG::new(&mut dp.sysconfig, dp.portg);
let mut led = portg.pg5.into_readable_push_pull_output();
let portg = PinsG::new(dp.portg);
let mut led = Output::new(portg.pg5, PinState::Low);
let tx = portg.pg0.into_funsel_1();
let rx = portg.pg1.into_funsel_1();
let uarta = uart::Uart::new(&mut dp.sysconfig, dp.uart0, (tx, rx), 115200.Hz(), &clocks);
let uarta =
uart::Uart::new(dp.uart0, portg.pg0, portg.pg1, &clocks, 115200.Hz().into()).unwrap();
let (mut tx_uart_a, rx_uart_a) = uarta.split();
let (prod_uart_a, cons_uart_a) = QUEUE_UART_A.take().split();

View File

@ -21,8 +21,10 @@ use embassy_executor::Spawner;
use embassy_time::{Duration, Instant, Ticker};
use embedded_io_async::Write;
use va416xx_hal::{
gpio::PinsG,
clock::ClockConfigurator,
gpio::{Output, PinState},
pac::{self, interrupt},
pins::PinsG,
prelude::*,
time::Hertz,
uart::{
@ -44,34 +46,22 @@ const STR_LIST: &[&str] = &[
async fn main(_spawner: Spawner) {
defmt::println!("-- VA108xx Async UART TX Demo --");
let mut dp = pac::Peripherals::take().unwrap();
let dp = pac::Peripherals::take().unwrap();
// Initialize the systick interrupt & obtain the token to prove that we did
// Use the external clock connected to XTAL_N.
let clocks = dp
.clkgen
.constrain()
let clocks = ClockConfigurator::new(dp.clkgen)
.xtal_n_clk_with_src_freq(Hertz::from_raw(EXTCLK_FREQ))
.freeze(&mut dp.sysconfig)
.freeze()
.unwrap();
// Safety: Only called once here.
unsafe {
va416xx_embassy::init(
&mut dp.sysconfig,
&dp.irq_router,
dp.tim15,
dp.tim14,
&clocks,
);
}
va416xx_embassy::init(dp.tim15, dp.tim14, &clocks);
let portg = PinsG::new(&mut dp.sysconfig, dp.portg);
let mut led = portg.pg5.into_readable_push_pull_output();
let pinsg = PinsG::new(dp.portg);
let mut led = Output::new(pinsg.pg5, PinState::Low);
let tx = portg.pg0.into_funsel_1();
let rx = portg.pg1.into_funsel_1();
let uarta = uart::Uart::new(&mut dp.sysconfig, dp.uart0, (tx, rx), 115200.Hz(), &clocks);
let uarta =
uart::Uart::new(dp.uart0, pinsg.pg0, pinsg.pg1, &clocks, 115200.Hz().into()).unwrap();
let (tx, _rx) = uarta.split();
let mut async_tx = TxAsync::new(tx);
let mut ticker = Ticker::every(Duration::from_secs(1));

View File

@ -27,15 +27,15 @@ use ringbuf::{
StaticRb,
};
use va416xx_hal::{
gpio::{OutputReadablePushPull, Pin, PinsG, PG5},
clock::ClockConfigurator,
gpio::{Output, PinState},
pac::{self, interrupt},
prelude::*,
pins::PinsG,
time::Hertz,
uart,
};
pub type SharedUart =
Mutex<CriticalSectionRawMutex, RefCell<Option<uart::RxWithInterrupt<pac::Uart0>>>>;
pub type SharedUart = Mutex<CriticalSectionRawMutex, RefCell<Option<uart::RxWithInterrupt>>>;
static RX: SharedUart = Mutex::new(RefCell::new(None));
const BAUDRATE: u32 = 115200;
@ -54,39 +54,27 @@ static RINGBUF: SharedRingBuf = Mutex::new(RefCell::new(None));
async fn main(spawner: Spawner) {
defmt::println!("VA416xx UART-Embassy Example");
let mut dp = pac::Peripherals::take().unwrap();
let dp = pac::Peripherals::take().unwrap();
// Initialize the systick interrupt & obtain the token to prove that we did
// Use the external clock connected to XTAL_N.
let clocks = dp
.clkgen
.constrain()
let clocks = ClockConfigurator::new(dp.clkgen)
.xtal_n_clk_with_src_freq(Hertz::from_raw(EXTCLK_FREQ))
.freeze(&mut dp.sysconfig)
.freeze()
.unwrap();
// Safety: Only called once here.
unsafe {
va416xx_embassy::init(
&mut dp.sysconfig,
&dp.irq_router,
dp.tim15,
dp.tim14,
&clocks,
)
};
va416xx_embassy::init(dp.tim15, dp.tim14, &clocks);
let portg = PinsG::new(&mut dp.sysconfig, dp.portg);
let tx = portg.pg0.into_funsel_1();
let rx = portg.pg1.into_funsel_1();
let portg = PinsG::new(dp.portg);
let uart0 = uart::Uart::new(
&mut dp.sysconfig,
dp.uart0,
(tx, rx),
Hertz::from_raw(BAUDRATE),
portg.pg0,
portg.pg1,
&clocks,
);
Hertz::from_raw(BAUDRATE).into(),
)
.unwrap();
let (mut tx, rx) = uart0.split();
let mut rx = rx.into_rx_with_irq();
rx.start();
@ -97,7 +85,7 @@ async fn main(spawner: Spawner) {
static_rb.borrow_mut().replace(StaticRb::default());
});
let led = portg.pg5.into_readable_push_pull_output();
let led = Output::new(portg.pg5, PinState::Low);
let mut ticker = Ticker::every(Duration::from_millis(50));
let mut processing_buf: [u8; RING_BUF_SIZE] = [0; RING_BUF_SIZE];
let mut read_bytes = 0;
@ -117,7 +105,7 @@ async fn main(spawner: Spawner) {
}
#[embassy_executor::task]
async fn blinky(mut led: Pin<PG5, OutputReadablePushPull>) {
async fn blinky(mut led: Output) {
let mut ticker = Ticker::every(Duration::from_millis(500));
loop {
led.toggle();

View File

@ -8,7 +8,13 @@ use defmt_rtt as _;
use embassy_example::EXTCLK_FREQ;
use embassy_executor::Spawner;
use embassy_time::{Duration, Instant, Ticker};
use va416xx_hal::{gpio::PinsG, pac, prelude::*, time::Hertz};
use va416xx_hal::{
clock::ClockConfigurator,
gpio::{Output, PinState},
pac,
pins::PinsG,
time::Hertz,
};
cfg_if::cfg_if! {
if #[cfg(feature = "custom-irqs")] {
@ -22,40 +28,32 @@ cfg_if::cfg_if! {
async fn main(_spawner: Spawner) {
defmt::println!("VA416xx Embassy Demo");
let mut dp = pac::Peripherals::take().unwrap();
let dp = pac::Peripherals::take().unwrap();
// Initialize the systick interrupt & obtain the token to prove that we did
// Use the external clock connected to XTAL_N.
let clocks = dp
.clkgen
.constrain()
let clocks = ClockConfigurator::new(dp.clkgen)
.xtal_n_clk_with_src_freq(Hertz::from_raw(EXTCLK_FREQ))
.freeze(&mut dp.sysconfig)
.freeze()
.unwrap();
// Safety: Only called once here.
unsafe {
cfg_if::cfg_if! {
if #[cfg(not(feature = "custom-irqs"))] {
va416xx_embassy::init(
&mut dp.sysconfig,
&dp.irq_router,
dp.tim15,
dp.tim14,
&clocks
);
} else {
va416xx_embassy::init(
&mut dp.sysconfig,
&dp.irq_router,
dp.tim12,
dp.tim11,
&clocks
);
}
cfg_if::cfg_if! {
if #[cfg(not(feature = "custom-irqs"))] {
va416xx_embassy::init(
dp.tim15,
dp.tim14,
&clocks
);
} else {
va416xx_embassy::init(
dp.tim12,
dp.tim11,
&clocks
);
}
}
let portg = PinsG::new(&mut dp.sysconfig, dp.portg);
let mut led = portg.pg5.into_readable_push_pull_output();
let pinsg = PinsG::new(dp.portg);
let mut led = Output::new(pinsg.pg5, PinState::Low);
let mut ticker = Ticker::every(Duration::from_secs(1));
loop {
ticker.next().await;

View File

@ -5,14 +5,11 @@ edition = "2021"
[dependencies]
cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
cortex-m-rt = "0.7"
embedded-hal = "1"
defmt-rtt = "0.4"
defmt = "1"
panic-probe = { version = "1", features = ["defmt"] }
rtic-sync = { version = "1.3", features = ["defmt-03"] }
va416xx-hal = { version = "0.5", features = ["va41630"] }
va416xx-hal = { version = "0.5", path = "../../va416xx-hal", features = ["va41630"] }
[dependencies.rtic]
version = "2"

View File

@ -17,14 +17,15 @@ mod app {
use rtic_monotonics::systick::prelude::*;
use rtic_monotonics::Monotonic;
use va416xx_hal::{
gpio::{OutputReadablePushPull, Pin, PinsG, PG5},
clock::ClockConfigurator,
gpio::{Output, PinState},
pac,
prelude::*,
pins::PinsG,
};
#[local]
struct Local {
led: Pin<PG5, OutputReadablePushPull>,
led: Output,
}
#[shared]
@ -33,19 +34,16 @@ mod app {
rtic_monotonics::systick_monotonic!(Mono, 1_000);
#[init]
fn init(mut cx: init::Context) -> (Shared, Local) {
fn init(cx: init::Context) -> (Shared, Local) {
defmt::println!("-- Vorago RTIC example application --");
// Use the external clock connected to XTAL_N.
let clocks = cx
.device
.clkgen
.constrain()
let clocks = ClockConfigurator::new(cx.device.clkgen)
.xtal_n_clk_with_src_freq(EXTCLK_FREQ)
.freeze(&mut cx.device.sysconfig)
.freeze()
.unwrap();
Mono::start(cx.core.SYST, clocks.sysclk().raw());
let portg = PinsG::new(&mut cx.device.sysconfig, cx.device.portg);
let led = portg.pg5.into_readable_push_pull_output();
let pinsg = PinsG::new(cx.device.portg);
let led = Output::new(pinsg.pg5, PinState::Low);
blinky::spawn().ok();
(Shared {}, Local { led })
}

View File

@ -17,20 +17,12 @@ embedded-io = "0.6"
panic-halt = "1"
accelerometer = "0.12"
va416xx-hal = { version = "0.5", features = ["va41630", "defmt"] }
va416xx-hal = { version = "0.5", path = "../../va416xx-hal", features = ["va41630", "defmt"] }
[dependencies.vorago-peb1]
path = "../../vorago-peb1"
optional = true
[dependencies.rtic]
version = "2"
features = ["thumbv7-backend"]
[dependencies.rtic-monotonics]
version = "2"
features = ["cortex-m-systick"]
[features]
default = ["va41630"]
va41630 = ["va416xx-hal/va41630", "has-adc-dac"]

View File

@ -12,8 +12,8 @@ use embedded_hal::delay::DelayNs;
use simple_examples::peb1;
use va416xx_hal::{
adc::{Adc, ChannelSelect, ChannelValue, MultiChannelSelect},
clock::ClockConfigurator,
pac,
prelude::*,
timer::CountdownTimer,
};
@ -24,17 +24,15 @@ const ENABLE_BUF_PRINTOUT: bool = false;
fn main() -> ! {
defmt::println!("VA416xx ADC example");
let mut dp = pac::Peripherals::take().unwrap();
let dp = pac::Peripherals::take().unwrap();
// Use the external clock connected to XTAL_N.
let clocks = dp
.clkgen
.constrain()
let clocks = ClockConfigurator::new(dp.clkgen)
.xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ)
.freeze(&mut dp.sysconfig)
.freeze()
.unwrap();
let adc = Adc::new_with_channel_tag(&mut dp.sysconfig, dp.adc, &clocks);
let mut delay_provider = CountdownTimer::new(&mut dp.sysconfig, dp.tim0, &clocks);
let adc = Adc::new_with_channel_tag(dp.adc, &clocks);
let mut delay = CountdownTimer::new(dp.tim0, &clocks);
let mut read_buf: [ChannelValue; 8] = [ChannelValue::default(); 8];
loop {
let single_value = adc
@ -70,6 +68,6 @@ fn main() -> ! {
assert_eq!(read_buf[0].channel(), ChannelSelect::AnIn0);
assert_eq!(read_buf[1].channel(), ChannelSelect::AnIn2);
assert_eq!(read_buf[2].channel(), ChannelSelect::TempSensor);
delay_provider.delay_ms(500);
delay.delay_ms(500);
}
}

View File

@ -8,15 +8,19 @@ use panic_probe as _;
use defmt_rtt as _;
use cortex_m_rt::entry;
use va416xx_hal::{gpio::PinsG, pac};
use va416xx_hal::{
gpio::{Output, PinState},
pac,
pins::PinsG,
};
#[entry]
fn main() -> ! {
defmt::println!("VA416xx HAL blinky example");
let mut dp = pac::Peripherals::take().unwrap();
let portg = PinsG::new(&mut dp.sysconfig, dp.portg);
let mut led = portg.pg5.into_readable_push_pull_output();
let dp = pac::Peripherals::take().unwrap();
let portg = PinsG::new(dp.portg);
let mut led = Output::new(portg.pg5, PinState::Low);
loop {
cortex_m::asm::delay(2_000_000);
led.toggle();

View File

@ -10,7 +10,7 @@ use defmt_rtt as _;
use cortex_m_rt::entry;
use embedded_hal::delay::DelayNs;
use simple_examples::peb1;
use va416xx_hal::{adc::Adc, dac::Dac, pac, prelude::*, timer::CountdownTimer};
use va416xx_hal::{adc::Adc, clock::ClockConfigurator, dac::Dac, pac, timer::CountdownTimer};
const DAC_INCREMENT: u16 = 256;
@ -30,18 +30,15 @@ const APP_MODE: AppMode = AppMode::DacAndAdc;
fn main() -> ! {
defmt::println!("VA416xx DAC/ADC example");
let mut dp = pac::Peripherals::take().unwrap();
let dp = pac::Peripherals::take().unwrap();
// Use the external clock connected to XTAL_N.
let clocks = dp
.clkgen
.constrain()
let clocks = ClockConfigurator::new(dp.clkgen)
.xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ)
.freeze(&mut dp.sysconfig)
.freeze()
.unwrap();
let mut dac = None;
if APP_MODE == AppMode::DacOnly || APP_MODE == AppMode::DacAndAdc {
dac = Some(Dac::new(
&mut dp.sysconfig,
dp.dac0,
va416xx_hal::dac::DacSettling::Apb2Times100,
&clocks,
@ -49,12 +46,12 @@ fn main() -> ! {
}
let mut adc = None;
if APP_MODE == AppMode::AdcOnly || APP_MODE == AppMode::DacAndAdc {
adc = Some(Adc::new(&mut dp.sysconfig, dp.adc, &clocks));
adc = Some(Adc::new(dp.adc, &clocks));
}
let mut delay_provider = CountdownTimer::new(&mut dp.sysconfig, dp.tim0, &clocks);
let mut delay_provider = CountdownTimer::new(dp.tim0, &clocks);
let mut current_val = 0;
loop {
if let Some(dac) = &dac {
if let Some(dac) = &mut dac {
defmt::info!("loading DAC with value {}", current_val);
dac.load_and_trigger_manually(current_val)
.expect("loading DAC value failed");

View File

@ -6,6 +6,7 @@
use panic_probe as _;
// Import logger.
use defmt_rtt as _;
use va416xx_hal::clock::ClockConfigurator;
use core::cell::Cell;
@ -15,11 +16,8 @@ use embedded_hal::delay::DelayNs;
use simple_examples::peb1;
use va416xx_hal::dma::{Dma, DmaCfg, DmaChannel, DmaCtrlBlock};
use va416xx_hal::irq_router::enable_and_init_irq_router;
use va416xx_hal::pac::{self, interrupt};
use va416xx_hal::timer::CountdownTimer;
use va416xx_hal::{
pac::{self, interrupt},
prelude::*,
};
static DMA_DONE_FLAG: Mutex<Cell<bool>> = Mutex::new(Cell::new(false));
static DMA_ACTIVE_FLAG: Mutex<Cell<bool>> = Mutex::new(Cell::new(false));
@ -40,26 +38,23 @@ static mut DMA_DEST_BUF: [u16; 36] = [0; 36];
fn main() -> ! {
defmt::println!("VA416xx DMA example");
let mut dp = pac::Peripherals::take().unwrap();
let dp = pac::Peripherals::take().unwrap();
// Use the external clock connected to XTAL_N.
let clocks = dp
.clkgen
.constrain()
let clocks = ClockConfigurator::new(dp.clkgen)
.xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ)
.freeze(&mut dp.sysconfig)
.freeze()
.unwrap();
enable_and_init_irq_router(&mut dp.sysconfig, &dp.irq_router);
enable_and_init_irq_router();
// Safety: The DMA control block has an alignment rule of 128 and we constructed it directly
// statically.
let dma = Dma::new(
&mut dp.sysconfig,
dp.dma,
DmaCfg::default(),
core::ptr::addr_of_mut!(DMA_CTRL_BLOCK),
)
.expect("error creating DMA");
let (mut dma0, _, _, _) = dma.split();
let mut delay_ms = CountdownTimer::new(&mut dp.sysconfig, dp.tim0, &clocks);
let mut delay_ms = CountdownTimer::new(dp.tim0, &clocks);
let mut src_buf_8_bit: [u8; 65] = [0; 65];
let mut dest_buf_8_bit: [u8; 65] = [0; 65];
let mut src_buf_32_bit: [u32; 17] = [0; 17];
@ -90,7 +85,7 @@ fn transfer_example_8_bit(
src_buf: &mut [u8; 65],
dest_buf: &mut [u8; 65],
dma0: &mut DmaChannel,
delay_ms: &mut CountdownTimer<pac::Tim0>,
delay: &mut CountdownTimer,
) {
(0..64).for_each(|i| {
src_buf[i] = i as u8;
@ -132,7 +127,7 @@ fn transfer_example_8_bit(
defmt::info!("8-bit transfer done");
break;
}
delay_ms.delay_ms(1);
delay.delay_ms(1);
}
(0..64).for_each(|i| {
assert_eq!(dest_buf[i], i as u8);
@ -142,7 +137,7 @@ fn transfer_example_8_bit(
dest_buf.fill(0);
}
fn transfer_example_16_bit(dma0: &mut DmaChannel, delay_ms: &mut CountdownTimer<pac::Tim0>) {
fn transfer_example_16_bit(dma0: &mut DmaChannel, delay_ms: &mut CountdownTimer) {
let dest_buf_ref = unsafe { &mut *core::ptr::addr_of_mut!(DMA_DEST_BUF[0..33]) };
unsafe {
// Set values scaled from 0 to 65535 to verify this is really a 16-bit transfer.
@ -207,7 +202,7 @@ fn transfer_example_32_bit(
src_buf: &mut [u32; 17],
dest_buf: &mut [u32; 17],
dma0: &mut DmaChannel,
delay_ms: &mut CountdownTimer<pac::Tim0>,
delay_ms: &mut CountdownTimer,
) {
// Set values scaled from 0 to 65535 to verify this is really a 16-bit transfer.
(0..16).for_each(|i| {

View File

@ -12,10 +12,11 @@ use cortex_m_rt::entry;
use embedded_hal::delay::DelayNs;
use simple_examples::peb1;
use va416xx_hal::{
clock::ClockConfigurator,
i2c,
pac::{self},
prelude::*,
pwm::CountdownTimer,
timer::CountdownTimer,
};
use vorago_peb1::lis2dh12::{self, detect_i2c_addr, FullScale, Odr};
@ -31,21 +32,18 @@ fn main() -> ! {
let mut dp = pac::Peripherals::take().unwrap();
defmt::println!("-- Vorago PEB1 accelerometer example --");
// Use the external clock connected to XTAL_N.
let clocks = dp
.clkgen
.constrain()
let clocks = ClockConfigurator::new(dp.clkgen)
.xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ)
.freeze(&mut dp.sysconfig)
.freeze()
.unwrap();
let mut i2c_master = i2c::I2cMaster::new(
dp.i2c0,
&mut dp.sysconfig,
i2c::MasterConfig::default(),
&clocks,
i2c::MasterConfig::default(),
i2c::I2cSpeed::Regular100khz,
)
.expect("creating I2C master failed");
let mut delay_provider = CountdownTimer::new(&mut dp.sysconfig, dp.tim1, &clocks);
let mut delay_provider = CountdownTimer::new(dp.tim1, &clocks);
// Detect the I2C address of the accelerometer by scanning all possible values.
let slave_addr = detect_i2c_addr(&mut i2c_master).expect("detecting I2C address failed");
// Create the accelerometer driver using the PEB1 BSP.
@ -53,7 +51,7 @@ fn main() -> ! {
.expect("creating accelerometer driver failed");
let device_id = accelerometer.get_device_id().unwrap();
accelerometer
.set_mode(lis2dh12::reg::Mode::Normal)
.set_mode(lis2dh12::Mode::Normal)
.expect("setting mode failed");
accelerometer
.set_odr(Odr::Hz100)
@ -65,7 +63,7 @@ fn main() -> ! {
accelerometer
.enable_temp(true)
.expect("enabling temperature sensor failed");
rprintln!("Device ID: 0x{:02X}", device_id);
defmt::info!("Device ID: 0x{:02X}", device_id);
// Start reading the accelerometer periodically.
loop {
let temperature = accelerometer
@ -76,13 +74,25 @@ fn main() -> ! {
let value = accelerometer
.accel_norm()
.expect("reading normalized accelerometer data failed");
rprintln!("Accel Norm F32x3: {:.06?} | Temp {} °C", value, temperature);
defmt::info!(
"Accel Norm F32x3 {{ x: {:05}, y: {:05}, z:{:05}}} | Temp {} °C",
value.x,
value.y,
value.z,
temperature
);
}
DisplayMode::Raw => {
let value_raw = accelerometer
.accel_raw()
.expect("reading raw accelerometer data failed");
rprintln!("Accel Raw F32x3: {:?} | Temp {} °C", value_raw, temperature);
defmt::info!(
"Accel Raw I32x3 {{ x: {:05}, y: {:05}, z:{:05}}} | Temp {} °C",
value_raw.x,
value_raw.y,
value_raw.z,
temperature
);
}
}
delay_provider.delay_ms(100);

View File

@ -1,4 +1,6 @@
//! Simple PWM example
//!
//! Outputs a PWM waveform on pin PG2.
#![no_main]
#![no_std]
@ -10,41 +12,34 @@ use panic_probe as _;
use defmt_rtt as _;
use simple_examples::peb1;
use va416xx_hal::{
gpio::PinsA,
clock::ClockConfigurator,
pac,
pins::PinsG,
prelude::*,
pwm::{self, get_duty_from_percent, PwmA, PwmB, ReducedPwmPin},
pwm::{get_duty_from_percent, PwmA, PwmB, PwmPin},
timer::CountdownTimer,
};
#[entry]
fn main() -> ! {
defmt::println!("-- VA108xx PWM example application--");
let mut dp = pac::Peripherals::take().unwrap();
let dp = pac::Peripherals::take().unwrap();
// Use the external clock connected to XTAL_N.
let clocks = dp
.clkgen
.constrain()
let clocks = ClockConfigurator::new(dp.clkgen)
.xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ)
.freeze(&mut dp.sysconfig)
.freeze()
.unwrap();
let pinsa = PinsA::new(&mut dp.sysconfig, dp.porta);
let mut pwm = pwm::PwmPin::new(
(pinsa.pa3.into_funsel_1(), dp.tim3),
&mut dp.sysconfig,
&clocks,
10.Hz(),
);
let mut delay_timer = CountdownTimer::new(&mut dp.sysconfig, dp.tim0, &clocks);
let pinsg = PinsG::new(dp.portg);
let mut pwm = PwmPin::new(pinsg.pg2, dp.tim9, &clocks, 10.Hz()).unwrap();
let mut delay_timer = CountdownTimer::new(dp.tim0, &clocks);
let mut current_duty_cycle = 0.0;
pwm.set_duty_cycle(get_duty_from_percent(current_duty_cycle))
.unwrap();
pwm.enable();
// Delete type information, increased code readibility for the rest of the code
let mut reduced_pin = ReducedPwmPin::from(pwm);
loop {
let mut counter = 0;
// Increase duty cycle continuously
@ -56,8 +51,7 @@ fn main() -> ! {
defmt::info!("current duty cycle: {}", current_duty_cycle);
}
reduced_pin
.set_duty_cycle(get_duty_from_percent(current_duty_cycle))
pwm.set_duty_cycle(get_duty_from_percent(current_duty_cycle))
.unwrap();
}
@ -66,7 +60,7 @@ fn main() -> ! {
current_duty_cycle = 0.0;
let mut upper_limit = 1.0;
let mut lower_limit = 0.0;
let mut pwmb: ReducedPwmPin<PwmB> = ReducedPwmPin::from(reduced_pin);
let mut pwmb: PwmPin<PwmB> = PwmPin::from(pwm);
pwmb.set_pwmb_lower_limit(get_duty_from_percent(lower_limit));
pwmb.set_pwmb_upper_limit(get_duty_from_percent(upper_limit));
while lower_limit < 0.5 {
@ -78,6 +72,6 @@ fn main() -> ! {
defmt::info!("Lower limit: {}", pwmb.pwmb_lower_limit());
defmt::info!("Upper limit: {}", pwmb.pwmb_upper_limit());
}
reduced_pin = ReducedPwmPin::<PwmA>::from(pwmb);
pwm = PwmPin::<PwmA>::from(pwmb);
}
}

View File

@ -1,38 +0,0 @@
// Code to test RTT logger functionality.
#![no_main]
#![no_std]
// Import panic provider.
use panic_probe as _;
// Import logger.
use defmt_rtt as _;
use cortex_m_rt::entry;
use va416xx_hal::pac;
// Mask for the LED
const LED_PG5: u32 = 1 << 5;
#[entry]
fn main() -> ! {
defmt::println!("VA416xx RTT Demo");
let dp = pac::Peripherals::take().unwrap();
// Enable all peripheral clocks
dp.sysconfig
.peripheral_clk_enable()
.modify(|_, w| unsafe { w.bits(0xffffffff) });
dp.portg.dir().modify(|_, w| unsafe { w.bits(LED_PG5) });
dp.portg
.datamask()
.modify(|_, w| unsafe { w.bits(LED_PG5) });
let mut counter = 0;
loop {
defmt::info!("{}: Hello, world!", counter);
// Still toggle LED. If there are issues with the RTT log, the LED
// blinking ensures that the application is actually running.
dp.portg.togout().write(|w| unsafe { w.bits(LED_PG5) });
counter += 1;
cortex_m::asm::delay(10_000_000);
}
}

View File

@ -3,6 +3,7 @@
//! If you do not use the loopback mode, MOSI and MISO need to be tied together on the board.
#![no_main]
#![no_std]
use embedded_hal::delay::DelayNs;
// Import panic provider.
use panic_probe as _;
// Import logger.
@ -11,11 +12,12 @@ use defmt_rtt as _;
use cortex_m_rt::entry;
use embedded_hal::spi::{Mode, SpiBus, MODE_0};
use simple_examples::peb1;
use va416xx_hal::clock::ClockConfigurator;
use va416xx_hal::spi::{Spi, SpiClkConfig};
use va416xx_hal::timer::CountdownTimer;
use va416xx_hal::{
gpio::{PinsB, PinsC},
pac,
prelude::*,
pins::{PinsB, PinsC},
spi::SpiConfig,
time::Hertz,
};
@ -37,29 +39,20 @@ const FILL_WORD: u8 = 0x0f;
#[entry]
fn main() -> ! {
defmt::println!("-- VA108xx SPI example application--");
let cp = cortex_m::Peripherals::take().unwrap();
let mut dp = pac::Peripherals::take().unwrap();
let dp = pac::Peripherals::take().unwrap();
// Use the external clock connected to XTAL_N.
let clocks = dp
.clkgen
.constrain()
let clocks = ClockConfigurator::new(dp.clkgen)
.xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ)
.freeze(&mut dp.sysconfig)
.freeze()
.unwrap();
let mut delay_sysclk = cortex_m::delay::Delay::new(cp.SYST, clocks.apb0().raw());
let mut delay = CountdownTimer::new(dp.tim1, &clocks);
let pins_b = PinsB::new(&mut dp.sysconfig, dp.portb);
let pins_c = PinsC::new(&mut dp.sysconfig, dp.portc);
// Configure SPI0 pins.
let (sck, miso, mosi) = (
pins_b.pb15.into_funsel_1(),
pins_c.pc0.into_funsel_1(),
pins_c.pc1.into_funsel_1(),
);
let pins_b = PinsB::new(dp.portb);
let pins_c = PinsC::new(dp.portc);
let mut spi_cfg = SpiConfig::default()
.clk_cfg(
SpiClkConfig::from_clk(Hertz::from_raw(SPI_SPEED_KHZ), &clocks)
SpiClkConfig::from_clks(&clocks, Hertz::from_raw(SPI_SPEED_KHZ))
.expect("invalid target clock"),
)
.mode(SPI_MODE)
@ -68,13 +61,7 @@ fn main() -> ! {
spi_cfg = spi_cfg.loopback(true)
}
// Create SPI peripheral.
let mut spi0 = Spi::new(
&mut dp.sysconfig,
&clocks,
dp.spi0,
(sck, miso, mosi),
spi_cfg,
);
let mut spi0 = Spi::new(dp.spi0, (pins_b.pb15, pins_c.pc0, pins_c.pc1), spi_cfg).unwrap();
spi0.set_fill_word(FILL_WORD);
loop {
let tx_buf: [u8; 4] = [1, 2, 3, 0];
@ -95,6 +82,6 @@ fn main() -> ! {
spi0.transfer(&mut rx_buf, &tx_buf)
.expect("SPI transfer failed");
assert_eq!(rx_buf, [1, 2, 3, 0]);
delay_sysclk.delay_ms(500);
delay.delay_ms(500);
}
}

View File

@ -6,16 +6,16 @@ use panic_probe as _;
// Import logger.
use defmt_rtt as _;
use core::cell::Cell;
use core::sync::atomic::{AtomicU32, Ordering};
use cortex_m::asm;
use cortex_m_rt::entry;
use critical_section::Mutex;
use simple_examples::peb1;
use va416xx_hal::{
clock::ClockConfigurator,
irq_router::enable_and_init_irq_router,
pac::{self, interrupt},
prelude::*,
timer::{default_ms_irq_handler, set_up_ms_tick, CountdownTimer, MS_COUNTER},
timer::CountdownTimer,
};
#[allow(dead_code)]
@ -24,32 +24,33 @@ enum LibType {
Hal,
}
static SEC_COUNTER: Mutex<Cell<u32>> = Mutex::new(Cell::new(0));
static MS_COUNTER: AtomicU32 = AtomicU32::new(0);
static SEC_COUNTER: AtomicU32 = AtomicU32::new(0);
#[entry]
fn main() -> ! {
let mut dp = pac::Peripherals::take().unwrap();
let dp = pac::Peripherals::take().unwrap();
let mut last_ms = 0;
defmt::println!("-- Vorago system ticks using timers --");
// Use the external clock connected to XTAL_N.
let clocks = dp
.clkgen
.constrain()
let clocks = ClockConfigurator::new(dp.clkgen)
.xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ)
.freeze(&mut dp.sysconfig)
.freeze()
.unwrap();
enable_and_init_irq_router(&mut dp.sysconfig, &dp.irq_router);
let _ = set_up_ms_tick(&mut dp.sysconfig, dp.tim0, &clocks);
let mut second_timer = CountdownTimer::new(&mut dp.sysconfig, dp.tim1, &clocks);
second_timer.listen();
enable_and_init_irq_router();
let mut ms_timer = CountdownTimer::new(dp.tim0, &clocks);
ms_timer.enable_interrupt(true);
ms_timer.start(1.Hz());
let mut second_timer = CountdownTimer::new(dp.tim1, &clocks);
second_timer.enable_interrupt(true);
second_timer.start(1.Hz());
loop {
let current_ms = critical_section::with(|cs| MS_COUNTER.borrow(cs).get());
let current_ms = MS_COUNTER.load(Ordering::Relaxed);
if current_ms >= last_ms + 1000 {
// To prevent drift.
last_ms += 1000;
defmt::info!("MS counter: {}", current_ms);
let second = critical_section::with(|cs| SEC_COUNTER.borrow(cs).get());
let second = SEC_COUNTER.load(Ordering::Relaxed);
defmt::info!("Second counter: {}", second);
}
asm::delay(1000);
@ -59,15 +60,11 @@ fn main() -> ! {
#[interrupt]
#[allow(non_snake_case)]
fn TIM0() {
default_ms_irq_handler()
MS_COUNTER.fetch_add(1, Ordering::Relaxed);
}
#[interrupt]
#[allow(non_snake_case)]
fn TIM1() {
critical_section::with(|cs| {
let mut sec = SEC_COUNTER.borrow(cs).get();
sec += 1;
SEC_COUNTER.borrow(cs).set(sec);
});
SEC_COUNTER.fetch_add(1, Ordering::Relaxed);
}

View File

@ -11,35 +11,33 @@ use cortex_m_rt::entry;
use embedded_hal_nb::serial::Read;
use embedded_io::Write;
use simple_examples::peb1;
use va416xx_hal::clock::ClkgenExt;
use va416xx_hal::clock::ClockConfigurator;
use va416xx_hal::pins::PinsG;
use va416xx_hal::time::Hertz;
use va416xx_hal::{gpio::PinsG, pac, uart};
use va416xx_hal::{pac, uart};
#[entry]
fn main() -> ! {
defmt::println!("-- VA416xx UART example application--");
let mut dp = pac::Peripherals::take().unwrap();
let dp = pac::Peripherals::take().unwrap();
// Use the external clock connected to XTAL_N.
let clocks = dp
.clkgen
.constrain()
let clocks = ClockConfigurator::new(dp.clkgen)
.xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ)
.freeze(&mut dp.sysconfig)
.freeze()
.unwrap();
let gpiob = PinsG::new(&mut dp.sysconfig, dp.portg);
let tx = gpiob.pg0.into_funsel_1();
let rx = gpiob.pg1.into_funsel_1();
let gpiog = PinsG::new(dp.portg);
let uart0 = uart::Uart::new(
&mut dp.sysconfig,
dp.uart0,
(tx, rx),
Hertz::from_raw(115200),
gpiog.pg0,
gpiog.pg1,
&clocks,
);
Hertz::from_raw(115200).into(),
)
.unwrap();
let (mut tx, mut rx) = uart0.split();
writeln!(tx, "Hello World\n\r").unwrap();
loop {

View File

@ -5,17 +5,16 @@
use panic_probe as _;
// Import logger.
use defmt_rtt as _;
use va416xx_hal::clock::ClockConfigurator;
use core::cell::Cell;
use core::sync::atomic::{AtomicU32, Ordering};
use cortex_m_rt::entry;
use critical_section::Mutex;
use simple_examples::peb1;
use va416xx_hal::irq_router::enable_and_init_irq_router;
use va416xx_hal::pac::{self, interrupt};
use va416xx_hal::prelude::*;
use va416xx_hal::wdt::Wdt;
static WDT_INTRPT_COUNT: Mutex<Cell<u32>> = Mutex::new(Cell::new(0));
static WDT_INTRPT_COUNT: AtomicU32 = AtomicU32::new(0);
#[derive(Debug, PartialEq, Eq)]
#[allow(dead_code)]
@ -33,33 +32,37 @@ const WDT_ROLLOVER_MS: u32 = 100;
fn main() -> ! {
defmt::println!("-- VA416xx WDT example application--");
let cp = cortex_m::Peripherals::take().unwrap();
let mut dp = pac::Peripherals::take().unwrap();
let dp = pac::Peripherals::take().unwrap();
// Use the external clock connected to XTAL_N.
let clocks = dp
.clkgen
.constrain()
let clocks = ClockConfigurator::new(dp.clkgen)
.xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ)
.freeze(&mut dp.sysconfig)
.freeze()
.unwrap();
enable_and_init_irq_router(&mut dp.sysconfig, &dp.irq_router);
let mut delay_sysclk = cortex_m::delay::Delay::new(cp.SYST, clocks.apb0().raw());
enable_and_init_irq_router();
let mut delay = cortex_m::delay::Delay::new(cp.SYST, clocks.apb0().raw());
let mut last_interrupt_counter = 0;
let mut wdt_ctrl = Wdt::start(&mut dp.sysconfig, dp.watch_dog, &clocks, WDT_ROLLOVER_MS);
let mut wdt_ctrl = Wdt::start(dp.watch_dog, &clocks, WDT_ROLLOVER_MS);
wdt_ctrl.enable_reset();
let log_divisor = 25;
let mut counter: u32 = 0;
loop {
counter = counter.wrapping_add(1);
if counter % log_divisor == 0 {
defmt::info!("wdt example main loop alive");
}
if TEST_MODE != TestMode::AllowReset {
wdt_ctrl.feed();
}
let interrupt_counter = critical_section::with(|cs| WDT_INTRPT_COUNT.borrow(cs).get());
let interrupt_counter = WDT_INTRPT_COUNT.load(Ordering::Relaxed);
if interrupt_counter > last_interrupt_counter {
defmt::info!("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),
TestMode::FedByMain => delay.delay_ms(WDT_ROLLOVER_MS / 5),
TestMode::FedByIrq => delay.delay_ms(WDT_ROLLOVER_MS),
_ => (),
}
}
@ -68,11 +71,7 @@ fn main() -> ! {
#[interrupt]
#[allow(non_snake_case)]
fn WATCHDOG() {
critical_section::with(|cs| {
WDT_INTRPT_COUNT
.borrow(cs)
.set(WDT_INTRPT_COUNT.borrow(cs).get() + 1);
});
WDT_INTRPT_COUNT.fetch_add(1, Ordering::Relaxed);
let wdt = unsafe { pac::WatchDog::steal() };
// Clear interrupt.
if TEST_MODE != TestMode::AllowReset {

View File

@ -5,16 +5,10 @@ edition = "2021"
[dependencies]
cortex-m = "0.7"
cortex-m-rt = "0.7"
embedded-hal = "1"
embedded-hal-nb = "1"
embedded-io = "0.6"
defmt-rtt = "0.4"
defmt = "1"
panic-probe = { version = "1", features = ["defmt"] }
log = "0.4"
crc = "3"
rtic-sync = "1"
static_cell = "2"
satrs = { version = "0.3.0-alpha.0", default-features = false }
ringbuf = { version = "0.4", default-features = false }
@ -22,7 +16,7 @@ once_cell = { version = "1", default-features = false, features = ["critical-sec
spacepackets = { version = "0.13", default-features = false, features = ["defmt"] }
cobs = { version = "0.3", default-features = false }
va416xx-hal = { version = "0.5", features = ["va41630", "defmt"] }
va416xx-hal = { version = "0.5", features = ["va41630", "defmt"], path = "../va416xx-hal" }
rtic = { version = "2", features = ["thumbv7-backend"] }
rtic-monotonics = { version = "2", features = ["cortex-m-systick"] }

View File

@ -99,7 +99,7 @@ class ImageLoader:
)
self.verificator.add_tc(action_tc)
self.com_if.send(bytes(action_tc.pack()))
self.await_for_command_copletion("boot image selection command")
self.await_for_command_completion("boot image selection command")
def handle_ping_cmd(self):
_LOGGER.info("Sending ping command")
@ -112,7 +112,7 @@ class ImageLoader:
)
self.verificator.add_tc(ping_tc)
self.com_if.send(bytes(ping_tc.pack()))
self.await_for_command_copletion("ping command")
self.await_for_command_completion("ping command")
def handle_corruption_cmd(self, target: Target):
if target == Target.BOOTLOADER:
@ -134,11 +134,11 @@ class ImageLoader:
),
)
def await_for_command_copletion(self, context: str):
def await_for_command_completion(self, context: str):
done = False
now = time.time()
while time.time() - now < 2.0:
if not self.com_if.data_available():
if self.com_if.data_available() == 0:
time.sleep(0.2)
continue
for reply in self.com_if.receive():

View File

@ -32,6 +32,7 @@ const MAX_TM_FRAME_SIZE: usize = cobs::max_encoding_length(MAX_TM_SIZE);
const UART_BAUDRATE: u32 = 115200;
const BOOT_NVM_MEMORY_ID: u8 = 1;
const RX_DEBUGGING: bool = false;
const TX_DEBUGGING: bool = false;
pub enum ActionId {
CorruptImageA = 128,
@ -105,14 +106,14 @@ mod app {
use spacepackets::ecss::{
tc::PusTcReader, tm::PusTmCreator, EcssEnumU8, PusPacket, WritablePusPacket,
};
use va416xx_hal::clock::ClockConfigurator;
use va416xx_hal::irq_router::enable_and_init_irq_router;
use va416xx_hal::uart::IrqContextTimeoutOrMaxSize;
use va416xx_hal::{
clock::ClkgenExt,
edac,
gpio::PinsG,
nvm::Nvm,
pac,
pins::PinsG,
uart::{self, Uart},
};
@ -128,8 +129,8 @@ mod app {
#[local]
struct Local {
uart_rx: uart::RxWithInterrupt<pac::Uart0>,
uart_tx: uart::Tx<pac::Uart0>,
uart_rx: uart::RxWithInterrupt,
uart_tx: uart::Tx,
rx_context: IrqContextTimeoutOrMaxSize,
rom_spi: Option<pac::Spi3>,
// We handle all TM in one task.
@ -154,28 +155,24 @@ mod app {
defmt::println!("-- Vorago flashloader --");
// Initialize the systick interrupt & obtain the token to prove that we did
// Use the external clock connected to XTAL_N.
let clocks = cx
.device
.clkgen
.constrain()
let clocks = ClockConfigurator::new(cx.device.clkgen)
.xtal_n_clk_with_src_freq(Hertz::from_raw(EXTCLK_FREQ))
.freeze(&mut cx.device.sysconfig)
.freeze()
.unwrap();
enable_and_init_irq_router(&mut cx.device.sysconfig, &cx.device.irq_router);
enable_and_init_irq_router();
setup_edac(&mut cx.device.sysconfig);
let gpiog = PinsG::new(&mut cx.device.sysconfig, cx.device.portg);
let tx = gpiog.pg0.into_funsel_1();
let rx = gpiog.pg1.into_funsel_1();
let gpiog = PinsG::new(cx.device.portg);
let uart0 = Uart::new(
&mut cx.device.sysconfig,
cx.device.uart0,
(tx, rx),
Hertz::from_raw(UART_BAUDRATE),
gpiog.pg0,
gpiog.pg1,
&clocks,
);
Hertz::from_raw(UART_BAUDRATE).into(),
)
.unwrap();
let (tx, rx) = uart0.split();
let verif_reporter = VerificationReportCreator::new(0).unwrap();
@ -259,8 +256,8 @@ mod app {
{
Ok(result) => {
if RX_DEBUGGING {
log::debug!("RX Info: {:?}", cx.local.rx_context);
log::debug!("RX Result: {:?}", result);
defmt::info!("RX Info: {:?}", cx.local.rx_context);
defmt::info!("RX Result: {:?}", result);
}
if result.complete() {
// Check frame validity (must have COBS format) and decode the frame.
@ -331,7 +328,7 @@ mod app {
continue;
}
let packet_len = packet_len.unwrap();
log::info!(target: "TC Handler", "received packet with length {}", packet_len);
defmt::info!("received packet with length {}", packet_len);
assert_eq!(
cx.local
.tc_cons
@ -378,9 +375,7 @@ mod app {
let mut corrupt_image = |base_addr: u32| {
// Safety: We only use this for NVM handling and we only do NVM
// handling here.
let mut sys_cfg = unsafe { pac::Sysconfig::steal() };
let nvm = Nvm::new(
&mut sys_cfg,
cx.local.rom_spi.take().unwrap(),
CLOCKS.get().as_ref().unwrap(),
);
@ -388,7 +383,7 @@ mod app {
nvm.read_data(base_addr + 32, &mut buf);
buf[0] += 1;
nvm.write_data(base_addr + 32, &buf);
*cx.local.rom_spi = Some(nvm.release(&mut sys_cfg));
*cx.local.rom_spi = Some(nvm.release());
let tm = cx
.local
.verif_reporter
@ -406,7 +401,7 @@ mod app {
}
}
if pus_tc.service() == PusServiceId::Test as u8 && pus_tc.subservice() == 1 {
log::info!(target: "TC Handler", "received ping TC");
defmt::info!("received ping TC");
let tm = cx
.local
.verif_reporter
@ -453,31 +448,22 @@ mod app {
return;
}
let data = &app_data[10..10 + data_len as usize];
log::info!(
target: "TC Handler",
"writing {} bytes at offset {} to NVM",
data_len,
offset
);
defmt::info!("writing {} bytes at offset {} to NVM", data_len, offset);
// Safety: We only use this for NVM handling and we only do NVM
// handling here.
let mut sys_cfg = unsafe { pac::Sysconfig::steal() };
let nvm = Nvm::new(
&mut sys_cfg,
cx.local.rom_spi.take().unwrap(),
CLOCKS.get().as_ref().unwrap(),
);
nvm.write_data(offset, data);
*cx.local.rom_spi = Some(nvm.release(&mut sys_cfg));
*cx.local.rom_spi = Some(nvm.release());
let tm = cx
.local
.verif_reporter
.completion_success(cx.local.src_data_buf, started_token, 0, 0, &[])
.expect("completion success failed");
write_and_send(&tm);
log::info!(
target: "TC Handler",
"NVM operation done");
defmt::info!("NVM operation done");
}
}
}
@ -506,9 +492,12 @@ mod app {
&mut cx.local.encoded_buf[1..],
);
cx.local.encoded_buf[send_size + 1] = 0;
if TX_DEBUGGING {
defmt::debug!("UART TX: Sending data with size {}", send_size + 2);
}
cx.local
.uart_tx
.write(&cx.local.encoded_buf[0..send_size + 2])
.write_all(&cx.local.encoded_buf[0..send_size + 2])
.unwrap();
Mono::delay(2.millis()).await;
}

View File

@ -11,17 +11,8 @@ keywords = ["no-std", "hal", "cortex-m", "vorago", "va416xx"]
categories = ["aerospace", "embedded", "no-std", "hardware-support"]
[dependencies]
critical-section = "1"
embassy-sync = "0.6"
embassy-executor = "0.7"
embassy-time-driver = "0.2"
embassy-time-queue-utils = "0.1"
portable-atomic = "1"
once_cell = { version = "1", default-features = false, features = ["critical-section"] }
va416xx-hal = { version = ">=0.4, <=0.5" }
vorago-shared-periphs = { path = "../../vorago-shared-periphs", features = ["vor4x"] }
va416xx-hal = { path = "../va416xx-hal" }
[features]
default = ["irq-tim14-tim15"]

View File

@ -21,11 +21,11 @@
//! itself by using the `irq-tim14-tim15` feature flag. This library exposes three combinations:
//!
//! - `irq-tim14-tim15`: Uses [pac::Interrupt::TIM14] for alarm and [pac::Interrupt::TIM15]
//! for timekeeper
//! for timekeeper
//! - `irq-tim13-tim14`: Uses [pac::Interrupt::TIM13] for alarm and [pac::Interrupt::TIM14]
//! for timekeeper
//! for timekeeper
//! - `irq-tim22-tim23`: Uses [pac::Interrupt::TIM22] for alarm and [pac::Interrupt::TIM23]
//! for timekeeper
//! for timekeeper
//!
//! You can disable the default features and then specify one of the features above to use the
//! documented combination of IRQs. It is also possible to specify custom IRQs by importing and
@ -38,34 +38,13 @@
//! [embassy example projects](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples/embassy)
#![no_std]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
use core::{
cell::{Cell, RefCell},
sync::atomic::{AtomicU32, Ordering},
};
use critical_section::{CriticalSection, Mutex};
use embassy_time_driver::{time_driver_impl, Driver, TICK_HZ};
use embassy_time_queue_utils::Queue;
use once_cell::sync::OnceCell;
use va416xx_hal::{
clock::Clocks,
enable_nvic_interrupt,
irq_router::enable_and_init_irq_router,
pac::{self, interrupt},
pwm::ValidTim,
timer::{
assert_tim_reset_for_two_cycles, enable_tim_clk, get_tim_raw, TimRegInterface,
TIM_IRQ_OFFSET,
},
timer::{TimMarker, TIM_IRQ_OFFSET},
};
time_driver_impl!(
static TIME_DRIVER: TimerDriver = TimerDriver {
periods: AtomicU32::new(0),
alarms: Mutex::new(AlarmState::new()),
queue: Mutex::new(RefCell::new(Queue::new())),
});
use vorago_shared_periphs::embassy::time_driver;
/// Macro to define the IRQ handlers for the time driver.
///
@ -110,289 +89,29 @@ embassy_time_driver_irqs!(timekeeper_irq = TIM14, alarm_irq = TIM13);
#[cfg(feature = "irq-tim22-tim23")]
embassy_time_driver_irqs!(timekeeper_irq = TIM23, alarm_irq = TIM22);
/// Expose the time driver so the user can specify the IRQ handlers themselves.
pub fn time_driver() -> &'static TimerDriver {
&TIME_DRIVER
}
/// Initialization method for embassy
///
/// If the interrupt handlers are provided by the library, the ID of the
/// used TIM peripherals has to match the ID of the passed timer peripherals. Currently, this
/// can only be checked at run-time, and a run-time assertion will panic on the embassy
/// initialization in case of a missmatch.
///
/// # Safety
///
/// This has to be called once at initialization time to initiate the time driver for
/// embassy.
pub unsafe fn init<
TimekeeperTim: TimRegInterface + ValidTim,
AlarmTim: TimRegInterface + ValidTim,
>(
syscfg: &mut pac::Sysconfig,
irq_router: &pac::IrqRouter,
pub fn init<TimekeeperTim: TimMarker, AlarmTim: TimMarker>(
timekeeper: TimekeeperTim,
alarm: AlarmTim,
clocks: &Clocks,
) {
#[cfg(feature = "_irqs-in-lib")]
assert_eq!(
TimekeeperTim::ID,
TimekeeperTim::ID.value(),
TIMEKEEPER_IRQ as u8 - TIM_IRQ_OFFSET as u8,
"Timekeeper TIM and IRQ missmatch"
);
#[cfg(feature = "_irqs-in-lib")]
assert_eq!(
AlarmTim::ID,
AlarmTim::ID.value(),
ALARM_IRQ as u8 - TIM_IRQ_OFFSET as u8,
"Alarm TIM and IRQ missmatch"
);
enable_and_init_irq_router(syscfg, irq_router);
TIME_DRIVER.init(syscfg, timekeeper, alarm, clocks)
}
struct AlarmState {
timestamp: Cell<u64>,
}
impl AlarmState {
const fn new() -> Self {
Self {
timestamp: Cell::new(u64::MAX),
}
}
}
unsafe impl Send for AlarmState {}
static SCALE: OnceCell<u64> = OnceCell::new();
static TIMEKEEPER_TIM: OnceCell<u8> = OnceCell::new();
static ALARM_TIM: OnceCell<u8> = OnceCell::new();
pub struct TimerDriver {
periods: AtomicU32,
/// Timestamp at which to fire alarm. u64::MAX if no alarm is scheduled.
alarms: Mutex<AlarmState>,
queue: Mutex<RefCell<Queue>>,
}
impl TimerDriver {
fn init<TimekeeperTim: TimRegInterface + ValidTim, AlarmTim: TimRegInterface + ValidTim>(
&self,
syscfg: &mut pac::Sysconfig,
timekeeper_tim: TimekeeperTim,
alarm_tim: AlarmTim,
clocks: &Clocks,
) {
if ALARM_TIM.get().is_some() || TIMEKEEPER_TIM.get().is_some() {
return;
}
ALARM_TIM.set(alarm_tim.tim_id()).ok();
TIMEKEEPER_TIM.set(timekeeper_tim.tim_id()).ok();
enable_tim_clk(syscfg, timekeeper_tim.tim_id());
assert_tim_reset_for_two_cycles(syscfg, alarm_tim.tim_id());
// Initiate scale value here. This is required to convert timer ticks back to a timestamp.
SCALE
.set((TimekeeperTim::clock(clocks).raw() / TICK_HZ as u32) as u64)
.unwrap();
let timekeeper_tim_regs = timekeeper_tim.reg_block();
timekeeper_tim_regs
.rst_value()
.write(|w| unsafe { w.bits(u32::MAX) });
// Decrementing counter.
timekeeper_tim_regs
.cnt_value()
.write(|w| unsafe { w.bits(u32::MAX) });
// Switch on. Timekeeping should always be done.
unsafe {
enable_nvic_interrupt(TimekeeperTim::IRQ);
}
timekeeper_tim_regs
.ctrl()
.modify(|_, w| w.irq_enb().set_bit());
timekeeper_tim_regs.enable().write(|w| unsafe { w.bits(1) });
enable_tim_clk(syscfg, AlarmTim::ID);
assert_tim_reset_for_two_cycles(syscfg, AlarmTim::ID);
let alarm_tim_regs = alarm_tim.reg_block();
// Explicitely disable alarm timer until needed.
alarm_tim_regs.ctrl().modify(|_, w| {
w.irq_enb().clear_bit();
w.enable().clear_bit()
});
// Enable general interrupts. The IRQ enable of the peripheral remains cleared.
unsafe {
enable_nvic_interrupt(AlarmTim::IRQ);
}
}
fn timekeeper_tim() -> &'static pac::tim0::RegisterBlock {
TIMEKEEPER_TIM
.get()
.map(|idx| unsafe { get_tim_raw(*idx as usize) })
.unwrap()
}
fn alarm_tim() -> &'static pac::tim0::RegisterBlock {
ALARM_TIM
.get()
.map(|idx| unsafe { get_tim_raw(*idx as usize) })
.unwrap()
}
/// Should be called inside the IRQ of the timekeeper timer.
///
/// # Safety
///
/// This function has to be called once by the TIM IRQ used for the timekeeping.
pub unsafe fn on_interrupt_timekeeping(&self) {
self.next_period();
}
/// Should be called inside the IRQ of the alarm timer.
///
/// # Safety
///
///This function has to be called once by the TIM IRQ used for the timekeeping.
pub unsafe fn on_interrupt_alarm(&self) {
critical_section::with(|cs| {
if self.alarms.borrow(cs).timestamp.get() <= self.now() {
self.trigger_alarm(cs)
}
})
}
fn next_period(&self) {
let period = self.periods.fetch_add(1, Ordering::AcqRel) + 1;
let t = (period as u64) << 32;
critical_section::with(|cs| {
let alarm = &self.alarms.borrow(cs);
let at = alarm.timestamp.get();
if at < t {
self.trigger_alarm(cs);
} else {
let alarm_tim = Self::alarm_tim();
let remaining_ticks = (at - t) * *SCALE.get().unwrap();
if remaining_ticks <= u32::MAX as u64 {
alarm_tim.enable().write(|w| unsafe { w.bits(0) });
alarm_tim
.cnt_value()
.write(|w| unsafe { w.bits(remaining_ticks as u32) });
alarm_tim.ctrl().modify(|_, w| w.irq_enb().set_bit());
alarm_tim.enable().write(|w| unsafe { w.bits(1) });
}
}
})
}
fn trigger_alarm(&self, cs: CriticalSection) {
Self::alarm_tim().ctrl().modify(|_, w| {
w.irq_enb().clear_bit();
w.enable().clear_bit()
});
let alarm = &self.alarms.borrow(cs);
// Setting the maximum value disables the alarm.
alarm.timestamp.set(u64::MAX);
// Call after clearing alarm, so the callback can set another alarm.
let mut next = self
.queue
.borrow(cs)
.borrow_mut()
.next_expiration(self.now());
while !self.set_alarm(cs, next) {
next = self
.queue
.borrow(cs)
.borrow_mut()
.next_expiration(self.now());
}
}
fn set_alarm(&self, cs: CriticalSection, timestamp: u64) -> bool {
if SCALE.get().is_none() {
return false;
}
let alarm_tim = Self::alarm_tim();
alarm_tim.ctrl().modify(|_, w| {
w.irq_enb().clear_bit();
w.enable().clear_bit()
});
let alarm = self.alarms.borrow(cs);
alarm.timestamp.set(timestamp);
let t = self.now();
if timestamp <= t {
alarm.timestamp.set(u64::MAX);
return false;
}
// If it hasn't triggered yet, setup the relevant reset value, regardless of whether
// the interrupts are enabled or not. When they are enabled at a later point, the
// right value is already set.
// If the timestamp is in the next few ticks, add a bit of buffer to be sure the alarm
// is not missed.
//
// This means that an alarm can be delayed for up to 2 ticks (from t+1 to t+3), but this is allowed
// by the Alarm trait contract. What's not allowed is triggering alarms *before* their scheduled time,
// and we don't do that here.
let safe_timestamp = timestamp.max(t + 3);
let timer_ticks = (safe_timestamp - t).checked_mul(*SCALE.get().unwrap());
alarm_tim.rst_value().write(|w| unsafe { w.bits(u32::MAX) });
if timer_ticks.is_some_and(|v| v <= u32::MAX as u64) {
alarm_tim
.cnt_value()
.write(|w| unsafe { w.bits(timer_ticks.unwrap() as u32) });
alarm_tim.ctrl().modify(|_, w| w.irq_enb().set_bit());
alarm_tim.enable().write(|w| unsafe { w.bits(1) });
}
// If it's too far in the future, don't enable timer yet.
// It will be enabled later by `next_period`.
true
}
}
impl Driver for TimerDriver {
fn now(&self) -> u64 {
if SCALE.get().is_none() {
return 0;
}
let mut period1: u32;
let mut period2: u32;
let mut counter_val: u32;
loop {
// Acquire ensures that we get the latest value of `periods` and
// no instructions can be reordered before the load.
period1 = self.periods.load(Ordering::Acquire);
counter_val = u32::MAX - Self::timekeeper_tim().cnt_value().read().bits();
// Double read to protect against race conditions when the counter is overflowing.
period2 = self.periods.load(Ordering::Relaxed);
if period1 == period2 {
let now = (((period1 as u64) << 32) | counter_val as u64) / *SCALE.get().unwrap();
return now;
}
}
}
fn schedule_wake(&self, at: u64, waker: &core::task::Waker) {
critical_section::with(|cs| {
let mut queue = self.queue.borrow(cs).borrow_mut();
if queue.schedule_wake(at, waker) {
let mut next = queue.next_expiration(self.now());
while !self.set_alarm(cs, next) {
next = queue.next_expiration(self.now());
}
}
})
}
enable_and_init_irq_router();
time_driver().__init(timekeeper, alarm, clocks)
}

View File

@ -12,39 +12,30 @@ categories = ["embedded", "no-std", "hardware-support"]
[dependencies]
cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
critical-section = "1"
nb = "1"
paste = "1"
embedded-hal-nb = "1"
embedded-hal-async = "1"
embedded-hal = "1"
embedded-io = "0.6"
embedded-io-async = "0.6"
num_enum = { version = "0.7", default-features = false }
typenum = "1"
bitflags = "2"
bitfield = { version = ">=0.17, <=0.18"}
fugit = "0.3"
delegate = ">=0.12, <=0.13"
heapless = "0.8"
void = { version = "1", default-features = false }
thiserror = { version = "2", default-features = false }
portable-atomic = "1"
embassy-sync = "0.6"
va416xx = { version = "0.4", features = ["critical-section"], default-features = false }
vorago-shared-periphs = { path = "../../vorago-shared-periphs", features = ["vor4x"] }
nb = "1"
embedded-hal = "1"
num_enum = { version = "0.7", default-features = false }
bitflags = "2"
bitbybit = "1.3"
arbitrary-int = "1.3"
fugit = "0.3"
thiserror = { version = "2", default-features = false }
defmt = { version = "0.3", optional = true }
[features]
default = ["rt", "revb"]
rt = ["va416xx/rt"]
defmt = ["dep:defmt", "fugit/defmt", "embedded-hal/defmt-03"]
defmt = ["dep:defmt", "fugit/defmt", "vorago-shared-periphs/defmt"]
va41630 = ["device-selected"]
va41620 = ["device-selected"]
va41629 = ["device-selected"]
va41628 = ["device-selected"]
va41628 = ["device-selected", "vorago-shared-periphs/va41628"]
device-selected = []
revb = []

View File

@ -8,9 +8,9 @@ use core::marker::PhantomData;
use crate::clock::Clocks;
use crate::pac;
use crate::prelude::*;
use crate::time::Hertz;
use num_enum::{IntoPrimitive, TryFromPrimitive};
use vorago_shared_periphs::{enable_peripheral_clock, PeripheralSelect};
pub const ADC_MIN_CLK: Hertz = Hertz::from_raw(2_000_000);
pub const ADC_MAX_CLK: Hertz = Hertz::from_raw(12_500_000);
@ -151,8 +151,8 @@ pub struct Adc<TagEnabled = ChannelTagDisabled> {
impl Adc<ChannelTagEnabled> {}
impl Adc<ChannelTagDisabled> {
pub fn new(syscfg: &mut pac::Sysconfig, adc: pac::Adc, clocks: &Clocks) -> Self {
Self::generic_new(syscfg, adc, clocks)
pub fn new(adc: pac::Adc, clocks: &Clocks) -> Self {
Self::generic_new(adc, clocks)
}
pub fn trigger_and_read_single_channel(&self, ch: ChannelSelect) -> Result<u16, AdcEmptyError> {
@ -209,12 +209,8 @@ impl Adc<ChannelTagDisabled> {
}
impl Adc<ChannelTagEnabled> {
pub fn new_with_channel_tag(
syscfg: &mut pac::Sysconfig,
adc: pac::Adc,
clocks: &Clocks,
) -> Self {
let mut adc = Self::generic_new(syscfg, adc, clocks);
pub fn new_with_channel_tag(adc: pac::Adc, clocks: &Clocks) -> Self {
let mut adc = Self::generic_new(adc, clocks);
adc.enable_channel_tag();
adc
}
@ -286,8 +282,8 @@ impl Adc<ChannelTagEnabled> {
}
impl<TagEnabled> Adc<TagEnabled> {
fn generic_new(syscfg: &mut pac::Sysconfig, adc: pac::Adc, _clocks: &Clocks) -> Self {
syscfg.enable_peripheral_clock(crate::clock::PeripheralSelect::Adc);
fn generic_new(adc: pac::Adc, _clocks: &Clocks) -> Self {
enable_peripheral_clock(PeripheralSelect::Adc);
adc.ctrl().write(|w| unsafe { w.bits(0) });
let adc = Self {
adc,

View File

@ -15,48 +15,11 @@ use crate::adc::ADC_MAX_CLK;
use crate::pac;
use crate::time::Hertz;
pub use vorago_shared_periphs::clock::{Clocks, HBO_FREQ};
use vorago_shared_periphs::{enable_peripheral_clock, PeripheralSelect};
pub const HBO_FREQ: Hertz = Hertz::from_raw(20_000_000);
pub const XTAL_OSC_TSTART_MS: u32 = 15;
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum PeripheralSelect {
Spi0 = 0,
Spi1 = 1,
Spi2 = 2,
Spi3 = 3,
Uart0 = 4,
Uart1 = 5,
Uart2 = 6,
I2c0 = 7,
I2c1 = 8,
I2c2 = 9,
Can0 = 10,
Can1 = 11,
Rng = 12,
Adc = 13,
Dac = 14,
Dma = 15,
Ebi = 16,
Eth = 17,
Spw = 18,
Clkgen = 19,
IrqRouter = 20,
IoConfig = 21,
Utility = 22,
Watchdog = 23,
PortA = 24,
PortB = 25,
PortC = 26,
PortD = 27,
PortE = 28,
PortF = 29,
PortG = 30,
}
pub type PeripheralClock = PeripheralSelect;
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum FilterClkSel {
@ -70,81 +33,6 @@ pub enum FilterClkSel {
Clk7 = 7,
}
#[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)) });
}
#[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)) });
}
#[inline(always)]
fn assert_periph_reset_for_two_cycles(syscfg: &mut pac::Sysconfig, periph: PeripheralSelect) {
assert_periph_reset(syscfg, periph);
cortex_m::asm::nop();
cortex_m::asm::nop();
deassert_periph_reset(syscfg, periph);
}
pub trait SyscfgExt {
fn enable_peripheral_clock(&mut self, clock: PeripheralClock);
fn disable_peripheral_clock(&mut self, clock: PeripheralClock);
fn assert_periph_reset(&mut self, periph: PeripheralSelect);
fn deassert_periph_reset(&mut self, periph: PeripheralSelect);
fn assert_periph_reset_for_two_cycles(&mut self, periph: PeripheralSelect);
}
impl SyscfgExt for pac::Sysconfig {
#[inline(always)]
fn enable_peripheral_clock(&mut self, clock: PeripheralClock) {
enable_peripheral_clock(self, clock)
}
#[inline(always)]
fn disable_peripheral_clock(&mut self, clock: PeripheralClock) {
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)
}
#[inline(always)]
fn assert_periph_reset_for_two_cycles(&mut self, periph: PeripheralSelect) {
assert_periph_reset_for_two_cycles(self, periph)
}
}
/// 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))]
@ -223,12 +111,12 @@ pub fn pll_setup_delay() {
}
pub trait ClkgenExt {
fn constrain(self) -> ClkgenCfgr;
fn constrain(self) -> ClockConfigurator;
}
impl ClkgenExt for pac::Clkgen {
fn constrain(self) -> ClkgenCfgr {
ClkgenCfgr {
fn constrain(self) -> ClockConfigurator {
ClockConfigurator {
source_clk: None,
ref_clk_sel: RefClkSel::None,
clksel_sys: ClkselSys::Hbo,
@ -241,21 +129,6 @@ impl ClkgenExt for pac::Clkgen {
}
}
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;
@ -269,6 +142,21 @@ pub enum ClkCfgError {
InconsistentCfg,
}
pub struct ClockConfigurator {
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,
}
/// Delays a given amount of milliseconds.
///
/// Taken from the HAL implementation. This implementation is probably not precise and it
@ -283,7 +171,30 @@ pub fn hbo_clock_delay_ms(ms: u32) {
}
}
impl ClkgenCfgr {
impl ClockConfigurator {
/// Create a new clock configuration instance.
pub fn new(clkgen: pac::Clkgen) -> Self {
ClockConfigurator {
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,
}
}
/// Steals a new [ClockConfigurator] instance.
///
/// # Safety
///
/// Circumvents HAL ownership rules.
pub unsafe fn steal() -> Self {
Self::new(unsafe { pac::Clkgen::steal() })
}
#[inline]
pub fn source_clk(mut self, src_clk: Hertz) -> Self {
self.source_clk = Some(src_clk);
@ -334,7 +245,7 @@ impl ClkgenCfgr {
/// 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> {
pub fn freeze(self) -> Result<Clocks, ClkCfgError> {
// Sanitize configuration.
if self.source_clk.is_none() {
return Err(ClkCfgError::ClkSourceFreqNotSet);
@ -349,7 +260,7 @@ impl ClkgenCfgr {
return Err(ClkCfgError::PllConfigNotSet);
}
syscfg.enable_peripheral_clock(PeripheralSelect::Clkgen);
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
@ -458,13 +369,11 @@ impl ClkgenCfgr {
.ctrl0()
.modify(|_, w| unsafe { w.clksel_sys().bits(self.clksel_sys as u8) });
Ok(Clocks {
sysclk: final_sysclk,
apb1: final_sysclk / 2,
apb2: final_sysclk / 4,
Ok(Clocks::__new(
final_sysclk,
#[cfg(not(feature = "va41628"))]
adc_clk: self.cfg_adc_clk_div(final_sysclk),
})
self.cfg_adc_clk_div(final_sysclk),
))
}
#[cfg(not(feature = "va41628"))]
@ -487,54 +396,6 @@ impl ClkgenCfgr {
}
}
/// Frozen clock frequencies
///
/// The existence of this value indicates that the clock configuration can no longer be changed.
/// The [self] module documentation gives some more information on how to retrieve an instance
/// of this structure.
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct Clocks {
sysclk: Hertz,
apb1: Hertz,
apb2: Hertz,
#[cfg(not(feature = "va41628"))]
adc_clk: Hertz,
}
impl Clocks {
/// Returns the frequency of the HBO clock
pub const fn hbo(&self) -> Hertz {
HBO_FREQ
}
/// Returns the frequency of the APB0 which is equal to the system clock.
pub const fn apb0(&self) -> Hertz {
self.sysclk()
}
/// Returns system clock divied by 2.
pub const fn apb1(&self) -> Hertz {
self.apb1
}
/// Returns system clock divied by 4.
pub const fn apb2(&self) -> Hertz {
self.apb2
}
/// Returns the system (core) frequency
pub const fn sysclk(&self) -> Hertz {
self.sysclk
}
/// Returns the ADC clock frequency which has a separate divider.
#[cfg(not(feature = "va41628"))]
pub const fn adc_clk(&self) -> Hertz {
self.adc_clk
}
}
pub fn rearm_sysclk_lost() {
rearm_sysclk_lost_with_periph(&unsafe { pac::Clkgen::steal() })
}

View File

@ -5,22 +5,24 @@
//! - [ADC and DAC example](https://github.com/us-irs/va416xx-rs/blob/main/examples/simple/examples/dac-adc.rs)
use core::ops::Deref;
use crate::{
clock::{Clocks, PeripheralSelect, SyscfgExt},
pac,
use vorago_shared_periphs::{
disable_peripheral_clock, enable_peripheral_clock, reset_peripheral_for_cycles,
PeripheralSelect,
};
use crate::{clock::Clocks, pac};
pub type DacRegisterBlock = pac::dac0::RegisterBlock;
/// Common trait implemented by all PAC peripheral access structures. The register block
/// format is the same for all DAC blocks.
pub trait Instance: Deref<Target = DacRegisterBlock> {
pub trait DacMarker: Deref<Target = DacRegisterBlock> {
const IDX: u8;
fn ptr() -> *const DacRegisterBlock;
}
impl Instance for pac::Dac0 {
impl DacMarker for pac::Dac0 {
const IDX: u8 = 0;
#[inline(always)]
@ -29,7 +31,7 @@ impl Instance for pac::Dac0 {
}
}
impl Instance for pac::Dac1 {
impl DacMarker for pac::Dac1 {
const IDX: u8 = 1;
#[inline(always)]
@ -50,40 +52,37 @@ pub enum DacSettling {
Apb2Times150 = 6,
}
pub struct Dac<DacInstance> {
dac: DacInstance,
}
pub struct Dac(*const DacRegisterBlock);
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct ValueTooLarge;
impl<DacInstance: Instance> Dac<DacInstance> {
impl Dac {
/// Create a new [Dac] driver instance.
///
/// The [Clocks] structure is expected here as well to ensure the clock was set up properly.
pub fn new(
syscfg: &mut pac::Sysconfig,
dac: DacInstance,
dac_settling: DacSettling,
_clocks: &Clocks,
) -> Self {
syscfg.enable_peripheral_clock(PeripheralSelect::Dac);
pub fn new<Dac: DacMarker>(dac: Dac, dac_settling: DacSettling, _clocks: &Clocks) -> Self {
enable_peripheral_clock(PeripheralSelect::Dac);
dac.ctrl1().write(|w| {
w.dac_en().set_bit();
// SAFETY: Enum values are valid values only.
unsafe { w.dac_settling().bits(dac_settling as u8) }
});
let dac = Self { dac };
let mut dac = Self(Dac::ptr());
dac.clear_fifo();
dac.clear_irqs();
dac
}
pub const fn regs(&self) -> &DacRegisterBlock {
unsafe { &*self.0 }
}
#[inline(always)]
pub fn clear_irqs(&self) {
self.dac.irq_clr().write(|w| {
pub fn clear_irqs(&mut self) {
self.regs().irq_clr().write(|w| {
w.fifo_oflow().set_bit();
w.fifo_uflow().set_bit();
w.dac_done().set_bit();
@ -92,31 +91,30 @@ impl<DacInstance: Instance> Dac<DacInstance> {
}
#[inline(always)]
pub fn clear_fifo(&self) {
self.dac.fifo_clr().write(|w| unsafe { w.bits(1) });
pub fn clear_fifo(&mut self) {
self.regs().fifo_clr().write(|w| unsafe { w.bits(1) });
}
/// Load next value into the FIFO.
///
/// Uses the [nb] API to allow blocking and non-blocking usage.
#[inline(always)]
pub fn load_value(&self, val: u16) -> nb::Result<(), ValueTooLarge> {
pub fn load_value(&mut self, val: u16) -> nb::Result<(), ValueTooLarge> {
if val > 2_u16.pow(12) - 1 {
return Err(nb::Error::Other(ValueTooLarge));
}
if self.dac.status().read().fifo_entry_cnt().bits() >= 32_u8 {
let regs = self.regs();
if regs.status().read().fifo_entry_cnt().bits() >= 32_u8 {
return Err(nb::Error::WouldBlock);
}
self.dac
.fifo_data()
.write(|w| unsafe { w.bits(val.into()) });
regs.fifo_data().write(|w| unsafe { w.bits(val.into()) });
Ok(())
}
/// This loads and triggers the next value immediately. It also clears the FIFO before
/// loading the passed value.
#[inline(always)]
pub fn load_and_trigger_manually(&self, val: u16) -> Result<(), ValueTooLarge> {
pub fn load_and_trigger_manually(&mut self, val: u16) -> Result<(), ValueTooLarge> {
if val > 2_u16.pow(12) - 1 {
return Err(ValueTooLarge);
}
@ -132,31 +130,30 @@ impl<DacInstance: Instance> Dac<DacInstance> {
/// to be processed by the DAC.
#[inline(always)]
pub fn trigger_manually(&self) {
self.dac.ctrl0().write(|w| w.man_trig_en().set_bit());
self.regs().ctrl0().write(|w| w.man_trig_en().set_bit());
}
#[inline(always)]
pub fn enable_external_trigger(&self) {
self.dac.ctrl0().write(|w| w.ext_trig_en().set_bit());
self.regs().ctrl0().write(|w| w.ext_trig_en().set_bit());
}
pub fn is_settled(&self) -> nb::Result<(), ()> {
if self.dac.status().read().dac_busy().bit_is_set() {
if self.regs().status().read().dac_busy().bit_is_set() {
return Err(nb::Error::WouldBlock);
}
Ok(())
}
#[inline(always)]
pub fn reset(&mut self, syscfg: &mut pac::Sysconfig) {
syscfg.enable_peripheral_clock(PeripheralSelect::Dac);
syscfg.assert_periph_reset_for_two_cycles(PeripheralSelect::Dac);
pub fn reset(&mut self) {
enable_peripheral_clock(PeripheralSelect::Dac);
reset_peripheral_for_cycles(PeripheralSelect::Dac, 2);
}
/// Relases the DAC, which also disables its peripheral clock.
/// Stops the DAC, which disables its peripheral clock.
#[inline(always)]
pub fn release(self, syscfg: &mut pac::Sysconfig) -> DacInstance {
syscfg.disable_peripheral_clock(PeripheralSelect::Dac);
self.dac
pub fn stop(self) {
disable_peripheral_clock(PeripheralSelect::Dac);
}
}

View File

@ -3,21 +3,23 @@
//! ## Examples
//!
//! - [Simple DMA example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/dma.rs)
use crate::{
clock::{PeripheralClock, PeripheralSelect},
enable_nvic_interrupt, pac,
prelude::*,
use arbitrary_int::{u10, u3};
use vorago_shared_periphs::{
enable_peripheral_clock, reset_peripheral_for_cycles, PeripheralSelect,
};
use crate::{enable_nvic_interrupt, pac};
const MAX_DMA_TRANSFERS_PER_CYCLE: usize = 1024;
const BASE_PTR_ADDR_MASK: u32 = 0b1111111;
/// DMA cycle control values.
///
/// Refer to chapter 6.3.1 and 6.6.3 of the datasheet for more details.
#[repr(u8)]
#[derive(Debug, Clone, Copy)]
#[bitbybit::bitenum(u3, exhaustive = true)]
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum CycleControl {
/// Indicates that the data structure is invalid.
Stop = 0b000,
@ -42,7 +44,8 @@ pub enum CycleControl {
PeriphScatterGatherAlternate = 0b111,
}
#[derive(Debug, Clone, Copy)]
#[bitbybit::bitenum(u2, exhaustive = true)]
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum AddrIncrement {
Byte = 0b00,
@ -51,7 +54,8 @@ pub enum AddrIncrement {
None = 0b11,
}
#[derive(Debug, Clone, Copy)]
#[bitbybit::bitenum(u2, exhaustive = false)]
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum DataSize {
Byte = 0b00,
@ -60,7 +64,8 @@ pub enum DataSize {
}
/// This configuration controls how many DMA transfers can occur before the controller arbitrates.
#[derive(Debug, Clone, Copy)]
#[bitbybit::bitenum(u4, exhaustive = true)]
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum RPower {
EachTransfer = 0b0000,
@ -73,8 +78,12 @@ pub enum RPower {
Every128 = 0b0111,
Every256 = 0b1000,
Every512 = 0b1001,
Every1024Min = 0b1010,
Every1024 = 0b1111,
Every1024 = 0b1010,
Every1024Alt0 = 0b1011,
Every1024Alt1 = 0b1100,
Every1024Alt2 = 0b1101,
Every1024Alt3 = 0b1110,
Every1024Alt4 = 0b1111,
}
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
@ -82,6 +91,7 @@ pub enum RPower {
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct InvalidCtrlBlockAddrError;
/*
bitfield::bitfield! {
#[repr(transparent)]
#[derive(Clone, Copy)]
@ -111,6 +121,33 @@ bitfield::bitfield! {
u8;
pub cycle_ctrl, set_cycle_ctr: 2, 0;
}
*/
#[bitbybit::bitfield(u32, default = 0x0)]
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct ChannelConfig {
#[bits(30..=31, rw)]
dst_inc: AddrIncrement,
#[bits(28..=29, rw)]
dst_size: Option<DataSize>,
#[bits(26..=27, rw)]
src_inc: AddrIncrement,
#[bits(24..=25, rw)]
src_size: Option<DataSize>,
#[bits(21..=23, rw)]
dest_prot_ctrl: u3,
#[bits(18..=20, rw)]
src_prot_ctrl: u3,
#[bits(14..=17, rw)]
r_power: RPower,
#[bits(4..=13, rw)]
n_minus_1: u10,
#[bit(3, rw)]
next_useburst: bool,
#[bits(0..=2, rw)]
cycle_ctrl: CycleControl,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
@ -127,7 +164,7 @@ impl DmaChannelControl {
Self {
src_end_ptr: 0,
dest_end_ptr: 0,
cfg: ChannelConfig(0),
cfg: ChannelConfig::new_with_raw_value(0),
padding: 0,
}
}
@ -428,20 +465,18 @@ impl DmaChannel {
return Err(DmaTransferInitError::TransferSizeTooLarge(source.len()));
}
let len = source.len() - 1;
self.ch_ctrl_pri.cfg.set_raw(0);
self.ch_ctrl_pri.cfg = ChannelConfig::new_with_raw_value(0);
self.ch_ctrl_pri.src_end_ptr = (source.as_ptr() as u32)
.checked_add(len as u32)
.ok_or(DmaTransferInitError::AddrOverflow)?;
self.ch_ctrl_pri.dest_end_ptr = dest as u32;
self.ch_ctrl_pri
.cfg
.set_cycle_ctr(CycleControl::Basic as u8);
self.ch_ctrl_pri.cfg.set_src_size(DataSize::Byte as u8);
self.ch_ctrl_pri.cfg.set_src_inc(AddrIncrement::Byte as u8);
self.ch_ctrl_pri.cfg.set_dst_size(DataSize::Byte as u8);
self.ch_ctrl_pri.cfg.set_dst_inc(AddrIncrement::None as u8);
self.ch_ctrl_pri.cfg.set_n_minus_1(len as u16);
self.ch_ctrl_pri.cfg.set_r_power(RPower::Every8 as u8);
self.ch_ctrl_pri.cfg.set_cycle_ctrl(CycleControl::Basic);
self.ch_ctrl_pri.cfg.set_src_size(DataSize::Byte);
self.ch_ctrl_pri.cfg.set_src_inc(AddrIncrement::Byte);
self.ch_ctrl_pri.cfg.set_dst_size(DataSize::Byte);
self.ch_ctrl_pri.cfg.set_dst_inc(AddrIncrement::None);
self.ch_ctrl_pri.cfg.set_n_minus_1(u10::new(len as u16));
self.ch_ctrl_pri.cfg.set_r_power(RPower::Every8);
self.select_primary_structure();
Ok(())
}
@ -470,16 +505,18 @@ impl DmaChannel {
data_size: DataSize,
addr_incr: AddrIncrement,
) {
self.ch_ctrl_pri.cfg.set_raw(0);
self.ch_ctrl_pri.cfg = ChannelConfig::new_with_raw_value(0);
self.ch_ctrl_pri.src_end_ptr = src_end_ptr;
self.ch_ctrl_pri.dest_end_ptr = dest_end_ptr;
self.ch_ctrl_pri.cfg.set_cycle_ctr(CycleControl::Auto as u8);
self.ch_ctrl_pri.cfg.set_src_size(data_size as u8);
self.ch_ctrl_pri.cfg.set_src_inc(addr_incr as u8);
self.ch_ctrl_pri.cfg.set_dst_size(data_size as u8);
self.ch_ctrl_pri.cfg.set_dst_inc(addr_incr as u8);
self.ch_ctrl_pri.cfg.set_n_minus_1(n_minus_one as u16);
self.ch_ctrl_pri.cfg.set_r_power(RPower::Every4 as u8);
self.ch_ctrl_pri.cfg.set_cycle_ctrl(CycleControl::Auto);
self.ch_ctrl_pri.cfg.set_src_size(data_size);
self.ch_ctrl_pri.cfg.set_src_inc(addr_incr);
self.ch_ctrl_pri.cfg.set_dst_size(data_size);
self.ch_ctrl_pri.cfg.set_dst_inc(addr_incr);
self.ch_ctrl_pri
.cfg
.set_n_minus_1(u10::new(n_minus_one as u16));
self.ch_ctrl_pri.cfg.set_r_power(RPower::Every4);
self.select_primary_structure();
}
}
@ -495,7 +532,6 @@ impl Dma {
/// Alternatively, the [DmaCtrlBlock::new_at_addr] function can be used to create the DMA
/// control block at a specific address.
pub fn new(
syscfg: &mut pac::Sysconfig,
dma: pac::Dma,
cfg: DmaCfg,
ctrl_block: *mut DmaCtrlBlock,
@ -505,8 +541,8 @@ impl Dma {
if raw_addr & BASE_PTR_ADDR_MASK > 0 {
return Err(InvalidCtrlBlockAddrError);
}
syscfg.enable_peripheral_clock(PeripheralClock::Dma);
syscfg.assert_periph_reset_for_two_cycles(PeripheralSelect::Dma);
enable_peripheral_clock(PeripheralSelect::Dma);
reset_peripheral_for_cycles(PeripheralSelect::Dma, 2);
let dma = Dma { dma, ctrl_block };
dma.dma
.ctrl_base_ptr()

View File

@ -1,448 +0,0 @@
//! # Async GPIO functionality for the VA416xx family.
//!
//! This module provides the [InputPinAsync] and [InputDynPinAsync] which both implement
//! the [embedded_hal_async::digital::Wait] trait. These types allow for asynchronous waiting
//! on GPIO pins. Please note that this module does not specify/declare the interrupt handlers
//! which must be provided for async support to work. However, it provides the
//! [on_interrupt_for_async_gpio_for_port] generic interrupt handler. This should be called in all
//! IRQ functions which handle any GPIO interrupts with the corresponding [Port] argument.
//!
//! # Example
//!
//! - [Async GPIO example](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples/embassy/src/bin/async-gpio.rs)
use core::future::Future;
use embassy_sync::waitqueue::AtomicWaker;
use embedded_hal_async::digital::Wait;
use portable_atomic::AtomicBool;
use va416xx::{self as pac};
use crate::enable_nvic_interrupt;
use super::{
pin, DynPin, DynPinId, InputConfig, InterruptEdge, InvalidPinTypeError, Pin, PinId, Port,
NUM_PINS_PORT_A_TO_F,
};
static WAKERS_FOR_PORT_A: [AtomicWaker; NUM_PINS_PORT_A_TO_F] =
[const { AtomicWaker::new() }; NUM_PINS_PORT_A_TO_F];
static WAKERS_FOR_PORT_B: [AtomicWaker; NUM_PINS_PORT_A_TO_F] =
[const { AtomicWaker::new() }; NUM_PINS_PORT_A_TO_F];
static WAKERS_FOR_PORT_C: [AtomicWaker; NUM_PINS_PORT_A_TO_F] =
[const { AtomicWaker::new() }; NUM_PINS_PORT_A_TO_F];
static WAKERS_FOR_PORT_D: [AtomicWaker; NUM_PINS_PORT_A_TO_F] =
[const { AtomicWaker::new() }; NUM_PINS_PORT_A_TO_F];
static WAKERS_FOR_PORT_E: [AtomicWaker; NUM_PINS_PORT_A_TO_F] =
[const { AtomicWaker::new() }; NUM_PINS_PORT_A_TO_F];
static WAKERS_FOR_PORT_F: [AtomicWaker; NUM_PINS_PORT_A_TO_F] =
[const { AtomicWaker::new() }; NUM_PINS_PORT_A_TO_F];
static EDGE_DETECTION_PORT_A: [AtomicBool; NUM_PINS_PORT_A_TO_F] =
[const { AtomicBool::new(false) }; NUM_PINS_PORT_A_TO_F];
static EDGE_DETECTION_PORT_B: [AtomicBool; NUM_PINS_PORT_A_TO_F] =
[const { AtomicBool::new(false) }; NUM_PINS_PORT_A_TO_F];
static EDGE_DETECTION_PORT_C: [AtomicBool; NUM_PINS_PORT_A_TO_F] =
[const { AtomicBool::new(false) }; NUM_PINS_PORT_A_TO_F];
static EDGE_DETECTION_PORT_D: [AtomicBool; NUM_PINS_PORT_A_TO_F] =
[const { AtomicBool::new(false) }; NUM_PINS_PORT_A_TO_F];
static EDGE_DETECTION_PORT_E: [AtomicBool; NUM_PINS_PORT_A_TO_F] =
[const { AtomicBool::new(false) }; NUM_PINS_PORT_A_TO_F];
static EDGE_DETECTION_PORT_F: [AtomicBool; NUM_PINS_PORT_A_TO_F] =
[const { AtomicBool::new(false) }; NUM_PINS_PORT_A_TO_F];
#[derive(Debug, thiserror::Error)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[error("port G does not support async functionality")]
pub struct PortGDoesNotSupportAsyncError;
#[derive(Debug, thiserror::Error)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum AsyncDynPinError {
#[error("invalid pin type: {0}")]
InvalidPinType(#[from] InvalidPinTypeError),
#[error("port g does not support async functionality: {0}")]
PortGDoesNotSupportAsync(#[from] PortGDoesNotSupportAsyncError),
}
/// Generic interrupt handler for GPIO interrupts on a specific port to support async functionalities
///
/// This function should be called in all interrupt handlers which handle any GPIO interrupts
/// matching the [Port] argument.
/// The handler will wake the corresponding wakers for the pins that triggered an interrupts
/// as well as update the static edge detection structures. This allows the pin future tocomplete
/// complete async operations.
pub fn on_interrupt_for_async_gpio_for_port(
port: Port,
) -> Result<(), PortGDoesNotSupportAsyncError> {
let periphs = unsafe { pac::Peripherals::steal() };
let (irq_enb, edge_status, wakers, edge_detection) = match port {
Port::A => (
periphs.porta.irq_enb().read().bits(),
periphs.porta.edge_status().read().bits(),
&WAKERS_FOR_PORT_A,
&EDGE_DETECTION_PORT_A,
),
Port::B => (
periphs.portb.irq_enb().read().bits(),
periphs.portb.edge_status().read().bits(),
&WAKERS_FOR_PORT_B,
&EDGE_DETECTION_PORT_B,
),
Port::C => (
periphs.portc.irq_enb().read().bits(),
periphs.portc.edge_status().read().bits(),
&WAKERS_FOR_PORT_C,
&EDGE_DETECTION_PORT_C,
),
Port::D => (
periphs.portd.irq_enb().read().bits(),
periphs.portd.edge_status().read().bits(),
&WAKERS_FOR_PORT_D,
&EDGE_DETECTION_PORT_D,
),
Port::E => (
periphs.porte.irq_enb().read().bits(),
periphs.porte.edge_status().read().bits(),
&WAKERS_FOR_PORT_E,
&EDGE_DETECTION_PORT_E,
),
Port::F => (
periphs.portf.irq_enb().read().bits(),
periphs.portf.edge_status().read().bits(),
&WAKERS_FOR_PORT_F,
&EDGE_DETECTION_PORT_F,
),
Port::G => return Err(PortGDoesNotSupportAsyncError),
};
on_interrupt_for_port(irq_enb, edge_status, wakers, edge_detection);
Ok(())
}
#[inline]
fn on_interrupt_for_port(
mut irq_enb: u32,
edge_status: u32,
wakers: &'static [AtomicWaker],
edge_detection: &'static [AtomicBool],
) {
while irq_enb != 0 {
let bit_pos = irq_enb.trailing_zeros() as usize;
let bit_mask = 1 << bit_pos;
wakers[bit_pos].wake();
if edge_status & bit_mask != 0 {
edge_detection[bit_pos].store(true, core::sync::atomic::Ordering::Relaxed);
// Clear the processed bit
irq_enb &= !bit_mask;
}
}
}
/// Input pin future which implements the [Future] trait.
///
/// Generally, you want to use the [InputPinAsync] or [InputDynPinAsync] types instead of this
/// which also implements the [embedded_hal_async::digital::Wait] trait. However, access to this
/// struture is granted to allow writing custom async structures.
pub struct InputPinFuture {
pin_id: DynPinId,
waker_group: &'static [AtomicWaker],
edge_detection_group: &'static [AtomicBool],
}
impl InputPinFuture {
pub fn new_with_dyn_pin(
pin: &mut DynPin,
edge: InterruptEdge,
) -> Result<Self, AsyncDynPinError> {
if !pin.is_input_pin() {
return Err(InvalidPinTypeError(pin.mode()).into());
}
if pin.id().port() == Port::G {
return Err(PortGDoesNotSupportAsyncError.into());
}
let (waker_group, edge_detection_group) =
Self::pin_group_to_waker_and_edge_detection_group(pin.id().port());
edge_detection_group[pin.id().num() as usize]
.store(false, core::sync::atomic::Ordering::Relaxed);
// Unwraps okay, checked for PORT G previously
pin.configure_edge_interrupt(edge).unwrap();
unsafe { enable_nvic_interrupt(pin.irq_id().unwrap()) };
pin.enable_interrupt();
Ok(Self {
pin_id: pin.id(),
waker_group,
edge_detection_group,
})
}
pub fn new_with_pin<I: PinId, C: InputConfig>(
pin: &mut Pin<I, pin::Input<C>>,
edge: InterruptEdge,
) -> Result<Self, PortGDoesNotSupportAsyncError> {
if pin.id().port() == Port::G {
return Err(PortGDoesNotSupportAsyncError);
}
let (waker_group, edge_detection_group) =
Self::pin_group_to_waker_and_edge_detection_group(pin.id().port());
edge_detection_group[pin.id().num() as usize]
.store(false, core::sync::atomic::Ordering::Relaxed);
// Unwraps okay, checked for PORT G previously
pin.configure_edge_interrupt(edge);
unsafe { enable_nvic_interrupt(I::IRQ.unwrap()) };
pin.enable_interrupt();
Ok(Self {
pin_id: pin.id(),
waker_group,
edge_detection_group,
})
}
#[inline]
pub fn pin_group_to_waker_and_edge_detection_group(
group: Port,
) -> (&'static [AtomicWaker], &'static [AtomicBool]) {
match group {
Port::A => (WAKERS_FOR_PORT_A.as_ref(), EDGE_DETECTION_PORT_A.as_ref()),
Port::B => (WAKERS_FOR_PORT_B.as_ref(), EDGE_DETECTION_PORT_B.as_ref()),
Port::C => (WAKERS_FOR_PORT_C.as_ref(), EDGE_DETECTION_PORT_C.as_ref()),
Port::D => (WAKERS_FOR_PORT_D.as_ref(), EDGE_DETECTION_PORT_D.as_ref()),
Port::E => (WAKERS_FOR_PORT_E.as_ref(), EDGE_DETECTION_PORT_E.as_ref()),
Port::F => (WAKERS_FOR_PORT_F.as_ref(), EDGE_DETECTION_PORT_F.as_ref()),
_ => panic!("unexpected pin group G"),
}
}
}
impl Drop for InputPinFuture {
fn drop(&mut self) {
// The API ensures that we actually own the pin, so stealing it here is okay.
unsafe { DynPin::steal(self.pin_id) }.disable_interrupt();
}
}
impl Future for InputPinFuture {
type Output = ();
fn poll(
self: core::pin::Pin<&mut Self>,
cx: &mut core::task::Context<'_>,
) -> core::task::Poll<Self::Output> {
let idx = self.pin_id.num() as usize;
self.waker_group[idx].register(cx.waker());
if self.edge_detection_group[idx].swap(false, core::sync::atomic::Ordering::Relaxed) {
return core::task::Poll::Ready(());
}
core::task::Poll::Pending
}
}
pub struct InputDynPinAsync {
pin: DynPin,
}
impl InputDynPinAsync {
/// Create a new asynchronous input pin from a [DynPin]. The interrupt ID to be used must be
/// passed as well and is used to route and enable the interrupt.
///
/// Please note that the interrupt handler itself must be provided by the user and the
/// generic [on_interrupt_for_async_gpio_for_port] function must be called inside that function
/// for the asynchronous functionality to work.
pub fn new(pin: DynPin) -> Result<Self, AsyncDynPinError> {
if !pin.is_input_pin() {
return Err(InvalidPinTypeError(pin.mode()).into());
}
if pin.id().port() == Port::G {
return Err(PortGDoesNotSupportAsyncError.into());
}
Ok(Self { pin })
}
/// Asynchronously wait until the pin is high.
///
/// This returns immediately if the pin is already high.
pub async fn wait_for_high(&mut self) {
// Unwrap okay, checked pin in constructor.
let fut =
InputPinFuture::new_with_dyn_pin(&mut self.pin, InterruptEdge::LowToHigh).unwrap();
if self.pin.is_high().unwrap() {
return;
}
fut.await;
}
/// Asynchronously wait until the pin is low.
///
/// This returns immediately if the pin is already high.
pub async fn wait_for_low(&mut self) {
// Unwrap okay, checked pin in constructor.
let fut =
InputPinFuture::new_with_dyn_pin(&mut self.pin, InterruptEdge::HighToLow).unwrap();
if self.pin.is_low().unwrap() {
return;
}
fut.await;
}
/// Asynchronously wait until the pin sees a falling edge.
pub async fn wait_for_falling_edge(&mut self) {
// Unwrap okay, checked pin in constructor.
InputPinFuture::new_with_dyn_pin(&mut self.pin, InterruptEdge::HighToLow)
.unwrap()
.await;
}
/// Asynchronously wait until the pin sees a rising edge.
pub async fn wait_for_rising_edge(&mut self) {
// Unwrap okay, checked pin in constructor.
InputPinFuture::new_with_dyn_pin(&mut self.pin, InterruptEdge::LowToHigh)
.unwrap()
.await;
}
/// Asynchronously wait until the pin sees any edge (either rising or falling).
pub async fn wait_for_any_edge(&mut self) {
// Unwrap okay, checked pin in constructor.
InputPinFuture::new_with_dyn_pin(&mut self.pin, InterruptEdge::BothEdges)
.unwrap()
.await;
}
pub fn release(self) -> DynPin {
self.pin
}
}
impl embedded_hal::digital::ErrorType for InputDynPinAsync {
type Error = core::convert::Infallible;
}
impl Wait for InputDynPinAsync {
async fn wait_for_high(&mut self) -> Result<(), Self::Error> {
self.wait_for_high().await;
Ok(())
}
async fn wait_for_low(&mut self) -> Result<(), Self::Error> {
self.wait_for_low().await;
Ok(())
}
async fn wait_for_rising_edge(&mut self) -> Result<(), Self::Error> {
self.wait_for_rising_edge().await;
Ok(())
}
async fn wait_for_falling_edge(&mut self) -> Result<(), Self::Error> {
self.wait_for_falling_edge().await;
Ok(())
}
async fn wait_for_any_edge(&mut self) -> Result<(), Self::Error> {
self.wait_for_any_edge().await;
Ok(())
}
}
pub struct InputPinAsync<I: PinId, C: InputConfig> {
pin: Pin<I, pin::Input<C>>,
}
impl<I: PinId, C: InputConfig> InputPinAsync<I, C> {
/// Create a new asynchronous input pin from a typed [Pin]. The interrupt ID to be used must be
/// passed as well and is used to route and enable the interrupt.
///
/// Please note that the interrupt handler itself must be provided by the user and the
/// generic [on_interrupt_for_async_gpio_for_port] function must be called inside that function
/// for the asynchronous functionality to work.
pub fn new(pin: Pin<I, pin::Input<C>>) -> Result<Self, PortGDoesNotSupportAsyncError> {
if pin.id().port() == Port::G {
return Err(PortGDoesNotSupportAsyncError);
}
Ok(Self { pin })
}
/// Asynchronously wait until the pin is high.
///
/// This returns immediately if the pin is already high.
pub async fn wait_for_high(&mut self) {
// Unwrap okay, checked pin in constructor.
let fut = InputPinFuture::new_with_pin(&mut self.pin, InterruptEdge::LowToHigh).unwrap();
if self.pin.is_high() {
return;
}
fut.await;
}
/// Asynchronously wait until the pin is low.
///
/// This returns immediately if the pin is already high.
pub async fn wait_for_low(&mut self) {
let fut = InputPinFuture::new_with_pin(&mut self.pin, InterruptEdge::HighToLow).unwrap();
if self.pin.is_low() {
return;
}
fut.await;
}
/// Asynchronously wait until the pin sees falling edge.
pub async fn wait_for_falling_edge(&mut self) {
// Unwrap okay, checked pin in constructor.
InputPinFuture::new_with_pin(&mut self.pin, InterruptEdge::HighToLow)
.unwrap()
.await;
}
/// Asynchronously wait until the pin sees rising edge.
pub async fn wait_for_rising_edge(&mut self) {
// Unwrap okay, checked pin in constructor.
InputPinFuture::new_with_pin(&mut self.pin, InterruptEdge::LowToHigh)
.unwrap()
.await;
}
/// Asynchronously wait until the pin sees any edge (either rising or falling).
pub async fn wait_for_any_edge(&mut self) {
// Unwrap okay, checked pin in constructor.
InputPinFuture::new_with_pin(&mut self.pin, InterruptEdge::BothEdges)
.unwrap()
.await;
}
pub fn release(self) -> Pin<I, pin::Input<C>> {
self.pin
}
}
impl<I: PinId, C: InputConfig> embedded_hal::digital::ErrorType for InputPinAsync<I, C> {
type Error = core::convert::Infallible;
}
impl<I: PinId, C: InputConfig> Wait for InputPinAsync<I, C> {
async fn wait_for_high(&mut self) -> Result<(), Self::Error> {
self.wait_for_high().await;
Ok(())
}
async fn wait_for_low(&mut self) -> Result<(), Self::Error> {
self.wait_for_low().await;
Ok(())
}
async fn wait_for_rising_edge(&mut self) -> Result<(), Self::Error> {
self.wait_for_rising_edge().await;
Ok(())
}
async fn wait_for_falling_edge(&mut self) -> Result<(), Self::Error> {
self.wait_for_falling_edge().await;
Ok(())
}
async fn wait_for_any_edge(&mut self) -> Result<(), Self::Error> {
self.wait_for_any_edge().await;
Ok(())
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,80 +1,14 @@
//! # API for the GPIO peripheral
//! # API for the UART peripheral
//!
//! The implementation of this GPIO module is heavily based on the
//! [ATSAMD HAL implementation](https://docs.rs/atsamd-hal/latest/atsamd_hal/gpio/index.html).
//! The core of this API are the [Uart], [Rx] and [Tx] structures.
//! The RX structure also has a dedicated [RxWithInterrupt] variant which allows reading the receiver
//! using interrupts.
//!
//! This API provides two different submodules, [pin] and [dynpin],
//! representing two different ways to handle GPIO pins. The default, [pin],
//! is a type-level API that tracks the state of each pin at compile-time. The
//! alternative, [dynpin] is a type-erased, value-level API that tracks the
//! state of each pin at run-time.
//!
//! The type-level API is strongly preferred. By representing the state of each
//! pin within the type system, the compiler can detect logic errors at
//! compile-time. Furthermore, the type-level API has absolutely zero run-time
//! cost.
//!
//! If needed, [dynpin] can be used to erase the type-level differences
//! between pins. However, by doing so, pins must now be tracked at run-time,
//! and each pin has a non-zero memory footprint.
//! The [rx_asynch] and [tx_asynch] modules provide an asynchronous non-blocking API for the UART
//! peripheral.
//!
//! ## Examples
//!
//! - [Blinky example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/blinky.rs)
//==================================================================================================
// Errors, Definitions and Constants
//==================================================================================================
pub const NUM_PINS_PORT_A_TO_F: usize = 16;
pub const NUM_PINS_PORT_G: usize = 8;
pub const NUM_GPIO_PINS: usize = NUM_PINS_PORT_A_TO_F * 6 + NUM_PINS_PORT_G;
pub const NUM_GPIO_PINS_WITH_IRQ: usize = NUM_GPIO_PINS - NUM_PINS_PORT_G;
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[error("pin is masked")]
pub struct IsMaskedError;
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Port {
A,
B,
C,
D,
E,
F,
G,
}
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum InterruptEdge {
HighToLow,
LowToHigh,
BothEdges,
}
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum InterruptLevel {
Low = 0,
High = 1,
}
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum PinState {
Low = 0,
High = 1,
}
pub mod pin;
pub use pin::*;
pub mod dynpin;
pub use dynpin::*;
pub mod asynch;
pub use asynch::*;
//! - [Async GPIO example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/embassy/src/bin/async-gpio.rs)
pub use vorago_shared_periphs::gpio::*;

View File

@ -1,935 +0,0 @@
//! # Type-level module for GPIO pins
//!
//! This documentation is strongly based on the
//! [atsamd documentation](https://docs.rs/atsamd-hal/latest/atsamd_hal/gpio/pin/index.html).
//!
//! This module provides a type-level API for GPIO pins. It uses the type system
//! to track the state of pins at compile-time. Representing GPIO pins in this
//! manner incurs no run-time overhead. Each [`Pin`] struct is zero-sized, so
//! there is no data to copy around. Instead, real code is generated as a side
//! effect of type transformations, and the resulting assembly is nearly
//! identical to the equivalent, hand-written C.
//!
//! To track the state of pins at compile-time, this module uses traits to
//! represent [type classes] and types as instances of those type classes. For
//! example, the trait [`InputConfig`] acts as a [type-level enum] of the
//! available input configurations, and the types [`Floating`], [`PullDown`] and
//! [`PullUp`] are its type-level variants.
//!
//! Type-level [`Pin`]s are parameterized by two type-level enums, [`PinId`] and
//! [`PinMode`].
//!
//! ```
//! pub struct Pin<I, M>
//! where
//! I: PinId,
//! M: PinMode,
//! {
//! // ...
//! }
//! ```
//!
//! A `PinId` identifies a pin by it's group (A to G) and pin number. Each
//! `PinId` instance is named according to its datasheet identifier, e.g.
//! [PA2].
//!
//! A `PinMode` represents the various pin modes. The available `PinMode`
//! variants are [`Input`], [`Output`] and [`Alternate`], each with its own
//! corresponding configurations.
//!
//! It is not possible for users to create new instances of a [`Pin`]. Singleton
//! instances of each pin are made available to users through the PinsX
//! struct.
//!
//! Example for the pins of PORT A:
//!
//! To create the [PinsA] struct, users must supply the PAC
//! [Port](crate::pac::Porta) peripheral. The [PinsA] struct takes
//! ownership of the [Porta] and provides the corresponding pins. Each [`Pin`]
//! within the [PinsA] struct can be moved out and used individually.
//!
//!
//! ```no_run
//! let mut peripherals = Peripherals::take().unwrap();
//! let pinsa = PinsA::new(peripherals.porta);
//! ```
//!
//! Pins can be converted between modes using several different methods.
//!
//! ```no_run
//! // Use one of the literal function names
//! let pa0 = pinsa.pa0.into_floating_input();
//! // Use a generic method and one of the `PinMode` variant types
//! let pa0 = pinsa.pa0.into_mode::<FloatingInput>();
//! // Specify the target type and use `From`/`Into`
//! let pa0: Pin<PA0, FloatingInput> = pinsa.pa27.into();
//! ```
//!
//! # Embedded HAL traits
//!
//! This module implements all of the embedded HAL GPIO traits for each [`Pin`]
//! in the corresponding [`PinMode`]s, namely: [embedded_hal::digital::InputPin],
//! [embedded_hal::digital::OutputPin] and [embedded_hal::digital::StatefulOutputPin].
use core::{convert::Infallible, marker::PhantomData, mem::transmute};
pub use crate::clock::FilterClkSel;
use crate::typelevel::Sealed;
use va416xx::{self as pac, Porta, Portb, Portc, Portd, Porte, Portf, Portg};
use super::{
DynAlternate, DynInput, DynOutput, DynPin, DynPinId, DynPinMode, InputPinAsync, InterruptEdge,
InterruptLevel, PinState, Port, PortGDoesNotSupportAsyncError,
};
//==================================================================================================
// Input configuration
//==================================================================================================
/// Type-level enum for input configurations
///
/// The valid options are [Floating], [PullDown] and [PullUp].
pub trait InputConfig: Sealed {
/// Corresponding [DynInput]
const DYN: DynInput;
}
pub enum Floating {}
pub enum PullDown {}
pub enum PullUp {}
impl InputConfig for Floating {
const DYN: DynInput = DynInput::Floating;
}
impl InputConfig for PullDown {
const DYN: DynInput = DynInput::PullDown;
}
impl InputConfig for PullUp {
const DYN: DynInput = DynInput::PullUp;
}
impl Sealed for Floating {}
impl Sealed for PullDown {}
impl Sealed for PullUp {}
/// Type-level variant of [PinMode] for floating input mode
pub type InputFloating = Input<Floating>;
/// Type-level variant of [PinMode] for pull-down input mode
pub type InputPullDown = Input<PullDown>;
/// Type-level variant of [PinMode] for pull-up input mode
pub type InputPullUp = Input<PullUp>;
/// Type-level variant of [PinMode] for input modes
///
/// Type `C` is one of three input configurations: [Floating], [PullDown] or
/// [PullUp]
pub struct Input<C: InputConfig> {
cfg: PhantomData<C>,
}
impl<C: InputConfig> Sealed for Input<C> {}
#[derive(Debug, PartialEq, Eq)]
pub enum FilterType {
SystemClock = 0,
DirectInputWithSynchronization = 1,
FilterOneClockCycle = 2,
FilterTwoClockCycles = 3,
FilterThreeClockCycles = 4,
FilterFourClockCycles = 5,
}
//==================================================================================================
// Output configuration
//==================================================================================================
pub trait OutputConfig: Sealed {
const DYN: DynOutput;
}
pub trait ReadableOutput: Sealed {}
/// Type-level variant of [`OutputConfig`] for a push-pull configuration
pub enum PushPull {}
/// Type-level variant of [`OutputConfig`] for an open drain configuration
pub enum OpenDrain {}
/// Type-level variant of [`OutputConfig`] for a readable push-pull configuration
pub enum ReadablePushPull {}
/// Type-level variant of [`OutputConfig`] for a readable open-drain configuration
pub enum ReadableOpenDrain {}
impl Sealed for PushPull {}
impl Sealed for OpenDrain {}
impl Sealed for ReadableOpenDrain {}
impl Sealed for ReadablePushPull {}
impl ReadableOutput for ReadableOpenDrain {}
impl ReadableOutput for ReadablePushPull {}
impl OutputConfig for PushPull {
const DYN: DynOutput = DynOutput::PushPull;
}
impl OutputConfig for OpenDrain {
const DYN: DynOutput = DynOutput::OpenDrain;
}
impl OutputConfig for ReadablePushPull {
const DYN: DynOutput = DynOutput::ReadablePushPull;
}
impl OutputConfig for ReadableOpenDrain {
const DYN: DynOutput = DynOutput::ReadableOpenDrain;
}
/// Type-level variant of [`PinMode`] for output modes
///
/// Type `C` is one of four output configurations: [`PushPull`], [`OpenDrain`] or
/// their respective readable versions
pub struct Output<C: OutputConfig> {
cfg: PhantomData<C>,
}
impl<C: OutputConfig> Sealed for Output<C> {}
/// Type-level variant of [`PinMode`] for push-pull output mode
pub type PushPullOutput = Output<PushPull>;
/// Type-level variant of [`PinMode`] for open drain output mode
pub type OutputOpenDrain = Output<OpenDrain>;
pub type OutputReadablePushPull = Output<ReadablePushPull>;
pub type OutputReadableOpenDrain = Output<ReadableOpenDrain>;
//==================================================================================================
// Alternate configurations
//==================================================================================================
/// Type-level enum for alternate peripheral function configurations
pub trait AlternateConfig: Sealed {
const DYN: DynAlternate;
}
pub enum Funsel1 {}
pub enum Funsel2 {}
pub enum Funsel3 {}
impl AlternateConfig for Funsel1 {
const DYN: DynAlternate = DynAlternate::Sel1;
}
impl AlternateConfig for Funsel2 {
const DYN: DynAlternate = DynAlternate::Sel2;
}
impl AlternateConfig for Funsel3 {
const DYN: DynAlternate = DynAlternate::Sel3;
}
impl Sealed for Funsel1 {}
impl Sealed for Funsel2 {}
impl Sealed for Funsel3 {}
/// Type-level variant of [`PinMode`] for alternate peripheral functions
///
/// Type `C` is an [`AlternateConfig`]
pub struct Alternate<C: AlternateConfig> {
cfg: PhantomData<C>,
}
impl<C: AlternateConfig> Sealed for Alternate<C> {}
pub type AltFunc1 = Alternate<Funsel1>;
pub type AltFunc2 = Alternate<Funsel2>;
pub type AltFunc3 = Alternate<Funsel3>;
/// Type alias for the [`PinMode`] at reset
pub type Reset = InputFloating;
//==================================================================================================
// Pin modes
//==================================================================================================
/// Type-level enum representing pin modes
///
/// The valid options are [Input], [Output] and [Alternate].
pub trait PinMode: Sealed {
/// Corresponding [DynPinMode]
const DYN: DynPinMode;
}
impl<C: InputConfig> PinMode for Input<C> {
const DYN: DynPinMode = DynPinMode::Input(C::DYN);
}
impl<C: OutputConfig> PinMode for Output<C> {
const DYN: DynPinMode = DynPinMode::Output(C::DYN);
}
impl<C: AlternateConfig> PinMode for Alternate<C> {
const DYN: DynPinMode = DynPinMode::Alternate(C::DYN);
}
//==================================================================================================
// Pin IDs
//==================================================================================================
/// Type-level enum for pin IDs
pub trait PinId: Sealed {
/// Corresponding [DynPinId]
const DYN: DynPinId;
const IRQ: Option<pac::Interrupt>;
}
macro_rules! pin_id {
($Port:ident, $Id:ident, $NUM:literal, $Irq:expr, $(, $meta: meta)?) => {
// Need paste macro to use ident in doc attribute
paste::paste! {
$(#[$meta])?
#[doc = "Pin ID representing pin " $Id]
pub enum $Id {}
$(#[$meta])?
impl Sealed for $Id {}
$(#[$meta])?
impl PinId for $Id {
const DYN: DynPinId = DynPinId::new(Port::$Port, $NUM);
const IRQ: Option<pac::Interrupt> = $Irq;
}
}
};
}
//==================================================================================================
// Pin
//==================================================================================================
/// A type-level GPIO pin, parameterized by [`PinId`] and [`PinMode`] types
#[derive(Debug)]
pub struct Pin<I: PinId, M: PinMode> {
inner: DynPin,
mode: PhantomData<(I, M)>,
}
impl<I: PinId, M: PinMode> Pin<I, M> {
/// Create a new [`Pin`]
///
/// # Safety
///
/// Each [`Pin`] must be a singleton. For a given [`PinId`], there must be
/// at most one corresponding [`Pin`] in existence at any given time.
/// Violating this requirement is `unsafe`.
#[inline]
pub(crate) const unsafe fn new() -> Pin<I, M> {
Pin {
inner: DynPin::new(I::DYN, M::DYN),
mode: PhantomData,
}
}
#[inline]
pub const fn id(&self) -> DynPinId {
self.inner.id()
}
#[inline(always)]
pub const fn irq_id(&self) -> Option<pac::Interrupt> {
I::IRQ
}
/// Convert the pin to the requested [`PinMode`]
#[inline]
pub fn into_mode<N: PinMode>(mut self) -> Pin<I, N> {
// Only modify registers if we are actually changing pin mode
// This check should compile away
if N::DYN != M::DYN {
self.inner.change_mode(N::DYN);
}
// Safe because we drop the existing Pin
unsafe { Pin::new() }
}
/// Configure the pin for function select 1. See Programmer Guide p. 286 for the function table
#[inline]
pub fn into_funsel_1(self) -> Pin<I, AltFunc1> {
self.into_mode()
}
/// Configure the pin for function select 2. See Programmer Guide p. 286 for the function table
#[inline]
pub fn into_funsel_2(self) -> Pin<I, AltFunc2> {
self.into_mode()
}
/// Configure the pin for function select 3. See Programmer Guide p. 286 for the function table
#[inline]
pub fn into_funsel_3(self) -> Pin<I, AltFunc3> {
self.into_mode()
}
/// Configure the pin to operate as a floating input
#[inline]
pub fn into_floating_input(self) -> Pin<I, InputFloating> {
self.into_mode()
}
/// Configure the pin to operate as a pulled down input
#[inline]
pub fn into_pull_down_input(self) -> Pin<I, InputPullDown> {
self.into_mode()
}
/// Configure the pin to operate as a pulled up input
#[inline]
pub fn into_pull_up_input(self) -> Pin<I, InputPullUp> {
self.into_mode()
}
/// Configure the pin to operate as a push-pull output
#[inline]
pub fn into_push_pull_output(self) -> Pin<I, PushPullOutput> {
self.into_mode()
}
/// Configure the pin to operate as a readable push-pull output
#[inline]
pub fn into_readable_push_pull_output(self) -> Pin<I, OutputReadablePushPull> {
self.into_mode()
}
/// Configure the pin to operate as a readable open-drain output
#[inline]
pub fn into_readable_open_drain_output(self) -> Pin<I, OutputReadableOpenDrain> {
self.into_mode()
}
#[inline]
pub fn is_low(&self) -> bool {
!self.inner.read_pin()
}
#[inline]
pub fn is_high(&self) -> bool {
self.inner.read_pin()
}
#[inline]
pub fn datamask(&self) -> bool {
self.inner.datamask()
}
#[inline]
pub fn clear_datamask(&mut self) {
self.inner.clear_datamask()
}
#[inline]
pub fn set_datamask(&mut self) {
self.inner.set_datamask()
}
#[inline]
pub fn is_high_masked(&self) -> Result<bool, crate::gpio::IsMaskedError> {
self.inner.is_high_masked()
}
#[inline]
pub fn is_low_masked(&self) -> Result<bool, crate::gpio::IsMaskedError> {
self.inner.is_low_masked()
}
#[inline]
pub fn downgrade(self) -> DynPin {
self.inner
}
// Those only serve for the embedded HAL implementations which have different mutability.
#[inline]
fn is_low_mut(&mut self) -> bool {
self.is_low()
}
#[inline]
fn is_high_mut(&mut self) -> bool {
self.is_high()
}
#[inline]
pub fn enable_interrupt(&mut self) {
self.inner.enable_interrupt();
}
#[inline]
pub fn disable_interrupt(&mut self) {
self.inner.disable_interrupt();
}
/// Configure the pin for an edge interrupt but does not enable the interrupt.
pub fn configure_edge_interrupt(&mut self, edge_type: InterruptEdge) {
self.inner.configure_edge_interrupt(edge_type).unwrap();
}
/// Configure the pin for a level interrupt but does not enable the interrupt.
pub fn configure_level_interrupt(&mut self, level_type: InterruptLevel) {
self.inner.configure_level_interrupt(level_type).unwrap();
}
}
//==============================================================================
// AnyPin
//==============================================================================
/// Type class for [`Pin`] types
///
/// This trait uses the [`AnyKind`] trait pattern to create a [type class] for
/// [`Pin`] types. See the `AnyKind` documentation for more details on the
/// pattern.
///
/// ## `v1` Compatibility
///
/// Normally, this trait would use `Is<Type = SpecificPin<Self>>` as a super
/// trait. But doing so would restrict implementations to only the `v2` `Pin`
/// type in this module. To aid in backwards compatibility, we want to implement
/// `AnyPin` for the `v1` `Pin` type as well. This is possible for a few
/// reasons. First, both structs are zero-sized, so there is no meaningful
/// memory layout to begin with. And even if there were, the `v1` `Pin` type is
/// a newtype wrapper around a `v2` `Pin`, and single-field structs are
/// guaranteed to have the same layout as the field, even for `repr(Rust)`.
///
/// [`AnyKind`]: crate::typelevel#anykind-trait-pattern
/// [type class]: crate::typelevel#type-classes
pub trait AnyPin
where
Self: Sealed,
Self: From<SpecificPin<Self>>,
Self: Into<SpecificPin<Self>>,
Self: AsRef<SpecificPin<Self>>,
Self: AsMut<SpecificPin<Self>>,
{
/// [`PinId`] of the corresponding [`Pin`]
type Id: PinId;
/// [`PinMode`] of the corresponding [`Pin`]
type Mode: PinMode;
}
impl<I, M> Sealed for Pin<I, M>
where
I: PinId,
M: PinMode,
{
}
impl<I, M> AnyPin for Pin<I, M>
where
I: PinId,
M: PinMode,
{
type Id = I;
type Mode = M;
}
/// Type alias to recover the specific [`Pin`] type from an implementation of
/// [`AnyPin`]
///
/// See the [`AnyKind`] documentation for more details on the pattern.
///
/// [`AnyKind`]: crate::typelevel#anykind-trait-pattern
pub type SpecificPin<P> = Pin<<P as AnyPin>::Id, <P as AnyPin>::Mode>;
impl<P: AnyPin> AsRef<P> for SpecificPin<P> {
#[inline]
fn as_ref(&self) -> &P {
// SAFETY: This is guaranteed to be safe, because P == SpecificPin<P>
// Transmuting between `v1` and `v2` `Pin` types is also safe, because
// both are zero-sized, and single-field, newtype structs are guaranteed
// to have the same layout as the field anyway, even for repr(Rust).
unsafe { transmute(self) }
}
}
impl<P: AnyPin> AsMut<P> for SpecificPin<P> {
#[inline]
fn as_mut(&mut self) -> &mut P {
// SAFETY: This is guaranteed to be safe, because P == SpecificPin<P>
// Transmuting between `v1` and `v2` `Pin` types is also safe, because
// both are zero-sized, and single-field, newtype structs are guaranteed
// to have the same layout as the field anyway, even for repr(Rust).
unsafe { transmute(self) }
}
}
//==================================================================================================
// Additional functionality
//==================================================================================================
impl<I: PinId, C: InputConfig> Pin<I, Input<C>> {
/// Convert the pin into an async pin. The pin can be converted back by calling
/// [InputPinAsync::release]
pub fn into_async_input(self) -> Result<InputPinAsync<I, C>, PortGDoesNotSupportAsyncError> {
InputPinAsync::new(self)
}
}
impl<I: PinId, C: OutputConfig> Pin<I, Output<C>> {
#[inline]
pub fn set_high(&mut self) {
self.inner.write_pin(true)
}
#[inline]
pub fn set_low(&mut self) {
self.inner.write_pin(false)
}
#[inline]
pub fn toggle(&mut self) {
self.inner.toggle().unwrap()
}
#[inline]
pub fn set_high_masked(&mut self) -> Result<(), crate::gpio::IsMaskedError> {
self.inner.set_high_masked()
}
#[inline]
pub fn set_low_masked(&mut self) -> Result<(), crate::gpio::IsMaskedError> {
self.inner.set_low_masked()
}
/// Possible delays in clock cycles:
/// - Delay 1: 1
/// - Delay 2: 2
/// - Delay 1 + Delay 2: 3
#[inline]
pub fn configure_delay(&mut self, delay_1: bool, delay_2: bool) {
self.inner.configure_delay(delay_1, delay_2).unwrap();
}
/// When configured for pulse mode, a given pin will set the non-default state for exactly
/// one clock cycle before returning to the configured default state
pub fn configure_pulse_mode(&mut self, enable: bool, default_state: PinState) {
self.inner
.configure_pulse_mode(enable, default_state)
.unwrap();
}
}
impl<I: PinId, C: InputConfig> Pin<I, Input<C>> {
#[inline]
pub fn configure_filter_type(&mut self, filter: FilterType, clksel: FilterClkSel) {
self.inner.configure_filter_type(filter, clksel).unwrap();
}
}
//==================================================================================================
// Embedded HAL traits
//==================================================================================================
impl<I, M> embedded_hal::digital::ErrorType for Pin<I, M>
where
I: PinId,
M: PinMode,
{
type Error = Infallible;
}
impl<I: PinId, C: OutputConfig> embedded_hal::digital::OutputPin for Pin<I, Output<C>> {
#[inline]
fn set_high(&mut self) -> Result<(), Self::Error> {
self.set_high();
Ok(())
}
#[inline]
fn set_low(&mut self) -> Result<(), Self::Error> {
self.set_low();
Ok(())
}
}
impl<I, C> embedded_hal::digital::InputPin for Pin<I, Input<C>>
where
I: PinId,
C: InputConfig,
{
#[inline]
fn is_high(&mut self) -> Result<bool, Self::Error> {
Ok(self.is_high_mut())
}
#[inline]
fn is_low(&mut self) -> Result<bool, Self::Error> {
Ok(self.is_low_mut())
}
}
impl<I, C> embedded_hal::digital::StatefulOutputPin for Pin<I, Output<C>>
where
I: PinId,
C: OutputConfig + ReadableOutput,
{
#[inline]
fn is_set_high(&mut self) -> Result<bool, Self::Error> {
Ok(self.is_high())
}
#[inline]
fn is_set_low(&mut self) -> Result<bool, Self::Error> {
Ok(self.is_low())
}
#[inline]
fn toggle(&mut self) -> Result<(), Self::Error> {
self.toggle();
Ok(())
}
}
impl<I, C> embedded_hal::digital::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_mut())
}
#[inline]
fn is_low(&mut self) -> Result<bool, Self::Error> {
Ok(self.is_low_mut())
}
}
//==================================================================================================
// Pin definitions
//==================================================================================================
macro_rules! pins {
(
$Port:ident, $PinsName:ident, $($Id:ident $(, $meta:meta)?)+,
) => {
paste::paste!(
/// Collection of all the individual [`Pin`]s for a given port (PORTA or PORTB)
pub struct $PinsName {
port: $Port,
$(
$(#[$meta])?
#[doc = "Pin " $Id]
pub [<$Id:lower>]: Pin<$Id, Reset>,
)+
}
impl $PinsName {
/// Create a new struct containing all the Pins. Passing the IOCONFIG peripheral
/// is optional because it might be required to create pin definitions for both
/// ports.
#[inline]
pub fn new(
syscfg: &mut va416xx::Sysconfig,
port: $Port
) -> $PinsName {
syscfg.peripheral_clk_enable().modify(|_, w| {
w.[<$Port:lower>]().set_bit();
w.ioconfig().set_bit()
});
$PinsName {
port,
// Safe because we only create one `Pin` per `PinId`
$(
$(#[$meta])?
[<$Id:lower>]: unsafe { Pin::new() },
)+
}
}
/// Get the peripheral ID
/// Safety: Read-only register
pub fn get_perid() -> u32 {
let port = unsafe { &(*$Port::ptr()) };
port.perid().read().bits()
}
/// Consumes the Pins struct and returns the port definitions
pub fn release(self) -> $Port {
self.port
}
}
);
}
}
macro_rules! declare_pins_with_irq {
(
$Group:ident, $PinsName:ident, $Port:ident, [$(($Id:ident, $NUM:literal $(, $meta:meta)?)),+]
) => {
pins!($Port, $PinsName, $($Id $(, $meta)?)+,);
$(
paste::paste! {
pin_id!($Group, $Id, $NUM, Some(pac::Interrupt::[<$Port:upper $NUM>]), $(, $meta)?);
}
)+
}
}
macro_rules! declare_pins {
(
$Group:ident, $PinsName:ident, $Port:ident, [$(($Id:ident, $NUM:literal $(, $meta:meta)?)),+]
) => {
pins!($Port, $PinsName, $($Id $(, $meta)?)+,);
$(
pin_id!($Group, $Id, $NUM, None, $(, $meta)?);
)+
}
}
declare_pins_with_irq!(
A,
PinsA,
Porta,
[
(PA0, 0),
(PA1, 1),
(PA2, 2),
(PA3, 3),
(PA4, 4),
(PA5, 5),
(PA6, 6),
(PA7, 7),
(PA8, 8),
(PA9, 9),
(PA10, 10),
(PA11, 11),
(PA12, 12),
(PA13, 13),
(PA14, 14),
(PA15, 15)
]
);
declare_pins_with_irq!(
B,
PinsB,
Portb,
[
(PB0, 0),
(PB1, 1),
(PB2, 2),
(PB3, 3),
(PB4, 4),
(PB5, 5, cfg(not(feature = "va41628"))),
(PB6, 6, cfg(not(feature = "va41628"))),
(PB7, 7, cfg(not(feature = "va41628"))),
(PB8, 8, cfg(not(feature = "va41628"))),
(PB9, 9, cfg(not(feature = "va41628"))),
(PB10, 10, cfg(not(feature = "va41628"))),
(PB11, 11, cfg(not(feature = "va41628"))),
(PB12, 12),
(PB13, 13),
(PB14, 14),
(PB15, 15)
]
);
declare_pins_with_irq!(
C,
PinsC,
Portc,
[
(PC0, 0),
(PC1, 1),
(PC2, 2),
(PC3, 3),
(PC4, 4),
(PC5, 5),
(PC6, 6),
(PC7, 7),
(PC8, 8),
(PC9, 9),
(PC10, 10),
(PC11, 11),
(PC12, 12),
(PC13, 13, cfg(not(feature = "va41628"))),
(PC14, 14),
(PC15, 15, cfg(not(feature = "va41628")))
]
);
declare_pins_with_irq!(
D,
PinsD,
Portd,
[
(PD0, 0, cfg(not(feature = "va41628"))),
(PD1, 1, cfg(not(feature = "va41628"))),
(PD2, 2, cfg(not(feature = "va41628"))),
(PD3, 3, cfg(not(feature = "va41628"))),
(PD4, 4, cfg(not(feature = "va41628"))),
(PD5, 5, cfg(not(feature = "va41628"))),
(PD6, 6, cfg(not(feature = "va41628"))),
(PD7, 7, cfg(not(feature = "va41628"))),
(PD8, 8, cfg(not(feature = "va41628"))),
(PD9, 9, cfg(not(feature = "va41628"))),
(PD10, 10),
(PD11, 11),
(PD12, 12),
(PD13, 13),
(PD14, 14),
(PD15, 15)
]
);
declare_pins_with_irq!(
E,
PinsE,
Porte,
[
(PE0, 0),
(PE1, 1),
(PE2, 2),
(PE3, 3),
(PE4, 4),
(PE5, 5),
(PE6, 6),
(PE7, 7),
(PE8, 8),
(PE9, 9),
(PE10, 10, cfg(not(feature = "va41628"))),
(PE11, 11, cfg(not(feature = "va41628"))),
(PE12, 12),
(PE13, 13),
(PE14, 14),
(PE15, 15)
]
);
declare_pins_with_irq!(
F,
PinsF,
Portf,
[
(PF0, 0),
(PF1, 1),
(PF2, 2, cfg(not(feature = "va41628"))),
(PF3, 3, cfg(not(feature = "va41628"))),
(PF4, 4, cfg(not(feature = "va41628"))),
(PF5, 5, cfg(not(feature = "va41628"))),
(PF6, 6, cfg(not(feature = "va41628"))),
(PF7, 7, cfg(not(feature = "va41628"))),
(PF8, 8, cfg(not(feature = "va41628"))),
(PF9, 9),
(PF10, 10, cfg(not(feature = "va41628"))),
(PF11, 11),
(PF12, 12),
(PF13, 13),
(PF14, 14),
(PF15, 15)
]
);
declare_pins!(
G,
PinsG,
Portg,
[
(PG0, 0),
(PG1, 1),
(PG2, 2),
(PG3, 3),
(PG4, 4),
(PG5, 5),
(PG6, 6),
(PG7, 7)
]
);

View File

@ -3,913 +3,4 @@
//! ## Examples
//!
//! - [PEB1 accelerometer example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/peb1-accelerometer.rs)
use crate::{
clock::{Clocks, PeripheralSelect},
pac,
prelude::SyscfgExt,
time::Hertz,
typelevel::Sealed,
};
use core::{marker::PhantomData, ops::Deref};
use embedded_hal::i2c::{self, Operation, SevenBitAddress, TenBitAddress};
//==================================================================================================
// Defintions
//==================================================================================================
const CLK_100K: Hertz = Hertz::from_raw(100_000);
const CLK_400K: Hertz = Hertz::from_raw(400_000);
const MIN_CLK_400K: Hertz = Hertz::from_raw(10_000_000);
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum FifoEmptyMode {
Stall = 0,
EndTransaction = 1,
}
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[error("clock too slow for fast I2C mode")]
pub struct ClockTooSlowForFastI2cError;
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
#[error("invalid timing parameters")]
pub struct InvalidTimingParamsError;
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Error {
#[error("arbitration lost")]
ArbitrationLost,
#[error("nack address")]
NackAddr,
/// Data not acknowledged in write operation
#[error("data not acknowledged in write operation")]
NackData,
/// Not enough data received in read operation
#[error("insufficient data received")]
InsufficientDataReceived,
/// Number of bytes in transfer too large (larger than 0x7fe)
#[error("data too large (larger than 0x7fe)")]
DataTooLarge,
}
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum InitError {
/// Wrong address used in constructor
#[error("wrong address mode")]
WrongAddrMode,
/// APB1 clock is too slow for fast I2C mode.
#[error("clock too slow for fast I2C mode: {0}")]
ClkTooSlow(#[from] ClockTooSlowForFastI2cError),
}
impl embedded_hal::i2c::Error for Error {
fn kind(&self) -> embedded_hal::i2c::ErrorKind {
match self {
Error::ArbitrationLost => embedded_hal::i2c::ErrorKind::ArbitrationLoss,
Error::NackAddr => {
embedded_hal::i2c::ErrorKind::NoAcknowledge(i2c::NoAcknowledgeSource::Address)
}
Error::NackData => {
embedded_hal::i2c::ErrorKind::NoAcknowledge(i2c::NoAcknowledgeSource::Data)
}
Error::DataTooLarge | Error::InsufficientDataReceived => {
embedded_hal::i2c::ErrorKind::Other
}
}
}
}
#[derive(Debug, PartialEq, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
enum I2cCmd {
Start = 0b00,
Stop = 0b10,
StartWithStop = 0b11,
Cancel = 0b100,
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum I2cSpeed {
Regular100khz = 0,
Fast400khz = 1,
}
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum I2cDirection {
Send = 0,
Read = 1,
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum I2cAddress {
Regular(u8),
TenBit(u16),
}
pub type I2cRegBlock = pac::i2c0::RegisterBlock;
/// Common trait implemented by all PAC peripheral access structures. The register block
/// format is the same for all SPI blocks.
pub trait Instance: Deref<Target = I2cRegBlock> {
const IDX: u8;
const PERIPH_SEL: PeripheralSelect;
fn ptr() -> *const I2cRegBlock;
}
impl Instance for pac::I2c0 {
const IDX: u8 = 0;
const PERIPH_SEL: PeripheralSelect = PeripheralSelect::I2c0;
#[inline(always)]
fn ptr() -> *const I2cRegBlock {
Self::ptr()
}
}
impl Instance for pac::I2c1 {
const IDX: u8 = 1;
const PERIPH_SEL: PeripheralSelect = PeripheralSelect::I2c1;
#[inline(always)]
fn ptr() -> *const I2cRegBlock {
Self::ptr()
}
}
impl Instance for pac::I2c2 {
const IDX: u8 = 2;
const PERIPH_SEL: PeripheralSelect = PeripheralSelect::I2c2;
#[inline(always)]
fn ptr() -> *const I2cRegBlock {
Self::ptr()
}
}
//==================================================================================================
// Config
//==================================================================================================
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct TrTfThighTlow(u8, u8, u8, u8);
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct TsuStoTsuStaThdStaTBuf(u8, u8, u8, u8);
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct TimingCfg {
// 4 bit max width
tr: u8,
// 4 bit max width
tf: u8,
// 4 bit max width
thigh: u8,
// 4 bit max width
tlow: u8,
// 4 bit max width
tsu_sto: u8,
// 4 bit max width
tsu_sta: u8,
// 4 bit max width
thd_sta: u8,
// 4 bit max width
tbuf: u8,
}
impl TimingCfg {
pub fn new(
first_16_bits: TrTfThighTlow,
second_16_bits: TsuStoTsuStaThdStaTBuf,
) -> Result<Self, InvalidTimingParamsError> {
if first_16_bits.0 > 0xf
|| first_16_bits.1 > 0xf
|| first_16_bits.2 > 0xf
|| first_16_bits.3 > 0xf
|| second_16_bits.0 > 0xf
|| second_16_bits.1 > 0xf
|| second_16_bits.2 > 0xf
|| second_16_bits.3 > 0xf
{
return Err(InvalidTimingParamsError);
}
Ok(TimingCfg {
tr: first_16_bits.0,
tf: first_16_bits.1,
thigh: first_16_bits.2,
tlow: first_16_bits.3,
tsu_sto: second_16_bits.0,
tsu_sta: second_16_bits.1,
thd_sta: second_16_bits.2,
tbuf: second_16_bits.3,
})
}
pub fn reg(&self) -> u32 {
((self.tbuf as u32) << 28)
| ((self.thd_sta as u32) << 24)
| ((self.tsu_sta as u32) << 20)
| ((self.tsu_sto as u32) << 16)
| ((self.tlow as u32) << 12)
| ((self.thigh as u32) << 8)
| ((self.tf as u32) << 4)
| (self.tr as u32)
}
}
impl Default for TimingCfg {
fn default() -> Self {
TimingCfg {
tr: 0x02,
tf: 0x01,
thigh: 0x08,
tlow: 0x09,
tsu_sto: 0x8,
tsu_sta: 0x0a,
thd_sta: 0x8,
tbuf: 0xa,
}
}
}
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct MasterConfig {
pub tx_fe_mode: FifoEmptyMode,
pub rx_fe_mode: FifoEmptyMode,
/// Enable the analog delay glitch filter
pub alg_filt: bool,
/// Enable the digital glitch filter
pub dlg_filt: bool,
pub tm_cfg: Option<TimingCfg>,
// Loopback mode
// lbm: bool,
}
impl Default for MasterConfig {
fn default() -> Self {
MasterConfig {
tx_fe_mode: FifoEmptyMode::Stall,
rx_fe_mode: FifoEmptyMode::Stall,
alg_filt: false,
dlg_filt: false,
tm_cfg: None,
}
}
}
impl Sealed for MasterConfig {}
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct SlaveConfig {
pub tx_fe_mode: FifoEmptyMode,
pub rx_fe_mode: FifoEmptyMode,
/// Maximum number of words before issuing a negative acknowledge.
/// Range should be 0 to 0x7fe. Setting the value to 0x7ff has the same effect as not setting
/// the enable bit since RXCOUNT stops counting at 0x7fe.
pub max_words: Option<usize>,
/// A received address is compared to the ADDRESS register (addr) using the address mask
/// (addr_mask). Those bits with a 1 in the address mask must match for there to be an address
/// match
pub addr: I2cAddress,
/// The default address mask will be 0x3ff to only allow full matches
pub addr_mask: Option<u16>,
/// Optionally specify a second I2C address the slave interface responds to
pub addr_b: Option<I2cAddress>,
pub addr_b_mask: Option<u16>,
}
impl SlaveConfig {
/// Build a default slave config given a specified slave address to respond to
pub fn new(addr: I2cAddress) -> Self {
SlaveConfig {
tx_fe_mode: FifoEmptyMode::Stall,
rx_fe_mode: FifoEmptyMode::Stall,
max_words: None,
addr,
addr_mask: None,
addr_b: None,
addr_b_mask: None,
}
}
}
impl Sealed for SlaveConfig {}
//==================================================================================================
// I2C Base
//==================================================================================================
pub struct I2cBase<I2c> {
i2c: I2c,
clock: Hertz,
}
impl<I2C> I2cBase<I2C> {
#[inline]
fn unwrap_addr(addr: I2cAddress) -> (u16, u32) {
match addr {
I2cAddress::Regular(addr) => (addr as u16, 0 << 15),
I2cAddress::TenBit(addr) => (addr, 1 << 15),
}
}
}
impl<I2c: Instance> I2cBase<I2c> {
pub fn new(
i2c: I2c,
syscfg: &mut pac::Sysconfig,
clocks: &Clocks,
speed_mode: I2cSpeed,
ms_cfg: Option<&MasterConfig>,
sl_cfg: Option<&SlaveConfig>,
) -> Result<Self, ClockTooSlowForFastI2cError> {
syscfg.enable_peripheral_clock(I2c::PERIPH_SEL);
let mut i2c_base = I2cBase {
i2c,
clock: clocks.apb1(),
};
if let Some(ms_cfg) = ms_cfg {
i2c_base.cfg_master(ms_cfg);
}
if let Some(sl_cfg) = sl_cfg {
i2c_base.cfg_slave(sl_cfg);
}
i2c_base.cfg_clk_scale(speed_mode)?;
Ok(i2c_base)
}
fn cfg_master(&mut self, ms_cfg: &MasterConfig) {
let (txfemd, rxfemd) = match (ms_cfg.tx_fe_mode, ms_cfg.rx_fe_mode) {
(FifoEmptyMode::Stall, FifoEmptyMode::Stall) => (false, false),
(FifoEmptyMode::Stall, FifoEmptyMode::EndTransaction) => (false, true),
(FifoEmptyMode::EndTransaction, FifoEmptyMode::Stall) => (true, false),
(FifoEmptyMode::EndTransaction, FifoEmptyMode::EndTransaction) => (true, true),
};
self.i2c.ctrl().modify(|_, w| {
w.txfemd().bit(txfemd);
w.rxffmd().bit(rxfemd);
w.dlgfilter().bit(ms_cfg.dlg_filt);
w.algfilter().bit(ms_cfg.alg_filt)
});
if let Some(ref tm_cfg) = ms_cfg.tm_cfg {
self.i2c
.tmconfig()
.write(|w| unsafe { w.bits(tm_cfg.reg()) });
}
self.i2c.fifo_clr().write(|w| {
w.rxfifo().set_bit();
w.txfifo().set_bit()
});
}
fn cfg_slave(&mut self, sl_cfg: &SlaveConfig) {
let (txfemd, rxfemd) = match (sl_cfg.tx_fe_mode, sl_cfg.rx_fe_mode) {
(FifoEmptyMode::Stall, FifoEmptyMode::Stall) => (false, false),
(FifoEmptyMode::Stall, FifoEmptyMode::EndTransaction) => (false, true),
(FifoEmptyMode::EndTransaction, FifoEmptyMode::Stall) => (true, false),
(FifoEmptyMode::EndTransaction, FifoEmptyMode::EndTransaction) => (true, true),
};
self.i2c.s0_ctrl().modify(|_, w| {
w.txfemd().bit(txfemd);
w.rxffmd().bit(rxfemd)
});
self.i2c.s0_fifo_clr().write(|w| {
w.rxfifo().set_bit();
w.txfifo().set_bit()
});
let max_words = sl_cfg.max_words;
if let Some(max_words) = max_words {
self.i2c
.s0_maxwords()
.write(|w| unsafe { w.bits((1 << 31) | max_words as u32) });
}
let (addr, addr_mode_mask) = Self::unwrap_addr(sl_cfg.addr);
// The first bit is the read/write value. Normally, both read and write are matched
// using the RWMASK bit of the address mask register
self.i2c
.s0_address()
.write(|w| unsafe { w.bits((addr << 1) as u32 | addr_mode_mask) });
if let Some(addr_mask) = sl_cfg.addr_mask {
self.i2c
.s0_addressmask()
.write(|w| unsafe { w.bits((addr_mask << 1) as u32) });
}
if let Some(addr_b) = sl_cfg.addr_b {
let (addr, addr_mode_mask) = Self::unwrap_addr(addr_b);
self.i2c
.s0_addressb()
.write(|w| unsafe { w.bits((addr << 1) as u32 | addr_mode_mask) });
}
if let Some(addr_b_mask) = sl_cfg.addr_b_mask {
self.i2c
.s0_addressmaskb()
.write(|w| unsafe { w.bits((addr_b_mask << 1) as u32) });
}
}
#[inline]
pub fn filters(&mut self, digital_filt: bool, analog_filt: bool) {
self.i2c.ctrl().modify(|_, w| {
w.dlgfilter().bit(digital_filt);
w.algfilter().bit(analog_filt)
});
}
#[inline]
pub fn fifo_empty_mode(&mut self, rx: FifoEmptyMode, tx: FifoEmptyMode) {
self.i2c.ctrl().modify(|_, w| {
w.txfemd().bit(tx as u8 != 0);
w.rxffmd().bit(rx as u8 != 0)
});
}
fn calc_clk_div(&self, speed_mode: I2cSpeed) -> Result<u8, ClockTooSlowForFastI2cError> {
if speed_mode == I2cSpeed::Regular100khz {
Ok(((self.clock.raw() / CLK_100K.raw() / 20) - 1) as u8)
} else {
if self.clock.raw() < MIN_CLK_400K.raw() {
return Err(ClockTooSlowForFastI2cError);
}
Ok(((self.clock.raw() / CLK_400K.raw() / 25) - 1) as u8)
}
}
/// Configures the clock scale for a given speed mode setting
pub fn cfg_clk_scale(
&mut self,
speed_mode: I2cSpeed,
) -> Result<(), ClockTooSlowForFastI2cError> {
let clk_div = self.calc_clk_div(speed_mode)?;
self.i2c
.clkscale()
.write(|w| unsafe { w.bits(((speed_mode as u32) << 31) | clk_div as u32) });
Ok(())
}
pub fn load_address(&mut self, addr: u16) {
// Load address
self.i2c
.address()
.write(|w| unsafe { w.bits((addr << 1) as u32) });
}
#[inline]
fn stop_cmd(&mut self) {
self.i2c
.cmd()
.write(|w| unsafe { w.bits(I2cCmd::Stop as u32) });
}
}
//==================================================================================================
// I2C Master
//==================================================================================================
pub struct I2cMaster<I2c, Addr = SevenBitAddress> {
i2c_base: I2cBase<I2c>,
addr: PhantomData<Addr>,
}
impl<I2c: Instance, Addr> I2cMaster<I2c, Addr> {
pub fn new(
i2c: I2c,
sys_cfg: &mut pac::Sysconfig,
cfg: MasterConfig,
clocks: &Clocks,
speed_mode: I2cSpeed,
) -> Result<Self, ClockTooSlowForFastI2cError> {
Ok(I2cMaster {
i2c_base: I2cBase::new(i2c, sys_cfg, clocks, speed_mode, Some(&cfg), None)?,
addr: PhantomData,
}
.enable_master())
}
#[inline]
pub fn cancel_transfer(&self) {
self.i2c_base
.i2c
.cmd()
.write(|w| unsafe { w.bits(I2cCmd::Cancel as u32) });
}
#[inline]
pub fn clear_tx_fifo(&self) {
self.i2c_base.i2c.fifo_clr().write(|w| w.txfifo().set_bit());
}
#[inline]
pub fn clear_rx_fifo(&self) {
self.i2c_base.i2c.fifo_clr().write(|w| w.rxfifo().set_bit());
}
#[inline]
pub fn enable_master(self) -> Self {
self.i2c_base.i2c.ctrl().modify(|_, w| w.enable().set_bit());
self
}
#[inline]
pub fn disable_master(self) -> Self {
self.i2c_base
.i2c
.ctrl()
.modify(|_, w| w.enable().clear_bit());
self
}
#[inline(always)]
fn load_fifo(&self, word: u8) {
self.i2c_base
.i2c
.data()
.write(|w| unsafe { w.bits(word as u32) });
}
#[inline(always)]
fn read_fifo(&self) -> u8 {
self.i2c_base.i2c.data().read().bits() as u8
}
fn error_handler_write(&mut self, init_cmd: &I2cCmd) {
self.clear_tx_fifo();
if *init_cmd == I2cCmd::Start {
self.i2c_base.stop_cmd()
}
}
fn write_base(
&mut self,
addr: I2cAddress,
init_cmd: I2cCmd,
bytes: impl IntoIterator<Item = u8>,
) -> Result<(), Error> {
let mut iter = bytes.into_iter();
// Load address
let (addr, addr_mode_bit) = I2cBase::<I2c>::unwrap_addr(addr);
self.i2c_base.i2c.address().write(|w| unsafe {
w.bits(I2cDirection::Send as u32 | (addr << 1) as u32 | addr_mode_bit)
});
self.i2c_base
.i2c
.cmd()
.write(|w| unsafe { w.bits(init_cmd as u32) });
let mut load_if_next_available = || {
if let Some(next_byte) = iter.next() {
self.load_fifo(next_byte);
}
};
loop {
let status_reader = self.i2c_base.i2c.status().read();
if status_reader.arblost().bit_is_set() {
self.error_handler_write(&init_cmd);
return Err(Error::ArbitrationLost);
} else if status_reader.nackaddr().bit_is_set() {
self.error_handler_write(&init_cmd);
return Err(Error::NackAddr);
} else if status_reader.nackdata().bit_is_set() {
self.error_handler_write(&init_cmd);
return Err(Error::NackData);
} else if status_reader.idle().bit_is_set() {
return Ok(());
} else {
while !status_reader.txnfull().bit_is_set() {
load_if_next_available();
}
}
}
}
fn write_from_buffer(
&mut self,
init_cmd: I2cCmd,
addr: I2cAddress,
output: &[u8],
) -> Result<(), Error> {
let len = output.len();
// It should theoretically possible to transfer larger data sizes by tracking
// the number of sent words and setting it to 0x7fe as soon as only that many
// bytes are remaining. However, large transfer like this are not common. This
// feature will therefore not be supported for now.
if len > 0x7fe {
return Err(Error::DataTooLarge);
}
// Load number of words
self.i2c_base
.i2c
.words()
.write(|w| unsafe { w.bits(len as u32) });
let mut bytes = output.iter();
// FIFO has a depth of 16. We load slightly above the trigger level
// but not all of it because the transaction might fail immediately
const FILL_DEPTH: usize = 12;
// load the FIFO
for _ in 0..core::cmp::min(FILL_DEPTH, len) {
self.load_fifo(*bytes.next().unwrap());
}
self.write_base(addr, init_cmd, output.iter().cloned())
}
fn read_internal(&mut self, addr: I2cAddress, buffer: &mut [u8]) -> Result<(), Error> {
let len = buffer.len();
// It should theoretically possible to transfer larger data sizes by tracking
// the number of sent words and setting it to 0x7fe as soon as only that many
// bytes are remaining. However, large transfer like this are not common. This
// feature will therefore not be supported for now.
if len > 0x7fe {
return Err(Error::DataTooLarge);
}
// Clear the receive FIFO
self.clear_rx_fifo();
// Load number of words
self.i2c_base
.i2c
.words()
.write(|w| unsafe { w.bits(len as u32) });
let (addr, addr_mode_bit) = match addr {
I2cAddress::Regular(addr) => (addr as u16, 0 << 15),
I2cAddress::TenBit(addr) => (addr, 1 << 15),
};
// Load address
self.i2c_base.i2c.address().write(|w| unsafe {
w.bits(I2cDirection::Read as u32 | (addr << 1) as u32 | addr_mode_bit)
});
let mut buf_iter = buffer.iter_mut();
let mut read_bytes = 0;
// Start receive transfer
self.i2c_base
.i2c
.cmd()
.write(|w| unsafe { w.bits(I2cCmd::StartWithStop as u32) });
let mut read_if_next_available = || {
if let Some(next_byte) = buf_iter.next() {
*next_byte = self.read_fifo();
}
};
loop {
let status_reader = self.i2c_base.i2c.status().read();
if status_reader.arblost().bit_is_set() {
self.clear_rx_fifo();
return Err(Error::ArbitrationLost);
} else if status_reader.nackaddr().bit_is_set() {
self.clear_rx_fifo();
return Err(Error::NackAddr);
} else if status_reader.idle().bit_is_set() {
if read_bytes != len {
return Err(Error::InsufficientDataReceived);
}
return Ok(());
} else if status_reader.rxnempty().bit_is_set() {
read_if_next_available();
read_bytes += 1;
}
}
}
}
//======================================================================================
// Embedded HAL I2C implementations
//======================================================================================
impl<I2c> embedded_hal::i2c::ErrorType for I2cMaster<I2c, SevenBitAddress> {
type Error = Error;
}
impl<I2c: Instance> embedded_hal::i2c::I2c for I2cMaster<I2c, SevenBitAddress> {
fn transaction(
&mut self,
address: SevenBitAddress,
operations: &mut [Operation<'_>],
) -> Result<(), Self::Error> {
for operation in operations {
match operation {
Operation::Read(buf) => self.read_internal(I2cAddress::Regular(address), buf)?,
Operation::Write(buf) => self.write_from_buffer(
I2cCmd::StartWithStop,
I2cAddress::Regular(address),
buf,
)?,
}
}
Ok(())
}
}
impl<I2c> embedded_hal::i2c::ErrorType for I2cMaster<I2c, TenBitAddress> {
type Error = Error;
}
impl<I2c: Instance> embedded_hal::i2c::I2c<TenBitAddress> for I2cMaster<I2c, TenBitAddress> {
fn transaction(
&mut self,
address: TenBitAddress,
operations: &mut [Operation<'_>],
) -> Result<(), Self::Error> {
for operation in operations {
match operation {
Operation::Read(buf) => self.read_internal(I2cAddress::TenBit(address), buf)?,
Operation::Write(buf) => {
self.write_from_buffer(I2cCmd::StartWithStop, I2cAddress::TenBit(address), buf)?
}
}
}
Ok(())
}
}
//==================================================================================================
// I2C Slave
//==================================================================================================
pub struct I2cSlave<I2c, Addr = SevenBitAddress> {
i2c_base: I2cBase<I2c>,
addr: PhantomData<Addr>,
}
impl<I2c: Instance, Addr> I2cSlave<I2c, Addr> {
fn new_generic(
i2c: I2c,
sys_cfg: &mut pac::Sysconfig,
cfg: SlaveConfig,
clocks: &Clocks,
speed_mode: I2cSpeed,
) -> Result<Self, ClockTooSlowForFastI2cError> {
Ok(I2cSlave {
i2c_base: I2cBase::new(i2c, sys_cfg, clocks, speed_mode, None, Some(&cfg))?,
addr: PhantomData,
}
.enable_slave())
}
#[inline]
pub fn enable_slave(self) -> Self {
self.i2c_base
.i2c
.s0_ctrl()
.modify(|_, w| w.enable().set_bit());
self
}
#[inline]
pub fn disable_slave(self) -> Self {
self.i2c_base
.i2c
.s0_ctrl()
.modify(|_, w| w.enable().clear_bit());
self
}
#[inline(always)]
fn load_fifo(&self, word: u8) {
self.i2c_base
.i2c
.s0_data()
.write(|w| unsafe { w.bits(word as u32) });
}
#[inline(always)]
fn read_fifo(&self) -> u8 {
self.i2c_base.i2c.s0_data().read().bits() as u8
}
#[inline]
fn clear_tx_fifo(&self) {
self.i2c_base
.i2c
.s0_fifo_clr()
.write(|w| w.txfifo().set_bit());
}
#[inline]
fn clear_rx_fifo(&self) {
self.i2c_base
.i2c
.s0_fifo_clr()
.write(|w| w.rxfifo().set_bit());
}
/// Get the last address that was matched by the slave control and the corresponding
/// master direction
pub fn last_address(&self) -> (I2cDirection, u32) {
let bits = self.i2c_base.i2c.s0_lastaddress().read().bits();
match bits & 0x01 {
0 => (I2cDirection::Send, bits >> 1),
1 => (I2cDirection::Read, bits >> 1),
_ => (I2cDirection::Send, bits >> 1),
}
}
pub fn write(&mut self, output: &[u8]) -> Result<(), Error> {
let len = output.len();
// It should theoretically possible to transfer larger data sizes by tracking
// the number of sent words and setting it to 0x7fe as soon as only that many
// bytes are remaining. However, large transfer like this are not common. This
// feature will therefore not be supported for now.
if len > 0x7fe {
return Err(Error::DataTooLarge);
}
let mut bytes = output.iter();
// FIFO has a depth of 16. We load slightly above the trigger level
// but not all of it because the transaction might fail immediately
const FILL_DEPTH: usize = 12;
// load the FIFO
for _ in 0..core::cmp::min(FILL_DEPTH, len) {
self.load_fifo(*bytes.next().unwrap());
}
let status_reader = self.i2c_base.i2c.s0_status().read();
let mut load_if_next_available = || {
if let Some(next_byte) = bytes.next() {
self.load_fifo(*next_byte);
}
};
loop {
if status_reader.nackdata().bit_is_set() {
self.clear_tx_fifo();
return Err(Error::NackData);
} else if status_reader.idle().bit_is_set() {
return Ok(());
} else {
while !status_reader.txnfull().bit_is_set() {
load_if_next_available();
}
}
}
}
pub fn read(&mut self, buffer: &mut [u8]) -> Result<(), Error> {
let len = buffer.len();
// It should theoretically possible to transfer larger data sizes by tracking
// the number of sent words and setting it to 0x7fe as soon as only that many
// bytes are remaining. However, large transfer like this are not common. This
// feature will therefore not be supported for now.
if len > 0x7fe {
return Err(Error::DataTooLarge);
}
// Clear the receive FIFO
self.clear_rx_fifo();
let mut buf_iter = buffer.iter_mut();
let mut read_bytes = 0;
let mut read_if_next_available = || {
if let Some(next_byte) = buf_iter.next() {
*next_byte = self.read_fifo();
}
};
loop {
let status_reader = self.i2c_base.i2c.s0_status().read();
if status_reader.idle().bit_is_set() {
if read_bytes != len {
return Err(Error::InsufficientDataReceived);
}
return Ok(());
} else if status_reader.rxnempty().bit_is_set() {
read_bytes += 1;
read_if_next_available();
}
}
}
}
impl<I2c: Instance> I2cSlave<I2c, SevenBitAddress> {
/// Create a new I2C slave for seven bit addresses
pub fn new(
i2c: I2c,
sys_cfg: &mut pac::Sysconfig,
cfg: SlaveConfig,
clocks: &Clocks,
speed_mode: I2cSpeed,
) -> Result<Self, InitError> {
if let I2cAddress::TenBit(_) = cfg.addr {
return Err(InitError::WrongAddrMode);
}
Ok(Self::new_generic(i2c, sys_cfg, cfg, clocks, speed_mode)?)
}
}
impl<I2c: Instance> I2cSlave<I2c, TenBitAddress> {
pub fn new_ten_bit_addr(
i2c: I2c,
sys_cfg: &mut pac::Sysconfig,
cfg: SlaveConfig,
clocks: &Clocks,
speed_mode: I2cSpeed,
) -> Result<Self, ClockTooSlowForFastI2cError> {
Self::new_generic(i2c, sys_cfg, cfg, clocks, speed_mode)
}
}
pub use vorago_shared_periphs::i2c::*;

View File

@ -1,9 +1,10 @@
//! IRQ Router peripheral support.
use crate::{
clock::{PeripheralSelect, SyscfgExt},
pac,
use vorago_shared_periphs::{
enable_peripheral_clock, reset_peripheral_for_cycles, PeripheralSelect,
};
use crate::pac;
/// This enables and initiates the peripheral.
///
/// Please note that this method also writes 0 to the registers which do not have 0 as the default
@ -11,9 +12,10 @@ use crate::{
/// are inconsistent here, and the registers being non-zero can actually lead to weird bugs
/// when working with interrupts. Registers DMASELx and ADCSEL/DMASELx will reset to 0x7f and 0x1f
/// respectively instead of 0x00.
pub fn enable_and_init_irq_router(sysconfig: &mut pac::Sysconfig, irq_router: &pac::IrqRouter) {
sysconfig.enable_peripheral_clock(PeripheralSelect::IrqRouter);
sysconfig.assert_periph_reset_for_two_cycles(PeripheralSelect::IrqRouter);
pub fn enable_and_init_irq_router() {
let irq_router = unsafe { pac::IrqRouter::steal() };
enable_peripheral_clock(PeripheralSelect::IrqRouter);
reset_peripheral_for_cycles(PeripheralSelect::IrqRouter, 2);
unsafe {
irq_router.dmasel0().write_with_zero(|w| w);
irq_router.dmasel1().write_with_zero(|w| w);

View File

@ -41,11 +41,11 @@ pub mod edac;
pub mod gpio;
pub mod i2c;
pub mod irq_router;
pub mod pins;
pub mod pwm;
pub mod spi;
pub mod time;
pub mod timer;
pub mod typelevel;
pub mod uart;
pub mod wdt;
@ -57,14 +57,11 @@ pub mod adc;
#[cfg(not(feature = "va41628"))]
pub mod dac;
#[derive(Debug, Eq, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum FunSel {
Sel0 = 0b00,
Sel1 = 0b01,
Sel2 = 0b10,
Sel3 = 0b11,
}
pub use vorago_shared_periphs::{
assert_peripheral_reset, deassert_peripheral_reset, disable_nvic_interrupt,
disable_peripheral_clock, enable_nvic_interrupt, enable_peripheral_clock,
reset_peripheral_for_cycles, FunSel, PeripheralSelect,
};
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
@ -100,18 +97,41 @@ pub fn port_function_select(
Ok(())
}
/// Enable a specific interrupt using the NVIC peripheral.
///
/// # Safety
///
/// This function is `unsafe` because it can break mask-based critical sections.
#[inline]
pub unsafe fn enable_nvic_interrupt(irq: pac::Interrupt) {
cortex_m::peripheral::NVIC::unmask(irq);
pub trait SyscfgExt {
fn enable_peripheral_clock(&mut self, clock: PeripheralSelect);
fn disable_peripheral_clock(&mut self, clock: PeripheralSelect);
fn assert_periph_reset(&mut self, periph: PeripheralSelect);
fn deassert_periph_reset(&mut self, periph: PeripheralSelect);
fn reset_peripheral_reset_for_cycles(&mut self, periph: PeripheralSelect, cycles: usize);
}
/// Disable a specific interrupt using the NVIC peripheral.
#[inline]
pub fn disable_nvic_interrupt(irq: pac::Interrupt) {
cortex_m::peripheral::NVIC::mask(irq);
impl SyscfgExt for pac::Sysconfig {
#[inline(always)]
fn enable_peripheral_clock(&mut self, clock: PeripheralSelect) {
enable_peripheral_clock(clock)
}
#[inline(always)]
fn disable_peripheral_clock(&mut self, clock: PeripheralSelect) {
disable_peripheral_clock(clock)
}
#[inline(always)]
fn assert_periph_reset(&mut self, clock: PeripheralSelect) {
assert_peripheral_reset(clock)
}
#[inline(always)]
fn deassert_periph_reset(&mut self, clock: PeripheralSelect) {
deassert_peripheral_reset(clock)
}
#[inline(always)]
fn reset_peripheral_reset_for_cycles(&mut self, periph: PeripheralSelect, cycles: usize) {
reset_peripheral_for_cycles(periph, cycles)
}
}

View File

@ -6,11 +6,14 @@
//!
//! - [Flashloader application](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/flashloader)
use embedded_hal::spi::MODE_0;
use vorago_shared_periphs::{
disable_peripheral_clock, enable_peripheral_clock, reset_peripheral_for_cycles,
};
use crate::clock::{Clocks, SyscfgExt};
use crate::clock::Clocks;
use crate::pac;
use crate::spi::{
mode_to_cpo_cph_bit, spi_clk_config_from_div, Instance, WordProvider, BMSTART_BMSTOP_MASK,
mode_to_cpo_cph_bit, spi_clk_config_from_div, SpiMarker, WordProvider, BMSTART_BMSTOP_MASK,
};
const NVM_CLOCK_DIV: u16 = 2;
@ -65,10 +68,10 @@ pub struct VerifyError {
}
impl Nvm {
pub fn new(syscfg: &mut pac::Sysconfig, spi: pac::Spi3, _clocks: &Clocks) -> Self {
crate::clock::enable_peripheral_clock(syscfg, pac::Spi3::PERIPH_SEL);
pub fn new(spi: pac::Spi3, _clocks: &Clocks) -> Self {
enable_peripheral_clock(pac::Spi3::PERIPH_SEL);
// This is done in the C HAL.
syscfg.assert_periph_reset_for_two_cycles(pac::Spi3::PERIPH_SEL);
reset_peripheral_for_cycles(pac::Spi3::PERIPH_SEL, 2);
let spi_clk_cfg = spi_clk_config_from_div(NVM_CLOCK_DIV).unwrap();
let (cpo_bit, cph_bit) = mode_to_cpo_cph_bit(MODE_0);
@ -234,17 +237,17 @@ impl Nvm {
}
/// Enable write-protection and disables the peripheral clock.
pub fn shutdown(&mut self, sys_cfg: &mut pac::Sysconfig) {
pub fn shutdown(&mut self) {
self.wait_for_tx_idle();
self.write_with_bmstop(FRAM_WREN);
self.wait_for_tx_idle();
self.write_single(WPEN_ENABLE_MASK | BP_0_ENABLE_MASK | BP_1_ENABLE_MASK);
crate::clock::disable_peripheral_clock(sys_cfg, pac::Spi3::PERIPH_SEL);
disable_peripheral_clock(pac::Spi3::PERIPH_SEL);
}
/// This function calls [Self::shutdown] and gives back the peripheral structure.
pub fn release(mut self, sys_cfg: &mut pac::Sysconfig) -> pac::Spi3 {
self.shutdown(sys_cfg);
pub fn release(mut self) -> pac::Spi3 {
self.shutdown();
self.spi.take().unwrap()
}
@ -268,7 +271,7 @@ impl Nvm {
impl Drop for Nvm {
fn drop(&mut self) {
if self.spi.is_some() {
self.shutdown(unsafe { &mut pac::Sysconfig::steal() });
self.shutdown();
}
}
}

6
va416xx-hal/src/pins.rs Normal file
View File

@ -0,0 +1,6 @@
//! Pin resource management singletons.
//!
//! This module contains the pin singletons. It allows creating those singletons
//! to access the [Pin] structures of individual ports in a safe way with checked ownership
//! rules.
pub use vorago_shared_periphs::pins::*;

View File

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

View File

@ -1,459 +1,8 @@
//! API for Pulse-Width Modulation (PWM)
//!
//! The Vorago VA416xx devices use the TIM peripherals to perform PWM related tasks.
//! The Vorago devices use the TIM peripherals to perform PWM related tasks
//!
//! ## Examples
//!
//! - [PWM example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/pwm.rs)
use core::convert::Infallible;
use core::marker::PhantomData;
use crate::pac;
use crate::time::Hertz;
pub use crate::timer::ValidTim;
use crate::timer::{TimAndPinRegister, TimDynRegister, TimPin, TimRegInterface, ValidTimAndPin};
use crate::{clock::Clocks, gpio::DynPinId};
const DUTY_MAX: u16 = u16::MAX;
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub(crate) struct PwmCommon {
clock: Hertz,
/// For PWMB, this is the upper limit
current_duty: u16,
/// For PWMA, this value will not be used
current_lower_limit: u16,
current_period: Hertz,
current_rst_val: u32,
}
enum StatusSelPwm {
PwmA = 3,
PwmB = 4,
}
pub struct PwmA {}
pub struct PwmB {}
//==================================================================================================
// Strongly typed PWM pin
//==================================================================================================
pub struct PwmPin<Pin: TimPin, Tim: ValidTim, Mode = PwmA> {
reg: TimAndPinRegister<Pin, Tim>,
inner: ReducedPwmPin<Mode>,
mode: PhantomData<Mode>,
}
impl<Pin: TimPin, Tim: ValidTim, Mode> PwmPin<Pin, Tim, Mode>
where
(Pin, Tim): ValidTimAndPin<Pin, Tim>,
{
/// Create a new stronlgy typed PWM pin
pub fn new(
pin_and_tim: (Pin, Tim),
sys_cfg: &mut pac::Sysconfig,
clocks: &Clocks,
initial_period: impl Into<Hertz> + Copy,
) -> Self {
let mut pin = PwmPin {
inner: ReducedPwmPin::<Mode>::new(
Tim::ID,
Pin::DYN,
PwmCommon {
clock: Tim::clock(clocks),
current_duty: 0,
current_lower_limit: 0,
current_period: initial_period.into(),
current_rst_val: 0,
},
),
reg: unsafe { TimAndPinRegister::new(pin_and_tim.0, pin_and_tim.1) },
mode: PhantomData,
};
sys_cfg
.tim_clk_enable()
.modify(|r, w| unsafe { w.bits(r.bits() | pin.reg.mask_32()) });
pin.enable_pwm_a();
pin.set_period(initial_period);
pin
}
pub fn downgrade(self) -> ReducedPwmPin<Mode> {
self.inner
}
pub fn release(self) -> (Pin, Tim) {
self.reg.release()
}
#[inline]
fn enable_pwm_a(&mut self) {
self.inner.enable_pwm_a();
}
#[inline]
fn enable_pwm_b(&mut self) {
self.inner.enable_pwm_b();
}
#[inline]
pub fn get_period(&self) -> Hertz {
self.inner.get_period()
}
#[inline]
pub fn set_period(&mut self, period: impl Into<Hertz>) {
self.inner.set_period(period);
}
#[inline]
pub fn disable(&mut self) {
self.inner.disable();
}
#[inline]
pub fn enable(&mut self) {
self.inner.enable();
}
#[inline]
pub fn period(&self) -> Hertz {
self.inner.period()
}
#[inline(always)]
pub fn duty(&self) -> u16 {
self.inner.duty()
}
}
impl<Pin: TimPin, Tim: ValidTim> From<PwmPin<Pin, Tim, PwmA>> for PwmPin<Pin, Tim, PwmB>
where
(Pin, Tim): ValidTimAndPin<Pin, Tim>,
{
fn from(other: PwmPin<Pin, Tim, PwmA>) -> Self {
let mut pwmb = Self {
reg: other.reg,
inner: other.inner.into(),
mode: PhantomData,
};
pwmb.enable_pwm_b();
pwmb
}
}
impl<PIN: TimPin, TIM: ValidTim> From<PwmPin<PIN, TIM, PwmB>> for PwmPin<PIN, TIM, PwmA>
where
(PIN, TIM): ValidTimAndPin<PIN, TIM>,
{
fn from(other: PwmPin<PIN, TIM, PwmB>) -> Self {
let mut pwmb = Self {
reg: other.reg,
inner: other.inner.into(),
mode: PhantomData,
};
pwmb.enable_pwm_a();
pwmb
}
}
impl<Pin: TimPin, Tim: ValidTim> PwmPin<Pin, Tim, PwmA>
where
(Pin, Tim): ValidTimAndPin<Pin, Tim>,
{
pub fn pwma(
tim_and_pin: (Pin, Tim),
sys_cfg: &mut pac::Sysconfig,
clocks: &Clocks,
initial_period: impl Into<Hertz> + Copy,
) -> Self {
let mut pin: PwmPin<Pin, Tim, PwmA> =
Self::new(tim_and_pin, sys_cfg, clocks, initial_period);
pin.enable_pwm_a();
pin
}
}
impl<Pin: TimPin, Tim: ValidTim> PwmPin<Pin, Tim, PwmB>
where
(Pin, Tim): ValidTimAndPin<Pin, Tim>,
{
pub fn pwmb(
tim_and_pin: (Pin, Tim),
sys_cfg: &mut pac::Sysconfig,
clocks: &Clocks,
initial_period: impl Into<Hertz> + Copy,
) -> Self {
let mut pin: PwmPin<Pin, Tim, PwmB> =
Self::new(tim_and_pin, sys_cfg, clocks, initial_period);
pin.enable_pwm_b();
pin
}
}
//==================================================================================================
// Reduced PWM pin
//==================================================================================================
/// Reduced version where type information is deleted
pub struct ReducedPwmPin<Mode = PwmA> {
dyn_reg: TimDynRegister,
common: PwmCommon,
mode: PhantomData<Mode>,
}
impl<Mode> ReducedPwmPin<Mode> {
pub(crate) fn new(tim_id: u8, pin_id: DynPinId, common: PwmCommon) -> Self {
Self {
dyn_reg: TimDynRegister { tim_id, pin_id },
common,
mode: PhantomData,
}
}
#[inline]
fn enable_pwm_a(&mut self) {
self.dyn_reg
.reg_block()
.ctrl()
.modify(|_, w| unsafe { w.status_sel().bits(StatusSelPwm::PwmA as u8) });
}
#[inline]
fn enable_pwm_b(&mut self) {
self.dyn_reg
.reg_block()
.ctrl()
.modify(|_, w| unsafe { w.status_sel().bits(StatusSelPwm::PwmB as u8) });
}
#[inline]
pub fn get_period(&self) -> Hertz {
self.common.current_period
}
#[inline]
pub fn set_period(&mut self, period: impl Into<Hertz>) {
self.common.current_period = period.into();
// Avoid division by 0
if self.common.current_period.raw() == 0 {
return;
}
self.common.current_rst_val = self.common.clock.raw() / self.common.current_period.raw();
self.dyn_reg
.reg_block()
.rst_value()
.write(|w| unsafe { w.bits(self.common.current_rst_val) });
}
#[inline]
pub fn disable(&mut self) {
self.dyn_reg
.reg_block()
.ctrl()
.modify(|_, w| w.enable().clear_bit());
}
#[inline]
pub fn enable(&mut self) {
self.dyn_reg
.reg_block()
.ctrl()
.modify(|_, w| w.enable().set_bit());
}
#[inline]
pub fn period(&self) -> Hertz {
self.common.current_period
}
#[inline(always)]
pub fn duty(&self) -> u16 {
self.common.current_duty
}
}
impl<Pin: TimPin, Tim: ValidTim> From<PwmPin<Pin, Tim, PwmA>> for ReducedPwmPin<PwmA>
where
(Pin, Tim): ValidTimAndPin<Pin, Tim>,
{
fn from(value: PwmPin<Pin, Tim, PwmA>) -> Self {
value.downgrade()
}
}
impl<Pin: TimPin, Tim: ValidTim> From<PwmPin<Pin, Tim, PwmB>> for ReducedPwmPin<PwmB>
where
(Pin, Tim): ValidTimAndPin<Pin, Tim>,
{
fn from(value: PwmPin<Pin, Tim, PwmB>) -> Self {
value.downgrade()
}
}
impl From<ReducedPwmPin<PwmA>> for ReducedPwmPin<PwmB> {
fn from(other: ReducedPwmPin<PwmA>) -> Self {
let mut pwmb = Self {
dyn_reg: other.dyn_reg,
common: other.common,
mode: PhantomData,
};
pwmb.enable_pwm_b();
pwmb
}
}
impl From<ReducedPwmPin<PwmB>> for ReducedPwmPin<PwmA> {
fn from(other: ReducedPwmPin<PwmB>) -> Self {
let mut pwmb = Self {
dyn_reg: other.dyn_reg,
common: other.common,
mode: PhantomData,
};
pwmb.enable_pwm_a();
pwmb
}
}
//==================================================================================================
// PWMB implementations
//==================================================================================================
impl<Pin: TimPin, Tim: ValidTim> PwmPin<Pin, Tim, PwmB>
where
(Pin, Tim): ValidTimAndPin<Pin, Tim>,
{
pub fn pwmb_lower_limit(&self) -> u16 {
self.inner.pwmb_lower_limit()
}
pub fn pwmb_upper_limit(&self) -> u16 {
self.inner.pwmb_upper_limit()
}
/// Set the lower limit for PWMB
///
/// The PWM signal will be 1 as long as the current RST counter is larger than
/// the lower limit. For example, with a lower limit of 0.5 and and an upper limit
/// of 0.7, Only a fixed period between 0.5 * period and 0.7 * period will be in a high
/// state
pub fn set_pwmb_lower_limit(&mut self, duty: u16) {
self.inner.set_pwmb_lower_limit(duty);
}
/// Set the higher limit for PWMB
///
/// The PWM signal will be 1 as long as the current RST counter is smaller than
/// the higher limit. For example, with a lower limit of 0.5 and and an upper limit
/// of 0.7, Only a fixed period between 0.5 * period and 0.7 * period will be in a high
/// state
pub fn set_pwmb_upper_limit(&mut self, duty: u16) {
self.inner.set_pwmb_upper_limit(duty);
}
}
impl ReducedPwmPin<PwmB> {
#[inline(always)]
pub fn pwmb_lower_limit(&self) -> u16 {
self.common.current_lower_limit
}
#[inline(always)]
pub fn pwmb_upper_limit(&self) -> u16 {
self.common.current_duty
}
/// Set the lower limit for PWMB
///
/// The PWM signal will be 1 as long as the current RST counter is larger than
/// the lower limit. For example, with a lower limit of 0.5 and and an upper limit
/// of 0.7, Only a fixed period between 0.5 * period and 0.7 * period will be in a high
/// state
#[inline(always)]
pub fn set_pwmb_lower_limit(&mut self, duty: u16) {
self.common.current_lower_limit = duty;
let pwmb_val: u64 = (self.common.current_rst_val as u64
* self.common.current_lower_limit as u64)
/ DUTY_MAX as u64;
self.dyn_reg
.reg_block()
.pwmb_value()
.write(|w| unsafe { w.bits(pwmb_val as u32) });
}
/// Set the higher limit for PWMB
///
/// The PWM signal will be 1 as long as the current RST counter is smaller than
/// the higher limit. For example, with a lower limit of 0.5 and and an upper limit
/// of 0.7, Only a fixed period between 0.5 * period and 0.7 * period will be in a high
/// state
pub fn set_pwmb_upper_limit(&mut self, duty: u16) {
self.common.current_duty = duty;
let pwma_val: u64 = (self.common.current_rst_val as u64 * self.common.current_duty as u64)
/ DUTY_MAX as u64;
self.dyn_reg
.reg_block()
.pwma_value()
.write(|w| unsafe { w.bits(pwma_val as u32) });
}
}
//==================================================================================================
// Embedded HAL implementation: PWMA only
//==================================================================================================
impl<Pin: TimPin, Tim: ValidTim> embedded_hal::pwm::ErrorType for PwmPin<Pin, Tim> {
type Error = Infallible;
}
impl embedded_hal::pwm::ErrorType for ReducedPwmPin {
type Error = Infallible;
}
impl embedded_hal::pwm::SetDutyCycle for ReducedPwmPin {
#[inline]
fn max_duty_cycle(&self) -> u16 {
DUTY_MAX
}
#[inline]
fn set_duty_cycle(&mut self, duty: u16) -> Result<(), Self::Error> {
self.common.current_duty = duty;
let pwma_val: u64 = (self.common.current_rst_val as u64
* (DUTY_MAX as u64 - self.common.current_duty as u64))
/ DUTY_MAX as u64;
self.dyn_reg
.reg_block()
.pwma_value()
.write(|w| unsafe { w.bits(pwma_val as u32) });
Ok(())
}
}
impl<Pin: TimPin, Tim: ValidTim> embedded_hal::pwm::SetDutyCycle for PwmPin<Pin, Tim> {
#[inline]
fn max_duty_cycle(&self) -> u16 {
DUTY_MAX
}
#[inline]
fn set_duty_cycle(&mut self, duty: u16) -> Result<(), Self::Error> {
self.inner.set_duty_cycle(duty)
}
}
/// Get the corresponding u16 duty cycle from a percent value ranging between 0.0 and 1.0.
///
/// Please note that this might load a lot of floating point code because this processor does not
/// have a FPU
pub fn get_duty_from_percent(percent: f32) -> u16 {
if percent > 1.0 {
DUTY_MAX
} else if percent <= 0.0 {
0
} else {
(percent * DUTY_MAX as f32) as u16
}
}
pub use vorago_shared_periphs::pwm::*;

File diff suppressed because it is too large Load Diff

View File

@ -2,904 +2,8 @@
//!
//! ## Examples
//!
//! - [Timer MS and Second Tick Example](https://github.com/us-irs/va416xx-rs/blob/main/examples/simple/examples/timer-ticks.rs)
use core::cell::Cell;
use cortex_m::asm;
use critical_section::Mutex;
use crate::clock::Clocks;
use crate::gpio::{
AltFunc1, AltFunc2, AltFunc3, DynPinId, Pin, PinId, PA0, PA1, PA10, PA11, PA12, PA13, PA14,
PA15, PA2, PA3, PA4, PA5, PA6, PA7, PB0, PB1, PB12, PB13, PB14, PB15, PB2, PB3, PB4, PC0, PC1,
PD10, PD11, PD12, PD13, PD14, PD15, PE0, PE1, PE12, PE13, PE14, PE15, PE2, PE3, PE4, PE5, PE6,
PE7, PE8, PE9, PF0, PF1, PF11, PF12, PF13, PF14, PF15, PF9, PG0, PG1, PG2, PG3, PG6,
};
#[cfg(not(feature = "va41628"))]
use crate::gpio::{
PB10, PB11, PB5, PB6, PB7, PB8, PB9, PD0, PD1, PD2, PD3, PD4, PD5, PD6, PD7, PD8, PD9, PE10,
PE11, PF10, PF2, PF3, PF4, PF5, PF6, PF7, PF8,
};
use crate::time::Hertz;
use crate::typelevel::Sealed;
use crate::{disable_nvic_interrupt, enable_nvic_interrupt, pac, prelude::*};
pub static MS_COUNTER: Mutex<Cell<u32>> = Mutex::new(Cell::new(0));
//! - [MS and second tick implementation](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/timer-ticks.rs)
//! - [Cascade feature example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/cascade.rs)
pub use vorago_shared_periphs::timer::*;
pub const TIM_IRQ_OFFSET: usize = 48;
/// Get the peripheral block of a TIM peripheral given the index.
///
/// This function panics if the given index is greater than 23.
///
/// # Safety
///
/// This returns a direct handle to the peripheral block, which allows to circumvent ownership
/// rules for the peripheral block. You have to ensure that the retrieved peripheral block is not
/// used by any other software component.
#[inline(always)]
pub const unsafe fn get_tim_raw(tim_idx: usize) -> &'static pac::tim0::RegisterBlock {
match tim_idx {
0 => unsafe { &*pac::Tim0::ptr() },
1 => unsafe { &*pac::Tim1::ptr() },
2 => unsafe { &*pac::Tim2::ptr() },
3 => unsafe { &*pac::Tim3::ptr() },
4 => unsafe { &*pac::Tim4::ptr() },
5 => unsafe { &*pac::Tim5::ptr() },
6 => unsafe { &*pac::Tim6::ptr() },
7 => unsafe { &*pac::Tim7::ptr() },
8 => unsafe { &*pac::Tim8::ptr() },
9 => unsafe { &*pac::Tim9::ptr() },
10 => unsafe { &*pac::Tim10::ptr() },
11 => unsafe { &*pac::Tim11::ptr() },
12 => unsafe { &*pac::Tim12::ptr() },
13 => unsafe { &*pac::Tim13::ptr() },
14 => unsafe { &*pac::Tim14::ptr() },
15 => unsafe { &*pac::Tim15::ptr() },
16 => unsafe { &*pac::Tim16::ptr() },
17 => unsafe { &*pac::Tim17::ptr() },
18 => unsafe { &*pac::Tim18::ptr() },
19 => unsafe { &*pac::Tim19::ptr() },
20 => unsafe { &*pac::Tim20::ptr() },
21 => unsafe { &*pac::Tim21::ptr() },
22 => unsafe { &*pac::Tim22::ptr() },
23 => unsafe { &*pac::Tim23::ptr() },
_ => {
panic!("invalid alarm timer index")
}
}
}
//==================================================================================================
// Defintions
//==================================================================================================
#[derive(Default, Debug, PartialEq, Eq, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct CascadeCtrl {
/// Enable Cascade 0 signal active as a requirement for counting
pub enb_start_src_csd0: bool,
/// Invert Cascade 0, making it active low
pub inv_csd0: bool,
/// Enable Cascade 1 signal active as a requirement for counting
pub enb_start_src_csd1: bool,
/// Invert Cascade 1, making it active low
pub inv_csd1: bool,
/// Specify required operation if both Cascade 0 and Cascade 1 are active.
/// 0 is a logical AND of both cascade signals, 1 is a logical OR
pub dual_csd_op: bool,
/// Enable trigger mode for Cascade 0. In trigger mode, couting will start with the selected
/// cascade signal active, but once the counter is active, cascade control will be ignored
pub trg_csd0: bool,
/// Trigger mode, identical to [`trg_csd0`](CascadeCtrl) but for Cascade 1
pub trg_csd1: bool,
/// Enable Cascade 2 signal active as a requirement to stop counting. This mode is similar
/// to the REQ_STOP control bit, but signalled by a Cascade source
pub enb_stop_src_csd2: bool,
/// Invert Cascade 2, making it active low
pub inv_csd2: bool,
/// The counter is automatically disabled if the corresponding Cascade 2 level-sensitive input
/// souce is active when the count reaches 0. If the counter is not 0, the cascade control is
/// ignored
pub trg_csd2: bool,
}
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum CascadeSel {
Sel0 = 0,
Sel1 = 1,
Sel2 = 2,
}
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct InvalidCascadeSourceId;
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum CascadeSource {
PortA(u8),
PortB(u8),
PortC(u8),
PortD(u8),
PortE(u8),
Tim(u8),
TxEv,
AdcIrq,
RomSbe,
RomMbe,
Ram0Sbe,
Ram0Mbe,
Ram1Sbe,
Ram2Mbe,
WdogIrq,
}
impl CascadeSource {
fn id(&self) -> Result<u8, InvalidCascadeSourceId> {
let port_check = |base: u8, id: u8| {
if id > 15 {
return Err(InvalidCascadeSourceId);
}
Ok(base + id)
};
match self {
CascadeSource::PortA(id) => port_check(0, *id),
CascadeSource::PortB(id) => port_check(16, *id),
CascadeSource::PortC(id) => port_check(32, *id),
CascadeSource::PortD(id) => port_check(48, *id),
CascadeSource::PortE(id) => port_check(65, *id),
CascadeSource::Tim(id) => {
if *id > 23 {
return Err(InvalidCascadeSourceId);
}
Ok(80 + id)
}
CascadeSource::TxEv => Ok(104),
CascadeSource::AdcIrq => Ok(105),
CascadeSource::RomSbe => Ok(106),
CascadeSource::RomMbe => Ok(106),
CascadeSource::Ram0Sbe => Ok(108),
CascadeSource::Ram0Mbe => Ok(109),
CascadeSource::Ram1Sbe => Ok(110),
CascadeSource::Ram2Mbe => Ok(111),
CascadeSource::WdogIrq => Ok(112),
}
}
}
//==================================================================================================
// Valid TIM and PIN combinations
//==================================================================================================
pub trait TimPin {
const DYN: DynPinId;
}
pub trait ValidTim {
// TIM ID ranging from 0 to 23 for 24 TIM peripherals
const ID: u8;
const IRQ: pac::Interrupt;
fn clock(clocks: &Clocks) -> Hertz {
if Self::ID <= 15 {
clocks.apb1()
} else {
clocks.apb2()
}
}
}
macro_rules! tim_markers {
(
$(
($TimX:path, $id:expr, $Irq:path),
)+
) => {
$(
impl ValidTim for $TimX {
const ID: u8 = $id;
const IRQ: pac::Interrupt = $Irq;
}
)+
};
}
pub const fn const_clock<Tim: ValidTim + ?Sized>(_: &Tim, clocks: &Clocks) -> Hertz {
if Tim::ID <= 15 {
clocks.apb1()
} else {
clocks.apb2()
}
}
tim_markers!(
(pac::Tim0, 0, pac::Interrupt::TIM0),
(pac::Tim1, 1, pac::Interrupt::TIM1),
(pac::Tim2, 2, pac::Interrupt::TIM2),
(pac::Tim3, 3, pac::Interrupt::TIM3),
(pac::Tim4, 4, pac::Interrupt::TIM4),
(pac::Tim5, 5, pac::Interrupt::TIM5),
(pac::Tim6, 6, pac::Interrupt::TIM6),
(pac::Tim7, 7, pac::Interrupt::TIM7),
(pac::Tim8, 8, pac::Interrupt::TIM8),
(pac::Tim9, 9, pac::Interrupt::TIM9),
(pac::Tim10, 10, pac::Interrupt::TIM10),
(pac::Tim11, 11, pac::Interrupt::TIM11),
(pac::Tim12, 12, pac::Interrupt::TIM12),
(pac::Tim13, 13, pac::Interrupt::TIM13),
(pac::Tim14, 14, pac::Interrupt::TIM14),
(pac::Tim15, 15, pac::Interrupt::TIM15),
(pac::Tim16, 16, pac::Interrupt::TIM16),
(pac::Tim17, 17, pac::Interrupt::TIM17),
(pac::Tim18, 18, pac::Interrupt::TIM18),
(pac::Tim19, 19, pac::Interrupt::TIM19),
(pac::Tim20, 20, pac::Interrupt::TIM20),
(pac::Tim21, 21, pac::Interrupt::TIM21),
(pac::Tim22, 22, pac::Interrupt::TIM22),
(pac::Tim23, 23, pac::Interrupt::TIM23),
);
pub trait ValidTimAndPin<Pin: TimPin, Tim: ValidTim>: Sealed {}
macro_rules! valid_pin_and_tims {
(
$(
($PinX:ident, $AltFunc:ident, $TimX:path $(, $meta: meta)?),
)+
) => {
$(
$(#[$meta])?
impl TimPin for Pin<$PinX, $AltFunc>
where
$PinX: PinId,
{
const DYN: DynPinId = $PinX::DYN;
}
$(#[$meta])?
impl<
PinInstance: TimPin,
Tim: ValidTim
> ValidTimAndPin<PinInstance, Tim> for (Pin<$PinX, $AltFunc>, $TimX)
where
Pin<$PinX, $AltFunc>: TimPin,
$PinX: PinId,
{
}
$(#[$meta])?
impl Sealed for (Pin<$PinX, $AltFunc>, $TimX) {}
)+
};
}
valid_pin_and_tims!(
(PA0, AltFunc1, pac::Tim0),
(PA1, AltFunc1, pac::Tim1),
(PA2, AltFunc1, pac::Tim2),
(PA3, AltFunc1, pac::Tim3),
(PA4, AltFunc1, pac::Tim4),
(PA5, AltFunc1, pac::Tim5),
(PA6, AltFunc1, pac::Tim6),
(PA7, AltFunc1, pac::Tim7),
(PA10, AltFunc2, pac::Tim23),
(PA11, AltFunc2, pac::Tim22),
(PA12, AltFunc2, pac::Tim21),
(PA13, AltFunc2, pac::Tim20),
(PA14, AltFunc2, pac::Tim19),
(PA15, AltFunc2, pac::Tim18),
(PB0, AltFunc2, pac::Tim17),
(PB1, AltFunc2, pac::Tim16),
(PB2, AltFunc2, pac::Tim15),
(PB3, AltFunc2, pac::Tim14),
(PB4, AltFunc2, pac::Tim13),
(PB5, AltFunc2, pac::Tim12, cfg(not(feature = "va41628"))),
(PB6, AltFunc2, pac::Tim11, cfg(not(feature = "va41628"))),
(PB7, AltFunc2, pac::Tim10, cfg(not(feature = "va41628"))),
(PB8, AltFunc2, pac::Tim9, cfg(not(feature = "va41628"))),
(PB9, AltFunc2, pac::Tim8, cfg(not(feature = "va41628"))),
(PB10, AltFunc2, pac::Tim7, cfg(not(feature = "va41628"))),
(PB11, AltFunc2, pac::Tim6, cfg(not(feature = "va41628"))),
(PB12, AltFunc2, pac::Tim5),
(PB13, AltFunc2, pac::Tim4),
(PB14, AltFunc2, pac::Tim3),
(PB15, AltFunc2, pac::Tim2),
(PC0, AltFunc2, pac::Tim1),
(PC1, AltFunc2, pac::Tim0),
(PD0, AltFunc2, pac::Tim0, cfg(not(feature = "va41628"))),
(PD1, AltFunc2, pac::Tim1, cfg(not(feature = "va41628"))),
(PD2, AltFunc2, pac::Tim2, cfg(not(feature = "va41628"))),
(PD3, AltFunc2, pac::Tim3, cfg(not(feature = "va41628"))),
(PD4, AltFunc2, pac::Tim4, cfg(not(feature = "va41628"))),
(PD5, AltFunc2, pac::Tim5, cfg(not(feature = "va41628"))),
(PD6, AltFunc2, pac::Tim6, cfg(not(feature = "va41628"))),
(PD7, AltFunc2, pac::Tim7, cfg(not(feature = "va41628"))),
(PD8, AltFunc2, pac::Tim8, cfg(not(feature = "va41628"))),
(PD9, AltFunc2, pac::Tim9, cfg(not(feature = "va41628"))),
(PD10, AltFunc2, pac::Tim10),
(PD11, AltFunc2, pac::Tim11),
(PD12, AltFunc2, pac::Tim12),
(PD13, AltFunc2, pac::Tim13),
(PD14, AltFunc2, pac::Tim14),
(PD15, AltFunc2, pac::Tim15),
(PE0, AltFunc2, pac::Tim16),
(PE1, AltFunc2, pac::Tim17),
(PE2, AltFunc2, pac::Tim18),
(PE3, AltFunc2, pac::Tim19),
(PE4, AltFunc2, pac::Tim20),
(PE5, AltFunc2, pac::Tim21),
(PE6, AltFunc2, pac::Tim22),
(PE7, AltFunc2, pac::Tim23),
(PE8, AltFunc3, pac::Tim16),
(PE9, AltFunc3, pac::Tim17),
(PE10, AltFunc3, pac::Tim18, cfg(not(feature = "va41628"))),
(PE11, AltFunc3, pac::Tim19, cfg(not(feature = "va41628"))),
(PE12, AltFunc3, pac::Tim20),
(PE13, AltFunc3, pac::Tim21),
(PE14, AltFunc3, pac::Tim22),
(PE15, AltFunc3, pac::Tim23),
(PF0, AltFunc3, pac::Tim0),
(PF1, AltFunc3, pac::Tim1),
(PF2, AltFunc3, pac::Tim2, cfg(not(feature = "va41628"))),
(PF3, AltFunc3, pac::Tim3, cfg(not(feature = "va41628"))),
(PF4, AltFunc3, pac::Tim4, cfg(not(feature = "va41628"))),
(PF5, AltFunc3, pac::Tim5, cfg(not(feature = "va41628"))),
(PF6, AltFunc3, pac::Tim6, cfg(not(feature = "va41628"))),
(PF7, AltFunc3, pac::Tim7, cfg(not(feature = "va41628"))),
(PF8, AltFunc3, pac::Tim8, cfg(not(feature = "va41628"))),
(PF9, AltFunc3, pac::Tim9),
(PF10, AltFunc3, pac::Tim10, cfg(not(feature = "va41628"))),
(PF11, AltFunc3, pac::Tim11),
(PF12, AltFunc3, pac::Tim12),
(PF13, AltFunc2, pac::Tim19),
(PF14, AltFunc2, pac::Tim20),
(PF15, AltFunc2, pac::Tim21),
(PG0, AltFunc2, pac::Tim22),
(PG1, AltFunc2, pac::Tim23),
(PG2, AltFunc1, pac::Tim9),
(PG3, AltFunc1, pac::Tim10),
(PG6, AltFunc1, pac::Tim12),
);
//==================================================================================================
// Register Interface for TIM registers and TIM pins
//==================================================================================================
/// Clear the reset bit of the TIM, holding it in reset
///
/// # Safety
///
/// Only the bit related to the corresponding TIM peripheral is modified
#[inline]
pub fn assert_tim_reset(syscfg: &mut pac::Sysconfig, tim_id: u8) {
syscfg
.tim_reset()
.modify(|r, w| unsafe { w.bits(r.bits() & !(1 << tim_id as u32)) });
}
#[inline]
pub fn deassert_tim_reset(syscfg: &mut pac::Sysconfig, tim_id: u8) {
syscfg
.tim_reset()
.modify(|r, w| unsafe { w.bits(r.bits() | (1 << tim_id as u32)) });
}
#[inline]
pub fn assert_tim_reset_for_two_cycles(syscfg: &mut pac::Sysconfig, tim_id: u8) {
assert_tim_reset(syscfg, tim_id);
asm::nop();
asm::nop();
deassert_tim_reset(syscfg, tim_id);
}
pub type TimRegBlock = pac::tim0::RegisterBlock;
/// Register interface.
///
/// This interface provides valid TIM pins a way to access their corresponding TIM
/// registers
///
/// # Safety
///
/// Users should only implement the [Self::tim_id] function. No default function
/// implementations should be overridden. The implementing type must also have
/// "control" over the corresponding pin ID, i.e. it must guarantee that a each
/// pin ID is a singleton.
pub unsafe trait TimRegInterface {
fn tim_id(&self) -> u8;
const PORT_BASE: *const pac::tim0::RegisterBlock = pac::Tim0::ptr() as *const _;
/// All 24 TIM blocks are identical. This helper functions returns the correct
/// memory mapped peripheral depending on the TIM ID.
#[inline(always)]
fn reg_block(&self) -> &TimRegBlock {
unsafe { &*Self::PORT_BASE.offset(self.tim_id() as isize) }
}
#[inline(always)]
fn mask_32(&self) -> u32 {
1 << self.tim_id()
}
/// Clear the reset bit of the TIM, holding it in reset
///
/// # Safety
///
/// Only the bit related to the corresponding TIM peripheral is modified
#[inline]
#[allow(dead_code)]
fn assert_tim_reset(&self, syscfg: &mut pac::Sysconfig) {
assert_tim_reset(syscfg, self.tim_id());
}
#[inline]
#[allow(dead_code)]
fn deassert_time_reset(&self, syscfg: &mut pac::Sysconfig) {
deassert_tim_reset(syscfg, self.tim_id());
}
}
unsafe impl<Tim: ValidTim> TimRegInterface for Tim {
fn tim_id(&self) -> u8 {
Tim::ID
}
}
/// Provide a safe register interface for [`ValidTimAndPin`]s
///
/// This `struct` takes ownership of a [`ValidTimAndPin`] and provides an API to
/// access the corresponding registers.
pub(super) struct TimAndPinRegister<Pin: TimPin, Tim: ValidTim> {
pin: Pin,
tim: Tim,
}
pub(super) struct TimRegister<TIM: ValidTim> {
tim: TIM,
}
impl<TIM: ValidTim> TimRegister<TIM> {
#[inline]
pub(super) unsafe fn new(tim: TIM) -> Self {
TimRegister { tim }
}
pub(super) fn release(self) -> TIM {
self.tim
}
}
unsafe impl<Tim: ValidTim> TimRegInterface for TimRegister<Tim> {
#[inline(always)]
fn tim_id(&self) -> u8 {
Tim::ID
}
}
impl<Pin: TimPin, Tim: ValidTim> TimAndPinRegister<Pin, Tim>
where
(Pin, Tim): ValidTimAndPin<Pin, Tim>,
{
#[inline]
pub(super) unsafe fn new(pin: Pin, tim: Tim) -> Self {
TimAndPinRegister { pin, tim }
}
pub(super) fn release(self) -> (Pin, Tim) {
(self.pin, self.tim)
}
}
unsafe impl<Pin: TimPin, Tim: ValidTim> TimRegInterface for TimAndPinRegister<Pin, Tim> {
#[inline(always)]
fn tim_id(&self) -> u8 {
Tim::ID
}
}
pub(crate) struct TimDynRegister {
pub(crate) tim_id: u8,
#[allow(dead_code)]
pub(crate) pin_id: DynPinId,
}
impl<Pin: TimPin, Tim: ValidTim> From<TimAndPinRegister<Pin, Tim>> for TimDynRegister {
fn from(_reg: TimAndPinRegister<Pin, Tim>) -> Self {
Self {
tim_id: Tim::ID,
pin_id: Pin::DYN,
}
}
}
unsafe impl TimRegInterface for TimDynRegister {
#[inline(always)]
fn tim_id(&self) -> u8 {
self.tim_id
}
}
//==================================================================================================
// Timers
//==================================================================================================
/// Hardware timers.
///
/// These timers also implement the [embedded_hal::delay::DelayNs] trait and can be used to delay
/// with a higher resolution compared to the Cortex-M systick delays.
pub struct CountdownTimer<TIM: ValidTim> {
tim: TimRegister<TIM>,
curr_freq: Hertz,
clock: Hertz,
rst_val: u32,
last_cnt: u32,
listening: bool,
}
#[inline]
pub fn enable_tim_clk(syscfg: &mut pac::Sysconfig, idx: u8) {
syscfg
.tim_clk_enable()
.modify(|r, w| unsafe { w.bits(r.bits() | (1 << idx)) });
}
unsafe impl<Tim: ValidTim> TimRegInterface for CountdownTimer<Tim> {
#[inline]
fn tim_id(&self) -> u8 {
Tim::ID
}
}
impl<Tim: ValidTim> CountdownTimer<Tim> {
/// Create a new countdown timer, but does not start it.
///
/// You can use [Self::start] to start the countdown timer, and you may optionally call
/// [Self::listen] to enable interrupts for the TIM peripheral as well.
pub fn new(syscfg: &mut pac::Sysconfig, tim: Tim, clocks: &Clocks) -> Self {
enable_tim_clk(syscfg, Tim::ID);
assert_tim_reset(syscfg, Tim::ID);
cortex_m::asm::nop();
cortex_m::asm::nop();
deassert_tim_reset(syscfg, Tim::ID);
CountdownTimer {
tim: unsafe { TimRegister::new(tim) },
clock: Tim::clock(clocks),
rst_val: 0,
curr_freq: 0_u32.Hz(),
listening: false,
last_cnt: 0,
}
}
#[inline]
pub fn start(&mut self, timeout: impl Into<Hertz>) {
self.load(timeout);
self.enable();
}
/// Listen for events. Depending on the IRQ configuration, this also activates the IRQ in the
/// IRQSEL peripheral for the provided interrupt and unmasks the interrupt
#[inline]
pub fn listen(&mut self) {
self.listening = true;
self.enable_interrupt();
unsafe { enable_nvic_interrupt(Tim::IRQ) }
}
/// Return `Ok` if the timer has wrapped. Peripheral will automatically clear the
/// flag and restart the time if configured correctly
pub fn wait(&mut self) -> nb::Result<(), void::Void> {
let cnt = self.tim.reg_block().cnt_value().read().bits();
if (cnt > self.last_cnt) || cnt == 0 {
self.last_cnt = self.rst_val;
Ok(())
} else {
self.last_cnt = cnt;
Err(nb::Error::WouldBlock)
}
}
#[inline]
pub fn stop(&mut self) {
self.tim
.reg_block()
.ctrl()
.write(|w| w.enable().clear_bit());
}
#[inline]
pub fn unlisten(&mut self) {
self.listening = true;
self.disable_interrupt();
disable_nvic_interrupt(Tim::IRQ);
}
#[inline(always)]
pub fn enable_interrupt(&mut self) {
self.tim
.reg_block()
.ctrl()
.modify(|_, w| w.irq_enb().set_bit());
}
#[inline(always)]
pub fn disable_interrupt(&mut self) {
self.tim
.reg_block()
.ctrl()
.modify(|_, w| w.irq_enb().clear_bit());
}
#[inline]
pub fn release(self, syscfg: &mut pac::Sysconfig) -> Tim {
self.tim
.reg_block()
.ctrl()
.write(|w| w.enable().clear_bit());
syscfg
.tim_clk_enable()
.modify(|r, w| unsafe { w.bits(r.bits() & !(1 << Tim::ID)) });
self.tim.release()
}
/// Load the count down timer with a timeout but do not start it.
pub fn load(&mut self, timeout: impl Into<Hertz>) {
self.tim
.reg_block()
.ctrl()
.modify(|_, w| w.enable().clear_bit());
self.curr_freq = timeout.into();
self.rst_val = (self.clock.raw() / self.curr_freq.raw()) - 1;
self.set_reload(self.rst_val);
// Decrementing counter, to set the reset value.
self.set_count(self.rst_val);
}
#[inline(always)]
pub fn set_reload(&mut self, val: u32) {
self.tim
.reg_block()
.rst_value()
.write(|w| unsafe { w.bits(val) });
}
#[inline(always)]
pub fn set_count(&mut self, val: u32) {
self.tim
.reg_block()
.cnt_value()
.write(|w| unsafe { w.bits(val) });
}
#[inline(always)]
pub fn count(&self) -> u32 {
self.tim.reg_block().cnt_value().read().bits()
}
#[inline(always)]
pub fn enable(&mut self) {
self.tim
.reg_block()
.enable()
.write(|w| unsafe { w.bits(1) });
}
#[inline(always)]
pub fn disable(&mut self) {
self.tim
.reg_block()
.ctrl()
.modify(|_, w| w.enable().clear_bit());
}
/// Disable the counter, setting both enable and active bit to 0
#[inline]
pub fn auto_disable(self, enable: bool) -> Self {
if enable {
self.tim
.reg_block()
.ctrl()
.modify(|_, w| w.auto_disable().set_bit());
} else {
self.tim
.reg_block()
.ctrl()
.modify(|_, w| w.auto_disable().clear_bit());
}
self
}
/// This option only applies when the Auto-Disable functionality is 0.
///
/// The active bit is changed to 0 when count reaches 0, but the counter stays
/// enabled. When Auto-Disable is 1, Auto-Deactivate is implied
#[inline]
pub fn auto_deactivate(self, enable: bool) -> Self {
if enable {
self.tim
.reg_block()
.ctrl()
.modify(|_, w| w.auto_deactivate().set_bit());
} else {
self.tim
.reg_block()
.ctrl()
.modify(|_, w| w.auto_deactivate().clear_bit());
}
self
}
/// Configure the cascade parameters
#[inline]
pub fn cascade_control(&mut self, ctrl: CascadeCtrl) {
self.tim.reg_block().csd_ctrl().write(|w| {
w.csden0().bit(ctrl.enb_start_src_csd0);
w.csdinv0().bit(ctrl.inv_csd0);
w.csden1().bit(ctrl.enb_start_src_csd1);
w.csdinv1().bit(ctrl.inv_csd1);
w.dcasop().bit(ctrl.dual_csd_op);
w.csdtrg0().bit(ctrl.trg_csd0);
w.csdtrg1().bit(ctrl.trg_csd1);
w.csden2().bit(ctrl.enb_stop_src_csd2);
w.csdinv2().bit(ctrl.inv_csd2);
w.csdtrg2().bit(ctrl.trg_csd2)
});
}
#[inline]
pub fn cascade_0_source(&mut self, src: CascadeSource) -> Result<(), InvalidCascadeSourceId> {
let id = src.id()?;
self.tim
.reg_block()
.cascade0()
.write(|w| unsafe { w.cassel().bits(id) });
Ok(())
}
#[inline]
pub fn cascade_1_source(&mut self, src: CascadeSource) -> Result<(), InvalidCascadeSourceId> {
let id = src.id()?;
self.tim
.reg_block()
.cascade1()
.write(|w| unsafe { w.cassel().bits(id) });
Ok(())
}
#[inline]
pub fn cascade_2_source(&mut self, src: CascadeSource) -> Result<(), InvalidCascadeSourceId> {
let id = src.id()?;
self.tim
.reg_block()
.cascade2()
.write(|w| unsafe { w.cassel().bits(id) });
Ok(())
}
#[inline]
pub fn curr_freq(&self) -> Hertz {
self.curr_freq
}
#[inline]
pub fn listening(&self) -> bool {
self.listening
}
}
impl<Tim: ValidTim> embedded_hal::delay::DelayNs for CountdownTimer<Tim> {
fn delay_ns(&mut self, ns: u32) {
let ticks = (u64::from(ns)) * (u64::from(self.clock.raw())) / 1_000_000_000;
let full_cycles = ticks >> 32;
let mut last_count;
let mut new_count;
if full_cycles > 0 {
self.set_reload(u32::MAX);
self.set_count(u32::MAX);
self.enable();
for _ in 0..full_cycles {
// Always ensure that both values are the same at the start.
new_count = self.count();
last_count = new_count;
loop {
new_count = self.count();
if new_count == 0 {
// Wait till timer has wrapped.
while self.count() == 0 {
cortex_m::asm::nop()
}
break;
}
// Timer has definitely wrapped.
if new_count > last_count {
break;
}
last_count = new_count;
}
}
}
let ticks = (ticks & u32::MAX as u64) as u32;
self.disable();
if ticks > 1 {
self.set_reload(ticks);
self.set_count(ticks);
self.enable();
last_count = ticks;
loop {
new_count = self.count();
if new_count == 0 || (new_count > last_count) {
break;
}
last_count = new_count;
}
}
self.disable();
}
}
//==================================================================================================
// MS tick implementations
//==================================================================================================
// Set up a millisecond timer on TIM0. Please note that the user still has to provide an IRQ handler
// which should call [default_ms_irq_handler].
pub fn set_up_ms_tick<Tim: ValidTim>(
sys_cfg: &mut pac::Sysconfig,
tim: Tim,
clocks: &Clocks,
) -> CountdownTimer<Tim> {
let mut ms_timer = CountdownTimer::new(sys_cfg, tim, clocks);
ms_timer.listen();
ms_timer.start(1000.Hz());
ms_timer
}
/// This function can be called in a specified interrupt handler to increment
/// the MS counter
pub fn default_ms_irq_handler() {
critical_section::with(|cs| {
let mut ms = MS_COUNTER.borrow(cs).get();
ms += 1;
MS_COUNTER.borrow(cs).set(ms);
});
}
/// Get the current MS tick count
pub fn get_ms_ticks() -> u32 {
critical_section::with(|cs| MS_COUNTER.borrow(cs).get())
}
pub struct DelayMs<Tim: ValidTim = pac::Tim0>(CountdownTimer<Tim>);
impl<Tim: ValidTim> DelayMs<Tim> {
pub fn new(timer: CountdownTimer<Tim>) -> Option<Self> {
if timer.curr_freq() != Hertz::from_raw(1000) || !timer.listening() {
return None;
}
Some(Self(timer))
}
}
/// This assumes that the user has already set up a MS tick timer with [set_up_ms_tick]
impl embedded_hal::delay::DelayNs for DelayMs {
fn delay_ns(&mut self, ns: u32) {
let ns_as_ms = ns / 1_000_000;
if self.0.curr_freq() != Hertz::from_raw(1000) || !self.0.listening() {
return;
}
let start_time = get_ms_ticks();
while get_ms_ticks() - start_time < ns_as_ms {
cortex_m::asm::nop();
}
}
}

View File

@ -1,155 +0,0 @@
//! Module supporting type-level programming
//!
//! This module is identical to the
//! [atsamd typelevel](https://docs.rs/atsamd-hal/latest/atsamd_hal/typelevel/index.html).
use core::ops::{Add, Sub};
use typenum::{Add1, Bit, Sub1, UInt, Unsigned, B1, U0};
mod private {
/// Super trait used to mark traits with an exhaustive set of
/// implementations
pub trait Sealed {}
impl Sealed for u8 {}
impl Sealed for i8 {}
impl Sealed for u16 {}
impl Sealed for i16 {}
impl Sealed for u32 {}
impl Sealed for i32 {}
impl Sealed for f32 {}
/// Mapping from an instance of a countable type to its successor
pub trait Increment {
/// Successor type of `Self`
type Inc;
/// Consume an instance of `Self` and return its successor
fn inc(self) -> Self::Inc;
}
/// Mapping from an instance of a countable type to its predecessor
pub trait Decrement {
/// Predecessor type of `Self`
type Dec;
/// Consume an instance of `Self` and return its predecessor
fn dec(self) -> Self::Dec;
}
}
pub(crate) use private::Decrement as PrivateDecrement;
pub(crate) use private::Increment as PrivateIncrement;
pub(crate) use private::Sealed;
/// Type-level version of the [`None`] variant
#[derive(Default)]
pub struct NoneT;
impl Sealed for NoneT {}
//==============================================================================
// Is
//==============================================================================
/// Marker trait for type identity
///
/// This trait is used as part of the [`AnyKind`] trait pattern. It represents
/// the concept of type identity, because all implementors have
/// `<Self as Is>::Type == Self`. When used as a trait bound with a specific
/// type, it guarantees that the corresponding type parameter is exactly the
/// specific type. Stated differently, it guarantees that `T == Specific` in
/// the following example.
///
/// ```ignore
/// where T: Is<Type = Specific>
/// ```
///
/// 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<T>(mut any: T)
/// where
/// T: Is<Type = Specific>,
/// {
/// let specific_mut: &mut Specific = any.as_mut();
/// let specific_ref: &Specific = any.as_ref();
/// let specific: Specific = any.into();
/// }
/// ```
///
/// [`AnyKind`]: #anykind-trait-pattern
pub trait Is
where
Self: Sealed,
Self: From<IsType<Self>>,
Self: Into<IsType<Self>>,
Self: AsRef<IsType<Self>>,
Self: AsMut<IsType<Self>>,
{
type Type;
}
/// Type alias for [`Is::Type`]
pub type IsType<T> = <T as Is>::Type;
impl<T> Is for T
where
T: Sealed + AsRef<T> + AsMut<T>,
{
type Type = T;
}
//==============================================================================
// Counting
//==============================================================================
/// Implement `Sealed` for [`U0`]
impl Sealed for U0 {}
/// Implement `Sealed` for all type-level, [`Unsigned`] integers *except* [`U0`]
impl<U: Unsigned, B: Bit> Sealed for UInt<U, B> {}
/// Trait mapping each countable type to its successor
///
/// This trait maps each countable type to its corresponding successor type. The
/// actual implementation of this trait is contained within `PrivateIncrement`.
/// Access to `PrivateIncrement` is restricted, so that safe HAL APIs can be
/// built with it.
pub trait Increment: PrivateIncrement {}
impl<T: PrivateIncrement> Increment for T {}
/// Trait mapping each countable type to its predecessor
///
/// This trait maps each countable type to its corresponding predecessor type.
/// The actual implementation of this trait is contained within
/// `PrivateDecrement`. Access to `PrivateDecrement` is restricted, so that safe
/// HAL APIs can be built with it.
pub trait Decrement: PrivateDecrement {}
impl<T: PrivateDecrement> Decrement for T {}
impl<N> PrivateIncrement for N
where
N: Unsigned + Add<B1>,
Add1<N>: Unsigned,
{
type Inc = Add1<N>;
#[inline]
fn inc(self) -> Self::Inc {
Self::Inc::default()
}
}
impl<N> PrivateDecrement for N
where
N: Unsigned + Sub<B1>,
Sub1<N>: Unsigned,
{
type Dec = Sub1<N>;
#[inline]
fn dec(self) -> Self::Dec {
Self::Dec::default()
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,448 +0,0 @@
//! # Async UART reception functionality for the VA416xx family.
//!
//! This module provides the [RxAsync] and [RxAsyncOverwriting] struct which both implement the
//! [embedded_io_async::Read] trait.
//! This trait allows for asynchronous reception of data streams. Please note that this module does
//! not specify/declare the interrupt handlers which must be provided for async support to work.
//! However, it provides two interrupt handlers:
//!
//! - [on_interrupt_rx]
//! - [on_interrupt_rx_overwriting]
//!
//! The first two are used for the [RxAsync] struct, while the latter two are used with the
//! [RxAsyncOverwriting] struct. The later two will overwrite old values in the used ring buffer.
//!
//! Error handling is performed in the user interrupt handler by checking the [AsyncUartErrors]
//! structure returned by the interrupt handlers.
//!
//! # Example
//!
//! - [Async UART RX example](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples/embassy/src/bin/async-uart-rx.rs)
use core::{cell::RefCell, convert::Infallible, future::Future, sync::atomic::Ordering};
use critical_section::Mutex;
use embassy_sync::waitqueue::AtomicWaker;
use embedded_io::ErrorType;
use portable_atomic::AtomicBool;
use va416xx::uart0 as uart_base;
use crate::enable_nvic_interrupt;
use super::{Bank, Instance, Rx, UartErrors};
static UART_RX_WAKERS: [AtomicWaker; 3] = [const { AtomicWaker::new() }; 3];
static RX_READ_ACTIVE: [AtomicBool; 3] = [const { AtomicBool::new(false) }; 3];
static RX_HAS_DATA: [AtomicBool; 3] = [const { AtomicBool::new(false) }; 3];
struct RxFuture {
uart_idx: usize,
}
impl RxFuture {
pub fn new<Uart: Instance>(_rx: &mut Rx<Uart>) -> Self {
RX_READ_ACTIVE[Uart::IDX as usize].store(true, Ordering::Relaxed);
Self {
uart_idx: Uart::IDX as usize,
}
}
}
impl Future for RxFuture {
type Output = Result<(), Infallible>;
fn poll(
self: core::pin::Pin<&mut Self>,
cx: &mut core::task::Context<'_>,
) -> core::task::Poll<Self::Output> {
UART_RX_WAKERS[self.uart_idx].register(cx.waker());
if RX_HAS_DATA[self.uart_idx].load(Ordering::Relaxed) {
return core::task::Poll::Ready(Ok(()));
}
core::task::Poll::Pending
}
}
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct AsyncUartErrors {
/// Queue has overflowed, data might have been lost.
pub queue_overflow: bool,
/// UART errors.
pub uart_errors: UartErrors,
}
fn on_interrupt_handle_rx_errors(uart: &'static uart_base::RegisterBlock) -> Option<UartErrors> {
let rx_status = uart.rxstatus().read();
if rx_status.rxovr().bit_is_set()
|| rx_status.rxfrm().bit_is_set()
|| rx_status.rxpar().bit_is_set()
{
let mut errors_val = UartErrors::default();
if rx_status.rxovr().bit_is_set() {
errors_val.overflow = true;
}
if rx_status.rxfrm().bit_is_set() {
errors_val.framing = true;
}
if rx_status.rxpar().bit_is_set() {
errors_val.parity = true;
}
return Some(errors_val);
}
None
}
fn on_interrupt_rx_common_post_processing(
bank: Bank,
rx_enabled: bool,
read_some_data: bool,
irq_end: u32,
) -> Option<UartErrors> {
let idx = bank as usize;
if read_some_data {
RX_HAS_DATA[idx].store(true, Ordering::Relaxed);
if RX_READ_ACTIVE[idx].load(Ordering::Relaxed) {
UART_RX_WAKERS[idx].wake();
}
}
let mut errors = None;
let uart_regs = unsafe { bank.reg_block() };
// Check for RX errors
if rx_enabled {
errors = on_interrupt_handle_rx_errors(uart_regs);
}
// Clear the interrupt status bits
uart_regs.irq_clr().write(|w| unsafe { w.bits(irq_end) });
errors
}
/// Interrupt handler with overwriting behaviour when the ring buffer is full.
///
/// Should be called in the user interrupt handler to enable
/// asynchronous reception. This variant will overwrite old data in the ring buffer in case
/// the ring buffer is full.
pub fn on_interrupt_rx_overwriting<const N: usize>(
bank: Bank,
prod: &mut heapless::spsc::Producer<u8, N>,
shared_consumer: &Mutex<RefCell<Option<heapless::spsc::Consumer<'static, u8, N>>>>,
) -> Result<(), AsyncUartErrors> {
on_interrupt_rx_async_heapless_queue_overwriting(bank, prod, shared_consumer)
}
pub fn on_interrupt_rx_async_heapless_queue_overwriting<const N: usize>(
bank: Bank,
prod: &mut heapless::spsc::Producer<u8, N>,
shared_consumer: &Mutex<RefCell<Option<heapless::spsc::Consumer<'static, u8, N>>>>,
) -> Result<(), AsyncUartErrors> {
let uart_regs = unsafe { bank.reg_block() };
let irq_end = uart_regs.irq_end().read();
let enb_status = uart_regs.enable().read();
let rx_enabled = enb_status.rxenable().bit_is_set();
let mut read_some_data = false;
let mut queue_overflow = false;
// Half-Full interrupt. We have a guaranteed amount of data we can read.
if irq_end.irq_rx().bit_is_set() {
let available_bytes = uart_regs.rxfifoirqtrg().read().bits() as usize;
// If this interrupt bit is set, the trigger level is available at the very least.
// Read everything as fast as possible
for _ in 0..available_bytes {
let byte = uart_regs.data().read().bits();
if !prod.ready() {
queue_overflow = true;
critical_section::with(|cs| {
let mut cons_ref = shared_consumer.borrow(cs).borrow_mut();
cons_ref.as_mut().unwrap().dequeue();
});
}
prod.enqueue(byte as u8).ok();
}
read_some_data = true;
}
// Timeout, empty the FIFO completely.
if irq_end.irq_rx_to().bit_is_set() {
while uart_regs.rxstatus().read().rdavl().bit_is_set() {
// While there is data in the FIFO, write it into the reception buffer
let byte = uart_regs.data().read().bits();
if !prod.ready() {
queue_overflow = true;
critical_section::with(|cs| {
let mut cons_ref = shared_consumer.borrow(cs).borrow_mut();
cons_ref.as_mut().unwrap().dequeue();
});
}
prod.enqueue(byte as u8).ok();
}
read_some_data = true;
}
let uart_errors =
on_interrupt_rx_common_post_processing(bank, rx_enabled, read_some_data, irq_end.bits());
if uart_errors.is_some() || queue_overflow {
return Err(AsyncUartErrors {
queue_overflow,
uart_errors: uart_errors.unwrap_or_default(),
});
}
Ok(())
}
/// Interrupt handler for asynchronous RX operations.
///
/// Should be called in the user interrupt handler to enable asynchronous reception.
pub fn on_interrupt_rx<const N: usize>(
bank: Bank,
prod: &mut heapless::spsc::Producer<'_, u8, N>,
) -> Result<(), AsyncUartErrors> {
on_interrupt_rx_async_heapless_queue(bank, prod)
}
pub fn on_interrupt_rx_async_heapless_queue<const N: usize>(
bank: Bank,
prod: &mut heapless::spsc::Producer<'_, u8, N>,
) -> Result<(), AsyncUartErrors> {
let uart = unsafe { bank.reg_block() };
let irq_end = uart.irq_end().read();
let enb_status = uart.enable().read();
let rx_enabled = enb_status.rxenable().bit_is_set();
let mut read_some_data = false;
let mut queue_overflow = false;
// Half-Full interrupt. We have a guaranteed amount of data we can read.
if irq_end.irq_rx().bit_is_set() {
let available_bytes = uart.rxfifoirqtrg().read().bits() as usize;
// If this interrupt bit is set, the trigger level is available at the very least.
// Read everything as fast as possible
for _ in 0..available_bytes {
let byte = uart.data().read().bits();
if !prod.ready() {
queue_overflow = true;
}
prod.enqueue(byte as u8).ok();
}
read_some_data = true;
}
// Timeout, empty the FIFO completely.
if irq_end.irq_rx_to().bit_is_set() {
while uart.rxstatus().read().rdavl().bit_is_set() {
// While there is data in the FIFO, write it into the reception buffer
let byte = uart.data().read().bits();
if !prod.ready() {
queue_overflow = true;
}
prod.enqueue(byte as u8).ok();
}
read_some_data = true;
}
let uart_errors =
on_interrupt_rx_common_post_processing(bank, rx_enabled, read_some_data, irq_end.bits());
if uart_errors.is_some() || queue_overflow {
return Err(AsyncUartErrors {
queue_overflow,
uart_errors: uart_errors.unwrap_or_default(),
});
}
Ok(())
}
struct ActiveReadGuard(usize);
impl Drop for ActiveReadGuard {
fn drop(&mut self) {
RX_READ_ACTIVE[self.0].store(false, Ordering::Relaxed);
}
}
struct RxAsyncInner<Uart: Instance, const N: usize> {
rx: Rx<Uart>,
pub queue: heapless::spsc::Consumer<'static, u8, N>,
}
/// Core data structure to allow asynchronous UART reception.
///
/// If the ring buffer becomes full, data will be lost.
pub struct RxAsync<Uart: Instance, const N: usize>(Option<RxAsyncInner<Uart, N>>);
impl<Uart: Instance, const N: usize> ErrorType for RxAsync<Uart, N> {
/// Error reporting is done using the result of the interrupt functions.
type Error = Infallible;
}
fn stop_async_rx<Uart: Instance>(rx: &mut Rx<Uart>) {
rx.disable_interrupts();
rx.disable();
unsafe {
enable_nvic_interrupt(Uart::IRQ_RX);
}
rx.clear_fifo();
}
impl<Uart: Instance, const N: usize> RxAsync<Uart, N> {
/// Create a new asynchronous receiver.
///
/// The passed [heapless::spsc::Consumer] will be used to asynchronously receive data which
/// is filled by the interrupt handler [on_interrupt_rx].
pub fn new(mut rx: Rx<Uart>, queue: heapless::spsc::Consumer<'static, u8, N>) -> Self {
rx.disable_interrupts();
rx.disable();
rx.clear_fifo();
// Enable those together.
critical_section::with(|_| {
unsafe {
enable_nvic_interrupt(Uart::IRQ_RX);
}
rx.enable_interrupts();
rx.enable();
});
Self(Some(RxAsyncInner { rx, queue }))
}
pub fn stop(&mut self) {
stop_async_rx(&mut self.0.as_mut().unwrap().rx);
}
pub fn release(mut self) -> (Rx<Uart>, heapless::spsc::Consumer<'static, u8, N>) {
self.stop();
let inner = self.0.take().unwrap();
(inner.rx, inner.queue)
}
}
impl<Uart: Instance, const N: usize> Drop for RxAsync<Uart, N> {
fn drop(&mut self) {
self.stop();
}
}
impl<Uart: Instance, const N: usize> embedded_io_async::Read for RxAsync<Uart, N> {
async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
// Need to wait for the IRQ to read data and set this flag. If the queue is not
// empty, we can read data immediately.
if self.0.as_ref().unwrap().queue.len() == 0 {
RX_HAS_DATA[Uart::IDX as usize].store(false, Ordering::Relaxed);
}
let _guard = ActiveReadGuard(Uart::IDX as usize);
let mut handle_data_in_queue = |consumer: &mut heapless::spsc::Consumer<'static, u8, N>| {
let data_to_read = consumer.len().min(buf.len());
for byte in buf.iter_mut().take(data_to_read) {
// We own the consumer and we checked that the amount of data is guaranteed to be available.
*byte = unsafe { consumer.dequeue_unchecked() };
}
data_to_read
};
let mut_ref = self.0.as_mut().unwrap();
let fut = RxFuture::new(&mut mut_ref.rx);
// Data is available, so read that data immediately.
let read_data = handle_data_in_queue(&mut mut_ref.queue);
if read_data > 0 {
return Ok(read_data);
}
// Await data.
let _ = fut.await;
Ok(handle_data_in_queue(&mut mut_ref.queue))
}
}
struct RxAsyncOverwritingInner<Uart: Instance, const N: usize> {
rx: Rx<Uart>,
pub shared_consumer: &'static Mutex<RefCell<Option<heapless::spsc::Consumer<'static, u8, N>>>>,
}
/// Core data structure to allow asynchronous UART reception.
///
/// If the ring buffer becomes full, the oldest data will be overwritten when using the
/// [on_interrupt_rx_overwriting] interrupt handlers.
pub struct RxAsyncOverwriting<Uart: Instance, const N: usize>(
Option<RxAsyncOverwritingInner<Uart, N>>,
);
impl<Uart: Instance, const N: usize> ErrorType for RxAsyncOverwriting<Uart, N> {
/// Error reporting is done using the result of the interrupt functions.
type Error = Infallible;
}
impl<Uart: Instance, const N: usize> RxAsyncOverwriting<Uart, N> {
/// Create a new asynchronous receiver.
///
/// The passed shared [heapless::spsc::Consumer] will be used to asynchronously receive data
/// which is filled by the interrupt handler. The shared property allows using it in the
/// interrupt handler to overwrite old data.
pub fn new(
mut rx: Rx<Uart>,
shared_consumer: &'static Mutex<RefCell<Option<heapless::spsc::Consumer<'static, u8, N>>>>,
) -> Self {
rx.disable_interrupts();
rx.disable();
rx.clear_fifo();
// Enable those together.
critical_section::with(|_| {
rx.enable_interrupts();
rx.enable();
});
Self(Some(RxAsyncOverwritingInner {
rx,
shared_consumer,
}))
}
pub fn stop(&mut self) {
stop_async_rx(&mut self.0.as_mut().unwrap().rx);
}
pub fn release(mut self) -> Rx<Uart> {
self.stop();
let inner = self.0.take().unwrap();
inner.rx
}
}
impl<Uart: Instance, const N: usize> Drop for RxAsyncOverwriting<Uart, N> {
fn drop(&mut self) {
self.stop();
}
}
impl<Uart: Instance, const N: usize> embedded_io_async::Read for RxAsyncOverwriting<Uart, N> {
async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
// Need to wait for the IRQ to read data and set this flag. If the queue is not
// empty, we can read data immediately.
critical_section::with(|cs| {
let queue = self.0.as_ref().unwrap().shared_consumer.borrow(cs);
if queue.borrow().as_ref().unwrap().len() == 0 {
RX_HAS_DATA[Uart::IDX as usize].store(false, Ordering::Relaxed);
}
});
let _guard = ActiveReadGuard(Uart::IDX as usize);
let mut handle_data_in_queue = |inner: &mut RxAsyncOverwritingInner<Uart, N>| {
critical_section::with(|cs| {
let mut consumer_ref = inner.shared_consumer.borrow(cs).borrow_mut();
let consumer = consumer_ref.as_mut().unwrap();
let data_to_read = consumer.len().min(buf.len());
for byte in buf.iter_mut().take(data_to_read) {
// We own the consumer and we checked that the amount of data is guaranteed to be available.
*byte = unsafe { consumer.dequeue_unchecked() };
}
data_to_read
})
};
let fut = RxFuture::new(&mut self.0.as_mut().unwrap().rx);
// Data is available, so read that data immediately.
let read_data = handle_data_in_queue(self.0.as_mut().unwrap());
if read_data > 0 {
return Ok(read_data);
}
// Await data.
let _ = fut.await;
let read_data = handle_data_in_queue(self.0.as_mut().unwrap());
Ok(read_data)
}
}

View File

@ -1,263 +0,0 @@
//! # Async UART transmission functionality for the VA416xx family.
//!
//! This module provides the [TxAsync] struct which implements the [embedded_io_async::Write] trait.
//! This trait allows for asynchronous sending of data streams. Please note that this module does
//! not specify/declare the interrupt handlers which must be provided for async support to work.
//! However, it the [on_interrupt_tx] interrupt handler.
//!
//! This handler should be called in ALL user interrupt handlers which handle UART TX interrupts
//! for a given UART bank.
//!
//! # Example
//!
//! - [Async UART TX example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/embassy/src/bin/async-uart-tx.rs)
use core::{cell::RefCell, future::Future};
use critical_section::Mutex;
use embassy_sync::waitqueue::AtomicWaker;
use embedded_io_async::Write;
use portable_atomic::AtomicBool;
use super::*;
static UART_TX_WAKERS: [AtomicWaker; 3] = [const { AtomicWaker::new() }; 3];
static TX_CONTEXTS: [Mutex<RefCell<TxContext>>; 3] =
[const { Mutex::new(RefCell::new(TxContext::new())) }; 3];
// Completion flag. Kept outside of the context structure as an atomic to avoid
// critical section.
static TX_DONE: [AtomicBool; 3] = [const { AtomicBool::new(false) }; 3];
/// This is a generic interrupt handler to handle asynchronous UART TX operations for a given
/// UART bank.
///
/// The user has to call this once in the interrupt handler responsible for the TX interrupts on
/// the given UART bank.
pub fn on_interrupt_tx(bank: Bank) {
let uart = unsafe { bank.reg_block() };
let idx = bank as usize;
let irq_enb = uart.irq_enb().read();
// IRQ is not related to TX.
if irq_enb.irq_tx().bit_is_clear() || irq_enb.irq_tx_empty().bit_is_clear() {
return;
}
let tx_status = uart.txstatus().read();
let unexpected_overrun = tx_status.wrlost().bit_is_set();
let mut context = critical_section::with(|cs| {
let context_ref = TX_CONTEXTS[idx].borrow(cs);
*context_ref.borrow()
});
context.tx_overrun = unexpected_overrun;
if context.progress >= context.slice.len && !tx_status.wrbusy().bit_is_set() {
uart.irq_enb().modify(|_, w| {
w.irq_tx().clear_bit();
w.irq_tx_empty().clear_bit();
w.irq_tx_status().clear_bit()
});
uart.enable().modify(|_, w| w.txenable().clear_bit());
// Write back updated context structure.
critical_section::with(|cs| {
let context_ref = TX_CONTEXTS[idx].borrow(cs);
*context_ref.borrow_mut() = context;
});
// Transfer is done.
TX_DONE[idx].store(true, core::sync::atomic::Ordering::Relaxed);
UART_TX_WAKERS[idx].wake();
return;
}
// Safety: We documented that the user provided slice must outlive the future, so we convert
// the raw pointer back to the slice here.
let slice = unsafe { core::slice::from_raw_parts(context.slice.data, context.slice.len) };
while context.progress < context.slice.len {
let wrrdy = uart.txstatus().read().wrrdy().bit_is_set();
if !wrrdy {
break;
}
// Safety: TX structure is owned by the future which does not write into the the data
// register, so we can assume we are the only one writing to the data register.
uart.data()
.write(|w| unsafe { w.bits(slice[context.progress] as u32) });
context.progress += 1;
}
// Write back updated context structure.
critical_section::with(|cs| {
let context_ref = TX_CONTEXTS[idx].borrow(cs);
*context_ref.borrow_mut() = context;
});
}
#[derive(Debug, Copy, Clone)]
pub struct TxContext {
progress: usize,
tx_overrun: bool,
slice: RawBufSlice,
}
#[allow(clippy::new_without_default)]
impl TxContext {
pub const fn new() -> Self {
Self {
progress: 0,
tx_overrun: false,
slice: RawBufSlice::new_empty(),
}
}
}
#[derive(Debug, Copy, Clone)]
struct RawBufSlice {
data: *const u8,
len: usize,
}
/// Safety: This type MUST be used with mutex to ensure concurrent access is valid.
unsafe impl Send for RawBufSlice {}
impl RawBufSlice {
/// # Safety
///
/// This function stores the raw pointer of the passed data slice. The user MUST ensure
/// that the slice outlives the data structure.
#[allow(dead_code)]
const unsafe fn new(data: &[u8]) -> Self {
Self {
data: data.as_ptr(),
len: data.len(),
}
}
const fn new_empty() -> Self {
Self {
data: core::ptr::null(),
len: 0,
}
}
/// # Safety
///
/// This function stores the raw pointer of the passed data slice. The user MUST ensure
/// that the slice outlives the data structure.
pub unsafe fn set(&mut self, data: &[u8]) {
self.data = data.as_ptr();
self.len = data.len();
}
}
pub struct TxFuture {
uart_idx: usize,
}
impl TxFuture {
/// # Safety
///
/// This function stores the raw pointer of the passed data slice. The user MUST ensure
/// that the slice outlives the data structure.
pub unsafe fn new<Uart: Instance>(tx: &mut Tx<Uart>, data: &[u8]) -> Self {
TX_DONE[Uart::IDX as usize].store(false, core::sync::atomic::Ordering::Relaxed);
tx.disable_interrupts();
tx.disable();
tx.clear_fifo();
let uart_tx = unsafe { tx.uart() };
let init_fill_count = core::cmp::min(data.len(), 16);
// We fill the FIFO.
for data in data.iter().take(init_fill_count) {
uart_tx.data().write(|w| unsafe { w.bits(*data as u32) });
}
critical_section::with(|cs| {
let context_ref = TX_CONTEXTS[Uart::IDX as usize].borrow(cs);
let mut context = context_ref.borrow_mut();
context.slice.set(data);
context.progress = init_fill_count;
// Ensure those are enabled inside a critical section at the same time. Can lead to
// weird glitches otherwise.
tx.enable_interrupts();
tx.enable();
});
Self {
uart_idx: Uart::IDX as usize,
}
}
}
impl Future for TxFuture {
type Output = Result<usize, TxOverrunError>;
fn poll(
self: core::pin::Pin<&mut Self>,
cx: &mut core::task::Context<'_>,
) -> core::task::Poll<Self::Output> {
UART_TX_WAKERS[self.uart_idx].register(cx.waker());
if TX_DONE[self.uart_idx].swap(false, core::sync::atomic::Ordering::Relaxed) {
let progress = critical_section::with(|cs| {
TX_CONTEXTS[self.uart_idx].borrow(cs).borrow().progress
});
return core::task::Poll::Ready(Ok(progress));
}
core::task::Poll::Pending
}
}
impl Drop for TxFuture {
fn drop(&mut self) {
let reg_block = match self.uart_idx {
0 => unsafe { pac::Uart0::reg_block() },
1 => unsafe { pac::Uart1::reg_block() },
2 => unsafe { pac::Uart2::reg_block() },
_ => unreachable!(),
};
disable_tx_interrupts(reg_block);
disable_tx(reg_block);
}
}
pub struct TxAsync<Uart: Instance> {
tx: Tx<Uart>,
}
impl<Uart: Instance> TxAsync<Uart> {
/// Create a new asynchronous TX object.
///
/// This function also enable the NVIC interrupt, but does not enable the peripheral specific
/// interrupts.
pub fn new(tx: Tx<Uart>) -> Self {
// Safety: We own TX now.
unsafe { enable_nvic_interrupt(Uart::IRQ_TX) };
Self { tx }
}
/// This function also disables the NVIC interrupt.
pub fn release(self) -> Tx<Uart> {
disable_nvic_interrupt(Uart::IRQ_TX);
self.tx
}
}
#[derive(Debug, thiserror::Error)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[error("TX overrun error")]
pub struct TxOverrunError;
impl embedded_io_async::Error for TxOverrunError {
fn kind(&self) -> embedded_io_async::ErrorKind {
embedded_io_async::ErrorKind::Other
}
}
impl<Uart: Instance> embedded_io::ErrorType for TxAsync<Uart> {
type Error = TxOverrunError;
}
impl<Uart: Instance> Write for TxAsync<Uart> {
/// Write a buffer asynchronously.
///
/// This implementation is not side effect free, and a started future might have already
/// written part of the passed buffer.
async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
let fut = unsafe { TxFuture::new(&mut self.tx, buf) };
fut.await
}
}

View File

@ -3,12 +3,12 @@
//! ## Examples
//!
//! - [Watchdog simple example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/wdt.rs)
use crate::time::Hertz;
use crate::{
clock::{Clocks, PeripheralSelect},
pac,
prelude::SyscfgExt,
use vorago_shared_periphs::{
enable_peripheral_clock, reset_peripheral_for_cycles, PeripheralSelect,
};
use crate::time::Hertz;
use crate::{clock::Clocks, pac};
use crate::{disable_nvic_interrupt, enable_nvic_interrupt};
pub const WDT_UNLOCK_VALUE: u32 = 0x1ACC_E551;
@ -39,23 +39,13 @@ pub fn disable_wdt_interrupts() {
}
impl Wdt {
pub fn new(
syscfg: &mut pac::Sysconfig,
wdt: pac::WatchDog,
clocks: &Clocks,
wdt_freq_ms: u32,
) -> Self {
Self::start(syscfg, wdt, clocks, wdt_freq_ms)
pub fn new(wdt: pac::WatchDog, clocks: &Clocks, wdt_freq_ms: u32) -> Self {
Self::start(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);
syscfg.assert_periph_reset_for_two_cycles(PeripheralSelect::Watchdog);
pub fn start(wdt: pac::WatchDog, clocks: &Clocks, wdt_freq_ms: u32) -> Self {
enable_peripheral_clock(PeripheralSelect::Watchdog);
reset_peripheral_for_cycles(PeripheralSelect::Watchdog, 2);
let wdt_clock = clocks.apb2();
let mut wdt_ctrl = Self {

View File

@ -11,15 +11,10 @@ keywords = ["no-std", "peb1", "cortex-m", "vorago", "va416xx"]
categories = ["embedded", "no-std", "hardware-support"]
[dependencies]
cortex-m = "0.7"
cortex-m-rt = "0.7"
embedded-hal = "1"
va416xx-hal = { version = ">=0.3, <=0.5", path = "../va416xx-hal", features = ["va41630"] }
lis2dh12 = { version = "0.7", features = ["out_f32"] }
va416xx-hal = { version = ">=0.3, <=0.5", features = ["va41630"] }
[features]
rt = ["va416xx-hal/rt"]
[package.metadata.docs.rs]
all-features = true

View File

@ -19,7 +19,7 @@ pub mod accelerometer {
};
// Accelerometer located on the GPIO board.
pub type Accelerometer = Lis2dh12<I2cMaster<pac::I2c0>>;
pub type Accelerometer = Lis2dh12<I2cMaster>;
#[derive(Debug)]
pub enum ConstructorError {
@ -30,14 +30,12 @@ pub mod accelerometer {
pub fn new_with_addr_detection(
i2c: pac::I2c0,
sysconfig: &mut pac::Sysconfig,
clocks: &Clocks,
) -> Result<Accelerometer, ConstructorError> {
let mut i2c_master = I2cMaster::new(
i2c,
sysconfig,
MasterConfig::default(),
clocks,
MasterConfig::default(),
I2cSpeed::Regular100khz,
)
.map_err(ConstructorError::ClkError)?;
@ -47,7 +45,7 @@ pub mod accelerometer {
}
pub fn new_with_i2cm(
i2c: I2cMaster<pac::I2c0>,
i2c: I2cMaster,
addr: lis2dh12::SlaveAddr,
) -> Result<Accelerometer, lis2dh12::Error<i2c::Error>> {
Lis2dh12::new(i2c, addr)