Page 1 of 1

Dual TWAI with Arduino ESP32

Posted: Wed Sep 18, 2024 1:05 pm
by Fusion
Hi everyone!
In my search for some dual TWAI examples for Arduino, I ran into https://github.com/outlandnish/esp32_can which is a fork of the well known https://github.com/collin80/esp32_can/, however this fork was last updated way before the recent release of arduino-esp32 based on IDF 5.2.0+ in 3.1.0-RC1 and probably not tested.
After replacing twai_general_cfg with g_config etc., it unfortunately returns this error:

Code: Select all

/Documents/Arduino/libraries/esp32_can/src/esp32_can_builtin.cpp: In constructor 'ESP32CAN::ESP32CAN(gpio_num_t, gpio_num_t, uint8_t)':
/Documents/Arduino/libraries/esp32_can/src/esp32_can_builtin.cpp:57:16: error: request for member 'controller_id' in '((ESP32CAN*)this)->ESP32CAN::bus_handle', which is of pointer type 'twai_handle_t' {aka 'twai_obj_t*'} (maybe you meant to use '->' ?)
   57 |     bus_handle.controller_id = busNumber;
In similar code for IDF, controller_id is assigned to g_config, however I do not understand how the two controllers are supposed to be distinguished with one bus_handle. The IDF examples use twai_bus_0 and twai_bus_1.
The idea here though is to also distinguish boards that have one TWAI controller vs two, and boards that have an external MCP controller..

Code: Select all

#define Can0 CAN0

#if (SOC_TWAI_CONTROLLER_NUM == 2 and ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0)) or defined (HAS_EXTERNAL_CAN_CONTROLLER)
#define Can1 CAN1
#endif

#if (SOC_TWAI_CONTROLLER_NUM == 2 and ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0)) and defined (HAS_EXTERNAL_CAN_CONTROLLER)
#define Can2 CAN2
#endif
When I replace

Code: Select all

bus_handle.controller_id = busNumber;
with

Code: Select all

g_config.controller_id = busNumber;
I get rid of the error, but it seems that the controller ID is always 1 as the first instance with ID 0 gets overwritten.

Could someone more familiar with Arduino IDE be willing to have a look at this? I have the hardware to test it, but not the in-depth knowledge of how both controllers should be handled.

The files in question:
https://github.com/outlandnish/esp32_ca ... 32_can.cpp
https://github.com/outlandnish/esp32_ca ... sp32_can.h
https://github.com/outlandnish/esp32_ca ... uiltin.cpp
https://github.com/outlandnish/esp32_ca ... _builtin.h

Thanks!

Re: Dual TWAI with Arduino ESP32

Posted: Wed Sep 18, 2024 3:42 pm
by Fusion
I have changed a few things in esp32_can_builtin.cpp, but it looks like it isn't switching the controller id.

Code: Select all

17:28:30.013 -> Initializing ...
17:28:30.111 -> TWAI V2 driver installed with controller ID: 0, TX: 17, RX: 16, bus: 1082212888
17:28:30.111 -> TWAI V2 driver started with controller ID: 0, TX: 17, RX: 16
17:28:30.111 -> Builtin CAN0 Init OK ...
17:28:30.209 -> TWAI V2 driver installed with controller ID: 0, TX: 19, RX: 18, bus: 1082214008
17:28:30.209 -> TWAI V2 driver started with controller ID: 0, TX: 19, RX: 18
17:28:30.209 -> Builtin CAN1 Init OK ...
17:28:30.209 -> Ready ...!
17:28:30.307 -> Guru Meditation Error: Core  0 panic'ed (Load access fault). Exception was unhandled.
This is the .ino example

Code: Select all

#include "esp32_can.h"

