Thanks to visit codestin.com
Credit goes to github.com

Skip to content

ports/esp32: CAN(TWAI) driver #9532

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

Closed
wants to merge 1 commit into from
Closed

Conversation

FeroxTL
Copy link

@FeroxTL FeroxTL commented Oct 6, 2022

Implemented CAN bus for esp32 controller.

This is based on #7381 and ports/stm32/pyb_can.c.

All TODOs were implemented and tested using both real devices and programming tests (loopback mode).

APIs between this two implementations (esp32 CAN and stm32 CAN) are made as close as possible.

Some misunderstandings:

  • Tests on real esp32 works only if Rx and Tx pins for CAN bus connected together (as pointed in docs for LOOPBACK_SILENT mode). I didn't find any examples of similar behaviour in another tests
  • The speed of CAN bus should be divided by two in NORMAL mode. So if you have 100k CAN bus the baudrate should be 50000. I think it is a bug in esp-idf -- esp-idf's arduino port works in the same way (should also divide boudrate by two)

@FeroxTL FeroxTL force-pushed the esp32-can branch 4 times, most recently from a0a3587 to daf1682 Compare October 6, 2022 14:23
@IhorNehrutsa
Copy link
Contributor

CAN(TWAI) driver must be in the machine module, same as UART, SPI, I2C and other communications.
https://docs.micropython.org/en/latest/library/machine.html?highlight=machine#classes
CAN bus is not specific to the ESP32 microcontroller.

Please move CAN from the esp32 module to the machine module

@FeroxTL
Copy link
Author

FeroxTL commented Oct 7, 2022

IhorNehrutsa Thank you for your response

Please move CAN from the esp32 module to the machine module

The CAN itself is not platform specific, but current implementation is rather specific.
There are differences in filter API and some functions (like recv()). Also pyb_can implementation is placed in pyb module, that is platform specific. That is why I placed it into esp32 subsystem

That is rather questionable how to combine this API interfaces together if anybody in future would make machine.CAN module, because even pyb_can supports only two can buses at once, what if some board has more?
I just followed API standards that are already in codebase


