2 Commits

Author SHA1 Message Date
491585c9fe Bugfix and improvements for async GPIO 2025-02-14 17:44:39 +01:00
d9e1994701 bugfix 2025-02-14 17:09:37 +01:00
43 changed files with 1568 additions and 1287 deletions

View File

@ -148,13 +148,3 @@ example.
The Segger RTT viewer can be used to display log messages received from the target. The base
address for the RTT block placement is 0x10000000. It is recommended to use a search range of
0x1000 around that base address when using the RTT viewer.
## Learning (Embedded) Rust
If you are unfamiliar with Rust on Embedded Systems or Rust in general, the following resources
are recommended:
- [Rust Book](https://doc.rust-lang.org/book/)
- [Embedded Rust Book](https://docs.rust-embedded.org/book/)
- [Embedded Rust Discovery](https://docs.rust-embedded.org/discovery/microbit/)
- [Awesome Embedded Rust](https://github.com/rust-embedded/awesome-embedded-rust)

View File

@ -14,6 +14,6 @@ embedded-hal-nb = "1"
embedded-io = "0.6"
[dependencies.va108xx-hal]
version = "0.11"
path = "../va108xx-hal"
features = ["rt"]

View File

@ -6,7 +6,10 @@
#![no_std]
use cortex_m_rt::entry;
use embedded_hal::delay::DelayNs;
use embedded_hal::{
delay::DelayNs,
digital::{InputPin, OutputPin, StatefulOutputPin},
};
use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print};
use va108xx_hal::{
@ -64,35 +67,35 @@ fn main() -> ! {
TestCase::TestBasic => {
// Tie PORTA[0] to PORTA[1] for these tests!
let mut out = pinsa.pa0.into_readable_push_pull_output();
let input = pinsa.pa1.into_floating_input();
out.set_high();
assert!(input.is_high());
out.set_low();
assert!(input.is_low());
let mut input = pinsa.pa1.into_floating_input();
out.set_high().unwrap();
assert!(input.is_high().unwrap());
out.set_low().unwrap();
assert!(input.is_low().unwrap());
}
TestCase::TestPullup => {
// Tie PORTA[0] to PORTA[1] for these tests!
let input = pinsa.pa1.into_pull_up_input();
assert!(input.is_high());
let mut input = pinsa.pa1.into_pull_up_input();
assert!(input.is_high().unwrap());
let mut out = pinsa.pa0.into_readable_push_pull_output();
out.set_low();
assert!(input.is_low());
out.set_high();
assert!(input.is_high());
out.set_low().unwrap();
assert!(input.is_low().unwrap());
out.set_high().unwrap();
assert!(input.is_high().unwrap());
out.into_floating_input();
assert!(input.is_high());
assert!(input.is_high().unwrap());
}
TestCase::TestPulldown => {
// Tie PORTA[0] to PORTA[1] for these tests!
let input = pinsa.pa1.into_pull_down_input();
assert!(input.is_low());
let mut input = pinsa.pa1.into_pull_down_input();
assert!(input.is_low().unwrap());
let mut out = pinsa.pa0.into_push_pull_output();
out.set_low();
assert!(input.is_low());
out.set_high();
assert!(input.is_high());
out.set_low().unwrap();
assert!(input.is_low().unwrap());
out.set_high().unwrap();
assert!(input.is_high().unwrap());
out.into_floating_input();
assert!(input.is_low());
assert!(input.is_low().unwrap());
}
TestCase::TestMask => {
// Tie PORTA[0] to PORTA[1] for these tests!
@ -107,11 +110,11 @@ fn main() -> ! {
TestCase::PortB => {
// Tie PORTB[22] to PORTB[23] for these tests!
let mut out = pinsb.pb22.into_readable_push_pull_output();
let input = pinsb.pb23.into_floating_input();
out.set_high();
assert!(input.is_high());
out.set_low();
assert!(input.is_low());
let mut input = pinsb.pb23.into_floating_input();
out.set_high().unwrap();
assert!(input.is_high().unwrap());
out.set_low().unwrap();
assert!(input.is_low().unwrap());
}
TestCase::Perid => {
assert_eq!(PinsA::get_perid(), 0x004007e1);
@ -121,15 +124,15 @@ fn main() -> ! {
let mut output_pulsed = pinsa.pa0.into_push_pull_output();
output_pulsed.configure_pulse_mode(true, PinState::Low);
rprintln!("Pulsing high 10 times..");
output_pulsed.set_low();
output_pulsed.set_low().unwrap();
for _ in 0..10 {
output_pulsed.set_high();
output_pulsed.set_high().unwrap();
cortex_m::asm::delay(25_000_000);
}
output_pulsed.configure_pulse_mode(true, PinState::High);
rprintln!("Pulsing low 10 times..");
for _ in 0..10 {
output_pulsed.set_low();
output_pulsed.set_low().unwrap();
cortex_m::asm::delay(25_000_000);
}
}
@ -141,9 +144,9 @@ fn main() -> ! {
let mut out_2 = pinsa.pa3.into_readable_push_pull_output();
out_2.configure_delay(true, true);
for _ in 0..20 {
out_0.toggle();
out_1.toggle();
out_2.toggle();
out_0.toggle().unwrap();
out_1.toggle().unwrap();
out_2.toggle().unwrap();
cortex_m::asm::delay(25_000_000);
}
}
@ -156,18 +159,18 @@ fn main() -> ! {
dp.tim0,
);
for _ in 0..5 {
led1.toggle();
led1.toggle().ok();
ms_timer.delay_ms(500);
led1.toggle();
led1.toggle().ok();
ms_timer.delay_ms(500);
}
let mut delay_timer = CountdownTimer::new(&mut dp.sysconfig, 50.MHz(), dp.tim1);
let mut pa0 = pinsa.pa0.into_readable_push_pull_output();
for _ in 0..5 {
led1.toggle();
led1.toggle().ok();
delay_timer.delay_ms(500);
led1.toggle();
led1.toggle().ok();
delay_timer.delay_ms(500);
}
let ahb_freq: Hertz = 50.MHz();
@ -175,13 +178,13 @@ fn main() -> ! {
// Test usecond delay using both TIM peripheral and SYST. Use the release image if you
// want to verify the timings!
loop {
pa0.toggle();
pa0.toggle().ok();
delay_timer.delay_us(50);
pa0.toggle();
pa0.toggle().ok();
delay_timer.delay_us(50);
pa0.toggle();
pa0.toggle_with_toggle_reg();
syst_delay.delay_us(50);
pa0.toggle();
pa0.toggle_with_toggle_reg();
syst_delay.delay_us(50);
}
}
@ -189,7 +192,7 @@ fn main() -> ! {
rprintln!("Test success");
loop {
led1.toggle();
led1.toggle().ok();
cortex_m::asm::delay(25_000_000);
}
}

View File

@ -15,10 +15,12 @@ num_enum = { version = "0.7", default-features = false }
static_assertions = "1"
[dependencies.va108xx-hal]
version = "0.11"
version = "0.9"
# path = "../va108xx-hal"
[dependencies.vorago-reb1]
version = "0.8"
version = "0.7"
# path = "../vorago-reb1"
[features]
default = []

View File

@ -27,8 +27,10 @@ version = "1"
features = ["cortex-m-systick"]
[dependencies.va108xx-hal]
version = "0.10"
version = "0.6"
path = "../va108xx-hal"
features = ["rt", "defmt"]
[dependencies.va108xx]
version = "0.5"
version = "0.3"
path = "../va108xx"

View File

@ -27,8 +27,8 @@ embassy-executor = { version = "0.7", features = [
"executor-interrupt"
]}
va108xx-hal = { version = "0.11" }
va108xx-embassy = { version = "0.2" }
va108xx-hal = { version = "0.9", path = "../../va108xx-hal" }
va108xx-embassy = { version = "0.1", path = "../../va108xx-embassy" }
[features]
default = ["ticks-hz-1_000", "va108xx-embassy/irq-oc30-oc31"]

View File

@ -9,11 +9,14 @@ use embassy_executor::Spawner;
use embassy_sync::channel::{Receiver, Sender};
use embassy_sync::{blocking_mutex::raw::ThreadModeRawMutex, channel::Channel};
use embassy_time::{Duration, Instant, Timer};
use embedded_hal::digital::{InputPin, OutputPin, StatefulOutputPin};
use embedded_hal_async::digital::Wait;
use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print};
use va108xx_embassy::embassy;
use va108xx_hal::gpio::{
on_interrupt_for_async_gpio_for_port, InputDynPinAsync, InputPinAsync, PinsB, Port,
on_interrupt_for_async_gpio_port_a, on_interrupt_for_async_gpio_port_b, InputDynPinAsync,
InputPinAsync, PinsB,
};
use va108xx_hal::{
gpio::{DynPin, PinsA},
@ -64,13 +67,15 @@ async fn main(spawner: Spawner) {
let mut dp = pac::Peripherals::take().unwrap();
// Safety: Only called once here.
va108xx_embassy::init(
&mut dp.sysconfig,
&dp.irqsel,
SYSCLK_FREQ,
dp.tim23,
dp.tim22,
);
unsafe {
embassy::init(
&mut dp.sysconfig,
&dp.irqsel,
SYSCLK_FREQ,
dp.tim23,
dp.tim22,
)
};
let porta = PinsA::new(&mut dp.sysconfig, dp.porta);
let portb = PinsB::new(&mut dp.sysconfig, dp.portb);
@ -112,7 +117,7 @@ async fn main(spawner: Spawner) {
rprintln!("Example done, toggling LED0");
loop {
led0.toggle();
led0.toggle().unwrap();
Timer::after(Duration::from_millis(500)).await;
}
}
@ -245,13 +250,13 @@ async fn output_task(
#[interrupt]
#[allow(non_snake_case)]
fn OC10() {
on_interrupt_for_async_gpio_for_port(Port::A);
on_interrupt_for_async_gpio_for_port(Port::B);
on_interrupt_for_async_gpio_port_a();
on_interrupt_for_async_gpio_port_b();
}
// This interrupt only handles PORT B interrupts.
#[interrupt]
#[allow(non_snake_case)]
fn OC11() {
on_interrupt_for_async_gpio_for_port(Port::B);
on_interrupt_for_async_gpio_port_b();
}

View File

@ -18,19 +18,21 @@ use core::cell::RefCell;
use critical_section::Mutex;
use embassy_executor::Spawner;
use embassy_time::Instant;
use embedded_hal::digital::StatefulOutputPin;
use embedded_io::Write;
use embedded_io_async::Read;
use heapless::spsc::{Consumer, Producer, Queue};
use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print};
use va108xx_embassy::embassy;
use va108xx_hal::{
gpio::PinsA,
pac::{self, interrupt},
prelude::*,
uart::{
self, on_interrupt_rx_overwriting,
rx_asynch::{on_interrupt_rx, RxAsync},
Bank, RxAsyncOverwriting, Tx,
self, on_interrupt_uart_b_overwriting,
rx_asynch::{on_interrupt_uart_a, RxAsync},
RxAsyncSharedConsumer, Tx,
},
InterruptConfig,
};
@ -55,13 +57,15 @@ async fn main(spawner: Spawner) {
let mut dp = pac::Peripherals::take().unwrap();
// Safety: Only called once here.
va108xx_embassy::init(
&mut dp.sysconfig,
&dp.irqsel,
SYSCLK_FREQ,
dp.tim23,
dp.tim22,
);
unsafe {
embassy::init(
&mut dp.sysconfig,
&dp.irqsel,
SYSCLK_FREQ,
dp.tim23,
dp.tim22,
);
}
let porta = PinsA::new(&mut dp.sysconfig, dp.porta);
let mut led0 = porta.pa10.into_readable_push_pull_output();
@ -102,16 +106,16 @@ async fn main(spawner: Spawner) {
*CONSUMER_UART_B.borrow(cs).borrow_mut() = Some(cons_uart_b);
});
let mut async_rx_uart_a = RxAsync::new(rx_uart_a, cons_uart_a);
let async_rx_uart_b = RxAsyncOverwriting::new(rx_uart_b, &CONSUMER_UART_B);
let async_rx_uart_b = RxAsyncSharedConsumer::new(rx_uart_b, &CONSUMER_UART_B);
spawner
.spawn(uart_b_task(async_rx_uart_b, tx_uart_b))
.unwrap();
let mut buf = [0u8; 256];
loop {
rprintln!("Current time UART A: {}", Instant::now().as_secs());
led0.toggle();
led1.toggle();
led2.toggle();
led0.toggle().ok();
led1.toggle().ok();
led2.toggle().ok();
let read_bytes = async_rx_uart_a.read(&mut buf).await.unwrap();
let read_str = core::str::from_utf8(&buf[..read_bytes]).unwrap();
rprintln!(
@ -124,7 +128,7 @@ async fn main(spawner: Spawner) {
}
#[embassy_executor::task]
async fn uart_b_task(mut async_rx: RxAsyncOverwriting<pac::Uartb, 256>, mut tx: Tx<pac::Uartb>) {
async fn uart_b_task(mut async_rx: RxAsyncSharedConsumer<pac::Uartb, 256>, mut tx: Tx<pac::Uartb>) {
let mut buf = [0u8; 256];
loop {
rprintln!("Current time UART B: {}", Instant::now().as_secs());
@ -145,7 +149,7 @@ async fn uart_b_task(mut async_rx: RxAsyncOverwriting<pac::Uartb, 256>, mut tx:
fn OC2() {
let mut prod =
critical_section::with(|cs| PRODUCER_UART_A.borrow(cs).borrow_mut().take().unwrap());
let errors = on_interrupt_rx(Bank::A, &mut prod);
let errors = on_interrupt_uart_a(&mut prod);
critical_section::with(|cs| *PRODUCER_UART_A.borrow(cs).borrow_mut() = Some(prod));
// In a production app, we could use a channel to send the errors to the main task.
if let Err(errors) = errors {
@ -158,7 +162,7 @@ fn OC2() {
fn OC3() {
let mut prod =
critical_section::with(|cs| PRODUCER_UART_B.borrow(cs).borrow_mut().take().unwrap());
let errors = on_interrupt_rx_overwriting(Bank::B, &mut prod, &CONSUMER_UART_B);
let errors = on_interrupt_uart_b_overwriting(&mut prod, &CONSUMER_UART_B);
critical_section::with(|cs| *PRODUCER_UART_B.borrow(cs).borrow_mut() = Some(prod));
// In a production app, we could use a channel to send the errors to the main task.
if let Err(errors) = errors {

View File

@ -12,14 +12,16 @@
#![no_main]
use embassy_executor::Spawner;
use embassy_time::{Duration, Instant, Ticker};
use embedded_hal::digital::StatefulOutputPin;
use embedded_io_async::Write;
use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print};
use va108xx_embassy::embassy;
use va108xx_hal::{
gpio::PinsA,
pac::{self, interrupt},
prelude::*,
uart::{self, on_interrupt_tx, Bank, TxAsync},
uart::{self, on_interrupt_uart_a_tx, TxAsync},
InterruptConfig,
};
@ -41,13 +43,15 @@ async fn main(_spawner: Spawner) {
let mut dp = pac::Peripherals::take().unwrap();
// Safety: Only called once here.
va108xx_embassy::init(
&mut dp.sysconfig,
&dp.irqsel,
SYSCLK_FREQ,
dp.tim23,
dp.tim22,
);
unsafe {
embassy::init(
&mut dp.sysconfig,
&dp.irqsel,
SYSCLK_FREQ,
dp.tim23,
dp.tim22,
);
}
let porta = PinsA::new(&mut dp.sysconfig, dp.porta);
let mut led0 = porta.pa10.into_readable_push_pull_output();
@ -71,9 +75,9 @@ async fn main(_spawner: Spawner) {
let mut idx = 0;
loop {
rprintln!("Current time: {}", Instant::now().as_secs());
led0.toggle();
led1.toggle();
led2.toggle();
led0.toggle().ok();
led1.toggle().ok();
led2.toggle().ok();
let _written = async_tx
.write(STR_LIST[idx].as_bytes())
.await
@ -89,5 +93,5 @@ async fn main(_spawner: Spawner) {
#[interrupt]
#[allow(non_snake_case)]
fn OC2() {
on_interrupt_tx(Bank::A);
on_interrupt_uart_a_tx();
}

View File

@ -2,8 +2,10 @@
#![no_main]
use embassy_executor::Spawner;
use embassy_time::{Duration, Instant, Ticker};
use embedded_hal::digital::StatefulOutputPin;
use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print};
use va108xx_embassy::embassy;
cfg_if::cfg_if! {
if #[cfg(feature = "custom-irqs")] {
@ -26,25 +28,27 @@ async fn main(_spawner: Spawner) {
let mut dp = pac::Peripherals::take().unwrap();
// Safety: Only called once here.
cfg_if::cfg_if! {
if #[cfg(not(feature = "custom-irqs"))] {
va108xx_embassy::init(
&mut dp.sysconfig,
&dp.irqsel,
SYSCLK_FREQ,
dp.tim23,
dp.tim22,
);
} else {
va108xx_embassy::init_with_custom_irqs(
&mut dp.sysconfig,
&dp.irqsel,
SYSCLK_FREQ,
dp.tim23,
dp.tim22,
pac::Interrupt::OC23,
pac::Interrupt::OC24,
);
unsafe {
cfg_if::cfg_if! {
if #[cfg(not(feature = "custom-irqs"))] {
embassy::init(
&mut dp.sysconfig,
&dp.irqsel,
SYSCLK_FREQ,
dp.tim23,
dp.tim22,
);
} else {
embassy::init_with_custom_irqs(
&mut dp.sysconfig,
&dp.irqsel,
SYSCLK_FREQ,
dp.tim23,
dp.tim22,
pac::Interrupt::OC23,
pac::Interrupt::OC24,
);
}
}
}
@ -56,8 +60,8 @@ async fn main(_spawner: Spawner) {
loop {
ticker.next().await;
rprintln!("Current time: {}", Instant::now().as_secs());
led0.toggle();
led1.toggle();
led2.toggle();
led0.toggle().ok();
led1.toggle().ok();
led2.toggle().ok();
}
}

View File

@ -22,5 +22,5 @@ rtic-sync = { version = "1.3", features = ["defmt-03"] }
once_cell = {version = "1", default-features = false, features = ["critical-section"]}
ringbuf = { version = "0.4.7", default-features = false, features = ["portable-atomic"] }
va108xx-hal = { version = "0.11" }
vorago-reb1 = { version = "0.8" }
va108xx-hal = "0.9"
vorago-reb1 = "0.7"

View File

@ -69,16 +69,18 @@ mod app {
// Configure an edge interrupt on the button and route it to interrupt vector 15
let mut button = Button::new(pinsa.pa11.into_floating_input());
button.configure_edge_interrupt(
edge_irq,
InterruptConfig::new(pac::interrupt::OC15, true, true),
Some(&mut dp.sysconfig),
Some(&mut dp.irqsel),
);
if mode == PressMode::Toggle {
// This filter debounces the switch for edge based interrupts
button.configure_filter_type(FilterType::FilterFourClockCycles, FilterClkSel::Clk1);
set_clk_div_register(&mut dp.sysconfig, FilterClkSel::Clk1, 50_000);
}
button.configure_and_enable_edge_interrupt(
edge_irq,
InterruptConfig::new(pac::interrupt::OC15, true, true),
);
let mut leds = Leds::new(
pinsa.pa10.into_push_pull_output(),
pinsa.pa7.into_push_pull_output(),

View File

@ -5,6 +5,7 @@
#[rtic::app(device = pac, dispatchers = [OC31, OC30, OC29])]
mod app {
use cortex_m::asm;
use embedded_hal::digital::StatefulOutputPin;
use panic_rtt_target as _;
use rtic_example::SYSCLK_FREQ;
use rtic_monotonics::systick::prelude::*;
@ -57,9 +58,9 @@ mod app {
async fn blinky(cx: blinky::Context) {
loop {
rprintln!("toggling LEDs");
cx.local.led0.toggle();
cx.local.led1.toggle();
cx.local.led2.toggle();
cx.local.led0.toggle().ok();
cx.local.led1.toggle().ok();
cx.local.led2.toggle().ok();
Mono::delay(1000.millis()).await;
}
}

View File

@ -16,8 +16,8 @@ embedded-io = "0.6"
cortex-m-semihosting = "0.5.0"
[dependencies.va108xx-hal]
version = "0.11"
version = "0.9"
features = ["rt", "defmt"]
[dependencies.vorago-reb1]
version = "0.8"
version = "0.7"

View File

@ -7,7 +7,10 @@
#![no_std]
use cortex_m_rt::entry;
use embedded_hal::delay::DelayNs;
use embedded_hal::{
delay::DelayNs,
digital::{OutputPin, StatefulOutputPin},
};
use panic_halt as _;
use va108xx_hal::{
gpio::PinsA,
@ -35,21 +38,21 @@ fn main() -> ! {
let mut led2 = porta.pa7.into_readable_push_pull_output();
let mut led3 = porta.pa6.into_readable_push_pull_output();
for _ in 0..10 {
led1.set_low();
led2.set_low();
led3.set_low();
led1.set_low().ok();
led2.set_low().ok();
led3.set_low().ok();
delay_ms.delay_ms(200);
led1.set_high();
led2.set_high();
led3.set_high();
led1.set_high().ok();
led2.set_high().ok();
led3.set_high().ok();
delay_tim1.delay_ms(200);
}
loop {
led1.toggle();
led1.toggle().ok();
delay_ms.delay_ms(200);
led2.toggle();
led2.toggle().ok();
delay_tim1.delay_ms(200);
led3.toggle();
led3.toggle().ok();
delay_ms.delay_ms(200);
}
}

View File

@ -45,6 +45,9 @@ fn main() -> ! {
.expect("TX send error");
}
Err(nb::Error::WouldBlock) => (),
Err(nb::Error::Other(uart_error)) => {
rprintln!("UART receive error {:?}", uart_error);
}
}
}
}

View File

@ -29,7 +29,9 @@ rtic-monotonics = { version = "2", features = ["cortex-m-systick"] }
rtic-sync = {version = "1", features = ["defmt-03"]}
[dependencies.va108xx-hal]
version = "0.11"
version = "0.9"
# path = "../va108xx-hal"
[dependencies.vorago-reb1]
version = "0.8"
version = "0.7"
# path = "../vorago-reb1"

View File

@ -11,7 +11,7 @@ panic-rtt-target = { version = "0.1.3" }
rtt-target = { version = "0.5" }
cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
embedded-hal = "1"
va108xx-hal = { version = "0.10.0" }
va108xx-hal = { path = "../../va108xx-hal" }
[profile.dev]
codegen-units = 1

View File

@ -11,7 +11,7 @@ panic-rtt-target = { version = "0.1.3" }
rtt-target = { version = "0.5" }
cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
embedded-hal = "1"
va108xx-hal = { version = "0.10.0" }
va108xx-hal = { path = "../../va108xx-hal" }
[profile.dev]
codegen-units = 1

View File

@ -8,16 +8,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
## [unreleased]
## [v0.2.1] 2025-03-07
- Bumped allowed va108xx-hal to v0.11
## [v0.2.0] 2025-02-17
- Bumped va108xx-hal to v0.10.0
- Remove `embassy` module, expose public functions in library root directly
## [v0.1.2] and [v0.1.1] 2025-02-13
Docs patch
@ -25,7 +15,3 @@ Docs patch
## [v0.1.0] 2025-02-13
Initial release
[unreleased]: https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/compare/va108xx-embassy-v0.2.1...HEAD
[v0.2.1]: https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/compare/va108xx-embassy-v0.2.0...va10xx-embassy-v0.2.1
[v0.2.0]: https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/compare/va108xx-embassy-v0.1.2...va10xx-embassy-v0.2.0

View File

@ -1,6 +1,6 @@
[package]
name = "va108xx-embassy"
version = "0.2.1"
version = "0.1.2"
edition = "2021"
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
description = "Embassy-rs support for the Vorago VA108xx family of microcontrollers"
@ -20,7 +20,7 @@ embassy-time-queue-utils = "0.1"
once_cell = { version = "1", default-features = false, features = ["critical-section"] }
va108xx-hal = { version = ">=0.10, <=0.11" }
va108xx-hal = { version = "0.9", path = "../va108xx-hal" }
[target.'cfg(all(target_arch = "arm", target_os = "none"))'.dependencies]
portable-atomic = { version = "1", features = ["unsafe-assume-single-core"] }

View File

@ -1,17 +1,17 @@
//! # Embassy-rs support for the Vorago VA108xx MCU family
//!
//! This repository contains the [embassy-rs](https://github.com/embassy-rs/embassy) support for
//! the VA108xx family. Currently, it contains the time driver to allow using embassy-rs. It uses
//! the TIM peripherals provided by the VA108xx family for this purpose.
//! This repository contains the [embassy-rs](https://github.com/embassy-rs/embassy) support for the
//! VA108xx family. Currently, it contains the time driver to allow using embassy-rs. It uses the TIM
//! peripherals provided by the VA108xx family for this purpose.
//!
//! ## Usage
//!
//! This library exposes the [init] or the [init_with_custom_irqs] functions which set up the time
//! driver. This function must be called once at the start of the application.
//! This library only exposes the [embassy::init] method which sets up the time driver. This
//! function must be called once at the start of the application.
//!
//! This implementation requires two TIM peripherals provided by the VA108xx device.
//! The user can freely specify the two used TIM peripheral by passing the concrete TIM instances
//! into the [init_with_custom_irqs] and [init] method.
//! into the [embassy::init_with_custom_irqs] and [embassy::init] method.
//!
//! The application also requires two interrupt handlers to handle the timekeeper and alarm
//! interrupts. By default, this library will define the interrupt handler inside the library
@ -24,7 +24,7 @@
//! 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
//! using the [embassy_time_driver_irqs] macro to declare the IRQ handlers in the
//! application code. If this is done, [init_with_custom_irqs] must be used
//! application code. If this is done, [embassy::init_with_custom_irqs] must be used
//! method to pass the IRQ numbers to the library.
//!
//! ## Examples
@ -76,7 +76,7 @@ macro_rules! embassy_time_driver_irqs {
#[allow(non_snake_case)]
fn $timekeeper_irq() {
// Safety: We call it once here.
unsafe { $crate::time_driver().on_interrupt_timekeeping() }
unsafe { $crate::embassy::time_driver().on_interrupt_timekeeping() }
}
const ALARM_IRQ: pac::Interrupt = pac::Interrupt::$alarm_irq;
@ -85,7 +85,7 @@ macro_rules! embassy_time_driver_irqs {
#[allow(non_snake_case)]
fn $alarm_irq() {
// Safety: We call it once here.
unsafe { $crate::time_driver().on_interrupt_alarm() }
unsafe { $crate::embassy::time_driver().on_interrupt_alarm() }
}
};
}
@ -99,58 +99,71 @@ embassy_time_driver_irqs!(timekeeper_irq = OC30, alarm_irq = OC29);
#[cfg(feature = "irq-oc28-oc29")]
embassy_time_driver_irqs!(timekeeper_irq = OC29, alarm_irq = OC28);
/// Expose the time driver so the user can specify the IRQ handlers themselves.
pub fn time_driver() -> &'static TimerDriver {
&TIME_DRIVER
}
pub mod embassy {
use super::*;
use va108xx_hal::{pac, timer::TimRegInterface};
/// Initialization method for embassy.
///
/// This should be used if the interrupt handler is provided by the library, which is the
/// default case.
#[cfg(feature = "irqs-in-lib")]
pub fn init<TimekeeperTim: TimRegInterface + ValidTim, AlarmTim: TimRegInterface + ValidTim>(
syscfg: &mut pac::Sysconfig,
irqsel: &pac::Irqsel,
sysclk: impl Into<Hertz>,
timekeeper_tim: TimekeeperTim,
alarm_tim: AlarmTim,
) {
TIME_DRIVER.init(
syscfg,
irqsel,
sysclk,
timekeeper_tim,
alarm_tim,
TIMEKEEPER_IRQ,
ALARM_IRQ,
)
}
/// 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 when using custom IRQ handlers.
///
/// Requires an explicit [pac::Interrupt] argument for the timekeeper and alarm IRQs.
pub fn init_with_custom_irqs<
TimekeeperTim: TimRegInterface + ValidTim,
AlarmTim: TimRegInterface + ValidTim,
>(
syscfg: &mut pac::Sysconfig,
irqsel: &pac::Irqsel,
sysclk: impl Into<Hertz>,
timekeeper_tim: TimekeeperTim,
alarm_tim: AlarmTim,
timekeeper_irq: pac::Interrupt,
alarm_irq: pac::Interrupt,
) {
TIME_DRIVER.init(
syscfg,
irqsel,
sysclk,
timekeeper_tim,
alarm_tim,
timekeeper_irq,
alarm_irq,
)
/// Initialization method for embassy
///
/// # Safety
///
/// This has to be called once at initialization time to initiate the time driver for
/// embassy.
#[cfg(feature = "irqs-in-lib")]
pub unsafe fn init<
TimekeeperTim: TimRegInterface + ValidTim,
AlarmTim: TimRegInterface + ValidTim,
>(
syscfg: &mut pac::Sysconfig,
irqsel: &pac::Irqsel,
sysclk: impl Into<Hertz>,
timekeeper_tim: TimekeeperTim,
alarm_tim: AlarmTim,
) {
TIME_DRIVER.init(
syscfg,
irqsel,
sysclk,
timekeeper_tim,
alarm_tim,
TIMEKEEPER_IRQ,
ALARM_IRQ,
)
}
/// Initialization method for embassy
///
/// # Safety
///
/// This has to be called once at initialization time to initiate the time driver for
/// embassy.
pub unsafe fn init_with_custom_irqs<
TimekeeperTim: TimRegInterface + ValidTim,
AlarmTim: TimRegInterface + ValidTim,
>(
syscfg: &mut pac::Sysconfig,
irqsel: &pac::Irqsel,
sysclk: impl Into<Hertz>,
timekeeper_tim: TimekeeperTim,
alarm_tim: AlarmTim,
timekeeper_irq: pac::Interrupt,
alarm_irq: pac::Interrupt,
) {
TIME_DRIVER.init(
syscfg,
irqsel,
sysclk,
timekeeper_tim,
alarm_tim,
timekeeper_irq,
alarm_irq,
)
}
}
struct AlarmState {

View File

@ -8,25 +8,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
## [unreleased]
## [v0.11.1] 2025-03-10
## Fixed
- Fix `embedded_io` UART implementation to implement the documented contract properly.
The implementation will now block until at least one byte is available or can be written, unless
the send or receive buffer is empty.
## [v0.11.0] 2025-03-07
## Changed
- Bugfix for I2C `TimingCfg::reg`
- Simplified UART error handling. All APIs are now infallible because writing to a FIFO or
reading from a FIFO never fails. Users can either poll errors using `Rx::poll_errors` or
`Uart::poll_rx_errors` / `UartBase::poll_rx_errors`, or detect errors using the provided
interrupt handlers.
## [v0.10.0] 2025-02-17
## [v0.10.0]
## Added
@ -34,19 +16,16 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
## Changed
- Larger refactoring of GPIO library. The edge and level interrupt configurator functions do not
enable interrupts anymore. Instead, there are explicit `enbable_interrupt` and
`disable_interrupt` methods
- Renamed GPIO `DynGroup` to `Port`
- Rename generic GPIO interrupt handler into `on_interrupt_for_asynch_gpio`
into `on_interrupt_for_async_gpio_for_port` which expects a Port argument
- Missing GPIO API replacements from `x` to `configure_x`
- Split up generic GPIO interrupt handler into `on_interrupt_for_asynch_gpio`
into port specific variants.
## Fixed
- Bug in async GPIO interrupt handler where all enabled interrupts, even the ones which might
be unrelated to the pin, were disabled.
## [v0.9.0] 2025-02-13
## [v0.9.0]
## Fixed
@ -122,14 +101,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Updated `embedded-hal` to v1
- Added optional `defmt` v0.3 feature and support.
## v0.5.2 2024-06-16
## [v0.5.2] 2024-06-16
## Fixed
- Replaced usage to `ptr::write_volatile` in UART module which is denied on more recent Rust
compilers.
## v0.5.1
## [v0.5.1]
### Changes
@ -138,7 +117,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- `once_cell` to 1.12.0
- Other dependencies: Only revision has changed
## v0.5.0
## [v0.5.0]
### Added
@ -151,14 +130,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Bugfix in UART code where RX and TX could not be enabled or disabled independently
## v0.4.3
## [v0.4.3]
- Various smaller fixes for READMEs, update of links in documentation
- Simplified CI for github, do not use `cross`
- New `blinky-pac` example
- Use HAL delay in `blinky` example
## v0.4.2
## [v0.4.2]
### Added
@ -168,24 +147,24 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Clear TX and RX FIFO in SPI transfer function
## v0.4.1
## [v0.4.1]
### Fixed
- Initial blockmode setting was not set in SPI constructor
## v0.4.0
## [v0.4.0]
### Changed
- Replaced `Hertz` by `impl Into<Hertz>` completely and removed
`+ Copy` where not necessary
## v0.3.1
## [v0.3.1]
- Updated all links to point to new repository
## v0.3.0
## [v0.3.0]
### Added
@ -197,7 +176,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Primary repository now hosted on IRS external git: https://egit.irs.uni-stuttgart.de/rust/va108xx-hal
- Relicensed as Apache-2.0
## v0.2.3
## [0.2.3]
### Added
@ -209,7 +188,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Improved Timer API. It is now possible to simply use `new` on `CountDownTimer`
## v0.2.2
## [0.2.2]
### Added
@ -221,7 +200,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- API which expects values in Hertz now uses `impl Into<Hertz>` as input parameter
## v0.2.1
## [0.2.1]
### Added
@ -235,7 +214,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Moved the `FilterClkSel` struct to the `clock` module, re-exporting in `gpio`
- Clearing output state at initialization of Output pins
## v0.2.0
## [0.2.0]
### Changed
@ -250,7 +229,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Some bugfixes for GPIO implementation
- Rust edition updated to 2021
## v0.1.0
## [0.1.0]
### Added
@ -259,12 +238,3 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- RTT example application
- Added basic test binary in form of an example
- README with basic instructions how to set up own binary crate
[unreleased]: https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/compare/va108xx-hal-v0.11.0...HEAD
[v0.11.1]: https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/compare/va108xx-hal-v0.11.0...va108xx-hal-v0.11.1
[v0.11.0]: https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/compare/va108xx-hal-v0.10.0...va108xx-hal-v0.11.0
[v0.10.0]: https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/compare/va108xx-hal-v0.9.0...va108xx-hal-v0.10.0
[v0.9.0]: https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/compare/va108xx-hal-v0.8.0...va108xx-hal-v0.9.0
[v0.8.0]: https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/compare/va108xx-hal-v0.7.0...va108xx-hal-v0.8.0
[v0.7.0]: https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/compare/va108xx-hal-v0.6.0...va108xx-hal-v0.7.0
[v0.6.0]: https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/tag/va108xx-hal-v0.6.0

View File

@ -1,6 +1,6 @@
[package]
name = "va108xx-hal"
version = "0.11.1"
version = "0.9.0"
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
edition = "2021"
description = "HAL for the Vorago VA108xx family of microcontrollers"
@ -28,8 +28,8 @@ heapless = "0.8"
static_cell = "2"
thiserror = { version = "2", default-features = false }
void = { version = "1", default-features = false }
once_cell = { version = "1", default-features = false }
va108xx = { version = "0.5", default-features = false, features = ["critical-section", "defmt"] }
once_cell = {version = "1", default-features = false }
va108xx = { version = "0.5", path = "../va108xx", default-features = false, features = ["critical-section", "defmt"] }
embassy-sync = "0.6"
defmt = { version = "0.3", optional = true }

View File

@ -3,9 +3,14 @@
//! 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.
//! which must be provided for async support to work. However, it provides two generic interrupt
//! handlers:
//!
//! - [on_interrupt_for_async_gpio_port_a]
//! - [on_interrupt_for_async_gpio_port_b]
//!
//! Those should be called in the interrupt handlers which handle GPIO interrupts for port A
//! and/or port B.
//!
//! # Example
//!
@ -13,14 +18,15 @@
use core::future::Future;
use embassy_sync::waitqueue::AtomicWaker;
use embedded_hal::digital::InputPin;
use embedded_hal_async::digital::Wait;
use portable_atomic::AtomicBool;
use va108xx::{self as pac};
use va108xx::{self as pac, Irqsel, Sysconfig};
use crate::InterruptConfig;
use super::{
pin, DynPin, DynPinId, InputConfig, InterruptEdge, InvalidPinTypeError, Pin, PinId, Port,
pin, DynGroup, DynPin, DynPinId, InputConfig, InterruptEdge, InvalidPinTypeError, Pin, PinId,
NUM_PINS_PORT_A, NUM_PINS_PORT_B,
};
@ -33,36 +39,51 @@ static EDGE_DETECTION_PORT_A: [AtomicBool; NUM_PINS_PORT_A] =
static EDGE_DETECTION_PORT_B: [AtomicBool; NUM_PINS_PORT_B] =
[const { AtomicBool::new(false) }; NUM_PINS_PORT_B];
/// Generic interrupt handler for GPIO interrupts on a specific port to support async functionalities
/// Generic interrupt handler for GPIO interrupts on PORT A to support the 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) {
/// This function should be called in all interrupt hanflers which handle any PORT A GPIO
/// interrupts.
///
/// This handler will wake the correspoding wakers for the pins which triggered an interrupt
/// as well as updating the static edge detection structures. This allows the pin future to
/// complete async operations. The user should call this function in ALL interrupt handlers
/// which handle any GPIO interrupts.
#[inline]
pub fn on_interrupt_for_async_gpio_port_a() {
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.as_ref(),
EDGE_DETECTION_PORT_A.as_ref(),
),
Port::B => (
periphs.portb.irq_enb().read().bits(),
periphs.portb.edge_status().read().bits(),
WAKERS_FOR_PORT_B.as_ref(),
EDGE_DETECTION_PORT_B.as_ref(),
),
};
on_interrupt_for_port(irq_enb, edge_status, wakers, edge_detection);
handle_interrupt_for_gpio_and_port(
periphs.porta.irq_enb().read().bits(),
periphs.porta.edge_status().read().bits(),
&WAKERS_FOR_PORT_A,
&EDGE_DETECTION_PORT_A,
);
}
/// Generic interrupt handler for GPIO interrupts on PORT B to support the async functionalities.
///
/// This function should be called in all interrupt hanflers which handle any PORT B GPIO
/// interrupts.
///
/// This handler will wake the correspoding wakers for the pins which triggered an interrupt
/// as well as updating the static edge detection structures. This allows the pin future to
/// complete async operations. The user should call this function in ALL interrupt handlers
/// which handle any GPIO interrupts.
#[inline]
fn on_interrupt_for_port(
pub fn on_interrupt_for_async_gpio_port_b() {
let periphs = unsafe { pac::Peripherals::steal() };
handle_interrupt_for_gpio_and_port(
periphs.portb.irq_enb().read().bits(),
periphs.portb.edge_status().read().bits(),
&WAKERS_FOR_PORT_B,
&EDGE_DETECTION_PORT_B,
);
}
// Uses the enabled interrupt register and the persistent edge status to capture all GPIO events.
#[inline]
fn handle_interrupt_for_gpio_and_port(
mut irq_enb: u32,
edge_status: u32,
wakers: &'static [AtomicWaker],
@ -95,13 +116,26 @@ pub struct InputPinFuture {
}
impl InputPinFuture {
#[inline]
/// # Safety
///
/// This calls [Self::new_with_dyn_pin] but uses [pac::Peripherals::steal] to get the system configuration
/// and IRQ selection peripherals. Users must ensure that the registers and configuration
/// related to this input pin are not being used elsewhere concurrently.
pub unsafe fn new_unchecked_with_dyn_pin(
pin: &mut DynPin,
irq: pac::Interrupt,
edge: InterruptEdge,
) -> Result<Self, InvalidPinTypeError> {
let mut periphs = pac::Peripherals::steal();
Self::new_with_dyn_pin(pin, irq, edge, &mut periphs.sysconfig, &mut periphs.irqsel)
}
pub fn pin_group_to_waker_and_edge_detection_group(
group: Port,
group: DynGroup,
) -> (&'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()),
DynGroup::A => (WAKERS_FOR_PORT_A.as_ref(), EDGE_DETECTION_PORT_A.as_ref()),
DynGroup::B => (WAKERS_FOR_PORT_B.as_ref(), EDGE_DETECTION_PORT_B.as_ref()),
}
}
@ -109,17 +143,24 @@ impl InputPinFuture {
pin: &mut DynPin,
irq: pac::Interrupt,
edge: InterruptEdge,
sys_cfg: &mut Sysconfig,
irq_sel: &mut Irqsel,
) -> Result<Self, InvalidPinTypeError> {
if !pin.is_input_pin() {
return Err(InvalidPinTypeError(pin.mode()));
}
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]
Self::pin_group_to_waker_and_edge_detection_group(pin.id().group);
edge_detection_group[pin.id().num as usize]
.store(false, core::sync::atomic::Ordering::Relaxed);
pin.configure_edge_interrupt(edge).unwrap();
pin.enable_interrupt(InterruptConfig::new(irq, true, true));
pin.configure_edge_interrupt(
edge,
InterruptConfig::new(irq, true, true),
Some(sys_cfg),
Some(irq_sel),
)
.unwrap();
Ok(Self {
pin_id: pin.id(),
waker_group,
@ -127,17 +168,37 @@ impl InputPinFuture {
})
}
pub fn new_with_pin<I: PinId, C: InputConfig>(
/// # Safety
///
/// This calls [Self::new_with_pin] but uses [pac::Peripherals::steal] to get the system configuration
/// and IRQ selection peripherals. Users must ensure that the registers and configuration
/// related to this input pin are not being used elsewhere concurrently.
pub unsafe fn new_unchecked_with_pin<I: PinId, C: InputConfig>(
pin: &mut Pin<I, pin::Input<C>>,
irq: pac::Interrupt,
edge: InterruptEdge,
) -> Self {
let mut periphs = pac::Peripherals::steal();
Self::new_with_pin(pin, irq, edge, &mut periphs.sysconfig, &mut periphs.irqsel)
}
pub fn new_with_pin<I: PinId, C: InputConfig>(
pin: &mut Pin<I, pin::Input<C>>,
irq: pac::Interrupt,
edge: InterruptEdge,
sys_cfg: &mut Sysconfig,
irq_sel: &mut Irqsel,
) -> Self {
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]
Self::pin_group_to_waker_and_edge_detection_group(pin.id().group);
edge_detection_group[pin.id().num as usize]
.store(false, core::sync::atomic::Ordering::Relaxed);
pin.configure_edge_interrupt(edge);
pin.enable_interrupt(InterruptConfig::new(irq, true, true));
pin.configure_edge_interrupt(
edge,
InterruptConfig::new(irq, true, true),
Some(sys_cfg),
Some(irq_sel),
);
Self {
pin_id: pin.id(),
edge_detection_group,
@ -148,8 +209,18 @@ impl InputPinFuture {
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(false);
let periphs = unsafe { pac::Peripherals::steal() };
if self.pin_id.group == DynGroup::A {
periphs
.porta
.irq_enb()
.modify(|r, w| unsafe { w.bits(r.bits() & !(1 << self.pin_id.num)) });
} else {
periphs
.porta
.irq_enb()
.modify(|r, w| unsafe { w.bits(r.bits() & !(1 << self.pin_id.num)) });
}
}
}
@ -159,7 +230,7 @@ impl Future for InputPinFuture {
self: core::pin::Pin<&mut Self>,
cx: &mut core::task::Context<'_>,
) -> core::task::Poll<Self::Output> {
let idx = self.pin_id.num() as usize;
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(());
@ -178,8 +249,8 @@ impl InputDynPinAsync {
/// 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.
/// generic [on_interrupt_for_async_gpio_port_a] or [on_interrupt_for_async_gpio_port_b]
/// function must be called inside that function for the asynchronous functionality to work.
pub fn new(pin: DynPin, irq: pac::Interrupt) -> Result<Self, InvalidPinTypeError> {
if !pin.is_input_pin() {
return Err(InvalidPinTypeError(pin.mode()));
@ -191,10 +262,15 @@ impl InputDynPinAsync {
///
/// 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, self.irq, InterruptEdge::LowToHigh)
.unwrap();
let fut = unsafe {
// Unwrap okay, checked pin in constructor.
InputPinFuture::new_unchecked_with_dyn_pin(
&mut self.pin,
self.irq,
InterruptEdge::LowToHigh,
)
.unwrap()
};
if self.pin.is_high().unwrap() {
return;
}
@ -205,10 +281,15 @@ impl InputDynPinAsync {
///
/// 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, self.irq, InterruptEdge::HighToLow)
.unwrap();
let fut = unsafe {
// Unwrap okay, checked pin in constructor.
InputPinFuture::new_unchecked_with_dyn_pin(
&mut self.pin,
self.irq,
InterruptEdge::HighToLow,
)
.unwrap()
};
if self.pin.is_low().unwrap() {
return;
}
@ -217,26 +298,44 @@ impl InputDynPinAsync {
/// 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, self.irq, InterruptEdge::HighToLow)
unsafe {
// Unwrap okay, checked pin in constructor.
InputPinFuture::new_unchecked_with_dyn_pin(
&mut self.pin,
self.irq,
InterruptEdge::HighToLow,
)
.unwrap()
.await;
}
.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, self.irq, InterruptEdge::LowToHigh)
unsafe {
// Unwrap okay, checked pin in constructor.
InputPinFuture::new_unchecked_with_dyn_pin(
&mut self.pin,
self.irq,
InterruptEdge::LowToHigh,
)
.unwrap()
.await;
}
.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, self.irq, InterruptEdge::BothEdges)
unsafe {
// Unwrap okay, checked pin in constructor.
InputPinFuture::new_unchecked_with_dyn_pin(
&mut self.pin,
self.irq,
InterruptEdge::BothEdges,
)
.unwrap()
.await;
}
.await;
}
pub fn release(self) -> DynPin {
@ -285,8 +384,8 @@ impl<I: PinId, C: InputConfig> InputPinAsync<I, C> {
/// 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.
/// generic [on_interrupt_for_async_gpio_port_a] or [on_interrupt_for_async_gpio_port_b]
/// function must be called inside that function for the asynchronous functionality to work.
pub fn new(pin: Pin<I, pin::Input<C>>, irq: pac::Interrupt) -> Self {
Self { pin, irq }
}
@ -295,8 +394,14 @@ impl<I: PinId, C: InputConfig> InputPinAsync<I, C> {
///
/// This returns immediately if the pin is already high.
pub async fn wait_for_high(&mut self) {
let fut = InputPinFuture::new_with_pin(&mut self.pin, self.irq, InterruptEdge::LowToHigh);
if self.pin.is_high() {
let fut = unsafe {
InputPinFuture::new_unchecked_with_pin(
&mut self.pin,
self.irq,
InterruptEdge::LowToHigh,
)
};
if self.pin.is_high().unwrap() {
return;
}
fut.await;
@ -306,8 +411,14 @@ impl<I: PinId, C: InputConfig> InputPinAsync<I, C> {
///
/// 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, self.irq, InterruptEdge::HighToLow);
if self.pin.is_low() {
let fut = unsafe {
InputPinFuture::new_unchecked_with_pin(
&mut self.pin,
self.irq,
InterruptEdge::HighToLow,
)
};
if self.pin.is_low().unwrap() {
return;
}
fut.await;
@ -315,19 +426,40 @@ impl<I: PinId, C: InputConfig> InputPinAsync<I, C> {
/// 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, self.irq, InterruptEdge::HighToLow).await;
unsafe {
// Unwrap okay, checked pin in constructor.
InputPinFuture::new_unchecked_with_pin(
&mut self.pin,
self.irq,
InterruptEdge::HighToLow,
)
}
.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, self.irq, InterruptEdge::LowToHigh).await;
unsafe {
// Unwrap okay, checked pin in constructor.
InputPinFuture::new_unchecked_with_pin(
&mut self.pin,
self.irq,
InterruptEdge::LowToHigh,
)
}
.await;
}
/// Asynchronously wait until the pin sees any edge (either rising or falling).
pub async fn wait_for_any_edge(&mut self) {
InputPinFuture::new_with_pin(&mut self.pin, self.irq, InterruptEdge::BothEdges).await;
unsafe {
InputPinFuture::new_unchecked_with_pin(
&mut self.pin,
self.irq,
InterruptEdge::BothEdges,
)
}
.await;
}
pub fn release(self) -> Pin<I, pin::Input<C>> {

View File

@ -57,10 +57,11 @@
//! [InvalidPinTypeError].
use super::{
pin::{FilterType, Pin, PinId, PinMode},
InputDynPinAsync, InterruptEdge, InterruptLevel, IsMaskedError, PinState, Port,
pin::{FilterType, InterruptEdge, InterruptLevel, Pin, PinId, PinMode, PinState},
reg::RegisterInterface,
InputDynPinAsync,
};
use crate::{clock::FilterClkSel, enable_nvic_interrupt, pac, FunSel};
use crate::{clock::FilterClkSel, enable_nvic_interrupt, pac, FunSel, InterruptConfig};
//==================================================================================================
// DynPinMode configurations
@ -155,91 +156,55 @@ pub const DYN_ALT_FUNC_3: DynPinMode = DynPinMode::Alternate(DynAlternate::Sel3)
// DynGroup & DynPinId
//==================================================================================================
pub type DynGroup = Port;
/// Value-level `enum` for pin groups
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum DynGroup {
A,
B,
}
/// Value-level `struct` representing pin IDs
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct DynPinId {
port: Port,
num: u8,
}
impl DynPinId {
pub const fn new(port: Port, num: u8) -> Self {
DynPinId { port, num }
}
pub const fn port(&self) -> Port {
self.port
}
pub const fn num(&self) -> u8 {
self.num
}
pub group: DynGroup,
pub num: u8,
}
//==================================================================================================
// ModeFields
// DynRegisters
//==================================================================================================
/// Collect all fields needed to set the [`PinMode`](super::PinMode)
#[derive(Default)]
struct ModeFields {
dir: bool,
opendrn: bool,
pull_en: bool,
/// true for pullup, false for pulldown
pull_dir: bool,
funsel: u8,
enb_input: bool,
}
/// Provide a safe register interface for [`DynPin`]s
///
/// This `struct` takes ownership of a [`DynPinId`] and provides an API to
/// access the corresponding regsiters.
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub(crate) struct DynRegisters(DynPinId);
impl From<DynPinMode> for ModeFields {
// [`DynRegisters`] takes ownership of the [`DynPinId`], and [`DynPin`]
// guarantees that each pin is a singleton, so this implementation is safe.
unsafe impl RegisterInterface for DynRegisters {
#[inline]
fn from(mode: DynPinMode) -> Self {
let mut fields = Self::default();
match mode {
DynPinMode::Input(config) => {
fields.dir = false;
fields.funsel = FunSel::Sel0 as u8;
match config {
DynInput::Floating => (),
DynInput::PullUp => {
fields.pull_en = true;
fields.pull_dir = true;
}
DynInput::PullDown => {
fields.pull_en = true;
}
}
}
DynPinMode::Output(config) => {
fields.dir = true;
fields.funsel = FunSel::Sel0 as u8;
match config {
DynOutput::PushPull => (),
DynOutput::OpenDrain => {
fields.opendrn = true;
}
DynOutput::ReadableOpenDrain => {
fields.enb_input = true;
fields.opendrn = true;
}
DynOutput::ReadablePushPull => {
fields.enb_input = true;
}
}
}
DynPinMode::Alternate(config) => {
fields.funsel = config as u8;
}
}
fields
fn id(&self) -> DynPinId {
self.0
}
}
/// Type definition to avoid confusion: These register blocks are identical
type PortRegisterBlock = pac::porta::RegisterBlock;
pub type PortReg = pac::ioconfig::Porta;
impl DynRegisters {
/// Create a new instance of [`DynRegisters`]
///
/// # Safety
///
/// Users must never create two simultaneous instances of this `struct` with
/// the same [`DynPinId`]
#[inline]
unsafe fn new(id: DynPinId) -> Self {
DynRegisters(id)
}
}
//==================================================================================================
// DynPin
@ -251,59 +216,46 @@ pub type PortReg = pac::ioconfig::Porta;
/// by the same type, and pins are tracked and distinguished at run-time.
#[derive(Debug)]
pub struct DynPin {
id: DynPinId,
pub(crate) regs: DynRegisters,
mode: DynPinMode,
}
impl DynPin {
/// Create a new [DynPin]
/// Create a new [`DynPin`]
///
/// # Safety
///
/// Each [DynPin] must be a singleton. For a given [DynPinId], there
/// Each [`DynPin`] must be a singleton. For a given [`DynPinId`], there
/// must be at most one corresponding [`DynPin`] in existence at any given
/// time. Violating this requirement is `unsafe`.
#[inline]
pub(crate) const unsafe fn new(id: DynPinId, mode: DynPinMode) -> Self {
DynPin { id, mode }
}
/// Steals a new [DynPin].
///
/// This function will simply set the internal mode to [DYN_FLOATING_INPUT] pin without
/// modifying any registers related to the behaviour of the pin. The user should call
/// [Self::into_mode] to ensure the correct mode of the pin.
///
/// # Safety
///
/// Circumvents the HAL's safety guarantees. The caller must ensure that the pin is not
/// used cocurrently somewhere else. The caller might also want to call [Self::into_mode]
/// to ensure the correct desired state of the pin. It is recommended to create the pin using
/// [Pin::downgrade] instead.
pub const unsafe fn steal(id: DynPinId) -> Self {
pub(crate) unsafe fn new(id: DynPinId, mode: DynPinMode) -> Self {
DynPin {
id,
mode: DYN_FLOATING_INPUT,
regs: DynRegisters::new(id),
mode,
}
}
/// Return a copy of the pin ID
#[inline]
pub const fn id(&self) -> DynPinId {
self.id
pub fn id(&self) -> DynPinId {
self.regs.0
}
/// Return a copy of the pin mode
#[inline]
pub const fn mode(&self) -> DynPinMode {
pub fn mode(&self) -> DynPinMode {
self.mode
}
/// Convert the pin to the requested [`DynPinMode`]
#[inline]
pub fn into_mode(&mut self, mode: DynPinMode) {
self.change_mode(mode);
self.mode = mode;
// Only modify registers if we are actually changing pin mode
if mode != self.mode {
self.regs.change_mode(mode);
self.mode = mode;
}
}
#[inline]
@ -311,11 +263,6 @@ impl DynPin {
matches!(self.mode, DynPinMode::Input(_))
}
#[inline]
pub fn is_output_pin(&self) -> bool {
matches!(self.mode, DynPinMode::Output(_))
}
#[inline]
pub fn into_funsel_1(&mut self) {
self.into_mode(DYN_ALT_FUNC_1);
@ -373,170 +320,74 @@ impl DynPin {
self.into_mode(DYN_RD_OPEN_DRAIN_OUTPUT);
}
#[inline(always)]
pub fn is_low(&self) -> Result<bool, InvalidPinTypeError> {
self.read_internal().map(|v| !v)
}
#[inline(always)]
pub fn is_high(&self) -> Result<bool, InvalidPinTypeError> {
self.read_internal()
}
#[inline(always)]
pub fn set_low(&mut self) -> Result<(), InvalidPinTypeError> {
self.write_internal(false)
}
#[inline(always)]
pub fn set_high(&mut self) -> Result<(), InvalidPinTypeError> {
self.write_internal(true)
}
/// Toggle the logic level of an output pin
#[inline(always)]
pub fn toggle(&mut self) -> Result<(), InvalidPinTypeError> {
if !self.is_output_pin() {
return Err(InvalidPinTypeError(self.mode));
}
// Safety: TOGOUT is a "mask" register, and we only write the bit for
// this pin ID
unsafe { self.port_reg().togout().write(|w| w.bits(self.mask_32())) };
Ok(())
}
pub fn enable_interrupt(&mut self, irq_cfg: crate::InterruptConfig) {
if irq_cfg.route {
self.configure_irqsel(irq_cfg.id);
}
if irq_cfg.enable_in_nvic {
unsafe { enable_nvic_interrupt(irq_cfg.id) };
}
// We only manipulate our own bit.
self.port_reg()
.irq_enb()
.modify(|r, w| unsafe { w.bits(r.bits() | self.mask_32()) });
}
pub fn disable_interrupt(&mut self, reset_irqsel: bool) {
if reset_irqsel {
self.reset_irqsel();
}
// We only manipulate our own bit.
self.port_reg()
.irq_enb()
.modify(|r, w| unsafe { w.bits(r.bits() & !self.mask_32()) });
}
/// Try to recreate a type-level [`Pin`] from a value-level [`DynPin`]
///
/// There is no way for the compiler to know if the conversion will be
/// successful at compile-time. We must verify the conversion at run-time
/// or refuse to perform it.
#[inline]
pub fn upgrade<I: PinId, M: PinMode>(self) -> Result<Pin<I, M>, InvalidPinTypeError> {
if self.id == I::DYN && self.mode == M::DYN {
// The `DynPin` is consumed, so it is safe to replace it with the
// corresponding `Pin`
return Ok(unsafe { Pin::new() });
}
Err(InvalidPinTypeError(self.mode))
}
/// Convert the pin into an async pin. The pin can be converted back by calling
/// [InputDynPinAsync::release]
pub fn into_async_input(
self,
irq: crate::pac::Interrupt,
) -> Result<InputDynPinAsync, InvalidPinTypeError> {
InputDynPinAsync::new(self, irq)
}
/// Configure the IRQSEL peripheral for this particular pin with the given interrupt ID.
pub fn configure_irqsel(&mut self, id: pac::Interrupt) {
let mut syscfg = unsafe { pac::Sysconfig::steal() };
let irqsel = unsafe { pac::Irqsel::steal() };
crate::clock::enable_peripheral_clock(&mut syscfg, crate::clock::PeripheralClocks::Irqsel);
match self.id().port() {
// Set the correct interrupt number in the IRQSEL register
super::Port::A => {
irqsel
.porta0(self.id().num() as usize)
.write(|w| unsafe { w.bits(id as u32) });
}
super::Port::B => {
irqsel
.portb0(self.id().num as usize)
.write(|w| unsafe { w.bits(id as u32) });
}
}
}
/// Reset the IRQSEL peripheral value for this particular pin.
pub fn reset_irqsel(&mut self) {
let mut syscfg = unsafe { pac::Sysconfig::steal() };
let irqsel = unsafe { pac::Irqsel::steal() };
crate::clock::enable_peripheral_clock(&mut syscfg, crate::clock::PeripheralClocks::Irqsel);
match self.id().port() {
// Set the correct interrupt number in the IRQSEL register
super::Port::A => {
irqsel
.porta0(self.id().num() as usize)
.write(|w| unsafe { w.bits(u32::MAX) });
}
super::Port::B => {
irqsel
.portb0(self.id().num as usize)
.write(|w| unsafe { w.bits(u32::MAX) });
}
}
}
// Get DATAMASK bit for this particular pin
#[inline(always)]
pub fn datamask(&self) -> bool {
(self.port_reg().datamask().read().bits() >> self.id().num) == 1
self.regs.datamask()
}
/// Clear DATAMASK bit for this particular pin. This prevents access
/// of the corresponding bit for output and input operations
#[inline(always)]
pub fn clear_datamask(&self) {
self.port_reg()
.datamask()
.modify(|r, w| unsafe { w.bits(r.bits() & !self.mask_32()) });
#[inline]
pub fn clear_datamask(&mut self) {
self.regs.clear_datamask();
}
/// Set DATAMASK bit for this particular pin. 1 is the default
/// state of the bit and allows access of the corresponding bit
#[inline(always)]
pub fn set_datamask(&self) {
self.port_reg()
.datamask()
.modify(|r, w| unsafe { w.bits(r.bits() | self.mask_32()) });
#[inline]
pub fn set_datamask(&mut self) {
self.regs.set_datamask();
}
#[inline]
pub fn is_high_masked(&self) -> Result<bool, crate::gpio::IsMaskedError> {
self.read_pin_masked()
self.regs.read_pin_masked()
}
#[inline]
pub fn is_low_masked(&self) -> Result<bool, crate::gpio::IsMaskedError> {
self.read_pin_masked().map(|v| !v)
self.regs.read_pin_masked().map(|v| !v)
}
#[inline]
pub fn set_high_masked(&mut self) -> Result<(), crate::gpio::IsMaskedError> {
self.write_pin_masked(true)
self.regs.write_pin_masked(true)
}
#[inline]
pub fn set_low_masked(&mut self) -> Result<(), crate::gpio::IsMaskedError> {
self.write_pin_masked(false)
self.regs.write_pin_masked(false)
}
pub(crate) fn irq_enb(
&mut self,
irq_cfg: crate::InterruptConfig,
syscfg: Option<&mut va108xx::Sysconfig>,
irqsel: Option<&mut va108xx::Irqsel>,
) {
if let Some(syscfg) = syscfg {
crate::clock::enable_peripheral_clock(syscfg, crate::clock::PeripheralClocks::Irqsel);
}
self.regs.enable_irq();
if let Some(irqsel) = irqsel {
if irq_cfg.route {
match self.regs.id().group {
// Set the correct interrupt number in the IRQSEL register
DynGroup::A => {
irqsel
.porta0(self.regs.id().num as usize)
.write(|w| unsafe { w.bits(irq_cfg.id as u32) });
}
DynGroup::B => {
irqsel
.portb0(self.regs.id().num as usize)
.write(|w| unsafe { w.bits(irq_cfg.id as u32) });
}
}
}
}
if irq_cfg.enable_in_nvic {
unsafe { enable_nvic_interrupt(irq_cfg.id) };
}
}
/// See p.53 of the programmers guide for more information.
/// Possible delays in clock cycles:
/// - Delay 1: 1
/// - Delay 2: 2
@ -549,13 +400,14 @@ impl DynPin {
) -> Result<(), InvalidPinTypeError> {
match self.mode {
DynPinMode::Output(_) => {
self.configure_delay_internal(delay_1, delay_2);
self.regs.configure_delay(delay_1, delay_2);
Ok(())
}
_ => Err(InvalidPinTypeError(self.mode)),
}
}
/// See p.52 of the programmers guide for more information.
/// 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
#[inline]
@ -566,7 +418,7 @@ impl DynPin {
) -> Result<(), InvalidPinTypeError> {
match self.mode {
DynPinMode::Output(_) => {
self.configure_pulse_mode_internal(enable, default_state);
self.regs.pulse_mode(enable, default_state);
Ok(())
}
_ => Err(InvalidPinTypeError(self.mode)),
@ -582,102 +434,54 @@ impl DynPin {
) -> Result<(), InvalidPinTypeError> {
match self.mode {
DynPinMode::Input(_) => {
self.configure_filter_type_internal(filter, clksel);
self.regs.configure_filter_type(filter, clksel);
Ok(())
}
_ => Err(InvalidPinTypeError(self.mode)),
}
}
#[inline]
pub fn configure_edge_interrupt(
&mut self,
edge_type: InterruptEdge,
irq_cfg: InterruptConfig,
syscfg: Option<&mut pac::Sysconfig>,
irqsel: Option<&mut pac::Irqsel>,
) -> Result<(), InvalidPinTypeError> {
match self.mode {
DynPinMode::Input(_) | DynPinMode::Output(_) => {
self.configure_edge_interrupt_internal(edge_type);
self.regs.configure_edge_interrupt(edge_type);
self.irq_enb(irq_cfg, syscfg, irqsel);
Ok(())
}
_ => Err(InvalidPinTypeError(self.mode)),
}
}
#[inline]
pub fn configure_level_interrupt(
&mut self,
level_type: InterruptLevel,
irq_cfg: InterruptConfig,
syscfg: Option<&mut pac::Sysconfig>,
irqsel: Option<&mut pac::Irqsel>,
) -> Result<(), InvalidPinTypeError> {
match self.mode {
DynPinMode::Input(_) | DynPinMode::Output(_) => {
self.configure_level_interrupt_internal(level_type);
self.regs.configure_level_interrupt(level_type);
self.irq_enb(irq_cfg, syscfg, irqsel);
Ok(())
}
_ => Err(InvalidPinTypeError(self.mode)),
}
}
/// Change the pin mode
#[inline]
pub(crate) fn change_mode(&mut self, mode: DynPinMode) {
let ModeFields {
dir,
funsel,
opendrn,
pull_dir,
pull_en,
enb_input,
} = mode.into();
let (portreg, iocfg) = (self.port_reg(), self.iocfg_port());
iocfg.write(|w| {
w.opendrn().bit(opendrn);
w.pen().bit(pull_en);
w.plevel().bit(pull_dir);
w.iewo().bit(enb_input);
unsafe { w.funsel().bits(funsel) }
});
let mask = self.mask_32();
unsafe {
if dir {
portreg.dir().modify(|r, w| w.bits(r.bits() | mask));
// Clear output
portreg.clrout().write(|w| w.bits(mask));
} else {
portreg.dir().modify(|r, w| w.bits(r.bits() & !mask));
}
}
}
#[inline]
const fn port_reg(&self) -> &PortRegisterBlock {
match self.id().port() {
Port::A => unsafe { &(*pac::Porta::ptr()) },
Port::B => unsafe { &(*pac::Portb::ptr()) },
}
}
#[inline]
const fn iocfg_port(&self) -> &PortReg {
let ioconfig = unsafe { va108xx::Ioconfig::ptr().as_ref().unwrap() };
match self.id().port() {
Port::A => ioconfig.porta(self.id().num() as usize),
Port::B => ioconfig.portb0(self.id().num() as usize),
}
}
#[inline(always)]
fn read_internal(&self) -> Result<bool, InvalidPinTypeError> {
match self.mode {
DynPinMode::Input(_) | DYN_RD_OPEN_DRAIN_OUTPUT | DYN_RD_PUSH_PULL_OUTPUT => {
Ok(self.read_pin())
}
_ => Err(InvalidPinTypeError(self.mode)),
}
}
#[inline(always)]
fn write_internal(&mut self, bit: bool) -> Result<(), InvalidPinTypeError> {
pub fn toggle_with_toggle_reg(&mut self) -> Result<(), InvalidPinTypeError> {
match self.mode {
DynPinMode::Output(_) => {
self.write_pin(bit);
self.regs.toggle();
Ok(())
}
_ => Err(InvalidPinTypeError(self.mode)),
@ -685,190 +489,64 @@ impl DynPin {
}
#[inline]
/// Read the logic level of an output pin
pub(crate) fn read_pin(&self) -> bool {
let portreg = self.port_reg();
((portreg.datainraw().read().bits() >> self.id().num) & 0x01) == 1
}
/// Read a pin but use the masked version but check whether the datamask for the pin is
/// cleared as well
#[inline(always)]
fn read_pin_masked(&self) -> Result<bool, IsMaskedError> {
if !self.datamask() {
Err(IsMaskedError)
} else {
Ok(((self.port_reg().datain().read().bits() >> self.id().num) & 0x01) == 1)
}
}
/// Write the logic level of an output pin
#[inline(always)]
pub(crate) fn write_pin(&mut self, bit: bool) {
// Safety: SETOUT is a "mask" register, and we only write the bit for
// this pin ID
unsafe {
if bit {
self.port_reg().setout().write(|w| w.bits(self.mask_32()));
} else {
self.port_reg().clrout().write(|w| w.bits(self.mask_32()));
fn _read(&self) -> Result<bool, InvalidPinTypeError> {
match self.mode {
DynPinMode::Input(_) | DYN_RD_OPEN_DRAIN_OUTPUT | DYN_RD_PUSH_PULL_OUTPUT => {
Ok(self.regs.read_pin())
}
_ => Err(InvalidPinTypeError(self.mode)),
}
}
/// Write the logic level of an output pin but check whether the datamask for the pin is
/// cleared as well
#[inline]
fn write_pin_masked(&mut self, bit: bool) -> Result<(), IsMaskedError> {
if !self.datamask() {
Err(IsMaskedError)
} else {
// Safety: SETOUT is a "mask" register, and we only write the bit for
// this pin ID
unsafe {
if bit {
self.port_reg().setout().write(|w| w.bits(self.mask_32()));
} else {
self.port_reg().clrout().write(|w| w.bits(self.mask_32()));
}
fn _write(&mut self, bit: bool) -> Result<(), InvalidPinTypeError> {
match self.mode {
DynPinMode::Output(_) => {
self.regs.write_pin(bit);
Ok(())
}
_ => Err(InvalidPinTypeError(self.mode)),
}
}
/// Toggle the logic level of an output pin
#[inline(always)]
pub fn toggle_with_togout_reg(&mut self) {
// Safety: TOGOUT is a "mask" register, and we only write the bit for
// this pin ID
unsafe { self.port_reg().togout().write(|w| w.bits(self.mask_32())) };
#[inline]
fn _is_low(&self) -> Result<bool, InvalidPinTypeError> {
self._read().map(|v| !v)
}
#[inline]
fn _is_high(&self) -> Result<bool, InvalidPinTypeError> {
self._read()
}
#[inline]
fn _set_low(&mut self) -> Result<(), InvalidPinTypeError> {
self._write(false)
}
#[inline]
fn _set_high(&mut self) -> Result<(), InvalidPinTypeError> {
self._write(true)
}
/// Only useful for interrupt pins. Configure whether to use edges or level as interrupt soure
/// When using edge mode, it is possible to generate interrupts on both edges as well
/// Try to recreate a type-level [`Pin`] from a value-level [`DynPin`]
///
/// There is no way for the compiler to know if the conversion will be
/// successful at compile-time. We must verify the conversion at run-time
/// or refuse to perform it.
#[inline]
fn configure_edge_interrupt_internal(&mut self, edge_type: InterruptEdge) {
unsafe {
self.port_reg()
.irq_sen()
.modify(|r, w| w.bits(r.bits() & !self.mask_32()));
match edge_type {
InterruptEdge::HighToLow => {
self.port_reg()
.irq_evt()
.modify(|r, w| w.bits(r.bits() & !self.mask_32()));
}
InterruptEdge::LowToHigh => {
self.port_reg()
.irq_evt()
.modify(|r, w| w.bits(r.bits() | self.mask_32()));
}
InterruptEdge::BothEdges => {
self.port_reg()
.irq_edge()
.modify(|r, w| w.bits(r.bits() | self.mask_32()));
}
}
pub fn upgrade<I: PinId, M: PinMode>(self) -> Result<Pin<I, M>, InvalidPinTypeError> {
if self.regs.0 == I::DYN && self.mode == M::DYN {
// The `DynPin` is consumed, so it is safe to replace it with the
// corresponding `Pin`
return Ok(unsafe { Pin::new() });
}
Err(InvalidPinTypeError(self.mode))
}
/// Configure which edge or level type triggers an interrupt
#[inline]
fn configure_level_interrupt_internal(&mut self, level: InterruptLevel) {
unsafe {
self.port_reg()
.irq_sen()
.modify(|r, w| w.bits(r.bits() | self.mask_32()));
if level == InterruptLevel::Low {
self.port_reg()
.irq_evt()
.modify(|r, w| w.bits(r.bits() & !self.mask_32()));
} else {
self.port_reg()
.irq_evt()
.modify(|r, w| w.bits(r.bits() | self.mask_32()));
}
}
}
/// Only useful for input pins
#[inline]
fn configure_filter_type_internal(&mut self, filter: FilterType, clksel: FilterClkSel) {
self.iocfg_port().modify(|_, w| {
// Safety: Only write to register for this Pin ID
unsafe {
w.flttype().bits(filter as u8);
w.fltclk().bits(clksel as u8)
}
});
}
#[inline]
fn configure_pulse_mode_internal(&mut self, enable: bool, default_state: PinState) {
let portreg = self.port_reg();
unsafe {
if enable {
portreg
.pulse()
.modify(|r, w| w.bits(r.bits() | self.mask_32()));
} else {
portreg
.pulse()
.modify(|r, w| w.bits(r.bits() & !self.mask_32()));
}
if default_state == PinState::Low {
portreg
.pulsebase()
.modify(|r, w| w.bits(r.bits() & !self.mask_32()));
} else {
portreg
.pulsebase()
.modify(|r, w| w.bits(r.bits() | self.mask_32()));
}
}
}
/// Only useful for output pins
#[inline]
fn configure_delay_internal(&mut self, delay_1: bool, delay_2: bool) {
let portreg = self.port_reg();
unsafe {
if delay_1 {
portreg
.delay1()
.modify(|r, w| w.bits(r.bits() | self.mask_32()));
} else {
portreg
.delay1()
.modify(|r, w| w.bits(r.bits() & !self.mask_32()));
}
if delay_2 {
portreg
.delay2()
.modify(|r, w| w.bits(r.bits() | self.mask_32()));
} else {
portreg
.delay2()
.modify(|r, w| w.bits(r.bits() & !self.mask_32()));
}
}
}
// Only serves disambiguation purposes for the Embedded HAL impl
#[inline(always)]
fn is_low_mut(&mut self) -> Result<bool, InvalidPinTypeError> {
self.is_low()
}
// Only serves disambiguation purposes for the Embedded HAL impl
#[inline(always)]
fn is_high_mut(&mut self) -> Result<bool, InvalidPinTypeError> {
self.is_high()
}
#[inline(always)]
const fn mask_32(&self) -> u32 {
1 << self.id().num()
/// Convert the pin into an async pin. The pin can be converted back by calling
/// [InputDynPinAsync::release]
pub fn into_async_input(
self,
irq: crate::pac::Interrupt,
) -> Result<InputDynPinAsync, InvalidPinTypeError> {
InputDynPinAsync::new(self, irq)
}
}
@ -910,38 +588,33 @@ impl embedded_hal::digital::ErrorType for DynPin {
impl embedded_hal::digital::OutputPin for DynPin {
#[inline]
fn set_high(&mut self) -> Result<(), Self::Error> {
self.set_high()
self._set_high()
}
#[inline]
fn set_low(&mut self) -> Result<(), Self::Error> {
self.set_low()
self._set_low()
}
}
impl embedded_hal::digital::InputPin for DynPin {
#[inline]
fn is_high(&mut self) -> Result<bool, Self::Error> {
self.is_high_mut()
self._is_high()
}
#[inline]
fn is_low(&mut self) -> Result<bool, Self::Error> {
self.is_low_mut()
self._is_low()
}
}
impl embedded_hal::digital::StatefulOutputPin for DynPin {
#[inline]
fn is_set_high(&mut self) -> Result<bool, Self::Error> {
self.is_high_mut()
self._is_high()
}
#[inline]
fn is_set_low(&mut self) -> Result<bool, Self::Error> {
self.is_low_mut()
}
#[inline]
fn toggle(&mut self) -> Result<(), Self::Error> {
self.toggle()
self._is_low()
}
}

View File

@ -22,47 +22,14 @@
//!
//! - [Blinky example](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples/simple/examples/blinky.rs)
//==================================================================================================
// Errors, Definitions and Constants
//==================================================================================================
pub const NUM_PINS_PORT_A: usize = 32;
pub const NUM_PINS_PORT_B: usize = 24;
pub const NUM_GPIO_PINS: usize = NUM_PINS_PORT_A + NUM_PINS_PORT_B;
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[error("The pin is masked")]
pub struct IsMaskedError;
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Port {
A,
B,
}
#[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 const NUM_PINS_PORT_A: usize = 32;
pub const NUM_PINS_PORT_B: usize = 24;
pub const NUM_GPIO_PINS: usize = NUM_PINS_PORT_A + NUM_PINS_PORT_B;
pub mod dynpin;
pub use dynpin::*;
@ -72,3 +39,5 @@ pub use pin::*;
pub mod asynch;
pub use asynch::*;
mod reg;

View File

@ -68,19 +68,48 @@
//! # 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 super::dynpin::{DynAlternate, DynInput, DynOutput, DynPinId, DynPinMode};
use super::{DynPin, InputPinAsync, InterruptEdge, InterruptLevel, PinState, Port};
//! in the corresponding [`PinMode`]s, namely: [`InputPin`], [`OutputPin`],
//! and [`StatefulOutputPin`].
use super::dynpin::{DynAlternate, DynGroup, DynInput, DynOutput, DynPinId, DynPinMode};
use super::reg::RegisterInterface;
use super::{DynPin, InputPinAsync};
use crate::{
pac::{Porta, Portb},
pac::{Irqsel, Porta, Portb, Sysconfig},
typelevel::Sealed,
InterruptConfig,
};
use core::convert::Infallible;
use core::marker::PhantomData;
use core::mem::transmute;
use embedded_hal::digital::{InputPin, OutputPin, StatefulOutputPin};
use paste::paste;
//==================================================================================================
// Errors and Definitions
//==================================================================================================
#[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,
}
//==================================================================================================
// Input configuration
//==================================================================================================
@ -291,7 +320,10 @@ macro_rules! pin_id {
pub enum $Id {}
impl Sealed for $Id {}
impl PinId for $Id {
const DYN: DynPinId = DynPinId::new(Port::$Group, $NUM);
const DYN: DynPinId = DynPinId {
group: DynGroup::$Group,
num: $NUM,
};
}
}
};
@ -317,7 +349,7 @@ impl<I: PinId, M: PinMode> Pin<I, M> {
/// 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> {
pub(crate) unsafe fn new() -> Pin<I, M> {
Pin {
inner: DynPin::new(I::DYN, M::DYN),
phantom: PhantomData,
@ -325,7 +357,7 @@ impl<I: PinId, M: PinMode> Pin<I, M> {
}
#[inline]
pub const fn id(&self) -> DynPinId {
pub fn id(&self) -> DynPinId {
self.inner.id()
}
@ -335,7 +367,7 @@ impl<I: PinId, M: PinMode> Pin<I, M> {
// 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);
self.inner.regs.change_mode(N::DYN);
}
// Safe because we drop the existing Pin
unsafe { Pin::new() }
@ -395,16 +427,6 @@ impl<I: PinId, M: PinMode> Pin<I, M> {
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()
@ -430,41 +452,48 @@ impl<I: PinId, M: PinMode> Pin<I, M> {
self.inner.is_low_masked()
}
#[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()
}
#[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()
fn irq_enb(
&mut self,
irq_cfg: crate::InterruptConfig,
syscfg: Option<&mut va108xx::Sysconfig>,
irqsel: Option<&mut va108xx::Irqsel>,
) {
self.inner.irq_enb(irq_cfg, syscfg, irqsel);
}
#[inline]
fn is_high_mut(&mut self) -> bool {
self.is_high()
pub(crate) fn _set_high(&mut self) {
self.inner.regs.write_pin(true)
}
#[inline]
pub fn enable_interrupt(&mut self, irq_cfg: crate::InterruptConfig) {
self.inner.enable_interrupt(irq_cfg);
pub(crate) fn _set_low(&mut self) {
self.inner.regs.write_pin(false)
}
#[inline]
pub fn disable_interrupt(&mut self, reset_irqsel: bool) {
self.inner.disable_interrupt(reset_irqsel);
pub(crate) fn _is_low(&self) -> bool {
!self.inner.regs.read_pin()
}
/// 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();
#[inline]
pub(crate) fn _is_high(&self) -> bool {
self.inner.regs.read_pin()
}
}
@ -561,34 +590,31 @@ impl<I: PinId, C: InputConfig> Pin<I, Input<C>> {
pub fn into_async_input(self, irq: crate::pac::Interrupt) -> InputPinAsync<I, C> {
InputPinAsync::new(self, irq)
}
pub fn configure_edge_interrupt(
&mut self,
edge_type: InterruptEdge,
irq_cfg: InterruptConfig,
syscfg: Option<&mut Sysconfig>,
irqsel: Option<&mut Irqsel>,
) {
self.inner.regs.configure_edge_interrupt(edge_type);
self.irq_enb(irq_cfg, syscfg, irqsel);
}
pub fn configure_level_interrupt(
&mut self,
level_type: InterruptLevel,
irq_cfg: InterruptConfig,
syscfg: Option<&mut Sysconfig>,
irqsel: Option<&mut Irqsel>,
) {
self.inner.regs.configure_level_interrupt(level_type);
self.irq_enb(irq_cfg, syscfg, irqsel);
}
}
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()
}
/// See p.53 of the programmers guide for more information.
/// Possible delays in clock cycles:
/// - Delay 1: 1
@ -596,17 +622,78 @@ impl<I: PinId, C: OutputConfig> Pin<I, Output<C>> {
/// - 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();
self.inner.regs.configure_delay(delay_1, delay_2);
}
#[inline]
pub fn toggle_with_toggle_reg(&mut self) {
self.inner.regs.toggle()
}
#[deprecated(
since = "0.9.0",
note = "Please use the `configure_pulse_mode` method instead"
)]
pub fn pulse_mode(&mut self, enable: bool, default_state: PinState) {
self.configure_pulse_mode(enable, default_state);
}
/// See p.52 of the programmers guide for more information.
///
/// 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();
self.inner.regs.pulse_mode(enable, default_state);
}
#[deprecated(
since = "0.9.0",
note = "Please use the `configure_edge_interrupt` method instead"
)]
pub fn interrupt_edge(
&mut self,
edge_type: InterruptEdge,
irq_cfg: InterruptConfig,
syscfg: Option<&mut Sysconfig>,
irqsel: Option<&mut Irqsel>,
) {
self.inner.regs.configure_edge_interrupt(edge_type);
self.irq_enb(irq_cfg, syscfg, irqsel);
}
pub fn configure_edge_interrupt(
&mut self,
edge_type: InterruptEdge,
irq_cfg: InterruptConfig,
syscfg: Option<&mut Sysconfig>,
irqsel: Option<&mut Irqsel>,
) {
self.inner.regs.configure_edge_interrupt(edge_type);
self.irq_enb(irq_cfg, syscfg, irqsel);
}
#[deprecated(
since = "0.9.0",
note = "Please use the `configure_level_interrupt` method instead"
)]
pub fn level_interrupt(
&mut self,
level_type: InterruptLevel,
irq_cfg: InterruptConfig,
syscfg: Option<&mut Sysconfig>,
irqsel: Option<&mut Irqsel>,
) {
self.configure_level_interrupt(level_type, irq_cfg, syscfg, irqsel);
}
pub fn configure_level_interrupt(
&mut self,
level_type: InterruptLevel,
irq_cfg: InterruptConfig,
syscfg: Option<&mut Sysconfig>,
irqsel: Option<&mut Irqsel>,
) {
self.inner.regs.configure_level_interrupt(level_type);
self.irq_enb(irq_cfg, syscfg, irqsel);
}
}
@ -614,7 +701,7 @@ impl<I: PinId, C: InputConfig> Pin<I, Input<C>> {
/// See p.37 and p.38 of the programmers guide for more information.
#[inline]
pub fn configure_filter_type(&mut self, filter: FilterType, clksel: FilterClkSel) {
self.inner.configure_filter_type(filter, clksel).unwrap();
self.inner.regs.configure_filter_type(filter, clksel);
}
}
@ -630,53 +717,63 @@ where
type Error = Infallible;
}
impl<I: PinId, C: OutputConfig> embedded_hal::digital::OutputPin for Pin<I, Output<C>> {
impl<I: PinId, C: OutputConfig> OutputPin for Pin<I, Output<C>> {
#[inline]
fn set_high(&mut self) -> Result<(), Self::Error> {
self.set_high();
self._set_high();
Ok(())
}
#[inline]
fn set_low(&mut self) -> Result<(), Self::Error> {
self.set_low();
self._set_low();
Ok(())
}
}
impl<I, C> embedded_hal::digital::InputPin for Pin<I, Input<C>>
impl<I, C> 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())
Ok(self._is_high())
}
#[inline]
fn is_low(&mut self) -> Result<bool, Self::Error> {
Ok(self.is_low_mut())
Ok(self._is_low())
}
}
impl<I, C> embedded_hal::digital::StatefulOutputPin for Pin<I, Output<C>>
impl<I, C> 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())
Ok(self._is_high())
}
#[inline]
fn is_set_low(&mut self) -> Result<bool, Self::Error> {
Ok(self.is_low())
Ok(self._is_low())
}
}
impl<I, C> InputPin for Pin<I, Output<C>>
where
I: PinId,
C: OutputConfig + ReadableOutput,
{
#[inline]
fn is_high(&mut self) -> Result<bool, Self::Error> {
Ok(self._is_high())
}
#[inline]
fn toggle(&mut self) -> Result<(), Self::Error> {
self.toggle();
Ok(())
fn is_low(&mut self) -> Result<bool, Self::Error> {
Ok(self._is_low())
}
}

375
va108xx-hal/src/gpio/reg.rs Normal file
View File

@ -0,0 +1,375 @@
use super::dynpin::{self, DynGroup, DynPinId, DynPinMode};
use super::pin::{FilterType, InterruptEdge, InterruptLevel, PinState};
use super::IsMaskedError;
use crate::clock::FilterClkSel;
use va108xx::{ioconfig, porta};
/// Type definition to avoid confusion: These register blocks are identical
type PortRegisterBlock = porta::RegisterBlock;
//==================================================================================================
// ModeFields
//==================================================================================================
/// Collect all fields needed to set the [`PinMode`](super::PinMode)
#[derive(Default)]
struct ModeFields {
dir: bool,
opendrn: bool,
pull_en: bool,
/// true for pullup, false for pulldown
pull_dir: bool,
funsel: u8,
enb_input: bool,
}
impl From<DynPinMode> for ModeFields {
#[inline]
fn from(mode: DynPinMode) -> Self {
let mut fields = Self::default();
use DynPinMode::*;
match mode {
Input(config) => {
use dynpin::DynInput::*;
fields.dir = false;
match config {
Floating => (),
PullUp => {
fields.pull_en = true;
fields.pull_dir = true;
}
PullDown => {
fields.pull_en = true;
}
}
}
Output(config) => {
use dynpin::DynOutput::*;
fields.dir = true;
match config {
PushPull => (),
OpenDrain => {
fields.opendrn = true;
}
ReadableOpenDrain => {
fields.enb_input = true;
fields.opendrn = true;
}
ReadablePushPull => {
fields.enb_input = true;
}
}
}
Alternate(config) => {
fields.funsel = config as u8;
}
}
fields
}
}
//==================================================================================================
// Register Interface
//==================================================================================================
pub type PortReg = ioconfig::Porta;
/// Provide a safe register interface for pin objects
///
/// [`PORTA`] and [`PORTB`], like every PAC `struct`, is [`Send`] but not [`Sync`], because it
/// points to a `RegisterBlock` of `VolatileCell`s. Unfortunately, such an
/// interface is quite restrictive. Instead, it would be ideal if we could split
/// the [`PORT`] into independent pins that are both [`Send`] and [`Sync`].
///
/// [`PORT`] is a single, zero-sized marker `struct` that provides access to
/// every [`PORT`] register. Instead, we would like to create zero-sized marker
/// `struct`s for every pin, where each pin is only allowed to control its own
/// registers. Furthermore, each pin `struct` should be a singleton, so that
/// exclusive access to the `struct` also guarantees exclusive access to the
/// corresponding registers. Finally, the pin `struct`s should not have any
/// interior mutability. Together, these requirements would allow the pin
/// `struct`s to be both [`Send`] and [`Sync`].
///
/// This trait creates a safe API for accomplishing these goals. Implementers
/// supply a pin ID through the [`id`] function. The remaining functions provide
/// a safe API for accessing the registers associated with that pin ID. Any
/// modification of the registers requires `&mut self`, which destroys interior
/// mutability.
///
/// # Safety
///
/// Users should only implement the [`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.
///
/// [`id`]: Self::id
pub(super) unsafe trait RegisterInterface {
/// Provide a [`DynPinId`] identifying the set of registers controlled by
/// this type.
fn id(&self) -> DynPinId;
const PORTA: *const PortRegisterBlock = va108xx::Porta::ptr();
const PORTB: *const PortRegisterBlock = va108xx::Portb::ptr();
/// Change the pin mode
#[inline]
fn change_mode(&mut self, mode: DynPinMode) {
let ModeFields {
dir,
funsel,
opendrn,
pull_dir,
pull_en,
enb_input,
} = mode.into();
let (portreg, iocfg) = (self.port_reg(), self.iocfg_port());
iocfg.write(|w| {
w.opendrn().bit(opendrn);
w.pen().bit(pull_en);
w.plevel().bit(pull_dir);
w.iewo().bit(enb_input);
unsafe { w.funsel().bits(funsel) }
});
let mask = self.mask_32();
unsafe {
if dir {
portreg.dir().modify(|r, w| w.bits(r.bits() | mask));
// Clear output
portreg.clrout().write(|w| w.bits(mask));
} else {
portreg.dir().modify(|r, w| w.bits(r.bits() & !mask));
}
}
}
#[inline]
fn port_reg(&self) -> &PortRegisterBlock {
match self.id().group {
DynGroup::A => unsafe { &(*Self::PORTA) },
DynGroup::B => unsafe { &(*Self::PORTB) },
}
}
fn iocfg_port(&self) -> &PortReg {
let ioconfig = unsafe { va108xx::Ioconfig::ptr().as_ref().unwrap() };
match self.id().group {
DynGroup::A => ioconfig.porta(self.id().num as usize),
DynGroup::B => ioconfig.portb0(self.id().num as usize),
}
}
#[inline]
fn mask_32(&self) -> u32 {
1 << self.id().num
}
#[inline]
fn enable_irq(&self) {
self.port_reg()
.irq_enb()
.modify(|r, w| unsafe { w.bits(r.bits() | self.mask_32()) });
}
#[inline]
/// Read the logic level of an output pin
fn read_pin(&self) -> bool {
let portreg = self.port_reg();
((portreg.datainraw().read().bits() >> self.id().num) & 0x01) == 1
}
// Get DATAMASK bit for this particular pin
#[inline(always)]
fn datamask(&self) -> bool {
let portreg = self.port_reg();
(portreg.datamask().read().bits() >> self.id().num) == 1
}
/// Read a pin but use the masked version but check whether the datamask for the pin is
/// cleared as well
#[inline(always)]
fn read_pin_masked(&self) -> Result<bool, IsMaskedError> {
if !self.datamask() {
Err(IsMaskedError)
} else {
Ok(((self.port_reg().datain().read().bits() >> self.id().num) & 0x01) == 1)
}
}
/// Write the logic level of an output pin
#[inline(always)]
fn write_pin(&mut self, bit: bool) {
// Safety: SETOUT is a "mask" register, and we only write the bit for
// this pin ID
unsafe {
if bit {
self.port_reg().setout().write(|w| w.bits(self.mask_32()));
} else {
self.port_reg().clrout().write(|w| w.bits(self.mask_32()));
}
}
}
/// Write the logic level of an output pin but check whether the datamask for the pin is
/// cleared as well
#[inline]
fn write_pin_masked(&mut self, bit: bool) -> Result<(), IsMaskedError> {
if !self.datamask() {
Err(IsMaskedError)
} else {
// Safety: SETOUT is a "mask" register, and we only write the bit for
// this pin ID
unsafe {
if bit {
self.port_reg().setout().write(|w| w.bits(self.mask_32()));
} else {
self.port_reg().clrout().write(|w| w.bits(self.mask_32()));
}
Ok(())
}
}
}
/// Toggle the logic level of an output pin
#[inline(always)]
fn toggle(&mut self) {
// Safety: TOGOUT is a "mask" register, and we only write the bit for
// this pin ID
unsafe { self.port_reg().togout().write(|w| w.bits(self.mask_32())) };
}
/// Only useful for interrupt pins. Configure whether to use edges or level as interrupt soure
/// When using edge mode, it is possible to generate interrupts on both edges as well
#[inline]
fn configure_edge_interrupt(&mut self, edge_type: InterruptEdge) {
unsafe {
self.port_reg()
.irq_sen()
.modify(|r, w| w.bits(r.bits() & !self.mask_32()));
match edge_type {
InterruptEdge::HighToLow => {
self.port_reg()
.irq_evt()
.modify(|r, w| w.bits(r.bits() & !self.mask_32()));
}
InterruptEdge::LowToHigh => {
self.port_reg()
.irq_evt()
.modify(|r, w| w.bits(r.bits() | self.mask_32()));
}
InterruptEdge::BothEdges => {
self.port_reg()
.irq_edge()
.modify(|r, w| w.bits(r.bits() | self.mask_32()));
}
}
}
}
/// Configure which edge or level type triggers an interrupt
#[inline]
fn configure_level_interrupt(&mut self, level: InterruptLevel) {
unsafe {
self.port_reg()
.irq_sen()
.modify(|r, w| w.bits(r.bits() | self.mask_32()));
if level == InterruptLevel::Low {
self.port_reg()
.irq_evt()
.modify(|r, w| w.bits(r.bits() & !self.mask_32()));
} else {
self.port_reg()
.irq_evt()
.modify(|r, w| w.bits(r.bits() | self.mask_32()));
}
}
}
/// Only useful for input pins
#[inline]
fn configure_filter_type(&mut self, filter: FilterType, clksel: FilterClkSel) {
self.iocfg_port().modify(|_, w| {
// Safety: Only write to register for this Pin ID
unsafe {
w.flttype().bits(filter as u8);
w.fltclk().bits(clksel as u8)
}
});
}
/// Set DATAMASK bit for this particular pin. 1 is the default
/// state of the bit and allows access of the corresponding bit
#[inline(always)]
fn set_datamask(&self) {
let portreg = self.port_reg();
unsafe {
portreg
.datamask()
.modify(|r, w| w.bits(r.bits() | self.mask_32()));
}
}
/// Clear DATAMASK bit for this particular pin. This prevents access
/// of the corresponding bit for output and input operations
#[inline(always)]
fn clear_datamask(&self) {
let portreg = self.port_reg();
unsafe {
portreg
.datamask()
.modify(|r, w| w.bits(r.bits() & !self.mask_32()));
}
}
/// Only useful for output pins
/// See p.52 of the programmers guide for more information.
/// 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
fn pulse_mode(&mut self, enable: bool, default_state: PinState) {
let portreg = self.port_reg();
unsafe {
if enable {
portreg
.pulse()
.modify(|r, w| w.bits(r.bits() | self.mask_32()));
} else {
portreg
.pulse()
.modify(|r, w| w.bits(r.bits() & !self.mask_32()));
}
if default_state == PinState::Low {
portreg
.pulsebase()
.modify(|r, w| w.bits(r.bits() & !self.mask_32()));
} else {
portreg
.pulsebase()
.modify(|r, w| w.bits(r.bits() | self.mask_32()));
}
}
}
/// Only useful for output pins
fn configure_delay(&mut self, delay_1: bool, delay_2: bool) {
let portreg = self.port_reg();
unsafe {
if delay_1 {
portreg
.delay1()
.modify(|r, w| w.bits(r.bits() | self.mask_32()));
} else {
portreg
.delay1()
.modify(|r, w| w.bits(r.bits() & !self.mask_32()));
}
if delay_2 {
portreg
.delay2()
.modify(|r, w| w.bits(r.bits() | self.mask_32()));
} else {
portreg
.delay2()
.modify(|r, w| w.bits(r.bits() & !self.mask_32()));
}
}
}
}

View File

@ -198,13 +198,13 @@ impl TimingCfg {
}
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.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)
}
}
@ -376,7 +376,7 @@ impl<I2c: Instance> I2cBase<I2c> {
if let Some(max_words) = max_words {
self.i2c
.s0_maxwords()
.write(|w| unsafe { w.bits((1 << 31) | max_words as u32) });
.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
@ -437,7 +437,7 @@ impl<I2c: Instance> I2cBase<I2c> {
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) });
.write(|w| unsafe { w.bits((speed_mode as u32) << 31 | clk_div as u32) });
Ok(())
}

View File

@ -1,7 +1,6 @@
#![no_std]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
use gpio::Port;
pub use va108xx;
pub use va108xx as pac;
@ -20,12 +19,18 @@ pub mod uart;
#[derive(Debug, Eq, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum FunSel {
Sel0 = 0b00,
Sel1 = 0b01,
Sel2 = 0b10,
Sel3 = 0b11,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum PortSel {
PortA,
PortB,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum PeripheralSelect {
@ -72,33 +77,37 @@ impl InterruptConfig {
pub type IrqCfg = InterruptConfig;
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[error("invalid pin with number {0}")]
pub struct InvalidPinError(u8);
pub struct InvalidPin(pub(crate) ());
/// Can be used to manually manipulate the function select of port pins.
///
/// The function selection table can be found on p.36 of the programmers guide. Please note
/// that most of the structures and APIs in this library will automatically correctly configure
/// the pin or statically expect the correct pin type.
/// Can be used to manually manipulate the function select of port pins
pub fn port_function_select(
ioconfig: &mut pac::Ioconfig,
port: Port,
port: PortSel,
pin: u8,
funsel: FunSel,
) -> Result<(), InvalidPinError> {
if (port == Port::A && pin >= 32) || (port == Port::B && pin >= 24) {
return Err(InvalidPinError(pin));
) -> Result<(), InvalidPin> {
match port {
PortSel::PortA => {
if pin > 31 {
return Err(InvalidPin(()));
}
ioconfig
.porta(pin as usize)
.modify(|_, w| unsafe { w.funsel().bits(funsel as u8) });
Ok(())
}
PortSel::PortB => {
if pin > 23 {
return Err(InvalidPin(()));
}
ioconfig
.portb0(pin as usize)
.modify(|_, w| unsafe { w.funsel().bits(funsel as u8) });
Ok(())
}
}
let reg_block = match port {
Port::A => ioconfig.porta(pin as usize),
Port::B => ioconfig.portb0(pin as usize),
};
reg_block.modify(|_, w| unsafe { w.funsel().bits(funsel as u8) });
Ok(())
}
/// Enable a specific interrupt using the NVIC peripheral.

View File

@ -1,12 +1,5 @@
//! # API for the UART peripheral
//!
//! The core of this API are the [Uart], [UartBase], [Rx] and [Tx] structures.
//! The RX structure also has a dedicated [RxWithInterrupt] variant which allows reading the receiver
//! using interrupts.
//!
//! The [rx_asynch] and [tx_asynch] modules provide an asynchronous non-blocking API for the UART
//! peripheral.
//!
//! ## Examples
//!
//! - [UART simple example](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples/simple/examples/uart.rs)
@ -29,7 +22,7 @@ use crate::{
};
use embedded_hal_nb::serial::Read;
#[derive(Debug, Clone, Copy)]
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Bank {
A = 0,
@ -71,6 +64,55 @@ pub struct NoInterruptIdWasSet;
#[error("transer is pending")]
pub struct TransferPendingError;
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum RxError {
#[error("overrun error")]
Overrun,
#[error("framing error")]
Framing,
#[error("parity error")]
Parity,
}
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Error {
#[error("rx error: {0}")]
Rx(#[from] RxError),
#[error("break condition")]
BreakCondition,
}
impl embedded_io::Error for Error {
fn kind(&self) -> embedded_io::ErrorKind {
embedded_io::ErrorKind::Other
}
}
impl embedded_io::Error for RxError {
fn kind(&self) -> embedded_io::ErrorKind {
embedded_io::ErrorKind::Other
}
}
impl embedded_hal_nb::serial::Error for RxError {
fn kind(&self) -> embedded_hal_nb::serial::ErrorKind {
match self {
RxError::Overrun => embedded_hal_nb::serial::ErrorKind::Overrun,
RxError::Framing => embedded_hal_nb::serial::ErrorKind::FrameFormat,
RxError::Parity => embedded_hal_nb::serial::ErrorKind::Parity,
}
}
}
impl embedded_hal_nb::serial::Error for Error {
fn kind(&self) -> embedded_hal_nb::serial::ErrorKind {
match self {
Error::Rx(rx_error) => embedded_hal_nb::serial::Error::kind(rx_error),
Error::BreakCondition => embedded_hal_nb::serial::ErrorKind::Other,
}
}
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Event {
@ -321,7 +363,7 @@ impl UartErrors {
impl UartErrors {
#[inline(always)]
pub fn error(&self) -> bool {
self.overflow || self.framing || self.parity || self.other
self.overflow || self.framing || self.parity
}
}
@ -339,7 +381,6 @@ pub struct BufferTooShortError {
pub trait Instance: Deref<Target = uart_base::RegisterBlock> {
const IDX: u8;
const PERIPH_SEL: PeripheralSelect;
const PTR: *const uart_base::RegisterBlock;
/// Retrieve the peripheral structure.
///
@ -347,11 +388,7 @@ pub trait Instance: Deref<Target = uart_base::RegisterBlock> {
///
/// This circumvents the safety guarantees of the HAL.
unsafe fn steal() -> Self;
#[inline(always)]
fn ptr() -> *const uart_base::RegisterBlock {
Self::PTR
}
fn ptr() -> *const uart_base::RegisterBlock;
/// Retrieve the type erased peripheral register block.
///
@ -368,11 +405,14 @@ impl Instance for pac::Uarta {
const IDX: u8 = 0;
const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Uart0;
const PTR: *const uart_base::RegisterBlock = Self::PTR;
#[inline(always)]
unsafe fn steal() -> Self {
Self::steal()
pac::Peripherals::steal().uarta
}
#[inline(always)]
fn ptr() -> *const uart_base::RegisterBlock {
Self::ptr() as *const _
}
}
@ -380,25 +420,14 @@ impl Instance for pac::Uartb {
const IDX: u8 = 1;
const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Uart1;
const PTR: *const uart_base::RegisterBlock = Self::PTR;
#[inline(always)]
unsafe fn steal() -> Self {
Self::steal()
pac::Peripherals::steal().uartb
}
}
impl Bank {
/// Retrieve the peripheral register block.
///
/// # Safety
///
/// Circumvents the HAL safety guarantees.
pub unsafe fn reg_block(&self) -> &'static uart_base::RegisterBlock {
match self {
Bank::A => unsafe { pac::Uarta::reg_block() },
Bank::B => unsafe { pac::Uartb::reg_block() },
}
#[inline(always)]
fn ptr() -> *const uart_base::RegisterBlock {
Self::ptr() as *const _
}
}
@ -543,27 +572,22 @@ impl<Uart: Instance> UartBase<Uart> {
self.uart
}
/// Poll receiver errors.
pub fn poll_rx_errors(&self) -> Option<UartErrors> {
self.rx.poll_errors()
}
pub fn split(self) -> (Tx<Uart>, Rx<Uart>) {
(self.tx, self.rx)
}
}
impl<UartInstance> embedded_io::ErrorType for UartBase<UartInstance> {
type Error = Infallible;
type Error = Error;
}
impl<UartInstance> embedded_hal_nb::serial::ErrorType for UartBase<UartInstance> {
type Error = Infallible;
type Error = Error;
}
impl<Uart: Instance> embedded_hal_nb::serial::Read<u8> for UartBase<Uart> {
fn read(&mut self) -> nb::Result<u8, Self::Error> {
self.rx.read()
self.rx.read().map_err(|e| e.map(Error::Rx))
}
}
@ -674,11 +698,6 @@ where
self
}
/// Poll receiver errors.
pub fn poll_rx_errors(&self) -> Option<UartErrors> {
self.inner.poll_rx_errors()
}
#[inline]
pub fn enable_rx(&mut self) {
self.inner.enable_rx();
@ -775,12 +794,14 @@ pub fn disable_rx_interrupts(uart: &uart_base::RegisterBlock) {
/// Serial receiver.
///
/// Can be created by using the [Uart::split] or [UartBase::split] API.
pub struct Rx<Uart>(Uart);
pub struct Rx<Uart> {
uart: Uart,
}
impl<Uart: Instance> Rx<Uart> {
#[inline(always)]
const fn new(uart: Uart) -> Self {
Self(uart)
fn new(uart: Uart) -> Self {
Self { uart }
}
/// Direct access to the peripheral structure.
@ -789,30 +810,13 @@ impl<Uart: Instance> Rx<Uart> {
///
/// You must ensure that only registers related to the operation of the RX side are used.
#[inline(always)]
pub const unsafe fn uart(&self) -> &Uart {
&self.0
}
pub fn poll_errors(&self) -> Option<UartErrors> {
let mut errors = UartErrors::default();
let uart = unsafe { &(*Uart::ptr()) };
let status_reader = uart.rxstatus().read();
if status_reader.rxovr().bit_is_set() {
errors.overflow = true;
} else if status_reader.rxfrm().bit_is_set() {
errors.framing = true;
} else if status_reader.rxpar().bit_is_set() {
errors.parity = true;
} else {
return None;
};
Some(errors)
pub unsafe fn uart(&self) -> &Uart {
&self.uart
}
#[inline]
pub fn clear_fifo(&self) {
self.0.fifo_clr().write(|w| w.rxfifo().set_bit());
self.uart.fifo_clr().write(|w| w.rxfifo().set_bit());
}
#[inline]
@ -842,7 +846,7 @@ impl<Uart: Instance> Rx<Uart> {
/// value if you use the manual parity mode. See chapter 4.6.2 for more information.
#[inline(always)]
pub fn read_fifo(&self) -> nb::Result<u32, Infallible> {
if self.0.rxstatus().read().rdavl().bit_is_clear() {
if self.uart.rxstatus().read().rdavl().bit_is_clear() {
return Err(nb::Error::WouldBlock);
}
Ok(self.read_fifo_unchecked())
@ -858,7 +862,7 @@ impl<Uart: Instance> Rx<Uart> {
/// value if you use the manual parity mode. See chapter 4.6.2 for more information.
#[inline(always)]
pub fn read_fifo_unchecked(&self) -> u32 {
self.0.data().read().bits()
self.uart.data().read().bits()
}
pub fn into_rx_with_irq(self) -> RxWithInterrupt<Uart> {
@ -867,20 +871,39 @@ impl<Uart: Instance> Rx<Uart> {
#[inline(always)]
pub fn release(self) -> Uart {
self.0
self.uart
}
}
impl<Uart> embedded_io::ErrorType for Rx<Uart> {
type Error = Infallible;
type Error = RxError;
}
impl<Uart> embedded_hal_nb::serial::ErrorType for Rx<Uart> {
type Error = Infallible;
type Error = RxError;
}
impl<Uart: Instance> embedded_hal_nb::serial::Read<u8> for Rx<Uart> {
fn read(&mut self) -> nb::Result<u8, Self::Error> {
let uart = unsafe { &(*Uart::ptr()) };
let status_reader = uart.rxstatus().read();
let err = if status_reader.rxovr().bit_is_set() {
Some(RxError::Overrun)
} else if status_reader.rxfrm().bit_is_set() {
Some(RxError::Framing)
} else if status_reader.rxpar().bit_is_set() {
Some(RxError::Parity)
} else {
None
};
if let Some(err) = err {
// The status code is always related to the next bit for the framing
// and parity status bits. We have to read the DATA register
// so that the next status reflects the next DATA word
// For overrun error, we read as well to clear the peripheral
self.read_fifo_unchecked();
return Err(err.into());
}
self.read_fifo().map(|val| (val & 0xff) as u8).map_err(|e| {
if let nb::Error::Other(_) = e {
unreachable!()
@ -895,23 +918,13 @@ impl<Uart: Instance> embedded_io::Read for Rx<Uart> {
if buf.is_empty() {
return Ok(0);
}
let mut read = 0;
loop {
if self.0.rxstatus().read().rdavl().bit_is_set() {
break;
}
}
for byte in buf.iter_mut() {
match <Self as embedded_hal_nb::serial::Read<u8>>::read(self) {
Ok(w) => {
*byte = w;
read += 1;
}
Err(nb::Error::WouldBlock) => break,
}
let w = nb::block!(<Self as embedded_hal_nb::serial::Read<u8>>::read(self))?;
*byte = w;
}
Ok(read)
Ok(buf.len())
}
}
@ -946,7 +959,9 @@ pub fn disable_tx_interrupts(uart: &uart_base::RegisterBlock) {
/// Serial transmitter
///
/// Can be created by using the [Uart::split] or [UartBase::split] API.
pub struct Tx<Uart>(Uart);
pub struct Tx<Uart> {
uart: Uart,
}
impl<Uart: Instance> Tx<Uart> {
/// Retrieve a TX pin without expecting an explicit UART structure
@ -956,12 +971,14 @@ impl<Uart: Instance> Tx<Uart> {
/// Circumvents the HAL safety guarantees.
#[inline(always)]
pub unsafe fn steal() -> Self {
Self(Uart::steal())
Self {
uart: Uart::steal(),
}
}
#[inline(always)]
fn new(uart: Uart) -> Self {
Self(uart)
Self { uart }
}
/// Direct access to the peripheral structure.
@ -970,23 +987,25 @@ impl<Uart: Instance> Tx<Uart> {
///
/// You must ensure that only registers related to the operation of the TX side are used.
#[inline(always)]
pub const unsafe fn uart(&self) -> &Uart {
&self.0
pub unsafe fn uart(&self) -> &Uart {
&self.uart
}
#[inline]
pub fn clear_fifo(&self) {
self.0.fifo_clr().write(|w| w.txfifo().set_bit());
self.uart.fifo_clr().write(|w| w.txfifo().set_bit());
}
#[inline]
pub fn enable(&mut self) {
self.0.enable().modify(|_, w| w.txenable().set_bit());
// Safety: We own the UART structure
enable_tx(unsafe { Uart::reg_block() });
}
#[inline]
pub fn disable(&mut self) {
self.0.enable().modify(|_, w| w.txenable().clear_bit());
// Safety: We own the UART structure
disable_tx(unsafe { Uart::reg_block() });
}
/// Enables the IRQ_TX, IRQ_TX_STATUS and IRQ_TX_EMPTY interrupts.
@ -1018,7 +1037,7 @@ impl<Uart: Instance> Tx<Uart> {
/// value if you use the manual parity mode. See chapter 11.4.1 for more information.
#[inline(always)]
pub fn write_fifo(&self, data: u32) -> nb::Result<(), Infallible> {
if self.0.txstatus().read().wrrdy().bit_is_clear() {
if self.uart.txstatus().read().wrrdy().bit_is_clear() {
return Err(nb::Error::WouldBlock);
}
self.write_fifo_unchecked(data);
@ -1033,7 +1052,7 @@ impl<Uart: Instance> Tx<Uart> {
/// API.
#[inline(always)]
pub fn write_fifo_unchecked(&self, data: u32) {
self.0.data().write(|w| unsafe { w.bits(data) });
self.uart.data().write(|w| unsafe { w.bits(data) });
}
pub fn into_async(self) -> TxAsync<Uart> {
@ -1069,20 +1088,14 @@ impl<Uart: Instance> embedded_io::Write for Tx<Uart> {
if buf.is_empty() {
return Ok(0);
}
loop {
if self.0.txstatus().read().wrrdy().bit_is_set() {
break;
}
}
let mut written = 0;
for byte in buf.iter() {
match <Self as embedded_hal_nb::serial::Write<u8>>::write(self, *byte) {
Ok(_) => written += 1,
Err(nb::Error::WouldBlock) => return Ok(written),
}
nb::block!(<Self as embedded_hal_nb::serial::Write<u8>>::write(
self, *byte
))?;
}
Ok(written)
Ok(buf.len())
}
fn flush(&mut self) -> Result<(), Self::Error> {
@ -1122,7 +1135,7 @@ impl<Uart: Instance> RxWithInterrupt<Uart> {
#[inline(always)]
pub fn uart(&self) -> &Uart {
&self.0 .0
&self.0.uart
}
/// This function is used together with the [Self::on_interrupt_max_size_or_timeout_based]
@ -1200,10 +1213,15 @@ impl<Uart: Instance> RxWithInterrupt<Uart> {
// Timeout, empty the FIFO completely.
if irq_end.irq_rx_to().bit_is_set() {
// While there is data in the FIFO, write it into the reception buffer
while let Ok(byte) = self.0.read_fifo() {
buf[result.bytes_read] = byte as u8;
result.bytes_read += 1;
loop {
// While there is data in the FIFO, write it into the reception buffer
let read_result = self.0.read();
if let Some(byte) = self.read_handler(&mut result.errors, &read_result) {
buf[result.bytes_read] = byte;
result.bytes_read += 1;
} else {
break;
}
}
}
@ -1280,13 +1298,12 @@ impl<Uart: Instance> RxWithInterrupt<Uart> {
if context.rx_idx == context.max_len {
break;
}
// While there is data in the FIFO, write it into the reception buffer
match self.0.read() {
Ok(byte) => {
buf[result.bytes_read] = byte;
result.bytes_read += 1;
}
Err(_) => break,
let read_result = self.0.read();
if let Some(byte) = self.read_handler(&mut result.errors, &read_result) {
buf[context.rx_idx] = byte;
context.rx_idx += 1;
} else {
break;
}
}
self.irq_completion_handler_max_size_timeout(&mut result, context);
@ -1305,6 +1322,29 @@ impl<Uart: Instance> RxWithInterrupt<Uart> {
Ok(result)
}
fn read_handler(
&self,
errors: &mut Option<UartErrors>,
read_res: &nb::Result<u8, RxError>,
) -> Option<u8> {
match read_res {
Ok(byte) => Some(*byte),
Err(nb::Error::WouldBlock) => None,
Err(nb::Error::Other(e)) => {
// Ensure `errors` is Some(IrqUartError), initializing if it's None
let err = errors.get_or_insert(UartErrors::default());
// Now we can safely modify fields inside `err`
match e {
RxError::Overrun => err.overflow = true,
RxError::Framing => err.framing = true,
RxError::Parity => err.parity = true,
}
None
}
}
}
fn check_for_errors(&self, errors: &mut Option<UartErrors>) {
let rx_status = self.uart().rxstatus().read();

View File

@ -1,16 +1,18 @@
//! # Async UART reception functionality for the VA416xx family.
//! # Async UART reception functionality for the VA108xx family.
//!
//! This module provides the [RxAsync] and [RxAsyncOverwriting] struct which both implement the
//! This module provides the [RxAsync] and [RxAsyncSharedConsumer] 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:
//! However, it provides four interrupt handlers:
//!
//! - [on_interrupt_rx]
//! - [on_interrupt_rx_overwriting]
//! - [on_interrupt_uart_a]
//! - [on_interrupt_uart_b]
//! - [on_interrupt_uart_a_overwriting]
//! - [on_interrupt_uart_b_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.
//! [RxAsyncSharedConsumer] 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.
@ -23,10 +25,11 @@ use core::{cell::RefCell, convert::Infallible, future::Future, sync::atomic::Ord
use critical_section::Mutex;
use embassy_sync::waitqueue::AtomicWaker;
use embedded_io::ErrorType;
use heapless::spsc::Consumer;
use portable_atomic::AtomicBool;
use va108xx::uarta as uart_base;
use va108xx as pac;
use super::{Bank, Instance, Rx, UartErrors};
use super::{Instance, Rx, RxError, UartErrors};
static UART_RX_WAKERS: [AtomicWaker; 2] = [const { AtomicWaker::new() }; 2];
static RX_READ_ACTIVE: [AtomicBool; 2] = [const { AtomicBool::new(false) }; 2];
@ -46,7 +49,7 @@ impl RxFuture {
}
impl Future for RxFuture {
type Output = Result<(), Infallible>;
type Output = Result<(), RxError>;
fn poll(
self: core::pin::Pin<&mut Self>,
@ -69,7 +72,7 @@ pub struct AsyncUartErrors {
pub uart_errors: UartErrors,
}
fn on_interrupt_handle_rx_errors(uart: &'static uart_base::RegisterBlock) -> Option<UartErrors> {
fn on_interrupt_handle_rx_errors<Uart: Instance>(uart: &Uart) -> Option<UartErrors> {
let rx_status = uart.rxstatus().read();
if rx_status.rxovr().bit_is_set()
|| rx_status.rxfrm().bit_is_set()
@ -91,65 +94,81 @@ fn on_interrupt_handle_rx_errors(uart: &'static uart_base::RegisterBlock) -> Opt
None
}
fn on_interrupt_rx_common_post_processing(
bank: Bank,
fn on_interrupt_rx_common_post_processing<Uart: Instance>(
uart: &Uart,
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();
RX_HAS_DATA[Uart::IDX as usize].store(true, Ordering::Relaxed);
if RX_READ_ACTIVE[Uart::IDX as usize].load(Ordering::Relaxed) {
UART_RX_WAKERS[Uart::IDX as usize].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);
errors = on_interrupt_handle_rx_errors(uart);
}
// Clear the interrupt status bits
uart_regs.irq_clr().write(|w| unsafe { w.bits(irq_end) });
uart.irq_clr().write(|w| unsafe { w.bits(irq_end) });
errors
}
/// Interrupt handler with overwriting behaviour when the ring buffer is full.
/// Interrupt handler for UART A.
///
/// 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,
pub fn on_interrupt_uart_a_overwriting<const N: usize>(
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)
on_interrupt_rx_async_heapless_queue_overwriting(
unsafe { pac::Uarta::steal() },
prod,
shared_consumer,
)
}
pub fn on_interrupt_rx_async_heapless_queue_overwriting<const N: usize>(
bank: Bank,
/// Interrupt handler for UART B.
///
/// 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_uart_b_overwriting<const N: usize>(
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();
on_interrupt_rx_async_heapless_queue_overwriting(
unsafe { pac::Uartb::steal() },
prod,
shared_consumer,
)
}
pub fn on_interrupt_rx_async_heapless_queue_overwriting<Uart: Instance, const N: usize>(
uart: Uart,
prod: &mut heapless::spsc::Producer<u8, N>,
shared_consumer: &Mutex<RefCell<Option<heapless::spsc::Consumer<'static, u8, N>>>>,
) -> Result<(), AsyncUartErrors> {
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_regs.rxfifoirqtrg().read().bits() as usize;
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_regs.data().read().bits();
let byte = uart.data().read().bits();
if !prod.ready() {
queue_overflow = true;
critical_section::with(|cs| {
@ -164,9 +183,9 @@ pub fn on_interrupt_rx_async_heapless_queue_overwriting<const N: usize>(
// Timeout, empty the FIFO completely.
if irq_end.irq_rx_to().bit_is_set() {
while uart_regs.rxstatus().read().rdavl().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_regs.data().read().bits();
let byte = uart.data().read().bits();
if !prod.ready() {
queue_overflow = true;
critical_section::with(|cs| {
@ -180,7 +199,7 @@ pub fn on_interrupt_rx_async_heapless_queue_overwriting<const N: usize>(
}
let uart_errors =
on_interrupt_rx_common_post_processing(bank, rx_enabled, read_some_data, irq_end.bits());
on_interrupt_rx_common_post_processing(&uart, rx_enabled, read_some_data, irq_end.bits());
if uart_errors.is_some() || queue_overflow {
return Err(AsyncUartErrors {
queue_overflow,
@ -190,21 +209,29 @@ pub fn on_interrupt_rx_async_heapless_queue_overwriting<const N: usize>(
Ok(())
}
/// Interrupt handler for asynchronous RX operations.
/// Interrupt handler for UART A.
///
/// Should be called in the user interrupt handler to enable asynchronous reception.
pub fn on_interrupt_rx<const N: usize>(
bank: Bank,
pub fn on_interrupt_uart_a<const N: usize>(
prod: &mut heapless::spsc::Producer<'_, u8, N>,
) -> Result<(), AsyncUartErrors> {
on_interrupt_rx_async_heapless_queue(bank, prod)
on_interrupt_rx_async_heapless_queue(unsafe { pac::Uarta::steal() }, prod)
}
pub fn on_interrupt_rx_async_heapless_queue<const N: usize>(
bank: Bank,
/// Interrupt handler for UART B.
///
/// Should be called in the user interrupt handler to enable asynchronous reception.
pub fn on_interrupt_uart_b<const N: usize>(
prod: &mut heapless::spsc::Producer<'_, u8, N>,
) -> Result<(), AsyncUartErrors> {
let uart = unsafe { bank.reg_block() };
on_interrupt_rx_async_heapless_queue(unsafe { pac::Uartb::steal() }, prod)
}
pub fn on_interrupt_rx_async_heapless_queue<Uart: Instance, const N: usize>(
uart: Uart,
prod: &mut heapless::spsc::Producer<'_, u8, N>,
) -> Result<(), AsyncUartErrors> {
//let uart = unsafe { Uart::steal() };
let irq_end = uart.irq_end().read();
let enb_status = uart.enable().read();
let rx_enabled = enb_status.rxenable().bit_is_set();
@ -241,7 +268,7 @@ pub fn on_interrupt_rx_async_heapless_queue<const N: usize>(
}
let uart_errors =
on_interrupt_rx_common_post_processing(bank, rx_enabled, read_some_data, irq_end.bits());
on_interrupt_rx_common_post_processing(&uart, rx_enabled, read_some_data, irq_end.bits());
if uart_errors.is_some() || queue_overflow {
return Err(AsyncUartErrors {
queue_overflow,
@ -259,32 +286,24 @@ impl Drop for ActiveReadGuard {
}
}
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>>);
pub struct RxAsync<Uart: Instance, const N: usize> {
rx: Rx<Uart>,
pub queue: heapless::spsc::Consumer<'static, u8, 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();
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].
/// is filled by the interrupt handler.
pub fn new(mut rx: Rx<Uart>, queue: heapless::spsc::Consumer<'static, u8, N>) -> Self {
rx.disable_interrupts();
rx.disable();
@ -294,23 +313,7 @@ impl<Uart: Instance, const N: usize> RxAsync<Uart, N> {
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();
Self { rx, queue }
}
}
@ -318,7 +321,7 @@ 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 {
if self.queue.len() == 0 {
RX_HAS_DATA[Uart::IDX as usize].store(false, Ordering::Relaxed);
}
let _guard = ActiveReadGuard(Uart::IDX as usize);
@ -330,38 +333,33 @@ impl<Uart: Instance, const N: usize> embedded_io_async::Read for RxAsync<Uart, N
}
data_to_read
};
let mut_ref = self.0.as_mut().unwrap();
let fut = RxFuture::new(&mut mut_ref.rx);
let fut = RxFuture::new(&mut self.rx);
// Data is available, so read that data immediately.
let read_data = handle_data_in_queue(&mut mut_ref.queue);
let read_data = handle_data_in_queue(&mut self.queue);
if read_data > 0 {
return Ok(read_data);
}
// Await data.
let _ = fut.await;
Ok(handle_data_in_queue(&mut mut_ref.queue))
Ok(handle_data_in_queue(&mut self.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>>,
);
/// [on_interrupt_uart_a_overwriting] and [on_interrupt_uart_b_overwriting] interrupt handlers.
pub struct RxAsyncSharedConsumer<Uart: Instance, const N: usize> {
rx: Rx<Uart>,
queue: &'static Mutex<RefCell<Option<Consumer<'static, u8, N>>>>,
}
impl<Uart: Instance, const N: usize> ErrorType for RxAsyncOverwriting<Uart, N> {
impl<Uart: Instance, const N: usize> ErrorType for RxAsyncSharedConsumer<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> {
impl<Uart: Instance, const N: usize> RxAsyncSharedConsumer<Uart, N> {
/// Create a new asynchronous receiver.
///
/// The passed shared [heapless::spsc::Consumer] will be used to asynchronously receive data
@ -369,7 +367,7 @@ impl<Uart: Instance, const N: usize> RxAsyncOverwriting<Uart, N> {
/// 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>>>>,
queue: &'static Mutex<RefCell<Option<heapless::spsc::Consumer<'static, u8, N>>>>,
) -> Self {
rx.disable_interrupts();
rx.disable();
@ -379,44 +377,25 @@ impl<Uart: Instance, const N: usize> RxAsyncOverwriting<Uart, N> {
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
Self { rx, queue }
}
}
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> {
impl<Uart: Instance, const N: usize> embedded_io_async::Read for RxAsyncSharedConsumer<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);
let queue = self.queue.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>| {
let mut handle_data_in_queue = || {
critical_section::with(|cs| {
let mut consumer_ref = inner.shared_consumer.borrow(cs).borrow_mut();
let mut consumer_ref = self.queue.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) {
@ -426,15 +405,15 @@ impl<Uart: Instance, const N: usize> embedded_io_async::Read for RxAsyncOverwrit
data_to_read
})
};
let fut = RxFuture::new(&mut self.0.as_mut().unwrap().rx);
let fut = RxFuture::new(&mut self.rx);
// Data is available, so read that data immediately.
let read_data = handle_data_in_queue(self.0.as_mut().unwrap());
let read_data = handle_data_in_queue();
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());
let read_data = handle_data_in_queue();
Ok(read_data)
}
}

View File

@ -3,10 +3,13 @@
//! 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.
//! However, it provides two interrupt handlers:
//!
//! This handler should be called in ALL user interrupt handlers which handle UART TX interrupts
//! for a given UART bank.
//! - [on_interrupt_uart_a_tx]
//! - [on_interrupt_uart_b_tx]
//!
//! Those should be called in ALL user interrupt handlers which handle UART TX interrupts,
//! depending on which UARTs are used.
//!
//! # Example
//!
@ -27,14 +30,21 @@ static TX_CONTEXTS: [Mutex<RefCell<TxContext>>; 2] =
// critical section.
static TX_DONE: [AtomicBool; 2] = [const { AtomicBool::new(false) }; 2];
/// 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;
/// This is a generic interrupt handler to handle asynchronous UART TX operations. The user
/// has to call this once in the interrupt handler responsible for UART A TX interrupts for
/// asynchronous operations to work.
pub fn on_interrupt_uart_a_tx() {
on_interrupt_uart_tx(unsafe { pac::Uarta::steal() });
}
/// This is a generic interrupt handler to handle asynchronous UART TX operations. The user
/// has to call this once in the interrupt handler responsible for UART B TX interrupts for
/// asynchronous operations to work.
pub fn on_interrupt_uart_b_tx() {
on_interrupt_uart_tx(unsafe { pac::Uartb::steal() });
}
fn on_interrupt_uart_tx<Uart: Instance>(uart: Uart) {
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() {
@ -44,7 +54,7 @@ pub fn on_interrupt_tx(bank: Bank) {
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);
let context_ref = TX_CONTEXTS[Uart::IDX as usize].borrow(cs);
*context_ref.borrow()
});
context.tx_overrun = unexpected_overrun;
@ -57,12 +67,12 @@ pub fn on_interrupt_tx(bank: Bank) {
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);
let context_ref = TX_CONTEXTS[Uart::IDX as usize].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();
TX_DONE[Uart::IDX as usize].store(true, core::sync::atomic::Ordering::Relaxed);
UART_TX_WAKERS[Uart::IDX as usize].wake();
return;
}
// Safety: We documented that the user provided slice must outlive the future, so we convert
@ -82,7 +92,7 @@ pub fn on_interrupt_tx(bank: Bank) {
// Write back updated context structure.
critical_section::with(|cs| {
let context_ref = TX_CONTEXTS[idx].borrow(cs);
let context_ref = TX_CONTEXTS[Uart::IDX as usize].borrow(cs);
*context_ref.borrow_mut() = context;
});
}

View File

@ -8,7 +8,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
## [unreleased]
## [v0.5.0] 2025-02-17
## [v0.5.0] 2025-02-14
- Re-generated PAC with `svd2rust` v0.35.0 and added optional `defmt` and `Debug` implementations

View File

@ -8,15 +8,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
## [unreleased]
## [v0.8.1] 2025-03-07
- Bumped allowed `va108xx-hal` dependency to 0.11
- Bumped `bitfield` dependency
## [v0.8.0] 2025-02-17
- Bumped `va108xx-hal` dependency to 0.10
## [v0.7.0] 2025-02-13
- Bumped `va108xx-hal` dependency to 0.9
@ -61,9 +52,3 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Added basic accelerometer example. Board in not populated so it is not complete, but
it provides a starting point
- Added ADC base library and example building on the new max116xx-10bit device driver crate
[unreleased]: https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/compare/vorago-reb1-v0.8.1...HEAD
[v0.8.1]: https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/compare/vorago-reb1-v0.8.0...vorago-reb1-v0.8.1
[v0.8.0]: https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/compare/vorago-reb1-v0.7.0...vorago-reb1-v0.8.0
[v0.7.0]: https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/compare/vorago-reb1-v0.6.0...vorago-reb1-v0.7.0
[v0.6.0]: https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/compare/vorago-reb1-v0.5.0...vorago-reb1-v0.6.0

View File

@ -1,6 +1,6 @@
[package]
name = "vorago-reb1"
version = "0.8.1"
version = "0.7.0"
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
edition = "2021"
description = "Board Support Crate for the Vorago REB1 development board"
@ -15,10 +15,12 @@ cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
cortex-m-rt = "0.7"
embedded-hal = "1"
nb = "1"
bitfield = ">=0.17, <=0.19"
bitfield = ">=0.17, <=0.18"
max116xx-10bit = "0.3"
va108xx-hal = { version = ">=0.10, <=0.11", features = ["rt"] }
[dependencies.va108xx-hal]
version = "0.9"
features = ["rt"]
[features]
rt = ["va108xx-hal/rt"]

View File

@ -5,8 +5,8 @@
#![no_main]
#![no_std]
use cortex_m_rt::entry;
use embedded_hal::delay::DelayNs;
use embedded_hal::spi::{SpiBus, MODE_3};
use embedded_hal::{delay::DelayNs, digital::OutputPin};
use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print};
use va108xx_hal::spi::SpiClkConfig;
@ -41,7 +41,9 @@ fn main() -> ! {
// Need to set the ADC chip select low
let mut adc_cs = pinsa.pa17.into_push_pull_output();
adc_cs.set_high();
adc_cs
.set_high()
.expect("Setting ADC chip select high failed");
let spi_cfg = SpiConfig::default()
.clk_cfg(

View File

@ -43,16 +43,18 @@ fn main() -> ! {
// Configure an edge interrupt on the button and route it to interrupt vector 15
let mut button = Button::new(pinsa.pa11.into_floating_input());
button.configure_edge_interrupt(
edge_irq,
InterruptConfig::new(pac::interrupt::OC15, true, true),
Some(&mut dp.sysconfig),
Some(&mut dp.irqsel),
);
if PRESS_MODE == PressMode::Toggle {
// This filter debounces the switch for edge based interrupts
button.configure_filter_type(FilterType::FilterFourClockCycles, FilterClkSel::Clk1);
set_clk_div_register(&mut dp.sysconfig, FilterClkSel::Clk1, 50_000);
}
button.configure_and_enable_edge_interrupt(
edge_irq,
InterruptConfig::new(pac::interrupt::OC15, true, true),
);
set_up_ms_tick(
InterruptConfig::new(pac::Interrupt::OC0, true, true),

View File

@ -8,6 +8,7 @@
use cortex_m_rt::entry;
use embedded_hal::delay::DelayNs;
use embedded_hal::digital::{OutputPin, StatefulOutputPin};
use panic_halt as _;
use va108xx_hal::{gpio::PinsA, pac, prelude::*, timer::set_up_ms_delay_provider};
use vorago_reb1::leds::Leds;
@ -66,19 +67,22 @@ fn main() -> ! {
let mut led3 = pins.pa6.into_readable_push_pull_output();
let mut delay = set_up_ms_delay_provider(&mut dp.sysconfig, 50.MHz(), dp.tim0);
for _ in 0..10 {
led1.set_low();
led2.set_low();
led3.set_low();
led1.set_low().ok();
led2.set_low().ok();
led3.set_low().ok();
delay.delay_ms(200);
led1.set_high();
led2.set_high();
led3.set_high();
led1.set_high().ok();
led2.set_high().ok();
led3.set_high().ok();
delay.delay_ms(200);
}
loop {
led1.toggle();
led1.toggle().ok();
delay.delay_ms(200);
led2.toggle();
led2.toggle().ok();
delay.delay_ms(200);
// Alternatively use deidscted register.
led3.toggle_with_toggle_reg();
delay.delay_ms(200);
}
}

View File

@ -8,13 +8,13 @@
use core::convert::Infallible;
use cortex_m_rt::entry;
use embedded_hal::digital::OutputPin;
use embedded_hal::spi::{SpiBus, SpiDevice, MODE_0};
use embedded_hal::{delay::DelayNs, spi};
use max116xx_10bit::VoltageRefMode;
use max116xx_10bit::{AveragingConversions, AveragingResults};
use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print};
use va108xx_hal::gpio::Port;
use va108xx_hal::spi::{OptionalHwCs, SpiClkConfig};
use va108xx_hal::timer::CountdownTimer;
use va108xx_hal::{
@ -24,7 +24,7 @@ use va108xx_hal::{
spi::{Spi, SpiBase, SpiConfig},
timer::{default_ms_irq_handler, set_up_ms_tick, DelayMs, InterruptConfig},
};
use va108xx_hal::{port_function_select, FunSel};
use va108xx_hal::{port_function_select, FunSel, PortSel};
use vorago_reb1::max11619::{
max11619_externally_clocked_no_wakeup, max11619_externally_clocked_with_wakeup,
max11619_internally_clocked, EocPin, AN2_CHANNEL, POTENTIOMETER_CHANNEL,
@ -135,14 +135,16 @@ fn main() -> ! {
);
if MUX_MODE == MuxMode::PortB19to17 {
port_function_select(&mut dp.ioconfig, Port::B, 19, FunSel::Sel1).ok();
port_function_select(&mut dp.ioconfig, Port::B, 18, FunSel::Sel2).ok();
port_function_select(&mut dp.ioconfig, Port::B, 17, FunSel::Sel1).ok();
port_function_select(&mut dp.ioconfig, Port::B, 16, FunSel::Sel1).ok();
port_function_select(&mut dp.ioconfig, PortSel::PortB, 19, FunSel::Sel1).ok();
port_function_select(&mut dp.ioconfig, PortSel::PortB, 18, FunSel::Sel2).ok();
port_function_select(&mut dp.ioconfig, PortSel::PortB, 17, FunSel::Sel1).ok();
port_function_select(&mut dp.ioconfig, PortSel::PortB, 16, FunSel::Sel1).ok();
}
// Set the accelerometer chip select low in case the board slot is populated
let mut accel_cs = pinsa.pa16.into_push_pull_output();
accel_cs.set_high();
accel_cs
.set_high()
.expect("Setting accelerometer chip select high failed");
let spi = Spi::new(
&mut dp.sysconfig,

View File

@ -4,9 +4,10 @@
//!
//! - [Button Blinky with IRQs](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/vorago-reb1/examples/blinky-button-irq.rs)
//! - [Button Blinky with IRQs and RTIC](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/vorago-reb1/examples/blinky-button-rtic.rs)
use embedded_hal::digital::InputPin;
use va108xx_hal::{
gpio::{FilterClkSel, FilterType, InputFloating, InterruptEdge, InterruptLevel, Pin, PA11},
InterruptConfig,
pac, InterruptConfig,
};
#[derive(Debug)]
@ -19,32 +20,36 @@ impl Button {
#[inline]
pub fn pressed(&mut self) -> bool {
self.0.is_low()
self.0.is_low().ok().unwrap()
}
#[inline]
pub fn released(&mut self) -> bool {
self.0.is_high()
self.0.is_high().ok().unwrap()
}
/// Configures an IRQ on edge.
pub fn configure_and_enable_edge_interrupt(
pub fn configure_edge_interrupt(
&mut self,
edge_type: InterruptEdge,
irq_cfg: InterruptConfig,
syscfg: Option<&mut pac::Sysconfig>,
irqsel: Option<&mut pac::Irqsel>,
) {
self.0.configure_edge_interrupt(edge_type);
self.0.enable_interrupt(irq_cfg);
self.0
.configure_edge_interrupt(edge_type, irq_cfg, syscfg, irqsel);
}
/// Configures an IRQ on level.
pub fn configure_and_enable_level_interrupt(
pub fn configure_level_interrupt(
&mut self,
level: InterruptLevel,
irq_cfg: InterruptConfig,
syscfg: Option<&mut pac::Sysconfig>,
irqsel: Option<&mut pac::Irqsel>,
) {
self.0.configure_level_interrupt(level);
self.0.enable_interrupt(irq_cfg);
self.0
.configure_level_interrupt(level, irq_cfg, syscfg, irqsel);
}
/// Configures a filter on the button. This can be useful for debouncing the switch.

View File

@ -5,6 +5,7 @@
//! - [LED example](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/vorago-reb1/examples/blinky-leds.rs)
//! - [Button Blinky using IRQs](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/vorago-reb1/examples/blinky-button-irq.rs)
//! - [Button Blinky using IRQs and RTIC](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/vorago-reb1/examples/blinky-button-rtic.rs)
use embedded_hal::digital::OutputPin;
use va108xx_hal::{
gpio::dynpin::DynPin,
gpio::pin::{Pin, PushPullOutput, PA10, PA6, PA7},
@ -84,6 +85,6 @@ impl Led {
/// Toggles the LED
#[inline]
pub fn toggle(&mut self) {
self.0.toggle().ok();
self.0.toggle_with_toggle_reg().ok();
}
}