add CAN example app

This commit is contained in:
Robin Müller 2025-04-29 11:24:48 +02:00
parent 997e0502ab
commit 629ba4f8f2
Signed by: muellerr
GPG Key ID: A649FB78196E3849
8 changed files with 155 additions and 91 deletions

View File

@ -18,6 +18,7 @@ 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 = [

View File

@ -0,0 +1,57 @@
#![no_std]
#![no_main]
// 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;
use va416xx_hal::can::{Can, CanFrame, CanFrameNormal, 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::*};
#[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);
let clk_config = ClockConfig::from_bitrate_and_segments(&clocks, 250.kHz(), 16, 4, 4).unwrap();
let mut can = Can::new(dp.can0, clk_config);
can.set_loopback(true);
can.set_bufflock(true);
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 = CanRx::new(channels.take(14).unwrap());
let send_frame = CanFrame::Normal(CanFrameNormal::new(
can::Id::Standard(can::StandardId::new(0x1).unwrap()),
&[1, 2, 3, 4],
));
defmt::info!("sending CAN frame");
tx.transmit_frame(send_frame).unwrap();
let _frame = nb::block!(rx.receive(true)).expect("invalid CAN rx state");
defmt::info!("received CAN frame with data");
}
#[interrupt]
#[allow(non_snake_case)]
fn CAN0() {
on_interrupt_can(CanId::Can0, false).unwrap();
}

View File

