-
Notifications
You must be signed in to change notification settings - Fork 218
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
Initial TWAI Driver Implementation #192
Conversation
Awesome progress! Thanks for working on this I personally don't have experience with TAWI/CAN so I hope that someone else can participate. (@jessebraham @MabezDev ?) Maybe one nit-pick alrready would be that identifiers like |
e3c1ce8
to
df907ac
Compare
Sorry for the lack of activity here. Unfortunately I have never worked with CAN/TWAI so I'm not really sure how to approach this 😅 I will at least get an updated PAC published for the C3 this week. Beyond that, I either need to spend some time reading or try to hunt down a colleague who is familiar with this peripheral. I will update again as needed, sorry again for the delays! |
I've published |
Thank you! I appreciate the help. I'm still working through the acceptance filter designs to make it a bit easier to understand without reading the reference manual. Any feedback is appreciated. |
Looks like the embedded-hal team has released the split out version of embedded-can: |
What is the status of this? I skimmed through the code and besides the missing read access on the data registers this seems workable? |
Our colleague @DNedic also took a look at this PR and said it looked fine to him. I would also be happy to help out however I can in getting this PR ready to merge. |
02970cf
to
062754f
Compare
Thanks for having @DNedic take a look at this! I had paused some work on this as I got distracted by embassy and all the interesting work going on between these two repos. Tonight I'm going to go through and add some As for how people can help, I think it might be nice to get some feedback on the filter and bitselector designs. Does it complicate a bitmask too much? Are there better libraries that would be acceptable? Adding support for other models is probably best done in another PR, but I believe expanding to the other models should be pretty straight forward, as it appears they use the same peripheral. This would involve patching the SVD files and testing on those platforms. If someone is interested helping add support for other platforms that would be great. |
eddea20
to
9f0d60c
Compare
Here's my thoughts on this ... then I read the reference manual. That has got to be the most convoluted register layout I have ever seen... My ideas:I tried to come up with an API myself but I'm not overly happy about it either. My thought process was that there's two scenarios we'd like to cover.
This is what I came up with: pub struct SingleStandardFilter {
data: [u8; 8],
}
impl SingleStandardFilter {
pub const fn from_id_bits(bits: BitSelector<false, 11>) -> Self;
pub const fn from_id_pattern(filter: &'static [u8; 11]) -> Self;
pub const fn from_id_raw(code: embedded_can::StandardId, mask: embedded_can::StandardId) -> Self;
pub const fn with_rtr(self, rtr: bool) -> Self;
pub const fn with_first_byte_bits(self, bits: BitSelector<false, 8>) -> Self;
pub const fn with_first_byte_pattern(self, filter: &'static [u8; 8]) -> Self;
pub const fn with_first_byte_raw(self, code: u8, mask: u8) -> Self;
pub const fn with_second_byte_bits(self, bits: BitSelector<false, 8>) -> Self;
pub const fn with_second_byte_pattern(self, filter: &'static [u8; 8]) -> Self;
pub const fn with_second_byte_raw(self, code: u8, mask: u8) -> Self;
}
// Example Usage:
const FILTER: SingleStandardFilter = SingleStandardFilter::from_id_pattern(b"1100xxxxxxxx").with_rtr(false).with_first_byte_pattern(b"xxxxxx01"); In the end it's even more overengineered, so yeah.. not really a recommendation, just some ideas 😃 What other folks do:
So not very helpful for us... PSIt seems the |
Thanks for those ideas, I really like the bytestrings approach! Yeah, I agree, coming from a background with the STM32 C HAL implementation of bxcan, there was a lot of head scratching about the register layout for the filter on the ESP32. To me the bytestrings are more readable than the arrays that I used in bitselector so I'm going to play around with some bytestring implementations tonight and probably drop the bitselector if it goes well. |
Here's the code I wrote when testing the idea :) const fn from_id_pattern(filter: &'static [u8; 11]) -> Self {
let mut acceptance_code = 0u16;
let mut acceptance_mask = 0u16;
let mut idx = 0;
while idx < 11 {
let shift = 15 - idx;
match filter[idx] {
b'1' => {
acceptance_code |= 1 << shift;
acceptance_mask |= 1 << shift;
}
b'0' => {
acceptance_mask |= 1 << shift;
}
b'x' => {}
_ => panic!("filter bits must be eiter '1', '0' or 'x'"),
}
idx += 1;
}
let [code_h, code_l] = acceptance_code.to_be_bytes();
let [mask_h, mask_l] = (!acceptance_mask).to_be_bytes();
SingleStandardFilter {
data: [code_h, code_l, 0, 0, mask_h, mask_l, u8::MAX, u8::MAX],
}
} |
With the good ideas from @dimpolo, I switched to using bytestrings in the implementation for the SingleStandardFilter. I ended up combining the parameters into the single constructor as I want to force the user to make the decision for every available parameter. I'd rather just have the person familiar with their specific application set an explicit value instead of trying to pick appropriate default values (ex: what is an appropriate default rtr bit filter value?). I personally like the bytestrings and if other people like them I'll expand them to the other filter options. |
Do you have ideas how to cater to this scenario?
|
112e62e
to
dee93b4
Compare
I think this is in a good state at the moment and I'm ready to merge.
I don't have any better ideas currently than just setting with raw code and mask so I added an implementation. |
b681637
to
7156659
Compare
Updated driver and example to use PeripheralRef for #249. |
As previously mentioned, I've been testing this driver with a Raspberry Pi, MCP2515 HAT, and Setting up the Pi is pretty easy.
I can then bring up the network interface with Viewing packets can be done with
Sending packets can be done using either
|
Hello @alexbohm, thanks for the PR (and your patience on reviewing!) I didn't have access to an MCP2515 HAT so I tested with two esp32c3's and modified the examples, one which just transmits and another that prints what it received. I'll explain my process, mostly for future me :D, but also to allow others to test. With this method, it's possible to test this without a can transceiver by putting the tx pin on each chip into open-drain, and putting a pull-up on the bus wire. See the diagram below (note that you might get away with the internal pull ups at a low enough baud rate): The code for the transmit example is the same as the supplied one in the PR, but with the following changes:
can_config.set_filter(FILTER);
loop {
let id = StandardId::new(0).unwrap();
println!("Sending can frame with id {:?}", id);
let frame = twai::EspTwaiFrame::new(id, &[0xAA]).unwrap();
let _result = block!(can.transmit(&frame)).unwrap_or_else(|e| { println!("Transmit failed with {:?}", e); None });
delay.delay_ms(1000u32);
}
With the changes I observed the following output: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall this is looks fantastic, just a couple of little bits to take care of and this should be ready to go :)
Co-authored-by: dimi <dimi.polonski@gmail.com>
c8e0fb1
to
d18a5cc
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, thank you!
For some visibility, I wanted to put up this pull request with an initial implementation of a driver for the TWAI peripheral. Here's some of my thoughts on what I have currently:
For the acceptance filters, I'm still thinking through ways of designing this code as there are a couple nuances that aren't initially apparent right now. The acceptance filters have two modes, single filter mode and dual filter mode. Within each of these modes the filter will match both standard ids and extended ids if they have matching bit layouts. This appears to be a limitation of the hardware as the hardware does not seem able to differentiate against id formats in the acceptance filter.
TLDR: I want to avoid situations where a filter is created to only accept a standard id of 0x000, and suddenly later in code extended id packets are received. Perhaps clever naming and documentation such as PartialAcceptanceFilter would be better.
For some background on the TWAI peripheral, most of the setup and communication is done with the TWAI_DATA_x_REG registers. In reset mode, they are used to set the acceptance filter. In operation mode, writing to them prepares data for transmission while reading from them reads received packet data. Because of this shared register layout, the driver does a lot of bitwise operations to encode or decode the packets as the bit layouts are significantly different between modes and frame formats.
This will require a patched pac with read access on the TWAI_DATA_x_REG registers. See: espressif/svd#24 This is patched in main for the esp32c3 pac but not pushed to crates.io yet.
As I only have a ESP32C3 at the moment, I've only tested this driver on that platform.