sl-firmware 34a937628d fix: guard TWAI tx against bus-off and fix recovery state machine
Three bugs blocked CAN forwarding when UART commands were received:
1. vesc_can_send_rpm had no g_twai_bus_off guard, flooding failed
   twai_transmit calls during BUS_OFF/RECOVERING states.
2. Recovery only handled TWAI_STATE_BUS_OFF; RECOVERING and STOPPED
   states were unhandled, leaving g_twai_bus_off=false while TWAI
   was still unusable.
3. No startup delay after twai_start() — VESC not yet ready to ACK
   caused immediate TEC runup to BUS_OFF at boot.

Fix: bus-off guard in send_rpm, full state machine in rx_task
(BUS_OFF→initiate, STOPPED→start, RECOVERING→wait), 200ms post-
start delay in vesc_can_init().

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 11:38:44 -04:00

165 lines
6.1 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;
twai_timing_config_t tcfg = TWAI_TIMING_CONFIG_500KBITS();
twai_filter_config_t fcfg = TWAI_FILTER_CONFIG_ACCEPT_ALL();
gcfg.tx_queue_len = 5;
ESP_LOGI(TAG, "TWAI: installing driver tx=%d rx=%d 500kbps", VESC_CAN_TX_GPIO, VESC_CAN_RX_GPIO);
ESP_ERROR_CHECK(twai_driver_install(&gcfg, &tcfg, &fcfg));
ESP_LOGI(TAG, "TWAI: driver installed OK");
ESP_ERROR_CHECK(twai_start());
/* Wait for VESC to join the bus before drive_task begins TX — prevents
* immediate TEC runup and BUS_OFF if VESC CAN isn't ready at ESP32 boot. */
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();
/* Driver auto-transitions RECOVERING→STOPPED after 128 recessive
* bit occurrences (~3 ms min at 500kbps); 100 ms is safe headroom. */
vTaskDelay(pdMS_TO_TICKS(100));
} else if (si.state == TWAI_STATE_STOPPED) {
/* Recovery completed — restart the driver. */
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", serr);
}
}
/* 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;
}
}
}