@ -53,6 +53,8 @@ pub enum InterruptResult {
},
}
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum InterruptError {
UnexpectedError,
InvalidInterruptId(StatusPending),
@ -95,35 +97,30 @@ pub fn on_interrupt_can(
return Err(InterruptError::InvalidInterruptId(pending_id));
}
match pending_id.interrupt_id().unwrap() {
super::regs::CanInterruptId::None => {
return Ok(InterruptResult::NoInterrupt);
}
super::regs::CanInterruptId::Error => {
return Err(InterruptError::CanError(regs.read_diag()));
}
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_status();
let status = channel.read_state();
if status.is_err() {
let mut clr = InterruptClear::new_with_raw_value(0);
clr.set_buffer(idx as usize, true);
clr.set_buffer(idx, true);
regs.write_iclr(clr);
regs.modify_ien(|mut val| {
val.set_buffer(idx as usize, false);
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 as usize].load(Ordering::Relaxed);
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 as usize]
.store(TxChannelState::Finished as u8, Ordering::Relaxed);
TX_WAKERS[idx as usize].wake();
TX_STATES[idx].store(TxChannelState::Finished as u8, Ordering::Relaxed);
TX_WAKERS[idx].wake();
return Ok(InterruptResult::TransmissionEvent {
channel_index: idx,
id: TxEventId::TxDataFrame,
@ -131,22 +128,21 @@ pub fn on_interrupt_can(
}
}
if buf_state == BufferState::RxReady {
let tx_state = TX_STATES[idx as usize].load(Ordering::Relaxed);
let tx_state = TX_STATES[idx].load(Ordering::Relaxed);
if tx_state == TxChannelState::TxRtrTransmission as u8 {
if reconfigure_tx_rtr_to_tx {
channel.write_status(BufferState::TxNotActive);
channel.write_state(BufferState::TxNotActive);
clear_and_disable_interrupt(&mut regs, idx);
// Transmission complete.
TX_STATES[idx as usize]
.store(TxChannelState::Idle as u8, Ordering::Relaxed);
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 as usize]
TX_STATES[idx]
.store(TxChannelState::TxRtrReception as u8, Ordering::Relaxed);
}
TX_WAKERS[idx as usize].wake();
TX_WAKERS[idx].wake();
return Ok(InterruptResult::TransmissionEvent {
channel_index: idx,
id: TxEventId::TxRemoteFrame,
@ -154,18 +150,18 @@ pub fn on_interrupt_can(
}
}
if buf_state == BufferState::RxOverrun || buf_state == BufferState::RxFull {
let tx_state = TX_STATES[idx as usize].load(Ordering::Relaxed);
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 as usize].store(TxChannelState::Idle as u8, Ordering::Relaxed);
channel.write_status(BufferState::TxNotActive);
TX_STATES[idx].store(TxChannelState::Idle as u8, Ordering::Relaxed);
channel.write_state(BufferState::TxNotActive);
} else {
// Assume continous reception of frames.
channel.write_status(BufferState::RxReady);
channel.write_state(BufferState::RxReady);
}
return Ok(InterruptResult::ReceivedFrame {
channel_index: idx,
@ -205,7 +201,7 @@ fn clear_interrupt(regs: &mut MmioCan<'static>, idx: usize) {
fn clear_and_disable_interrupt(regs: &mut MmioCan<'static>, idx: usize) {
clear_interrupt(regs, idx);
regs.modify_ien(|mut val| {
val.set_buffer(idx as usize, false);
val.set_buffer(idx, false);
val
});
}

View File

@ -1,3 +1,5 @@
pub use embedded_can::{ExtendedId, Id, StandardId};
#[derive(Debug, thiserror::Error)]
#[error("invalid data size error {0}")]
pub struct InvalidDataSizeError(usize);

View File

@ -63,12 +63,12 @@ impl CanChannelLowLevel {
}
#[inline]
pub fn read_status(&self) -> Result<BufferState, u8> {
pub fn read_state(&self) -> Result<BufferState, u8> {
self.msg_buf.read_stat_ctrl().status()
}
#[inline]
pub fn write_status(&mut self, buffer_state: BufferState) {
pub fn write_state(&mut self, buffer_state: BufferState) {
self.msg_buf.modify_stat_ctrl(|mut val| {
val.set_status(buffer_state);
val
@ -90,7 +90,7 @@ impl CanChannelLowLevel {
Ok(())
}
pub fn configure_for_reception_with_standard_id(
pub fn set_standard_id(
&mut self,
standard_id: embedded_can::StandardId,
set_rtr: bool,
@ -101,18 +101,10 @@ impl CanChannelLowLevel {
}
self.msg_buf
.write_id1(BaseId::new_with_raw_value(id1_reg as u32));
self.msg_buf.write_stat_ctrl(
BufStatusAndControl::builder()
.with_dlc(u4::new(0))
.with_priority(u4::new(0))
.with_status(BufferState::RxReady)
.build(),
);
Ok(())
}
pub fn configure_for_reception_with_extended_id(
pub fn set_extended_id(
&mut self,
extended_id: embedded_can::ExtendedId,
set_rtr: bool,
@ -124,6 +116,10 @@ impl CanChannelLowLevel {
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));
Ok(())
}
pub fn configure_for_reception(&mut self) {
self.msg_buf.write_stat_ctrl(
BufStatusAndControl::builder()
.with_dlc(u4::new(0))
@ -131,7 +127,6 @@ impl CanChannelLowLevel {
.with_status(BufferState::RxReady)
.build(),
);
Ok(())
}
pub fn transmit_frame_unchecked(&mut self, frame: CanFrame) {

View File

@ -6,7 +6,7 @@ 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, ExtendedId, MmioCan, TimingConfig};
use regs::{BaseId, BufferState, Control, MmioCan, TimingConfig};
use crate::{clock::Clocks, enable_peripheral_clock, time::Hertz, PeripheralSelect};
use libm::roundf;
@ -230,7 +230,7 @@ impl ClockConfig {
}
/// Calculate the clock configuration for the given input clock, the target bitrate and for a
/// set of timing parameters.
/// 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:
@ -314,7 +314,7 @@ impl Can {
/// with the ID in the receive message buffers. This is the default reset configuration for
/// the global mask as well.
pub fn set_global_mask_for_exact_id_match(&mut self) {
self.regs.write_gmskx(ExtendedId::new_with_raw_value(0));
self.regs.write_gmskx(regs::ExtendedId::new_with_raw_value(0));
self.regs.write_gmskb(BaseId::new_with_raw_value(0));
}
@ -333,7 +333,7 @@ impl Can {
/// on that buffer.
pub fn set_global_mask_for_exact_id_match_with_rtr_masked(&mut self) {
self.regs.write_gmskx(
ExtendedId::builder()
regs::ExtendedId::builder()
.with_mask_14_0(u15::new(0))
.with_xrtr(true)
.build(),
@ -352,7 +352,7 @@ impl Can {
/// exact match with the ID in the receive message buffers. This is the default reset
/// configuration for the global mask as well.
pub fn set_base_mask_for_exact_id_match(&mut self) {
self.regs.write_bmskx(ExtendedId::new_with_raw_value(0));
self.regs.write_bmskx(regs::ExtendedId::new_with_raw_value(0));
self.regs.write_bmskb(BaseId::new_with_raw_value(0));
}
@ -360,7 +360,7 @@ impl Can {
/// buffers are accepted by the base buffer 14.
pub fn set_base_mask_for_all_match(&mut self) {
self.regs
.write_bmskx(ExtendedId::new_with_raw_value(0xffff));
.write_bmskx(regs::ExtendedId::new_with_raw_value(0xffff));
self.regs.write_bmskb(BaseId::new_with_raw_value(0xffff));
}
@ -380,9 +380,17 @@ impl Can {
}
#[inline]
pub fn enable_bufflock(&mut self) {
pub fn set_loopback(&mut self, enable: bool) {
self.regs.modify_control(|mut val| {
val.set_loopback(enable);
val
});
}
#[inline]
pub fn set_bufflock(&mut self, enable: bool) {
self.regs.modify_control(|mut ctrl| {
ctrl.set_bufflock(true);
ctrl.set_bufflock(enable);
ctrl
});
}
@ -400,7 +408,6 @@ impl Can {
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum TxState {
Idle,
TransmissionIdle,
TransmittingDataFrame,
TransmittingRemoteFrame,
AwaitingRemoteFrameReply,
@ -430,28 +437,20 @@ pub struct CanTx {
}
impl CanTx {
pub fn new(ll: CanChannelLowLevel) -> Self {
pub fn new(mut ll: CanChannelLowLevel, tx_priority: Option<u4>) -> Self {
ll.configure_for_transmission(tx_priority).unwrap();
Self {
ll,
mode: TxState::Idle,
}
}
pub fn configure_for_transmission(
&mut self,
tx_priority: Option<u4>,
) -> Result<(), InvalidBufferIndexError> {
self.ll.configure_for_transmission(tx_priority).unwrap();
self.mode = TxState::TransmissionIdle;
Ok(())
}
pub fn transmit_frame(&mut self, frame: CanFrame) -> Result<(), InvalidTxStateError> {
if self.mode == TxState::AwaitingRemoteFrameReply {
self.configure_for_transmission(None).unwrap();
self.mode = TxState::TransmissionIdle;
self.ll.configure_for_transmission(None).unwrap();
self.mode = TxState::Idle;
}
if self.mode != TxState::TransmissionIdle {
if self.mode != TxState::Idle {
return Err(InvalidTxStateError(self.mode));
}
if !frame.is_remote_frame() {
@ -467,13 +466,13 @@ impl CanTx {
if self.mode != TxState::TransmittingDataFrame {
return Err(nb::Error::Other(InvalidTxStateError(self.mode)));
}
let status = self.ll.read_status();
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::TransmissionIdle;
self.mode = TxState::Idle;
return Ok(());
}
Err(nb::Error::WouldBlock)
@ -483,7 +482,7 @@ impl CanTx {
if self.mode != TxState::TransmittingRemoteFrame {
return Err(nb::Error::Other(InvalidTxStateError(self.mode)));
}
let status = self.ll.read_status();
let status = self.ll.read_state();
if status.is_err() {
return Err(nb::Error::WouldBlock);
}
@ -505,14 +504,19 @@ pub struct CanRx {
}
impl CanRx {
pub fn new(ll: CanChannelLowLevel) -> Self {
Self {
ll,
mode: RxState::Idle,
}
}
pub fn configure_for_reception_with_standard_id(
&mut self,
standard_id: embedded_can::StandardId,
set_rtr: bool,
) -> Result<(), InvalidBufferIndexError> {
self.ll
.configure_for_reception_with_standard_id(standard_id, set_rtr)?;
self.mode = RxState::Receiving;
self.ll.set_standard_id(standard_id, set_rtr)?;
self.configure_for_reception();
Ok(())
}
@ -521,12 +525,16 @@ impl CanRx {
extended_id: embedded_can::ExtendedId,
set_rtr: bool,
) -> Result<(), InvalidBufferIndexError> {
self.ll
.configure_for_reception_with_extended_id(extended_id, set_rtr)?;
self.mode = RxState::Receiving;
self.ll.set_extended_id(extended_id, set_rtr)?;
self.configure_for_reception();
Ok(())
}
pub fn configure_for_reception(&mut self) {
self.ll.configure_for_reception();
self.mode = RxState::Receiving;
}
pub fn receive(
&mut self,
reconfigure_for_reception: bool,
@ -534,7 +542,7 @@ impl CanRx {
if self.mode != RxState::Receiving {
return Err(nb::Error::Other(InvalidRxStateError(self.mode)));
}
let status = self.ll.read_status();
let status = self.ll.read_state();
if status.is_err() {
return Err(nb::Error::WouldBlock);
}
@ -542,7 +550,7 @@ impl CanRx {
if status == BufferState::RxReady || status == BufferState::RxOverrun {
self.mode = RxState::Idle;
if reconfigure_for_reception {
self.ll.write_status(BufferState::RxReady);
self.ll.write_state(BufferState::RxReady);
}
return Ok(self.ll.read_frame_unchecked());
}
@ -557,25 +565,28 @@ pub struct CanChannels {
impl CanChannels {
const fn new(id: CanId) -> Self {
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)),
],
// 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)),
],
}
}
}

View File

@ -8,6 +8,7 @@ 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,
@ -207,6 +208,7 @@ pub enum CanInterruptId {
#[bitbybit::bitfield(u32)]
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct StatusPending {
#[bits(5..=7, r)]
ns: u3,

View File

@ -33,7 +33,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;
@ -41,12 +41,12 @@ 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;
pub mod timer;
pub mod uart;
pub mod can;
pub mod wdt;
#[cfg(feature = "va41630")]