CAN-FD support for esp32s3

gautam-dev-maker
Posts: 3
Joined: Mon Jul 11, 2022 9:47 pm

CAN-FD support for esp32s3

Postby gautam-dev-maker » Tue Dec 27, 2022 12:30 pm

Hi,
I want to use CAN-FD with ESP32S3, since FD is faster than classical CAN. In ESP-IDF, TWAI is compatible with CAN, but not CAN-FD. The TWAI documentation features the following warning:
The TWAI controller is not compatible with ISO11898-1 FD Format frames, and will interpret such frames as errors.
I was wondering if there is any external component for making ESP32S3 compatible with CAN-FD.

Thank you.

DrMickeyLauer
Posts: 167
Joined: Sun May 22, 2022 2:42 pm

Re: CAN-FD support for esp32s3

Postby DrMickeyLauer » Fri May 12, 2023 4:20 pm

Unfortunately there is nothing ready made. Sadly, Espressif seems to ignore the fact that CAN-FD is spreading a lot. (Much like they ditched RMII, which I still can't understand).

What did you end up using? I have to do the same soon, trying to make a MCP251863 (MCP2518fd + transceiver) work with my ESP32C6.

samsonx
Posts: 3
Joined: Fri Jun 26, 2020 7:07 pm

Re: CAN-FD support for esp32s3

Postby samsonx » Mon May 15, 2023 5:58 am

DrMickeyLauer wrote:
Fri May 12, 2023 4:20 pm
Unfortunately there is nothing ready made. Sadly, Espressif seems to ignore the fact that CAN-FD is spreading a lot. (Much like they ditched RMII, which I still can't understand).

What did you end up using? I have to do the same soon, trying to make a MCP251863 (MCP2518fd + transceiver) work with my ESP32C6.
I'm going this same route! Currently have it working with hacked up canbed (separate 2518 and xcvr) am working on a board with the 251863

DrMickeyLauer
Posts: 167
Joined: Sun May 22, 2022 2:42 pm

Re: CAN-FD support for esp32s3

Postby DrMickeyLauer » Wed May 17, 2023 10:27 am

samsonx wrote:
Mon May 15, 2023 5:58 am
DrMickeyLauer wrote:
Fri May 12, 2023 4:20 pm
Unfortunately there is nothing ready made. Sadly, Espressif seems to ignore the fact that CAN-FD is spreading a lot. (Much like they ditched RMII, which I still can't understand).

What did you end up using? I have to do the same soon, trying to make a MCP251863 (MCP2518fd + transceiver) work with my ESP32C6.
I'm going this same route! Currently have it working with hacked up canbed (separate 2518 and xcvr) am working on a board with the 251863
Excellent. Would be cool to hear about your progress. By the way, as I'm working via ESP-IDF, not Arduino, I have yet to find a driver example for the MCP2518fd. I'm not yet working on it, but will be very soon. Perhaps we can share some code for that.

pirrup
Posts: 2
Joined: Thu Jun 08, 2023 10:03 am

Re: CAN-FD support for esp32s3

Postby pirrup » Thu Jun 08, 2023 12:32 pm

DrMickeyLauer wrote:
Wed May 17, 2023 10:27 am
samsonx wrote:
Mon May 15, 2023 5:58 am
DrMickeyLauer wrote:
Fri May 12, 2023 4:20 pm
Unfortunately there is nothing ready made. Sadly, Espressif seems to ignore the fact that CAN-FD is spreading a lot. (Much like they ditched RMII, which I still can't understand).

What did you end up using? I have to do the same soon, trying to make a MCP251863 (MCP2518fd + transceiver) work with my ESP32C6.
I'm going this same route! Currently have it working with hacked up canbed (separate 2518 and xcvr) am working on a board with the 251863
Excellent. Would be cool to hear about your progress. By the way, as I'm working via ESP-IDF, not Arduino, I have yet to find a driver example for the MCP2518fd. I'm not yet working on it, but will be very soon. Perhaps we can share some code for that.
Any progress on this ?

DrMickeyLauer
Posts: 167
Joined: Sun May 22, 2022 2:42 pm

Re: CAN-FD support for esp32s3

Postby DrMickeyLauer » Mon Nov 06, 2023 4:35 pm

Unfortunately no progress yet, but this will hopefully change in the next weeks. By now we have given up on using the internal CAN controller: We have produced a device w/ the builtin CAN controller, but could not achieve reliable timings (for details, see, e.g., https://github.com/espressif/esp-idf/issues/12316). I'm afraid this is a limitation of the Espressif Silicon.

Our new design uses the 2518fd and a matching transceiver. I have yet to write a driver, but I'll start checking out the options soon. There are a few existing drivers, like the generic MCP driver library (for this you will only have to provide the SPI glue logic, which is not much). There's also a pretty comprehensive ESP32/Arduino driver which would have to be rewritten for ESP-IDF, if we were to use it. And also two 3rdparty drivers, one from SeeedStudio and one from another guy. Last but not least, there is the Linux driver which also is pretty comprehensive and has great code quality.

To achieve our reliable timing constraints, we will probably have to utilize the timestamped transmit buffers in the MC2518fd. I did not check yet whether any of these existing libraries support that.

Did you get any further with your project? Since this is getting off-topic, we might want to communicate out of this board. Drop me a mail via mickey (at) vanille.de, if you're interested on collaborating. I'd be fine if this would be an open source driver, but it's not a necessity for us.

DrMickeyLauer
Posts: 167
Joined: Sun May 22, 2022 2:42 pm

Re: CAN-FD support for esp32s3

Postby DrMickeyLauer » Wed Dec 13, 2023 1:59 pm

Update: I have an alpha driver working, which is based on the very brillant https://github.com/Emandhal/MCP251XFD. The MCP2518fd is a great little chip ­– not without weaknesses, but I can't see anything better for CAN(-FD) at this point of time.

gandalf68
Posts: 1
Joined: Tue Apr 16, 2024 10:16 pm

Re: CAN-FD support for esp32s3

Postby gandalf68 » Tue Apr 16, 2024 10:44 pm

Hi there,

i also try to use a MCP2518fd + transceiver with an own PCB and looked at the MCP driver library, the comprehensive ESP32/Arduino driver and to the Emandhal / MCP251XFD things and so on, but i am really new to the esp32 (using the ESP32-S3) SPI driver and not sure how to implement all the register readings and writings to the MCP2518fd via SPI.

Some using only the tx_data/rx_data, others working with cmd and rx/tx_buffers, strange.
I will test my hardware and software so far this week hopefully...:-)

So the questions is: is there anything new to this topic, may some examples how looks like the transaction to read a CAN message from the MCP2518fd RAM or how to read or write registers of the MCP2518fd...

Thanx for some short information.

CU
Frank

DrMickeyLauer
Posts: 167
Joined: Sun May 22, 2022 2:42 pm

Re: CAN-FD support for esp32s3

Postby DrMickeyLauer » Mon Apr 29, 2024 9:08 am

By now I have a solid setup w/ the MCP2518fd. It took me several months, but it's all working now.

Unfortunately I can't share my code, but I should be able to give you some pointers.

I have settled on using https://github.com/Emandhal/MCP251XFD, which is pretty simple to integrate and handles all the nitty low-level details. You just need to provide a single function to communicate via SPI and then use it to write a "real" driver.

My integration function looks like roughly like that:

Code: Select all

static eERRORRESULT mcp251x_spi_transfer(void *pIntDev, uint8_t chipSelect, uint8_t *txData, uint8_t *rxData, size_t size) {
    if (!txData) {
        return ERR__SPI_PARAMETER_ERROR;
    }

    auto object = reinterpret_cast<ESP_MCP251XFD*>(pIntDev);
    auto locker = CriticalSection(object->spiSemaphore);
    auto result = object->spiDevice->transfer(txData, rxData, size);
    if (result != ESP_OK) {
        ESP_LOGE(TAG, "SPI Communication Error: %d", result);
        return ERR__SPI_COMM_ERROR;
    }
    return ERR_NONE;
}
I'm locking all the transfers from this driver, since I may call the driver functions from multiple tasks. My SPI communication's transfer method looks like that:

Code: Select all

esp_err_t Device::transfer(uint8_t* txdata, uint8_t* rxdata, size_t length) {

    spi_transaction_t t = {

        .flags = 0,
        .cmd = 0,
        .addr = 0,
        .length = length * 8, // write register number, write value
        .rxlength = length * 8,
        .user = nullptr,
        .tx_buffer = txdata,
        .rx_buffer = rxdata,
    };
    auto result = spi_device_polling_transmit(handle, &t);
    return result;
}
So you see I'm not using cmd or addr or any translation, but just transparently feeding the commands from the low-level driver to SPI and vice versa.

The basic higher level idea for the driver is that I setup an IRQ connected to the MCP's INT line. This one notifies a high priority task (using task notifications) which then calls the respective lower level functions to read from / send to FIFOs, etc.

Here's the IRQ handler:

Code: Select all

//----------------------------------------------------------------
// ISR
//----------------------------------------------------------------
void IrqHandlerTask::irqHandlerISR(void* arg) {

    auto irqHandler = static_cast<IrqHandlerTask*>(arg);
#ifdef ESP_MCP251XFD_ENABLE_IRQ_DEADLOCK_TIMER
    // Create irq deadlock timer, if not existing
    if (!irqHandler->irqDeadlockTimer) {
        const esp_timer_create_args_t timerConfiguration = {
            .callback = &irqHandlerDeadlockTimerCallback,
            .arg = arg,
            .dispatch_method = ESP_TIMER_TASK,
            .name = "IRQ-Deadlock-Timeout",
            .skip_unhandled_events = false,
        };

        esp_timer_create(&timerConfiguration, &irqHandler->irqDeadlockTimer);
    }
#endif

#ifdef ESP_MCP251XFD_ENABLE_IRQ_DEADLOCK_TIMER
    bool irqActive = !gpio_get_level(irqHandler->device->irqPin);
    if (irqActive) {
        //ESP_EARLY_LOGI("IRQ", "Launching deadlock timer...");
        esp_timer_start_once(irqHandler->irqDeadlockTimer, 1000000);
#endif
        // Wake up IRQ handler task
        auto xHigherPriorityTaskWoken = irqHandler->notifyGiveFromISR();
        portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
#ifdef ESP_MCP251XFD_ENABLE_IRQ_DEADLOCK_TIMER
    } else {
        esp_timer_stop(irqHandler->irqDeadlockTimer);
        //ESP_EARLY_LOGI("IRQ", "Cancelling deadlock timer...");
    }
#endif
}

On top of all that, I have a simple API for the consumers. Here's the header file to give you an idea:

Code: Select all

class ESP_MCP251XFD {

friend class IrqHandlerTask;

    enum Alert: uint8_t {
        None                = 0,
        BusError            = 1 << 0,
        ReceiveOverflow     = 1 << 5,
    };

public:
    static std::unique_ptr<ESP_MCP251XFD> createDevice(std::unique_ptr<SPI::Device> device, gpio_num_t irqPin, uint8_t xtalMHz = 40, bool gpiosControlTransceiverStandby = false);
    /// Start the device in normal mode with the requested `nominal` bitrate and, if mixed CAN/CAN-FD is requested, the `data` bitrate.
    bool start(uint32_t nominal, uint32_t data = 0, uint32_t minimumInterframeInterval = 0);
    /// Start the device in receive-only mode, with the requested `nominal` bitrate and, if mixed CAN/CAN-FD is requested, the `data` bitrate.
    bool startReceiveOnly(uint32_t nominal, uint32_t data = 0);
    /// Stop the device.
    void stop();
    /// Release resources for the MCP251XFD device.
    virtual ~ESP_MCP251XFD();

    // Delete copy constructor (we're encapsulating a hardware resource)
    ESP_MCP251XFD(const ESP_MCP251XFD&) = delete;
    // Delete copy assignment operator (dito)
    ESP_MCP251XFD& operator=(const ESP_MCP251XFD&) = delete;

    /// Auto-detect the bitrate.
    uint32_t autodetectBitrate(std::vector<uint32_t>& bitrates);
    /// Update the acceptance filter.
    bool updateFilter(uint32_t mask, uint32_t pattern);
    /// Update the bitrate.
    bool updateBitrate(uint32_t nominal, uint32_t data = 0, uint32_t minimumInterfaceInterval = 0);
    /// Transmit a single frame.
    bool transmit(const CANMessage& message);
    /// Transmit multiple frames.
    bool transmit(std::vector<CANMessage>& messages);
    /// Receive a single frame.
    bool receive(CANMessage** message, TickType_t timeout = portMAX_DELAY);

    /// Return the current set of alerts and reset it.
    Alert currentAlerts() {
        auto alertAccess = CriticalSection(alertMutex);
        Alert currentAlerts = alerts;
        alerts = Alert::None;
        return currentAlerts;
    }

public:
    uint32_t bitrate;            // in bits per second
    uint32_t sysclk;             // in Hz
    uint32_t interframeInterval; // in microseconds
    uint32_t timestamp();

public /* private */:
    std::unique_ptr<SPI::Device> spiDevice = nullptr;
    MCP251XFD* mcpDevice = nullptr;
    SemaphoreHandle_t spiSemaphore = NULL;
    bool spi_debug_enabled = false;
    bool irqHandlingEnabled = false;

protected:
    void startIRQHandling();
    void handleIRQ(); // Called from IRQ Handler task
    void handleIRQDeadlockTimeout(); // Called from FreeRTOS Timer task
    void stopIRQHandling();
    void sleep();

    void updateAlerts(Alert alert) {
        auto alertAccess = CriticalSection(alertMutex);
        alerts = static_cast<Alert>(static_cast<uint8_t>(alerts) | static_cast<uint8_t>(alert));
    }
    // Transmit a message to the TX FIFO. @returns true, if there is no space.
    bool transmitToFifoIfPossible(const CANMessage& message);

private:
    ESP_MCP251XFD(std::unique_ptr<SPI::Device> device, gpio_num_t irqPin, uint8_t xtalMHz, bool gpiosControlTransceiverStandby);
    
private:
    gpio_num_t irqPin;
    uint8_t xtalMHz;
    bool gpiosControlTransceiverStandby;
    std::unique_ptr<IrqHandlerTask> irqHandler = nullptr;
    std::unique_ptr<Queue<CANMessage*>> receiveQueue = nullptr;

    Mutex outgoingMessagesMutex;
    std::queue<CANMessage> outgoingMessages;

    Mutex alertMutex;
    Alert alerts = Alert::None;

};
The client usually has a mid-priority task which then can block in receive to wait for the next message from the bus.

I have some more C++-abstractions for tasks, SPI bus, SPI master, and stuff. But this is roughly it. The only
critical thing (which took me a while) is to figure out when/how to service the FIFOs, since the MCP IRQ handling
is a bit limited. But in contrast to other solutions I found on github, mine is a fully IRQ-based driver now with no polling whatsoever.

I hope this helps.

Who is online

Users browsing this forum: fengcai and 94 guests