void setup() {
  delay(3000);
  Serial.begin(115200);
  Serial.println("Initializing ...");

  pinMode(GPIO_NUM_0, OUTPUT);
  digitalWrite(GPIO_NUM_0, LOW); //enable CAN0 (RS) / HIGH for standby mode
  pinMode(GPIO_NUM_3, OUTPUT);
  digitalWrite(GPIO_NUM_3, LOW); //disable CAN0 Shutdown (SHDN), not mandatory / HIGH for shutdown mode
  CAN0.setCANPins(GPIO_NUM_16, GPIO_NUM_17);

  // Initialize builtin CAN0 controller at the specified speed
  if (CAN0.begin(500000)) {
    Serial.println("Builtin CAN0 Init OK ...");
  } else {
    Serial.println("BuiltIn CAN0 Init Failed ...");
  }

  pinMode(GPIO_NUM_1, OUTPUT);
  digitalWrite(GPIO_NUM_1, LOW); //enable CAN1 (RS) / HIGH for standby mode
  pinMode(GPIO_NUM_2, OUTPUT);
  digitalWrite(GPIO_NUM_2, LOW); //disable CAN1 Shutdown (SHDN), not mandatory / HIGH for shutdown mode
  CAN1.setCANPins(GPIO_NUM_18, GPIO_NUM_19);
	
  // Initialize bultin CAN1 controller at the specified speed
  if (CAN1.begin(500000)) {
    Serial.println("Builtin CAN1 Init OK ...");
  } else {
    Serial.println("BuiltIn CAN1 Init Failed ...");
  }

  CAN_FRAME txFrame;
  txFrame.rtr = 0;
  txFrame.id = 0x123;
  txFrame.extended = false;
  txFrame.length = 4;
  txFrame.data.uint8[0] = 0xFF;
  txFrame.data.uint8[1] = 0xFF;
  txFrame.data.uint8[2] = 0xFF;
  txFrame.data.uint8[3] = 0xFF;
  CAN0.sendFrame(txFrame);

  // CAN0.setRXFilter(0, 0x0d, 0x0c, false);
  // CAN0.watchFor(0x0d, 0x0c);
  CAN0.watchFor(); //allow everything else through
  // CAN0.setCallback(0, handleCAN0CB);

  // CAN1.setRXFilter(0, 0x230, 0x7F0, false);
  // CAN1.watchFor(); //allow everything else through
  // CAN1.setCallback(0, handleCAN1CB);

  Serial.println("Ready ...!");
}
And the modified esp32_can_builtin.cpp

Code: Select all

/*
  ESP32_CAN.cpp - Library for ESP32 built-in CAN module
    Now converted to use the built-in TWAI driver in ESP-IDF. This should allow for support for the
    full range of ESP32 hardware and probably be more stable than the old approach.
  
  Author: Collin Kidder
  
  Created: 31/1/18, significant rework 1/5/23
*/

#include "Arduino.h"
#include "esp32_can_builtin.h"

twai_handle_t twai_bus_0;
twai_handle_t twai_bus_1;
                                                                 //tx,         rx,           mode
twai_general_config_t g_config = TWAI_GENERAL_CONFIG_DEFAULT(GPIO_NUM_17, GPIO_NUM_16, TWAI_MODE_NORMAL);
twai_timing_config_t t_config = TWAI_TIMING_CONFIG_500KBITS();
twai_filter_config_t f_config = TWAI_FILTER_CONFIG_ACCEPT_ALL();

QueueHandle_t callbackQueue;
QueueHandle_t rx_queue;

//because of the way the TWAI library works, it's just easier to store the valid timings here and anything not found here
//is just plain not supported. If you need a different speed then add it here. Be sure to leave the zero record at the end
//as it serves as a terminator
const VALID_TIMING valid_timings[] = 
{
    {TWAI_TIMING_CONFIG_1MBITS(), 1000000},
    {TWAI_TIMING_CONFIG_500KBITS(), 500000},
    {TWAI_TIMING_CONFIG_250KBITS(), 250000},
    {TWAI_TIMING_CONFIG_125KBITS(), 125000},
    {TWAI_TIMING_CONFIG_800KBITS(), 800000},
    {TWAI_TIMING_CONFIG_100KBITS(), 100000},
    {TWAI_TIMING_CONFIG_50KBITS(), 50000},
    {TWAI_TIMING_CONFIG_25KBITS(), 25000},
    //caution, these next entries are custom and haven't really been fully tested yet.
    //Note that brp can take values in multiples of 2 up to 128 and multiples of 4 up to 256
    //TSEG1 can be 1 to 16 and TSEG2 can be 1 to 8. There is a silent +1 added to the sum of these two.
    //The default clock is 80MHz so plan accordingly
    {{.brp = 100, .tseg_1 = 7, .tseg_2 = 2, .sjw = 3, .triple_sampling = false}, 80000}, 
    {{.brp = 120, .tseg_1 = 15, .tseg_2 = 4, .sjw = 3, .triple_sampling = false}, 33333},
    //this one is only possible on ECO2 ESP32 or ESP32-S3 not on the older ESP32 chips
    {{.brp = 200, .tseg_1 = 15, .tseg_2 = 4, .sjw = 3, .triple_sampling = false}, 20000},
    {TWAI_TIMING_CONFIG_25KBITS(), 0} //this is a terminator record. When the code sees an entry with 0 speed it stops searching
};

ESP32CAN::ESP32CAN(gpio_num_t rxPin, gpio_num_t txPin, uint8_t busNumber) : CAN_COMMON(32)
{
    // printf("ESP32CAN::ESP32CAN bus number: %d\n", busNumber);

#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0)
    if (busNumber == 0) {
        bus_handle = twai_bus_0;
    }
    else if (busNumber == 1) {
        bus_handle = twai_bus_1;
    }
    //bus_handle.controller_id = busNumber;
#endif
    g_config.rx_io = rxPin;
    g_config.tx_io = txPin;
    cyclesSinceTraffic = 0;
    initializedResources = false;
    readyForTraffic = false;
    g_config.tx_queue_len = BI_TX_BUFFER_SIZE;
    g_config.rx_queue_len = 6;
    rxBufferSize = BI_RX_BUFFER_SIZE;
}

