sl-firmware 4335d71187 fix(balance): TWAI bus-off detection, auto-recovery, console off UART0
- vesc_can: poll twai_get_status_info() every 500ms; auto-recover from
  bus-off (twai_initiate_recovery) and stopped state (twai_start)
- vesc_can: expose g_twai_bus_off / g_twai_tx_err_count / g_twai_rx_err_count
- main: set flags bit2 when TWAI is bus-off (visible in TELEM_STATUS)
- sdkconfig: switch console from UART0 (conflicts with binary protocol
  at 460800) to USB serial JTAG — eliminates log corruption on Orin

Flags byte: bit0=estop, bit1=hb_timeout, bit2=twai_bus_off

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 22:54:03 -04:00

154 lines
5.4 KiB
C
Raw Permalink 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 <string.h>
static const char *TAG = "vesc_can";
vesc_state_t g_vesc[2] = {0};
volatile bool g_twai_bus_off = false;
volatile uint32_t g_twai_tx_err_count = 0;
volatile uint32_t g_twai_rx_err_count = 0;
/* 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();
ESP_ERROR_CHECK(twai_driver_install(&gcfg, &tcfg, &fcfg));
ESP_ERROR_CHECK(twai_start());
ESP_LOGI(TAG, "TWAI init OK: tx=%d rx=%d 500kbps", VESC_CAN_TX_GPIO, VESC_CAN_RX_GPIO);
}
void vesc_can_send_rpm(uint8_t vesc_id, int32_t erpm)
{
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_err_t ret = twai_transmit(&msg, pdMS_TO_TICKS(5));
if (ret != ESP_OK) {
ESP_LOGW(TAG, "twai_transmit vesc_id=%u erpm=%ld: %s",
vesc_id, (long)erpm, esp_err_to_name(ret));
}
}
void vesc_can_rx_task(void *arg)
{
QueueHandle_t tx_q = (QueueHandle_t)arg;
twai_message_t msg;
uint32_t status_tick = 0;
for (;;) {
/* Poll TWAI health every 500 ms and recover from bus-off */
uint32_t now_ms = (uint32_t)(esp_timer_get_time() / 1000LL);
if ((now_ms - status_tick) >= 500u) {
status_tick = now_ms;
twai_status_info_t si;
if (twai_get_status_info(&si) == ESP_OK) {
g_twai_bus_off = (si.state == TWAI_STATE_BUS_OFF);
g_twai_tx_err_count = si.tx_error_counter;
g_twai_rx_err_count = si.rx_error_counter;
if (si.state == TWAI_STATE_BUS_OFF) {
ESP_LOGW(TAG, "TWAI bus-off — initiating recovery");
twai_initiate_recovery();
} else if (si.state == TWAI_STATE_STOPPED) {
ESP_LOGW(TAG, "TWAI stopped — restarting");
twai_start();
}
if (si.tx_error_counter > 0 || si.rx_error_counter > 0) {
ESP_LOGW(TAG, "TWAI errs tx=%lu rx=%lu msgs_tx=%lu msgs_rx=%lu",
(unsigned long)si.tx_error_counter,
(unsigned long)si.rx_error_counter,
(unsigned long)si.msgs_to_tx,
(unsigned long)si.msgs_to_rx);
}
}
}
if (twai_receive(&msg, pdMS_TO_TICKS(50)) != ESP_OK) {
continue;
}
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;
}
}
}