Skip to content

CAN driver proposal #75

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = [
] }
embedded-hal = { package = "embedded-hal", version = "1.0" }
embedded-hal-async = "1.0.0"
embedded-can = "0.4.1"

critical-section = { version = "1.2.0" }
defmt = { version = "0.3.8", optional = true }
Expand Down Expand Up @@ -56,7 +57,7 @@ quote = "1.0"

[features]
default = ["embassy", "rt"]
rt = ["dep:qingke-rt"]
rt = ["dep:qingke-rt", "rt-wfi"]
highcode = ["qingke-rt/highcode"]
embassy = [
"dep:embassy-time-driver",
Expand Down Expand Up @@ -88,6 +89,7 @@ time-driver-tim8 = ["_time-driver"]
time-driver-tim9 = ["_time-driver"]
## Use TIM10 as time driver
time-driver-tim10 = ["_time-driver"]
rt-wfi = []

# Chip-selection features

Expand Down
2 changes: 2 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,8 @@ fn main() {
// USBPD, handled by usbpd/mod.rs
//(("usbpd", "CC1"), quote!(crate::usbpd::Cc1Pin)),
//(("usbpd", "CC2"), quote!(crate::usbpd::Cc2Pin)),
(("can", "TX"), quote!(crate::can::TxPin)),
(("can", "RX"), quote!(crate::can::RxPin)),
]
.into();

Expand Down
35 changes: 35 additions & 0 deletions examples/ch32v203/src/bin/can.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#![no_std]
#![no_main]
#![feature(type_alias_impl_trait)]
#![feature(impl_trait_in_assoc_type)]

use ch32_hal::can::{Can, CanFifo, CanFilter, CanFrame, CanMode, StandardId};
use embassy_executor::Spawner;
use embassy_time::{Duration, Ticker};
use {ch32_hal as hal, panic_halt as _};

#[embassy_executor::main(entry = "ch32_hal::entry")]
async fn main(_spawner: Spawner) {
let p = hal::init(Default::default());

let can = Can::new(p.CAN1, p.PA11, p.PA12, CanFifo::Fifo1, CanMode::Normal, 500_000).expect("Valid");
let mut filter = CanFilter::new_id_list();

filter
.get(0)
.unwrap()
.set(StandardId::new(0x580 | 0x42).unwrap().into(), Default::default());

can.add_filter(CanFilter::accept_all());

let mut ticker = Ticker::every(Duration::from_millis(200));

let body: [u8; 8] = [0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF];

loop {
let frame = CanFrame::new(StandardId::new(0x317).unwrap(), &body).unwrap();

let _ = can.transmit(&frame);
ticker.next().await;
}
}
203 changes: 203 additions & 0 deletions src/can/can.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
use super::enums::*;
use super::filter::{BitMode, FilterMode};
use super::{CanFilter, CanFrame};
use crate::can::registers::Registers;
use crate::can::util;
use crate::{into_ref, pac, peripherals, Peripheral, PeripheralRef, RccPeripheral, RemapPeripheral};

pub struct Can<'d, T: Instance> {
_peri: PeripheralRef<'d, T>,
fifo: CanFifo,
last_mailbox_used: usize,
}

#[derive(Debug)]
pub enum CanInitError {
InvalidTimings,
}

impl<'d, T: Instance> Can<'d, T> {
/// Assumes AFIO & PORTB clocks have been enabled by HAL.
///
/// CAN_RX is mapped to PB8, and CAN_TX is mapped to PB9.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These comments can be removed.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will review the relevance of the comments)

pub fn new<const REMAP: u8>(
peri: impl Peripheral<P = T> + 'd,
rx: impl Peripheral<P = impl RxPin<T, REMAP>> + 'd,
tx: impl Peripheral<P = impl TxPin<T, REMAP>> + 'd,
fifo: CanFifo,
mode: CanMode,
bitrate: u32,
) -> Result<Self, CanInitError> {
into_ref!(peri, rx, tx);

let this = Self {
_peri: peri,
fifo,
last_mailbox_used: usize::MAX,
};
T::enable_and_reset(); // Enable CAN peripheral

rx.set_mode_cnf(
pac::gpio::vals::Mode::INPUT,
pac::gpio::vals::Cnf::PULL_IN__AF_PUSH_PULL_OUT,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leave these pin configs to me. I'll get it implemented.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't mind. What should I do?)

);

tx.set_mode_cnf(
pac::gpio::vals::Mode::OUTPUT_50MHZ,
pac::gpio::vals::Cnf::PULL_IN__AF_PUSH_PULL_OUT,
);
T::set_remap(REMAP);

// //here should remap functionality be added
// T::remap(0b10);

Registers(T::regs()).enter_init_mode(); // CAN enter initialization mode

// Configure bit timing parameters and CAN operating mode
let Some(bit_timings) = util::calc_can_timings(T::frequency().0, bitrate) else {
return Err(CanInitError::InvalidTimings);
};

Registers(T::regs()).set_bit_timing_and_mode(bit_timings, mode);

Registers(T::regs()).leave_init_mode(); // Exit CAN initialization mode

Ok(this)
}