ESP32CAN::ESP32CAN() : CAN_COMMON(BI_NUM_FILTERS) 
{
    g_config.tx_queue_len = BI_TX_BUFFER_SIZE;
    g_config.rx_queue_len = 6;

    rxBufferSize = BI_RX_BUFFER_SIZE;

    for (int i = 0; i < BI_NUM_FILTERS; i++)
    {
        filters[i].id = 0;
        filters[i].mask = 0;
        filters[i].extended = false;
        filters[i].configured = false;
    }
    initializedResources = false;
    readyForTraffic = false;
    cyclesSinceTraffic = 0;
}

void ESP32CAN::setCANPins(gpio_num_t rxPin, gpio_num_t txPin)
{
    g_config.rx_io = rxPin;
    g_config.tx_io = txPin;
}

void CAN_WatchDog_Builtin( void *pvParameters )
{
    ESP32CAN* espCan = (ESP32CAN*)pvParameters;
    const TickType_t xDelay = 200 / portTICK_PERIOD_MS;
    twai_status_info_t status_info;

    for(;;)
    {
        vTaskDelay( xDelay );
        espCan->cyclesSinceTraffic++;

        esp_err_t result;
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0)
        result = twai_get_status_info_v2(espCan->bus_handle, &status_info);
#else
        result = twai_get_status_info(&status_info);
#endif
        if (result == ESP_OK)
        {
            if (status_info.state == TWAI_STATE_BUS_OFF)
            {
                espCan->cyclesSinceTraffic = 0;

#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0)
                result = twai_initiate_recovery_v2(espCan->bus_handle);
#else
                result = twai_initiate_recovery();
#endif
                if (result != ESP_OK)
                {
                    printf("Could not initiate bus recovery!\n");
                }
            }
        }
    }
}

//infinitely loops accepting frames from the TWAI driver. Calls
//our processing routine which then applies the custom 32 filters and
//decides whether to trigger callbacks or queue the frame (or throw it away)
void task_LowLevelRX(void *pvParameters)
{
    ESP32CAN* espCan = (ESP32CAN*)pvParameters;
    
    while (1)
    {
        twai_message_t message;
        if (espCan->readyForTraffic)
        {
            esp_err_t result;
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0)
            result = twai_receive_v2(espCan->bus_handle, &message, pdMS_TO_TICKS(100));
#else
            result = twai_receive(&message, pdMS_TO_TICKS(100));
#endif
            if (result == ESP_OK)
            {
                espCan->processFrame(message);
            }
        }
        else vTaskDelay(pdMS_TO_TICKS(100));
    }
    
}

/*
Issue callbacks to registered functions and objects
Used to keep this kind of thing out of the interrupt handler
The callback type and mailbox are passed in the fid member of the
CAN_FRAME struct. It isn't really used by anything.
Layout of the storage:
bit   31 -    If set indicates an object callback
bits  24-30 - Idx into listener table
bits  0-7   - Mailbox number that triggered callback
*/
void task_CAN( void *pvParameters )
{
    ESP32CAN* espCan = (ESP32CAN*)pvParameters;
    CAN_FRAME rxFrame;

    while (1)
    {
        //receive next CAN frame from queue and fire off the callback
        if(xQueueReceive(callbackQueue, &rxFrame, portMAX_DELAY)==pdTRUE)
        {
            espCan->sendCallback(&rxFrame);
        }
    }
}

void ESP32CAN::sendCallback(CAN_FRAME *frame)
{
    //frame buffer
    CANListener *thisListener;
    int mb;
    int idx;

    mb = (frame->fid & 0xFF);
    if (mb == 0xFF) mb = -1;

    if (frame->fid & 0x80000000ul) //object callback
    {
        idx = (frame->fid >> 24) & 0x7F;
        thisListener = listener[idx];
        thisListener->gotFrame(frame, mb);
    }
    else //C function callback
    {
        if (mb > -1) (*cbCANFrame[mb])(frame);
        else (*cbGeneral)(frame);
    }
}

void ESP32CAN::setRXBufferSize(int newSize)
{
    rxBufferSize = newSize;
}

void ESP32CAN::setTXBufferSize(int newSize)
{
    g_config.tx_queue_len = newSize;
}

int ESP32CAN::_setFilterSpecific(uint8_t mailbox, uint32_t id, uint32_t mask, bool extended)
{
    if (mailbox < BI_NUM_FILTERS)
    {
        filters[mailbox].id = id & mask;
        filters[mailbox].mask = mask;
        filters[mailbox].extended = extended;
        filters[mailbox].configured = true;
        return mailbox;
    }
    return -1;
}

