11 Commits

Author SHA1 Message Date
Robin Mueller
72d89790c3 typo 2025-08-01 11:55:44 +02:00
Robin Mueller
3ab3c2fe13 readme fix 2025-08-01 11:55:02 +02:00
Robin Mueller
a33f71217c add pwm test app 2025-08-01 11:37:37 +02:00
7035780beb Merge pull request 'prepare PAC release' (#72) from prep-pac-release into main
Reviewed-on: #72
2025-07-22 12:14:47 +02:00
Robin Mueller
e82effb2e4 prepare PAC release 2025-07-22 12:14:17 +02:00
6a886651fc Merge pull request 'bump all dependencies' (#71) from dependency-update into main
Reviewed-on: #71
2025-07-22 11:24:15 +02:00
Robin Mueller
191642d76a dependency update 2025-07-22 11:16:08 +02:00
c1e4935d22 Merge pull request 'dependency update' (#70) from dependency-update into main
Reviewed-on: #70
2025-07-22 10:23:57 +02:00
Robin Mueller
e692f922a4 dependency update 2025-07-22 00:24:54 +02:00
ce02b38ff6 Merge pull request 'CAN Support' (#69) from can-support into main
Reviewed-on: #69
2025-05-13 16:55:10 +02:00
993a0bcd3e CAN peripheral support 2025-05-13 16:52:35 +02:00
22 changed files with 2367 additions and 36 deletions

View File

@@ -61,6 +61,16 @@ You can then adapt the files in `.vscode` to your needs.
You can use CLI or VS Code for flashing, running and debugging. In any case, take You can use CLI or VS Code for flashing, running and debugging. In any case, take
care of installing the pre-requisites first. care of installing the pre-requisites first.
### Pre-Requisites
1. [SEGGER J-Link tools](https://www.segger.com/downloads/jlink/) installed
2. [Rust `thumbv7em-none-eaibhf` toolchain](https://doc.rust-lang.org/nightly/rustc/platform-support/thumbv7em-none-eabi.html).
Use the following command to install it:
```sh
rustup target add thumbv7em-none-eabihf
```
### Using CLI with probe-rs ### Using CLI with probe-rs
Install [probe-rs](https://probe.rs/docs/getting-started/installation/) first. Install [probe-rs](https://probe.rs/docs/getting-started/installation/) first.
@@ -80,15 +90,6 @@ available for persistent flashing.
Runner configuration is available in the `.cargo/def-config.toml` file to use `probe-rs` for Runner configuration is available in the `.cargo/def-config.toml` file to use `probe-rs` for
convenience. `probe-rs` is also able to process and display `defmt` strings directly. convenience. `probe-rs` is also able to process and display `defmt` strings directly.
### Pre-Requisites
1. [SEGGER J-Link tools](https://www.segger.com/downloads/jlink/) installed
2. [gdb-multiarch](https://packages.debian.org/sid/gdb-multiarch) or similar
cross-architecture debugger installed. All commands here assume `gdb-multiarch`.
### Using CLI
### Using VS Code ### Using VS Code
Assuming a working debug connection to your VA416xx board, you can debug using VS Code with Assuming a working debug connection to your VA416xx board, you can debug using VS Code with

View File

@@ -6,7 +6,7 @@ edition = "2021"
[dependencies] [dependencies]
cortex-m = "0.7" cortex-m = "0.7"
cortex-m-rt = "0.7" cortex-m-rt = "0.7"
defmt-rtt = "0.4" defmt-rtt = "1"
defmt = "1" defmt = "1"
panic-probe = { version = "1", features = ["defmt"] } panic-probe = { version = "1", features = ["defmt"] }
crc = "3" crc = "3"

View File

@@ -4,6 +4,7 @@ version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
cortex-m = "0.7"
cortex-m-rt = "0.7" cortex-m-rt = "0.7"
cfg-if = "1" cfg-if = "1"
embedded-io = "0.6" embedded-io = "0.6"
@@ -11,14 +12,15 @@ embedded-hal-async = "1"
embedded-io-async = "0.6" embedded-io-async = "0.6"
heapless = "0.8" heapless = "0.8"
defmt-rtt = "0.4" defmt-rtt = "1"
defmt = "1" defmt = "1"
panic-probe = { version = "1", features = ["defmt"] } panic-probe = { version = "1", features = ["print-defmt"] }
static_cell = "2" static_cell = "2"
critical-section = "1" critical-section = "1"
ringbuf = { version = "0.4", default-features = false } ringbuf = { version = "0.4", default-features = false }
embassy-sync = "0.6" nb = "1"
embassy-sync = "0.7"
embassy-time = "0.4" embassy-time = "0.4"
embassy-executor = { version = "0.7", features = [ embassy-executor = { version = "0.7", features = [
"arch-cortex-m", "arch-cortex-m",

View File

@@ -0,0 +1,239 @@
#![no_std]
#![no_main]
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
// Import panic provider.
use panic_probe as _;
// Import logger.
use defmt_rtt as _;
use embassy_example::EXTCLK_FREQ;
use embassy_executor::Spawner;
use va416xx_hal::can::asynch::{on_interrupt_can, CanTxAsync};
use va416xx_hal::can::{
Can, CanFrame, CanFrameNormal, CanFrameRtr, CanId, CanRx, CanTx, ClockConfig,
};
use va416xx_hal::clock::ClockConfigurator;
use va416xx_hal::pac::{self, interrupt};
use va416xx_hal::time::Hertz;
use va416xx_hal::{can, prelude::*};
const STANDARD_ID_0: can::StandardId = can::StandardId::new(0x42).unwrap();
const STANDARD_ID_1: can::StandardId = can::StandardId::new(0x5).unwrap();
const EXTENDED_ID_0: can::ExtendedId = can::ExtendedId::new(0x10).unwrap();
// Declare a bounded channel of 3 u32s.
static CAN_RX_CHANNEL: embassy_sync::channel::Channel<
CriticalSectionRawMutex,
(usize, CanFrame),
3,
> = embassy_sync::channel::Channel::<CriticalSectionRawMutex, (usize, CanFrame), 3>::new();
#[embassy_executor::main]
async fn main(_spawner: Spawner) {
defmt::println!("-- VA416xx CAN Demo --");
let dp = pac::Peripherals::take().unwrap();
// Initialize the systick interrupt & obtain the token to prove that we did
// Use the external clock connected to XTAL_N.
let clocks = ClockConfigurator::new(dp.clkgen)
.xtal_n_clk_with_src_freq(Hertz::from_raw(EXTCLK_FREQ))
.freeze()
.unwrap();
// Safety: Only called once here.
va416xx_embassy::init(dp.tim15, dp.tim14, &clocks);
defmt::info!("creating CAN peripheral driver");
defmt::info!("clocks: {}", clocks);
let clk_config = ClockConfig::from_bitrate_and_segments(&clocks, 250.kHz(), 14, 5, 4)
.expect("CAN clock config error");
let mut can = Can::new(dp.can0, clk_config);
can.modify_control(|mut val| {
val.set_loopback(true);
val.set_ignore_ack(true);
val.set_internal(true);
val.set_bufflock(true);
val.set_diag_enable(true);
val
});
can.set_global_mask_for_exact_id_match_with_rtr_masked();
can.set_base_mask_for_all_match();
can.enable();
let mut channels = can.take_channels().unwrap();
// Transmit channel.
let mut tx = CanTx::new(channels.take(0).unwrap(), None);
// Base channel which has dedicated mask.
let mut rx_dedicated = CanRx::new(channels.take(1).unwrap());
// Base channel which has dedicated mask.
let mut rx_base = CanRx::new(channels.take(14).unwrap());
rx_base.configure_for_reception();
defmt::info!("Running blocking examples");
send_and_receive_on_dedicated_channel(&mut can, &mut tx, &mut rx_dedicated);
send_and_receive_rtr_on_dedicated_channel(&mut can, &mut tx, &mut rx_dedicated);
send_extended_on_base_channel(&mut can, &mut tx, &mut rx_base);
defmt::info!("Running non-blocking (asycnhronous) examples");
non_blocking_example(&mut can, &mut rx_dedicated, &mut rx_base).await;
defmt::info!("Non-blocking (asycnhronous) examples done");
loop {
cortex_m::asm::nop();
}
}
fn send_and_receive_on_dedicated_channel(can: &mut Can, tx: &mut CanTx, rx_dedicated: &mut CanRx) {
let send_data = &[1, 2, 3, 4];
let sent_frame =
CanFrame::Normal(CanFrameNormal::new(can::Id::Standard(STANDARD_ID_0), send_data).unwrap());
defmt::info!(
"sending CAN frame with ID {:#X} and data {}",
STANDARD_ID_0.as_raw(),
send_data
);
rx_dedicated.configure_for_reception_with_standard_id(STANDARD_ID_0, false);
tx.transmit_frame(sent_frame).unwrap();
// Await frame transmission completion.
nb::block!(tx.transfer_done()).unwrap();
check_and_handle_errors(can);
let received_frame = nb::block!(rx_dedicated.receive(true)).expect("invalid CAN rx state");
check_and_handle_errors(can);
assert_eq!(received_frame, sent_frame);
if let CanFrame::Normal(can_frame_normal) = received_frame {
if let can::Id::Standard(standard_id) = can_frame_normal.id() {
defmt::info!(
"received CAN frame with ID {:#X} and data {}",
standard_id.as_raw(),
can_frame_normal.data()
);
} else {
panic!("unexpected CAN extended frame ID");
}
} else {
defmt::error!("received unexpected CAN remote frame");
}
}
fn send_and_receive_rtr_on_dedicated_channel(
can: &mut Can,
tx: &mut CanTx,
rx_dedicated: &mut CanRx,
) {
let rtr_frame = CanFrame::Rtr(CanFrameRtr::new(can::Id::Standard(STANDARD_ID_1), 0));
// RTR bit is masked, so the setting should not matter.
rx_dedicated.configure_for_reception_with_standard_id(STANDARD_ID_1, false);
tx.transmit_frame(rtr_frame).unwrap();
// Await frame transmission completion.
nb::block!(tx.remote_transfer_done_with_tx_reconfig()).unwrap();
check_and_handle_errors(can);
let received_frame = nb::block!(rx_dedicated.receive(true)).expect("invalid CAN rx state");
check_and_handle_errors(can);
assert_eq!(received_frame, rtr_frame);
if let CanFrame::Rtr(can_frame_rtr) = received_frame {
if let can::Id::Standard(standard_id) = can_frame_rtr.id() {
defmt::info!("received CAN RTR frame with ID {:#X}", standard_id.as_raw(),);
} else {
panic!("unexpected CAN extended frame ID");
}
} else {
defmt::error!("received unexpected CAN data frame");
}
}
fn check_and_handle_errors(can: &mut Can) {
let err_counter = can.read_error_counters();
if err_counter.transmit() > 0 || err_counter.receive() > 0 {
defmt::warn!(
"error count tx {}, error count rx {}",
err_counter.transmit(),
err_counter.receive()
);
let diag = can.read_error_diagnostics();
defmt::warn!("EFID: {}, EBID: {}", diag.efid(), diag.ebid());
}
}
fn send_extended_on_base_channel(can: &mut Can, tx: &mut CanTx, rx: &mut CanRx) {
let send_data = &[4, 3, 2, 1];
let sent_frame =
CanFrame::Normal(CanFrameNormal::new(can::Id::Extended(EXTENDED_ID_0), send_data).unwrap());
tx.transmit_frame(sent_frame).unwrap();
// Await frame transmission completion.
nb::block!(tx.transfer_done()).unwrap();
check_and_handle_errors(can);
let received_frame = nb::block!(rx.receive(true)).expect("invalid CAN rx state");
check_and_handle_errors(can);
assert_eq!(sent_frame, received_frame);
if let CanFrame::Normal(can_frame_normal) = received_frame {
if let can::Id::Extended(extended_id) = can_frame_normal.id() {
defmt::info!(
"received CAN frame with ID {:#X} and data {}",
extended_id.as_raw(),
can_frame_normal.data()
);
} else {
panic!("unexpected CAN extended frame ID");
}
} else {
defmt::error!("received unexpected CAN data frame");
}
}
async fn non_blocking_example(can: &mut Can, rx_dedicated: &mut CanRx, rx_base: &mut CanRx) {
let mut tx_async = CanTxAsync::new(can);
// Enable interrupts for RX channels.
rx_dedicated.enable_interrupt(true);
rx_base.enable_interrupt(true);
// For asynchronous mode, all TX channels needs to be configured explicitely. Configuring more
// channels allows multiple active transfers when using the async API.
tx_async.configure_channel(0).unwrap();
let send_data = &[1, 2, 3, 4];
let send_frame =
CanFrame::Normal(CanFrameNormal::new(can::Id::Standard(STANDARD_ID_0), send_data).unwrap());
let fut = tx_async.start_transmit(send_frame).unwrap();
fut.await;
let (ch_idx, frame) = CAN_RX_CHANNEL.receive().await;
assert_eq!(send_frame, frame);
// Received on base channel.
assert_eq!(ch_idx, 14);
if let CanFrame::Normal(can_frame_normal) = frame {
if let can::Id::Standard(standard_id) = can_frame_normal.id() {
defmt::info!(
"received CAN frame with ID {:#X} and data {}",
standard_id.as_raw(),
can_frame_normal.data()
);
} else {
panic!("unexpected CAN extended frame ID");
}
} else {
defmt::error!("received unexpected CAN remote frame");
}
}
#[interrupt]
#[allow(non_snake_case)]
fn CAN0() {
match on_interrupt_can(CanId::Can0, false).unwrap() {
can::asynch::InterruptResult::NoInterrupt => {
defmt::warn!("unexpected interrupt on CAN0");
}
can::asynch::InterruptResult::ReceivedFrame {
channel_index,
frame,
} => {
CAN_RX_CHANNEL.try_send((channel_index, frame)).unwrap();
}
can::asynch::InterruptResult::TransmissionEvent { channel_index, id } => {
defmt::info!(
"transmission event on channel {} with event ID {}",
channel_index,
id
);
}
}
}

View File

@@ -5,7 +5,7 @@ edition = "2021"
[dependencies] [dependencies]
cortex-m = { version = "0.7", features = ["critical-section-single-core"] } cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
defmt-rtt = "0.4" defmt-rtt = "1"
defmt = "1" defmt = "1"
panic-probe = { version = "1", features = ["defmt"] } panic-probe = { version = "1", features = ["defmt"] }

View File

@@ -7,7 +7,7 @@ edition = "2021"
cortex-m = { version = "0.7", features = ["critical-section-single-core"] } cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
cortex-m-rt = "0.7" cortex-m-rt = "0.7"
critical-section = "1" critical-section = "1"
defmt-rtt = "0.4" defmt-rtt = "1"
defmt = "1" defmt = "1"
panic-probe = { version = "1", features = ["defmt"] } panic-probe = { version = "1", features = ["defmt"] }
embedded-hal = "1" embedded-hal = "1"

View File

@@ -0,0 +1,44 @@
//! Simple PWM example
//!
//! Outputs a PWM waveform on pin PG2.
#![no_main]
#![no_std]
use cortex_m_rt::entry;
use embedded_hal::pwm::SetDutyCycle;
// Import panic provider.
use panic_probe as _;
// Import logger.
use defmt_rtt as _;
use simple_examples::peb1;
use va416xx_hal::{
clock::ClockConfigurator,
pac,
pins::PinsG,
prelude::*,
pwm::{get_duty_from_percent, PwmPin},
};
#[entry]
fn main() -> ! {
defmt::println!("-- VA108xx PWM example application--");
let dp = pac::Peripherals::take().unwrap();
// Use the external clock connected to XTAL_N.
let clocks = ClockConfigurator::new(dp.clkgen)
.xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ)
.freeze()
.unwrap();
let pinsg = PinsG::new(dp.portg);
let mut pwm = PwmPin::new(pinsg.pg2, dp.tim9, &clocks, 10.kHz()).unwrap();
//let mut delay_timer = CountdownTimer::new(dp.tim0, &clocks);
//let mut current_duty_cycle = 0.0;
pwm.set_duty_cycle(get_duty_from_percent(0.5)).unwrap();
pwm.enable();
// Delete type information, increased code readibility for the rest of the code
loop {
cortex_m::asm::nop();
}
}

View File

@@ -6,15 +6,15 @@ edition = "2021"
[dependencies] [dependencies]
cortex-m = "0.7" cortex-m = "0.7"
embedded-io = "0.6" embedded-io = "0.6"
defmt-rtt = "0.4" defmt-rtt = "1"
defmt = "1" defmt = "1"
panic-probe = { version = "1", features = ["defmt"] } panic-probe = { version = "1", features = ["defmt"] }
static_cell = "2" static_cell = "2"
satrs = { version = "0.3.0-alpha.0", default-features = false } satrs = { version = "0.3.0-alpha.1", default-features = false }
ringbuf = { version = "0.4", default-features = false } ringbuf = { version = "0.4", default-features = false }
once_cell = { version = "1", default-features = false, features = ["critical-section"] } once_cell = { version = "1", default-features = false, features = ["critical-section"] }
spacepackets = { version = "0.13", default-features = false, features = ["defmt"] } spacepackets = { version = "0.15", default-features = false, features = ["defmt"] }
cobs = { version = "0.3", default-features = false } cobs = { version = "0.4", default-features = false }
va416xx-hal = { version = "0.5", features = ["va41630", "defmt"], path = "../va416xx-hal" } va416xx-hal = { version = "0.5", features = ["va41630", "defmt"], path = "../va416xx-hal" }

View File

@@ -347,7 +347,7 @@ mod app {
defmt::warn!("PUS TC error: {}", pus_tc.unwrap_err()); defmt::warn!("PUS TC error: {}", pus_tc.unwrap_err());
return; return;
} }
let (pus_tc, _) = pus_tc.unwrap(); let pus_tc = pus_tc.unwrap();
let mut write_and_send = |tm: &PusTmCreator| { let mut write_and_send = |tm: &PusTmCreator| {
let written_size = tm.write_to_bytes(cx.local.verif_buf).unwrap(); let written_size = tm.write_to_bytes(cx.local.verif_buf).unwrap();
cx.shared.tm_prod.lock(|prod| { cx.shared.tm_prod.lock(|prod| {
@@ -356,18 +356,18 @@ mod app {
.push_slice(&cx.local.verif_buf[0..written_size]); .push_slice(&cx.local.verif_buf[0..written_size]);
}); });
}; };
let token = cx.local.verif_reporter.add_tc(&pus_tc); let request_id = VerificationReportCreator::read_request_id_from_tc(&pus_tc);
let (tm, accepted_token) = cx let tm = cx
.local .local
.verif_reporter .verif_reporter
.acceptance_success(cx.local.src_data_buf, token, 0, 0, &[]) .acceptance_success(cx.local.src_data_buf, &request_id, 0, 0, &[])
.expect("acceptance success failed"); .expect("acceptance success failed");
write_and_send(&tm); write_and_send(&tm);
let (tm, started_token) = cx let tm = cx
.local .local
.verif_reporter .verif_reporter
.start_success(cx.local.src_data_buf, accepted_token, 0, 0, &[]) .start_success(cx.local.src_data_buf, &request_id, 0, 0, &[])
.expect("acceptance success failed"); .expect("acceptance success failed");
write_and_send(&tm); write_and_send(&tm);
@@ -387,7 +387,7 @@ mod app {
let tm = cx let tm = cx
.local .local
.verif_reporter .verif_reporter
.completion_success(cx.local.src_data_buf, started_token, 0, 0, &[]) .completion_success(cx.local.src_data_buf, &request_id, 0, 0, &[])
.expect("completion success failed"); .expect("completion success failed");
write_and_send(&tm); write_and_send(&tm);
}; };
@@ -405,7 +405,7 @@ mod app {
let tm = cx let tm = cx
.local .local
.verif_reporter .verif_reporter
.completion_success(cx.local.src_data_buf, started_token, 0, 0, &[]) .completion_success(cx.local.src_data_buf, &request_id, 0, 0, &[])
.expect("completion success failed"); .expect("completion success failed");
write_and_send(&tm); write_and_send(&tm);
} else if pus_tc.service() == PusServiceId::MemoryManagement as u8 { } else if pus_tc.service() == PusServiceId::MemoryManagement as u8 {
@@ -414,7 +414,7 @@ mod app {
.verif_reporter .verif_reporter
.step_success( .step_success(
cx.local.src_data_buf, cx.local.src_data_buf,
&started_token, &request_id,
0, 0,
0, 0,
&[], &[],
@@ -460,7 +460,7 @@ mod app {
let tm = cx let tm = cx
.local .local
.verif_reporter .verif_reporter
.completion_success(cx.local.src_data_buf, started_token, 0, 0, &[]) .completion_success(cx.local.src_data_buf, &request_id, 0, 0, &[])
.expect("completion success failed"); .expect("completion success failed");
write_and_send(&tm); write_and_send(&tm);
defmt::info!("NVM operation done"); defmt::info!("NVM operation done");

1
scripts/can-clk-calc/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/target

View File

@@ -0,0 +1,9 @@
[workspace]
[package]
name = "can-clk-calc"
version = "0.1.0"
edition = "2024"
[dependencies]
va416xx-hal = { path = "../../va416xx-hal", features = ["alloc", "revb"], default-features = false }

View File

@@ -0,0 +1,14 @@
use va416xx_hal::can::calculate_all_viable_clock_configs;
use va416xx_hal::time::Hertz;
fn main() {
let cfgs = calculate_all_viable_clock_configs(
Hertz::from_raw(20_000_000),
Hertz::from_raw(250_000),
0.75,
)
.unwrap();
for cfg in &cfgs {
println!("Config: {:#?}", cfg);
}
}

View File

@@ -11,7 +11,7 @@ keywords = ["no-std", "hal", "cortex-m", "vorago", "va416xx"]
categories = ["aerospace", "embedded", "no-std", "hardware-support"] categories = ["aerospace", "embedded", "no-std", "hardware-support"]
[dependencies] [dependencies]
vorago-shared-periphs = { git = "https://egit.irs.uni-stuttgart.de/rust/vorago-shared-periphs.git", features = ["vor4x"] } vorago-shared-periphs = { git = "https://egit.irs.uni-stuttgart.de/rust/vorago-shared-periphs.git", rev = "c8e475cbba820a4b235b46f3d284e23d72396855", features = ["vor4x"] }
va416xx-hal = { path = "../va416xx-hal" } va416xx-hal = { path = "../va416xx-hal" }
[features] [features]

View File

@@ -13,8 +13,11 @@ categories = ["embedded", "no-std", "hardware-support"]
[dependencies] [dependencies]
cortex-m = { version = "0.7", features = ["critical-section-single-core"] } cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
va416xx = { version = "0.4", features = ["critical-section"], default-features = false } va416xx = { version = "0.4", features = ["critical-section"], default-features = false }
vorago-shared-periphs = { git = "https://egit.irs.uni-stuttgart.de/rust/vorago-shared-periphs.git", features = ["vor4x"] } derive-mmio = { git = "https://github.com/knurling-rs/derive-mmio.git", version = "0.6" }
static_assertions = "1.1"
vorago-shared-periphs = { git = "https://egit.irs.uni-stuttgart.de/rust/vorago-shared-periphs.git", rev = "c8e475cbba820a4b235b46f3d284e23d72396855", features = ["vor4x"] }
libm = "0.2"
nb = "1" nb = "1"
embedded-hal = "1" embedded-hal = "1"
num_enum = { version = "0.7", default-features = false } num_enum = { version = "0.7", default-features = false }
@@ -22,13 +25,16 @@ bitflags = "2"
bitbybit = "1.3" bitbybit = "1.3"
arbitrary-int = "1.3" arbitrary-int = "1.3"
fugit = "0.3" fugit = "0.3"
embedded-can = "0.4"
embassy-sync = "0.7"
thiserror = { version = "2", default-features = false } thiserror = { version = "2", default-features = false }
defmt = { version = "0.3", optional = true } defmt = { version = "1", optional = true }
[features] [features]
default = ["rt", "revb"] default = ["rt", "revb"]
rt = ["va416xx/rt"] rt = ["va416xx/rt"]
alloc = []
defmt = ["dep:defmt", "fugit/defmt", "vorago-shared-periphs/defmt"] defmt = ["dep:defmt", "fugit/defmt", "vorago-shared-periphs/defmt"]
va41630 = ["device-selected"] va41630 = ["device-selected"]

View File

@@ -0,0 +1,311 @@
use core::{
future::Future,
sync::atomic::{AtomicU8, Ordering},
};
use crate::can::regs::BufferState;
use super::{
regs::{DiagnosticRegister, InterruptClear, MmioCan, StatusPending},
CanChannelLowLevel, CanFrame, CanId, InvalidBufferIndexError,
};
#[derive(Debug)]
pub enum TxChannelState {
Unconfigured = 0,
Idle = 1,
TxDataFrame = 2,
TxRtrTransmission = 3,
TxRtrReception = 4,
Finished = 5,
}
static TX_STATES: [AtomicU8; 15] = [const { AtomicU8::new(0) }; 15];
static TX_WAKERS: [embassy_sync::waitqueue::AtomicWaker; 15] =
[const { embassy_sync::waitqueue::AtomicWaker::new() }; 15];
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum TxEventId {
/// Buffer state went from [BufferState::TxOnce] to [BufferState::TxNotActive].
TxDataFrame,
/// Buffer state went from [BufferState::TxOnce] to [BufferState::TxNotActive] for a remote
/// frame (RTR bit set). Channel might be in reception mode [BufferState::RxReady] now.
TxRemoteFrame,
/// A response to a remote frame was performed successfully, and the buffer state went from
/// [BufferState::TxOnceRtr] to [BufferState::TxRtr].
RtrResponse,
/// A remote frame was received and the transmission of a response frame was scheduled. The
/// buffer state went from [BufferState::TxRtr] to [BufferState::TxOnceRtr].
TransmitScheduling,
}
#[derive(Debug)]
pub enum InterruptResult {
NoInterrupt,
ReceivedFrame {
channel_index: usize,
frame: CanFrame,
},
TransmissionEvent {
channel_index: usize,
id: TxEventId,
},
}
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum InterruptError {
UnexpectedError,
InvalidInterruptId(StatusPending),
InvalidStatus(u8),
UnexpectedState(BufferState),
CanError(DiagnosticRegister),
}
/// This interrupt handler allow asynchronous transmission and reception of CAN frames.
///
/// This handler will re-configure a channel to [BufferState::RxReady] after successfull reception
/// of a frame without disabling the interrupts, assuming that the user wants to immediately
/// receive the next frame on the channel.
/// The user should re-configure the buffer state to [BufferState::RxNotActive] if the reception
/// should be disabled.
///
/// The handler will re-configure a channel to [BufferState::TxNotActive] instead of
/// [BufferState::RxReady] if the completed frame transmission was a remote frame and after
/// successfully having received a response to that remote frame. The assumption is that this
/// channel is used to request more frames. If the argument `reconfigure_tx_rtr_to_tx` is set to
/// true, the channel will automatically be configured back to [BufferState::TxNotActive] with
/// interrupts for the respective channel disabled after transmission of a remote frame.
///
/// The handler will not disable the interrupts realted to the TX RTR and TX RTR ONCE auto-response
/// functionality of the CAN peripheral. It will report the event type to the caller via the
/// [TxEventId] enumeration.
pub fn on_interrupt_can(
id: CanId,
reconfigure_tx_rtr_to_tx: bool,
) -> Result<InterruptResult, InterruptError> {
let mut regs = unsafe { id.steal_regs() };
// Check if any interrupts are enabled.
let ie = regs.read_ien();
if ie.raw_value() == 0 {
return Ok(InterruptResult::NoInterrupt);
}
let pending_id = regs.read_status_pending();
if pending_id.interrupt_id().is_none() {
regs.write_iclr(InterruptClear::new_with_raw_value(0xFFFF_FFFF));
return Err(InterruptError::InvalidInterruptId(pending_id));
}
match pending_id.interrupt_id().unwrap() {
super::regs::CanInterruptId::None => Ok(InterruptResult::NoInterrupt),
super::regs::CanInterruptId::Error => Err(InterruptError::CanError(regs.read_diag())),
super::regs::CanInterruptId::Buffer(idx) => {
let mut channel = unsafe { CanChannelLowLevel::steal_unchecked(id, idx) };
let status = channel.read_state();
if status.is_err() {
let mut clr = InterruptClear::new_with_raw_value(0);
clr.set_buffer(idx, true);
regs.write_iclr(clr);
regs.modify_ien(|mut val| {
val.set_buffer(idx, false);
val
});
return Err(InterruptError::InvalidStatus(status.unwrap_err()));
}
let buf_state = status.unwrap();
if buf_state == BufferState::TxNotActive {
let tx_state = TX_STATES[idx].load(Ordering::Relaxed);
clear_and_disable_interrupt(&mut regs, idx);
// Handle reading frames, updating states etc.
if tx_state == TxChannelState::TxDataFrame as u8 {
// Transmission complete.
TX_STATES[idx].store(TxChannelState::Finished as u8, Ordering::Relaxed);
TX_WAKERS[idx].wake();
return Ok(InterruptResult::TransmissionEvent {
channel_index: idx,
id: TxEventId::TxDataFrame,
});
}
}
if buf_state == BufferState::RxReady {
let tx_state = TX_STATES[idx].load(Ordering::Relaxed);
if tx_state == TxChannelState::TxRtrTransmission as u8 {
if reconfigure_tx_rtr_to_tx {
channel.write_state(BufferState::TxNotActive);
clear_and_disable_interrupt(&mut regs, idx);
// Transmission complete.
TX_STATES[idx].store(TxChannelState::Idle as u8, Ordering::Relaxed);
} else {
// Do not disable interrupt, channel is now used to receive the frame.
clear_interrupt(&mut regs, idx);
// Transmission complete.
TX_STATES[idx]
.store(TxChannelState::TxRtrReception as u8, Ordering::Relaxed);
}
TX_WAKERS[idx].wake();
return Ok(InterruptResult::TransmissionEvent {
channel_index: idx,
id: TxEventId::TxRemoteFrame,
});
}
}
if buf_state == BufferState::RxOverrun || buf_state == BufferState::RxFull {
let tx_state = TX_STATES[idx].load(Ordering::Relaxed);
// Do not disable interrupt and assume continuous reception.
clear_interrupt(&mut regs, idx);
let frame = channel.read_frame_unchecked();
if tx_state == TxChannelState::TxRtrReception as u8 {
// Reception of response complete. We can release the channel for TX (or RX)
// usage again.
TX_STATES[idx].store(TxChannelState::Idle as u8, Ordering::Relaxed);
channel.write_state(BufferState::TxNotActive);
} else {
// Assume continous reception of frames.
channel.write_state(BufferState::RxReady);
}
return Ok(InterruptResult::ReceivedFrame {
channel_index: idx,
frame,
});
}
if buf_state == BufferState::TxRtr {
// Do not disable interrupt and assume continuous transmission.
clear_interrupt(&mut regs, idx);
return Ok(InterruptResult::TransmissionEvent {
channel_index: idx,
id: TxEventId::RtrResponse,
});
}
if buf_state == BufferState::TxOnceRtr {
// Do not disable interrupt and assume continuous transmission.
clear_interrupt(&mut regs, idx);
return Ok(InterruptResult::TransmissionEvent {
channel_index: idx,
id: TxEventId::TransmitScheduling,
});
}
Err(InterruptError::UnexpectedState(buf_state))
}
}
}
#[inline(always)]
fn clear_interrupt(regs: &mut MmioCan<'static>, idx: usize) {
let mut clr = InterruptClear::new_with_raw_value(0);
clr.set_buffer(idx, true);
regs.write_iclr(clr);
}
#[inline(always)]
fn clear_and_disable_interrupt(regs: &mut MmioCan<'static>, idx: usize) {
clear_interrupt(regs, idx);
regs.modify_ien(|mut val| {
val.set_buffer(idx, false);
val
});
}
#[derive(Debug, thiserror::Error)]
#[error("all channels are unconfigured, none available for TX")]
pub struct AllTxChannelsUnconfiguredError;
pub struct CanTxFuture(usize);
impl Future for CanTxFuture {
type Output = ();
fn poll(
self: core::pin::Pin<&mut Self>,
cx: &mut core::task::Context<'_>,
) -> core::task::Poll<Self::Output> {
TX_WAKERS[self.0].register(cx.waker());
if TX_STATES[self.0].load(Ordering::Relaxed) == TxChannelState::Finished as u8 {
TX_STATES[self.0].store(TxChannelState::Idle as u8, Ordering::Relaxed);
return core::task::Poll::Ready(());
}
core::task::Poll::Pending
}
}
impl CanTxFuture {
pub fn new(frame: CanFrame) -> nb::Result<Self, AllTxChannelsUnconfiguredError> {
let mut channel_is_free = [false; 15];
let mut all_channels_unused = true;
for (idx, state) in TX_STATES.iter().enumerate() {
let state = state.load(Ordering::Relaxed);
if state == TxChannelState::Idle as u8 {
channel_is_free[idx] = true;
}
if state != TxChannelState::Unconfigured as u8 {
all_channels_unused = false;
}
}
if channel_is_free.iter().all(|&x| !x) {
return Err(nb::Error::WouldBlock);
}
if all_channels_unused {
return Err(nb::Error::Other(AllTxChannelsUnconfiguredError));
}
let free_channel_id = channel_is_free.iter().position(|&x| x).unwrap();
let mut channel =
unsafe { CanChannelLowLevel::steal_unchecked(CanId::Can0, free_channel_id) };
TX_STATES[free_channel_id].store(TxChannelState::TxDataFrame as u8, Ordering::Relaxed);
channel.write_state(BufferState::TxNotActive);
channel.transmit_frame_unchecked(frame);
channel.clear_interrupt();
channel.enable_interrupt(true);
channel.enable_error_interrupt(true);
Ok(CanTxFuture(free_channel_id))
}
}
#[derive(Debug, thiserror::Error)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum ChannelConfigError {
#[error("channel is busy")]
Busy,
#[error("invalid offset: {0}")]
Offset(#[from] InvalidBufferIndexError),
}
pub struct CanTxAsync;
impl CanTxAsync {
pub fn new(can: &mut super::Can) -> Self {
can.clear_interrupts();
can.enable_nvic_interrupt();
CanTxAsync
}
pub fn configure_channel(&mut self, channel_idx: usize) -> Result<(), ChannelConfigError> {
if channel_idx >= TX_STATES.len() {
return Err(ChannelConfigError::Offset(InvalidBufferIndexError(
channel_idx,
)));
}
let state = TX_STATES[channel_idx].load(Ordering::Relaxed);
if state != TxChannelState::Idle as u8 && state != TxChannelState::Unconfigured as u8 {
return Err(ChannelConfigError::Busy);
}
TX_STATES[channel_idx].store(TxChannelState::Idle as u8, Ordering::Relaxed);
Ok(())
}
/// Start a transmission and returns the future which can be polled to completion.
pub fn start_transmit(
&mut self,
frame: CanFrame,
) -> nb::Result<CanTxFuture, AllTxChannelsUnconfiguredError> {
CanTxFuture::new(frame)
}
/// Calls [Self::start_transmit] and awaits the returned future to completion immediately.
pub async fn transmit(
&mut self,
frame: CanFrame,
) -> nb::Result<(), AllTxChannelsUnconfiguredError> {
self.start_transmit(frame)?.await;
Ok(())
}
}

View File

@@ -0,0 +1,131 @@
pub use embedded_can::{ExtendedId, Id, StandardId};
#[derive(Debug, thiserror::Error)]
#[error("invalid data size error {0}")]
pub struct InvalidDataSizeError(usize);
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct CanFrameNormal {
id: embedded_can::Id,
size: usize,
data: [u8; 8],
}
impl CanFrameNormal {
pub fn new(id: embedded_can::Id, data: &[u8]) -> Result<Self, InvalidDataSizeError> {
if data.len() > 8 {
return Err(InvalidDataSizeError(data.len()));
}
let size = data.len();
let mut data_array = [0; 8];
data_array[0..size].copy_from_slice(data);
Ok(Self {
id,
size,
data: data_array,
})
}
#[inline]
pub fn id(&self) -> embedded_can::Id {
self.id
}
#[inline]
pub fn data(&self) -> &[u8] {
&self.data[0..self.dlc()]
}
#[inline]
pub fn dlc(&self) -> usize {
self.size
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
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
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
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> {
if data.len() > 8 {
return None;
}
let id: embedded_can::Id = id.into();
Some(Self::Normal(CanFrameNormal::new(id, data).unwrap()))
}
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(_) => &[],
}
}
}

317
va416xx-hal/src/can/ll.rs Normal file
View File

@@ -0,0 +1,317 @@
use arbitrary_int::{u11, u15, u3, u4, Number};
use embedded_can::Frame;
use super::{
regs::{
self, BaseId, BufStatusAndControl, BufferState, ExtendedId, MmioCanMsgBuf, TwoBytesData,
},
CanFrame, CanFrameNormal, CanFrameRtr, CanId, InvalidBufferIndexError,
};
pub struct CanChannelLowLevel {
id: CanId,
/// Message buffer index.
idx: usize,
msg_buf: MmioCanMsgBuf<'static>,
}
impl core::fmt::Debug for CanChannelLowLevel {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("CanChannel")
.field("can_id", &self.id)
.field("idx", &self.idx)
.finish()
}
}
impl CanChannelLowLevel {
/// Steal a low level instance of a CAN channel.
///
/// # Safety
///
/// Circumvents ownership and safety guarantees of the HAL.
#[inline]
pub unsafe fn steal(can: CanId, idx: usize) -> Result<Self, InvalidBufferIndexError> {
if idx > 14 {
return Err(InvalidBufferIndexError(idx));
}
let msg_buf = unsafe { can.steal_regs().steal_cmbs_unchecked(idx) };
Ok(Self {
id: can,
idx,
msg_buf,
})
}
/// Steal a low level instance of a CAN channel without and index checks.
///
/// # Safety
///
/// Does not perform any bound checks. Passing an invalid index of 15 or higher leads to
/// undefined behaviour.
#[inline]
pub const unsafe fn steal_unchecked(can: CanId, idx: usize) -> Self {
if idx > 14 {
panic!("invalid buffer index for CAN low level channel");
}
let msg_buf = unsafe { can.steal_regs().steal_cmbs_unchecked(idx) };
Self {
id: can,
idx,
msg_buf,
}
}
/// # Safety
///
/// Allows to create an aribtrary amoutn of driver handles to the same message block, which
/// might lead to data races on invalid usage.
#[inline]
pub const unsafe fn clone(&self) -> Self {
Self {
id: self.id,
idx: self.idx,
msg_buf: unsafe { self.msg_buf.clone() },
}
}
pub fn reset(&mut self) {
self.msg_buf.reset();
}
#[inline]
pub fn read_state(&self) -> Result<BufferState, u8> {
self.msg_buf.read_stat_ctrl().state()
}
#[inline]
pub fn write_state(&mut self, buffer_state: BufferState) {
self.msg_buf.modify_stat_ctrl(|mut val| {
val.set_state(buffer_state);
val
});
}
pub fn configure_for_transmission(&mut self, tx_priority: Option<u4>) {
self.msg_buf.modify_stat_ctrl(|mut val| {
val.set_dlc(u4::new(0));
if let Some(tx_priority) = tx_priority {
val.set_priority(tx_priority);
}
val.set_state(BufferState::TxNotActive);
val
});
}
pub fn set_standard_id(&mut self, standard_id: embedded_can::StandardId, set_rtr: bool) {
let mut id1_reg = standard_id.as_raw() << 5;
if set_rtr {
id1_reg |= 1 << 4;
}
self.msg_buf
.write_id1(BaseId::new_with_raw_value(id1_reg as u32));
}
pub fn set_extended_id(&mut self, extended_id: embedded_can::ExtendedId, set_rtr: bool) {
let id_raw = extended_id.as_raw();
let id1_reg = (((id_raw >> 18) & 0x7FF) << 4) as u16 | ((id_raw >> 15) & 0b111) as u16;
self.msg_buf
.write_id1(BaseId::new_with_raw_value(id1_reg as u32));
let id0_reg = ((id_raw & 0x7FFF) << 1) as u16 | set_rtr as u16;
self.msg_buf
.write_id0(ExtendedId::new_with_raw_value(id0_reg as u32));
}
pub fn configure_for_reception(&mut self) {
self.msg_buf.write_stat_ctrl(
BufStatusAndControl::builder()
.with_dlc(u4::new(0))
.with_priority(u4::new(0))
.with_state(BufferState::RxReady)
.build(),
);
}
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.msg_buf.modify_stat_ctrl(|mut ctrl| {
ctrl.set_dlc(u4::new(dlc as u8));
ctrl
});
if !is_remote {
self.msg_buf
.write_data0(TwoBytesData::new_with_raw_value(0));
self.msg_buf
.write_data1(TwoBytesData::new_with_raw_value(0));
self.msg_buf
.write_data2(TwoBytesData::new_with_raw_value(0));
self.msg_buf
.write_data3(TwoBytesData::new_with_raw_value(0));
for idx in 0..dlc {
match idx {
0 => self.msg_buf.modify_data0(|mut val| {
val.set_data_upper_byte(frame.data()[idx]);
val
}),
1 => self.msg_buf.modify_data0(|mut val| {
val.set_data_lower_byte(frame.data()[idx]);
val
}),
2 => self.msg_buf.modify_data1(|mut val| {
val.set_data_upper_byte(frame.data()[idx]);
val
}),
3 => self.msg_buf.modify_data1(|mut val| {
val.set_data_lower_byte(frame.data()[idx]);
val
}),
4 => self.msg_buf.modify_data2(|mut val| {
val.set_data_upper_byte(frame.data()[idx]);
val
}),
5 => self.msg_buf.modify_data2(|mut val| {
val.set_data_lower_byte(frame.data()[idx]);
val
}),
6 => self.msg_buf.modify_data3(|mut val| {
val.set_data_upper_byte(frame.data()[idx]);
val
}),
7 => self.msg_buf.modify_data3(|mut val| {
val.set_data_lower_byte(frame.data()[idx]);
val
}),
_ => unreachable!(),
}
}
}
self.write_state(BufferState::TxOnce);
}
#[inline]
pub fn clear_interrupt(&mut self) {
let mut regs = unsafe { self.id.steal_regs() };
let mut clear = regs::InterruptClear::new_with_raw_value(0);
clear.set_buffer(self.idx, true);
regs.write_iclr(clear);
}
pub fn enable_error_interrupt(&mut self, enable_translation: bool) {
let mut regs = unsafe { self.id.steal_regs() };
if enable_translation {
regs.modify_icen(|mut val| {
val.set_error(true);
val
});
}
regs.modify_ien(|mut val| {
val.set_error(true);
val
});
}
pub fn enable_interrupt(&mut self, enable_translation: bool) {
let mut regs = unsafe { self.id.steal_regs() };
if enable_translation {
regs.modify_icen(|mut val| {
val.set_buffer(self.idx, true);
val
});
}
regs.modify_ien(|mut val| {
val.set_buffer(self.idx, true);
val
});
}
fn write_id(&mut self, id: embedded_can::Id, is_remote: bool) {
match id {
embedded_can::Id::Standard(standard_id) => {
self.msg_buf.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.msg_buf.write_id0(ExtendedId::new_with_raw_value(0));
}
embedded_can::Id::Extended(extended_id) => {
let id_raw = extended_id.as_raw();
self.msg_buf.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.msg_buf.write_id0(
ExtendedId::builder()
.with_mask_14_0(u15::new((id_raw & 0x7FFF) as u16))
.with_xrtr(is_remote)
.build(),
);
}
}
}
/// 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.msg_buf.read_id0();
let id1 = self.msg_buf.read_id1();
let data0 = self.msg_buf.read_data0();
let data1 = self.msg_buf.read_data1();
let data2 = self.msg_buf.read_data2();
let data3 = self.msg_buf.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] = data0.data_upper_byte().as_u8(),
1 => data[i] = data0.data_lower_byte().as_u8(),
2 => data[i] = data1.data_upper_byte().as_u8(),
3 => data[i] = data1.data_lower_byte().as_u8(),
4 => data[i] = data2.data_upper_byte().as_u8(),
5 => data[i] = data2.data_lower_byte().as_u8(),
6 => data[i] = data3.data_upper_byte().as_u8(),
7 => data[i] = data3.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.msg_buf.read_stat_ctrl().dlc().as_usize()).into()
} else {
let dlc = self.msg_buf.read_stat_ctrl().dlc();
read_data(dlc);
CanFrameNormal::new(id, &data[0..dlc.as_usize()])
.unwrap()
.into()
}
}
}

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

@@ -0,0 +1,830 @@
//! # CAN peripheral driver.
//!
//! The VA416xx CAN module is based on the CP3UB26 module.
//!
//! Using the CAN bus generally involves the following steps:
//!
//! 1. Create a [Can] instance
//! 2. The [CanChannels] resource management singleton can be retrieved by using
//! [Can::take_channels].
//! 3. Individual [CanRx] and [CanTx] channels can be created using the [CanChannels::take]
//! function. These allow to send or receive CAN frames on individual channels.
//! 4. The [asynch::CanTxAsync] structure can be created to transmit frames asynchronously.
//! The [asynch::on_interrupt_can] function should be called in the user interrupt handler
//! for CAN0 and CAN1 for this to work properly. The interrupt handler can also take care of
//! receiving frames on [CanRx] channels with enabled interrupts.
//!
//! # Example
//!
//! - [CAN example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/embassy/src/bin/can.rs)
use core::sync::atomic::AtomicBool;
use arbitrary_int::{u11, u15, u2, u3, u4, u7, Number};
use embedded_can::Frame;
use ll::CanChannelLowLevel;
use regs::{BaseId, BufferState, Control, MmioCan, TimingConfig};
use vorago_shared_periphs::enable_nvic_interrupt;
use crate::{clock::Clocks, enable_peripheral_clock, time::Hertz, PeripheralSelect};
use libm::roundf;
pub mod frame;
pub use frame::*;
pub mod asynch;
pub mod ll;
pub mod regs;
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;
static CHANNELS_TAKEN: [AtomicBool; 2] = [AtomicBool::new(false), AtomicBool::new(false)];
#[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].
#[inline]
pub const 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() },
}
}
#[inline]
pub const fn irq_id(&self) -> va416xx::Interrupt {
match self {
CanId::Can0 => va416xx::Interrupt::CAN0,
CanId::Can1 => va416xx::Interrupt::CAN1,
}
}
}
/// 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)
}
#[derive(Debug, Clone, Copy)]
pub struct ClockConfig {
prescaler: u8,
tseg1: u8,
tseg2: u8,
sjw: u8,
}
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. The CAN controller uses the APB1 clock.
///
/// 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 nominal_bit_time = 1 + tseg1 as u32 + tseg2 as u32;
let prescaler =
roundf(clocks.apb1().raw() as f32 / (bitrate.raw() as f32 * nominal_bit_time as f32))
as u32;
if !(PRESCALER_MIN as u32..=PRESCALER_MAX as u32).contains(&prescaler) {
return Err(ClockConfigError::CanNotFindPrescaler);
}
let actual_bitrate = (clocks.apb1().raw() as f32) / (prescaler * nominal_bit_time) as f32;
let bitrate_deviation = calculate_bitrate_deviation(actual_bitrate, bitrate);
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)
}
#[inline]
pub fn sjw_reg_value(&self) -> u2 {
u2::new(self.sjw.value() - 1)
}
#[inline]
pub fn tseg1_reg_value(&self) -> u4 {
u4::new(self.tseg1.value() - 1)
}
#[inline]
pub fn tseg2_reg_value(&self) -> u3 {
u3::new(self.tseg2.value() - 1)
}
#[inline]
pub fn prescaler_reg_value(&self) -> u7 {
u7::new(self.prescaler.value() - 2)
}
}
/// 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, f32)>, 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 = calculate_nominal_bit_time(apb1_clock, bitrate, prescaler);
// This is taken from the Python CAN library. NBT should not be too small.
if nom_bit_time < 8 {
break;
}
let actual_bitrate = calculate_actual_bitrate(apb1_clock, prescaler, nom_bit_time);
let bitrate_deviation = calculate_bitrate_deviation(actual_bitrate, bitrate);
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,
},
bitrate_deviation,
));
}
Ok(configs)
}
#[inline]
pub const fn calculate_nominal_bit_time(
apb1_clock: Hertz,
target_bitrate: Hertz,
prescaler: u8,
) -> u32 {
apb1_clock.raw() / (target_bitrate.raw() * prescaler as u32)
}
#[inline]
pub const fn calculate_actual_bitrate(apb1_clock: Hertz, prescaler: u8, nom_bit_time: u32) -> f32 {
apb1_clock.raw() as f32 / (prescaler as u32 * nom_bit_time) as f32
}
#[inline]
pub const fn calculate_bitrate_deviation(actual_bitrate: f32, target_bitrate: Hertz) -> f32 {
(actual_bitrate - target_bitrate.raw() as f32).abs() / target_bitrate.raw() as f32
}
pub trait CanMarker {
const ID: CanId;
const IRQ: va416xx::Interrupt;
const PERIPH_SEL: PeripheralSelect;
}
impl CanMarker for va416xx::Can0 {
const ID: CanId = CanId::Can0;
const IRQ: va416xx::Interrupt = va416xx::Interrupt::CAN0;
const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Can0;
}
impl CanMarker for va416xx::Can1 {
const ID: CanId = CanId::Can1;
const IRQ: va416xx::Interrupt = va416xx::Interrupt::CAN1;
const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Can1;
}
#[derive(Debug, thiserror::Error)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[error("invalid buffer index {0}")]
pub struct InvalidBufferIndexError(usize);
#[derive(Debug, thiserror::Error)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[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,
}
/// The main CAN peripheral driver.
pub struct Can {
regs: regs::MmioCan<'static>,
id: CanId,
}
impl Can {
pub fn new<CanI: CanMarker>(_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.cmbs(i).unwrap().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(regs::ExtendedId::new_with_raw_value(0));
self.regs.write_gmskb(BaseId::new_with_raw_value(0));
}
/// Retrieve a resource management singleton for the 15 CAN channels.
pub fn take_channels(&self) -> Option<CanChannels> {
if CHANNELS_TAKEN[self.id() as usize].swap(true, core::sync::atomic::Ordering::SeqCst) {
return None;
}
Some(CanChannels::new(self.id))
}
/// 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(
regs::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.
#[inline]
pub fn set_base_mask_for_exact_id_match(&mut self) {
self.regs
.write_bmskx(regs::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.
#[inline]
pub fn set_base_mask_for_all_match(&mut self) {
self.regs
.write_bmskx(regs::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
}
/// Clear all interrupts.
#[inline]
pub fn clear_interrupts(&mut self) {
self.regs
.write_iclr(regs::InterruptClear::new_with_raw_value(0xFFFF_FFFF));
}
/// This function only enable the CAN interrupt vector in the NVIC.
///
/// The interrupts for the individual channels or errors still need to be enabled
/// separately.
#[inline]
pub fn enable_nvic_interrupt(&mut self) {
unsafe {
enable_nvic_interrupt(self.id().irq_id());
}
}
#[inline]
pub fn read_error_counters(&self) -> regs::ErrorCounter {
self.regs.read_error_counter()
}
#[inline]
pub fn read_error_diagnostics(&self) -> regs::DiagnosticRegister {
self.regs.read_diag()
}
#[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 modify_control<F>(&mut self, f: F)
where
F: FnOnce(Control) -> Control,
{
self.regs.modify_control(f);
}
#[inline]
pub fn set_bufflock(&mut self, enable: bool) {
self.regs.modify_control(|mut ctrl| {
ctrl.set_bufflock(enable);
ctrl
});
}
#[inline]
pub fn enable(&mut self) {
self.regs.modify_control(|mut ctrl| {
ctrl.set_enable(true);
ctrl
});
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum TxState {
Idle,
TransmittingDataFrame,
TransmittingRemoteFrame,
AwaitingRemoteFrameReply,
}
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum InvalidTxState {
State(TxState),
BufferState(BufferState),
}
impl From<TxState> for InvalidTxState {
fn from(state: TxState) -> Self {
InvalidTxState::State(state)
}
}
impl From<BufferState> for InvalidTxState {
fn from(state: BufferState) -> Self {
InvalidTxState::BufferState(state)
}
}
#[derive(Debug, thiserror::Error)]
#[error("invalid tx state {0:?}")]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct InvalidTxStateError(pub InvalidTxState);
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum RxState {
Idle,
Receiving,
}
#[derive(Debug, thiserror::Error)]
#[error("invalid rx state {0:?}")]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct InvalidRxStateError(pub RxState);
/// Driver instance to use an individual CAN channel as a transmission channel.
#[derive(Debug)]
pub struct CanTx {
ll: CanChannelLowLevel,
mode: TxState,
}
impl CanTx {
pub fn new(mut ll: CanChannelLowLevel, tx_priority: Option<u4>) -> Self {
ll.reset();
ll.configure_for_transmission(tx_priority);
Self {
ll,
mode: TxState::Idle,
}
}
#[inline]
pub fn into_rx_channel(self) -> CanRx {
CanRx::new(self.ll)
}
/// Start transmitting a frame.
///
/// The frame transmission can be polled/awaited to completion using the [Self::transfer_done]
/// method.
///
/// This function will return a [state error][InvalidTxStateError] if a transmission is already
/// active and/or the transmit buffer has an invalid state.
pub fn transmit_frame(&mut self, frame: CanFrame) -> Result<(), InvalidTxStateError> {
if self.mode == TxState::AwaitingRemoteFrameReply {
self.ll.configure_for_transmission(None);
self.mode = TxState::Idle;
}
if self.mode != TxState::Idle {
return Err(InvalidTxStateError(self.mode.into()));
}
if !frame.is_remote_frame() {
self.mode = TxState::TransmittingDataFrame;
} else {
self.mode = TxState::TransmittingRemoteFrame;
}
if let Ok(state) = self.ll.read_state() {
if state != BufferState::TxNotActive {
return Err(InvalidTxStateError(state.into()));
}
}
self.ll.transmit_frame_unchecked(frame);
Ok(())
}
/// Poll whether an active data frame transmission is done.
///
/// Returns a [state error][InvalidTxStateError] if no transmission is active.
pub fn transfer_done(&mut self) -> nb::Result<(), InvalidTxStateError> {
if self.mode != TxState::TransmittingDataFrame {
return Err(nb::Error::Other(InvalidTxStateError(self.mode.into())));
}
let status = self.ll.read_state();
if status.is_err() {
return Err(nb::Error::WouldBlock);
}
let status = status.unwrap();
if status == BufferState::TxNotActive {
self.mode = TxState::Idle;
return Ok(());
}
Err(nb::Error::WouldBlock)
}
/// Poll whether an active remote frame transmission is done.
///
/// On success, returns the channel re-configured to a [CanRx] channel. This is because the
/// default behaviour of the hardware will be to re-configure the channel state to
/// [BufferState::RxReady] once the remote frame has been transmitted so that the response
/// frame can be awaited.
///
/// If the channel should instead be re-configured for transmission again,
/// [Self::remote_transfer_done_with_tx_reconfig] can be used.
///
/// Returns a [state error][InvalidTxStateError] if no transmission is active.
pub fn remote_transfer_done(&mut self) -> nb::Result<CanRx, InvalidTxStateError> {
if self.mode != TxState::TransmittingRemoteFrame {
return Err(nb::Error::Other(InvalidTxStateError(self.mode.into())));
}
let status = self.ll.read_state();
if status.is_err() {
return Err(nb::Error::WouldBlock);
}
let status = status.unwrap();
if status == BufferState::RxReady {
self.mode = TxState::AwaitingRemoteFrameReply;
return Ok(CanRx {
ll: unsafe { self.ll.clone() },
mode: RxState::Receiving,
});
}
Err(nb::Error::WouldBlock)
}
/// Poll whether an active remote frame transmission is done.
///
/// This function will re-configure the buffer back for transmission once the
/// transmission has completed.
///
/// Returns a [state error][InvalidTxStateError] if no transmission is active.
pub fn remote_transfer_done_with_tx_reconfig(&mut self) -> nb::Result<(), InvalidTxStateError> {
if self.mode != TxState::TransmittingRemoteFrame {
return Err(nb::Error::Other(InvalidTxStateError(self.mode.into())));
}
let status = self.ll.read_state();
if status.is_err() {
return Err(nb::Error::WouldBlock);
}
let status = status.unwrap();
if status == BufferState::RxReady {
self.ll.write_state(BufferState::TxNotActive);
self.mode = TxState::Idle;
return Ok(());
}
Err(nb::Error::WouldBlock)
}
pub fn reset(&mut self) {
self.ll.reset();
self.mode = TxState::Idle;
}
}
/// Driver instance to use an individual CAN channel as a reception channel.
pub struct CanRx {
ll: CanChannelLowLevel,
mode: RxState,
}
impl CanRx {
pub fn new(mut ll: CanChannelLowLevel) -> Self {
ll.reset();
Self {
ll,
mode: RxState::Idle,
}
}
#[inline]
pub fn into_tx_channel(self, tx_priority: Option<u4>) -> CanTx {
CanTx::new(self.ll, tx_priority)
}
#[inline]
pub fn enable_interrupt(&mut self, enable_translation: bool) {
self.ll.enable_interrupt(enable_translation);
}
pub fn configure_for_reception_with_standard_id(
&mut self,
standard_id: embedded_can::StandardId,
set_rtr: bool,
) {
self.ll.set_standard_id(standard_id, set_rtr);
self.configure_for_reception();
}
pub fn configure_for_reception_with_extended_id(
&mut self,
extended_id: embedded_can::ExtendedId,
set_rtr: bool,
) {
self.ll.set_extended_id(extended_id, set_rtr);
self.configure_for_reception();
}
pub fn configure_for_reception(&mut self) {
self.ll.configure_for_reception();
self.mode = RxState::Receiving;
}
#[inline]
pub fn frame_available(&self) -> bool {
self.ll
.read_state()
.is_ok_and(|state| state == BufferState::RxFull || state == BufferState::RxOverrun)
}
/// Poll for frame reception. Returns the frame if one is available.
pub fn receive(
&mut self,
reconfigure_for_reception: bool,
) -> nb::Result<CanFrame, InvalidRxStateError> {
if self.mode != RxState::Receiving {
return Err(nb::Error::Other(InvalidRxStateError(self.mode)));
}
let status = self.ll.read_state();
if status.is_err() {
return Err(nb::Error::WouldBlock);
}
let status = status.unwrap();
if status == BufferState::RxFull || status == BufferState::RxOverrun {
self.mode = RxState::Idle;
if reconfigure_for_reception {
self.ll.write_state(BufferState::RxReady);
}
return Ok(self.ll.read_frame_unchecked());
}
Err(nb::Error::WouldBlock)
}
}
pub struct CanChannels {
id: CanId,
channels: [Option<CanChannelLowLevel>; 15],
}
impl CanChannels {
const fn new(id: CanId) -> Self {
// Safety: Private function, ownership rules enforced by public API.
unsafe {
Self {
id,
channels: [
Some(CanChannelLowLevel::steal_unchecked(id, 0)),
Some(CanChannelLowLevel::steal_unchecked(id, 1)),
Some(CanChannelLowLevel::steal_unchecked(id, 2)),
Some(CanChannelLowLevel::steal_unchecked(id, 3)),
Some(CanChannelLowLevel::steal_unchecked(id, 4)),
Some(CanChannelLowLevel::steal_unchecked(id, 5)),
Some(CanChannelLowLevel::steal_unchecked(id, 6)),
Some(CanChannelLowLevel::steal_unchecked(id, 7)),
Some(CanChannelLowLevel::steal_unchecked(id, 8)),
Some(CanChannelLowLevel::steal_unchecked(id, 9)),
Some(CanChannelLowLevel::steal_unchecked(id, 10)),
Some(CanChannelLowLevel::steal_unchecked(id, 11)),
Some(CanChannelLowLevel::steal_unchecked(id, 12)),
Some(CanChannelLowLevel::steal_unchecked(id, 13)),
Some(CanChannelLowLevel::steal_unchecked(id, 14)),
],
}
}
}
pub const fn can_id(&self) -> CanId {
self.id
}
/// Take the indidivual CAN channel low level driver instance.
pub fn take(&mut self, idx: usize) -> Option<CanChannelLowLevel> {
if idx > 14 {
return None;
}
self.channels[idx].take()
}
pub fn give(&mut self, idx: usize, channel: CanChannelLowLevel) {
if idx > 14 {
panic!("invalid buffer index for CAN channel");
}
self.channels[idx] = Some(channel);
}
}
#[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);
}
}

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

@@ -0,0 +1,416 @@
//! Custom register definitions for the CAN register block to circumvent PAC API / SVD
//! shortcomings.
use arbitrary_int::{u11, u15, u2, u3, u4, u6, u7, Number};
pub const CAN_0_BASE: usize = 0x4001_4000;
pub const CAN_1_BASE: usize = 0x4001_4400;
#[derive(Debug, PartialEq, Eq)]
#[bitbybit::bitenum(u4)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
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)]
state: 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(0..=7, rw)]
data_lower_byte: u8,
#[bits(8..=15, rw)]
data_upper_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,
}
static_assertions::const_assert_eq!(core::mem::size_of::<CanMsgBuf>(), 0x20);
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_data0(TwoBytesData::new_with_raw_value(0));
self.write_data1(TwoBytesData::new_with_raw_value(0));
self.write_data2(TwoBytesData::new_with_raw_value(0));
self.write_data3(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],
}
#[derive(Debug)]
#[repr(usize)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum CanInterruptId {
None = 0b00000,
Error = 0b10000,
Buffer(usize),
}
#[bitbybit::bitfield(u32)]
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct StatusPending {
#[bits(5..=7, r)]
ns: u3,
#[bit(4, r)]
irq: bool,
#[bits(0..=3, r)]
ist: u4,
}
impl StatusPending {
pub fn interrupt_id(&self) -> Option<CanInterruptId> {
if !self.irq() && self.ist().value() == 0 {
return Some(CanInterruptId::None);
}
if self.irq() && self.ist().value() == 0 {
return Some(CanInterruptId::Error);
}
if !self.irq() {
return None;
}
Some(CanInterruptId::Buffer(self.ist().as_usize() - 1))
}
}
#[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(Debug, PartialEq, Eq)]
#[bitbybit::bitenum(u4, exhaustive = true)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum ErrorFieldId {
Error = 0b0000,
ErrorDel = 0b0001,
ErrorEcho = 0b0010,
BusIdle = 0b0011,
Ack = 0b0100,
Eof = 0b0101,
Intermission = 0b0110,
SuspendTransmission = 0b0111,
Sof = 0b1000,
Arbitration = 0b1001,
Ide = 0b1010,
ExtendedArbitration = 0b1011,
R1R0 = 0b1100,
Dlc = 0b1101,
Data = 0b1110,
Crc = 0b1111,
}
#[bitbybit::bitfield(u32)]
pub struct DiagnosticRegister {
/// Shows the output value on the CAN TX pin at the time of the error.
#[bit(14, r)]
drive: bool,
/// Shows the bus value on the CAN RX pin as sampled by the CAN module at the time of the
/// error.
#[bit(13, r)]
mon: bool,
/// Indicated whether the CRC is invalid. This bit should only be checked if the EFID field
/// is [ErrorFieldId::Ack].
#[bit(12, r)]
crc: bool,
/// Indicated whether the bit stuffing rule was violated at the time the error occured.
#[bit(11, r)]
stuff: bool,
/// Indicated whether the CAN module was an active transmitter at the time the error occured.
#[bit(10, r)]
txe: bool,
#[bits(4..=9, r)]
ebid: u6,
#[bits(0..=3, r)]
efid: ErrorFieldId,
}
impl core::fmt::Debug for DiagnosticRegister {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("DiagnosticRegister")
.field("efid", &self.efid())
.field("ebid", &self.ebid())
.field("txe", &self.txe())
.field("stuff", &self.stuff())
.field("crc", &self.crc())
.field("mon", &self.mon())
.field("drive", &self.drive())
.finish()
}
}
#[cfg(feature = "defmt")]
impl defmt::Format for DiagnosticRegister {
fn format(&self, fmt: defmt::Formatter) {
defmt::write!(
fmt,
"DiagnosticRegister {{ efid: {}, ebid: {}, txe: {}, stuff: {}, crc: {}, mon: {}, drive: {} }}",
self.efid(),
self.ebid(),
self.txe(),
self.stuff(),
self.crc(),
self.mon(),
self.drive()
)
}
}
#[derive(derive_mmio::Mmio)]
#[mmio(const_inner)]
#[repr(C)]
pub struct Can {
#[mmio(Inner)]
cmbs: [CanMsgBuf; 15],
/// 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: StatusPending,
#[mmio(PureRead)]
error_counter: ErrorCounter,
#[mmio(PureRead)]
diag: DiagnosticRegister,
#[mmio(PureRead)]
timer: u32,
}
static_assertions::const_assert_eq!(core::mem::size_of::<Can>(), 0x238);
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)
}
}