if (msgs_to_rx == 1) {
// first message in queue
mp_sched_schedule(self->rxcallback, mp_obj_new_int(0));

Choose a reason for hiding this comment

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

The identifier that is passed in the callback should be a constant that is exposed to the python side of things.

You are using mp_obj_new_int(0) in this line and then in the lines below 1 and 2 are being done the same way. It is not going to be know what those values mean.

I think that it would be best to pass the actual message that is received and open up that receive buffer asap. You can collect the message information and pass it as a dictionary. This way you wouldn't have to bother with the 3 different integer values that are being passed to the callback. I would actually empty all of the receive buffers and pass an iterable of dictionaries. if a callback is received for TWAI_ALERT_RX_QUEUE_FULL then empty the buffer.

This would obviously only apply if the user registered a callback. The user should be able to single out what they want callbacks for. It look like having a callback is going to be a requirement in your code because there is no checking of rxcallback

Copy link
Author

Choose a reason for hiding this comment

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

The identifier that is passed in the callback should be a constant that is exposed to the python side of things.

Do you mean I should use MP_OBJ_NEW_SMALL_INT instead if mp_obj_new_int? That is my fault, I'll fix it. It could also be good idea to put values (0, 1, 2) to some constant or definitions

You can collect the message information and pass it as a dictionary.

I would like to make it in this way, but this API interface is made as close as possible to pyb.CAN and according to pyb.CAN rxcallback documentation reason argument should be one of 0, 1, or 2 and python-side code should look like

def cb0(bus, reason):
  if reason == 0:
      print('pending')
  if reason == 1:
      print('full')
  if reason == 2:
      print('overflow')

So there is no way (until massive refactoring and api-breaking changes) to make it.

Choose a reason for hiding this comment

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

I don't believe the STM32 way of handling the callback is the best way to go about doing it. It is dependent on what appears to be a scheduler lock which I would think is going to unlock when it has time to. I would think that would be holding up the interrupt from being released. It also looks like the callback is made from inside the interrupt handler. until it makes the callback. It is also letting the buffer sit there and not get emptied. How large is the receive buffer is it large enough to wait to be emptied?.

If the interrupt pulled the message and passed to to the scheduler to make the call to the callback when here is time to tht would empty the buffer freeing up space and release the interrupt.

But at any rate..

This is the callback handler for the STM32

STATIC void can_rx_irq_handler(uint can_id, uint fifo_id) {
    mp_obj_t callback;
    pyb_can_obj_t *self;
    mp_obj_t irq_reason = MP_OBJ_NEW_SMALL_INT(0);
    byte *state;

    self = MP_STATE_PORT(pyb_can_obj_all)[can_id - 1];

    if (fifo_id == CAN_FIFO0) {
        callback = self->rxcallback0;
        state = &self->rx_state0;
    } else {
        callback = self->rxcallback1;
        state = &self->rx_state1;
    }

    switch (*state) {
        case RX_STATE_FIFO_EMPTY:
            __HAL_CAN_DISABLE_IT(&self->can,  (fifo_id == CAN_FIFO0) ? CAN_IT_FMP0 : CAN_IT_FMP1);
            irq_reason = MP_OBJ_NEW_SMALL_INT(0);
            *state = RX_STATE_MESSAGE_PENDING;
            break;
        case RX_STATE_MESSAGE_PENDING:
            __HAL_CAN_DISABLE_IT(&self->can, (fifo_id == CAN_FIFO0) ? CAN_IT_FF0 : CAN_IT_FF1);
            __HAL_CAN_CLEAR_FLAG(&self->can, (fifo_id == CAN_FIFO0) ? CAN_FLAG_FF0 : CAN_FLAG_FF1);
            irq_reason = MP_OBJ_NEW_SMALL_INT(1);
            *state = RX_STATE_FIFO_FULL;
            break;
        case RX_STATE_FIFO_FULL:
            __HAL_CAN_DISABLE_IT(&self->can, (fifo_id == CAN_FIFO0) ? CAN_IT_FOV0 : CAN_IT_FOV1);
            __HAL_CAN_CLEAR_FLAG(&self->can, (fifo_id == CAN_FIFO0) ? CAN_FLAG_FOV0 : CAN_FLAG_FOV1);
            irq_reason = MP_OBJ_NEW_SMALL_INT(2);
            *state = RX_STATE_FIFO_OVERFLOW;
            break;
        case RX_STATE_FIFO_OVERFLOW:
            // This should never happen
            break;
    }

    pyb_can_handle_callback(self, fifo_id, callback, irq_reason);
}
typedef enum _rx_state_t {
    RX_STATE_FIFO_EMPTY = 0,
    RX_STATE_MESSAGE_PENDING,
    RX_STATE_FIFO_FULL,
    RX_STATE_FIFO_OVERFLOW,
} rx_state_t;

This is how it should be written

STATIC void can_rx_irq_handler(uint can_id, uint fifo_id) {
    mp_obj_t callback;
    pyb_can_obj_t *self;
    byte *state;

    self = MP_STATE_PORT(pyb_can_obj_all)[can_id - 1];

    if (fifo_id == CAN_FIFO0) {
        callback = self->rxcallback0;
        state = &self->rx_state0;
    } else {
        callback = self->rxcallback1;
        state = &self->rx_state1;
    }

    mp_obj_t irq_reason = MP_OBJ_NEW_SMALL_INT(*state);

    switch (*state) {
        case RX_STATE_FIFO_EMPTY:
            __HAL_CAN_DISABLE_IT(&self->can,  (fifo_id == CAN_FIFO0) ? CAN_IT_FMP0 : CAN_IT_FMP1);
            *state = RX_STATE_MESSAGE_PENDING;
            break;
        case RX_STATE_MESSAGE_PENDING:
            __HAL_CAN_DISABLE_IT(&self->can, (fifo_id == CAN_FIFO0) ? CAN_IT_FF0 : CAN_IT_FF1);
            __HAL_CAN_CLEAR_FLAG(&self->can, (fifo_id == CAN_FIFO0) ? CAN_FLAG_FF0 : CAN_FLAG_FF1);

            *state = RX_STATE_FIFO_FULL;
            break;
        case RX_STATE_FIFO_FULL:
            __HAL_CAN_DISABLE_IT(&self->can, (fifo_id == CAN_FIFO0) ? CAN_IT_FOV0 : CAN_IT_FOV1);
            __HAL_CAN_CLEAR_FLAG(&self->can, (fifo_id == CAN_FIFO0) ? CAN_FLAG_FOV0 : CAN_FLAG_FOV1);
            *state = RX_STATE_FIFO_OVERFLOW;
            break;
        case RX_STATE_FIFO_OVERFLOW:
            // This should never happen
            mp_obj_t irq_reason = MP_OBJ_NEW_SMALL_INT(RX_STATE_FIFO_EMPTY);
            break;
    }

I do not believe this line
mp_obj_t irq_reason = MP_OBJ_NEW_SMALL_INT(RX_STATE_FIFO_EMPTY);
should be there but to not break API that is how the function should be written. For readibility purposes this would be better

STATIC void can_rx_irq_handler(uint can_id, uint fifo_id) {
    mp_obj_t callback;
    pyb_can_obj_t *self;
    mp_obj_t irq_reason = MP_OBJ_NEW_SMALL_INT(RX_STATE_FIFO_EMPTY);
    byte *state;

    self = MP_STATE_PORT(pyb_can_obj_all)[can_id - 1];

    if (fifo_id == CAN_FIFO0) {
        callback = self->rxcallback0;
        state = &self->rx_state0;
    } else {
        callback = self->rxcallback1;
        state = &self->rx_state1;
    }

    switch (*state) {
        case RX_STATE_FIFO_EMPTY:
            __HAL_CAN_DISABLE_IT(&self->can,  (fifo_id == CAN_FIFO0) ? CAN_IT_FMP0 : CAN_IT_FMP1);
            irq_reason = MP_OBJ_NEW_SMALL_INT(RX_STATE_FIFO_EMPTY);
            *state = RX_STATE_MESSAGE_PENDING;
            break;
        case RX_STATE_MESSAGE_PENDING:
            __HAL_CAN_DISABLE_IT(&self->can, (fifo_id == CAN_FIFO0) ? CAN_IT_FF0 : CAN_IT_FF1);
            __HAL_CAN_CLEAR_FLAG(&self->can, (fifo_id == CAN_FIFO0) ? CAN_FLAG_FF0 : CAN_FLAG_FF1);
            irq_reason = MP_OBJ_NEW_SMALL_INT(RX_STATE_MESSAGE_PENDING);
            *state = RX_STATE_FIFO_FULL;
            break;
        case RX_STATE_FIFO_FULL:
            __HAL_CAN_DISABLE_IT(&self->can, (fifo_id == CAN_FIFO0) ? CAN_IT_FOV0 : CAN_IT_FOV1);
            __HAL_CAN_CLEAR_FLAG(&self->can, (fifo_id == CAN_FIFO0) ? CAN_FLAG_FOV0 : CAN_FLAG_FOV1);
            irq_reason = MP_OBJ_NEW_SMALL_INT(RX_STATE_FIFO_FULL);
            *state = RX_STATE_FIFO_OVERFLOW;
            break;
        case RX_STATE_FIFO_OVERFLOW:
            // This should never happen
            break;
    }

    pyb_can_handle_callback(self, fifo_id, callback, irq_reason);
}

I am not sure what this is

def cb0(bus, reason):
  if reason == 0:
      print('pending')
  if reason == 1:
      print('full')
  if reason == 2:
      print('overflow')

but if it has anything to do with the rx callback it is incorrectly written

If a buffer overrun did occur it would be incorrectly reported as the buffer being empty and the user would not be able to write code to properly address the problem.

Choose a reason for hiding this comment

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

we are both on the same page as far as how it should work. the ESP32 while it is a peppy MCU a buffer overrun on the CAN interface can occur and it would be far better to collect the messages inside the interrupt handler if it can be done and pass that message to the scheduler to hand it off to the callback when it can.

That makes more sense to me to do it that way. The memory can be allocated for the number of messages it is able to do this with when the callback gets registered. an array of message structs can get made and one of the fields in the struct would be for marking the message as read so the interrupt handler would know if they could use that structure for an incoming message.This gives the user some ability to tweak it to suit their needs. I know you cannot allocate memory in an interrupt handler and I feel this would e an idea way to handle it. a static memory allocation for received messages and that allocation is able to be changed by unregistered the callback and registering it again with different number of messages to allocate.

Or at least give the user the option to do it that way or call the callback directly from the interrupt handler and not schedule the callback this way there is no delay and the user is able to act on it right away.

mp_raise_ValueError("CAN data field too long");
}
tx_msg.data_length_code = length;
tx_msg.identifier = args[ARG_id].u_int;

Choose a reason for hiding this comment

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

checking for proper frame ID. You are forcefully modifying the frame id that a user is passing instead of kicking back an error telling them that the frame id is out of bounds. you are also setting tx_msg.identifier before a check is done as to the id type being used. Seems rather odd to do that twice.

One of the things that is done in DBC files is the setting of bit 30 in the frame id to identify the frame id bit depth. If it is a 1 it's extended and if it is a 0 then it is not. might want to consider using that. just an idea.

Copy link
Author

Choose a reason for hiding this comment

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

tx_msg.identifier = args[ARG_id].u_int;

Oh, that's literally left by chance, I'll fix it.

As for rising exception -- I agree with it, but stm32 implementation has literally the same code, that only converts identifiers to correct intervals (30 or 11 bits). It is even tested in the same way

Choose a reason for hiding this comment

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

Just because of how it is done in the STM32 library does not make it right.

You do have it correct in setting the bit depth properly for both the 29 bit and the 11 bit so that is good. The reason why i suggest against just force setting it is if there is an error in someone's program and it is passing an incorrect identifier you could be stripping off part of the identifier that is valid and thus sending the data to the wrong node. If the node happens to exist on the network there could be behavior that is unwanted and without the error it is going to be difficult to track down the source of the problem.

this error
mp_raise_ValueError("CAN data field too long");
is unfounded. There is no maximum CAN data packet size for the ESP32. That is because of the TWAI_MSG_FLAG_DLC_NON_COMP macro. This allows more then 8 bytes to be sent in a can message. This should be added ad a parameter in the send function.

That is one example of why you cannot have a shared API between the CAN implementations. There is simply too many differences and if you omit features from one or the other there will be functionality that gets lost.

It's like the info function and returning a list of numbers. Well the ESP32 is going to provide a whole lot more information then what the STM32 does. Returning a dictionary or even a named tuple would be a better choice then just a list.

Not it happens to be by off chance that the STM32 and the ESP32 do share using the tseg1 and tseg2 to set the timing registers. Not al of them do. some use 2 different registers and some use 1. it all depends on the manufacturer and what is going to be used in the next up and coming MCU.

I don't personally like using the bs1 and bs2 keywords for the esp32 because this is not what it is called in the esp32 documentation it is called tseg1 and tseg2. and for other CAN interfaces it is called brt1 and btr2. And they are not necessarily calculated in the same way.

Choose a reason for hiding this comment

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

I am in agreement with you o a lot of this because tying to match the STM32 API is crazy because a lot is going to get lost.

For example if one was to keep the API the same then the loopback mode would need to be removed from the STM32 CAN. There is no loopback mode on the ESP32. nor is there a silent loopback mode.

TWAI_MODE_NORMAL
TWAI_MODE_NO_ACK
TWAI_MODE_LISTEN_ONLY

Those are the modes that are available for the ESP32

Alerts and states. ESP32 has these ones that are going to end up going unused.

CAN_STATE_RECOVERING

CAN_ALERT_BUS_OFF
CAN_ALERT_TX_FAILED
CAN_ALERT_BUS_ERROR
CAN_ALERT_ARB_LOST
CAN_ALERT_BUS_RECOVERED
CAN_ALERT_RECOVERY_IN_PROGRESS
CAN_ALERT_TX_SUCCESS
CAN_ALERT_TX_IDLE
TWAI_ALERT_PERIPH_RESET

I believe the ESP32 also has a pin that gets set to either a high or a low I cannot remember which and that pin can get connected to an input pin that can wake the ESP32. That pin can be read to know when the ESP32 should go to sleep if the bus is not active. IDK if that feature is available or not. That right there is a damned handy thing to have if say you are developing something for a car.

I don't see how they can be made to have a common API.

@kdschlosser
Copy link

That's awesome that they finally added a callback for receiving. Happy that has been done.

@IhorNehrutsa

If the can module is to be moved to the machine module then the CAN implementation for the STM32 also has to be moved as well. It is currently located in it's own module called "CAN" The other issue with moving it to the machine module along with the STM32 CAN implementation is the gross differences in how the 2 operate.

I am sifting through the ESP32 and the STM32 CAN code to see if a common API can be hammered out.

Part of the issue with the STM32 CAN is the code was written so it is combining 2 different forms of CAN into a single entry point. It should not have been done this way as it really complicates the heck out of things. CAN FD and CAN are not the same thing and shouldn't have the same entry point.

In order to correct things there is going to have to be API breaking changes made and there is not going to be a way around that.

So this is the choice, change this PR so it makes a can module like is done for the STM32 or break the API for the STM32 and build a common entry point that will handle CAN and a separate entry point for CAN-FD and have them located in the machine module.

@IhorNehrutsa
Copy link
Contributor

@robert-hh
@jonathanhogg
@harbaum
May you be interested in this PR?

@robert-hh
Copy link
Contributor

robert-hh commented Oct 10, 2022

Interested yes, but at the moment I cannot test it lacking suitable CAN hardware for connecting.

@kdschlosser
Copy link

I have 2 esp32's and also 2 can transceiver IC's. I can set up a breadboard and a VM with whatever OS you want and you can run all the tests you want.

@FeroxTL FeroxTL force-pushed the esp32-can branch 2 times, most recently from 3e31c3b to 6921294 Compare October 10, 2022 15:10
@FeroxTL
Copy link
Author

FeroxTL commented Oct 10, 2022

I have updated the issues above + now it is building with idf4.4 and is disabled under idf4.0 because twai.h support is available only for idf4.2+

@kdschlosser
Copy link

I know that the stm32 has the ability to accept only the bitrate in the constructor and it will calculate the timing registers needed to set up the interface. I have attached a file that does the same thing for the ESP32. This one is more in depth than the one that is available for the stm32 as it will calculate the SJW based on what the bus_length and transceiver_delay are. That provides an ideal balance of speed and robustness. it also uses a lot of calculation instead of all iteration to locate possible matches. A single bitrate can have multiple matches depending on what the sample point is. If a sample point below 50.0% is supplied then a CIA compliant sample point will be used.

It handles the different BRP ranges for the different ESP32 versions. so no need to do anything special there I would also recommend not using the built in macros for getting the registers. They are not optimal ones to use and I believe that either one or more of them are actually incorrect. IDK if they have fixed this yet or not. I did open an issue with them about it.

The other thing is depending on what the clock speed is you may not be able to dial exactly into the bitrate that is being requested. an example is 33,000bps/ This is not an achievable bitrate for the ESP32 but there can be a slight variation in the bitrate between nodes. That is what the SJW handles it's to keep things in sync. so there is an allowed deviation that can exist between nodes. The STM32 code does not take this into consideration so unless it finds an exact bitrate match you are outta luck. With this code it will return the closes possible match given the input values.

It is attached. You will have to give it a test as I wrote it almost a year ago and there may still be a couple of glitches in it.

get_bitrate_registers.zip

@IhorNehrutsa
Copy link
Contributor

IhorNehrutsa commented Oct 14, 2022

For info:
MicroPython CAN API #7892

@stko
Copy link

stko commented Nov 8, 2022

@FeroxTL Hi
I'm sorry, I'm not familar with the Micropython nightly builds structure at all, so this might be a silly question: Is there any place where I could download a micropython image (nightly build?) for the ESP32 (Wrover), which does contain the CAN patch? I'm working on making can tester devices out of ESPlay Micros, and now I've realized that I was a little bit to optimistic in regards of the CAN bus support in actual micropython - but I think your solution would solve my problem, wouldn't it?

@kdschlosser
Copy link

I can compile the firmware up for ya. You have to give me a day to do it tho.

@stko
Copy link

stko commented Nov 9, 2022

@kdschlosser That would be great!! You do not have to hurry, I'm actual on a business trip and won't be home before friday. The can tester which I'm currently try to get to work can be found on https://github.com/stko/canspy. It's in an early stage, I've just started... - I'm looking forward to your binary and many thanks in advance!

@FeroxTL
Copy link
Author

FeroxTL commented Nov 9, 2022

2022.10.06.zip

Is there any place where I could download a micropython image (nightly build?) for the ESP32 (Wrover), which does contain the CAN patch?

Attaching a bit outdated version, but it 100% works. Could be flashed with command

esptool.py -p /dev/ttyUSB1 -b 460800 --before default_reset --after hard_reset --chip esp32  write_flash --flash_mode dio --flash_size detect --flash_freq 40m 0x1000 bootloader.bin 0x8000 partition-table.bin 0x10000 micropython.bin

@stko
Copy link

stko commented Nov 9, 2022

2022.10.06.zip
Attaching a bit outdated version, but it 100% works. Could be flashed with command

Great! Sadly I'm on a business trip and don't have the device with me, but I'll try it out as as I'm back home on the weekend!

Thanks a lot!

@stko
Copy link

stko commented Nov 22, 2022

Attaching a bit outdated version, but it 100% works. Could be flashed with command
``
I've forgot to give some feedback - It works like a charm!

@Knight-w-2000
Copy link

I can compile the firmware up for ya. You have to give me a day to do it tho.

I have just dowoloaded the latest nightly build firmware in terms of ESP32-S3-N8R8-OCT-SPRAM.It seems like CAN Module is not implemented yet.Not all the firmware support micropython CAN Module?

@straga
Copy link

straga commented Mar 4, 2023

Work perfectly. For esp32-c3+TJA1050

CAN bus for esp32 controller.
@bjpirt
Copy link

bjpirt commented Mar 19, 2023

I just compiled this branch and it works well with my ESP32 (ESP32-D0WD-V3) - haven't got a transceiver at the moment but my logic analyser can decode the frames fine.

@thalesmaoa
Copy link

I want to test it. Are there any docs?

@FeroxTL
Copy link
Author

FeroxTL commented May 20, 2023

I want to test it. Are there any docs?

There is documentation for this module. Checkout to the brunch (esp32-can) and follow official instructions: https://docs.micropython.org/en/latest/develop/gettingstarted.html#building-the-documentation

@ValentinBirth
Copy link

Hi,
I tried to test it on the ESP-32 with the MCP2515 CAN Bus Modul.
This was the output of the tests.

Device is not initialized
True
CAN(tx=19, rx=23, baudrate=500000kb, mode=LOOPBACK, loopback=1)
False
True
[0, 0, 0, 0, 0, 0, 0]
False [0, 0, 0, 0, 0, 2, 0]
Traceback (most recent call last):
  File "<stdin>", line 47, in <module>
OSError: [Errno 116] ETIMEDOUT: ESP_ERR_TIMEOUT

Im not shure if its the fault of the MCP2515 because its output pins are for SPI or if the setup I did was wrong.

@thalesmaoa
Copy link

The MCP2515 is one driver using SPI. TWAI is the in-built driver. You should only add a transceiver, nothing else.

@ValentinBirth
Copy link

The MCP2515 is one driver using SPI. TWAI is the in-built driver. You should only add a transceiver, nothing else.

Not shure if im getting it right. I have an ESP32 NodeMCU Development Board, and because the MCP2515 uses SPI its not viable in this situation. So do I just need to conmnect the CAN high an low to any gpio or do I need to connect a TJA1050 CAN Controller Interface?

@FeroxTL
Copy link
Author

FeroxTL commented Jun 13, 2023

This module is TWAI driver. The only thing you need to connect is transmitter like VP230 (SN65HVD23x). There are custom boards, they are compatible too
Screenshot_20230613_145457

@kdschlosser
Copy link

@FeroxTL and @thalesmaoa are correct.

OK so the MCP2515 "module" is a can interface IC and also a transceiver packaged onto a board and that board has it's own oscillator on it and it will typically use SPI as an interface. The CAN that is built into MicroPython for the ESP32 is NOT for communicating with one of those kinds of interfaces. It is for using the built in interface the ESP32 has. The built in interface for the ESP32 does not have the transceiver so that would need to be added. It's a small inexpensive IC like what you see in the photo above.

I am sure there is a CAN library available for your interface that runs on MicroPython. Do a search

"MicroPython MCP2515"

You will more than likely find several different implementations.

The problem with the MCP2515 interfaces is that most of the manufacturers only provide an 8mHz oscillator. That means there are a lot of bitrates you will not be able to access. Where as the ESP32 uses an 80mHz clock and that opens up a lot more bitrates.

@kdschlosser
Copy link

Your board looks something like this.

image

image

@kdschlosser
Copy link

The benefit of using the built in one is using 2 pins instead of 5. as well as the larger range of supported bitrates.

@bjpirt
Copy link

bjpirt commented Jun 25, 2023

Any chance we could get this merged into the main branch? Keen to use it in my projects! Just needs some conflicts resolving

@bjpirt
Copy link

bjpirt commented Jul 22, 2023

@FeroxTL Would you mind rebasing to fix the conflicts please? Really keen to get this into the main branch :-)

