Tasmota/tasmota/tasmota_xdrv_driver/xdrv_91_esp32_twai.ino

424 lines
16 KiB
Arduino
Raw Normal View History

2024-12-28 16:46:15 +00:00
/*
xdrv_91_esp32_twai.ino - ESP32 TWAI support for Tasmota
SPDX-FileCopyrightText: 2025 Theo Arends
2024-12-28 16:46:15 +00:00
SPDX-License-Identifier: GPL-3.0-only
*/
#ifdef ESP32
#ifdef USE_ESP32_TWAI
#if SOC_TWAI_SUPPORTED
2024-12-28 16:46:15 +00:00
/*********************************************************************************************\
* ESP32 Two-Wire Automotive Interface (TWAI) or Controller Area Network (CAN) busses.
* - Supports all TWAI busses provided by different ESP32 architectures.
* - Needs external tranceiver like M5Unit Mini CAN (not isolated!) or M5Unit CANBus (isolated).
2024-12-28 16:46:15 +00:00
*
* The TWAI controller is not compatible with ISO11898-1 FD Format frames, and will interpret
* such frames as errors. CAN bus message format is 32-bit identifier and up to 8 bytes of data
2024-12-28 16:46:15 +00:00
*
* Due to the CAN-bus nature of many received datagrams you'll needs a berry driver with
* class "twai" and functions "config" and "decode".
* - See example below.
* - When executed by `preinit.be` it allows to configure the TWAI driver mode and speed
*
2025-01-03 09:37:18 +00:00
* For more information see https://tasmota.github.io/docs/TWAI/
*
* Supported command:
* TwaiSend[<bus>] <identifier>[[[[[[[[<data1>],<data2>],<data3>],<data4>],<data5>],<data6>],<data7>],<data8>]
* TwaiSend[<bus>] {"ID":<identifier>,["DATA":[[[[[[[<data1>],<data2>],<data3>],<data4>],<data5>],<data6>],<data7>],<data8>]]}
* - An <identifier> with bit 31 set (0x80000000) is a 29-bit identifier
* - All <data> is optional
*
* Examples:
* TwaiSend1 0x481,0x03,0x1E,0xFF,0xFF,0xFF,0x01,0x21
* TwaiSend1 {"ID":0x481,"DATA":[0x03,0x1E,0xFF,0xFF,0xFF,0x01,0x21]}
* TwaiSend2 {"ID":0x80000481,"DATA":[0x03,0x1E,0xFF,0xFF,0xFF,0x01,0x21]}
2024-12-28 16:46:15 +00:00
\*********************************************************************************************/
#define XDRV_91 91
#define TWAI_POLLING_RATE_MS 100
2024-12-29 16:07:05 +00:00
#include "driver/twai.h"
2024-12-28 16:46:15 +00:00
enum TwaiSpeeds { TWAI_SPEED_25KBITS = 0,
TWAI_SPEED_50KBITS,
TWAI_SPEED_100KBITS,
TWAI_SPEED_125KBITS,
TWAI_SPEED_250KBITS,
TWAI_SPEED_500KBITS,
TWAI_SPEED_800KBITS,
TWAI_SPEED_1MBITS,
TWAI_SPEED_MAX };
2024-12-29 16:07:05 +00:00
const char kTwaiSpeeds[] PROGMEM = "25K|50K|100K|125K|250K|500K|800K|1M";
const char kTwaiModes[] PROGMEM = "Normal|No Ack|Listen Only";
2024-12-28 16:46:15 +00:00
struct TWAIs {
twai_handle_t bus[MAX_TWAI];
twai_mode_t mode[MAX_TWAI];
uint8_t speed[MAX_TWAI];
bool installed[MAX_TWAI];
2024-12-28 16:46:15 +00:00
bool supported;
} Twai;
2024-12-29 16:07:05 +00:00
#ifdef USE_BERRY
/*********************************************************************************************\
* Berry example code
\*********************************************************************************************/
/*
2025-01-03 09:37:18 +00:00
file twai_minimal.be contents:
2024-12-29 16:07:05 +00:00
class twai_cls
2025-01-03 09:37:18 +00:00
var twai_speed, twai_mode # (int, int)
2024-12-29 16:07:05 +00:00
def init()
2025-01-03 09:37:18 +00:00
self.twai_speed = 4 # 0 = 25K, 1 = 50K, 2 = 100K, 3 = 125K, 4 = 250K, 5 = 500K, 6 = 800K, 7 = 1Mbits
2024-12-29 16:07:05 +00:00
self.twai_mode = 2 # 0 = TWAI_MODE_NORMAL, 1 = TWAI_MODE_NO_ACK, 2 = TWAI_MODE_LISTEN_ONLY
end
2025-01-01 14:48:02 +00:00
#----------------------------------------------------------------------------------------------
Allow TWAI driver configuration on restart (if this file is installed by preinit.be)
----------------------------------------------------------------------------------------------#
2025-01-03 09:37:18 +00:00
def config(bus) # This function name (config) is called by the TWAI driver!
return self.twai_mode << 3 | self.twai_speed # Initial configure TWAI driver
2024-12-29 16:07:05 +00:00
end
2025-01-01 14:48:02 +00:00
#----------------------------------------------------------------------------------------------
2025-01-03 09:37:18 +00:00
This example decodes nothing but allows for the driver to show message logging in log level 4
2025-01-01 14:48:02 +00:00
----------------------------------------------------------------------------------------------#
2025-01-03 09:37:18 +00:00
def decode(param, ident, data1, data2) # This function name (decode) is called by the TWAI driver!
2025-01-01 14:48:02 +00:00
var bus = param & 0xF # Bus number (1..3)
var len = param >> 4 & 0xF # Number of data bytes (0..8)
var extended = ident >> 31 & 0x1 # Extended identifier flag (0..1)
var id = ident & 0x1fffffff
2024-12-29 16:07:05 +00:00
end
end
2025-01-01 14:48:02 +00:00
2025-01-03 09:37:18 +00:00
twai = twai_cls() # This class name (twai) is used by the TWAI driver!
2024-12-29 16:07:05 +00:00
tasmota.add_driver(twai)
// *******************************************************************************************
file preinit.be contents:
2025-01-03 09:37:18 +00:00
load('twai_minimal.be')
2024-12-29 16:07:05 +00:00
*/
2024-12-28 16:46:15 +00:00
/*********************************************************************************************\
2024-12-29 16:07:05 +00:00
* Berry interface
\*********************************************************************************************/
bool TWAIBerryDecode(uint32_t bus, uint32_t identifier, uint32_t data_length_code, uint8_t *data) {
2024-12-29 16:07:05 +00:00
bvm *vm = berry.vm;
if (nullptr == vm) { return false; }
bool handled = false;
if (be_getglobal(vm, "twai")) { // Look for class
if (be_getmember(vm, 1, "decode")) { // and function
be_pushvalue(vm, -2);
2025-01-01 14:48:02 +00:00
uint32_t param = bus +1 | data_length_code << 4;
uint32_t ident = identifier;
2024-12-29 16:07:05 +00:00
uint32_t data1 = data[3] << 24 | data[2] << 16 | data[1] << 8 | data[0];
uint32_t data2 = data[7] << 24 | data[6] << 16 | data[5] << 8 | data[4];
2025-01-01 14:48:02 +00:00
AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("TWA: Bus%d param %08X, ident %08X, data1 %08X, data2 %08X"), bus +1, param, ident, data1, data2);
2024-12-29 16:07:05 +00:00
2025-01-01 14:48:02 +00:00
be_pushint(vm, param); // Bus 1, 2 or 3 | data_length
2024-12-29 16:07:05 +00:00
be_pushint(vm, ident);
be_pushint(vm, data1);
be_pushint(vm, data2);
int32_t ret = be_pcall(vm, 5); // Number of arguments, self, bus, ident. data1, data2
2024-12-29 16:07:05 +00:00
if (ret != 0) {
be_error_pop_all(vm); // Clear Berry stack
}
handled = true;
}
}
be_pop(vm, be_top(vm)); // Clean
return handled;
}
/*********************************************************************************************/
bool TWAIBerryConfig(uint32_t bus) {
2024-12-29 16:07:05 +00:00
bvm *vm = berry.vm;
if (nullptr == vm) { return false; }
bool handled = false;
if (be_getglobal(vm, "twai")) { // Look for class
if (be_getmember(vm, 1, "config")) { // and function
be_pushvalue(vm, -2);
be_pushint(vm, bus +1); // Bus 1, 2 or 3
int32_t ret = be_pcall(vm, 2); // Number of arguments, self, bus
2024-12-29 16:07:05 +00:00
if (ret != 0) {
be_error_pop_all(vm); // Clear Berry stack
}
be_pop(vm, 2);
2024-12-29 16:07:05 +00:00
if (be_isint(vm, -1)) {
uint32_t config = be_toint(vm, -1);
Twai.speed[bus] = config & 0x7; // User input check 0..7
2024-12-29 16:07:05 +00:00
uint32_t mode = config >> 3 & 0x3;
if (mode > 2) { mode = 2; } // User input check 0..2
Twai.mode[bus] = (twai_mode_t)mode;
2024-12-29 16:07:05 +00:00
handled = true;
}
}
}
be_pop(vm, be_top(vm)); // Clean
return handled;
}
#endif // USE_BERRY
/*********************************************************************************************\
* TWAI low level
2024-12-28 16:46:15 +00:00
\*********************************************************************************************/
bool TWAIStart(int tx, int rx, uint32_t bus = 0);
bool TWAIStart(int tx, int rx, uint32_t bus) {
twai_general_config_t g_config = TWAI_GENERAL_CONFIG_DEFAULT((gpio_num_t)tx, (gpio_num_t)rx, Twai.mode[bus]);
g_config.controller_id = bus;
2024-12-28 16:46:15 +00:00
g_config.rx_queue_len = 32;
twai_timing_config_t t_config;
switch (Twai.speed[bus]) {
2024-12-28 16:46:15 +00:00
case 0:
t_config = TWAI_TIMING_CONFIG_25KBITS();
break;
case 1:
t_config = TWAI_TIMING_CONFIG_50KBITS();
break;
case 2:
t_config = TWAI_TIMING_CONFIG_100KBITS();
break;
case 3:
t_config = TWAI_TIMING_CONFIG_125KBITS();
break;
case 4:
t_config = TWAI_TIMING_CONFIG_250KBITS();
break;
case 5:
t_config = TWAI_TIMING_CONFIG_500KBITS();
break;
case 6:
t_config = TWAI_TIMING_CONFIG_800KBITS();
break;
case 7:
t_config = TWAI_TIMING_CONFIG_1MBITS();
break;
default:
t_config = TWAI_TIMING_CONFIG_125KBITS();
break;
}
twai_filter_config_t f_config = TWAI_FILTER_CONFIG_ACCEPT_ALL();
if (twai_driver_install_v2(&g_config, &t_config, &f_config, &Twai.bus[bus]) != ESP_OK) {
AddLog(LOG_LEVEL_DEBUG, PSTR("TWA: Bus%d Not installed"), bus +1);
2024-12-28 16:46:15 +00:00
return false;
}
if (twai_start_v2(Twai.bus[bus]) != ESP_OK) {
AddLog(LOG_LEVEL_DEBUG, PSTR("TWA: Bus%d Not started"), bus +1);
2024-12-28 16:46:15 +00:00
return false;
}
// Reconfigure alerts to detect frame receive, Bus-Off error and RX queue full states
uint32_t alerts_to_enable = TWAI_ALERT_RX_DATA | TWAI_ALERT_RX_QUEUE_FULL | TWAI_ALERT_TX_IDLE | TWAI_ALERT_TX_SUCCESS | TWAI_ALERT_TX_FAILED | TWAI_ALERT_ERR_PASS | TWAI_ALERT_BUS_ERROR;
if (twai_reconfigure_alerts_v2(Twai.bus[bus], alerts_to_enable, NULL) != ESP_OK) {
AddLog(LOG_LEVEL_DEBUG, PSTR("TWA: Bus%d Failed to reconfigure CAN alerts"), bus +1);
2024-12-28 16:46:15 +00:00
return false;
}
2024-12-29 16:07:05 +00:00
char smode[16];
GetTextIndexed(smode, sizeof(smode), Twai.mode[bus], kTwaiModes);
2024-12-29 16:07:05 +00:00
char sspeed[16];
GetTextIndexed(sspeed, sizeof(sspeed), Twai.speed[bus], kTwaiSpeeds);
AddLog(LOG_LEVEL_DEBUG, PSTR("TWA: Bus%d using GPIO%02d(Tx) and GPIO%02d(Rx) started in %s Mode and %sbit/s"),
bus +1, tx, rx, smode, sspeed);
Twai.installed[bus] = true;
2024-12-28 16:46:15 +00:00
return true;
}
/*********************************************************************************************/
void TWAIStop(uint32_t bus) {
if (Twai.installed[bus]) {
twai_stop_v2(Twai.bus[bus]);
twai_driver_uninstall_v2(Twai.bus[bus]);
Twai.installed[bus] = false;
AddLog(LOG_LEVEL_DEBUG, PSTR("TWA: Bus%d stopped"), bus +1);
2024-12-28 16:46:15 +00:00
}
}
/*********************************************************************************************/
uint32_t TWAICheckAlerts(uint32_t bus) {
2024-12-28 16:46:15 +00:00
uint32_t alerts_triggered;
twai_read_alerts_v2(Twai.bus[bus], &alerts_triggered, pdMS_TO_TICKS(TWAI_POLLING_RATE_MS));
2024-12-28 16:46:15 +00:00
twai_status_info_t twaistatus;
twai_get_status_info_v2(Twai.bus[bus], &twaistatus);
2024-12-28 16:46:15 +00:00
// Handle alerts
if (alerts_triggered & TWAI_ALERT_ERR_PASS) {
AddLog(LOG_LEVEL_DEBUG, PSTR("TWA: Bus%d Alert - Controller has become error passive"), bus +1);
2024-12-28 16:46:15 +00:00
}
if (alerts_triggered & TWAI_ALERT_BUS_ERROR) {
AddLog(LOG_LEVEL_DEBUG, PSTR("TWA: Bus%d Alert - A Bit, Stuff, CRC, Form or ACK error has occurred on the bus. Errors %d"), bus +1, twaistatus.bus_error_count);
2024-12-28 16:46:15 +00:00
}
if (alerts_triggered & TWAI_ALERT_RX_QUEUE_FULL) {
AddLog(LOG_LEVEL_DEBUG, PSTR("TWA: Bus%d Alert - RX queue is full causing a received frame to be lost. Buffered %d, Missed %d, Overrun %d"),
bus +1, twaistatus.msgs_to_rx, twaistatus.rx_missed_count, twaistatus.rx_overrun_count);
2024-12-28 16:46:15 +00:00
}
if (alerts_triggered & TWAI_ALERT_TX_FAILED) {
AddLog(LOG_LEVEL_DEBUG, PSTR("TWA: Bus%d Alert - Transmission failed. Buffered %d, Error %d, Failed %d"),
bus +1, twaistatus.msgs_to_tx, twaistatus.tx_error_counter, twaistatus.tx_failed_count);
2024-12-28 16:46:15 +00:00
}
// if (alerts_triggered & TWAI_ALERT_TX_SUCCESS) {
// AddLog(LOG_LEVEL_DEBUG, PSTR("TWA: Bus%d Transmission successful. Buffered %d"), bus +1, twaistatus.msgs_to_tx);
// }
2024-12-28 16:46:15 +00:00
return alerts_triggered;
}
/*********************************************************************************************/
void TWAIRead(uint32_t bus) {
uint32_t alerts_triggered = TWAICheckAlerts(bus);
2024-12-28 16:46:15 +00:00
// Check if message is received
if (alerts_triggered & TWAI_ALERT_RX_DATA) {
// One or more messages received. Handle all.
twai_message_t message;
while (twai_receive_v2(Twai.bus[bus], &message, 0) == ESP_OK) {
2025-01-01 14:48:02 +00:00
uint32_t identifier = message.identifier | message.extd << 31; // Add extended 29-bit flag
2024-12-28 16:46:15 +00:00
#ifdef USE_BERRY
2025-01-01 14:48:02 +00:00
if (TWAIBerryDecode(bus, identifier, message.data_length_code, message.data)) { continue; }
2024-12-29 16:07:05 +00:00
#endif // USE_BERRY
// Show log if no berry found (Messages come too fast for supporting MQTT at this time)
AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("TWA: Bus%d Id 0x%08X, Rcvd '%*_H'"),
2025-01-01 14:48:02 +00:00
bus +1, identifier, message.data_length_code, message.data);
2024-12-28 16:46:15 +00:00
}
}
}
/*********************************************************************************************\
* Initialization
\*********************************************************************************************/
void TWAIInit(void) {
Twai.supported = false;
for (uint32_t bus = 0; bus < MAX_TWAI; bus++) {
if (PinUsed(GPIO_TWAI_TX, bus) && PinUsed(GPIO_TWAI_RX, bus)) {
2025-01-02 11:44:49 +00:00
Twai.speed[bus] = TWAI_SPEED_100KBITS;
Twai.mode[bus] = TWAI_MODE_NORMAL; // 0 = TWAI_MODE_NORMAL, 1 = TWAI_MODE_NO_ACK, 2 = TWAI_MODE_LISTEN_ONLY
2024-12-29 16:07:05 +00:00
#ifdef USE_BERRY
TWAIBerryConfig(bus);
2024-12-29 16:07:05 +00:00
#endif // USE_BERRY
if (TWAIStart(Pin(GPIO_TWAI_TX, bus), Pin(GPIO_TWAI_RX, bus), bus)) {
Twai.supported = true;
}
}
}
}
/*********************************************************************************************\
* Commands
\*********************************************************************************************/
const char kTwaiCommands[] PROGMEM = "TWAI|" // Prefix
"Send";
void (* const TwaiCommand[])(void) PROGMEM = {
&CmndTWAISend };
void CmndTWAISend(void) {
// TwaiSend<bus> <identifier>[[[[[[[[<data1>],<data2>],<data3>],<data4>],<data5>],<data6>],<data7>],<data8>]
// TwaiSend<bus> {"ID":<identifier>,"DATA":[[[[[[[[<data1>],<data2>],<data3>],<data4>],<data5>],<data6>],<data7>],<data8>]]}
// An identifier with bit 32 set (0x80000000) is a 29-bit identifier
// TwaiSend1 0x481,0x03,0x1E,0xFF,0xFF,0xFF,0x01,0x21
// TwaiSend1 {"ID":0x481,"DATA":[0x03,0x1E,0xFF,0xFF,0xFF,0x01,0x21]}
// TwaiSend2 {"ID":0x80000481,"DATA":[0x03,0x1E,0xFF,0xFF,0xFF,0x01,0x21]}
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_TWAI) && (XdrvMailbox.data_len > 0)) {
uint32_t bus = XdrvMailbox.index -1;
if (!Twai.installed[bus]) { return; }
uint32_t data[9] = { 0 }; // Accomodate identifier and 8 data bytes
uint32_t data_count = 0;
if (XdrvMailbox.data[0] == ('{')) {
// parse JSON
JsonParser parser(XdrvMailbox.data);
JsonParserObject root = parser.getRootObject();
if (!root) { return; } // No valid JSON
JsonParserToken val = root[PSTR("ID")];
if (!val) { return; } // We need at least an identifier
data[0] = val.getUInt();
data_count = 1;
JsonParserArray arr = root[PSTR("DATA")];
for (auto value : arr) { // All data is optional
data[data_count] = value.getUInt();
data_count++;
if (9 == data_count) {
break;
}
}
} else {
// parse comma separated values
data_count = ParseParameters(9, data);
}
twai_message_t message;
message.extd = (data[0] & 0x80000000) ? 1 : 0; // Standard (0) vs extended (1) format
message.rtr = 0, // Data (0) vs RTR (1) frame
message.ss = 0, // Whether the message is single shot (1) (i.e., does not repeat on error)
message.self = 0, // Whether the message is a self reception request (1) (loopback)
message.dlc_non_comp = 0, // DLC is less than 8 (0)
message.identifier = data[0] & 0x1FFFFFFF; // Message ID (11-bits or 29-bits)
message.data_length_code = data_count -1; // Payload length (0..8)
for (uint8_t i = 0; i < message.data_length_code; i++) {
message.data[i] = data[i +1]; // Payload
}
twai_clear_receive_queue_v2(Twai.bus[bus]);
//Queue message for transmission
if (twai_transmit_v2(Twai.bus[bus], &message, pdMS_TO_TICKS(TWAI_POLLING_RATE_MS)) == ESP_OK) {
AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("TWA: Bus%d Message queued for transmission"), bus +1);
} else {
AddLog(LOG_LEVEL_DEBUG, PSTR("TWA: Bus%d Alert - Failed to queue message for transmission"), bus +1);
}
ResponseCmndDone();
2024-12-28 16:46:15 +00:00
}
}
/*********************************************************************************************\
* Interface
\*********************************************************************************************/
bool Xdrv91(uint32_t function) {
bool result = false;
if (FUNC_INIT == function) {
TWAIInit();
}
else if (Twai.supported) {
switch (function) {
case FUNC_LOOP:
for (uint32_t bus = 0; bus < MAX_TWAI; bus++) {
if (Twai.installed[bus]) {
TWAIRead(bus);
}
2024-12-28 16:46:15 +00:00
}
break;
case FUNC_COMMAND:
result = DecodeCommand(kTwaiCommands, TwaiCommand);
break;
2024-12-28 16:46:15 +00:00
}
}
return result;
}
#endif // SOC_TWAI_SUPPORTED
2024-12-28 16:46:15 +00:00
#endif // USE_ESP32_TWAI
#endif // ESP32