int ESP32CAN::_setFilter(uint32_t id, uint32_t mask, bool extended)
{
    for (int i = 0; i < BI_NUM_FILTERS; i++)
    {
        if (!filters[i].configured) 
        {
            _setFilterSpecific(i, id, mask, extended);
            return i;
        }
    }
    if (debuggingMode) Serial.println("Could not set filter!");
    return -1;
}

void ESP32CAN::_init()
{
    if (debuggingMode) Serial.println("Built in CAN Init");
    for (int i = 0; i < BI_NUM_FILTERS; i++)
    {
        filters[i].id = 0;
        filters[i].mask = 0;
        filters[i].extended = false;
        filters[i].configured = false;
    }

    if (!initializedResources)
    {
        if (debuggingMode) printf("Initializing resources for built-in CAN\n");

                                 //Queue size, item size
        callbackQueue = xQueueCreate(16, sizeof(CAN_FRAME));
        rx_queue = xQueueCreate(rxBufferSize, sizeof(CAN_FRAME));
        if (debuggingMode) Serial.println("Created queues.");

                  //func        desc    stack, params, priority, handle to task
        xTaskCreate(&task_CAN, "CAN_RX", 8192, this, 15, NULL);
        if (debuggingMode) Serial.println("task rx created.");
        if (debuggingMode) Serial.println("task low level rx created.");

#if defined(CONFIG_FREERTOS_UNICORE)
        xTaskCreate(&CAN_WatchDog_Builtin, "CAN_WD_BI", 2048, this, 10, NULL);
#else
        xTaskCreatePinnedToCore(&CAN_WatchDog_Builtin, "CAN_WD_BI", 2048, this, 10, NULL, 1);
#endif
        if (debuggingMode) Serial.println("task watchdog created.");
        initializedResources = true;
    }
    if (debuggingMode) Serial.println("_init done");
}

uint32_t ESP32CAN::init(uint32_t ul_baudrate)
{
    _init();
    set_baudrate(ul_baudrate);
    if (debuggingMode)
    {
        //Reconfigure alerts to detect Error Passive and Bus-Off error states
        uint32_t alerts_to_enable = TWAI_ALERT_ERR_PASS | TWAI_ALERT_BUS_OFF | TWAI_ALERT_AND_LOG | TWAI_ALERT_ERR_ACTIVE 
                                  | TWAI_ALERT_ARB_LOST | TWAI_ALERT_BUS_ERROR | TWAI_ALERT_TX_FAILED | TWAI_ALERT_RX_QUEUE_FULL;

        esp_err_t result;
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0)
        result = twai_reconfigure_alerts_v2(bus_handle, alerts_to_enable, NULL);
#else
        result = twai_reconfigure_alerts(alerts_to_enable, NULL);
#endif
        if (result == ESP_OK)
        {
            printf("Alerts reconfigured\n");
        }
        else
        {
            printf("Failed to reconfigure alerts");
        }
    }
    //this task implements our better filtering on top of the TWAI library. Accept all frames then filter in here VVVVV
#if defined(CONFIG_FREERTOS_UNICORE)
    xTaskCreate(&task_LowLevelRX, "CAN_LORX", 4096, this, 19, NULL);
#else
    xTaskCreatePinnedToCore(&task_LowLevelRX, "CAN_LORX", 4096, this, 19, NULL, 1);
#endif
    readyForTraffic = true;
    return ul_baudrate;
}

uint32_t ESP32CAN::beginAutoSpeed()
{
    twai_general_config_t oldMode = g_config;

    _init();

    readyForTraffic = false;
    twai_stop();
    g_config.mode = TWAI_MODE_LISTEN_ONLY;
    int idx = 0;
    while (valid_timings[idx].speed != 0)
    {
        t_config = valid_timings[idx].cfg;
        disable();
        Serial.print("Trying Speed ");
        Serial.print(valid_timings[idx].speed);
        enable();
        delay(600); //wait a while
        if (cyclesSinceTraffic < 2) //only would happen if there had been traffic
        {
            disable();
            g_config.mode = oldMode.mode;
            enable();
            Serial.println(" SUCCESS!");
            return valid_timings[idx].speed;
        }
        else
        {
            Serial.println(" FAILED.");
        }
        idx++;
    }
    Serial.println("None of the tested CAN speeds worked!");
    twai_stop();
    return 0;
}

uint32_t ESP32CAN::set_baudrate(uint32_t ul_baudrate)
{
    disable();
    //now try to find a valid timing to use
    int idx = 0;
    while (valid_timings[idx].speed != 0)
    {
        if (valid_timings[idx].speed == ul_baudrate)
        {
            t_config = valid_timings[idx].cfg;
            enable();
            return ul_baudrate;
        }
        idx++;
    }
    printf("Could not find a valid bit timing! You will need to add your desired speed to the library!\n");
    return 0;
}

void ESP32CAN::setListenOnlyMode(bool state)
{
    disable();
    g_config.mode = state?TWAI_MODE_LISTEN_ONLY:TWAI_MODE_NORMAL;
    enable();
}

