Merge pull request 'CAN Support' (#69) from can-support into main
Reviewed-on: #69
This commit is contained in:
commit
ce02b38ff6
@ -4,20 +4,23 @@ version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
cortex-m = "0.7"
|
||||
cortex-m-rt = "0.7"
|
||||
cfg-if = "1"
|
||||
embedded-io = "0.6"
|
||||
embedded-can = "0.4"
|
||||
embedded-hal-async = "1"
|
||||
embedded-io-async = "0.6"
|
||||
|
||||
heapless = "0.8"
|
||||
defmt-rtt = "0.4"
|
||||
defmt = "1"
|
||||
panic-probe = { version = "1", features = ["defmt"] }
|
||||
panic-probe = { version = "1", features = ["print-defmt"] }
|
||||
static_cell = "2"
|
||||
critical-section = "1"
|
||||
ringbuf = { version = "0.4", default-features = false }
|
||||
|
||||
nb = "1"
|
||||
embassy-sync = "0.6"
|
||||
embassy-time = "0.4"
|
||||
embassy-executor = { version = "0.7", features = [
|
||||
|
239
examples/embassy/src/bin/can.rs
Normal file
239
examples/embassy/src/bin/can.rs
Normal 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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
1
scripts/can-clk-calc/.gitignore
vendored
Normal file
1
scripts/can-clk-calc/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/target
|
9
scripts/can-clk-calc/Cargo.toml
Normal file
9
scripts/can-clk-calc/Cargo.toml
Normal 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 }
|
14
scripts/can-clk-calc/src/main.rs
Normal file
14
scripts/can-clk-calc/src/main.rs
Normal 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);
|
||||
}
|
||||
}
|
@ -13,8 +13,11 @@ categories = ["embedded", "no-std", "hardware-support"]
|
||||
[dependencies]
|
||||
cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
|
||||
va416xx = { version = "0.4", features = ["critical-section"], default-features = false }
|
||||
derive-mmio = { version = "0.4", git = "https://github.com/knurling-rs/derive-mmio.git" }
|
||||
static_assertions = "1.1"
|
||||
vorago-shared-periphs = { git = "https://egit.irs.uni-stuttgart.de/rust/vorago-shared-periphs.git", features = ["vor4x"] }
|
||||
|
||||
libm = "0.2"
|
||||
nb = "1"
|
||||
embedded-hal = "1"
|
||||
num_enum = { version = "0.7", default-features = false }
|
||||
@ -22,6 +25,8 @@ bitflags = "2"
|
||||
bitbybit = "1.3"
|
||||
arbitrary-int = "1.3"
|
||||
fugit = "0.3"
|
||||
embedded-can = "0.4"
|
||||
embassy-sync = "0.6"
|
||||
thiserror = { version = "2", default-features = false }
|
||||
|
||||
defmt = { version = "0.3", optional = true }
|
||||
@ -29,6 +34,7 @@ defmt = { version = "0.3", optional = true }
|
||||
[features]
|
||||
default = ["rt", "revb"]
|
||||
rt = ["va416xx/rt"]
|
||||
alloc = []
|
||||
defmt = ["dep:defmt", "fugit/defmt", "vorago-shared-periphs/defmt"]
|
||||
|
||||
va41630 = ["device-selected"]
|
||||
|
311
va416xx-hal/src/can/asynch.rs
Normal file
311
va416xx-hal/src/can/asynch.rs
Normal 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(())
|
||||
}
|
||||
}
|
131
va416xx-hal/src/can/frame.rs
Normal file
131
va416xx-hal/src/can/frame.rs
Normal 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
317
va416xx-hal/src/can/ll.rs
Normal 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
830
va416xx-hal/src/can/mod.rs
Normal 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
416
va416xx-hal/src/can/regs.rs
Normal 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)
|
||||
}
|
||||
}
|
@ -26,6 +26,8 @@
|
||||
//! 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;
|
||||
|
||||
@ -33,8 +35,7 @@ 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;
|
||||
@ -42,6 +43,7 @@ pub mod gpio;
|
||||
pub mod i2c;
|
||||
pub mod irq_router;
|
||||
pub mod pins;
|
||||
pub mod prelude;
|
||||
pub mod pwm;
|
||||
pub mod spi;
|
||||
pub mod time;
|
||||
|
Loading…
x
Reference in New Issue
Block a user