目录

前言

硬件准备

ESP32s3的CAN外设资源介绍

总体介绍

常用API

初始化

twai_driver_install

作用:

函数原型

参数:

返回值: 

快速使用:

 twai_start

作用:

函数原型:

参数:

返回值:

发送

twai_transmit

作用:

函数原型:

参数:

返回值:

接收 

twai_receive

作用:

函数原型:

参数:

返回值:

去初始化

twai_stop

作用:

函数原型:

参数:

返回值:

twai_driver_uninstall

作用:

函数原型:

参数:

返回值:

代码配置

主机代码

从机代码

接线示意图

最终效果

代码地址:


前言

在嵌入式系统开发中,控制器局域网(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_installtwai_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));
    }
}

接线示意图

最终效果

左:主机     右:从机

代码地址:

https://gitee.com/jian-xingjie/esp32-can.git

Logo

纵情码海钱塘涌,杭州开发者创新动! 属于杭州的开发者社区!致力于为杭州地区的开发者提供学习、合作和成长的机会;同时也为企业交流招聘提供舞台!

更多推荐