void ESP32CAN::enable()
{
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0)
    if (bus_handle == twai_bus_0) {
        g_config.controller_id = 0;
    }
    else if (bus_handle == twai_bus_1) {
        g_config.controller_id = 1;
    }

    //printf("ESP32CAN::enable controller_id: %d\n", g_config.controller_id);

    if (twai_driver_install_v2(&g_config, &t_config, &f_config, &bus_handle) == ESP_OK) {
        printf("TWAI V2 driver installed with controller ID: %d, RX: %d, TX: %d, bus: %d\n", g_config.controller_id, g_config.rx_io, g_config.tx_io, bus_handle);
    } else {
        printf("Failed to install TWAI V2 driver for controller ID: %d\n", g_config.controller_id);
        return;
    }
    //Start TWAI driver
    if (twai_start_v2(bus_handle) == ESP_OK) {
        printf("TWAI V2 driver started with controller ID: %d, RX: %d, TX: %d\n", g_config.controller_id, g_config.rx_io, g_config.tx_io);
    } else {
        printf("Failed to start TWAI V2 driver for controller ID: %d\n", g_config.controller_id);
        return;
    }
#else
    if (twai_driver_install(&g_config, &t_config, &f_config) == ESP_OK)
    {
        //printf("TWAI Driver installed\n");
    }
    else
    {
        printf("Failed to install TWAI driver\n");
        return;
    }
    // Start TWAI driver
    if (twai_start() == ESP_OK)
    {
        //printf("TWAI Driver started\n");
    }
    else
    {
        printf("Failed to start TWAI driver\n");
        return;
    }
#endif

    readyForTraffic = true;
}

void ESP32CAN::disable()
{
    readyForTraffic = false;
    twai_stop();
    vTaskDelay(pdMS_TO_TICKS(100)); //a bit of delay here seems to fix a race condition triggered by task_LowLevelRX
    twai_driver_uninstall();
}

//This function is too big to be running in interrupt context. Refactored so it doesn't.
bool ESP32CAN::processFrame(twai_message_t &frame)
{
    CANListener *thisListener;
    CAN_FRAME msg;

    cyclesSinceTraffic = 0; //reset counter to show that we are receiving traffic

    msg.id = frame.identifier;
    msg.length = frame.data_length_code;
    msg.rtr = frame.rtr;
    msg.extended = frame.extd;
    for (int i = 0; i < 8; i++) msg.data.byte[i] = frame.data[i];
    
    for (int i = 0; i < BI_NUM_FILTERS; i++)
    {
        if (!filters[i].configured) continue;
        if ((msg.id & filters[i].mask) == filters[i].id && (filters[i].extended == msg.extended))
        {
            //frame is accepted, lets see if it matches a mailbox callback
            if (cbCANFrame[i])
            {
                msg.fid = i;
                xQueueSend(callbackQueue, &msg, 0);
                return true;
            }
            else if (cbGeneral)
            {
                msg.fid = 0xFF;
                xQueueSend(callbackQueue, &msg, 0);
                return true;
            }
            else
            {
                for (int listenerPos = 0; listenerPos < SIZE_LISTENERS; listenerPos++)
                {
                    thisListener = listener[listenerPos];
                    if (thisListener != NULL)
                    {
                        if (thisListener->isCallbackActive(i)) 
                        {
                            msg.fid = 0x80000000ul + (listenerPos << 24ul) + i;
                            xQueueSend(callbackQueue, &msg, 0);
                            return true;
                        }
                        else if (thisListener->isCallbackActive(numFilters)) //global catch-all 
                        {
                            msg.fid = 0x80000000ul + (listenerPos << 24ul) + 0xFF;
                            xQueueSend(callbackQueue, &msg, 0);
                            return true;
                        }
                    }
                }
            }
            
            //otherwise, send frame to input queue
            xQueueSend(rx_queue, &msg, 0);
            if (debuggingMode) Serial.write('_');
            return true;
        }
    }
    return false;
}

bool ESP32CAN::sendFrame(CAN_FRAME& txFrame)
{
    twai_message_t __TX_frame;

    __TX_frame.identifier = txFrame.id;
    __TX_frame.data_length_code = txFrame.length;
    __TX_frame.rtr = txFrame.rtr;
    __TX_frame.extd = txFrame.extended;
    for (int i = 0; i < 8; i++) __TX_frame.data[i] = txFrame.data.byte[i];

    //don't wait long if the queue was full. The end user code shouldn't be sending faster
    //than the buffer can empty. Set a bigger TX buffer or delay sending if this is a problem.
    esp_err_t result;
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0)
    result = twai_transmit_v2(bus_handle, &__TX_frame, pdMS_TO_TICKS(4));
#else
    result = twai_transmit(&__TX_frame, pdMS_TO_TICKS(4));
