下面给出一个基于 ESP-IDF 的 ESP32-C3 TWAI(CAN)总线收发示例。主要包含:
- 初始化并安装 TWAI 驱动
- 配置波特率、过滤器
- 启动驱动
- 周期性发送帧
- 阻塞方式接收帧并打印
注意:请根据你板子的实际接线,把
TWAI_TX_GPIO_NUM
和TWAI_RX_GPIO_NUM
改成对应的 GPIO 引脚。
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "driver/twai.h"
static const char *TAG = "TWAI_DEMO";
// 用户根据硬件接线修改
#define TWAI_TX_GPIO_NUM 21 // CAN TX
#define TWAI_RX_GPIO_NUM 22 // CAN RX
void app_main(void)
{
// 1. 配置 TWAI 通用参数(模式、GPIO、队列深度等)
twai_general_config_t g_config = {
.mode = TWAI_MODE_NORMAL, // 常规模式,也可选监听模式 TWAI_MODE_LISTEN_ONLY
.tx_io = TWAI_TX_GPIO_NUM,
.rx_io = TWAI_RX_GPIO_NUM,
.clkout_io = TWAI_IO_UNUSED, // 不输出时钟
.bus_off_io = TWAI_IO_UNUSED, // 不监控 bus-off
.tx_queue_len = 5, // 发送队列长度
.rx_queue_len = 5, // 接收队列长度
.alerts_enabled = TWAI_ALERT_ALL, // 使能所有中断
.clkout_divider = 0,
};
// 2. 配置位定时(这里只示例 500 kbit/s)
// 你也可以使用 TWAI_TIMING_CONFIG_250KBITS() 等宏
twai_timing_config_t t_config = TWAI_TIMING_CONFIG_500KBITS();
// 3. 配置过滤器(这里全部接收)
twai_filter_config_t f_config = TWAI_FILTER_CONFIG_ACCEPT_ALL();
// 安装驱动
esp_err_t err = twai_driver_install(&g_config, &t_config, &f_config);
if (err != ESP_OK) {
ESP_LOGE(TAG, "TWAI 驱动安装失败: %s", esp_err_to_name(err));
return;
}
ESP_LOGI(TAG, "TWAI 驱动安装成功");
// 启动 TWAI
err = twai_start();
if (err != ESP_OK) {
ESP_LOGE(TAG, "TWAI 启动失败: %s", esp_err_to_name(err));
twai_driver_uninstall();
return;
}
ESP_LOGI(TAG, "TWAI 启动成功");
// 创建一个发送与接收的任务
xTaskCreatePinnedToCore([](void*){
uint32_t msg_id = 0x123;
uint8_t data[] = {0xDE, 0xAD, 0xBE, 0xEF};
twai_message_t tx_frame = {
.identifier = msg_id,
.data_length_code = sizeof(data),
.data = {0},
.flags = TWAI_MSG_FLAG_NONE
};
memcpy(tx_frame.data, data, sizeof(data));
while (1) {
// 发送
esp_err_t r = twai_transmit(&tx_frame, pdMS_TO_TICKS(1000));
if (r == ESP_OK) {
ESP_LOGI(TAG, "已发送 CAN 帧 ID=0x%03X DLC=%d", tx_frame.identifier, tx_frame.data_length_code);
} else {
ESP_LOGW(TAG, "发送 CAN 帧超时或失败: %s", esp_err_to_name(r));
}
vTaskDelay(pdMS_TO_TICKS(2000));
}
}, "can_tx_task", 4*1024, NULL, 5, NULL, tskNO_AFFINITY);
xTaskCreatePinnedToCore([](void*){
twai_message_t rx_frame;
while (1) {
// 接收
esp_err_t r = twai_receive(&rx_frame, pdMS_TO_TICKS(1000));
if (r == ESP_OK) {
ESP_LOGI(TAG, "收到 CAN 帧 ID=0x%03X DLC=%d Data=%02X %02X %02X %02X ...",
rx_frame.identifier,
rx_frame.data_length_code,
rx_frame.data[0], rx_frame.data[1],
rx_frame.data[2], rx_frame.data[3]
);
} else if (r == ESP_ERR_TIMEOUT) {
// 超时,无新帧
} else {
ESP_LOGW(TAG, "接收 CAN 帧出错: %s", esp_err_to_name(r));
}
}
}, "can_rx_task", 4*1024, NULL, 5, NULL, tskNO_AFFINITY);
}
关键说明
-
TWAI_MODE_NORMAL:既能收也能发。调试时也可用 TWAI_MODE_LISTEN_ONLY 仅监听总线流量。
-
位定时配置:常用宏有
TWAI_TIMING_CONFIG_1MBITS()
TWAI_TIMING_CONFIG_500KBITS()
TWAI_TIMING_CONFIG_250KBITS()
-
过滤器:示例中接收所有 ID;也可按需求只接收某些 ID(节省 CPU)。
-
twai_transmit()
和twai_receive()
都是阻塞接口,可通过超时返回。当不想阻塞,可使用零超时时间或配合事件驱动。
将以上代码放入你的 ESP-IDF 工程的 main/app_main.c
中,配置好 sdkconfig
(打开 TWAI 驱动),编译下载即可。希望能帮你快速上手 ESP-C3 的 CAN 总线开发!