/// Each filter bank consists of 2 32-bit registers CAN_FxR0 and CAN_FxR1
pub fn add_filter<BIT: BitMode, MODE: FilterMode>(&self, filter: CanFilter<BIT, MODE>) {
let can = T::regs();
let fifo = &self.fifo;

can.fctlr().modify(|w| w.set_finit(true)); // Enable filter init mode

can.fscfgr()
.modify(|w| w.set_fsc(filter.bank, filter.bit_mode.val_bool())); // Set filter scale config (32bit or 16bit mode)

can.fr(filter.fr_id_value_reg())
.write_value(crate::pac::can::regs::Fr(filter.id_value)); // Set filter's id value to match/mask

can.fr(filter.fr_id_mask_reg())
.write_value(crate::pac::can::regs::Fr(filter.id_mask)); // Set filter's id bits to mask

can.fmcfgr().modify(|w| w.set_fbm(filter.bank, filter.mode.val_bool())); // Set new filter's operating mode

can.fafifor()
.modify(|w| w.set_ffa(filter.bank, fifo.val_bool())); // Associate CAN's FIFO to new filter

can.fwr().modify(|w| w.set_fact(filter.bank, true)); // Activate new filter

can.fctlr().modify(|w| w.set_finit(false)); // Exit filter init mode
}

/// Puts a frame in the transmit buffer to be sent on the bus.
///
/// If the transmit buffer is full, this function will try to replace a pending
/// lower priority frame and return the frame that was replaced.
/// Returns `Err(WouldBlock)` if the transmit buffer is full and no frame can be
/// replaced.
pub fn transmit(&self, frame: &CanFrame) -> nb::Result<Option<CanFrame>, CanError> {
let mailbox_num = match Registers(T::regs()).find_free_mailbox() {
Some(n) => n,
None => return Err(nb::Error::WouldBlock),
};

Registers(T::regs()).write_frame_mailbox(mailbox_num, frame);

// Success in readying packet for transmit. No packets can be replaced in the
// transmit buffer so return None in accordance with embedded-can.
Ok(None)
}

/// Retrieves status of the last frame transmission
pub fn transmit_status(&self) -> TxStatus {
if self.last_mailbox_used > 2 {
return TxStatus::OtherError;
}

Registers(T::regs()).transmit_status(self.last_mailbox_used)
}

/// Try to read the next message from the queue.
/// If there are no messages, an error is returned.
pub fn try_recv(&self) -> nb::Result<CanFrame, CanError> {
let can = &T::regs();
let fifo = self.fifo.val();

//check pending messages
if can.rfifo(self.fifo.val()).read().fmp() == 0 {
return Err(nb::Error::WouldBlock);
}

let dlc = can.rxmdtr(fifo).read().dlc() as usize;
let raw_id = can.rxmir(fifo).read().stid();

let id = embedded_can::StandardId::new(raw_id).unwrap();

let frame_data_unordered: u64 = ((can.rxmdhr(fifo).read().0 as u64) << 32) | can.rxmdlr(fifo).read().0 as u64;

let frame = CanFrame::new_from_data_registers(id, frame_data_unordered, dlc);

can.rfifo(fifo).write(|w| {
//set the data was read
w.set_rfom(true);
});

Ok(frame)
}
}

/// These trait methods are only usable within the embedded_can context.
/// Under normal use of the [Can] instance,
impl<'d, T> embedded_can::nb::Can for Can<'d, T>
where
T: Instance,
{
type Frame = CanFrame;
type Error = CanError;

/// Puts a frame in the transmit buffer to be sent on the bus.
///
/// If the transmit buffer is full, this function will try to replace a pending
/// lower priority frame and return the frame that was replaced.
/// Returns `Err(WouldBlock)` if the transmit buffer is full and no frame can be
/// replaced.
fn transmit(&mut self, frame: &Self::Frame) -> nb::Result<Option<Self::Frame>, Self::Error> {
Can::transmit(self, frame)
}

/// Returns a received frame if available.
fn receive(&mut self) -> nb::Result<Self::Frame, Self::Error> {
Can::try_recv(self)
}
}

pub trait SealedInstance: RccPeripheral + RemapPeripheral {
fn regs() -> pac::can::Can;
// Either `0b00`, `0b10` or `b11` on CAN1. `0` or `1` on CAN2.
// fn remap(rm: u8) -> ();
}

pub trait Instance: SealedInstance + 'static {}

pin_trait!(RxPin, Instance);
pin_trait!(TxPin, Instance);

foreach_peripheral!(
(can, $inst:ident) => {
impl SealedInstance for peripherals::$inst {
// FIXME: CH32L1 supports CANFD, and is compatible with the CAN peripheral.
fn regs() -> crate::pac::can::Can {
#[cfg(ch32l1)]
return unsafe { crate::pac::can::Can::from_ptr(crate::pac::$inst.as_ptr()) };
#[cfg(not(ch32l1))]
return crate::pac::$inst;
}
}

impl Instance for peripherals::$inst {
// type Interrupt = crate::_generated::peripheral_interrupts::$inst::GLOBAL;
}
};
);
Loading