#endif
    switch (result)
    {
    case ESP_OK:
        if (debuggingMode) Serial.write('<');
        break;
    case ESP_ERR_TIMEOUT:
        if (debuggingMode) Serial.write('T');
        break;
    case ESP_ERR_INVALID_ARG:
    case ESP_FAIL:
    case ESP_ERR_INVALID_STATE:
    case ESP_ERR_NOT_SUPPORTED:
        if (debuggingMode) Serial.write('!');
        break;
    }
    
    return true;
}

bool ESP32CAN::rx_avail()
{
    if (!rx_queue) return false;
    return uxQueueMessagesWaiting(rx_queue) > 0?true:false;
}

uint16_t ESP32CAN::available()
{
    if (!rx_queue) return 0;
    return uxQueueMessagesWaiting(rx_queue);
}

uint32_t ESP32CAN::get_rx_buff(CAN_FRAME &msg)
{
    CAN_FRAME frame;
    //receive next CAN frame from queue
    if(xQueueReceive(rx_queue,&frame, 0) == pdTRUE)
    {
        msg = frame; //do a copy in the case that the receive worked
        return true;
    }
    return false; //otherwise we leave the msg variable alone and just return false
}

Re: Dual TWAI with Arduino ESP32

Posted: Tue Sep 24, 2024 2:02 pm
by Fusion
Did some more experimenting with the library, but seems like I can't enable both buses at the same time.
Can anyone confirm whether both buses can run at the same time in ESP-IDF, so I know that the error is in this code and not an underlying issue?
This is being tested with a C6 dev board btw.

Code: Select all