@hwopereis
Copy link

Hi,

I'm trying to use micropython with CAN to communicate between two custom ESP32 pcbs. With the upload provided by @stko (thank you, it helped a lot in getting started!), I got the communication up and running.
I could also compile the provided code in this PR with ESP-IDF V4.4.5 using Python3, and the tests run fine.

I also tried to import this implementation into the latest masterbranch of micropython. To get it to compile, I had to remove the ESP_IDF MAJOR_VERSION ==4 requirements at two places, and I had to adjust a diferent CMake file (esp32_common.cmake).
Then it compiles and can be flashed.

Running the tests, everything is working fine on the CAN_SILENT, CAN_LOOPBACK and CAN_SILENT_LOOPBACK modes.

However, as soon as I try to run the CAN_NORMAL mode, I get an OSError: (-258, 'ESP_ERR_INVALID_ARG') error. From debugging, it appears that the brp argument is suddenly set to 0 inside the twai_driver_install() function in twai.c (found in the esp-idf package), whereas with the other modes it is properly set (val=16).

The error happens as soon as I run: can = CAN(0, mode=CAN.NORMAL).
It happens also when I run: can = CAN(0, mode=CAN.NORMAL, baudrate=500000)

I've tried to figure out what could be the cause of this, but I haven't found the reason yet.
In the migration guide there is some mentioning with respect to TWAI, but I can't find anything in the code related to these changes (see https://docs.espressif.com/projects/esp-idf/en/latest/esp32/migration-guides/release-5.x/5.0/peripherals.html)