View File

@@ -26,6 +26,8 @@
//! faulty register reset values which might lead to weird bugs and glitches. //! faulty register reset values which might lead to weird bugs and glitches.
#![no_std] #![no_std]
#![cfg_attr(docsrs, feature(doc_auto_cfg))] #![cfg_attr(docsrs, feature(doc_auto_cfg))]
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(test)] #[cfg(test)]
extern crate std; extern crate std;
@@ -33,8 +35,7 @@ use gpio::Port;
pub use va416xx as device; pub use va416xx as device;
pub use va416xx as pac; pub use va416xx as pac;
pub mod prelude; pub mod can;
pub mod clock; pub mod clock;
pub mod dma; pub mod dma;
pub mod edac; pub mod edac;
@@ -42,6 +43,7 @@ pub mod gpio;
pub mod i2c; pub mod i2c;
pub mod irq_router; pub mod irq_router;
pub mod pins; pub mod pins;
pub mod prelude;
pub mod pwm; pub mod pwm;
pub mod spi; pub mod spi;
pub mod time; pub mod time;

View File

@@ -8,6 +8,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
## [unreleased] ## [unreleased]
## [v0.4.1] 2025-07-22
defmt v1
## [v0.4.0] 2025-02-18 ## [v0.4.0] 2025-02-18
- Re-generated PAC with `svd2rust` v0.35.0 and added optional `defmt` and `Debug` implementations - Re-generated PAC with `svd2rust` v0.35.0 and added optional `defmt` and `Debug` implementations
@@ -32,3 +36,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
Related issue: https://github.com/rust-embedded/svd2rust/issues/557 Related issue: https://github.com/rust-embedded/svd2rust/issues/557
Clippy is disabled in CI/CD for now. Clippy is disabled in CI/CD for now.
- Initial release - Initial release
[unreleased]: https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/compare/va416xx-v0.4.1...HEAD
[v0.4.1]: https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/compare/va416xx-v0.4.0...va416xx-v0.4.1
[v0.4.0]: https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/compare/va416xx-v0.3.0...va416xx-v0.4.0

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "va416xx" name = "va416xx"
version = "0.4.0" version = "0.4.1"
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"] authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
edition = "2021" edition = "2021"
description = "PAC for the Vorago VA416xx family of MCUs" description = "PAC for the Vorago VA416xx family of MCUs"
@@ -16,7 +16,7 @@ categories = ["embedded", "no-std", "hardware-support"]
cortex-m = "0.7" cortex-m = "0.7"
vcell = "0.1.3" vcell = "0.1.3"
defmt = { version = "0.3", optional = true } defmt = { version = "1", optional = true }
critical-section = { version = "1", optional = true } critical-section = { version = "1", optional = true }
[dependencies.cortex-m-rt] [dependencies.cortex-m-rt]