03:30:12.718 -> Rebooting...
03:30:12.718 -> ESP-ROM:esp32c6-20220919
03:30:12.718 -> Build:Sep 19 2022
03:30:12.718 -> rst:0xc (SW_CPU),boot:0x6c (SPI_FAST_FLASH_BOOT)
03:30:12.718 -> Saved PC:0x4001975a
03:30:12.718 -> SPIWP:0xee
03:30:12.718 -> mode:DIO, clock div:2
03:30:12.718 -> load:0x40875720,len:0x1190
03:30:12.718 -> load:0x4086c110,len:0xd98
03:30:12.718 -> load:0x4086e610,len:0x2e98
03:30:12.718 -> entry 0x4086c110
03:30:15.857 -> Initializing ...
03:30:15.955 -> Enable: Called for CAN0
03:30:15.955 -> Enable: Setting CAN0 controller_id = 0
03:30:15.955 -> TWAI V2 Driver installed for CAN0
03:30:15.955 -> Enable: Set CAN0 to bus_handle 0x408143a4 and twai_bus 0x408143a4
03:30:15.988 -> TWAI V2 driver started with controller ID: 0, RX: 16, TX: 17
03:30:15.988 -> Builtin CAN0 Init OK ...
03:30:15.988 -> *************
03:30:16.087 -> Enable: Called for CAN1
03:30:16.087 -> Enable: Setting CAN1 controller_id = 1
03:30:16.087 -> TWAI V2 Driver installed for CAN1
03:30:16.087 -> Enable: Set CAN1 to bus_handle 0x408146ac and twai_bus 0x408146ac
03:30:16.087 -> TWAI V2 driver started with controller ID: 1, RX: 18, TX: 19
03:30:16.087 -> Builtin CAN1 Init OK ...
03:30:16.087 -> Ready ...!
03:30:16.184 -> Guru Meditation Error: Core  0 panic'ed (Load access fault). Exception was unhandled.
03:30:16.184 -> 
03:30:16.184 -> Core  0 register dump:
03:30:16.184 -> MEPC    : 0x40030c86  RA      : 0x40807368  SP      : 0x4081bd90  GP      : 0x4080d374  
03:30:16.184 -> TP      : 0x4081be70  T0      : 0x00000000  T1      : 0x40000a18  T2      : 0x00000000  
03:30:16.184 -> S0/FP   : 0x408143d0  S1      : 0x00000000  A0      : 0x4081bddc  A1      : 0x00000000  
03:30:16.184 -> A2      : 0x40814470  A3      : 0x00000000  A4      : 0x8103024c  A5      : 0x4081bddd  
03:30:16.217 -> A6      : 0x00000000  A7      : 0x00000014  S2      : 0x4081bddc  S3      : 0xfefefefd  
03:30:16.217 -> S4      : 0xffffffff  S5      : 0x408143f4  S6      : 0x00000000  S7      : 0x00000000  
03:30:16.217 -> S8      : 0x00000000  S9      : 0x00000000  S10     : 0x00000000  S11     : 0x00000000  
03:30:16.217 -> T3      : 0x40811000  T4      : 0x00000001  T5      : 0x40811000  T6      : 0x00000001  
03:30:16.250 -> MSTATUS : 0x00001881  MTVEC   : 0x40800001  MCAUSE  : 0x00000005  MTVAL   : 0x00000000  
03:30:16.250 -> MHARTID : 0x00000000  
03:30:16.250 -> 
03:30:16.250 -> Stack memory:
03:30:16.250 -> 4081bd90: 0xa5a5a5a5 0xa5a5a5a5 0xa5a5a5a5 0x00000064 0xa5a5a5a5 0xa5a5a5a5 0x00000000 0x00000c8e
03:30:16.250 -> 4081bdb0: 0xa5a5a5a5 0x00000000 0x00000000 0x00000000 0x408143a4 0x40811000 0x4081be18 0x42015526
03:30:16.283 -> 4081bdd0: 0x40807ff6 0x00000000 0x00000000 0x4080d300 0x4081be70 0x00000000 0x00000000 0x0fdc5035
03:30:16.283 -> 4081bdf0: 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x4080eb38 0x42000be2
03:30:16.283 -> 4081be10: 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x0fdc5035
03:30:16.283 -> 4081be30: 0x00000000 0x00000000 0x00000000 0x40808002 0x00000000 0x00000000 0x00000000 0x00000000
03:30:16.315 -> 4081be50: 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0xa5a5a5a5 0xa5a5a5a5 0xa5a5a5a5
03:30:16.315 -> 4081be70: 0xa5a5a5a5 0xa5a5a5a5 0xa5a5a5a5 0xbaad5678 0x0000015c 0xabba1234 0x00000150 0x4081bd00
03:30:16.315 -> 4081be90: 0x00000cf2 0x4080f5ec 0x4080f5ec 0x4081be8c 0x4080f5e4 0x00000006 0x4081457c 0x4081457c
03:30:16.315 -> 4081beb0: 0x4081be8c 0x00000000 0x00000013 0x4081ae7c 0x5f4e4143 0x58524f4c 0x00000000 0x00000000
03:30:16.348 -> 4081bed0: 0x4081be70 0x00000013 0x00000000 0x00000000 0x00000000 0x00000000 0x408116dc 0x40811744
03:30:16.348 -> 4081bef0: 0x408117ac 0x00000000 0x00000000 0x00000001 0x00000000 0x00000000 0x00000000 0x4200d50a
03:30:16.348 -> 4081bf10: 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000
03:30:16.348 -> 4081bf30: 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000
03:30:16.381 -> 4081bf50: 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000
03:30:16.381 -> 4081bf70: 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000
03:30:16.381 -> 4081bf90: 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000
03:30:16.381 -> 4081bfb0: 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000
03:30:16.413 -> 4081bfd0: 0x00000000 0x00000000 0x00000000 0xbaad5678 0x000001e0 0xabba1234 0x000001d4 0x4081c040
03:30:16.413 -> 4081bff0: 0x4081c040 0x4081c1c0 0x4081c1a8 0x00000000 0x4081c004 0xffffffff 0x4081c004 0x4081c004
03:30:16.413 -> 4081c010: 0x00000001 0x4081c018 0xffffffff 0x4081e85c 0x4081e85c 0x00000000 0x00000010 0x00000018
03:30:16.413 -> 4081c030: 0x8600ffff 0x00000000 0xba832b16 0x55d23f02 0x2d6effdf 0x3a860868 0x56b3677a 0x14939881
03:30:16.444 -> 4081c050: 0xc5137c0d 0x878986df 0x2c5db0a4 0xf5cbfaf8 0x35deae7e 0xb226118c 0xa6aae66b 0x79408269
03:30:16.444 -> 4081c070: 0xab2c9441 0xd2f1cd4d 0x0486c9e8 0xd618a515 0xbb8a07d3 0x996b0d05 0xe17ae7cf 0x749fe082
03:30:16.444 -> 4081c090: 0x451354c9 0xeaef5162 0xc0d6a001 0xfa8b7e7f 0x7041a4ee 0x8e02a125 0x097e02e6 0x6edae8e9
03:30:16.477 -> 4081c0b0: 0x044077c0 0xd62e2360 0xfaec49d9 0xd3e22b07 0xafdf6cea 0x114608c6 0x43c9cc5e 0x07717231
03:30:16.477 -> 4081c0d0: 0xe8e82809 0xe145cefc 0xf00f8466 0x866b87de 0x9b6dee3e 0xf30d46ef 0xac43c8cc 0x4488635f
03:30:16.477 -> 4081c0f0: 0x0b74ca1a 0xed8bc220 0x57b2d82c 0xa84ef5fe 0xf97424ec 0x749e96df 0x0b38472b 0x9165fc1d
03:30:16.477 -> 4081c110: 0x2a9d3fd6 0x8d8d7549 0x0461ac66 0x35f6f596 0x7bfd563a 0xd8d8108d 0xf527e271 0xf6322e82
03:30:16.509 -> 4081c130: 0xf250c299 0x21a7e3a4 0xe8754f2d 0xc049d656 0xa08efce5 0x79457088 0x41f15696 0x43410000
03:30:16.509 -> 4081c150: 0x3c846daa 0xa5f14abe 0xc7990056 0x9eee499b 0xeb74c84f 0x2adb08d2 0x5bbcda56 0x24e18b21
03:30:16.509 -> 4081c170: 0xf55d025c 0xfaf920d9 0xa2ec4565 0x32aa3518 0x0d124f90 0x8a4a93f8 0xbd6b4e36 0x21601cc2
03:30:16.509 -> 
03:30:16.509 -> 
03:30:16.509 -> 
03:30:16.509 -> ELF file SHA256: 43d03f65f
Relevant snippets:

