kevinoconnor / can2040 Goto Github PK
View Code? Open in Web Editor NEWSoftware CAN bus implementation for rp2040 micro-controllers
License: GNU General Public License v3.0
Software CAN bus implementation for rp2040 micro-controllers
License: GNU General Public License v3.0
Hello!
First of all, thank you so much for writing this project! I've been hoping/thinking about this for a while but never had the expertise to make it happen. This allows me to add CAN to a lot of my projects since STM32 lineups are out of stock.
I respect and understand your decision to keep this copyleft via GPL. Unfortunately, this disallows me from encorporating your code into new RP2040 CAN drivers I would like to write for the NuttX and Zephyr RTOS's, which are both Apache 2.0 licensed. My relevant downstream projects with these are open source (I eventually hope to encorporate it into the open source PX4 autopilot which runs on NuttX but cannot do so currently). Would you be willing to license this under a more flexible alternative?
If not, I understand and will probably maintain an out of tree driver, but I would prefer not to.
Thanks!
If I want to change the baudrate after calling can2040_start the first time, is it safe to call the function a second time, with different parameters? Or should I stop the statemachine or even call can2040_setup again before that?
I'm using can2040 on one core and a wifi (lwip_threadsafe_background) on the 2nd core. CANBus data is being replayed on a TCP connection. At 250kbit/s (with a message every 4ms) I get around 0.5% CAN parse errors when TCP connections are running (comparing to near 0 when they are not), I guess that is due to PIO interrupts being handled on the 2nd core in which the cyw43 and lwip are being used.
If this could be the case, is there anyway to force PIO IRQs to being handled in a given core?
// Enable irqs
irq_set_exclusive_handler(PIO0_IRQ_0_IRQn, PIOx_IRQHandler);
NVIC_SetPriority(PIO0_IRQ_0_IRQn, 1);
NVIC_EnableIRQ(PIO0_IRQ_0_IRQn);
Hi All,
I am looking for some insight or guidance on an issue I am running into with the can2040 libraries.
I have noticed that when I attempt to transmit a message, if the ACK
is not detected, the RP2040 becomes unresponsive and needs a reset. It is entirely possible that I am using the library incorrectly, my code can be found below:
My code can be seen below:
#include <stdio.h>
#include <stdint.h>
#include "pico/stdlib.h"
#include "pico/binary_info.h"
#include "hardware/irq.h"
#include "can2040.h"
#include "RP2040.h"
static struct can2040 cbus;
static void can2040_cb(struct can2040 *cd, uint32_t notify, struct can2040_msg *msg)
{
// Add message processing code here...
return;
}
static void PIOx_IRQHandler(void)
{
can2040_pio_irq_handler(&cbus);
}
void canbus_setup(void)
{
uint32_t pio_num = 0;
uint32_t sys_clock = 125000000, bitrate = 500000;
uint32_t gpio_rx = 5, gpio_tx = 4;
// Setup canbus
can2040_setup(&cbus, pio_num);
can2040_callback_config(&cbus, can2040_cb);
// Enable irqs
irq_set_exclusive_handler(PIO0_IRQ_0_IRQn, PIOx_IRQHandler);
NVIC_SetPriority(PIO0_IRQ_0_IRQn, 1);
NVIC_EnableIRQ(PIO0_IRQ_0_IRQn);
// Start canbus
can2040_start(&cbus, sys_clock, bitrate, gpio_rx, gpio_tx);
}
int main(void){
stdio_init_all();
canbus_setup();
struct can2040_msg test_msg = {0x123,8,{1,2,3,4,5,6,7,8} };
can2040_transmit(&cbus,&test_msg);
can2040_transmit(&cbus,&test_msg);
}
When I run the above code, I only see one attempt at transmitting the test message.
My test setup is as follows:
I have tried both with a 120 ohm termination resistor and without.
Here is a Saleae screenshot of the ACK
not being received. For my application, the RP2040 is the first active node on the bus so I expect it to not receive some ACK
bits while the system is still powering up.
I've tried analyzing this behavior with multiple adapters, and have only ever seen one message transmitted.
Is there a way to tell the Pico to ignore the ACK bit? Is there something blatantly wrong with my setup?
Hi @KevinOConnor - thanks again for creating such a great library. I'm currently looking at wrapping it for MicroPython and was hoping to submit a PR to include it by default as I imagine it would be useful for many other people. However, the GPLv3 prevents this, so I was wondering if you would consider licensing it under a more permissive license to allow it to be included in other open source projects? Or providing an exemption for this purpose? Obviously it's your right to license it however you want, but it seems a shame if the license prevents your awesome code being used by even more people.
Maybe a idea to add some examples to get started?
I was having linking errors using this library until I realized there was no extern "C" block included. I think it would be a nice feature thanks!
I initially discovered this by using the arduino wrapper library but then confirmed the same with this library.
This is a transmission at 500k:
Basically it needs to be around 1-2% faster and currently fails to be read by other node. I tried to compensate for this by changing the clock speed parameter and it does work but also leads to incoming messages not received. I understand that my pico clock speed could be off but definitely it looks like transmissions are slower than they have to be. Is there a way to fine tune the timing independently for transmit and receive?
Description:
I've encountered a situation where can2040 consistently remains in an error state, rendering it non-operational. My custom error logs show the following:
xfguo: error(1) on msg: (id: 0, size: 0, data: 0, 0)
xfguo: error(1) on msg: (id: 0, size: 0, data: 0, 0)
xfguo: error(1) on msg: (id: 0, size: 0, data: 0, 0)
xfguo: error(11) on msg: (id: 0, size: 0, data: 0, 0)
xfguo: error(4) on msg: (id: 0, size: 0, data: 0, 0)
xfguo: error(1) on msg: (id: 0, size: 0, data: 0, 0)
xfguo: error(4) on msg: (id: 0, size: 0, data: 0, 0)
xfguo: error(11) on msg: (id: 0, size: 0, data: 0, 0)
xfguo: error(1) on msg: (id: 0, size: 0, data: 0, 0)
xfguo: error(1) on msg: (id: 0, size: 0, data: 0, 0)
xfguo: error(4) on msg: (id: 0, size: 0, data: 0, 0)
xfguo: error(11) on msg: (id: 0, size: 0, data: 0, 0)
How To Reproduce:
If the issue doesn't replicate, you can also try the following steps:
Questions:
Hi,
thanks for this great implementation!
I am, however, struggling to get PIO1 to work (minimal example code see down below). PIO0 works fine but as soon as I set pio_num to 1 (2nd argument in can2040_setup call) and change irq nums from PIO0_IRQ_0 to PIO1_IRQ_0 (in all three lines after "Enable irqs" comment) its not working. I checked using an Oszilloscope: I don't even see any initial signal edges (e.g. of first bit).
Do you see any problem in my code? Is it a problem of this project? Is it a usage error?
Cheers,
Adrian
#include <stdio.h>
#include <intctrl.h>
#include "pico/stdlib.h"
#include "can2040.h"
typedef struct can2040 CANHandle;
typedef struct can2040_msg CANMsg;
static CANHandle can1;
static void can2040_cb1(CANHandle *cd, uint32_t notify, CANMsg *msg) {
printf("can1 callback called. id: &d", notify);
}
static void PIO1_IRQHandler(void) {
can2040_pio_irq_handler(&can1);
}
void canbus_setup() {
uint32_t sys_clock = 125000000, bitrate = 250000;
// Setup canbus
can2040_setup(&can1, 1);
can2040_callback_config(&can1, can2040_cb1);
// Enable irqs
irq_set_exclusive_handler(PIO1_IRQ_0, PIO1_IRQHandler);
irq_set_priority(PIO1_IRQ_0, 1);
irq_set_enabled(PIO1_IRQ_0, true);
// Start canbus
can2040_start(&can1, sys_clock, bitrate, 8, 10);
}
int main() {
stdio_init_all();
sleep_ms(10000);
printf("Startup delay over\n");
printf("Starting Initialization of CAN\n");
canbus_setup();
printf("Initialized CAN1\n");
sleep_ms(1000);
while (true) {
CANMsg msg = {0};
msg.dlc = 8;
msg.id = 34;
msg.data[0] = 11; msg.data[1] = 22; msg.data[2] = 33; msg.data[3] = 44; msg.data[4] = 55; msg.data[5] = 66; msg.data[6] = 77; msg.data[7] = 88;
int res = can2040_transmit(&can1, &msg);
printf("Sending! returned: %d\n", res);
sleep_ms(1000);
}
}
I have already adjusted the CAN rate to 10 kHz, low enough for debugging. The PIO should be capturing a signal every 100 µs, and then the can2040 processes 10 of these signals. So, the time difference I'm logging should be around 1 ms. While most of the logs are accurate, sometimes there's a bump, like the case of 6501 => 9307 you can see.
The code is available here: https://github.com/lamuguo/pico-examples/tree/debug-can2040, and the main program is: https://github.com/lamuguo/pico-examples/blob/debug-can2040/pio/can2040/can_main.c. It is a can2040 hello world. I guess I miss something to enable can2040 PIO to get the correct clock. Any hints?
btw, I can use my simple_gpio (https://github.com/lamuguo/pico-examples/blob/debug-can2040/pio/simple_can/simple_gpio.c) and simple_can (https://github.com/lamuguo/pico-examples/blob/debug-can2040/pio/simple_can/simple_can.c) to get the correct result.
And the message on the bus for debugging is "322#DEADBEAF", sent by cansend:
$ cansend can0 B22#DEADBEAF
Hi,
Any plan to moving the ISR into the RAM to minimise the latency due the cache misses ?
I have created a wrapper library that works with the arduino-pico Arduino core: https://github.com/obdevel/ACAN2040
It has received some limited testing and will sustain 1000 msgs/sec at 125kb/s bus speed. It has been tested with two CAN controllers ICs: MCP2562 (DIP8 package on a breadboard) and SN65HVD230 (using modules available from eBay, etc). We are currently characterising error conditions (e.g. poor wiring, connections, other nodes failing, etc).
It is currently limited to a single bus instance as that is all I need. I may add support for two concurrent instances if there is a demand for this.
I have had to include Kevin's code in my library as there doesn't seem to be a way for an Arduino library to reference external files. This means that I will need to manually track any changes that Kevin makes. One additional #include was required to successfully compile using the toolchain supplied with the arduino-pico core. You don't need to separately install the Pico SDK as this is included with the Arduino core.
Documentation is limited to an example sketch and the code itself. I'm happy to receive bug reports or PRs to fix them.
I haven't issued a formal release, created the library metadata, or registered it with the Arduino project, so it is not available from the Arduino IDE Library Manager. I may do this in the future. For now, you'll have to download and install it manually. The licence is GPL-3.0, in common with this project.
Many thanks to Kevin for this very useful project.
Hey,
First, I would like to say how great this project is. I was trying to run this project the other day on a pi pico connected to a Waveshare SN65HVD230 CAN Board. I followed the example code shown but CMake threw this error
[build] /home/agmui/cs/Robomasters/pico/CANbus/temp/can.c:39:31: error: 'PIO0_IRQ_0_IRQn' undeclared (first use in this function); did you mean 'PIO0_IRQ_0'?
[build] 39 | irq_set_exclusive_handler(PIO0_IRQ_0_IRQn, PIOx_IRQHandler);
[build] | ^~~~~~~~~~~~~~~
[build] | PIO0_IRQ_0
[build] /home/agmui/cs/Robomasters/pico/CANbus/temp/can.c:39:31: note: each undeclared identifier is reported only once for each function it appears in
[build] /home/agmui/cs/Robomasters/pico/CANbus/temp/can.c:40:5: warning: implicit declaration of function 'NVIC_SetPriority' [-Wimplicit-function-declaration]
[build] 40 | NVIC_SetPriority(PIO0_IRQ_0_IRQn, 1);
[build] | ^~~~~~~~~~~~~~~~
[build] /home/agmui/cs/Robomasters/pico/CANbus/temp/can.c:41:5: warning: implicit declaration of function 'NVIC_EnableIRQ' [-Wimplicit-function-declaration]
[build] 41 | NVIC_EnableIRQ(PIO0_IRQ_0_IRQn);
[build] | ^~~~~~~~~~~~~~
[build] make[2]: *** [temp/CMakeFiles/build.dir/build.make:63: temp/CMakeFiles/build.dir/can.c.obj] Error 1
[build] make[1]: *** [CMakeFiles/Makefile2:1805: temp/CMakeFiles/build.dir/all] Error 2
[build] make: *** [Makefile:84: all] Error 2
I saw in the previous issue that you could put PIO0_IRQ_0 and I tried that but when I built it and used an oscilloscope I only got noise.
Is this because I am compiling wrong through CMake and not just using the command shown in the guide?
Here is my code:
#include <stdio.h>
// #include <intctrl.h>
#include "pico/stdlib.h"
#include "can2040.h"
// #include "../../pico-sdk/src/rp2_common/cmsis/stub/CMSIS/Device/RaspberryPi/RP2040/Include/RP2040.h"
// #include "../../pico-sdk/src/rp2_common/cmsis/stub/CMSIS/Core/Include/core_cm0plus.h"
const uint led_pin = 25;
static struct can2040 cbus;
static void
can2040_cb(struct can2040 *cd, uint32_t notify, struct can2040_msg *msg)
{
// Add message processing code here...
}
static void
PIOx_IRQHandler(void)
{
can2040_pio_irq_handler(&cbus);
}
void
canbus_setup(void)
{
uint32_t pio_num = 0;
uint32_t sys_clock = 125000000, bitrate = 1000000;
uint32_t gpio_rx = 3, gpio_tx = 5;
// Setup canbus
can2040_setup(&cbus, pio_num);
can2040_callback_config(&cbus, can2040_cb);
// Enable irqs
irq_set_exclusive_handler(PIO0_IRQ_0_IRQn, PIOx_IRQHandler);
NVIC_SetPriority(PIO0_IRQ_0_IRQn, 1);
NVIC_EnableIRQ(PIO0_IRQ_0_IRQn);
// Enable irqs
// irq_set_exclusive_handler(PIO0_IRQ_0, PIOx_IRQHandler);
// irq_set_priority(PIO0_IRQ_0, 1);
// irq_set_enabled(PIO0_IRQ_0, true);
// Start canbus
can2040_start(&cbus, sys_clock, bitrate, gpio_rx, gpio_tx);
}
void main()
{
canbus_setup();
struct can2040_msg msg;
msg.id = 0x201;//id
msg.dlc = 8;//msg size
msg.data[0] = 0x01;
msg.data[1] = 0xFF;
msg.data[2] = 0x01;
msg.data[3] = 0xFF;
msg.data[4] = 0x01;
msg.data[5] = 0xFF;
msg.data[6] = 0x01;
msg.data[7] = 0xFF;
// Initialize LED pin
gpio_init(led_pin);
gpio_set_dir(led_pin, GPIO_OUT);
while(true){
gpio_put(led_pin, true);
sleep_ms(1000);
gpio_put(led_pin, false);
sleep_ms(1000);
int x = can2040_transmit(&cbus, &msg);
sleep_ms(100);
}
}
Hello Kevin,
great work. Thanks for building the CAN interface. The days of the MCP2515 as the bread and butter CAN controller are counted :-). I used your library in a project in an Arduino. Other people already wrote about the "extern C" setting in the include file. Perhaps you could put it in the header file. It would be big help.
In the wrapper I wrote for my projects ( they allows to use a MCP2515 or your library ), the implementation uses a small inbound queue from the PICO SDK to store away incoming messages. This way the paradigm allows for a "check availability" and "receive" approach. I was wondering if it would make sense to make this kind of queue the default callback handler in case users do not want to write their own callback handler and rather use the "check / receive" paradigm just as they do in Serial IO on Arduino.
Finally ( to the point of this writing ), do you plan or have any insight to use the other core on the PICO for your CAN bus library. I can imagine that your outbound queue and an inbound queue as just described above would build a nice decoupling between the two cores. But I do not know how to set up the "running" of your code on the other core. Any help would be greatly appreciated.
Thanks gain for the great library,
Helmut
Hi, thanks for an excellent driver for the pico. Just one small issue in the API document. I think the example code should read:
// Enable irqs
irq_set_exclusive_handler(PIO0_IRQ_0_IRQn, PIOx_IRQHandler);
The irq_set_exclusive_handler has the arguments swapped.
Hi All,
I'm really looking forward to getting this library working, but I've been fighting with it a decent amount as time goes on.
currently my code looks as follows:
#include "can2040.h"
#include "pico/stdlib.h"
#include "hardware/regs/intctrl.h"
#include <stdio.h>
struct can2040 can;
static void can2040_cb1(struct can2040 *cd, uint32_t notify, struct can2040_msg *msg) {
printf("can callback called. id: &d", notify);
}
static void PIO0_IRQHandler(void) {
can2040_pio_irq_handler(&can);
}
void canbus_setup() {
uint32_t sys_clock = 125000000, bitrate = 250000;
// Setup canbus
can2040_setup(&can, 0);
can2040_callback_config(&can, can2040_cb1);
// Enable irqs
irq_set_exclusive_handler(PIO1_IRQ_0, PIO0_IRQHandler);
irq_set_priority(PIO1_IRQ_0, 1);
irq_set_enabled(PIO1_IRQ_0, true);
// Start canbus
can2040_start(&can, sys_clock, bitrate, 0, 1);
}
int main() {
stdio_init_all();
canbus_setup();
printf("Initialized CAN\n");
sleep_ms(1000);
while (true) {
struct can2040_msg msg = {0};
msg.dlc = 8;
msg.id = 34;
msg.data[0] = 0xff;
msg.data[3] = 0xee;
msg.data[5] = 0x11;
int res = can2040_transmit(&can, &msg);
printf("transmit returned: %d\n", res);
sleep_ms(1000);
}
}
When I run it, I get nothing out of pins 0 and 1, and get a -1 from can2040_transmit()
. This leads me to believe that the PIO FIFO is filling up and it's never starting in the first place. I do get a single pin pulled high, (either 0 or 1), but I'm not sure why that would be.
for other reference, here's the CMakelists.txt for the target itself:
add_executable(can2040test
src/main.c
)
target_link_libraries(can2040test PUBLIC
pico_stdlib
hardware_i2c
can2040
)
pico_add_extra_outputs(can2040test) # output the uf2 file for flashing
pico_enable_stdio_usb(can2040test 1) # enable stdio over usb instead of UART
and the CMakelists.txt for my generic libraries folder
# can2040
add_library(can2040 can2040/src/can2040.c)
target_include_directories(can2040 PUBLIC
can2040/src/
can2040/pio/
../sdk/pico-sdk/src/rp2_common/cmsis/stub/CMSIS/Device/RaspberryPi/RP2040/Include/
../sdk/pico-sdk/src/rp2_common/cmsis/stub/CMSIS/Core/Include/
)
target_link_libraries(can2040 PUBLIC
cmsis_core
hardware_irq
pico_stdlib
hardware_pio
)
Thanks in advance!
Zach
Hello Kevin
I am writing a program using can2040.
An error is intentionally generated at the time of transmission and reception, and the behavior is confirmed.
If an error occurs during transmission, recovery is not possible.
I also tested it with Klipper.
cangen can0 -g 2 -e -I 00EF0102 -L 8 -D 0000000000000000
It stops when an error is generated on purpose.
The command stops and prints the following comment:
write: No buffer space available
After this I can't restore without unplugging and plugging the USB.
Can I restart with can2040 software?
During some experimentation, I discovered that the callback function may trigger with CAN2040_NOTIFY_TX even when the bus is physically disconnected. I had previously sent some frames and expected they'd be stuck in the output buffer. The spurious notification seems to happen unpredictably some time later, or not.
Is this due to floating inputs on the transceiver appearing to be an ack from another (non-existent) node and confusing the state machine ?
This is not a problem for me; merely a curious observation. Although the output buffer being full for some seconds could be a useful indicator of disconnection from the bus or wiring problems, by implication.
Thank you for this amazing library - it's a game changer for me with this processor.
I am using the arduino wrapper version of this library for clarity.
I seem to be getting error frames after virtually every Tx from BusMaster to the device. The data is received by the RP2040 fine, so there is no issue there, but the error flags are being raised, which will cause issues if it was added to a bigger bus - this doesn't happen with exactly the same set up, but exchanging the RP2040 for a teensy 4.1 and using FlexCANT4.
The set up/config I am using is as follows;
Sender: Busmaster -> Peak CAN FD, 1MBaud -> 120Ohm terminator
Receiver: 120Ohm terminator -> SN65HVD Transceiver -> Pi Pico pins 18RX, 19TX
The errors apear after a single send, as well as with cyclic transmissions and seems to cause timing issues with the send on cyclic.
Sending from the device seems to be ok, with perfect timing on the cyclic send;
Please let me know if there is any more information I can collect for this issue to resolve it - thank you again!
Edit: I updated the ACAN2040 library can2040.h and can2040.c files to your latest, using the following wrapper in ACAN2040.h to remove compilation errors for the IRQ handling, but still have the same issues when sending to the RP2040 from another device.
extern "C" { #include "can2040.h" }
Hi,
Any plan to add a "max retry count" when transmitting ? (like on a empty BUS)
anding a new notify like CAN2040_NOTIFY_TX_CANCELLED when the given packet was cancelled.
Hello.
I have a phenomenon that I don't understand.
After can2040 is in receiving state and returns Ack to other device's transmission packet, Ack-like waveform appears again.
Waveform during normal and error. (250kbps / 2 nodes)
This phenomenon occurs once every 700000~1000000 packets.
What could be the cause?
The CAN spec requires that the transmission of a message is complete at the end of the last bit of EOF, but that the reception of a message is complete at the end of the second-to-last bit of EOF (see 11989 section 10.7). It has to be this way because of CAN's atomicity property. There is a consequential feature of the protocol of the Double Receive Problem. See here for more on that: https://kentindell.github.io/2020/07/11/can-atomic-multicast/
Hi,
First of all big thanks for writing software implementation of can. This is something i hope existed earlier but nonetheless cheers! I just wanted to know if are you planning to make microcontroller agnostic. I myself is tempted to port this version to use it in one of my project.
I am using the library to perform some CAN transmissions between two RP2040-based devices. Before transmitting any CAN message, I verify whether it is possible to perform the transmission with "can2040_check_transmit" function. This works fine from the beginning, but after some random time the function "can2040_check_transmit" starts to return as there is no space available to transmit, making the transmission impossible.
I believe this is due to some messages that were transmitted and never acknowledged back. I am trying to find a solution like restarting the CAN on RP2040 and I noticed the presence of a "can2040_shutdown" function, however that one is empty ...
Do you have any solution or any workaround for this issue?
Thanks in advance.
Hi Kevin,
thanks for your great code!
I used it to create NMEA2000_can2040 as a driver library for the excellent NMEA2000 library by Timo Lappalainen1.
In case you might look at my code, bear with me, as I am not proficient in C++ - it is fine for me if it works. I head no special project in mind, but found it interesting to get some of Timo's examples running on an RP2040.
I also saw CAN2040 used in Klipper CanBoot and really like the idea. A lot of commercial NMEA2000 manufacturers offer firmware update possibilities via NMEA2000/CAN, but there is no standard way (hope someone will eventually share some logged data with the CanBoat project to show how things work).
With kind regards from Hamburg,
Sönke Peters
NMEA2000 is a CAN standard for instrument data on (sailing, motor, leisure) boats. Its standard is "protected" by an organisation with hefty membership fees, so free implementations must rely mainly on sniffed data, and scarce documentation snippets of manufacturers. ↩
Hi, thanks for the great library!
Are there plans for a micropython version?
*The implementation uses one of the two rp2040 PIO hardware blocks. It is possible for a single rp2040 chip to have two separate CAN bus interfaces by using both PIO blocks.
Example: i have 2 canbus toolheads. I run skr pico with usb connected raspi. I know i can connect 1 toolhead with usb to canbus bridge mode. But i want to connect 2 canbus toolheads to skr pico. Is this possible?
Sending a CAN frame with no other devices on the network (resulting in no acknowledgement to the frame) will lock up the transmit queue. Even after another device joins the network, no messages can be transmitted from the Pico.
Raspberry Pi Pico connected to an MCP2562 transceiver (with external pull-up on the TX pin) with no other devices on the network. This bug also occurs if the TX and RX pins are directly connected together on the Pico with no transceiver.
At startup, can2040 is initialized, and a CAN frame (DLC of 1) is sent every second, incrementing the data byte.
Example code demonstrating this code can be found in this repository (rjp5th/can2040-loopback).
The first CAN frame is re-transmitted until a device on the network ACKs the frame.
The first CAN frame is transmitted twice. After this point no additional CAN frames are transmitted, even if another device is added to the network. Because the packet is never re-transmitted, an acknowledgment cannot be received, so the first message will never be marked as complete.
A logic trace shows this behavior below:
In the example repository, logging was added into the CAN2040 (using Trice for low-latency logging in interrupts), with the log output captured here.
According to the logs, everything works as expected for the first transmitted message (Target timestamp 1162751 us). Before this point, can2040 was initialized (1152092 us) and the first heartbeat message is sent. The message is sent (1162389 us), and received back (1162652 us). During the processing of the ACK bits, the parse state enters MS_DISCARD (1162721 us) as the message was not ACKed. The maytx irq is fired (1162748 us) since the bus is idle from the last transmit, so the previous frame is queued again (1162749 us). The irq routine now exits (1162751 us).
After this point, on the second transmission, the bug appears. The parse state is in MS_DISCARD from the previous frame's ACK error. data_state_line_passive
is called from the EOF/inter-frame spacing before the new transmission (1162758 us). However, instead of detecting a new start-of-frame from the current transmission, the data is dropped and continues to wait for a new start-of-frame (1162759 us). Because the frame has been transmitted but not detected by the input state tracking, it continues to wait for the transmitted frame.
This results in a deadlock between the transmit and receive states, with the transmit waiting for the transmitted message to be received by the input state tracking, and the input state tracking waiting for the transmitter to send the queued message. This will never recover as tx_state=TS_QUEUED
, but pio_tx_did_conflict
will return false (since it was able to successfully send the frame onto the bus), so the transmit will not send another message until the previous message is received, thus locking the TX queue.
First of all I would like to say you did a great job with this implementation.
Straight to the topic: Do you intend to create a Arduino LIB for direct import in Arduino IDE?
Hi,
pls add this file to the project to allow importing as a library (like RTOS, and so on..) into the project.
usage: add few lines to the main CMakeLists.txt of the project as follows
#Add the next line after pico_sd_init ()
include(path_to_the/can2040-import.cmake)
target_link_libraries ( your-project
your_lib1
your_lib2
.
can2040
.
your_libx
)
As a newbie in the PICO and specifically the PICO_W environment a CMakeList.txt file would be extremely helpful to build can2040.
Hello people,
I am trying to establish a communication using 2 RPi Picos (VP230) and 1 Arduino UNO (MCP2515) on the same bus.
I have tried to run the basic example that was provided on different sources. It works for me for basic transmission. I also have a Picoscope on which i am reading the data where i can see that the transmission on the bus.
Also i would like to implement Remote frame transmission and reception but i feel there is lack of documentation on this aspect. It would be great if someone could provide an example code or their own implementation.
Thanks
For reference, this is my implementation of main.c:
#include <RP2040.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <pico/stdlib.h>
#include <pico/multicore.h>
#include <hardware/pio.h>
#include <hardware/dma.h>
#include <hardware/uart.h>
#include <hardware/flash.h>
#include <hardware/watchdog.h>
#include <pico/bootrom.h>
#include <hardware/irq.h>
#include <core_cm0plus.h>
#include "hardware/clocks.h"
#include <hardware/regs/intctrl.h>
#include "can2040.h"
#define CANMSG_DATA_LEN(msg) ((msg)->dlc > 8 ? 8 : (msg)->dlc)
typedef struct can2040 CANHandle;
typedef struct can2040_msg CANMsg;
static CANHandle cbus;
bool flag = 0;
uint8_t data_len = 0;
uint8_t rbuf[8];
volatile bool cb_called = false;
volatile uint32_t cb_notify;
struct can2040_msg tx_msg, rx_msg;
/// format CAN message as a string
char *msg_to_str(struct can2040_msg *msg) {
static char buf[64], buf2[8];
sprintf(buf, "[ %lu ] [ %lu ] [ ", msg->id, msg->dlc);
for (uint32_t i = 0; i < msg->dlc && i < 8; i++) {
sprintf(buf2, "%u ", msg->data[i]);
strcat(buf, buf2);
}
strcat(buf, " ] ");
if (msg->id & CAN2040_ID_RTR) {
strcat(buf, "R");
}
if (msg->id & CAN2040_ID_EFF) {
strcat(buf, "X");
}
return buf;
}
static void can2040_cb(CANHandle *cd, uint32_t notify, CANMsg *msg)
{
cb_called = true;
cb_notify = notify;
if (notify == CAN2040_NOTIFY_RX) {
rx_msg = *msg;
}
return;
}
static void PIO0_IRQHandler(void)
{
can2040_pio_irq_handler(&cbus);
}
void
canbus_setup(void)
{
uint32_t pio_num = 0;
uint32_t bitrate = 250000;
uint32_t gpio_rx = 1, gpio_tx = 0;
// Setup canbus
can2040_setup(&cbus, pio_num);
can2040_callback_config(&cbus, can2040_cb);
// Start canbus
can2040_start(&cbus, clock_get_hz(clk_sys), bitrate, gpio_rx, gpio_tx);
// Enable irqs
irq_set_exclusive_handler(PIO0_IRQ_0_IRQn, PIO0_IRQHandler);
NVIC_SetPriority(PIO0_IRQ_0_IRQn, 1);
NVIC_EnableIRQ(PIO0_IRQ_0_IRQn);
}
int main(){
stdio_init_all();
sleep_ms(1000);
printf("Starting Initialization of CAN\n");
canbus_setup();
printf("Initialized CAN\n");
sleep_ms(1000);
while (true) {
uint8_t command = getchar_timeout_us(0);
if(command == 'u' || command == 'U'){
CANMsg txmsg = {0};
txmsg.dlc = 8;
txmsg.id = 0x202;
txmsg.data[0] = 0xDE;
txmsg.data[1] = 0xAD;
txmsg.data[2] = 0xBE;
txmsg.data[3] = 0xEF;
txmsg.data[4] = 0xDE;
txmsg.data[5] = 0xAD;
txmsg.data[6] = 0xBE;
txmsg.data[7] = 0xEF;
if(can2040_check_transmit(&cbus)){
int res = can2040_transmit(&cbus, &txmsg);
printf("Returned: %d\n", res);
}
printf("not transmittin\n");
}
else if(command == 't'){
for(int i=0; i<8; i++){
printf("%X ", rx_msg.data[i]);
}
printf("\n");
}
if (cb_called) {
cb_called = false;
printf("callback called\n");
switch (cb_notify) {
// received message
case CAN2040_NOTIFY_RX:
printf("cb: received msg: %s\n", msg_to_str(&rx_msg));
for(int i=0; i<8; i++){
printf("%X ", rbuf[i]);
}
printf("\n");
break;
// message sent ok
case CAN2040_NOTIFY_TX:
printf("cb: message sent ok\n");
break;
// error
case CAN2040_NOTIFY_ERROR:
printf("cb: an error occurred\n");
break;
// unknown
default:
printf("cb: unknown notification = %lu\n", cb_notify);
}
}
}
}
Thanks for making this library!
I was doing a simple test where I set up a can2040 on PIO0 and another on PIO1 and then wired them together, using SN65HVD1040 breakout boards as the transceivers. However, when I analyzed the signals I kept seeing messages getting resent because of no acknowledgement NAK. At 1mbps bitrate and 100hz message frequency, the CAN messages get resent like this
It seemed that whether NAK occurred or not depended on the bitrate and message frequency. At 1mbps and 10hz and 100hz message rates I saw the repeat messages, sometimes up to 3 attempts. However at 1khz message rate, I did not see any repeat messages. At 500 kbps and 10 hz, 100 hz, and 1khz message rates I did not see any repeats either.
My code is here: https://github.com/Nate711/can2040-test, in particular, the dual_can_test.cc
executable
Any guess as to why this is happening at the lower message rates at 1mbps? IRQ latency due to both CAN network IRQs triggering at the same time? Anything I can do to mitigate this? I should also add that my end goal is to control actuators via 2 CAN networks from one Pico, so maybe the IRQs won't be firing in unison so much in my real application. Thanks!
Hello, Kevin.
Good job, your project help me to reduce my work time. I want to know if I adjust RP2040 to 250Mhz, will there be any problem? If there is a problem, which parameter should I adjust?
I am getting following error when compiling the library:
Compiling .pio/build/rp2040/libdb9/can2040/can2040.c.o
.pio/libdeps/rp2040/can2040/src/can2040.c: In function 'tx_schedule_transmit':
.pio/libdeps/rp2040/can2040/src/can2040.c:666:9: warning: implicit declaration of function '__DMB' [-Wimplicit-function-declaration]
666 | __DMB();
| ^~~~~
Archiving .pio/build/rp2040/libdb9/libcan2040.a
Indexing .pio/build/rp2040/libdb9/libcan2040.a
Linking .pio/build/rp2040/firmware.elf
/home/danman/.platformio/packages/toolchain-gccarmnoneeabi/bin/../lib/gcc/arm-none-eabi/9.2.1/../../../../arm-none-eabi/bin/ld: .pio/build/rp2040/libdb9/libcan2040.a(can2040.c.o): in function `tx_schedule_transmit':
can2040.c:(.text.tx_schedule_transmit+0x4e): undefined reference to `__DMB'
collect2: error: ld returned 1 exit status
*** [.pio/build/rp2040/firmware.elf] Error 1
I managed to get rid of the error by following patch:
diff --git a/src/can2040.c b/src/can2040.c
index c2bd006..8b4abd5 100644
--- a/src/can2040.c
+++ b/src/can2040.c
@@ -14,7 +14,7 @@
#include "hardware/structs/padsbank0.h" // padsbank0_hw
#include "hardware/structs/pio.h" // pio0_hw
#include "hardware/structs/resets.h" // RESETS_RESET_PIO0_BITS
-
+#include "cmsis_gcc.h"
/****************************************************************
* rp2040 and low-level helper functions
Is this the correct way? Is the include really missing?
Hi!,
i just found you library. up to now i used an MCP2515 controller on the RP2040 but maybe i might switch to this.
from your experience implementing this, would you say it could be possible to support CanFD ?
from standard can to CanFD the Baudrate of the Payload is increased and the payload can be up to 64 bytes long.
is that to much for the PIO ?
thanks for the Great Work ;]
can2040 looks like it could help me with a project that reads data from my motorcycle with an Arduino Nano Connect. Is there a description of a reference application with a single-board microcontroller somewhere?
Thanks
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.