Sebastien Vayrette 2622696772 fix: Make TWAI init non-fatal + add recovery backoff
TWAI init now logs error and sets g_twai_bus_off instead of panicking.
Bus-off recovery loop increased from 100ms to 1000ms to prevent
watchdog reset when no CAN transceiver is connected.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-20 13:56:11 -04:00

169 lines
6.0 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* vesc_can.c — VESC CAN TWAI driver (bd-66hx)
*
* Receives VESC STATUS/4/5 frames via TWAI, proxies to Orin over serial.
* Transmits SET_RPM commands from Orin drive requests.
*/
#include "vesc_can.h"
#include "orin_serial.h"
#include "config.h"
#include "driver/twai.h"
#include "esp_log.h"
#include "esp_timer.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include <inttypes.h>
#include <string.h>
static const char *TAG = "vesc_can";
vesc_state_t g_vesc[2] = {0};
volatile bool g_twai_bus_off = false;
/* Index for a given VESC node ID: 0=VESC_ID_A, 1=VESC_ID_B */
static int vesc_idx(uint8_t id)
{
if (id == VESC_ID_A) return 0;
if (id == VESC_ID_B) return 1;
return -1;
}
void vesc_can_init(void)
{
twai_general_config_t gcfg = TWAI_GENERAL_CONFIG_DEFAULT(
(gpio_num_t)VESC_CAN_TX_GPIO,
(gpio_num_t)VESC_CAN_RX_GPIO,
TWAI_MODE_NORMAL);
gcfg.rx_queue_len = VESC_CAN_RX_QUEUE;
gcfg.tx_queue_len = 5;
twai_timing_config_t tcfg = TWAI_TIMING_CONFIG_500KBITS();
twai_filter_config_t fcfg = TWAI_FILTER_CONFIG_ACCEPT_ALL();
ESP_LOGI(TAG, "TWAI: installing driver tx=%d rx=%d 500kbps", VESC_CAN_TX_GPIO, VESC_CAN_RX_GPIO);
esp_err_t err = twai_driver_install(&gcfg, &tcfg, &fcfg);
if (err != ESP_OK) {
ESP_LOGE(TAG, "TWAI install failed (0x%x) — CAN disabled", err);
g_twai_bus_off = true;
return;
}
err = twai_start();
if (err != ESP_OK) {
ESP_LOGE(TAG, "TWAI start failed (0x%x) — CAN disabled", err);
g_twai_bus_off = true;
return;
}
vTaskDelay(pdMS_TO_TICKS(200));
ESP_LOGI(TAG, "TWAI: started OK — bus active");
}
void vesc_can_send_rpm(uint8_t vesc_id, int32_t erpm)
{
if (g_twai_bus_off) { return; }
uint32_t ext_id = ((uint32_t)VESC_PKT_SET_RPM << 8u) | vesc_id;
twai_message_t msg = {
.extd = 1,
.identifier = ext_id,
.data_length_code = 4,
};
uint32_t u = (uint32_t)erpm;
msg.data[0] = (uint8_t)(u >> 24u);
msg.data[1] = (uint8_t)(u >> 16u);
msg.data[2] = (uint8_t)(u >> 8u);
msg.data[3] = (uint8_t)(u);
ESP_LOGI(TAG, "send_rpm vesc_id=%u erpm=%" PRId32 " ext_id=0x%08" PRIx32, vesc_id, erpm, ext_id);
esp_err_t err = twai_transmit(&msg, pdMS_TO_TICKS(5));
if (err != ESP_OK) {
ESP_LOGW(TAG, "twai_transmit failed vesc_id=%u err=0x%x", vesc_id, err);
}
}
void vesc_can_rx_task(void *arg)
{
QueueHandle_t tx_q = (QueueHandle_t)arg;
twai_message_t msg;
for (;;) {
esp_err_t rx_err = twai_receive(&msg, pdMS_TO_TICKS(50));
if (rx_err != ESP_OK) {
if (rx_err != ESP_ERR_TIMEOUT) {
ESP_LOGW(TAG, "twai_receive err=0x%x", rx_err);
}
twai_status_info_t si;
if (twai_get_status_info(&si) == ESP_OK) {
/* Mark bus-off for ANY non-running state so vesc_can_send_rpm
* won't flood with failed transmits during recovery. */
g_twai_bus_off = (si.state != TWAI_STATE_RUNNING);
if (si.state == TWAI_STATE_BUS_OFF) {
ESP_LOGE(TAG, "TWAI BUS OFF tx_err=%lu rx_err=%lu — recovering",
(unsigned long)si.tx_error_counter, (unsigned long)si.rx_error_counter);
twai_initiate_recovery();
vTaskDelay(pdMS_TO_TICKS(1000));
} else if (si.state == TWAI_STATE_STOPPED) {
esp_err_t serr = twai_start();
if (serr == ESP_OK) {
g_twai_bus_off = false;
ESP_LOGI(TAG, "TWAI recovered — bus active");
} else {
ESP_LOGE(TAG, "TWAI restart failed 0x%x — backing off", serr);
vTaskDelay(pdMS_TO_TICKS(2000));
}
}
/* TWAI_STATE_RECOVERING: initiation already called, just wait. */
}
continue;
}
ESP_LOGI(TAG, "twai_receive OK id=0x%08" PRIx32 " dlc=%u", msg.identifier, msg.data_length_code);
if (!msg.extd) {
continue; /* ignore standard frames */
}
uint8_t pkt_type = (uint8_t)(msg.identifier >> 8u);
uint8_t vesc_id = (uint8_t)(msg.identifier & 0xFFu);
int idx = vesc_idx(vesc_id);
if (idx < 0) {
continue; /* not our VESC */
}
uint32_t now_ms = (uint32_t)(esp_timer_get_time() / 1000LL);
vesc_state_t *s = &g_vesc[idx];
switch (pkt_type) {
case VESC_PKT_STATUS:
if (msg.data_length_code < 8u) { break; }
s->erpm = (int32_t)(
((uint32_t)msg.data[0] << 24u) | ((uint32_t)msg.data[1] << 16u) |
((uint32_t)msg.data[2] << 8u) | (uint32_t)msg.data[3]);
s->current_x10 = (int16_t)(((uint16_t)msg.data[4] << 8u) | msg.data[5]);
s->last_rx_ms = now_ms;
/* Proxy to Orin: voltage from STATUS_5 (may be zero until received) */
{
uint8_t ttype = (vesc_id == VESC_ID_A) ? TELEM_VESC_LEFT : TELEM_VESC_RIGHT;
/* voltage_mv: V×10 → mV (/10 * 1000 = *100); current_ma: A×10 → mA (*100) */
uint16_t vmv = (uint16_t)((int32_t)s->voltage_x10 * 100);
int16_t ima = (int16_t)((int32_t)s->current_x10 * 100);
orin_send_vesc(tx_q, ttype, s->erpm, vmv, ima,
(uint16_t)s->temp_mot_x10);
}
break;
case VESC_PKT_STATUS_4:
if (msg.data_length_code < 6u) { break; }
/* T_fet×10, T_mot×10, I_in×10 */
s->temp_mot_x10 = (int16_t)(((uint16_t)msg.data[2] << 8u) | msg.data[3]);
break;
case VESC_PKT_STATUS_5:
if (msg.data_length_code < 6u) { break; }
/* int32 tacho (ignored), int16 V_in×10 */
s->voltage_x10 = (int16_t)(((uint16_t)msg.data[4] << 8u) | msg.data[5]);
break;
default:
break;
}
}
}