Code: Select all

ESP32CAN::ESP32CAN(gpio_num_t rxPin, gpio_num_t txPin, uint8_t busNumber) : CAN_COMMON(32)
{
    // printf("ESP32CAN::ESP32CAN bus number: %d\n", busNumber);

#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0)
    this->busNumber = busNumber;  // Store the bus number (0 for CAN0, 1 for CAN1)
#endif
    g_config.rx_io = rxPin;
    g_config.tx_io = txPin;
    cyclesSinceTraffic = 0;
    initializedResources = false;
    readyForTraffic = false;
    g_config.tx_queue_len = BI_TX_BUFFER_SIZE;
    g_config.rx_queue_len = 6;
    rxBufferSize = BI_RX_BUFFER_SIZE;
}

Code: Select all

void ESP32CAN::enable()
{
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0)

    printf("Enable: Called for CAN%d\n", busNumber);  // Check bus number

    if (busNumber == 0) {
        g_config.controller_id = 0;
        g_config.rx_io = GPIO_NUM_16;
        g_config.tx_io = GPIO_NUM_17;
        printf("Enable: Setting CAN0 controller_id = 0\n");
    } else if (busNumber == 1) {
        g_config.controller_id = 1;
        g_config.rx_io = GPIO_NUM_18;
        g_config.tx_io = GPIO_NUM_19;
        printf("Enable: Setting CAN1 controller_id = 1\n");
    } else {
        printf("Enable: Invalid busNumber: %d\n", busNumber);
    }

    if (twai_driver_install_v2(&g_config, &t_config, &f_config, &bus_handle) == ESP_OK) {
        printf("TWAI V2 Driver installed for CAN%d\n", busNumber);

        // Assign bus handle to the correct global variable
        if (busNumber == 0) {
            twai_bus_0 = bus_handle;  // Now twai_bus_0 gets the actual handle
            printf("Enable: Set CAN0 to bus_handle %p and twai_bus %p\n", (void*)bus_handle, (void*)twai_bus_0);
        } else if (busNumber == 1) {
            twai_bus_1 = bus_handle;  // Now twai_bus_1 gets the actual handle
            printf("Enable: Set CAN1 to bus_handle %p and twai_bus %p\n", (void*)bus_handle, (void*)twai_bus_1);
        }
    } else {
        printf("Failed to install TWAI V2 driver for CAN%d\n", busNumber);
        return;  // Return error code
    }

    //Start TWAI driver
    if (twai_start_v2(bus_handle) == ESP_OK) {
        printf("TWAI V2 driver started with controller ID: %d, RX: %d, TX: %d\n", g_config.controller_id, g_config.rx_io, g_config.tx_io);
    } else {
        printf("Failed to start TWAI V2 driver for controller ID: %d\n", g_config.controller_id);
        return;
    }
    
#else
    if (twai_driver_install(&g_config, &t_config, &f_config) == ESP_OK)
    {
        //printf("TWAI Driver installed\n");
    }
    else
    {
        printf("Failed to install TWAI driver\n");
        return;
    }
    // Start TWAI driver
    if (twai_start() == ESP_OK)
    {
        //printf("TWAI Driver started\n");
    }
    else
    {
        printf("Failed to start TWAI driver\n");
        return;
    }
#endif

    readyForTraffic = true;
}

Re: Dual TWAI with Arduino ESP32

Posted: Tue Sep 24, 2024 8:03 pm
by lbernstone
ESP32-C6 has 2 TWAI controllers, but at the moment, the driver can only support TWAI0 due to the limitation of the driver structure.
This will go away in arduino-esp32 v3.1, which uses esp-idf 5.3 (and the v2 twai driver).

Re: Dual TWAI with Arduino ESP32

Posted: Tue Sep 24, 2024 10:56 pm
by Fusion
I'm using 3.1.0-RC1 with IDF 5.3.0, which should not have this limitation.
https://github.com/espressif/arduino-esp32/releases

Re: Dual TWAI with Arduino ESP32

Posted: Wed Sep 25, 2024 12:29 am
by lbernstone
Sorry. My reading comprehension is not very good sometimes.

Re: Dual TWAI with Arduino ESP32

Posted: Wed Sep 25, 2024 1:38 am
by Fusion
No problem ;)