espilon-source/espilon_bot/components/mod_canbus/canbus_driver.c
Eun0us 6d45770d98 epsilon: merge command system into core + add 5 new modules
Move command registry from components/command/ into components/core/.
New modules: mod_canbus, mod_honeypot, mod_fallback, mod_redteam, mod_ota.
Replace mod_proxy with tun_core (multiplexed SOCKS5 tunnel).
Kconfig extended with per-module settings and async worker config.
2026-02-28 20:07:59 +01:00

816 lines
24 KiB
C

/*
* canbus_driver.c
* MCP2515 CAN 2.0B controller driver via ESP-IDF SPI master.
*
* Architecture:
* GPIO ISR (INT pin, active low) → binary semaphore → RX task → callback
* TX: direct SPI writes to TX buffer 0, poll for completion.
*/
#include "sdkconfig.h"
#ifdef CONFIG_MODULE_CANBUS
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "driver/spi_master.h"
#include "driver/gpio.h"
#include "esp_timer.h"
#include "esp_log.h"
#include "canbus_driver.h"
#define TAG "CAN_DRV"
/* ============================================================
* MCP2515 SPI Instructions
* ============================================================ */
#define MCP_RESET 0xC0
#define MCP_READ 0x03
#define MCP_WRITE 0x02
#define MCP_BIT_MODIFY 0x05
#define MCP_READ_STATUS 0xA0
#define MCP_RX_STATUS 0xB0
#define MCP_READ_RX0 0x90 /* Read RX buffer 0 starting at SIDH */
#define MCP_READ_RX1 0x94 /* Read RX buffer 1 starting at SIDH */
#define MCP_LOAD_TX0 0x40 /* Load TX buffer 0 starting at SIDH */
#define MCP_RTS_TX0 0x81 /* Request-To-Send TX buffer 0 */
/* ============================================================
* MCP2515 Registers
* ============================================================ */
#define MCP_CANCTRL 0x0F
#define MCP_CANSTAT 0x0E
#define MCP_CNF1 0x2A
#define MCP_CNF2 0x29
#define MCP_CNF3 0x28
#define MCP_CANINTE 0x2B
#define MCP_CANINTF 0x2C
#define MCP_EFLG 0x2D
#define MCP_TEC 0x1C
#define MCP_REC 0x1D
/* RXB0CTRL / RXB1CTRL */
#define MCP_RXB0CTRL 0x60
#define MCP_RXB1CTRL 0x70
/* Filter/mask registers */
#define MCP_RXF0SIDH 0x00
#define MCP_RXF1SIDH 0x04
#define MCP_RXF2SIDH 0x08
#define MCP_RXF3SIDH 0x10
#define MCP_RXF4SIDH 0x14
#define MCP_RXF5SIDH 0x18
#define MCP_RXM0SIDH 0x20
#define MCP_RXM1SIDH 0x24
/* TXB0 registers */
#define MCP_TXB0CTRL 0x30
#define MCP_TXB0SIDH 0x31
/* CANCTRL mode bits */
#define MCP_MODE_NORMAL 0x00
#define MCP_MODE_LISTEN 0x60
#define MCP_MODE_LOOPBACK 0x40
#define MCP_MODE_CONFIG 0x80
/* CANINTF bits */
#define MCP_RX0IF 0x01
#define MCP_RX1IF 0x02
#define MCP_TX0IF 0x04
#define MCP_TX1IF 0x08
#define MCP_TX2IF 0x10
#define MCP_ERRIF 0x20
#define MCP_WAKIF 0x40
#define MCP_MERRF 0x80
/* CANINTE bits */
#define MCP_RX0IE 0x01
#define MCP_RX1IE 0x02
#define MCP_ERRIE 0x20
/* EFLG bits */
#define MCP_EFLG_RX0OVR 0x40
#define MCP_EFLG_RX1OVR 0x80
#define MCP_EFLG_TXBO 0x20
#define MCP_EFLG_RXEP 0x10
#define MCP_EFLG_TXEP 0x08
/* ============================================================
* Bit Timing Tables
* ============================================================ */
typedef struct {
int bitrate;
uint8_t cnf1, cnf2, cnf3;
} can_timing_t;
/* 16 MHz oscillator — TQ = 2/Fosc = 125ns */
static const can_timing_t s_timing_16mhz[] = {
{ 1000000, 0x00, 0xCA, 0x01 }, /* 1 Mbps: SJW=1, BRP=0, 8 TQ */
{ 500000, 0x00, 0xF0, 0x86 }, /* 500 kbps: SJW=1, BRP=0, 16 TQ */
{ 250000, 0x01, 0xF0, 0x86 }, /* 250 kbps: SJW=1, BRP=1, 16 TQ */
{ 125000, 0x03, 0xF0, 0x86 }, /* 125 kbps: SJW=1, BRP=3, 16 TQ */
{ 100000, 0x04, 0xF0, 0x86 }, /* 100 kbps: SJW=1, BRP=4, 16 TQ */
{ 0, 0, 0, 0 }
};
/* 8 MHz oscillator — TQ = 2/Fosc = 250ns */
static const can_timing_t s_timing_8mhz[] = {
{ 500000, 0x00, 0x90, 0x02 }, /* 500 kbps: SJW=1, BRP=0, 8 TQ */
{ 250000, 0x00, 0xF0, 0x86 }, /* 250 kbps: SJW=1, BRP=0, 16 TQ */
{ 125000, 0x01, 0xF0, 0x86 }, /* 125 kbps: SJW=1, BRP=1, 16 TQ */
{ 100000, 0x03, 0xAC, 0x03 }, /* 100 kbps: SJW=1, BRP=3, 10 TQ */
{ 0, 0, 0, 0 }
};
/* ============================================================
* Driver State
* ============================================================ */
static spi_device_handle_t s_spi = NULL;
static TaskHandle_t s_rx_task = NULL;
static SemaphoreHandle_t s_int_sem = NULL;
static SemaphoreHandle_t s_tx_mutex = NULL;
static volatile bool s_running = false;
static can_rx_callback_t s_rx_cb = NULL;
static void *s_rx_ctx = NULL;
/* Counters */
static uint32_t s_rx_count = 0;
static uint32_t s_tx_count = 0;
static uint32_t s_bus_errors = 0;
static uint32_t s_rx_overflow = 0;
static bool s_bus_off = false;
static bool s_err_passive = false;
/* ============================================================
* SPI Low-Level Helpers
* ============================================================ */
static uint8_t mcp_read_reg(uint8_t addr)
{
uint8_t tx[3] = { MCP_READ, addr, 0x00 };
uint8_t rx[3] = { 0 };
spi_transaction_t t = {
.length = 24,
.tx_buffer = tx,
.rx_buffer = rx,
};
spi_device_transmit(s_spi, &t);
return rx[2];
}
static void mcp_write_reg(uint8_t addr, uint8_t val)
{
uint8_t tx[3] = { MCP_WRITE, addr, val };
spi_transaction_t t = {
.length = 24,
.tx_buffer = tx,
};
spi_device_transmit(s_spi, &t);
}
static void mcp_modify_reg(uint8_t addr, uint8_t mask, uint8_t val)
{
uint8_t tx[4] = { MCP_BIT_MODIFY, addr, mask, val };
spi_transaction_t t = {
.length = 32,
.tx_buffer = tx,
};
spi_device_transmit(s_spi, &t);
}
static void mcp_reset(void)
{
uint8_t tx[1] = { MCP_RESET };
spi_transaction_t t = {
.length = 8,
.tx_buffer = tx,
};
spi_device_transmit(s_spi, &t);
vTaskDelay(pdMS_TO_TICKS(10)); /* MCP2515 needs time after reset */
}
static void mcp_set_mode(uint8_t mode)
{
mcp_modify_reg(MCP_CANCTRL, 0xE0, mode);
/* Wait for mode change confirmation */
for (int i = 0; i < 50; i++) {
uint8_t stat = mcp_read_reg(MCP_CANSTAT);
if ((stat & 0xE0) == mode) return;
vTaskDelay(pdMS_TO_TICKS(1));
}
ESP_LOGW(TAG, "Mode change to 0x%02X timeout", mode);
}
/* Read a complete frame from RX buffer (0 or 1) */
static void mcp_read_rx_buffer(int buf, can_frame_t *frame)
{
/* Use READ_RX instruction for auto-clear of interrupt flag */
uint8_t cmd = (buf == 0) ? MCP_READ_RX0 : MCP_READ_RX1;
/* Read: cmd + SIDH + SIDL + EID8 + EID0 + DLC + 8 data = 14 bytes */
uint8_t tx[14] = { 0 };
uint8_t rx[14] = { 0 };
tx[0] = cmd;
spi_transaction_t t = {
.length = 14 * 8,
.tx_buffer = tx,
.rx_buffer = rx,
};
spi_device_transmit(s_spi, &t);
/* Parse — offsets relative to rx[1] (SIDH is byte 1) */
uint8_t sidh = rx[1];
uint8_t sidl = rx[2];
uint8_t eid8 = rx[3];
uint8_t eid0 = rx[4];
uint8_t dlc = rx[5];
frame->extended = (sidl & 0x08) != 0;
frame->rtr = false;
if (frame->extended) {
frame->id = ((uint32_t)sidh << 21)
| ((uint32_t)(sidl & 0xE0) << 13)
| ((uint32_t)(sidl & 0x03) << 16)
| ((uint32_t)eid8 << 8)
| (uint32_t)eid0;
frame->rtr = (dlc & 0x40) != 0;
} else {
frame->id = ((uint32_t)sidh << 3) | ((uint32_t)(sidl >> 5) & 0x07);
frame->rtr = (sidl & 0x10) != 0;
}
frame->dlc = dlc & 0x0F;
if (frame->dlc > 8) frame->dlc = 8;
memcpy(frame->data, &rx[6], 8);
frame->timestamp_us = 0; /* Caller sets timestamp */
}
/* Write a frame to TX buffer 0 and request send */
static bool mcp_write_tx_buffer(const can_frame_t *frame)
{
/* Check if TX buffer 0 is free */
uint8_t ctrl = mcp_read_reg(MCP_TXB0CTRL);
if (ctrl & 0x08) {
/* TXREQ still set — previous TX pending */
return false;
}
/* Build TX buffer content: SIDH + SIDL + EID8 + EID0 + DLC + data */
uint8_t tx[14] = { 0 };
tx[0] = MCP_LOAD_TX0;
if (frame->extended) {
tx[1] = (uint8_t)(frame->id >> 21); /* SIDH */
tx[2] = (uint8_t)((frame->id >> 13) & 0xE0) /* SIDL high bits */
| 0x08 /* EXIDE = 1 */
| (uint8_t)((frame->id >> 16) & 0x03); /* SIDL low bits */
tx[3] = (uint8_t)(frame->id >> 8); /* EID8 */
tx[4] = (uint8_t)(frame->id); /* EID0 */
tx[5] = frame->dlc | (frame->rtr ? 0x40 : 0x00); /* DLC + RTR */
} else {
tx[1] = (uint8_t)(frame->id >> 3); /* SIDH */
tx[2] = (uint8_t)((frame->id & 0x07) << 5) /* SIDL */
| (frame->rtr ? 0x10 : 0x00);
tx[3] = 0;
tx[4] = 0;
tx[5] = frame->dlc;
}
memcpy(&tx[6], frame->data, 8);
spi_transaction_t t = {
.length = 14 * 8,
.tx_buffer = tx,
};
spi_device_transmit(s_spi, &t);
/* Request to send */
uint8_t rts = MCP_RTS_TX0;
spi_transaction_t rts_t = {
.length = 8,
.tx_buffer = &rts,
};
spi_device_transmit(s_spi, &rts_t);
return true;
}
/* ============================================================
* GPIO ISR — INT pin (active low)
* ============================================================ */
static void IRAM_ATTR gpio_isr_handler(void *arg)
{
BaseType_t woken = pdFALSE;
xSemaphoreGiveFromISR(s_int_sem, &woken);
if (woken) portYIELD_FROM_ISR();
}
/* ============================================================
* RX Task
* ============================================================ */
static void rx_task(void *arg)
{
ESP_LOGI(TAG, "RX task started");
while (s_running) {
/* Wait for interrupt or timeout (poll every 100ms as fallback) */
if (xSemaphoreTake(s_int_sem, pdMS_TO_TICKS(100)) != pdTRUE) {
continue;
}
/* Read interrupt flags */
uint8_t intf = mcp_read_reg(MCP_CANINTF);
/* RX buffer 0 full */
if (intf & MCP_RX0IF) {
can_frame_t frame;
mcp_read_rx_buffer(0, &frame); /* READ_RX auto-clears RX0IF */
frame.timestamp_us = esp_timer_get_time();
s_rx_count++;
if (s_rx_cb) s_rx_cb(&frame, s_rx_ctx);
}
/* RX buffer 1 full */
if (intf & MCP_RX1IF) {
can_frame_t frame;
mcp_read_rx_buffer(1, &frame); /* READ_RX auto-clears RX1IF */
frame.timestamp_us = esp_timer_get_time();
s_rx_count++;
if (s_rx_cb) s_rx_cb(&frame, s_rx_ctx);
}
/* Error interrupt */
if (intf & MCP_ERRIF) {
uint8_t eflg = mcp_read_reg(MCP_EFLG);
s_bus_errors++;
if (eflg & MCP_EFLG_TXBO) {
s_bus_off = true;
ESP_LOGW(TAG, "Bus-off detected");
}
if (eflg & (MCP_EFLG_RXEP | MCP_EFLG_TXEP)) {
s_err_passive = true;
}
if (eflg & (MCP_EFLG_RX0OVR | MCP_EFLG_RX1OVR)) {
s_rx_overflow++;
}
/* Clear error flags */
mcp_modify_reg(MCP_EFLG, 0xFF, 0x00);
mcp_modify_reg(MCP_CANINTF, MCP_ERRIF, 0x00);
}
/* TX complete — clear flags */
if (intf & (MCP_TX0IF | MCP_TX1IF | MCP_TX2IF)) {
mcp_modify_reg(MCP_CANINTF, MCP_TX0IF | MCP_TX1IF | MCP_TX2IF, 0x00);
}
}
ESP_LOGI(TAG, "RX task stopped");
s_rx_task = NULL;
vTaskDelete(NULL);
}
/* ============================================================
* Public API — Lifecycle
* ============================================================ */
bool can_driver_init(int bitrate, uint8_t osc_mhz)
{
if (s_spi) {
ESP_LOGW(TAG, "Already initialized");
return false;
}
/* Select timing table */
const can_timing_t *table = NULL;
if (osc_mhz == 16) {
table = s_timing_16mhz;
} else if (osc_mhz == 8) {
table = s_timing_8mhz;
} else {
ESP_LOGE(TAG, "Unsupported oscillator: %u MHz", osc_mhz);
return false;
}
/* Find matching bitrate */
const can_timing_t *timing = NULL;
for (int i = 0; table[i].bitrate != 0; i++) {
if (table[i].bitrate == bitrate) {
timing = &table[i];
break;
}
}
if (!timing) {
ESP_LOGE(TAG, "Unsupported bitrate %d for %u MHz osc", bitrate, osc_mhz);
return false;
}
/* Init SPI bus */
spi_bus_config_t bus_cfg = {
.mosi_io_num = CONFIG_CANBUS_PIN_MOSI,
.miso_io_num = CONFIG_CANBUS_PIN_MISO,
.sclk_io_num = CONFIG_CANBUS_PIN_SCK,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
.max_transfer_sz = 32,
};
esp_err_t ret = spi_bus_initialize(CONFIG_CANBUS_SPI_HOST, &bus_cfg, SPI_DMA_DISABLED);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "SPI bus init failed: %s", esp_err_to_name(ret));
return false;
}
/* Add MCP2515 as SPI device */
spi_device_interface_config_t dev_cfg = {
.mode = 0, /* SPI mode 0 (CPOL=0, CPHA=0) */
.clock_speed_hz = CONFIG_CANBUS_SPI_CLOCK_HZ,
.spics_io_num = CONFIG_CANBUS_PIN_CS,
.queue_size = 4,
};
ret = spi_bus_add_device(CONFIG_CANBUS_SPI_HOST, &dev_cfg, &s_spi);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "SPI device add failed: %s", esp_err_to_name(ret));
spi_bus_free(CONFIG_CANBUS_SPI_HOST);
return false;
}
/* Reset MCP2515 (enters CONFIG mode automatically) */
mcp_reset();
/* Verify we can read CANSTAT — should be in CONFIG mode (0x80) */
uint8_t stat = mcp_read_reg(MCP_CANSTAT);
if ((stat & 0xE0) != MCP_MODE_CONFIG) {
ESP_LOGE(TAG, "MCP2515 not responding (CANSTAT=0x%02X)", stat);
spi_bus_remove_device(s_spi);
spi_bus_free(CONFIG_CANBUS_SPI_HOST);
s_spi = NULL;
return false;
}
ESP_LOGI(TAG, "MCP2515 detected (CANSTAT=0x%02X)", stat);
/* Set bit timing */
mcp_write_reg(MCP_CNF1, timing->cnf1);
mcp_write_reg(MCP_CNF2, timing->cnf2);
mcp_write_reg(MCP_CNF3, timing->cnf3);
/* Enable interrupts: RX0, RX1, Error */
mcp_write_reg(MCP_CANINTE, MCP_RX0IE | MCP_RX1IE | MCP_ERRIE);
/* Clear all interrupt flags */
mcp_write_reg(MCP_CANINTF, 0x00);
/* RXB0CTRL: rollover to RXB1 if RXB0 full, receive all valid messages */
mcp_write_reg(MCP_RXB0CTRL, 0x64); /* BUKT=1, RXM=11 (turn mask/filter off) */
mcp_write_reg(MCP_RXB1CTRL, 0x60); /* RXM=11 (turn mask/filter off) */
/* Create semaphores */
s_int_sem = xSemaphoreCreateBinary();
s_tx_mutex = xSemaphoreCreateMutex();
/* Reset counters */
s_rx_count = 0;
s_tx_count = 0;
s_bus_errors = 0;
s_rx_overflow = 0;
s_bus_off = false;
s_err_passive = false;
/* Configure INT pin as input with pull-up, falling edge interrupt */
gpio_config_t io_cfg = {
.pin_bit_mask = (1ULL << CONFIG_CANBUS_PIN_INT),
.mode = GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLUP_ENABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_NEGEDGE,
};
gpio_config(&io_cfg);
gpio_install_isr_service(0);
gpio_isr_handler_add(CONFIG_CANBUS_PIN_INT, gpio_isr_handler, NULL);
ESP_LOGI(TAG, "Initialized: %d bps, %u MHz osc, SPI@%d Hz",
bitrate, osc_mhz, CONFIG_CANBUS_SPI_CLOCK_HZ);
return true;
}
bool can_driver_start(can_mode_t mode)
{
if (!s_spi) {
ESP_LOGE(TAG, "Not initialized");
return false;
}
if (s_running) {
ESP_LOGW(TAG, "Already running");
return false;
}
/* Map mode enum to MCP2515 mode register value */
uint8_t mcp_mode;
const char *mode_str;
switch (mode) {
case CAN_MODE_LISTEN_ONLY:
mcp_mode = MCP_MODE_LISTEN;
mode_str = "listen-only";
break;
case CAN_MODE_LOOPBACK:
mcp_mode = MCP_MODE_LOOPBACK;
mode_str = "loopback";
break;
default:
mcp_mode = MCP_MODE_NORMAL;
mode_str = "normal";
break;
}
/* Set operational mode */
mcp_set_mode(mcp_mode);
/* Verify mode */
uint8_t stat = mcp_read_reg(MCP_CANSTAT);
if ((stat & 0xE0) != mcp_mode) {
ESP_LOGE(TAG, "Failed to enter %s mode (CANSTAT=0x%02X)", mode_str, stat);
return false;
}
s_running = true;
/* Start RX task on Core 1, priority 5 (above normal) */
BaseType_t ret = xTaskCreatePinnedToCore(
rx_task, "can_rx", 4096, NULL, 5, &s_rx_task, 1
);
if (ret != pdPASS) {
ESP_LOGE(TAG, "Failed to create RX task");
s_running = false;
mcp_set_mode(MCP_MODE_CONFIG);
return false;
}
ESP_LOGI(TAG, "Started in %s mode", mode_str);
return true;
}
void can_driver_stop(void)
{
if (!s_running) return;
s_running = false;
/* Give semaphore to wake RX task so it exits */
if (s_int_sem) xSemaphoreGive(s_int_sem);
/* Wait for RX task to die */
for (int i = 0; i < 20 && s_rx_task != NULL; i++) {
vTaskDelay(pdMS_TO_TICKS(50));
}
/* Put MCP2515 back to CONFIG mode */
if (s_spi) {
mcp_set_mode(MCP_MODE_CONFIG);
}
ESP_LOGI(TAG, "Stopped");
}
void can_driver_deinit(void)
{
can_driver_stop();
/* Remove ISR */
gpio_isr_handler_remove(CONFIG_CANBUS_PIN_INT);
/* Free SPI */
if (s_spi) {
spi_bus_remove_device(s_spi);
spi_bus_free(CONFIG_CANBUS_SPI_HOST);
s_spi = NULL;
}
/* Free semaphores */
if (s_int_sem) { vSemaphoreDelete(s_int_sem); s_int_sem = NULL; }
if (s_tx_mutex) { vSemaphoreDelete(s_tx_mutex); s_tx_mutex = NULL; }
ESP_LOGI(TAG, "Deinitialized");
}
bool can_driver_is_running(void)
{
return s_running;
}
/* ============================================================
* Public API — TX / RX
* ============================================================ */
bool can_driver_send(const can_frame_t *frame)
{
if (!s_running || !s_spi) return false;
xSemaphoreTake(s_tx_mutex, portMAX_DELAY);
/* Try to load into TX buffer, with retries for busy buffer */
bool ok = false;
for (int i = 0; i < 10; i++) {
if (mcp_write_tx_buffer(frame)) {
ok = true;
break;
}
vTaskDelay(pdMS_TO_TICKS(1));
}
if (ok) {
/* Wait for TX complete (TX0IF) or timeout */
for (int i = 0; i < 100; i++) {
uint8_t intf = mcp_read_reg(MCP_CANINTF);
if (intf & MCP_TX0IF) {
mcp_modify_reg(MCP_CANINTF, MCP_TX0IF, 0x00);
s_tx_count++;
break;
}
vTaskDelay(pdMS_TO_TICKS(1));
}
}
xSemaphoreGive(s_tx_mutex);
return ok;
}
void can_driver_set_rx_callback(can_rx_callback_t cb, void *ctx)
{
s_rx_cb = cb;
s_rx_ctx = ctx;
}
void can_driver_get_rx_callback(can_rx_callback_t *cb, void **ctx)
{
if (cb) *cb = s_rx_cb;
if (ctx) *ctx = s_rx_ctx;
}
/* ============================================================
* Public API — Hardware Filters
* ============================================================ */
/* Filter register base addresses (SIDH of each filter) */
static const uint8_t s_filter_addrs[6] = {
MCP_RXF0SIDH, MCP_RXF1SIDH, MCP_RXF2SIDH,
MCP_RXF3SIDH, MCP_RXF4SIDH, MCP_RXF5SIDH,
};
static const uint8_t s_mask_addrs[2] = {
MCP_RXM0SIDH, MCP_RXM1SIDH,
};
/* Write ID to filter/mask register set (4 bytes: SIDH, SIDL, EID8, EID0) */
static void write_id_regs(uint8_t base_addr, uint32_t id, bool extended)
{
uint8_t sidh, sidl, eid8, eid0;
if (extended) {
sidh = (uint8_t)(id >> 21);
sidl = (uint8_t)((id >> 13) & 0xE0) | 0x08 | (uint8_t)((id >> 16) & 0x03);
eid8 = (uint8_t)(id >> 8);
eid0 = (uint8_t)(id);
} else {
sidh = (uint8_t)(id >> 3);
sidl = (uint8_t)((id & 0x07) << 5);
eid8 = 0;
eid0 = 0;
}
mcp_write_reg(base_addr, sidh);
mcp_write_reg(base_addr + 1, sidl);
mcp_write_reg(base_addr + 2, eid8);
mcp_write_reg(base_addr + 3, eid0);
}
bool can_driver_set_filter(int idx, uint32_t id, bool extended)
{
if (!s_spi || idx < 0 || idx > 5) return false;
/* Filters can only be set in CONFIG mode */
bool was_running = s_running;
if (was_running) can_driver_stop();
mcp_set_mode(MCP_MODE_CONFIG);
write_id_regs(s_filter_addrs[idx], id, extended);
/* Enable filtering on the relevant RX buffer */
if (idx < 2) {
mcp_write_reg(MCP_RXB0CTRL, 0x04); /* BUKT=1, RXM=00 (use filter) */
} else {
mcp_write_reg(MCP_RXB1CTRL, 0x00); /* RXM=00 (use filter) */
}
if (was_running) can_driver_start(CAN_MODE_NORMAL);
return true;
}
bool can_driver_set_mask(int idx, uint32_t mask, bool extended)
{
if (!s_spi || idx < 0 || idx > 1) return false;
bool was_running = s_running;
if (was_running) can_driver_stop();
mcp_set_mode(MCP_MODE_CONFIG);
write_id_regs(s_mask_addrs[idx], mask, extended);
if (was_running) can_driver_start(CAN_MODE_NORMAL);
return true;
}
void can_driver_clear_filters(void)
{
if (!s_spi) return;
bool was_running = s_running;
if (was_running) can_driver_stop();
mcp_set_mode(MCP_MODE_CONFIG);
/* Set masks to 0 (match anything) */
for (int i = 0; i < 2; i++) {
write_id_regs(s_mask_addrs[i], 0, false);
}
/* RXM=11 → turn off mask/filter, receive all */
mcp_write_reg(MCP_RXB0CTRL, 0x64);
mcp_write_reg(MCP_RXB1CTRL, 0x60);
if (was_running) can_driver_start(CAN_MODE_NORMAL);
}
/* ============================================================
* Public API — Status
* ============================================================ */
void can_driver_get_status(can_status_t *out)
{
memset(out, 0, sizeof(*out));
out->rx_count = s_rx_count;
out->tx_count = s_tx_count;
out->bus_errors = s_bus_errors;
out->rx_overflow = s_rx_overflow;
out->bus_off = s_bus_off;
if (s_spi) {
out->tx_errors = mcp_read_reg(MCP_TEC);
out->rx_errors = mcp_read_reg(MCP_REC);
out->error_passive = (out->tx_errors > 127) || (out->rx_errors > 127);
}
if (!s_spi) out->state = "not_initialized";
else if (!s_running) out->state = "stopped";
else if (out->bus_off) out->state = "bus_off";
else if (out->error_passive) out->state = "error_passive";
else out->state = "running";
}
/* ============================================================
* Public API — Replay
* ============================================================ */
bool can_driver_replay(const can_frame_t *frames, int count, int speed_pct)
{
if (!s_running || !frames || count <= 0) return false;
ESP_LOGI(TAG, "Replaying %d frames at %d%% speed", count, speed_pct);
int64_t base_ts = frames[0].timestamp_us;
for (int i = 0; i < count && s_running; i++) {
/* Wait for inter-frame delay */
if (i > 0 && speed_pct > 0) {
int64_t delta_us = frames[i].timestamp_us - frames[i - 1].timestamp_us;
if (delta_us > 0) {
int64_t wait_us = (delta_us * 100) / speed_pct;
if (wait_us > 1000) {
vTaskDelay(pdMS_TO_TICKS(wait_us / 1000));
}
}
}
can_frame_t tx = frames[i];
if (!can_driver_send(&tx)) {
ESP_LOGW(TAG, "Replay: send failed at frame %d", i);
}
}
ESP_LOGI(TAG, "Replay complete (%d frames)", count);
return true;
}
#endif /* CONFIG_MODULE_CANBUS */