├── README.md ├── examples ├── CANTestESP32_Both │ └── CANTestESP32_Both.ino ├── CANTestESP32_BuiltIn │ └── CANTestESP32_BuiltIn.ino ├── CANTestESP32_BuiltIn_AltPins │ └── CANTestESP32_BuiltIn_AltPins.ino ├── CANTestESP32_BuiltIn_BitModification │ └── CANTestESP32_BuiltIn_BitModification.ino ├── CANTestESP32_CAN1 │ └── CANTestESP32_CAN1.ino └── CANTestESP32_FDMode │ └── CANTestESP32_FDMode.ino ├── keywords.txt ├── library.properties ├── license.txt └── src ├── esp32_can.cpp ├── esp32_can.h ├── esp32_can_builtin.cpp └── esp32_can_builtin.h /README.md: -------------------------------------------------------------------------------- 1 | esp32_can 2 | ========== 3 | 4 | Implements a CAN driver for the built-in CAN hardware on an ESP32. 5 | The builtin CAN is called CAN0, and also CAN1 if there is a second CAN port on the ESP32 chip in use. 6 | This library is specifically meant to be used with the EVTV ESP32-Due board. 7 | However, with small modifications either driver found within this library could be used on other boards. 8 | Primarily, one should check TX and RX pins if using a different board. 9 | 10 | This library requires the can_common library. That library is a common base that 11 | other libraries can be built off of to allow a more universal API for CAN. 12 | 13 | The needed can_common library is found here: https://github.com/collin80/can_common 14 | 15 | As of version 0.3.1 of this library, MCP2517FD support is no longer included. It 16 | has been moved to its own library. Get the library here: 17 | 18 | https://github.com/collin80/esp32_mcp2517fd 19 | 20 | then add this header include to your source code: 21 | 22 | #include 23 | -------------------------------------------------------------------------------- /examples/CANTestESP32_Both/CANTestESP32_Both.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Demo of using both CAN ports on the EVTV ESP32 board. Shows an example of many techniques in the library: 3 | 1. Initializing both 4 | 2. Set Can0 to forward any traffic where the ID starts with 0x1?? where ?? could be any number 0 - FF 5 | 3. Set Can1 to forward any traffic where the ID starts with 0x23? where ? could be any number 0 - F 6 | 4. Do both of the above via a C style callback 7 | 5. Display any other frames over the serial port 8 | */ 9 | 10 | #include "esp32_can.h" 11 | 12 | void handleCAN0CB(CAN_FRAME *frame) 13 | { 14 | CAN1.sendFrame(*frame); 15 | } 16 | 17 | void handleCAN1CB(CAN_FRAME *frame) 18 | { 19 | CAN0.sendFrame(*frame); 20 | } 21 | 22 | void printFrame(CAN_FRAME &frame) 23 | { 24 | // Print message 25 | Serial.print("ID: "); 26 | Serial.println(frame.id,HEX); 27 | Serial.print("Ext: "); 28 | if(frame.extended) { 29 | Serial.println("Y"); 30 | } else { 31 | Serial.println("N"); 32 | } 33 | Serial.print("Len: "); 34 | Serial.println(frame.length,DEC); 35 | for(int i = 0;i < frame.length; i++) { 36 | Serial.print(frame.data.uint8[i],HEX); 37 | Serial.print(" "); 38 | } 39 | Serial.println(); 40 | } 41 | 42 | void setup() { 43 | Serial.begin(115200); 44 | 45 | Serial.println("Initializing ..."); 46 | 47 | // Initialize builtin CAN controller at the specified speed 48 | if(CAN0.begin(500000)) 49 | { 50 | Serial.println("Builtin CAN Init OK ..."); 51 | } else { 52 | Serial.println("BuiltIn CAN Init Failed ..."); 53 | } 54 | 55 | // Initialize MCP2517FD CAN controller at the specified speed 56 | if(CAN1.begin(500000)) 57 | { 58 | Serial.println("MCP2517FD Init OK ..."); 59 | } else { 60 | Serial.println("MCP2517FD Init Failed ..."); 61 | } 62 | 63 | CAN0.setRXFilter(0, 0x100, 0x700, false); 64 | CAN0.watchFor(); //allow everything else through 65 | CAN0.setCallback(0, handleCAN0CB); 66 | 67 | CAN1.setRXFilter(0, 0x230, 0x7F0, false); 68 | CAN1.watchFor(); //allow everything else through 69 | CAN1.setCallback(0, handleCAN1CB); 70 | 71 | Serial.println("Ready ...!"); 72 | } 73 | 74 | 75 | void loop() { 76 | CAN_FRAME message; 77 | 78 | if (CAN0.read(message)) { 79 | printFrame(message); 80 | } 81 | if (CAN1.read(message)) { 82 | printFrame(message); 83 | } 84 | } 85 | 86 | -------------------------------------------------------------------------------- /examples/CANTestESP32_BuiltIn/CANTestESP32_BuiltIn.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void printFrame(CAN_FRAME *message) 4 | { 5 | Serial.print(message->id, HEX); 6 | if (message->extended) Serial.print(" X "); 7 | else Serial.print(" S "); 8 | Serial.print(message->length, DEC); 9 | for (int i = 0; i < message->length; i++) { 10 | Serial.print(message->data.byte[i], HEX); 11 | Serial.print(" "); 12 | } 13 | Serial.println(); 14 | } 15 | 16 | void gotHundred(CAN_FRAME *frame) 17 | { 18 | Serial.print("Got special frame! "); 19 | printFrame(frame); 20 | } 21 | 22 | void setup() { 23 | Serial.begin(115200); 24 | 25 | Serial.println("Initializing ..."); 26 | 27 | CAN0.begin(500000); 28 | 29 | Serial.println("Ready ...!"); 30 | CAN_FRAME txFrame; 31 | txFrame.rtr = 0; 32 | txFrame.id = 0x123; 33 | txFrame.extended = false; 34 | txFrame.length = 4; 35 | txFrame.data.uint8[0] = 0x10; 36 | txFrame.data.uint8[1] = 0x1A; 37 | txFrame.data.uint8[2] = 0xFF; 38 | txFrame.data.uint8[3] = 0x5D; 39 | CAN0.sendFrame(txFrame); 40 | 41 | CAN0.watchFor(0x100, 0xF00); //setup a special filter 42 | CAN0.watchFor(); //then let everything else through anyway 43 | CAN0.setCallback(0, gotHundred); //callback on that first special filter 44 | } 45 | 46 | void loop() { 47 | byte i = 0; 48 | CAN_FRAME message; 49 | if (CAN0.read(message)) { 50 | 51 | printFrame(&message); 52 | 53 | // Send out a return message for each one received 54 | // Simply increment message id and data bytes to show proper transmission 55 | // Note: this will double the traffic on the network (provided it passes the filter above) 56 | message.id++; 57 | for (i = 0; i < message.length; i++) { 58 | message.data.uint8[i]++; 59 | } 60 | //CAN.sendFrame(message); 61 | } 62 | //or, just plain send traffic periodically 63 | /* 64 | delayMicroseconds(200); 65 | message.id++; 66 | message.length = 8; 67 | for(i=0;i 2 | 3 | void printFrame(CAN_FRAME *message) 4 | { 5 | Serial.print(message->id, HEX); 6 | if (message->extended) Serial.print(" X "); 7 | else Serial.print(" S "); 8 | Serial.print(message->length, DEC); 9 | Serial.print(" "); 10 | for (int i = 0; i < message->length; i++) { 11 | Serial.print(message->data.byte[i], HEX); 12 | Serial.print(" "); 13 | } 14 | Serial.println(); 15 | } 16 | 17 | void gotHundred(CAN_FRAME *frame) 18 | { 19 | Serial.print("Got special frame! "); 20 | printFrame(frame); 21 | } 22 | 23 | void setup() { 24 | Serial.begin(115200); 25 | 26 | Serial.println("Initializing ..."); 27 | 28 | pinMode(GPIO_NUM_16, OUTPUT); 29 | digitalWrite(GPIO_NUM_16, LOW); //enable CAN transceiver 30 | CAN0.setCANPins(GPIO_NUM_4, GPIO_NUM_5); 31 | CAN0.begin(500000); 32 | 33 | Serial.println("Ready ...!"); 34 | CAN_FRAME txFrame; 35 | txFrame.rtr = 0; 36 | txFrame.id = 0x123; 37 | txFrame.extended = false; 38 | txFrame.length = 4; 39 | txFrame.data.uint8[0] = 0x10; 40 | txFrame.data.uint8[1] = 0x1A; 41 | txFrame.data.uint8[2] = 0xFF; 42 | txFrame.data.uint8[3] = 0x5D; 43 | CAN0.sendFrame(txFrame); 44 | 45 | CAN0.watchFor(0x100, 0xF00); //setup a special filter 46 | CAN0.watchFor(); //then let everything else through anyway 47 | CAN0.setCallback(0, gotHundred); //callback on that first special filter 48 | } 49 | 50 | void loop() { 51 | byte i = 0; 52 | CAN_FRAME message; 53 | if (CAN0.read(message)) { 54 | 55 | printFrame(&message); 56 | 57 | // Send out a return message for each one received 58 | // Simply increment message id and data bytes to show proper transmission 59 | // Note: this will double the traffic on the network (provided it passes the filter above) 60 | message.id++; 61 | for (i = 0; i < message.length; i++) { 62 | message.data.uint8[i]++; 63 | } 64 | //CAN.sendFrame(message); 65 | } 66 | //or, just plain send traffic periodically 67 | /* 68 | delayMicroseconds(200); 69 | message.id++; 70 | message.length = 8; 71 | for(i=0;i 2 | 3 | void printFrame(CAN_FRAME *message) 4 | { 5 | Serial.print(message->id, HEX); 6 | if (message->extended) Serial.print(" X "); 7 | else Serial.print(" S "); 8 | Serial.print(message->length, DEC); 9 | for (int i = 0; i < message->length; i++) { 10 | Serial.print(message->data.byte[i], HEX); 11 | Serial.print(" "); 12 | } 13 | Serial.println(); 14 | } 15 | 16 | void gotHundred(CAN_FRAME *frame) 17 | { 18 | Serial.print("Got special frame! "); 19 | printFrame(frame); 20 | } 21 | 22 | void setup() { 23 | Serial.begin(115200); 24 | 25 | Serial.println("Initializing ..."); 26 | 27 | CAN0.begin(500000); 28 | 29 | Serial.println("Ready ...!"); 30 | CAN_FRAME txFrame; 31 | txFrame.rtr = 0; 32 | txFrame.id = 0x123; 33 | txFrame.extended = false; 34 | txFrame.length = 4; 35 | txFrame.data.int8[0] = 0x10; 36 | txFrame.data.int8[1] = 0x1A; 37 | txFrame.data.int8[2] = 0xFF; 38 | txFrame.data.int8[3] = 0x5D; 39 | txFrame.data.int8[7] = 0xFF; 40 | txFrame.data.bit[0] = 1; 41 | txFrame.data.bit[10] = 1; 42 | txFrame.data.bit[20] = 1; 43 | txFrame.data.bit[40] = 1; 44 | txFrame.data.bit[50] = 1; 45 | txFrame.data.bit[60] = 0; 46 | for (int i = 0; i < 8; i++) 47 | { 48 | for (int j = 7; j > -1; j--) { 49 | Serial.print(txFrame.data.bit[i * 8 + j]); 50 | } 51 | Serial.println(); 52 | } 53 | Serial.println(); 54 | CAN0.sendFrame(txFrame); 55 | 56 | CAN0.watchFor(0x100, 0xF00); //setup a special filter 57 | CAN0.watchFor(); //then let everything else through anyway 58 | CAN0.setCallback(0, gotHundred); //callback on that first special filter 59 | } 60 | 61 | void loop() { 62 | byte i = 0; 63 | CAN_FRAME message; 64 | if (CAN0.read(message)) { 65 | 66 | printFrame(&message); 67 | 68 | // Send out a return message for each one received 69 | // Simply increment message id and data bytes to show proper transmission 70 | // Note: this will double the traffic on the network (provided it passes the filter above) 71 | message.id++; 72 | for (i = 0; i < message.length; i++) { 73 | message.data.uint8[i]++; 74 | } 75 | //CAN.sendFrame(message); 76 | } 77 | //or, just plain send traffic periodically 78 | /* 79 | delayMicroseconds(200); 80 | message.id++; 81 | message.length = 8; 82 | for(i=0;i 5 | sentence=Library to facilitate CAN functionality with the on-chip CAN hardware 6 | paragraph= 7 | category=Communication 8 | url=https://github.com/collin80/esp32_can 9 | architectures=esp32 10 | includes=esp32_can.h 11 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | Copyright 2018 Collin Kidder 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), 4 | to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 5 | and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 10 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 11 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 12 | IN THE SOFTWARE. 13 | -------------------------------------------------------------------------------- /src/esp32_can.cpp: -------------------------------------------------------------------------------- 1 | #include "esp32_can.h" 2 | 3 | //The objects in here are defined weak now. If you're using an EVTV board you need to do nothing. 4 | //If you're using something else that uses different pins then just go ahead and redefine 5 | //the objects with whichever pins you need. 6 | 7 | //Set these to the proper pin numbers for you board. Set by default to correct for EVTV ESP32-Due 8 | //rxpin txpin 9 | ESP32CAN __attribute__((weak)) CAN0(GPIO_NUM_16, GPIO_NUM_17, 0) ; 10 | 11 | #if SOC_TWAI_CONTROLLER_NUM == 2 and ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0) 12 | ESP32CAN __attribute__((weak)) CAN1(GPIO_NUM_18, GPIO_NUM_19, 1); 13 | #endif 14 | -------------------------------------------------------------------------------- /src/esp32_can.h: -------------------------------------------------------------------------------- 1 | #include "esp32_can_builtin.h" 2 | 3 | extern ESP32CAN CAN0; 4 | //Select which external chip you've got connected 5 | 6 | #if SOC_TWAI_CONTROLLER_NUM == 2 and ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0) 7 | extern ESP32CAN CAN1; 8 | #endif 9 | 10 | extern volatile uint32_t biIntsCounter; 11 | extern volatile uint32_t biReadFrames; 12 | 13 | #define Can0 CAN0 14 | 15 | #if (SOC_TWAI_CONTROLLER_NUM == 2 and ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0)) or defined (USES_MCP2517FD) or defined (USES_MCP2515) 16 | #define Can1 CAN1 17 | #endif 18 | -------------------------------------------------------------------------------- /src/esp32_can_builtin.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | ESP32_CAN.cpp - Library for ESP32 built-in CAN module 3 | Now converted to use the built-in TWAI driver in ESP-IDF. This should allow for support for the 4 | full range of ESP32 hardware and probably be more stable than the old approach. 5 | 6 | Author: Collin Kidder 7 | 8 | Created: 31/1/18, significant rework 1/5/23 9 | */ 10 | 11 | #include "Arduino.h" 12 | #include "esp32_can_builtin.h" 13 | //because of the way the TWAI library works, it's just easier to store the valid timings here and anything not found here 14 | //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 15 | //as it serves as a terminator 16 | const VALID_TIMING valid_timings[] = 17 | { 18 | {TWAI_TIMING_CONFIG_1MBITS(), 1000000}, 19 | {TWAI_TIMING_CONFIG_500KBITS(), 500000}, 20 | {TWAI_TIMING_CONFIG_250KBITS(), 250000}, 21 | {TWAI_TIMING_CONFIG_125KBITS(), 125000}, 22 | {TWAI_TIMING_CONFIG_800KBITS(), 800000}, 23 | {TWAI_TIMING_CONFIG_100KBITS(), 100000}, 24 | {TWAI_TIMING_CONFIG_50KBITS(), 50000}, 25 | {TWAI_TIMING_CONFIG_25KBITS(), 25000}, 26 | //caution, these next entries are custom and haven't really been fully tested yet. 27 | //Note that brp can take values in multiples of 2 up to 128 and multiples of 4 up to 256 28 | //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. 29 | //The default clock is 80MHz so plan accordingly 30 | {{.brp = 100, .tseg_1 = 7, .tseg_2 = 2, .sjw = 3, .triple_sampling = false}, 80000}, 31 | {{.brp = 120, .tseg_1 = 15, .tseg_2 = 4, .sjw = 3, .triple_sampling = false}, 33333}, 32 | //this one is only possible on ECO2 ESP32 or ESP32-S3 not on the older ESP32 chips 33 | {{.brp = 200, .tseg_1 = 15, .tseg_2 = 4, .sjw = 3, .triple_sampling = false}, 20000}, 34 | {TWAI_TIMING_CONFIG_25KBITS(), 0} //this is a terminator record. When the code sees an entry with 0 speed it stops searching 35 | }; 36 | 37 | ESP32CAN::ESP32CAN(gpio_num_t rxPin, gpio_num_t txPin, uint8_t busNumber) : CAN_COMMON(32) 38 | { 39 | twai_general_cfg.rx_io = rxPin; 40 | twai_general_cfg.tx_io = txPin; 41 | cyclesSinceTraffic = 0; 42 | readyForTraffic = false; 43 | twai_general_cfg.tx_queue_len = BI_TX_BUFFER_SIZE; 44 | twai_general_cfg.rx_queue_len = 6; 45 | rxBufferSize = BI_RX_BUFFER_SIZE; 46 | 47 | #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0) 48 | bus_handle = nullptr; 49 | twai_general_cfg.controller_id = busNumber; 50 | #endif 51 | } 52 | 53 | ESP32CAN::ESP32CAN() : CAN_COMMON(BI_NUM_FILTERS) 54 | { 55 | twai_general_cfg.tx_queue_len = BI_TX_BUFFER_SIZE; 56 | twai_general_cfg.rx_queue_len = 6; 57 | #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0) 58 | bus_handle = nullptr; 59 | #endif 60 | rxBufferSize = BI_RX_BUFFER_SIZE; 61 | 62 | for (int i = 0; i < BI_NUM_FILTERS; i++) 63 | { 64 | filters[i].id = 0; 65 | filters[i].mask = 0; 66 | filters[i].extended = false; 67 | filters[i].configured = false; 68 | } 69 | 70 | readyForTraffic = false; 71 | cyclesSinceTraffic = 0; 72 | } 73 | 74 | void ESP32CAN::setCANPins(gpio_num_t rxPin, gpio_num_t txPin) 75 | { 76 | twai_general_cfg.rx_io = rxPin; 77 | twai_general_cfg.tx_io = txPin; 78 | } 79 | 80 | void ESP32CAN::CAN_WatchDog_Builtin( void *pvParameters ) 81 | { 82 | ESP32CAN* espCan = (ESP32CAN*)pvParameters; 83 | const TickType_t xDelay = 200 / portTICK_PERIOD_MS; 84 | twai_status_info_t status_info; 85 | 86 | for(;;) 87 | { 88 | vTaskDelay( xDelay ); 89 | espCan->cyclesSinceTraffic++; 90 | 91 | esp_err_t result; 92 | #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0) 93 | result = twai_get_status_info_v2(espCan->bus_handle, &status_info); 94 | #else 95 | result = twai_get_status_info(&status_info); 96 | #endif 97 | if (result == ESP_OK) 98 | { 99 | if (status_info.state == TWAI_STATE_BUS_OFF) 100 | { 101 | espCan->cyclesSinceTraffic = 0; 102 | 103 | #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0) 104 | result = twai_initiate_recovery_v2(espCan->bus_handle); 105 | #else 106 | result = twai_initiate_recovery(); 107 | #endif 108 | if (result != ESP_OK) 109 | { 110 | printf("Could not initiate bus recovery!\n"); 111 | } 112 | } 113 | } 114 | } 115 | } 116 | 117 | //infinitely loops accepting frames from the TWAI driver. Calls 118 | //our processing routine which then applies the custom 32 filters and 119 | //decides whether to trigger callbacks or queue the frame (or throw it away) 120 | void ESP32CAN::task_LowLevelRX(void *pvParameters) 121 | { 122 | ESP32CAN* espCan = (ESP32CAN*)pvParameters; 123 | 124 | while (1) 125 | { 126 | twai_message_t message; 127 | if (espCan->readyForTraffic) 128 | { 129 | esp_err_t result; 130 | #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0) 131 | result = twai_receive_v2(espCan->bus_handle, &message, pdMS_TO_TICKS(100)); 132 | #else 133 | result = twai_receive(&message, pdMS_TO_TICKS(100)); 134 | #endif 135 | if (result == ESP_OK) 136 | { 137 | espCan->processFrame(message); 138 | } 139 | } 140 | else vTaskDelay(pdMS_TO_TICKS(100)); 141 | } 142 | 143 | } 144 | 145 | /* 146 | Issue callbacks to registered functions and objects 147 | Used to keep this kind of thing out of the interrupt handler 148 | The callback type and mailbox are passed in the fid member of the 149 | CAN_FRAME struct. It isn't really used by anything. 150 | Layout of the storage: 151 | bit 31 - If set indicates an object callback 152 | bits 24-30 - Idx into listener table 153 | bits 0-7 - Mailbox number that triggered callback 154 | */ 155 | void ESP32CAN::task_CAN( void *pvParameters ) 156 | { 157 | ESP32CAN* espCan = (ESP32CAN*)pvParameters; 158 | CAN_FRAME rxFrame; 159 | 160 | //delay a bit upon initial start up 161 | vTaskDelay(pdMS_TO_TICKS(100)); 162 | 163 | while (1) 164 | { 165 | if (uxQueueMessagesWaiting(espCan->callbackQueue)) { 166 | //receive next CAN frame from queue and fire off the callback 167 | if(xQueueReceive(espCan->callbackQueue, &rxFrame, portMAX_DELAY) == pdTRUE) 168 | { 169 | espCan->sendCallback(&rxFrame); 170 | } 171 | } 172 | else vTaskDelay(pdMS_TO_TICKS(4)); //if you don't delay here it will slow down the whole system. Need some delay. 173 | 174 | //probably don't need this extra delay. Test and find out. 175 | #if defined(CONFIG_FREERTOS_UNICORE) 176 | vTaskDelay(pdMS_TO_TICKS(6)); 177 | #endif 178 | } 179 | 180 | vTaskDelete(NULL); 181 | } 182 | 183 | void ESP32CAN::sendCallback(CAN_FRAME *frame) 184 | { 185 | //frame buffer 186 | CANListener *thisListener; 187 | int mb; 188 | int idx; 189 | 190 | mb = (frame->fid & 0xFF); 191 | if (mb == 0xFF) mb = -1; 192 | 193 | if (frame->fid & 0x80000000ul) //object callback 194 | { 195 | idx = (frame->fid >> 24) & 0x7F; 196 | thisListener = listener[idx]; 197 | thisListener->gotFrame(frame, mb); 198 | } 199 | else //C function callback 200 | { 201 | if (mb > -1) (*cbCANFrame[mb])(frame); 202 | else (*cbGeneral)(frame); 203 | } 204 | } 205 | 206 | void ESP32CAN::setRXBufferSize(int newSize) 207 | { 208 | rxBufferSize = newSize; 209 | } 210 | 211 | void ESP32CAN::setTXBufferSize(int newSize) 212 | { 213 | twai_general_cfg.tx_queue_len = newSize; 214 | } 215 | 216 | int ESP32CAN::_setFilterSpecific(uint8_t mailbox, uint32_t id, uint32_t mask, bool extended) 217 | { 218 | if (mailbox < BI_NUM_FILTERS) 219 | { 220 | filters[mailbox].id = id & mask; 221 | filters[mailbox].mask = mask; 222 | filters[mailbox].extended = extended; 223 | filters[mailbox].configured = true; 224 | return mailbox; 225 | } 226 | return -1; 227 | } 228 | 229 | int ESP32CAN::_setFilter(uint32_t id, uint32_t mask, bool extended) 230 | { 231 | for (int i = 0; i < BI_NUM_FILTERS; i++) 232 | { 233 | if (!filters[i].configured) 234 | { 235 | _setFilterSpecific(i, id, mask, extended); 236 | return i; 237 | } 238 | } 239 | if (debuggingMode) Serial.println("Could not set filter!"); 240 | return -1; 241 | } 242 | 243 | void ESP32CAN::_init() 244 | { 245 | if (debuggingMode) Serial.println("Built in CAN Init"); 246 | for (int i = 0; i < BI_NUM_FILTERS; i++) 247 | { 248 | filters[i].id = 0; 249 | filters[i].mask = 0; 250 | filters[i].extended = false; 251 | filters[i].configured = false; 252 | } 253 | 254 | if (!CAN_WatchDog_Builtin_handler) { 255 | #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0) 256 | std::ostringstream canWatchDogTaskNameStream; 257 | canWatchDogTaskNameStream << "CAN_WD_BI_CAN" << twai_general_cfg.controller_id; 258 | const char *canWatchDogTaskName = canWatchDogTaskNameStream.str().c_str(); 259 | #else 260 | const char *canWatchDogTaskName = "CAN_WD_BI"; 261 | #endif 262 | 263 | #if defined(CONFIG_FREERTOS_UNICORE) 264 | xTaskCreate(&CAN_WatchDog_Builtin, canWatchDogTaskName, 2048, this, 10, &CAN_WatchDog_Builtin_handler); 265 | #else 266 | xTaskCreatePinnedToCore(&CAN_WatchDog_Builtin, canWatchDogTaskName, 2048, this, 10, &CAN_WatchDog_Builtin_handler, 1); 267 | #endif 268 | } 269 | if (debuggingMode) Serial.println("_init done"); 270 | } 271 | 272 | uint32_t ESP32CAN::init(uint32_t ul_baudrate) 273 | { 274 | ESP_LOGD("CAN", "Init called"); 275 | _init(); 276 | ESP_LOGD("CAN", "Init done"); 277 | set_baudrate(ul_baudrate); 278 | ESP_LOGD("CAN", "Baudrate set"); 279 | if (debuggingMode) 280 | { 281 | //Reconfigure alerts to detect Error Passive and Bus-Off error states 282 | uint32_t alerts_to_enable = TWAI_ALERT_ERR_PASS | TWAI_ALERT_BUS_OFF | TWAI_ALERT_AND_LOG | TWAI_ALERT_ERR_ACTIVE 283 | | TWAI_ALERT_ARB_LOST | TWAI_ALERT_BUS_ERROR | TWAI_ALERT_TX_FAILED | TWAI_ALERT_RX_QUEUE_FULL; 284 | 285 | esp_err_t result; 286 | #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0) 287 | result = twai_reconfigure_alerts_v2(bus_handle, alerts_to_enable, NULL); 288 | #else 289 | result = twai_reconfigure_alerts(alerts_to_enable, NULL); 290 | #endif 291 | if (result == ESP_OK) 292 | { 293 | printf("Alerts reconfigured\n"); 294 | } 295 | else 296 | { 297 | printf("Failed to reconfigure alerts"); 298 | } 299 | } 300 | //this task implements our better filtering on top of the TWAI library. Accept all frames then filter in here VVVVV 301 | #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0) 302 | std::ostringstream canLowLevelTaskNameStream; 303 | canLowLevelTaskNameStream << "CAN_LORX_CAN" << twai_general_cfg.controller_id; 304 | const char* canLowLevelTaskName = canLowLevelTaskNameStream.str().c_str(); 305 | #else 306 | const char* canLowLevelTaskName = "CAN_LORX_CAN0"; 307 | #endif 308 | 309 | #if defined(CONFIG_FREERTOS_UNICORE) 310 | xTaskCreate(ESP32CAN::task_LowLevelRX, canLowLevelTaskName, 4096, this, 19, NULL); 311 | #else 312 | xTaskCreatePinnedToCore(ESP32CAN::task_LowLevelRX, canLowLevelTaskName, 4096, this, 19, NULL, 1); 313 | #endif 314 | readyForTraffic = true; 315 | return ul_baudrate; 316 | } 317 | 318 | uint32_t ESP32CAN::beginAutoSpeed() 319 | { 320 | twai_general_config_t oldMode = twai_general_cfg; 321 | 322 | _init(); 323 | 324 | readyForTraffic = false; 325 | twai_stop(); 326 | twai_general_cfg.mode = TWAI_MODE_LISTEN_ONLY; 327 | int idx = 0; 328 | while (valid_timings[idx].speed != 0) 329 | { 330 | twai_speed_cfg = valid_timings[idx].cfg; 331 | disable(); 332 | Serial.print("Trying Speed "); 333 | Serial.print(valid_timings[idx].speed); 334 | enable(); 335 | delay(600); //wait a while 336 | if (cyclesSinceTraffic < 2) //only would happen if there had been traffic 337 | { 338 | disable(); 339 | twai_general_cfg.mode = oldMode.mode; 340 | enable(); 341 | Serial.println(" SUCCESS!"); 342 | return valid_timings[idx].speed; 343 | } 344 | else 345 | { 346 | Serial.println(" FAILED."); 347 | } 348 | idx++; 349 | } 350 | Serial.println("None of the tested CAN speeds worked!"); 351 | twai_stop(); 352 | return 0; 353 | } 354 | 355 | uint32_t ESP32CAN::set_baudrate(uint32_t ul_baudrate) 356 | { 357 | disable(); 358 | //now try to find a valid timing to use 359 | int idx = 0; 360 | while (valid_timings[idx].speed != 0) 361 | { 362 | if (valid_timings[idx].speed == ul_baudrate) 363 | { 364 | twai_speed_cfg = valid_timings[idx].cfg; 365 | enable(); 366 | return ul_baudrate; 367 | } 368 | idx++; 369 | } 370 | printf("Could not find a valid bit timing! You will need to add your desired speed to the library!\n"); 371 | return 0; 372 | } 373 | 374 | void ESP32CAN::setListenOnlyMode(bool state) 375 | { 376 | disable(); 377 | twai_general_cfg.mode = state?TWAI_MODE_LISTEN_ONLY:TWAI_MODE_NORMAL; 378 | enable(); 379 | } 380 | 381 | void ESP32CAN::setNoACKMode(bool state) 382 | { 383 | disable(); 384 | twai_general_cfg.mode = state?TWAI_MODE_NO_ACK:TWAI_MODE_NORMAL; 385 | enable(); 386 | } 387 | 388 | void ESP32CAN::enable() 389 | { 390 | #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0) 391 | if (twai_driver_install_v2(&twai_general_cfg, &twai_speed_cfg, &twai_filters_cfg, &bus_handle) == ESP_OK) { 392 | printf("Driver installed - bus %d\n", twai_general_cfg.controller_id); 393 | } else { 394 | printf("Failed to install driver - bus %d\n", twai_general_cfg.controller_id); 395 | return; 396 | } 397 | #else 398 | if (twai_driver_install(&twai_general_cfg, &twai_speed_cfg, &twai_filters_cfg) == ESP_OK) 399 | { 400 | printf("TWAI Driver installed\n"); 401 | } 402 | else 403 | { 404 | printf("Failed to install TWAI driver\n"); 405 | return; 406 | } 407 | #endif 408 | 409 | printf("Creating queues\n"); 410 | 411 | callbackQueue = xQueueCreate(16, sizeof(CAN_FRAME)); 412 | rx_queue = xQueueCreate(rxBufferSize, sizeof(CAN_FRAME)); 413 | 414 | #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0) 415 | std::ostringstream canHandlerTaskNameStream; 416 | std::ostringstream canLowLevelTaskNameStream; 417 | canHandlerTaskNameStream << "CAN_RX_CAN" << twai_general_cfg.controller_id; 418 | canLowLevelTaskNameStream << "CAN_LORX_CAN" << twai_general_cfg.controller_id; 419 | const char* canHandlerTaskName = canHandlerTaskNameStream.str().c_str(); 420 | const char* canLowLevelTaskName = canLowLevelTaskNameStream.str().c_str(); 421 | #else 422 | const char* canHandlerTaskName = "CAN_RX_CAN"; 423 | const char* canLowLevelTaskName = "CAN_LORX_CAN"; 424 | #endif 425 | 426 | printf("Starting can handler task\n"); 427 | xTaskCreate(ESP32CAN::task_CAN, canHandlerTaskName, 8192, this, 15, &task_CAN_handler); 428 | 429 | #if defined(CONFIG_FREERTOS_UNICORE) 430 | printf("Starting low level RX task\n"); 431 | xTaskCreate(ESP32CAN::task_LowLevelRX, canLowLevelTaskName, 4096, this, 19, &task_LowLevelRX_handler); 432 | #else 433 | //this next task implements our better filtering on top of the TWAI library. Accept all frames then filter in here VVVVV 434 | xTaskCreatePinnedToCore(&task_LowLevelRX, canLowLevelTaskName, 4096, this, 19, &task_LowLevelRX_handler, 1); 435 | #endif 436 | 437 | #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0) 438 | //Start TWAI driver 439 | if (twai_start_v2(bus_handle) == ESP_OK) { 440 | printf("Driver started - bus %d\n", twai_general_cfg.controller_id); 441 | } else { 442 | printf("Failed to start driver\n"); 443 | return; 444 | } 445 | #else 446 | // Start TWAI driver 447 | if (twai_start() == ESP_OK) 448 | { 449 | printf("TWAI Driver started\n"); 450 | } 451 | else 452 | { 453 | printf("Failed to start TWAI driver\n"); 454 | return; 455 | } 456 | #endif 457 | 458 | readyForTraffic = true; 459 | } 460 | 461 | void ESP32CAN::disable() 462 | { 463 | twai_status_info_t info; 464 | if (twai_get_status_info(&info) == ESP_OK) { 465 | if (info.state == TWAI_STATE_RUNNING) { 466 | twai_stop(); 467 | } 468 | 469 | for (auto task : {task_CAN_handler, task_LowLevelRX_handler}) { 470 | if (task != NULL) 471 | { 472 | vTaskDelete(task); 473 | task = NULL; 474 | } 475 | } 476 | 477 | for (auto queue : {rx_queue, callbackQueue}) { 478 | if (queue) { 479 | vQueueDelete(queue); 480 | } 481 | } 482 | 483 | twai_driver_uninstall(); 484 | } else { 485 | return; 486 | } 487 | readyForTraffic = false; 488 | } 489 | 490 | //This function is too big to be running in interrupt context. Refactored so it doesn't. 491 | bool ESP32CAN::processFrame(twai_message_t &frame) 492 | { 493 | CANListener *thisListener; 494 | CAN_FRAME msg; 495 | 496 | cyclesSinceTraffic = 0; //reset counter to show that we are receiving traffic 497 | 498 | msg.id = frame.identifier; 499 | msg.length = frame.data_length_code; 500 | msg.rtr = frame.rtr; 501 | msg.extended = frame.extd; 502 | for (int i = 0; i < 8; i++) msg.data.byte[i] = frame.data[i]; 503 | 504 | for (int i = 0; i < BI_NUM_FILTERS; i++) 505 | { 506 | if (!filters[i].configured) continue; 507 | if ((msg.id & filters[i].mask) == filters[i].id && (filters[i].extended == msg.extended)) 508 | { 509 | //frame is accepted, lets see if it matches a mailbox callback 510 | if (cbCANFrame[i]) 511 | { 512 | msg.fid = i; 513 | xQueueSend(callbackQueue, &msg, 0); 514 | return true; 515 | } 516 | else if (cbGeneral) 517 | { 518 | msg.fid = 0xFF; 519 | xQueueSend(callbackQueue, &msg, 0); 520 | return true; 521 | } 522 | else 523 | { 524 | for (int listenerPos = 0; listenerPos < SIZE_LISTENERS; listenerPos++) 525 | { 526 | thisListener = listener[listenerPos]; 527 | if (thisListener != NULL) 528 | { 529 | if (thisListener->isCallbackActive(i)) 530 | { 531 | msg.fid = 0x80000000ul + (listenerPos << 24ul) + i; 532 | xQueueSend(callbackQueue, &msg, 0); 533 | return true; 534 | } 535 | else if (thisListener->isCallbackActive(numFilters)) //global catch-all 536 | { 537 | msg.fid = 0x80000000ul + (listenerPos << 24ul) + 0xFF; 538 | xQueueSend(callbackQueue, &msg, 0); 539 | return true; 540 | } 541 | } 542 | } 543 | } 544 | 545 | //otherwise, send frame to input queue 546 | xQueueSend(rx_queue, &msg, 0); 547 | if (debuggingMode) Serial.write('_'); 548 | return true; 549 | } 550 | } 551 | return false; 552 | } 553 | 554 | bool ESP32CAN::sendFrame(CAN_FRAME& txFrame) 555 | { 556 | twai_message_t __TX_frame; 557 | 558 | __TX_frame.identifier = txFrame.id; 559 | __TX_frame.data_length_code = txFrame.length; 560 | __TX_frame.rtr = txFrame.rtr; 561 | __TX_frame.extd = txFrame.extended; 562 | for (int i = 0; i < 8; i++) __TX_frame.data[i] = txFrame.data.byte[i]; 563 | 564 | //don't wait long if the queue was full. The end user code shouldn't be sending faster 565 | //than the buffer can empty. Set a bigger TX buffer or delay sending if this is a problem. 566 | esp_err_t result; 567 | #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0) 568 | result = twai_transmit_v2(bus_handle, &__TX_frame, pdMS_TO_TICKS(4)); 569 | #else 570 | result = twai_transmit(&__TX_frame, pdMS_TO_TICKS(4)); 571 | #endif 572 | switch (result) 573 | { 574 | case ESP_OK: 575 | if (debuggingMode) Serial.write('<'); 576 | break; 577 | case ESP_ERR_TIMEOUT: 578 | if (debuggingMode) Serial.write('T'); 579 | break; 580 | case ESP_ERR_INVALID_ARG: 581 | case ESP_FAIL: 582 | case ESP_ERR_INVALID_STATE: 583 | case ESP_ERR_NOT_SUPPORTED: 584 | if (debuggingMode) Serial.write('!'); 585 | break; 586 | } 587 | 588 | return true; 589 | } 590 | 591 | bool ESP32CAN::rx_avail() 592 | { 593 | if (!rx_queue) return false; 594 | return uxQueueMessagesWaiting(rx_queue) > 0?true:false; 595 | } 596 | 597 | uint16_t ESP32CAN::available() 598 | { 599 | if (!rx_queue) return 0; 600 | return uxQueueMessagesWaiting(rx_queue); 601 | } 602 | 603 | uint32_t ESP32CAN::get_rx_buff(CAN_FRAME &msg) 604 | { 605 | CAN_FRAME frame; 606 | //receive next CAN frame from queue 607 | if (uxQueueMessagesWaiting(rx_queue)) { 608 | if(xQueueReceive(rx_queue, &frame, 0) == pdTRUE) 609 | { 610 | msg = frame; //do a copy in the case that the receive worked 611 | return true; 612 | } 613 | else 614 | return false; 615 | } 616 | return false; //otherwise we leave the msg variable alone and just return false 617 | } 618 | 619 | -------------------------------------------------------------------------------- /src/esp32_can_builtin.h: -------------------------------------------------------------------------------- 1 | /* 2 | MCP2515.h - Library for Microchip MCP2515 CAN Controller 3 | 4 | Author: David Harding 5 | Maintainer: RechargeCar Inc (http://rechargecar.com) 6 | Further Modification: Collin Kidder 7 | 8 | Created: 11/08/2010 9 | 10 | For further information see: 11 | 12 | http://ww1.microchip.com/downloads/en/DeviceDoc/21801e.pdf 13 | http://en.wikipedia.org/wiki/CAN_bus 14 | 15 | 16 | This library is free software; you can redistribute it and/or 17 | modify it under the terms of the GNU Lesser General Public 18 | License as published by the Free Software Foundation; either 19 | version 2.1 of the License, or (at your option) any later version. 20 | 21 | This library is distributed in the hope that it will be useful, 22 | but WITHOUT ANY WARRANTY; without even the implied warranty of 23 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 24 | Lesser General Public License for more details. 25 | 26 | You should have received a copy of the GNU Lesser General Public 27 | License along with this library; if not, write to the Free Software 28 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 29 | */ 30 | 31 | #ifndef __ESP32_CAN__ 32 | #define __ESP32_CAN__ 33 | 34 | #include "Arduino.h" 35 | #include 36 | #include "freertos/FreeRTOS.h" 37 | #include "freertos/task.h" 38 | #include "freertos/queue.h" 39 | #include "driver/gpio.h" 40 | #include "driver/adc.h" 41 | #include "esp_system.h" 42 | #include "esp_adc_cal.h" 43 | #include "driver/twai.h" 44 | #include 45 | #include 46 | 47 | //#define DEBUG_SETUP 48 | #define BI_NUM_FILTERS 32 49 | 50 | #define BI_RX_BUFFER_SIZE 64 51 | #define BI_TX_BUFFER_SIZE 16 52 | 53 | typedef struct 54 | { 55 | uint32_t mask; 56 | uint32_t id; 57 | bool extended; 58 | bool configured; 59 | } ESP32_FILTER; 60 | 61 | typedef struct 62 | { 63 | twai_timing_config_t cfg; 64 | uint32_t speed; 65 | } VALID_TIMING; 66 | 67 | class ESP32CAN : public CAN_COMMON 68 | { 69 | public: 70 | ESP32CAN(gpio_num_t rxPin, gpio_num_t txPin, uint8_t busNumber = 0); 71 | ESP32CAN(); 72 | 73 | //block of functions which must be overriden from CAN_COMMON to implement functionality for this hardware 74 | int _setFilterSpecific(uint8_t mailbox, uint32_t id, uint32_t mask, bool extended); 75 | int _setFilter(uint32_t id, uint32_t mask, bool extended); 76 | void _init(); 77 | uint32_t init(uint32_t ul_baudrate); 78 | uint32_t beginAutoSpeed(); 79 | uint32_t set_baudrate(uint32_t ul_baudrate); 80 | void setListenOnlyMode(bool state); 81 | void setNoACKMode(bool state); 82 | void enable(); 83 | void disable(); 84 | bool sendFrame(CAN_FRAME& txFrame); 85 | bool rx_avail(); 86 | void setTXBufferSize(int newSize); 87 | void setRXBufferSize(int newSize); 88 | uint16_t available(); //like rx_avail but returns the number of waiting frames 89 | uint32_t get_rx_buff(CAN_FRAME &msg); 90 | bool processFrame(twai_message_t &frame); 91 | void sendCallback(CAN_FRAME *frame); 92 | 93 | void setCANPins(gpio_num_t rxPin, gpio_num_t txPin); 94 | 95 | static void CAN_WatchDog_Builtin( void *pvParameters ); 96 | 97 | #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0) 98 | twai_handle_t bus_handle; 99 | #endif 100 | 101 | protected: 102 | bool readyForTraffic; 103 | int cyclesSinceTraffic; 104 | //tx, rx, mode 105 | twai_general_config_t twai_general_cfg = TWAI_GENERAL_CONFIG_DEFAULT(GPIO_NUM_17, GPIO_NUM_16, TWAI_MODE_NORMAL); 106 | twai_timing_config_t twai_speed_cfg = TWAI_TIMING_CONFIG_500KBITS(); 107 | twai_filter_config_t twai_filters_cfg = TWAI_FILTER_CONFIG_ACCEPT_ALL(); 108 | 109 | QueueHandle_t callbackQueue; 110 | QueueHandle_t rx_queue; 111 | 112 | TaskHandle_t CAN_WatchDog_Builtin_handler = NULL; 113 | TaskHandle_t task_CAN_handler = NULL; 114 | TaskHandle_t task_LowLevelRX_handler = NULL; 115 | 116 | private: 117 | // Pin variables 118 | ESP32_FILTER filters[BI_NUM_FILTERS]; 119 | int rxBufferSize; 120 | 121 | static void task_CAN(void *pvParameters); 122 | static void task_LowLevelRX(void *pvParameters); 123 | }; 124 | 125 | extern QueueHandle_t callbackQueue; 126 | 127 | #endif 128 | --------------------------------------------------------------------------------