ESP32实现CAN通信
在嵌入式系统开发中,控制器局域网(CAN)总线因其高可靠性、实时性和抗干扰能力,被广泛应用于汽车电子、工业控制以及物联网设备间的通信。ESP32作为一款功能强大的低成本微控制器,内置了CAN控制器外设(ESP32中的CAN外设叫做TWAI,Two-Wire Automotive Interface双线汽车接口),使其成为实现CAN通信的理想选择。本文将详细介绍ESP32的CAN(TWAI)通信实现
目录
前言
在嵌入式系统开发中,控制器局域网(CAN)总线因其高可靠性、实时性和抗干扰能力,被广泛应用于汽车电子、工业控制以及物联网设备间的通信。ESP32作为一款功能强大的低成本微控制器,内置了CAN控制器外设(ESP32中的CAN外设叫做TWAI,Two-Wire Automotive Interface双线汽车接口),使其成为实现CAN通信的理想选择。
本文将详细介绍ESP32的CAN(TWAI)通信实现方法,涵盖硬件连接、ESP32s3的CAN外设资源介绍、代码配置、数据收发等关键步骤。通过示例代码和实际应用场景分析,帮助开发者快速掌握ESP32的CAN通信技术,并应用于实际项目中。本文中,其中一个ESP32作为主机往CAN总线发送消息,另一个ESP32作为从机接收消息。
硬件准备
如图,需要两块ESP32s3、2个TJA1050CAN收发器、杜邦线若干、USB-C线*2。