Does anybody have a clue what could be causing this problem?
I'm more than happy to test hypotheses or to do some tests.

@projectgus
Copy link
Contributor

projectgus commented Aug 30, 2023

@FeroxTL Thanks for submitting this driver and keeping it up to date.

We've decided on a plan for a cross-platform machine.CAN driver, rather than individual per-port drivers. Please refer to #12337.

@FeroxTL @IhorNehrutsa I'd like to be sure we include everyone's considerations in the new API. To save me diff-ing many branches, can you please help me understand: Are there significant differences in approach between this PR and #12331 other than the choice of machine vs esp32 for the parent module? I see they were both based on the same older PR.

@IhorNehrutsa
Copy link
Contributor

@projectgus
This PR is copy-past from the early #7381(based on #5310)
I plan to develop port/esp32: Add CAN(TWAI) driver #12331 according to the New machine.CAN driver #12337.
@FeroxTL
You may create PR to the latest #12331

@FeroxTL
Copy link
Author

FeroxTL commented Sep 2, 2023

@projectgus I can confirm that it's just a copy-paste from an old PR. I just adapted it for modern micropython APIs and changed the location from esp32 to machine. I've been using this version for about 6 months in production, and everything works perfectly (except for things I have listed at the beginning of this thread).

Sorry, I do not have much time to support this PR. Tried to merge new changes with fast-forward, but it did not work.

@projectgus
Copy link
Contributor

@FeroxTL No worries at all, I appreciate the confirmation.

@projectgus
Copy link
Contributor

I'm going to close this out, then. We have #12331 open with similar changes against a newer master branch, and plans for the eventual CAN feature that will be merged.

@projectgus projectgus closed this Sep 5, 2023
tannewt pushed a commit to tannewt/circuitpython that referenced this pull request Aug 29, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.