Skip to content
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

CAN driver proposal #75

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
}
}
164 changes: 164 additions & 0 deletions src/can/can.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
use super::enums::*;
use super::filter::{BitMode, FilterMode};
use super::{CanFilter, CanFrame};
use crate::can::registers::Registers;
use crate::can::util;
use crate::{self as hal, 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)
}

pub fn add_filter<BIT: BitMode, MODE: FilterMode>(&self, filter: CanFilter<BIT, MODE>) {
Registers(T::regs()).add_filter(filter, &self.fifo);
}

/// 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)
}

/// Returns a received frame if available.
pub fn receive(&self) -> nb::Result<CanFrame, CanError> {
if !Registers(T::regs()).fifo_has_messages_pending(&self.fifo) {
return nb::Result::Err(nb::Error::WouldBlock);
}

let frame = Registers(T::regs()).read_frame_fifo(&self.fifo);

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::receive(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;
}
};
);
130 changes: 130 additions & 0 deletions src/can/enums.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum CanError {
/// The peripheral receive buffer was overrun.
Overrun,
// MAC sublayer errors
/// A bit error is detected at that bit time when the bit value that is
/// monitored differs from the bit value sent.
Bit,
/// Bit stuffing error - more than 5 equal bits
Stuff,
/// The CRC check sum of a received message was incorrect. The CRC of an
/// incoming message does not match with the CRC calculated from the received data.
Crc,
/// Form error - A fixed format part of a received message has wrong format
Form,
/// The message transmitted by the FDCAN was not acknowledged by another node.
Acknowledge,
/// CAN is in Bus_Off state.
BusOff,
/// CAN is in the Error_Passive state.
BusPassive,
/// At least one of error counter has reached the Error_Warning limit of 96.
BusWarning,
}

impl core::fmt::Display for CanError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Overrun => write!(f, "The peripheral receive buffer was overrun"),
Self::Bit => write!(f, "Bit value that is monitored differs from the bit value sent"),
Self::Stuff => write!(f, "Sixth consecutive equal bits detected"),
Self::Crc => write!(f, "Calculated CRC sequence does not equal the received one"),
Self::Form => write!(f, "A fixed-form bit field contains one or more illegal bits"),
Self::Acknowledge => write!(f, "Transmitted frame was not acknowledged"),
Self::BusOff => write!(f, "The peripheral is in Bus Off mode"),
Self::BusPassive => write!(f, "The peripheral is in Bus Passive mode"),
Self::BusWarning => write!(f, "A peripheral error counter has reached the Warning threshold"),
}
}
}

impl Into<embedded_can::ErrorKind> for CanError {
fn into(self) -> embedded_can::ErrorKind {
match self {
Self::Overrun => embedded_can::ErrorKind::Overrun,
Self::Bit => embedded_can::ErrorKind::Bit,
Self::Stuff => embedded_can::ErrorKind::Stuff,
Self::Crc => embedded_can::ErrorKind::Crc,
Self::Form => embedded_can::ErrorKind::Form,
Self::Acknowledge => embedded_can::ErrorKind::Acknowledge,
Self::BusOff => embedded_can::ErrorKind::Other,
Self::BusPassive => embedded_can::ErrorKind::Other,
Self::BusWarning => embedded_can::ErrorKind::Other,
}
}
}

impl embedded_can::Error for CanError {
fn kind(&self) -> embedded_can::ErrorKind {
(*self).into()
}
}

#[derive(PartialEq)]
pub enum CanMode {
Normal,
Silent,
Loopback,
SilentLoopback,
}

pub(crate) struct CanModeRegs {
/// Loopback mode setting
pub(crate) lbkm: bool,
/// Silent mode setting
pub(crate) silm: bool,
}

impl CanMode {
pub(crate) fn regs(&self) -> CanModeRegs {
match self {
CanMode::Normal => CanModeRegs {
lbkm: false,
silm: false,
},
CanMode::Silent => CanModeRegs {
lbkm: false,
silm: true,
},
CanMode::Loopback => CanModeRegs {
lbkm: true,
silm: false,
},
CanMode::SilentLoopback => CanModeRegs { lbkm: true, silm: true },
}
}
}

pub enum CanFifo {
Fifo0,
Fifo1,
}

impl CanFifo {
pub(crate) fn val(&self) -> usize {
match self {
CanFifo::Fifo0 => 0,
CanFifo::Fifo1 => 1,
}
}

pub(crate) fn val_bool(&self) -> bool {
match self {
CanFifo::Fifo0 => false,
CanFifo::Fifo1 => true,
}
}
}

#[derive(Debug)]
pub enum TxStatus {
/// Message was sent correctly
Sent,
/// Message wasn't sent correctly due to send timeout
TimeoutError,
/// Message wasn't sent correctly due to arbitration
ArbitrationError,
/// Message wasn't sent correctly due to error
OtherError,
}
Loading
Loading