diff --git a/examples/embassy/Cargo.toml b/examples/embassy/Cargo.toml index b37f672..b37dc51 100644 --- a/examples/embassy/Cargo.toml +++ b/examples/embassy/Cargo.toml @@ -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 = [ diff --git a/examples/embassy/src/bin/can.rs b/examples/embassy/src/bin/can.rs new file mode 100644 index 0000000..7907d70 --- /dev/null +++ b/examples/embassy/src/bin/can.rs @@ -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::::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 + ); + } + } +} diff --git a/scripts/can-clk-calc/.gitignore b/scripts/can-clk-calc/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/scripts/can-clk-calc/.gitignore @@ -0,0 +1 @@ +/target diff --git a/scripts/can-clk-calc/Cargo.toml b/scripts/can-clk-calc/Cargo.toml new file mode 100644 index 0000000..ad25349 --- /dev/null +++ b/scripts/can-clk-calc/Cargo.toml @@ -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 } diff --git a/scripts/can-clk-calc/src/main.rs b/scripts/can-clk-calc/src/main.rs new file mode 100644 index 0000000..0031a05 --- /dev/null +++ b/scripts/can-clk-calc/src/main.rs @@ -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); + } +} diff --git a/va416xx-hal/Cargo.toml b/va416xx-hal/Cargo.toml index 9334b9c..24ba437 100644 --- a/va416xx-hal/Cargo.toml +++ b/va416xx-hal/Cargo.toml @@ -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"] diff --git a/va416xx-hal/src/can/asynch.rs b/va416xx-hal/src/can/asynch.rs new file mode 100644 index 0000000..5b22fe2 --- /dev/null +++ b/va416xx-hal/src/can/asynch.rs @@ -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 { + 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 { + 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 { + 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::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(()) + } +} diff --git a/va416xx-hal/src/can/frame.rs b/va416xx-hal/src/can/frame.rs new file mode 100644 index 0000000..0f2989e --- /dev/null +++ b/va416xx-hal/src/can/frame.rs @@ -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 { + 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 for CanFrame { + fn from(value: CanFrameNormal) -> Self { + Self::Normal(value) + } +} + +impl From for CanFrame { + fn from(value: CanFrameRtr) -> Self { + Self::Rtr(value) + } +} + +impl embedded_can::Frame for CanFrame { + fn new(id: impl Into, data: &[u8]) -> Option { + 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, dlc: usize) -> Option { + 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(_) => &[], + } + } +} diff --git a/va416xx-hal/src/can/ll.rs b/va416xx-hal/src/can/ll.rs new file mode 100644 index 0000000..622e66b --- /dev/null +++ b/va416xx-hal/src/can/ll.rs @@ -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 { + 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 { + 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) { + 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() + } + } +} diff --git a/va416xx-hal/src/can/mod.rs b/va416xx-hal/src/can/mod.rs new file mode 100644 index 0000000..3943a12 --- /dev/null +++ b/va416xx-hal/src/can/mod.rs @@ -0,0 +1,807 @@ +//! CAN driver. +//! +//! The VA416xx CAN module is based on the CP3UB26 module. +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 { + 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 { + 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, 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, +} + +pub struct Can { + regs: regs::MmioCan<'static>, + id: CanId, +} + +impl Can { + pub fn new(_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)); + } + + pub fn take_channels(&self) -> Option { + 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 + } + + #[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(&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 for InvalidTxState { + fn from(state: TxState) -> Self { + InvalidTxState::State(state) + } +} + +impl From 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); + +#[derive(Debug)] +pub struct CanTx { + ll: CanChannelLowLevel, + mode: TxState, +} + +impl CanTx { + pub fn new(mut ll: CanChannelLowLevel, tx_priority: Option) -> 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 { + 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; + } +} + +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) -> 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 { + 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; 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 + } + + pub fn take(&mut self, idx: usize) -> Option { + 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); + } +} diff --git a/va416xx-hal/src/can/regs.rs b/va416xx-hal/src/can/regs.rs new file mode 100644 index 0000000..e3bd3e8 --- /dev/null +++ b/va416xx-hal/src/can/regs.rs @@ -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, +} + +#[derive(Debug)] +pub struct Timestamp(arbitrary_int::UInt); + +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::(), 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 { + 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::(), 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) + } +} diff --git a/va416xx-hal/src/lib.rs b/va416xx-hal/src/lib.rs index 84b058d..e4c9861 100644 --- a/va416xx-hal/src/lib.rs +++ b/va416xx-hal/src/lib.rs @@ -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;