ESP32s3的CAN外设资源介绍
总体介绍
ESP32-S3搭载了TWAI控制器(兼容CAN 2.0A/B协议),支持标准帧(11位标识符)和扩展帧(29位标识符),波特率最高可达1 Mbps。主要特性包括:
- 双通道模式:支持独立双CAN控制器(需外部收发器,如本文使用的TJA1050)。
- 灵活过滤:可配置验收滤波器(ACF)以减少CPU负载。
- 中断支持:包含发送/接收成功、错误状态等中断事件。
- DMA支持:降低CPU处理负担。
硬件资源依赖:
- 引脚映射:可自由配置GPIO管脚(需手动设置)。
- 时钟源:依赖APB时钟(通常80 MHz)。
常用API
初始化
twai_driver_install
作用:
用于初始化TWAI(Two-Wire Automotive Interface,原CAN)驱动的函数。该函数配置硬件参数并安装驱动,为后续的TWAI通信提供基础支持。
函数原型
esp_err_t twai_driver_install(const twai_general_config_t *g_config, const twai_timing_config_t *t_config, const twai_filter_config_t *f_config);
参数:
g_config:指向通用配置结构体(如工作模式、GPIO引脚等)。
typedef struct {
twai_mode_t mode; // 工作模式(正常/监听/回环)
gpio_num_t tx_io_num; // TX引脚
gpio_num_t rx_io_num; // RX引脚
twai_clk_src_t clk_src; // 时钟源(可选APB或外部)
uint32_t alerts_enabled; // 启用的警报标志位
twai_intr_flags_t intr_flags; // 中断配置
} twai_general_config_t;
t_config:指向时序配置结构体(如波特率、时钟分频等)。
typedef struct {
uint32_t brp; // 波特率预分频
uint8_t tseg_1; // 时间段1长度
uint8_t tseg_2; // 时间段2长度
uint8_t sjw; // 同步跳转宽度
bool triple_sampling; // 是否启用三重采样
} twai_timing_config_t;
f_config:指向过滤器配置结构体(如接收过滤规则)。
typedef struct {
uint32_t acceptance_code; // 验收码
uint32_t acceptance_mask; // 验收掩码
bool single_filter; // 是否启用单过滤器模式
} twai_filter_config_t;
返回值:
ESP_OK表示成功,其他错误码(如ESP_ERR_INVALID_ARG)表示配置无效或硬件冲突。
快速使用:
可以使用宏TWAI_GENERAL_CONFIG_DEFAULT、TWAI_TIMING_CONFIG_1MBITS、TWAI_FILTER_CONFIG_ACCEPT_ALL等快速初始化默认配置。
twai_general_config_t g_config = TWAI_GENERAL_CONFIG_DEFAULT(TX_PIN, RX_PIN, TWAI_MODE_NORMAL);
twai_timing_config_t t_config = TWAI_TIMING_CONFIG_500KBITS();
twai_filter_config_t f_config = TWAI_FILTER_CONFIG_ACCEPT_ALL();
esp_err_t ret = twai_driver_install(&g_config, &t_config, &f_config);
twai_start
作用:
启动 TWAI 控制器。使用时需要先确保已正确配置 TWAI 控制器参数并用twai_driver_install安装驱动。
函数原型:
esp_err_t twai_start(void);
参数:
无。
返回值:
ESP_OK:启动成功ESP_ERR_INVALID_STATE:TWAI 驱动未安装或已处于运行状态ESP_FAIL:其他错误导致启动失败
发送
twai_transmit
作用:
用于发送CAN数据帧。必须先调用twai_driver_install和twai_start,否则发送会失败。
函数原型:
esp_err_t twai_transmit(const twai_message_t *message, TickType_t ticks_to_wait);
参数:
- message:指向
twai_message_t结构体的指针,包含CAN帧信息(标识符、数据长度码、数据等)。 - ticks_to_wait:发送超时时间(以FreeRTOS tick为单位),设为
portMAX_DELAY可无限等待。
返回值:
- ESP_OK: 消息成功放入发送队列。
- ESP_ERR_INVALID_ARG: 参数无效(如
message为NULL或数据长度超过8)。 - ESP_ERR_TIMEOUT: 发送队列已满且在超时时间内无法放入消息。
- ESP_FAIL: 其他错误(如TWAI驱动未安装或未启动)。
接收
twai_receive
作用:
从接收缓冲区读取CAN帧。
函数原型:
esp_err_t twai_receive(twai_message_t *message, TickType_t ticks_to_wait);
参数:
- message:指向
twai_message_t结构的指针,用于存储接收到的CAN帧数据。 - ticks_to_wait:等待接收数据的超时时间(以FreeRTOS tick为单位)。若为
portMAX_DELAY,函数会无限阻塞直到接收到数据;若为0,则立即返回(非阻塞模式)。
返回值:
- ESP_OK:成功接收到CAN帧。
- ESP_ERR_TIMEOUT:超时未收到数据。
- ESP_ERR_INVALID_ARG:参数无效(如
message为NULL)。 - ESP_ERR_INVALID_STATE:TWAI驱动未安装或未运行。
去初始化
twai_stop
作用:
用于停止TWAI(Two-Wire Automotive Interface,即CAN总线)控制器运行。
函数原型:
esp_err_t twai_stop(void);
参数:
无。
返回值:
ESP_OK:停止成功。ESP_ERR_INVALID_STATE:TWAI驱动未安装或已处于停止状态。
twai_driver_uninstall
作用:
用于完全卸载TWAI驱动程序,释放相关资源(如中断、内存等)。调用后需重新安装驱动(twai_driver_install)才能再次使用TWAI。
函数原型:
esp_err_t twai_driver_uninstall(void);
参数:
无。
返回值:
ESP_OK:卸载成功。ESP_ERR_INVALID_STATE:TWAI驱动未安装或仍在运行(需先调用twai_stop)。
代码配置
-
主机主动发出 10 条扩展数据帧,ID 递增;
-
从机永远只收 扩展数据帧,把收到的每个字节用 255-x 取反后,再立刻回发一条回复帧。
两台机器都跑 1 Mbit/s,引脚 TX=9、RX=10,滤波器全开。 -
仅实现了扩展数据帧收发,其他形式请读者自行探索。
主机代码
#include <stdio.h>
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_err.h"
#include "esp_log.h"
#include "driver/twai.h"
#define EXAMPLE_TAG "TWAI master"
#define SENDMSG 0
#define RECEIVEMSG 1
#define RX_GPIO_PIN GPIO_NUM_10
#define TX_GPIO_PIN GPIO_NUM_9
/* --------------------------- Tasks and Functions -------------------------- */
void printf_msg(int flag, twai_message_t *msg) // flag:0-send 1-receive
{
int j;
if (flag)
printf("Receive: ");
else
printf("Send : ");
if (msg->extd)
printf("Extended ");
else
printf("Standard ");
if (msg->rtr)
printf("Remote Frame, ");
else
printf("Data Frame, ");
printf("ID: 0x%lx ", msg->identifier);
if (msg->rtr == 0)
{
for (j = 0; j < msg->data_length_code; j++)
{
printf("D%d: %d\t", j, msg->data[j]);
}
printf("\n");
}
else
printf("\n");
}
static void twai_transmit_task(void *arg)
{
int i;
// 只发送扩展数据帧
twai_message_t s1;
s1.extd = 1; // 扩展帧
s1.rtr = 0; // 数据帧
s1.ss = 1; // 单次发送
s1.self = 0; // 不接收自己发送的消息
s1.dlc_non_comp = 0; // 标准DLC
s1.identifier = 0x6666666; // 29位扩展ID
s1.data_length_code = 8; // 数据长度
vTaskDelay(pdMS_TO_TICKS(1000));
// 发送10个扩展数据帧
for (i = 0; i < 10; i++) {
// 填充数据
for (int j = 0; j < 8; j++) {
s1.data[j] = i * 10 + j;
}
esp_err_t result = twai_transmit(&s1, portMAX_DELAY);
if (result == ESP_OK) {
printf_msg(SENDMSG, &s1);
s1.identifier++;
} else {
printf("Failed to send message: %s\n", esp_err_to_name(result));
}
vTaskDelay(pdMS_TO_TICKS(1000));
}
vTaskDelete(NULL);
}
static void twai_receive_task(void *arg)
{
twai_message_t r1;
// 接收5个消息
for (int i = 0; i < 5; i++) {
esp_err_t result = twai_receive(&r1, portMAX_DELAY);
if (result == ESP_OK) {
printf_msg(RECEIVEMSG, &r1);
} else {
printf("Failed to receive message: %s\n", esp_err_to_name(result));
}
}
vTaskDelete(NULL);
}
void app_main(void)
{
// CAN接口基本配置 - 使用修改后的引脚
twai_general_config_t g_config = TWAI_GENERAL_CONFIG_DEFAULT(TX_GPIO_PIN, RX_GPIO_PIN, TWAI_MODE_NORMAL);
g_config.tx_queue_len = 10;
g_config.rx_queue_len = 10;
// CAN接口时序配置 - 1Mbps
twai_timing_config_t t_config = TWAI_TIMING_CONFIG_1MBITS();
// 过滤器配置 - 接收所有消息
twai_filter_config_t f_config = {
.acceptance_code = 0,
.acceptance_mask = 0xFFFFFFFF,
.single_filter = true};
// 安装和启动TWAI驱动
esp_err_t result = twai_driver_install(&g_config, &t_config, &f_config);
if (result != ESP_OK) {
ESP_LOGE(EXAMPLE_TAG, "Failed to install driver: %s", esp_err_to_name(result));
return;
}
ESP_LOGI(EXAMPLE_TAG, "Driver installed");
result = twai_start();
if (result != ESP_OK) {
ESP_LOGE(EXAMPLE_TAG, "Failed to start driver: %s", esp_err_to_name(result));
return;
}
ESP_LOGI(EXAMPLE_TAG, "Driver started");
// 创建发送和接收任务
xTaskCreatePinnedToCore(twai_receive_task, "TWAI_rx", 4096, NULL, 8, NULL, tskNO_AFFINITY);
xTaskCreatePinnedToCore(twai_transmit_task, "TWAI_tx", 4096, NULL, 9, NULL, tskNO_AFFINITY);
// 运行15秒
vTaskDelay(pdMS_TO_TICKS(15000));
// 停止和卸载驱动
twai_status_info_t status_info;
twai_get_status_info(&status_info);
while (status_info.msgs_to_tx != 0)
{
ESP_ERROR_CHECK(twai_get_status_info(&status_info));
vTaskDelay(pdMS_TO_TICKS(100));
}
ESP_ERROR_CHECK(twai_stop());
ESP_LOGI(EXAMPLE_TAG, "Driver stopped");
ESP_ERROR_CHECK(twai_driver_uninstall());
ESP_LOGI(EXAMPLE_TAG, "Driver uninstalled");
}
从机代码
#include <stdio.h>
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_err.h"
#include "esp_log.h"
#include "driver/twai.h"
#define EXAMPLE_TAG "TWAI slave"
#define SENDMSG 0
#define RECEIVEMSG 1
#define RX_GPIO_PIN GPIO_NUM_10
#define TX_GPIO_PIN GPIO_NUM_9
/* --------------------------- Tasks and Functions -------------------------- */
void printf_msg(int flag, twai_message_t *msg) // flag:0-send 1-receive
{
int j;
if (flag)
printf("Receive: ");
else
printf("Send : ");
if (msg->extd)
printf("Extended ");
else
printf("Standard ");
if (msg->rtr)
printf("Remote Frame, ");
else
printf("Data Frame, ");
printf("ID: 0x%lx ", msg->identifier);
if (msg->rtr == 0)
{
for (j = 0; j < msg->data_length_code; j++)
{
printf("D%d: %d\t", j, msg->data[j]);
}
printf("\n");
}
else
printf("\n");
}
static void twai_receive_task(void *arg)
{
int j;
// 发送响应消息的模板
twai_message_t s1;
s1.extd = 1; // 扩展帧
s1.rtr = 0; // 数据帧
s1.ss = 1; // 单次发送
s1.self = 0; // 不接收自己发送的消息
s1.dlc_non_comp = 0; // 标准DLC
s1.identifier = 0x7654321; // 回复用的扩展ID
s1.data_length_code = 8; // 数据长度
// 接收消息
twai_message_t r1;
for (int i = 0; i < 10; i++) // 接收消息
{
esp_err_t result = twai_receive(&r1, portMAX_DELAY);
if (result != ESP_OK) {
printf("Failed to receive message: %s\n", esp_err_to_name(result));
continue;
}
printf_msg(RECEIVEMSG, &r1);
// 检查是否为扩展帧
if (r1.extd == 1 && r1.rtr == 0) {
// 处理接收到的数据,255减去原始数据回传
for (j = 0; j < r1.data_length_code && j < 8; j++) {
s1.data[j] = 255 - r1.data[j];
}
// 保持其他数据字段为0
for (j = r1.data_length_code; j < 8; j++) {
s1.data[j] = 0;
}
// 发送回复
result = twai_transmit(&s1, portMAX_DELAY);
if (result == ESP_OK) {
printf_msg(SENDMSG, &s1);
} else {
printf("Failed to send response: %s\n", esp_err_to_name(result));
}
} else {
printf("Received non-extended frame or remote frame, ignoring\n");
}
}
vTaskDelete(NULL);
}
void app_main(void)
{
// CAN接口基本配置
twai_general_config_t g_config = TWAI_GENERAL_CONFIG_DEFAULT(TX_GPIO_PIN, RX_GPIO_PIN, TWAI_MODE_NORMAL);
g_config.tx_queue_len = 10;
g_config.rx_queue_len = 10;
// CAN接口时序配置 - 1Mbps
twai_timing_config_t t_config = TWAI_TIMING_CONFIG_1MBITS();
// 过滤器配置 - 接收所有扩展帧
twai_filter_config_t f_config = {
.acceptance_code = 0,
.acceptance_mask = 0xFFFFFFFF,
.single_filter = true};
// 安装和启动TWAI驱动
esp_err_t result = twai_driver_install(&g_config, &t_config, &f_config);
if (result != ESP_OK) {
ESP_LOGE(EXAMPLE_TAG, "Failed to install driver: %s", esp_err_to_name(result));
return;
}
ESP_LOGI(EXAMPLE_TAG, "Driver installed");
result = twai_start();
if (result != ESP_OK) {
ESP_LOGE(EXAMPLE_TAG, "Failed to start driver: %s", esp_err_to_name(result));
return;
}
ESP_LOGI(EXAMPLE_TAG, "Driver started");
// 创建接收任务
xTaskCreatePinnedToCore(twai_receive_task, "TWAI_rx", 4096, NULL, 8, NULL, tskNO_AFFINITY);
// 保持运行
while(1) {
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
接线示意图

最终效果
左:主机 右:从机

代码地址:
更多推荐


所有评论(0)