CAN driver proposal
Added support for the CAN driver (tested only on g6u6 model) Added a flag to start the runtime (for some reason qingke does not initialize what it should)
Forgot to mention that CAN implementation primary was taken from this repo: https://github.com/marti157/ch32-can-rs
Maybe we should cite marti157/ch32-can-rs somewhere. @marti157
I'm happy to see an initiative to get my CAN implementation into the HAL, since the CAN implementation already depended on the HAL. Some work needs to be done, especially with the pin remapping. I managed to fully test the implementation on a 208wbu6 device with real CAN hardware, so let me know if you need any help!
I'm testing CAN on 203g6u6 and I found that the filter don't work. Let's say I have:
let filter = CanFilter {
bank: 0,
mode: CanFilterMode::IdMask,
id_value: (0x580 | 0x42) as u32,
id_mask: 0x0FF,
};
As I believe only frames with id equals to 0x5C2 (0x580 | 0x42) should be accepted. But hearbeats' frames of another node are received too. @marti157 Everything is working for you?
I have introduced a new api for filters' configuration (with a bit unsafe code). But I still can't set up the filter correctly (it's probably a hardware bug, but even without the filter receive method, a frame with the ID 0x1101 is created). Only the "accept_all" strategy works for me (I can intercept real message frames).
@paval-shlyk Thanks for your contribution. I've made some refactoring to your work:
Changes made:
- Using generated peripheral definitions
- Using generated pin traits and pin remapping
- Extended support for CH32L1 series, as the peripheral layout is compatible
- removed separate
canfeature flag, to match embassy-stm32
Please review these changes and let me know if everything looks good to proceed with the merge.
@andelf Using the feature flag is, of course, a matter of taste...) I prefer to give the user the ability to customize the library's functionality (as a way to speed up the compilation process) But for ease of use, I agree)
I'm trying to get CAN working on a v203f6p6. Sending does work, but can.receive() always returns a WouldBlock error. Unfortunately, there is no example for receiving. Can anyone give me a hint?
#![no_std]
#![no_main]
use ch32_hal as hal;
use hal::println;
use hal::can::{Can, CanFifo, CanFilter, CanFrame, CanMode, StandardId};
use embedded_can::nb::Can as _;
use qingke::riscv;
#[qingke_rt::entry]
fn main() -> ! {
hal::debug::SDIPrint::enable();
let p = hal::init(hal::Config::default());
let mut can = Can::new(p.CAN1, p.PA11, p.PA12, CanFifo::Fifo1, CanMode::Normal, 500_000).expect("Valid");
can.add_filter(CanFilter::accept_all());
let msg: [u8; 3] = [0xAB, 0xCD, 0xEF];
loop {
let frame = CanFrame::new(StandardId::new(0x42).unwrap(), &msg).unwrap();
match can.transmit(&frame) {
Ok(_) => println!("Sent CAN message {:?}", msg),
Err(_) => println!("Error sending CAN message"),
};
let _ = can.transmit(&frame);
match can.receive() {
Err(err) => println!("Receive error: {:?}", err),
Ok(recv_msg) => println!("Received: {:?}", recv_msg),
}
riscv::asm::delay(1_000_000);
}
}
#[panic_handler]
fn panic(info: &core::panic::PanicInfo) -> ! {
println!("panic: {}", info);
loop {}
}
@jalr Thanks for reporting an issue! Your code seems to be correct. I encountered a similar issue on my board. As far as I remember, it was a hardware misconfiguration. I will try to check the documentation and add examples as soon as I have free time)
It seems like the communication of my setup somehow got stuck. I could make the receiving work now, however when I run the code, it receives the last frame and keeps receiving it. When I send a frame to the ch32, it receives the new frame and keeps receiving it. When I send another frame, nothing changes.
@jalr Sorry for the too long answer. I had the same problem. For me, it was solved by setting a flag that the data has been read:
can.rfifo(fifo).write(|w| {
//set the data was read
w.set_rfom(true);
});
@jalr I checked your code. There is not receive method for can (I have renamed it to try_recv in latest commit as more clarifying; recv method, by analogy with channels, should be blocking). Also embassy-rt integration is under considertion (I will add this in the near future)
I have tested this code with a CH32L103 and can confirm it works fine!
I have found some minor not implemented features in the code. What is the best approach for me to contribute them? Should I wait until this has been merged to main or should I create a PR against paval-shlyk:mai?
For info for uses interested in this. I have also started CANFD support on CH32L103. My code is available here: https://github.com/mickeprag/ch32-hal/tree/mickeprag/canfd
But I think we should try to (finish and) merge this PR first with basic CAN support before looking at my work.
@mickeprag That's great that you've found something not implemented.
I see nothing in your code can break existing functionality. Therefore I'd recommend participating in this PR
@andelf What should we do move this PR?)
@mickeprag That's great that you've found something not implemented.
I see nothing in your code can break existing functionality. Therefore I'd recommend participating in this PR
Since I cannot push to your branch I suggest you cherrypick my two commits: https://github.com/ch32-rs/ch32-hal/commit/ec6633dfddc95b13e236955d8e77021bf4d4f82f https://github.com/ch32-rs/ch32-hal/commit/6c959336182f9c7ee14f19a56d2a095424b4be16
CANFD is not finished yet so I suggest we keep this in a separate PR.
@andelf What should we do move this PR?)
@andelf has approved this PR already. Are you able to merge it?
FYI, I have both CAN FD and async support working now. I really like to get this merged so we can start looking at the next things...
@mickeprag Your code looks good, would be a great addition to the HAL. Will you open a PR after this one has been merged or will you make a draft PR?
Thank you. My plan is to wait for this PR to be merged first then I will create two PRs. One with async support and once that has been merged I will add CAN FD support.
@mickeprag It sounds cool that you have added fd and async!) Thanks a lot!) Can I take your changes to this PR (since mainline maintainers haven't been able to accept this PR for several months; it will be annoying to wait new features more time)
@mickeprag
I have taken your changes for canfd support only as your asynccan branch has incompatible changes causing merge conflicts. Can you fix async support?
Actually I was planning on doing it the other way around. Async support first and CAN FD on top of it later, the reason being I think async support will benefit more people than CAN FD. I have prepared a branch with async support that is ready. I am touching some files outside this driver so we need to see what the maintainers think of it and redo some patches if they don't like it. I will push this to this PR.
I am going away on vacation with my family so I will not be able to finish all things for a couple of weeks.
I have pushed the async version to your branch and I will work in bringing in the canfd support when I am back again. Hopefully the maintainers will be back again soon so we can get some feedback and the possibility to merge this to the main branch...
Nice to see CAN is coming to ch32-hal! I'm testing the paval-shlyk:ch32-hal:main branch, with both CAN interfaces connected to the same bus. Each CAN peripheral can emit frames, and ACK each other's frames, so that's a promising start. Using the CH32V307 dev board BTW.
However, I can't receive anything on CAN2 (at least with the blocking API, didn't try the others).
let mut can1 = Can::new_blocking(
p.CAN1,
p.PA11,
p.PA12,
CanFifo::Fifo0,
CanMode::Normal,
500_000,
Default::default(),
)
.expect("Valid");
let mut can2 = Can::new_blocking(
p.CAN2,
p.PB12,
p.PB13,
CanFifo::Fifo1,
CanMode::Normal,
500_000,
Default::default(),
)
.expect("Valid");
can1.add_filter(CanFilter::accept_all());
can2.add_filter(CanFilter::accept_all());
let mut ticker = Ticker::every(Duration::from_millis(200));
let body1: [u8; 8] = [0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF];
let body2: [u8; 4] = [0xDE, 0xAD, 0xBE, 0xEF];
loop {
let frame1 = CanFrame::new(StandardId::new(0x317).unwrap(), &body1).unwrap();
let frame2 = CanFrame::new(StandardId::new(0x318).unwrap(), &body2).unwrap();
let _ = can1.transmit(&frame1);
match can2.receive() {
Ok(frame) => {
println!("CAN2 Received frame: {:?}", frame);
}
Err(err) => {
println!("CAN2 Error receiving frame: {:?}", err);
}
}
let _ = can2.transmit(&frame2);
match can1.receive() {
Ok(frame) => {
println!("CAN1 Received frame: {:?}", frame);
}
Err(err) => {
println!("CAN1 Error receiving frame: {:?}", err);
}
}
ticker.next().await;
}
Result is
2025-08-12 07:03:48.601: CAN2 Error receiving frame: Timeout
2025-08-12 07:03:48.604: CAN1 Received frame: CanFrame { id: Standard(StandardId(792)), dlc: 4, data: [222, 173, 190, 239, 0, 0, 0, 0], is_remote: false }
2025-08-12 07:03:49.614: CAN2 Error receiving frame: Timeout
2025-08-12 07:03:49.616: CAN1 Received frame: CanFrame { id: Standard(StandardId(792)), dlc: 4, data: [222, 173, 190, 239, 0, 0, 0, 0], is_remote: false }
Not sure if it's a HAL issue or Hardware problem.
Nice to see CAN is coming to ch32-hal! I'm testing the paval-shlyk:ch32-hal:main branch, with both CAN interfaces connected to the same bus. Each CAN peripheral can emit frames, and ACK each other's frames, so that's a promising start. Using the CH32V307 dev board BTW.
However, I can't receive anything on CAN2 (at least with the blocking API, didn't try the others).
Thanks for the testing. Great to see something working with the second interface. Unfortunately I do not have access to a uC with dual CAN so I cannot test this.
My guess is that there must be some register that differs when receiving frames from the second interface that we need to take into account. I will look through the reference manual and see if I can spot something...
@chmousset When looking through the code I remembered I has some issues when using one of the fifos. Maybe you can do some testing to see if this can be the source of the issues before investigating further? Try only activating one CAN interface at a time. Try them using both fifo0 and fifo1. Since you only have one interface active at a time you need to send the frames from some other source than your own microcontroller. So basically test these:
p.CAN1withCanFifo::Fifo0p.CAN1withCanFifo::Fifo1p.CAN2withCanFifo::Fifo0p.CAN2withCanFifo::Fifo1
@mickeprag I tested with a single CAN at a time. In that case, it looks like it's related to FIFO1:
| CAN | FIFO | RX ACK | RX Data | TX |
|---|---|---|---|---|
| p.CAN1 | CanFifo::Fifo0 | ✅ | ✅ | ✅ |
| p.CAN1 | CanFifo::Fifo1 | ✅ | ❌ | ✅ |
| p.CAN2 | CanFifo::Fifo0 | ✅ | ✅ | ✅ |
| p.CAN2 | CanFifo::Fifo1 | ✅ | ❌ | ✅ |
If I test with both CAN, results are a bit weirder:
| CAN1 | CAN2 | CAN1 RX ACK | CAN1 RX Data | CAN1 TX | CAN2 RX ACK | CAN2 RX Data | CAN2 TX |
|---|---|---|---|---|---|---|---|
| CanFifo::Fifo0 | CanFifo::Fifo0 | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ |
| CanFifo::Fifo0 | CanFifo::Fifo1 | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ |
| CanFifo::Fifo1 | CanFifo::Fifo0 | ✅ | ❌ | ✅ | ✅ | ❌ | ✅ |
| CanFifo::Fifo1 | CanFifo::Fifo1 | ✅ | ❌ | ✅ | ✅ | ❌ | ✅ |
So:
- if a single CAN is used, only FIFO0 works with both
- if two CAN are used, only FIFO0 with CAN1 works
However, I noticed something strange: if CAN2 peripherals is active, my "blink" async task stops working. I'm not sure what to make of it. Perhaps is messes up with interrupts or timer peripherals?
@mickeprag did you have the occasion to look into it? Anything I can do to help?
@mickeprag did you have the occasion to look into it? Anything I can do to help?
Your findings are valuable information. Unfortunately it does not make any sense to me. I have tried reading the reference manual but do not understand why. To be honest the CH32 chips are quite new to me so I am afraid I won't be able to solve this without for help from someone more experienced.