25 Commits

Author SHA1 Message Date
be6cd2d609 complicated 2025-04-09 16:15:36 +02:00
5e9526a2d1 this should be sufficient 2025-04-08 19:01:44 +02:00
125619e485 continue CAN 2025-04-08 16:22:02 +02:00
a6c9a6fcdc continue CAN support 2025-04-08 11:08:25 +02:00
e8e7ea9b1c some more const improvements 2025-04-07 14:36:17 +02:00
7d6f69d808 clean up crate structure a bit 2025-04-07 14:32:47 +02:00
0baf8d7b32 need to get familiar with CAN 2025-04-04 12:33:12 +02:00
512de17719 use bitbybit 2025-04-04 12:22:09 +02:00
1a5670b362 small README update 2025-03-11 16:42:16 +01:00
2b8a9dbce4 Merge pull request 'UART embedded-io fixes' (#66) from uart-embedded-io-fixes into main
Reviewed-on: #66
2025-03-10 17:39:54 +01:00
6528dd855f UART embedded-io fixes 2025-03-10 17:37:12 +01:00
4455cb0343 Merge pull request 'bump dependencies' (#65) from bump-dependencies into main
Reviewed-on: #65
2025-03-07 17:25:55 +01:00
2706dbf461 bump dependencies 2025-03-07 17:25:34 +01:00
c3e16b4278 prepare embassy release 2025-03-07 17:22:19 +01:00
2088d7dc8a prepare peb1 release 2025-03-07 17:19:42 +01:00
a44ba7b8a5 Merge pull request 'UART error handling update' (#64) from uart-error-handling-update into main
Reviewed-on: #64
2025-03-07 17:14:02 +01:00
a3c6366e98 UART error handling update 2025-03-07 17:10:42 +01:00
016c421cb8 small docs fix 2025-02-18 19:10:05 +01:00
0e99e04dd1 use released packages 2025-02-18 19:07:32 +01:00
aea3d835f0 Merge pull request 'prepare embassy release' (#63) from prep-embassy-release into main
Reviewed-on: #63
2025-02-18 18:33:02 +01:00
5f39b916fa prepare embassy release 2025-02-18 18:26:38 +01:00
6d8a114f49 Merge pull request 'prepare HAL patch and embassy release' (#62) from prep-hal-patch-embassy-release into main
Reviewed-on: #62
2025-02-18 18:24:09 +01:00
969f0f4ca5 prepare HAL patch and embassy release 2025-02-18 18:20:11 +01:00
b6971ab7eb Merge pull request 'bump all dependencies and prepare BSP and embassy release' (#61) from prep-bsp-embassy-releases into main
Reviewed-on: #61
2025-02-18 16:56:44 +01:00
41b215e326 bump all dependencies and prepare BSP and embassy release 2025-02-18 16:55:08 +01:00
37 changed files with 1537 additions and 464 deletions

View File

@ -14,7 +14,7 @@ This workspace contains the following crates:
PAC crate containing basic low-level register definition
- The [`va416xx-hal`](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/va416xx-hal)
HAL crate containing higher-level abstractions on top of the PAC register crate.
- The [`va416xx-embassy`](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/va108xx-embassy)
- The [`va416xx-embassy`](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/va416xx-embassy)
crate containing support for running the embassy-rs RTOS.
- The [`vorago-peb1`](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/vorago-peb1)
BSP crate containing support for the PEB1 development board.
@ -155,3 +155,13 @@ 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 0x1fff8000. 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

@ -7,14 +7,14 @@ edition = "2021"
cortex-m = "0.7"
cortex-m-rt = "0.7"
embedded-hal = "1"
panic-rtt-target = { version = "0.1.3" }
panic-halt = { version = "0.2" }
rtt-target = { version = "0.5" }
panic-rtt-target = { version = "0.2" }
panic-halt = { version = "1" }
rtt-target = { version = "0.6" }
crc = "3"
static_assertions = "1"
[dependencies.va416xx-hal]
path = "../va416xx-hal"
version = "0.5"
features = ["va41630"]
[features]

View File

@ -12,9 +12,9 @@ embedded-io = "0.6"
embedded-hal-async = "1"
embedded-io-async = "0.6"
rtt-target = { version = "0.5" }
rtt-target = { version = "0.6" }
heapless = "0.8"
panic-rtt-target = { version = "0.1" }
panic-rtt-target = { version = "0.2" }
static_cell = "2"
critical-section = "1"
once_cell = { version = "1", default-features = false, features = ["critical-section"] }
@ -28,11 +28,8 @@ embassy-executor = { version = "0.7", features = [
"executor-interrupt"
]}
va416xx-embassy = { path = "../../va416xx-embassy", default-features = false }
[dependencies.va416xx-hal]
path = "../../va416xx-hal"
features = ["va41630"]
va416xx-hal = { version = "0.5" }
va416xx-embassy = { version = "0.1", default-features = false }
[features]
default = ["ticks-hz-1_000", "va416xx-embassy/irq-tim14-tim15"]

View File

@ -132,7 +132,7 @@ fn UART0_RX() {
RX.lock(|static_rx| {
let mut rx_borrow = static_rx.borrow_mut();
let rx_mut_ref = rx_borrow.as_mut().unwrap();
let result = rx_mut_ref.irq_handler(&mut buf);
let result = rx_mut_ref.on_interrupt(&mut buf);
read_len = result.bytes_read;
if result.errors.is_some() {
errors = result.errors;

View File

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

View File

@ -7,17 +7,16 @@ edition = "2021"
cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
cortex-m-rt = "0.7"
critical-section = "1"
panic-rtt-target = { version = "0.1.3" }
rtt-target = { version = "0.5" }
panic-rtt-target = { version = "0.2" }
rtt-target = { version = "0.6" }
embedded-hal = "1"
embedded-hal-nb = "1"
nb = "1"
embedded-io = "0.6"
panic-halt = "0.2"
panic-halt = "1"
accelerometer = "0.12"
[dependencies.va416xx-hal]
path = "../../va416xx-hal"
va416xx-hal = { version = "0.5", features = ["va41630"] }
[dependencies.vorago-peb1]
path = "../../vorago-peb1"

View File

@ -9,44 +9,20 @@ cortex-m-rt = "0.7"
embedded-hal = "1"
embedded-hal-nb = "1"
embedded-io = "0.6"
panic-rtt-target = { version = "0.1.3" }
rtt-target = { version = "0.5" }
rtt-log = "0.3"
panic-rtt-target = { version = "0.2" }
rtt-target = { version = "0.6" }
rtt-log = "0.5"
log = "0.4"
crc = "3"
rtic-sync = "1"
static_cell = "2"
satrs = { version = "0.3.0-alpha.0", default-features = false }
ringbuf = { version = "0.4", default-features = false }
once_cell = { version = "1", default-features = false, features = ["critical-section"] }
spacepackets = { version = "0.13", default-features = false }
cobs = { version = "0.3", default-features = false }
[dependencies.satrs]
version = "0.2"
default-features = false
va416xx-hal = { version = "0.5", features = ["va41630"] }
[dependencies.ringbuf]
version = "0.4"
default-features = false
[dependencies.once_cell]
version = "1"
default-features = false
features = ["critical-section"]
[dependencies.spacepackets]
version = "0.11"
default-features = false
[dependencies.cobs]
git = "https://github.com/robamu/cobs.rs.git"
branch = "all_features"
default-features = false
[dependencies.va416xx-hal]
path = "../va416xx-hal"
features = ["va41630"]
[dependencies.rtic]
version = "2"
features = ["thumbv7-backend"]
[dependencies.rtic-monotonics]
version = "2"
features = ["cortex-m-systick"]
rtic = { version = "2", features = ["thumbv7-backend"] }
rtic-monotonics = { version = "2", features = ["cortex-m-systick"] }

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"
va416xx-hal = { path = "../../va416xx-hal", features = ["va41630"] }
va416xx-hal = { path = "0.4", features = ["va41630"] }
[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"
va416xx-hal = { path = "../../va416xx-hal", features = ["va41630"] }
va416xx-hal = { path = "0.4", features = ["va41630"] }
[profile.dev]
codegen-units = 1

View File

@ -256,7 +256,7 @@ mod app {
match cx
.local
.uart_rx
.irq_handler_max_size_or_timeout_based(cx.local.rx_context, cx.local.rx_buf)
.on_interrupt_max_size_or_timeout_based(cx.local.rx_context, cx.local.rx_buf)
{
Ok(result) => {
if RX_DEBUGGING {

2
scripts/unittest.sh Executable file
View File

@ -0,0 +1,2 @@
#!/bin/bash
cargo +stable test --target $(rustc -vV | grep host | cut -d ' ' -f2) -p va416xx-hal --features alloc

View File

@ -8,6 +8,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
## [unreleased]
## [v0.1.0] 2025-02-13
## [v0.1.1] 2025-03-07
- Bumped allowed HAL dependency to v0.5
## [v0.1.0] 2025-02-18
Initial release
[unreleased]: https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/compare/va416xx-embassy-v0.1.1...HEAD
[v0.1.1]: https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/compare/va416xx-embassy-v0.1.0...va416xx-embassy-v0.1.1
[v0.1.0]: https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/tag/va416xx-embassy-v0.1.0

View File

@ -1,6 +1,6 @@
[package]
name = "va416xx-embassy"
version = "0.1.0"
version = "0.1.1"
edition = "2021"
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
description = "Embassy-rs support for the Vorago VA416xx family of microcontrollers"
@ -21,17 +21,20 @@ portable-atomic = "1"
once_cell = { version = "1", default-features = false, features = ["critical-section"] }
va416xx-hal = { version = "0.4", path = "../va416xx-hal" }
va416xx-hal = { version = ">=0.4, <=0.5" }
[features]
default = ["irq-tim14-tim15"]
irqs-in-lib = []
# This determines the reserved interrupt functions for the embassy time drivers. Only one
# is allowed to be selected!
irq-tim14-tim15 = ["irqs-in-lib"]
irq-tim13-tim14 = ["irqs-in-lib"]
irq-tim14-tim15 = ["_irqs-in-lib"]
irq-tim13-tim14 = ["_irqs-in-lib"]
# These TIMs are clocked slower!
irq-tim22-tim23 = ["irqs-in-lib"]
irq-tim22-tim23 = ["_irqs-in-lib"]
# Private feature.
_irqs-in-lib = []
[package.metadata.docs.rs]
rustdoc-args = ["--generate-link-to-definition"]

View File

@ -136,13 +136,13 @@ pub unsafe fn init<
alarm: AlarmTim,
clocks: &Clocks,
) {
#[cfg(feature = "irqs-in-lib")]
#[cfg(feature = "_irqs-in-lib")]
assert_eq!(
TimekeeperTim::ID,
TIMEKEEPER_IRQ as u8 - TIM_IRQ_OFFSET as u8,
"Timekeeper TIM and IRQ missmatch"
);
#[cfg(feature = "irqs-in-lib")]
#[cfg(feature = "_irqs-in-lib")]
assert_eq!(
AlarmTim::ID,
ALARM_IRQ as u8 - TIM_IRQ_OFFSET as u8,

View File

@ -8,6 +8,27 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
# [unreleased]
# [v0.5.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.5.0] 2025-03-07
- 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.4.1] 2025-02-18
- Chip selection is not enforced anymore, but advised through documentation. This makes using
the HAL in libraries a lot easier.
# [v0.4.0] 2025-02-18
## Changed
@ -96,3 +117,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
# [v0.1.0] 2024-07-01
- Initial release with basic HAL drivers
[unreleased]: https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/compare/va416xx-hal-v0.5.0...HEAD
[v0.5.1]: https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/compare/va416xx-hal-v0.5.0...va416xx-hal-v0.5.1
[v0.5.0]: https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/compare/va416xx-hal-v0.4.1...va416xx-hal-v0.5.0
[v0.4.1]: https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/compare/va416xx-hal-v0.4.0...va416xx-hal-v0.4.1
[v0.4.0]: https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/compare/va416xx-hal-v0.3.0...va416xx-hal-v0.4.0
[v0.3.0]: https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/compare/va416xx-hal-v0.2.0...va108xx-hal-v0.3.0
[v0.2.0]: https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/compare/va416xx-hal-v0.1.0...va108xx-hal-v0.2.0
[v0.1.0]: https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/tag/va416xx-hal-v0.1.0

View File

@ -1,6 +1,6 @@
[package]
name = "va416xx-hal"
version = "0.4.0"
version = "0.5.1"
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
edition = "2021"
description = "HAL for the Vorago VA416xx family of MCUs"
@ -15,17 +15,21 @@ cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
critical-section = "1"
nb = "1"
paste = "1"
libm = "0.2"
embedded-hal-nb = "1"
embedded-hal-async = "1"
embedded-hal = "1"
embedded-io = "0.6"
embedded-io-async = "0.6"
embedded-can = "0.4"
num_enum = { version = "0.7", default-features = false }
typenum = "1"
bitflags = "2"
bitfield = "0.17"
bitbybit = "1.3"
arbitrary-int = "1.3"
derive-mmio = "0.4"
fugit = "0.3"
delegate = "0.12"
delegate = ">=0.12, <=0.13"
heapless = "0.8"
void = { version = "1", default-features = false }
thiserror = { version = "2", default-features = false }
@ -33,11 +37,12 @@ portable-atomic = "1"
embassy-sync = "0.6"
va416xx = { version = "0.4", features = ["critical-section"], default-features = false }
defmt = { version = "0.3", optional = true }
defmt = { version = "1", optional = true }
[features]
default = ["rt", "revb"]
rt = ["va416xx/rt"]
alloc = []
defmt = ["dep:defmt", "fugit/defmt", "embedded-hal/defmt-03"]
va41630 = ["device-selected"]

View File

@ -11,14 +11,17 @@ raw PAC. This crate also implements traits specified by the
[embedded-hal](https://github.com/rust-embedded/embedded-hal) project, making it compatible with
various drivers in the embedded rust ecosystem.
You have to enable one of the following device features to use this crate depending on
which chip you are using:
It is generally advised to enable ONE of the following device features to use this crate
depending on which chip you are using:
- `va41630`
- `va41629`
- `va41628`
- `va41620`
If no chip is specified, only access to APIs which are common for all families or
which are not disabled for specific families is granted.
## Building
Building an application requires the `thumbv7em-none-eabihf` cross-compiler toolchain.

View File

@ -287,7 +287,7 @@ impl Adc<ChannelTagEnabled> {
impl<TagEnabled> Adc<TagEnabled> {
fn generic_new(syscfg: &mut pac::Sysconfig, adc: pac::Adc, _clocks: &Clocks) -> Self {
syscfg.enable_peripheral_clock(crate::clock::PeripheralSelect::Adc);
syscfg.enable_peripheral_clock(crate::PeripheralSelect::Adc);
adc.ctrl().write(|w| unsafe { w.bits(0) });
let adc = Self {
adc,

View File

@ -0,0 +1,117 @@
#[derive(Debug, thiserror::Error)]
#[error("invalid data size error {0}")]
pub struct InvalidDataSizeError(usize);
pub struct CanFrameNormal {
id: embedded_can::Id,
size: usize,
data: [u8; 8],
}
impl CanFrameNormal {
pub fn new(id: embedded_can::Id, data: &[u8]) -> Self {
let size = data.len();
let mut data_array = [0; 8];
data_array[..size].copy_from_slice(data);
Self {
id,
size,
data: data_array,
}
}
pub fn id(&self) -> embedded_can::Id {
self.id
}
pub fn data(&self) -> &[u8] {
&self.data[..self.size]
}
pub fn dlc(&self) -> usize {
self.size
}
}
pub struct CanFrameRtr {
id: embedded_can::Id,
dlc: usize,
}
impl CanFrameRtr {
pub fn new(id: embedded_can::Id, dlc: usize) -> Self {
Self { id, dlc }
}
pub fn id(&self) -> embedded_can::Id {
self.id
}
pub fn dlc(&self) -> usize {
self.dlc
}
}
pub enum CanFrame {
Normal(CanFrameNormal),
Rtr(CanFrameRtr),
}
impl From<CanFrameNormal> for CanFrame {
fn from(value: CanFrameNormal) -> Self {
Self::Normal(value)
}
}
impl From<CanFrameRtr> for CanFrame {
fn from(value: CanFrameRtr) -> Self {
Self::Rtr(value)
}
}
impl embedded_can::Frame for CanFrame {
fn new(id: impl Into<embedded_can::Id>, data: &[u8]) -> Option<Self> {
let id: embedded_can::Id = id.into();
Some(Self::Normal(CanFrameNormal::new(id, data)))
}
fn new_remote(id: impl Into<embedded_can::Id>, dlc: usize) -> Option<Self> {
let id: embedded_can::Id = id.into();
Some(Self::Rtr(CanFrameRtr::new(id, dlc)))
}
fn is_extended(&self) -> bool {
match self.id() {
embedded_can::Id::Extended(_) => true,
embedded_can::Id::Standard(_) => false,
}
}
fn is_remote_frame(&self) -> bool {
match self {
CanFrame::Normal(_) => false,
CanFrame::Rtr(_) => true,
}
}
fn id(&self) -> embedded_can::Id {
match self {
CanFrame::Normal(can_frame_normal) => can_frame_normal.id(),
CanFrame::Rtr(can_frame_rtr) => can_frame_rtr.id(),
}
}
fn dlc(&self) -> usize {
match self {
CanFrame::Normal(can_frame_normal) => can_frame_normal.dlc(),
CanFrame::Rtr(can_frame_rtr) => can_frame_rtr.dlc(),
}
}
fn data(&self) -> &[u8] {
match self {
CanFrame::Normal(can_frame_normal) => can_frame_normal.data(),
CanFrame::Rtr(_) => &[],
}
}
}

606
va416xx-hal/src/can/mod.rs Normal file
View File

@ -0,0 +1,606 @@
//! CAN driver.
//!
//! The VA416xx CAN module is based on the CP3UB26 module.
use arbitrary_int::{u11, u15, u2, u3, u4, u7, Number};
use embedded_can::Frame;
use regs::{
BaseId, BufStatusAndControl, Control, DataDirection, ExtendedId, MmioCan, TimingConfig,
};
use crate::{clock::Clocks, enable_peripheral_clock, time::Hertz, PeripheralSelect};
use libm::roundf;
pub mod frame;
pub mod regs;
pub use frame::*;
pub const PRESCALER_MIN: u8 = 2;
pub const PRESCALER_MAX: u8 = 128;
/// 1 is the minimum value, but not recommended by Vorago.
pub const TSEG1_MIN: u8 = 1;
pub const TSEG1_MAX: u8 = 16;
pub const TSEG2_MAX: u8 = 8;
/// In addition, SJW may not be larger than TSEG2.
pub const SJW_MAX: u8 = 4;
pub const MIN_SAMPLE_POINT: f32 = 0.5;
pub const MAX_BITRATE_DEVIATION: f32 = 0.005;
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum CanId {
Can0 = 0,
Can1 = 1,
}
impl CanId {
/// Steal the register block for the CAN ID.
///
/// # Safety
///
/// See safety of the [regs::Can::new_mmio_fixed_0].
pub unsafe fn steal_regs(&self) -> regs::MmioCan<'static> {
match self {
CanId::Can0 => unsafe { regs::Can::new_mmio_fixed_0() },
CanId::Can1 => unsafe { regs::Can::new_mmio_fixed_1() },
}
}
}
/// Sample point between 0 and 1.0 for the given time segments.
pub const fn calculate_sample_point(tseg1: u8, tseg2: u8) -> f32 {
let tseg1_val = tseg1 as f32;
(tseg1_val + 1.0) / (1.0 + tseg1_val + tseg2 as f32)
}
/// Calculate all viable clock configurations for the given input clock, the target bitrate and
/// for a sample point between 0.5 and 1.0.
///
/// There are various recommendations for the sample point when using the CAN bus. The value
/// depends on different parameters like the bus length and propagation time, as well as
/// the information processing time of the nodes. It should always be at least 50 %.
/// In doubt, select a value like 0.75.
///
/// - The [Python CAN library](https://python-can.readthedocs.io/en/stable/bit_timing.html)
/// assumes a default value of 69 % as the sample point if none is specified.
/// - CiA-301 recommends 87.5 %
/// - For simpler setups like laboratory setups, smaller values should work as well.
///
/// A clock configuration is consideres viable when
///
/// - The sample point deviation is less than 5 %.
/// - The bitrate error is less than +-0.5 %.
///
/// SJW will be set to either TSEG2 or 4, whichever is smaller.
#[cfg(feature = "alloc")]
pub fn calculate_all_viable_clock_configs(
apb1_clock: Hertz,
bitrate: Hertz,
sample_point: f32,
) -> Result<alloc::vec::Vec<ClockConfig>, InvalidSamplePointError> {
if sample_point < 0.5 || sample_point > 1.0 {
return Err(InvalidSamplePointError { sample_point });
}
let mut configs = alloc::vec::Vec::new();
for prescaler in PRESCALER_MIN..PRESCALER_MAX {
let nom_bit_time = apb1_clock / (bitrate * prescaler as u32);
// This is taken from the Python CAN library. NBT should not be too small.
if nom_bit_time < 8 {
break;
}
let actual_bitrate = apb1_clock / (prescaler as u32 * nom_bit_time);
let bitrate_deviation = ((actual_bitrate.raw() as i32 - bitrate.raw() as i32).abs() as f32)
/ bitrate.raw() as f32;
if bitrate_deviation > 0.05 {
continue;
}
let tseg1 = roundf(sample_point * nom_bit_time as f32) as u32 - 1;
if tseg1 > TSEG1_MAX as u32 || tseg1 < TSEG1_MIN as u32 {
continue;
}
// limit tseg1, so tseg2 is at least 1 TQ
let tseg1 = core::cmp::min(tseg1, nom_bit_time - 2) as u8;
let tseg2 = nom_bit_time - tseg1 as u32 - 1;
if tseg2 > TSEG2_MAX as u32 {
continue;
}
let tseg2 = tseg2 as u8;
let sjw = core::cmp::min(tseg2, 4) as u8;
// Use percent to have a higher resolution for the sample point deviation.
let sample_point_actual = roundf(calculate_sample_point(tseg1, tseg2) * 100.0) as u32;
let sample_point = roundf(sample_point * 100.0) as u32;
let deviation = (sample_point_actual as i32 - sample_point as i32).abs();
if deviation > 5 {
continue;
}
configs.push(ClockConfig {
prescaler,
tseg1,
tseg2,
sjw,
});
}
Ok(configs)
}
pub trait Instance {
const ID: CanId;
const PERIPH_SEL: PeripheralSelect;
}
impl Instance for va416xx::Can0 {
const ID: CanId = CanId::Can0;
const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Can0;
}
impl Instance for va416xx::Can1 {
const ID: CanId = CanId::Can1;
const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Can1;
}
#[derive(Debug, Clone, Copy)]
pub struct ClockConfig {
prescaler: u8,
tseg1: u8,
tseg2: u8,
sjw: u8,
}
#[derive(Debug, thiserror::Error)]
#[error("invalid buffer index {0}")]
pub struct InvalidBufferIndexError(usize);
#[derive(Debug, thiserror::Error)]
#[error("sjw must be less than or equal to the smaller tseg value")]
pub struct InvalidSjwError(u8);
#[derive(Debug, thiserror::Error)]
#[error("invalid sample point {sample_point}")]
pub struct InvalidSamplePointError {
/// Sample point, should be larger than 0.5 (50 %) but was not.
sample_point: f32,
}
#[derive(Debug, thiserror::Error)]
pub enum ClockConfigError {
#[error("invalid sjw: {0}")]
InvalidSjw(#[from] InvalidSjwError),
#[error("TSEG is zero which is not allowed")]
TsegIsZero,
#[error("TSEG1 is larger than 16")]
InvalidTseg1,
#[error("TSEG1 is larger than 8")]
InvalidTseg2,
#[error("invalid sample point: {0}")]
InvalidSamplePoint(#[from] InvalidSamplePointError),
#[error("bitrate is zero")]
BitrateIsZero,
#[error("bitrate error larger than +-0.5 %")]
BitrateErrorTooLarge,
#[error("maximum or minimum allowed prescaler is not sufficient for target bitrate clock")]
CanNotFindPrescaler,
}
impl ClockConfig {
/// New clock configuration from the raw configuration values.
///
/// The values specified here are not the register values, but the actual numerical values
/// relevant for calculations.
///
/// The values have the following requirements:
///
/// - Prescaler must be between 2 and 128.
/// - TSEG1 must be smaller than 16 and should be larger than 1.
/// - TSEG2 must be smaller than 8 and small enough so that the calculated sample point
/// is larger than 0.5 (50 %).
/// - SJW (Synchronization Jump Width) must be smaller than the smaller of the time segment
/// configuration values and smaller than 4.
pub fn new(prescaler: u8, tseg1: u8, tseg2: u8, sjw: u8) -> Result<Self, ClockConfigError> {
if !(PRESCALER_MIN..=PRESCALER_MAX).contains(&prescaler.value()) {
return Err(ClockConfigError::CanNotFindPrescaler);
}
if tseg1 == 0 || tseg2 == 0 {
return Err(ClockConfigError::TsegIsZero);
}
if tseg1 > TSEG1_MAX {
return Err(ClockConfigError::InvalidTseg1);
}
if tseg2 > TSEG2_MAX {
return Err(ClockConfigError::InvalidTseg2);
}
let smaller_tseg = core::cmp::min(tseg1.value(), tseg2.value());
if sjw.value() > smaller_tseg || sjw > SJW_MAX {
return Err(InvalidSjwError(sjw).into());
}
let sample_point = calculate_sample_point(tseg1, tseg2);
if sample_point < MIN_SAMPLE_POINT {
return Err(InvalidSamplePointError { sample_point }.into());
}
Ok(Self {
prescaler,
tseg1,
tseg2,
sjw,
})
}
/// Calculate the clock configuration for the given input clock, the target bitrate and for a
/// set of timing parameters.
///
/// This function basically calculates the necessary prescaler to achieve the given timing
/// parameters. It also performs sanity and validity checks for the calculated prescaler:
/// The bitrate error for the given prescaler needs to be smaller than 0.5 %.
pub fn from_bitrate_and_segments(
clocks: &Clocks,
bitrate: Hertz,
tseg1: u8,
tseg2: u8,
sjw: u8,
) -> Result<ClockConfig, ClockConfigError> {
if bitrate.raw() == 0 {
return Err(ClockConfigError::BitrateIsZero);
}
let prescaler = roundf(
clocks.apb1().raw() as f32
/ (bitrate.raw() as f32 * (1.0 + tseg1.as_u32() as f32 + tseg2.as_u32() as f32)),
) as u32;
if !(PRESCALER_MIN as u32..=PRESCALER_MAX as u32).contains(&prescaler) {
return Err(ClockConfigError::CanNotFindPrescaler);
}
let actual_bitrate = clocks.apb1() / (prescaler * (1 + tseg1.as_u32() + tseg2.as_u32()));
let bitrate_deviation = ((actual_bitrate.raw() as i32 - bitrate.raw() as i32).abs() as f32)
/ bitrate.raw() as f32;
if bitrate_deviation > MAX_BITRATE_DEVIATION {
return Err(ClockConfigError::BitrateErrorTooLarge);
}
// The subtractions are fine because we made checks to avoid underflows.
Self::new(prescaler as u8, tseg1, tseg2, sjw)
}
pub fn sjw_reg_value(&self) -> u2 {
u2::new(self.sjw.value() - 1)
}
pub fn tseg1_reg_value(&self) -> u4 {
u4::new(self.tseg1.value() - 1)
}
pub fn tseg2_reg_value(&self) -> u3 {
u3::new(self.tseg2.value() - 1)
}
pub fn prescaler_reg_value(&self) -> u7 {
u7::new(self.prescaler.value() - 2)
}
}
pub struct Can {
regs: regs::MmioCan<'static>,
id: CanId,
}
impl Can {
pub fn new<CanI: Instance>(_can: CanI, clk_config: ClockConfig) -> Self {
enable_peripheral_clock(CanI::PERIPH_SEL);
let id = CanI::ID;
let mut regs = if id == CanId::Can0 {
unsafe { regs::Can::new_mmio_fixed_0() }
} else {
unsafe { regs::Can::new_mmio_fixed_1() }
};
// Disable the CAN bus before configuring it.
regs.write_control(Control::new_with_raw_value(0));
for i in 0..15 {
regs.msg_buf_block_mut(i).reset();
}
regs.write_timing(
TimingConfig::builder()
.with_tseg2(clk_config.tseg2_reg_value())
.with_tseg1(clk_config.tseg1_reg_value())
.with_sync_jump_width(clk_config.sjw_reg_value())
.with_prescaler(clk_config.prescaler_reg_value())
.build(),
);
Self { regs, id }
}
/// This configures the global mask so that acceptance is only determined by an exact match
/// with the ID in the receive message buffers. This is the default reset configuration for
/// the global mask as well.
pub fn set_global_mask_for_exact_id_match(&mut self) {
self.regs.write_gmskx(ExtendedId::new_with_raw_value(0));
self.regs.write_gmskb(BaseId::new_with_raw_value(0));
}
/// Similar to [Self::set_global_mask_for_exact_id_match] but masks the XRTR and RTR/SRR bits.
///
/// This is useful for when transmitting remote frames with the RTR bit set. The hardware
/// will automatically go into the [regs::BufferState::RxReady] state after the transmission,
/// but the XRTR and RTR/SRR bits need to be masked for the response frame to be accepted
/// on that buffer.
pub fn set_global_mask_for_exact_id_match_with_rtr_masked(&mut self) {
self.regs.write_gmskx(
ExtendedId::builder()
.with_mask_14_0(u15::new(0))
.with_xrtr(true)
.build(),
);
self.regs.write_gmskb(
BaseId::builder()
.with_mask_28_18(u11::new(0))
.with_rtr_or_srr(true)
.with_ide(false)
.with_mask_17_15(u3::new(0))
.build(),
);
}
/// This configures the base mask for buffer 14 so that acceptance is only determined by an
/// exact match with the ID in the receive message buffers. This is the default reset
/// configuration for the global mask as well.
pub fn set_base_mask_for_exact_id_match(&mut self) {
self.regs.write_bmskx(ExtendedId::new_with_raw_value(0));
self.regs.write_bmskb(BaseId::new_with_raw_value(0));
}
/// This configures the base mask so that all CAN frames which are not handled by any other
/// buffers are accepted by the base buffer 14.
pub fn set_base_mask_for_all_match(&mut self) {
self.regs
.write_bmskx(ExtendedId::new_with_raw_value(0xffff));
self.regs.write_bmskb(BaseId::new_with_raw_value(0xffff));
}
#[inline]
pub fn regs(&mut self) -> &mut MmioCan<'static> {
&mut self.regs
}
#[inline]
pub fn id(&self) -> CanId {
self.id
}
#[inline]
pub fn write_ctrl_reg(&mut self, ctrl: Control) {
self.regs.write_control(ctrl);
}
#[inline]
pub fn enable_bufflock(&mut self) {
self.regs.modify_control(|mut ctrl| {
ctrl.set_bufflock(true);
ctrl
});
}
#[inline]
pub fn enable(&mut self) {
self.regs.modify_control(|mut ctrl| {
ctrl.set_enable(true);
ctrl
});
}
}
#[derive(Debug)]
pub enum ChannelState {
Idle,
Receiving,
Transmitting,
AwaitingRtrReply,
}
pub struct CanChannel {
can_id: CanId,
idx: usize,
regs: regs::MmioCanMsgBuf<'static>,
mode: ChannelState,
}
impl core::fmt::Debug for CanChannel {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("CanChannel")
.field("can_id", &self.can_id)
.field("idx", &self.idx)
.field("mode", &self.mode)
.finish()
}
}
impl CanChannel {
pub fn configure_for_reception_with_standard_id(
&mut self,
standard_id: embedded_can::StandardId,
set_rtr: bool,
) -> Result<(), InvalidBufferIndexError> {
let mut id1_reg = standard_id.as_raw() << 5;
if set_rtr {
id1_reg |= 1 << 4;
}
self.regs
.write_id1(BaseId::new_with_raw_value(id1_reg as u32));
self.regs.write_stat_ctrl(
BufStatusAndControl::builder()
.with_dlc(u4::new(0))
.with_priority(u4::new(0))
.with_status(regs::BufferState::RxReady)
.build(),
);
Ok(())
}
pub fn configure_for_reception_with_extended_id(
&mut self,
extended_id: embedded_can::ExtendedId,
set_rtr: bool,
) -> Result<(), InvalidBufferIndexError> {
let mut regs = unsafe { self.can_id.steal_regs() };
let mut cmb_block = regs.msg_buf_block_mut(self.idx);
let id_raw = extended_id.as_raw();
let id1_reg = (((id_raw >> 18) & 0x7FF) << 4) as u16 | ((id_raw >> 15) & 0b111) as u16;
cmb_block.write_id1(BaseId::new_with_raw_value(id1_reg as u32));
let id0_reg = ((id_raw & 0x7FFF) << 1) as u16 | set_rtr as u16;
cmb_block.write_id0(ExtendedId::new_with_raw_value(id0_reg as u32));
cmb_block.write_stat_ctrl(
BufStatusAndControl::builder()
.with_dlc(u4::new(0))
.with_priority(u4::new(0))
.with_status(regs::BufferState::RxReady)
.build(),
);
self.mode = ChannelState::Receiving;
Ok(())
}
pub fn configure_for_transmission(
&mut self,
tx_priority: u4,
) -> Result<(), InvalidBufferIndexError> {
let mut regs = unsafe { self.can_id.steal_regs() };
let mut cmb_block = regs.msg_buf_block_mut(self.idx);
cmb_block.write_stat_ctrl(
BufStatusAndControl::builder()
.with_dlc(u4::new(0))
.with_priority(tx_priority)
.with_status(regs::BufferState::TxNotActive)
.build(),
);
self.mode = ChannelState::Receiving;
Ok(())
}
/// Reads a received CAN frame from the message buffer.
///
/// This function does not check whether the pre-requisites for reading a CAN frame were
/// met and assumes this was already checked by the user.
pub fn read_frame_unchecked(&self) -> CanFrame {
let id0 = self.regs.read_id0();
let id1 = self.regs.read_id1();
let data0 = self.regs.read_data0();
let data1 = self.regs.read_data1();
let data2 = self.regs.read_data2();
let data3 = self.regs.read_data3();
let mut data: [u8; 8] = [0; 8];
let mut read_data = |dlc: u4| {
(0..dlc.as_usize()).for_each(|i| match i {
0 => data[i] = data3.data_upper_byte().as_u8(),
1 => data[i] = data3.data_lower_byte().as_u8(),
2 => data[i] = data2.data_upper_byte().as_u8(),
3 => data[i] = data2.data_lower_byte().as_u8(),
4 => data[i] = data1.data_upper_byte().as_u8(),
5 => data[i] = data1.data_lower_byte().as_u8(),
6 => data[i] = data0.data_upper_byte().as_u8(),
7 => data[i] = data0.data_lower_byte().as_u8(),
_ => unreachable!(),
});
};
let (id, rtr) = if !id1.ide() {
let id = embedded_can::Id::Standard(
embedded_can::StandardId::new(id1.mask_28_18().as_u16()).unwrap(),
);
if id1.rtr_or_srr() {
(id, true)
} else {
(id, false)
}
} else {
let id_raw = (id1.mask_28_18().as_u32() << 18)
| (id1.mask_17_15().as_u32() << 15)
| id0.mask_14_0().as_u32();
let id = embedded_can::Id::Extended(embedded_can::ExtendedId::new(id_raw).unwrap());
if id0.xrtr() {
(id, true)
} else {
(id, false)
}
};
if rtr {
CanFrameRtr::new(id, self.regs.read_stat_ctrl().dlc().as_usize()).into()
} else {
let dlc = self.regs.read_stat_ctrl().dlc();
read_data(dlc);
CanFrameNormal::new(id, &data[0..dlc.as_usize()]).into()
}
}
pub fn transmit_frame_unchecked(&mut self, frame: CanFrame) {
let is_remote = frame.is_remote_frame();
self.write_id(frame.id(), is_remote);
let dlc = frame.dlc();
self.regs.modify_stat_ctrl(|mut ctrl| {
ctrl.set_status(regs::BufferState::TxOnce);
ctrl
});
}
fn write_id(&mut self, id: embedded_can::Id, is_remote: bool) {
match id {
embedded_can::Id::Standard(standard_id) => {
self.regs.write_id1(
BaseId::builder()
.with_mask_28_18(u11::new(standard_id.as_raw()))
.with_rtr_or_srr(is_remote)
.with_ide(false)
.with_mask_17_15(u3::new(0))
.build(),
);
self.regs.write_id0(ExtendedId::new_with_raw_value(0));
}
embedded_can::Id::Extended(extended_id) => {
let id_raw = extended_id.as_raw();
self.regs.write_id1(
BaseId::builder()
.with_mask_28_18(u11::new(((id_raw >> 18) & 0x7FF) as u16))
.with_rtr_or_srr(true)
.with_ide(true)
.with_mask_17_15(u3::new(((id_raw >> 15) & 0b111) as u8))
.build(),
);
self.regs.write_id0(
ExtendedId::builder()
.with_mask_14_0(u15::new((id_raw & 0x7FFF) as u16))
.with_xrtr(is_remote)
.build(),
);
}
}
}
}
pub struct CanWorker {
can: Can,
channels: [CanChannel; 15],
}
#[cfg(test)]
mod tests {
#[cfg(feature = "alloc")]
use std::println;
#[cfg(feature = "alloc")]
#[test]
pub fn test_clock_calculator_example_1() {
let configs = super::calculate_all_viable_clock_configs(
crate::time::Hertz::from_raw(50_000_000),
crate::time::Hertz::from_raw(25_000),
0.75,
)
.expect("clock calculation failed");
// Bitrate: 25278.05 Hz. Sample point: 0.7391
assert_eq!(configs[0].prescaler, 84);
assert_eq!(configs[0].tseg1, 16);
assert_eq!(configs[0].tseg2, 6);
assert_eq!(configs[0].sjw, 4);
// Vorago sample value.
let sample_cfg = configs
.iter()
.find(|c| c.prescaler == 100)
.expect("clock config not found");
// Slightly different distribution because we use a different sample point, but
// the sum of TSEG1 and TSEG2 is the same as the Vorago example 1.
assert_eq!(sample_cfg.tseg1, 14);
assert_eq!(sample_cfg.tseg2, 5);
}
}

349
va416xx-hal/src/can/regs.rs Normal file
View File

@ -0,0 +1,349 @@
//! Custom register definitions for the CAN register block to circumvent PAC API / SVD
//! shortcomings.
use arbitrary_int::{u11, u15, u2, u3, u4, u7};
pub const CAN_0_BASE: usize = 0x4001_4000;
pub const CAN_1_BASE: usize = 0x4001_4400;
#[derive(Debug)]
#[bitbybit::bitenum(u4)]
pub enum BufferState {
/// Passive channel.
RxNotActive = 0b0000,
/// This condition indicated that SW wrote RxNotActive to a buffer when a data copy
/// process is still active.
RxBusy = 0b0001,
RxReady = 0b0010,
/// Indicated that data is being copied for the first time (RxRead -> RxBusy0).
RxBusy0 = 0b0011,
RxFull = 0b0100,
/// Indicated that data is being copied for the second time (RxFull -> RxBusy2).
RxBusy1 = 0b0101,
RxOverrun = 0b0110,
RxBusy2 = 0b0111,
TxNotActive = 0b1000,
/// Automatical response to a remote frame.
TxRtr = 0b1010,
/// Transmit one frame.
TxOnce = 0b1100,
TxBusy0 = 0b1101,
/// Transmit one frame, and changes to TxRtr after that. This can either be written by
/// software, or it will be written by the hardware after an auto response of the
/// [BufferState::TxRtr] state.
TxOnceRtr = 0b1110,
TxBusy2 = 0b1111,
}
/// Status control register for individual message buffers.
#[bitbybit::bitfield(u32, default = 0x0)]
#[derive(Debug)]
pub struct BufStatusAndControl {
/// Data length code.
#[bits(12..=15, rw)]
dlc: u4,
#[bits(4..=7, rw)]
priority: u4,
#[bits(0..=3, rw)]
status: Option<BufferState>,
}
#[derive(Debug)]
pub struct Timestamp(arbitrary_int::UInt<u32, 16>);
impl Timestamp {
pub fn new(value: u16) -> Self {
Self(value.into())
}
pub fn value(&self) -> u16 {
self.0.value() as u16
}
pub fn write(&mut self, value: u16) {
self.0 = value.into();
}
}
#[bitbybit::bitfield(u32, default = 0x0)]
#[derive(Debug)]
pub struct TwoBytesData {
#[bits(8..=15, rw)]
data_upper_byte: u8,
#[bits(8..=15, rw)]
data_lower_byte: u8,
}
#[derive(derive_mmio::Mmio)]
#[repr(C)]
pub struct CanMsgBuf {
stat_ctrl: BufStatusAndControl,
timestamp: Timestamp,
data3: TwoBytesData,
data2: TwoBytesData,
data1: TwoBytesData,
data0: TwoBytesData,
id0: ExtendedId,
id1: BaseId,
}
impl MmioCanMsgBuf<'_> {
pub fn reset(&mut self) {
self.write_stat_ctrl(BufStatusAndControl::new_with_raw_value(0));
self.write_timestamp(Timestamp::new(0));
self.write_data3(TwoBytesData::new_with_raw_value(0));
self.write_data2(TwoBytesData::new_with_raw_value(0));
self.write_data1(TwoBytesData::new_with_raw_value(0));
self.write_data0(TwoBytesData::new_with_raw_value(0));
self.write_id1(BaseId::new_with_raw_value(0));
self.write_id0(ExtendedId::new_with_raw_value(0));
}
}
#[bitbybit::bitenum(u1, exhaustive = true)]
#[derive(Debug)]
pub enum PinLogicLevel {
DominantIsZero = 0b0,
DominantIsOne = 0b1,
}
#[bitbybit::bitenum(u1, exhaustive = true)]
#[derive(Debug)]
pub enum ErrorInterruptType {
/// EIPND bit is set on every error.
EveryError = 0b0,
/// EIPND bit is only set if error state changes as a result of a receive or transmit
/// error counter increment.
ErrorOnRxTxCounterChange = 0b1,
}
#[bitbybit::bitenum(u1, exhaustive = true)]
#[derive(Debug)]
pub enum DataDirection {
FirstByteAtHighestAddr = 0b0,
LastByteAtHighestAddr = 0b1,
}
#[bitbybit::bitfield(u32)]
pub struct Control {
#[bit(11, rw)]
error_interrupt_type: ErrorInterruptType,
/// Enables special diagnostics features of the CAN like LO, IGNACK, LOOPBACK, INTERNAL.
#[bit(10, rw)]
diag_enable: bool,
/// CANTX and CANRX pins are internally connected to each other.
#[bit(9, rw)]
internal: bool,
/// All messages sent by the CAN controller can also be received by a CAN buffer with a
/// matching buffer ID.
#[bit(8, rw)]
loopback: bool,
/// IGNACK feature. The CAN does not expect to receive an ACK bit.
#[bit(7, rw)]
ignore_ack: bool,
/// LO feature. The CAN is only configured as a receiver.
#[bit(6, rw)]
listen_only: bool,
#[bit(5, rw)]
data_dir: DataDirection,
#[bit(4, rw)]
timestamp_enable: bool,
#[bit(3, rw)]
bufflock: bool,
#[bit(2, rw)]
tx_logic_level: PinLogicLevel,
#[bit(1, rw)]
rx_logic_level: PinLogicLevel,
#[bit(0, rw)]
enable: bool,
}
#[bitbybit::bitfield(u32, default = 0x0)]
#[derive(Debug)]
pub struct TimingConfig {
#[bits(0..=2, rw)]
tseg2: u3,
#[bits(3..=6, rw)]
tseg1: u4,
#[bits(7..=8, rw)]
sync_jump_width: u2,
#[bits(9..=15, rw)]
prescaler: u7,
}
#[bitbybit::bitfield(u32)]
#[derive(Debug)]
pub struct InterruptEnable {
#[bit(15, rw)]
error: bool,
#[bit(0, rw)]
buffer: [bool; 15],
}
#[bitbybit::bitfield(u32)]
#[derive(Debug)]
pub struct InterruptClear {
#[bit(15, w)]
error: bool,
#[bit(0, w)]
buffer: [bool; 15],
}
#[bitbybit::bitfield(u32)]
#[derive(Debug)]
pub struct InterruptPending {
#[bit(15, r)]
error: bool,
#[bit(0, r)]
buffer: [bool; 15],
}
#[bitbybit::bitfield(u32)]
#[derive(Debug)]
pub struct ErrorCounter {
#[bits(0..=7, r)]
transmit: u8,
#[bits(8..=15, r)]
receive: u8,
}
/// This register is unused for standard frames.
#[bitbybit::bitfield(u32, default = 0x0)]
#[derive(Debug)]
pub struct ExtendedId {
/// Mask for ID bits \[14:0\] of extended frames.
#[bits(1..=15, rw)]
mask_14_0: u15,
/// CAN XRTR bit.
#[bit(0, rw)]
xrtr: bool,
}
#[bitbybit::bitfield(u32, default = 0x0)]
#[derive(Debug)]
pub struct BaseId {
/// This will contain ID\[10:0\] for standard frames and bits [28:18] for extended frames.
#[bits(5..=15, rw)]
mask_28_18: u11,
/// This is the RTR bit for standard frames, and the SRR bit for extended frames.
#[bit(4, rw)]
rtr_or_srr: bool,
/// Identifier extension bit.
#[bit(3, rw)]
ide: bool,
/// Mask for ID bits \[17:15\] of extended frames.
#[bits(0..=2, rw)]
mask_17_15: u3,
}
#[derive(derive_mmio::Mmio)]
#[repr(C)]
pub struct Can {
#[mmio(inner)]
cmb0: CanMsgBuf,
#[mmio(inner)]
cmb1: CanMsgBuf,
#[mmio(inner)]
cmb2: CanMsgBuf,
#[mmio(inner)]
cmb3: CanMsgBuf,
#[mmio(inner)]
cmb4: CanMsgBuf,
#[mmio(inner)]
cmb5: CanMsgBuf,
#[mmio(inner)]
cmb6: CanMsgBuf,
#[mmio(inner)]
cmb7: CanMsgBuf,
#[mmio(inner)]
cmb8: CanMsgBuf,
#[mmio(inner)]
cmb9: CanMsgBuf,
#[mmio(inner)]
cmb10: CanMsgBuf,
#[mmio(inner)]
cmb11: CanMsgBuf,
#[mmio(inner)]
cmb12: CanMsgBuf,
#[mmio(inner)]
cmb13: CanMsgBuf,
// This CAN message buffer has different mask registers.
#[mmio(inner)]
cmb14: CanMsgBuf,
/// Hidden CAN message buffer. Only allowed to be used internally by the peripheral.
#[mmio(inner)]
_hcmb: CanMsgBuf,
control: Control,
timing: TimingConfig,
/// Global mask extension used for buffers 0 to 13.
gmskx: ExtendedId,
/// Global mask base used for buffers 0 to 13.
gmskb: BaseId,
/// Basic mask extension used for buffer 14.
bmskx: ExtendedId,
/// Basic mask base used for buffer 14.
bmskb: BaseId,
ien: InterruptEnable,
#[mmio(PureRead)]
ipnd: InterruptPending,
#[mmio(Write)]
iclr: InterruptClear,
/// Interrupt Code Enable Register.
icen: InterruptEnable,
#[mmio(PureRead)]
status_pending: u32,
#[mmio(PureRead)]
error_counter: ErrorCounter,
#[mmio(PureRead)]
diag: u32,
#[mmio(PureRead)]
timer: u32,
}
impl Can {
/// Create a new CAN MMIO instance for peripheral 0.
///
/// # Safety
///
/// This API can be used to potentially create a driver to the same peripheral structure
/// from multiple threads. The user must ensure that concurrent accesses are safe and do not
/// interfere with each other.
pub const unsafe fn new_mmio_fixed_0() -> MmioCan<'static> {
Self::new_mmio_at(CAN_0_BASE)
}
/// Create a new CAN MMIO instance for peripheral 1.
///
/// # Safety
///
/// This API can be used to potentially create a driver to the same peripheral structure
/// from multiple threads. The user must ensure that concurrent accesses are safe and do not
/// interfere with each other.
pub const unsafe fn new_mmio_fixed_1() -> MmioCan<'static> {
Self::new_mmio_at(CAN_1_BASE)
}
}
impl MmioCan<'_> {
// TODO: It would be nice if derive-mmio could generate this for us..
pub fn msg_buf_block_mut(&mut self, idx: usize) -> MmioCanMsgBuf<'static> {
assert!(idx < 15, "invalid index for CAN message buffer");
match idx {
0 => unsafe { self.steal_cmb0() },
1 => unsafe { self.steal_cmb1() },
2 => unsafe { self.steal_cmb2() },
3 => unsafe { self.steal_cmb3() },
4 => unsafe { self.steal_cmb4() },
5 => unsafe { self.steal_cmb5() },
6 => unsafe { self.steal_cmb6() },
7 => unsafe { self.steal_cmb7() },
8 => unsafe { self.steal_cmb8() },
9 => unsafe { self.steal_cmb9() },
10 => unsafe { self.steal_cmb10() },
11 => unsafe { self.steal_cmb11() },
12 => unsafe { self.steal_cmb12() },
13 => unsafe { self.steal_cmb13() },
14 => unsafe { self.steal_cmb14() },
_ => unreachable!(),
}
}
}

View File

@ -12,51 +12,13 @@
//! - [UART example on the PEB1 board](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/uart.rs)
#[cfg(not(feature = "va41628"))]
use crate::adc::ADC_MAX_CLK;
use crate::pac;
use crate::{pac, PeripheralSelect, SyscfgExt as _};
use crate::time::Hertz;
pub const HBO_FREQ: Hertz = Hertz::from_raw(20_000_000);
pub const XTAL_OSC_TSTART_MS: u32 = 15;
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum PeripheralSelect {
Spi0 = 0,
Spi1 = 1,
Spi2 = 2,
Spi3 = 3,
Uart0 = 4,
Uart1 = 5,
Uart2 = 6,
I2c0 = 7,
I2c1 = 8,
I2c2 = 9,
Can0 = 10,
Can1 = 11,
Rng = 12,
Adc = 13,
Dac = 14,
Dma = 15,
Ebi = 16,
Eth = 17,
Spw = 18,
Clkgen = 19,
IrqRouter = 20,
IoConfig = 21,
Utility = 22,
Watchdog = 23,
PortA = 24,
PortB = 25,
PortC = 26,
PortD = 27,
PortE = 28,
PortF = 29,
PortG = 30,
}
pub type PeripheralClock = PeripheralSelect;
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum FilterClkSel {
@ -70,81 +32,6 @@ pub enum FilterClkSel {
Clk7 = 7,
}
#[inline(always)]
pub fn enable_peripheral_clock(syscfg: &mut pac::Sysconfig, clock: PeripheralSelect) {
syscfg
.peripheral_clk_enable()
.modify(|r, w| unsafe { w.bits(r.bits() | (1 << clock as u8)) });
}
#[inline(always)]
pub fn disable_peripheral_clock(syscfg: &mut pac::Sysconfig, clock: PeripheralSelect) {
syscfg
.peripheral_clk_enable()
.modify(|r, w| unsafe { w.bits(r.bits() & !(1 << clock as u8)) });
}
#[inline(always)]
pub fn assert_periph_reset(syscfg: &mut pac::Sysconfig, periph: PeripheralSelect) {
syscfg
.peripheral_reset()
.modify(|r, w| unsafe { w.bits(r.bits() & !(1 << periph as u8)) });
}
#[inline(always)]
pub fn deassert_periph_reset(syscfg: &mut pac::Sysconfig, periph: PeripheralSelect) {
syscfg
.peripheral_reset()
.modify(|r, w| unsafe { w.bits(r.bits() | (1 << periph as u8)) });
}
#[inline(always)]
fn assert_periph_reset_for_two_cycles(syscfg: &mut pac::Sysconfig, periph: PeripheralSelect) {
assert_periph_reset(syscfg, periph);
cortex_m::asm::nop();
cortex_m::asm::nop();
deassert_periph_reset(syscfg, periph);
}
pub trait SyscfgExt {
fn enable_peripheral_clock(&mut self, clock: PeripheralClock);
fn disable_peripheral_clock(&mut self, clock: PeripheralClock);
fn assert_periph_reset(&mut self, periph: PeripheralSelect);
fn deassert_periph_reset(&mut self, periph: PeripheralSelect);
fn assert_periph_reset_for_two_cycles(&mut self, periph: PeripheralSelect);
}
impl SyscfgExt for pac::Sysconfig {
#[inline(always)]
fn enable_peripheral_clock(&mut self, clock: PeripheralClock) {
enable_peripheral_clock(self, clock)
}
#[inline(always)]
fn disable_peripheral_clock(&mut self, clock: PeripheralClock) {
disable_peripheral_clock(self, clock)
}
#[inline(always)]
fn assert_periph_reset(&mut self, clock: PeripheralSelect) {
assert_periph_reset(self, clock)
}
#[inline(always)]
fn deassert_periph_reset(&mut self, clock: PeripheralSelect) {
deassert_periph_reset(self, clock)
}
#[inline(always)]
fn assert_periph_reset_for_two_cycles(&mut self, periph: PeripheralSelect) {
assert_periph_reset_for_two_cycles(self, periph)
}
}
/// Refer to chapter 8 (p.57) of the programmers guide for detailed information.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
@ -509,16 +396,45 @@ impl Clocks {
}
/// Returns the frequency of the APB0 which is equal to the system clock.
///
/// This clock is the reference clock for the following peripherals:
///
/// - Ethernet
/// - SpaceWire
/// - IRQ Router
/// - DMA
/// - Clock Generator
pub const fn apb0(&self) -> Hertz {
self.sysclk()
}
/// Returns system clock divied by 2.
///
/// This clock is the reference clock for the following peripherals:
///
/// - Timer[15:0]
/// - UART2
/// - SPI
/// - I2C
/// - CAN
/// - GPIO
/// - IOCONFIG
/// - System Config
pub const fn apb1(&self) -> Hertz {
self.apb1
}
/// Returns system clock divied by 4.
///
/// This clock is the reference clock for the following peripherals:
///
/// - Timer[23:16]
/// - TRNG
/// - UART[1:0]
/// - DAC
/// - ADC
/// - Watchdog
/// - Utility
pub const fn apb2(&self) -> Hertz {
self.apb2
}

View File

@ -5,10 +5,7 @@
//! - [ADC and DAC example](https://github.com/us-irs/va416xx-rs/blob/main/examples/simple/examples/dac-adc.rs)
use core::ops::Deref;
use crate::{
clock::{Clocks, PeripheralSelect, SyscfgExt},
pac,
};
use crate::{clock::Clocks, pac, PeripheralSelect, SyscfgExt as _};
pub type DacRegisterBlock = pac::dac0::RegisterBlock;

View File

@ -3,11 +3,9 @@
//! ## Examples
//!
//! - [Simple DMA example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/dma.rs)
use crate::{
clock::{PeripheralClock, PeripheralSelect},
enable_nvic_interrupt, pac,
prelude::*,
};
use arbitrary_int::{u10, u2, u3, u4};
use crate::{enable_nvic_interrupt, pac, PeripheralClock, PeripheralSelect, SyscfgExt as _};
const MAX_DMA_TRANSFERS_PER_CYCLE: usize = 1024;
const BASE_PTR_ADDR_MASK: u32 = 0b1111111;
@ -82,34 +80,30 @@ pub enum RPower {
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct InvalidCtrlBlockAddrError;
bitfield::bitfield! {
#[repr(transparent)]
#[derive(Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct ChannelConfig(u32);
impl Debug;
u32;
pub raw, set_raw: 31,0;
u8;
pub dst_inc, set_dst_inc: 31, 30;
u8;
pub dst_size, set_dst_size: 29, 28;
u8;
pub src_inc, set_src_inc: 27, 26;
u8;
pub src_size, set_src_size: 25, 24;
u8;
pub dest_prot_ctrl, set_dest_prot_ctrl: 23, 21;
u8;
pub src_prot_ctrl, set_src_prot_ctrl: 20, 18;
u8;
pub r_power, set_r_power: 17, 14;
u16;
pub n_minus_1, set_n_minus_1: 13, 4;
bool;
pub next_useburst, set_next_useburst: 3;
u8;
pub cycle_ctrl, set_cycle_ctr: 2, 0;
#[bitbybit::bitfield(u32)]
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct ChannelConfig {
#[bits(30..=31, rw)]
dest_inc: u2,
#[bits(28..=29, rw)]
dest_size: u2,
#[bits(26..=27, rw)]
src_inc: u2,
#[bits(24..=25, rw)]
src_size: u2,
#[bits(21..=23, rw)]
dest_prot_ctrl: u3,
#[bits(18..=20, rw)]
src_prot_ctrl: u3,
#[bits(14..=17, rw)]
r_power: u4,
#[bits(4..=13, rw)]
n_minus_1: u10,
#[bit(3, rw)]
next_useburst: bool,
#[bits(0..=2, rw)]
cycle_ctrl: u3,
}
#[repr(C)]
@ -127,7 +121,7 @@ impl DmaChannelControl {
Self {
src_end_ptr: 0,
dest_end_ptr: 0,
cfg: ChannelConfig(0),
cfg: ChannelConfig::new_with_raw_value(0),
padding: 0,
}
}
@ -428,20 +422,30 @@ impl DmaChannel {
return Err(DmaTransferInitError::TransferSizeTooLarge(source.len()));
}
let len = source.len() - 1;
self.ch_ctrl_pri.cfg.set_raw(0);
self.ch_ctrl_pri.cfg = ChannelConfig::new_with_raw_value(0);
self.ch_ctrl_pri.src_end_ptr = (source.as_ptr() as u32)
.checked_add(len as u32)
.ok_or(DmaTransferInitError::AddrOverflow)?;
self.ch_ctrl_pri.dest_end_ptr = dest as u32;
self.ch_ctrl_pri
.cfg
.set_cycle_ctr(CycleControl::Basic as u8);
self.ch_ctrl_pri.cfg.set_src_size(DataSize::Byte as u8);
self.ch_ctrl_pri.cfg.set_src_inc(AddrIncrement::Byte as u8);
self.ch_ctrl_pri.cfg.set_dst_size(DataSize::Byte as u8);
self.ch_ctrl_pri.cfg.set_dst_inc(AddrIncrement::None as u8);
self.ch_ctrl_pri.cfg.set_n_minus_1(len as u16);
self.ch_ctrl_pri.cfg.set_r_power(RPower::Every8 as u8);
.set_cycle_ctrl(u3::new(CycleControl::Basic as u8));
self.ch_ctrl_pri
.cfg
.set_src_size(u2::new(DataSize::Byte as u8));
self.ch_ctrl_pri
.cfg
.set_src_inc(u2::new(AddrIncrement::Byte as u8));
self.ch_ctrl_pri
.cfg
.set_dest_size(u2::new(DataSize::Byte as u8));
self.ch_ctrl_pri
.cfg
.set_dest_inc(u2::new(AddrIncrement::None as u8));
self.ch_ctrl_pri.cfg.set_n_minus_1(u10::new(len as u16));
self.ch_ctrl_pri
.cfg
.set_r_power(u4::new(RPower::Every8 as u8));
self.select_primary_structure();
Ok(())
}
@ -470,16 +474,22 @@ impl DmaChannel {
data_size: DataSize,
addr_incr: AddrIncrement,
) {
self.ch_ctrl_pri.cfg.set_raw(0);
self.ch_ctrl_pri.cfg = ChannelConfig::new_with_raw_value(0);
self.ch_ctrl_pri.src_end_ptr = src_end_ptr;
self.ch_ctrl_pri.dest_end_ptr = dest_end_ptr;
self.ch_ctrl_pri.cfg.set_cycle_ctr(CycleControl::Auto as u8);
self.ch_ctrl_pri.cfg.set_src_size(data_size as u8);
self.ch_ctrl_pri.cfg.set_src_inc(addr_incr as u8);
self.ch_ctrl_pri.cfg.set_dst_size(data_size as u8);
self.ch_ctrl_pri.cfg.set_dst_inc(addr_incr as u8);
self.ch_ctrl_pri.cfg.set_n_minus_1(n_minus_one as u16);
self.ch_ctrl_pri.cfg.set_r_power(RPower::Every4 as u8);
self.ch_ctrl_pri
.cfg
.set_cycle_ctrl(u3::new(CycleControl::Auto as u8));
self.ch_ctrl_pri.cfg.set_src_size(u2::new(data_size as u8));
self.ch_ctrl_pri.cfg.set_src_inc(u2::new(addr_incr as u8));
self.ch_ctrl_pri.cfg.set_dest_size(u2::new(data_size as u8));
self.ch_ctrl_pri.cfg.set_dest_inc(u2::new(addr_incr as u8));
self.ch_ctrl_pri
.cfg
.set_n_minus_1(u10::new(n_minus_one as u16));
self.ch_ctrl_pri
.cfg
.set_r_power(u4::new(RPower::Every4 as u8));
self.select_primary_structure();
}
}

View File

@ -14,7 +14,7 @@
//! Instances of [`DynPin`] cannot be created directly. Rather, they must be
//! created from their type-level equivalents using [`From`]/[`Into`].
//!
//! ```
//! ```ignore
//! // Move a pin out of the Pins struct and convert to a DynPin
//! let pa0: DynPin = pins.pa0.into();
//! ```
@ -22,7 +22,7 @@
//! Conversions between pin modes use a value-level version of the type-level
//! API.
//!
//! ```
//! ```ignore
//! // Use one of the literal function names
//! pa0.into_floating_input();
//! // Use a method and a DynPinMode variant
@ -38,7 +38,7 @@
//! guarantee the pin has the correct ID or is in the correct mode at
//! compile-time. Use [TryFrom]/[TryInto] for this conversion.
//!
//! ```
//! ```ignore
//! // Convert to a `DynPin`
//! let pa0: DynPin = pins.pa0.into();
//! // Change pin mode

View File

@ -19,7 +19,7 @@
//! Type-level [`Pin`]s are parameterized by two type-level enums, [`PinId`] and
//! [`PinMode`].
//!
//! ```
//! ```ignore
//! pub struct Pin<I, M>
//! where
//! I: PinId,
@ -49,14 +49,14 @@
//! within the [PinsA] struct can be moved out and used individually.
//!
//!
//! ```no_run
//! ```no_run,ignore
//! let mut peripherals = Peripherals::take().unwrap();
//! let pinsa = PinsA::new(peripherals.porta);
//! ```
//!
//! Pins can be converted between modes using several different methods.
//!
//! ```no_run
//! ```no_run,ignore
//! // Use one of the literal function names
//! let pa0 = pinsa.pa0.into_floating_input();
//! // Use a generic method and one of the `PinMode` variant types

View File

@ -4,11 +4,7 @@
//!
//! - [PEB1 accelerometer example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/peb1-accelerometer.rs)
use crate::{
clock::{Clocks, PeripheralSelect},
pac,
prelude::SyscfgExt,
time::Hertz,
typelevel::Sealed,
clock::Clocks, enable_peripheral_clock, pac, time::Hertz, typelevel::Sealed, PeripheralSelect,
};
use core::{marker::PhantomData, ops::Deref};
use embedded_hal::i2c::{self, Operation, SevenBitAddress, TenBitAddress};
@ -212,13 +208,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)
}
}
@ -324,13 +320,12 @@ impl<I2C> I2cBase<I2C> {
impl<I2c: Instance> I2cBase<I2c> {
pub fn new(
i2c: I2c,
syscfg: &mut pac::Sysconfig,
clocks: &Clocks,
speed_mode: I2cSpeed,
ms_cfg: Option<&MasterConfig>,
sl_cfg: Option<&SlaveConfig>,
) -> Result<Self, ClockTooSlowForFastI2cError> {
syscfg.enable_peripheral_clock(I2c::PERIPH_SEL);
enable_peripheral_clock(I2c::PERIPH_SEL);
let mut i2c_base = I2cBase {
i2c,
@ -390,7 +385,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
@ -451,7 +446,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(())
}
@ -482,13 +477,12 @@ pub struct I2cMaster<I2c, Addr = SevenBitAddress> {
impl<I2c: Instance, Addr> I2cMaster<I2c, Addr> {
pub fn new(
i2c: I2c,
sys_cfg: &mut pac::Sysconfig,
cfg: MasterConfig,
clocks: &Clocks,
speed_mode: I2cSpeed,
) -> Result<Self, ClockTooSlowForFastI2cError> {
Ok(I2cMaster {
i2c_base: I2cBase::new(i2c, sys_cfg, clocks, speed_mode, Some(&cfg), None)?,
i2c_base: I2cBase::new(i2c, clocks, speed_mode, Some(&cfg), None)?,
addr: PhantomData,
}
.enable_master())
@ -743,13 +737,12 @@ pub struct I2cSlave<I2c, Addr = SevenBitAddress> {
impl<I2c: Instance, Addr> I2cSlave<I2c, Addr> {
fn new_generic(
i2c: I2c,
sys_cfg: &mut pac::Sysconfig,
cfg: SlaveConfig,
clocks: &Clocks,
speed_mode: I2cSpeed,
) -> Result<Self, ClockTooSlowForFastI2cError> {
Ok(I2cSlave {
i2c_base: I2cBase::new(i2c, sys_cfg, clocks, speed_mode, None, Some(&cfg))?,
i2c_base: I2cBase::new(i2c, clocks, speed_mode, None, Some(&cfg))?,
addr: PhantomData,
}
.enable_slave())
@ -890,7 +883,6 @@ impl<I2c: Instance> I2cSlave<I2c, SevenBitAddress> {
/// Create a new I2C slave for seven bit addresses
pub fn new(
i2c: I2c,
sys_cfg: &mut pac::Sysconfig,
cfg: SlaveConfig,
clocks: &Clocks,
speed_mode: I2cSpeed,
@ -898,18 +890,17 @@ impl<I2c: Instance> I2cSlave<I2c, SevenBitAddress> {
if let I2cAddress::TenBit(_) = cfg.addr {
return Err(InitError::WrongAddrMode);
}
Ok(Self::new_generic(i2c, sys_cfg, cfg, clocks, speed_mode)?)
Ok(Self::new_generic(i2c, cfg, clocks, speed_mode)?)
}
}
impl<I2c: Instance> I2cSlave<I2c, TenBitAddress> {
pub fn new_ten_bit_addr(
i2c: I2c,
sys_cfg: &mut pac::Sysconfig,
cfg: SlaveConfig,
clocks: &Clocks,
speed_mode: I2cSpeed,
) -> Result<Self, ClockTooSlowForFastI2cError> {
Self::new_generic(i2c, sys_cfg, cfg, clocks, speed_mode)
Self::new_generic(i2c, cfg, clocks, speed_mode)
}
}

View File

@ -1,8 +1,5 @@
//! IRQ Router peripheral support.
use crate::{
clock::{PeripheralSelect, SyscfgExt},
pac,
};
use crate::{pac, PeripheralSelect, SyscfgExt as _};
/// This enables and initiates the peripheral.
///

View File

@ -1,20 +1,23 @@
//! This is the **H**ardware **A**bstraction **L**ayer (HAL) for the VA416xx MCU family.
//!
//! It is an additional hardware abstraction on top of the [peripheral access API](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/va416xx).
//!
//! It is the result of reading the datasheet for the device and encoding a type-safe layer over the
//! raw PAC. This crate also implements traits specified by the
//! [embedded-hal](https://github.com/rust-embedded/embedded-hal) project, making it compatible with
//! various drivers in the embedded rust ecosystem.
//! You have to enable one of the following device features to use this crate depending on
//! which chip you are using:
//!
//! It is generally advised to enable ONE of the following device features to use this crate
//! depending on which chip you are using:
//!
//! - `va41630`
//! - `va41629`
//! - `va41628`
//! - `va41620`
//!
//! If no option is specified, only access to APIs which are common for all families or
//! which are not disabled for specific families is granted.
//!
//! When using this HAL and writing applications for the VA416xx family in general, it is strongly
//! recommended that you set up the clock properly, because the default internal HBO clock
//! is not very accurate. You can use the [crate::clock] module for this. If you are working
@ -23,24 +26,18 @@
//! faulty register reset values which might lead to weird bugs and glitches.
#![no_std]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(test)]
extern crate std;
#[cfg(not(feature = "device-selected"))]
compile_error!(
"This crate requires one of the following device features enabled:
va41630
va41629
va41628
"
);
use gpio::Port;
pub use va416xx as device;
pub use va416xx as pac;
pub mod prelude;
pub mod can;
pub mod clock;
pub mod dma;
pub mod edac;
@ -63,6 +60,84 @@ pub mod adc;
#[cfg(not(feature = "va41628"))]
pub mod dac;
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum PeripheralSelect {
Spi0 = 0,
Spi1 = 1,
Spi2 = 2,
Spi3 = 3,
Uart0 = 4,
Uart1 = 5,
Uart2 = 6,
I2c0 = 7,
I2c1 = 8,
I2c2 = 9,
Can0 = 10,
Can1 = 11,
Rng = 12,
Adc = 13,
Dac = 14,
Dma = 15,
Ebi = 16,
Eth = 17,
Spw = 18,
Clkgen = 19,
IrqRouter = 20,
IoConfig = 21,
Utility = 22,
Watchdog = 23,
PortA = 24,
PortB = 25,
PortC = 26,
PortD = 27,
PortE = 28,
PortF = 29,
PortG = 30,
}
pub type PeripheralClock = PeripheralSelect;
#[inline(always)]
pub fn enable_peripheral_clock(clock: PeripheralSelect) {
// Safety: Only bit of peripheral is modified.
unsafe { pac::Sysconfig::steal() }
.peripheral_clk_enable()
.modify(|r, w| unsafe { w.bits(r.bits() | (1 << clock as u8)) });
}
#[inline(always)]
pub fn disable_peripheral_clock(clock: PeripheralSelect) {
// Safety: Only bit of peripheral is modified.
unsafe { pac::Sysconfig::steal() }
.peripheral_clk_enable()
.modify(|r, w| unsafe { w.bits(r.bits() & !(1 << clock as u8)) });
}
#[inline(always)]
pub fn assert_periph_reset(periph: PeripheralSelect) {
// Safety: Only reset bit of peripheral is modified.
unsafe { pac::Sysconfig::steal() }
.peripheral_reset()
.modify(|r, w| unsafe { w.bits(r.bits() & !(1 << periph as u8)) });
}
#[inline(always)]
pub fn deassert_periph_reset(periph: PeripheralSelect) {
// Safety: Only rest bit of peripheral is modified.
unsafe { pac::Sysconfig::steal() }
.peripheral_reset()
.modify(|r, w| unsafe { w.bits(r.bits() | (1 << periph as u8)) });
}
#[inline(always)]
fn assert_periph_reset_for_two_cycles(periph: PeripheralSelect) {
assert_periph_reset(periph);
cortex_m::asm::nop();
cortex_m::asm::nop();
deassert_periph_reset(periph);
}
#[derive(Debug, Eq, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum FunSel {
@ -106,6 +181,45 @@ pub fn port_function_select(
Ok(())
}
pub trait SyscfgExt {
fn enable_peripheral_clock(&mut self, clock: PeripheralClock);
fn disable_peripheral_clock(&mut self, clock: PeripheralClock);
fn assert_periph_reset(&mut self, periph: PeripheralSelect);
fn deassert_periph_reset(&mut self, periph: PeripheralSelect);
fn assert_periph_reset_for_two_cycles(&mut self, periph: PeripheralSelect);
}
impl SyscfgExt for pac::Sysconfig {
#[inline(always)]
fn enable_peripheral_clock(&mut self, clock: PeripheralClock) {
enable_peripheral_clock(clock)
}
#[inline(always)]
fn disable_peripheral_clock(&mut self, clock: PeripheralClock) {
disable_peripheral_clock(clock)
}
#[inline(always)]
fn assert_periph_reset(&mut self, clock: PeripheralSelect) {
assert_periph_reset(clock)
}
#[inline(always)]
fn deassert_periph_reset(&mut self, clock: PeripheralSelect) {
deassert_periph_reset(clock)
}
#[inline(always)]
fn assert_periph_reset_for_two_cycles(&mut self, periph: PeripheralSelect) {
assert_periph_reset_for_two_cycles(periph)
}
}
/// Enable a specific interrupt using the NVIC peripheral.
///
/// # Safety

View File

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

View File

@ -12,7 +12,7 @@ use core::{convert::Infallible, marker::PhantomData, ops::Deref};
use embedded_hal::spi::{Mode, MODE_0};
use crate::{
clock::{Clocks, PeripheralSelect, SyscfgExt},
clock::Clocks,
gpio::{
AltFunc1, AltFunc2, AltFunc3, Pin, PA0, PA1, PA2, PA3, PA4, PA5, PA6, PA7, PA8, PA9, PB0,
PB1, PB12, PB13, PB14, PB15, PB2, PB3, PB4, PC0, PC1, PC10, PC11, PC7, PC8, PC9, PE12,
@ -21,6 +21,7 @@ use crate::{
pac,
time::Hertz,
typelevel::{NoneT, Sealed},
PeripheralSelect,
};
#[cfg(not(feature = "va41628"))]
@ -1040,15 +1041,14 @@ where
/// to be done once.
/// * `syscfg` - Can be passed optionally to enable the peripheral clock
pub fn new(
syscfg: &mut pac::Sysconfig,
clocks: &crate::clock::Clocks,
spi: SpiI,
pins: (Sck, Miso, Mosi),
spi_cfg: SpiConfig,
) -> Self {
crate::clock::enable_peripheral_clock(syscfg, SpiI::PERIPH_SEL);
crate::enable_peripheral_clock(SpiI::PERIPH_SEL);
// This is done in the C HAL.
syscfg.assert_periph_reset_for_two_cycles(SpiI::PERIPH_SEL);
crate::assert_periph_reset_for_two_cycles(SpiI::PERIPH_SEL);
let SpiConfig {
clk,
init_mode,

View File

@ -15,10 +15,10 @@ use core::ops::Deref;
use embedded_hal_nb::serial::Read;
use fugit::RateExtU32;
use crate::clock::{Clocks, PeripheralSelect, SyscfgExt};
use crate::clock::Clocks;
use crate::gpio::PF13;
use crate::time::Hertz;
use crate::{disable_nvic_interrupt, enable_nvic_interrupt};
use crate::{disable_nvic_interrupt, enable_nvic_interrupt, PeripheralSelect};
use crate::{
gpio::{
AltFunc1, AltFunc2, AltFunc3, Pin, PA2, PA3, PB14, PB15, PC14, PC4, PC5, PD11, PD12, PE2,
@ -38,6 +38,21 @@ pub enum Bank {
Uart2 = 2,
}
impl Bank {
/// Retrieve the peripheral register block.
///
/// # Safety
///
/// Circumvents the HAL safety guarantees.
pub const unsafe fn reg_block(&self) -> &'static uart_base::RegisterBlock {
match self {
Bank::Uart0 => unsafe { &(*pac::Uart0::ptr()) },
Bank::Uart1 => unsafe { &(*pac::Uart1::ptr()) },
Bank::Uart2 => unsafe { &(*pac::Uart2::ptr()) },
}
}
}
//==================================================================================================
// Type-Level support
//==================================================================================================
@ -82,56 +97,6 @@ impl RxPin<Uart2> for Pin<PF9, AltFunc1> {}
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct TransferPendingError;
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum RxError {
Overrun,
Framing,
Parity,
}
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Error {
Rx(RxError),
BreakCondition,
}
impl From<RxError> for Error {
fn from(value: RxError) -> Self {
Self::Rx(value)
}
}
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 {
@ -398,7 +363,7 @@ pub struct BufferTooShortError {
pub trait Instance: Deref<Target = uart_base::RegisterBlock> {
const IDX: u8;
const PERIPH_SEL: PeripheralSelect;
const PERIPH_SEL: crate::PeripheralSelect;
const PTR: *const uart_base::RegisterBlock;
const IRQ_RX: pac::Interrupt;
const IRQ_TX: pac::Interrupt;
@ -471,21 +436,6 @@ impl Instance for Uart2 {
}
}
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::Uart0 => unsafe { pac::Uart0::reg_block() },
Bank::Uart1 => unsafe { pac::Uart1::reg_block() },
Bank::Uart2 => unsafe { pac::Uart2::reg_block() },
}
}
}
//==================================================================================================
// UART implementation
//==================================================================================================
@ -626,22 +576,27 @@ 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 = Error;
type Error = Infallible;
}
impl<UartInstance> embedded_hal_nb::serial::ErrorType for UartBase<UartInstance> {
type Error = Error;
type Error = Infallible;
}
impl<Uart: Instance> embedded_hal_nb::serial::Read<u8> for UartBase<Uart> {
fn read(&mut self) -> nb::Result<u8, Self::Error> {
self.rx.read().map_err(|e| e.map(Error::Rx))
self.rx.read()
}
}
@ -675,15 +630,14 @@ impl<TxPinInst: TxPin<UartInstance>, RxPinInst: RxPin<UartInstance>, UartInstanc
Uart<UartInstance, (TxPinInst, RxPinInst)>
{
pub fn new(
syscfg: &mut va416xx::Sysconfig,
uart: UartInstance,
pins: (TxPinInst, RxPinInst),
config: impl Into<Config>,
clocks: &Clocks,
) -> Self {
crate::clock::enable_peripheral_clock(syscfg, UartInstance::PERIPH_SEL);
crate::enable_peripheral_clock(UartInstance::PERIPH_SEL);
// This is done in the C HAL.
syscfg.assert_periph_reset_for_two_cycles(UartInstance::PERIPH_SEL);
crate::assert_periph_reset_for_two_cycles(UartInstance::PERIPH_SEL);
Uart {
inner: UartBase {
uart,
@ -696,13 +650,12 @@ impl<TxPinInst: TxPin<UartInstance>, RxPinInst: RxPin<UartInstance>, UartInstanc
}
pub fn new_with_clock_freq(
syscfg: &mut va416xx::Sysconfig,
uart: UartInstance,
pins: (TxPinInst, RxPinInst),
config: impl Into<Config>,
clock: impl Into<Hertz>,
) -> Self {
crate::clock::enable_peripheral_clock(syscfg, UartInstance::PERIPH_SEL);
crate::enable_peripheral_clock(UartInstance::PERIPH_SEL);
Uart {
inner: UartBase {
uart,
@ -729,6 +682,8 @@ impl<TxPinInst: TxPin<UartInstance>, RxPinInst: RxPin<UartInstance>, UartInstanc
delegate::delegate! {
to self.inner {
/// Poll receiver errors.
pub fn poll_rx_errors(&self) -> Option<UartErrors>;
#[inline]
pub fn enable_rx(&mut self);
#[inline]
@ -813,6 +768,23 @@ impl<Uart: Instance> Rx<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)
}
#[inline]
pub fn clear_fifo(&self) {
self.0.fifo_clr().write(|w| w.rxfifo().set_bit());
@ -874,34 +846,15 @@ impl<Uart: Instance> Rx<Uart> {
}
impl<Uart> embedded_io::ErrorType for Rx<Uart> {
type Error = RxError;
type Error = Infallible;
}
impl<Uart> embedded_hal_nb::serial::ErrorType for Rx<Uart> {
type Error = RxError;
type Error = Infallible;
}
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!()
@ -916,13 +869,23 @@ 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() {
let w = nb::block!(<Self as embedded_hal_nb::serial::Read<u8>>::read(self))?;
*byte = w;
match <Self as embedded_hal_nb::serial::Read<u8>>::read(self) {
Ok(w) => {
*byte = w;
read += 1;
}
Err(nb::Error::WouldBlock) => break,
}
}
Ok(buf.len())
Ok(read)
}
}
@ -1081,14 +1044,19 @@ impl<Uart: Instance> embedded_io::Write for Tx<Uart> {
if buf.is_empty() {
return Ok(0);
}
for byte in buf.iter() {
nb::block!(<Self as embedded_hal_nb::serial::Write<u8>>::write(
self, *byte
))?;
loop {
if self.0.txstatus().read().wrrdy().bit_is_set() {
break;
}
}
Ok(buf.len())
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),
}
}
Ok(written)
}
fn flush(&mut self) -> Result<(), Self::Error> {
@ -1182,7 +1150,7 @@ impl<Uart: Instance> RxWithInterrupt<Uart> {
/// This function will not disable the RX interrupts, so you don't need to call any other
/// API after calling this function to continue emptying the FIFO. RX errors are handled
/// as partial errors and are returned as part of the [IrqResult].
pub fn irq_handler(&mut self, buf: &mut [u8; 16]) -> IrqResult {
pub fn on_interrupt(&mut self, buf: &mut [u8; 16]) -> IrqResult {
let mut result = IrqResult::default();
let irq_end = self.uart().irq_end().read();
@ -1203,15 +1171,10 @@ impl<Uart: Instance> RxWithInterrupt<Uart> {
// Timeout, empty the FIFO completely.
if irq_end.irq_rx_to().bit_is_set() {
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;
}
// 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;
}
}
@ -1239,7 +1202,7 @@ impl<Uart: Instance> RxWithInterrupt<Uart> {
/// If passed buffer is equal to or larger than the specified maximum length, an
/// [BufferTooShortError] will be returned. Other RX errors are treated as partial errors
/// and returned inside the [IrqResultMaxSizeOrTimeout] structure.
pub fn irq_handler_max_size_or_timeout_based(
pub fn on_interrupt_max_size_or_timeout_based(
&mut self,
context: &mut IrqContextTimeoutOrMaxSize,
buf: &mut [u8],
@ -1288,12 +1251,13 @@ impl<Uart: Instance> RxWithInterrupt<Uart> {
if context.rx_idx == context.max_len {
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;
// 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,
}
}
self.irq_completion_handler_max_size_timeout(&mut result, context);
@ -1312,29 +1276,6 @@ 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

@ -28,7 +28,7 @@ use va416xx::uart0 as uart_base;
use crate::enable_nvic_interrupt;
use super::{Bank, Instance, Rx, RxError, UartErrors};
use super::{Bank, Instance, Rx, UartErrors};
static UART_RX_WAKERS: [AtomicWaker; 3] = [const { AtomicWaker::new() }; 3];
static RX_READ_ACTIVE: [AtomicBool; 3] = [const { AtomicBool::new(false) }; 3];
@ -48,7 +48,7 @@ impl RxFuture {
}
impl Future for RxFuture {
type Output = Result<(), RxError>;
type Output = Result<(), Infallible>;
fn poll(
self: core::pin::Pin<&mut Self>,

View File

@ -4,12 +4,8 @@
//!
//! - [Watchdog simple example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/wdt.rs)
use crate::time::Hertz;
use crate::{
clock::{Clocks, PeripheralSelect},
pac,
prelude::SyscfgExt,
};
use crate::{disable_nvic_interrupt, enable_nvic_interrupt};
use crate::{clock::Clocks, pac};
use crate::{disable_nvic_interrupt, enable_nvic_interrupt, PeripheralSelect, SyscfgExt as _};
pub const WDT_UNLOCK_VALUE: u32 = 0x1ACC_E551;

View File

@ -16,7 +16,7 @@ categories = ["embedded", "no-std", "hardware-support"]
cortex-m = "0.7"
vcell = "0.1.3"
defmt = { version = "0.3", optional = true }
defmt = { version = "1", optional = true }
critical-section = { version = "1", optional = true }
[dependencies.cortex-m-rt]

View File

@ -8,6 +8,19 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
# [unreleased]
# [v0.1.2] 2025-03-07
- Bump allowed HAL version to v0.5
# [v0.1.1] 2025-02-18
- Bump allowed HAL version to v0.4
# [v0.1.0] 2024-10-01
- Initial release
[unreleased]: https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/compare/vorago-peb1-v0.1.2...HEAD
[v0.1.2]: https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/compare/vorago-peb1-v0.1.1...vorago-peb1-v0.1.2
[v0.1.1]: https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/compare/vorago-peb1-v0.1.0...vorago-peb1-v0.1.1
[v0.1.0]: https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/tag/vorago-peb1-v0.1.0

View File

@ -1,6 +1,6 @@
[package]
name = "vorago-peb1"
version = "0.1.0"
version = "0.1.2"
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
edition = "2021"
description = "Board Support Crate for the Vorago PEB1 development board"
@ -14,15 +14,9 @@ categories = ["embedded", "no-std", "hardware-support"]
cortex-m = "0.7"
cortex-m-rt = "0.7"
embedded-hal = "1"
lis2dh12 = { version = "0.7", features = ["out_f32"] }
[dependencies.va416xx-hal]
path = "../va416xx-hal"
features = ["va41630"]
version = ">=0.3, <=0.4"
[dependencies.lis2dh12]
version = "0.7"
features = ["out_f32"]
va416xx-hal = { version = ">=0.3, <=0.5", features = ["va41630"] }
[features]
rt = ["va416xx-hal/rt"]