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.
816 lines
24 KiB
C
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 */
|