跳到主要内容

第 06 节 测试 CAN 接口

TWAI 是 ESP32 官方对 CAN Bus(控制器局域网) 的实现, 它是一种高可靠性、多主控、实时串行异步通信协议,专为汽车和工业应用而设计。它本质上就是 CAN 总线,功能、用法、协议都和标准的 CAN 一样,兼容 ISO 11898-1 标准中定义的帧结构,并支持 11 位标识符的标准帧和 29 位标识符的扩展帧。

在本产品 ESP32-S3-Touch-LCD-7 当中,我们引出了一个 PH2.0 2PIN 的座子,可供客户连接到各种 CAN 的设备进行通信和控制。

ESP32-S3-Touch-LCD-7 CAN demo 1

对于这个 CAN 接口的测试,我们提供了简单的与 PC 进行通信的发送和接收的例程,方便客户快速调试和测试该接口。

硬件设计

在 ESP32 包含 1 个 TWAI 控制器外设,允许创建 1 个驱动程序实例,需要使用外部收发器才能连接到 TWAI 总线。在本产品 ESP32-S3-Touch-LCD-7 当中,我们使用 IO19 和 IO20 分别作为 RX 和 TX 引脚,连接到板载的 TJA1051T/3/1 收发器,转换出 CAN 总线(CANH/CANL)通信接口。下面是一个简单的电路图说明:

ESP32-S3-Touch-LCD-7 CAN demo 17

信息
  • 一个 CAN 节点一般包含 3 个部分:微控制器,CAN 控制器和 CAN 收发器。有的微控制器内部已经包含了 CAN 控制器,例如 ESP32,内置一个 CAN(也叫做 TWAI) 控制器,只需要外加 CAN 收发器即可进行 CAN 通信,收发器一般都是 8 个引脚的芯片,例如我们 ESP32-S3-Touch-LCD-7 上面用到的 TJA1051T/3/1。
ESP32-S3-Touch-LCD-7 CAN demo 22
  • 所有节点通过两条线连接起来,分别称为 CAN_H 和 CAN_L。并且网络的两端必须有 120Ω的终端电阻,因为电缆的特性阻抗为 120Ω,为了模拟无限远的传输线,所以在设计线路板的时候都要有一个 120 欧的电阻,通过跳线或者拨码开关选择是否使用这个电阻。

元件清单

本次例程需要用到的元件有:

  • ESP32-S3-Touch-LCD-7 x1
  • USB-CAN-A 模块 x1
  • USB 线 Type A 公口 转 Type C 公口 x1
  • HY2.0 2PIN 转杜邦公头线 x1
  • 扁头螺丝刀 x1
ESP32-S3-Touch-LCD-7 CAN demo 18

硬件连接

  1. 先将配套的 2PIN 排线公头连接器接到 RS485 的白色接口上,确保线材与接口接触良好

  2. 将 ESP32-S3-Touch-LCD-7 的 CAN H 端接入到 USB-CAN-A 模块的 CAN_H 端,同样,L 端接到 CAN_L 端。使用扁头螺丝刀调整 USB-CAN-A 模块上接口的螺丝,将黑红两根线的排针卡住,确保连接牢固稳定。

    ESP32-S3-Touch-LCD-7 CAN demo 2
  3. 再将 USB-CAN-A 的 USB 口插到电脑的 USB 口

  4. 用 Type-A 公口 转 Type-C 的 USB 线将 ESP32 的 USB 也接到电脑的 USB,用于下载程序。

    完成全部连接如下图:

    ESP32-S3-Touch-LCD-7 CAN demo 3

CAN 信息发送例程

示例代码

请先从以下地址下载例程包:ESP32-S3-Touch-LCD-7 示例程序 (如果先前已经下载,直接打开对应文件夹即可) 将例程压缩包解压之后,打开对应的 \Arduino\examples\06_TWAItransmit 文件夹。

ESP32-S3-Touch-LCD-7 CAN demo 4

双击打开 06_TWAItransmit.ino

ESP32-S3-Touch-LCD-7 CAN demo 5

前面我们已经连接好了下载程序的 USB 口,在打开程序后,右上角选择好串口和开发板型号 Waveshare ESP32-S3-Touch-LCD-7

ESP32-S3-Touch-LCD-7 CAN demo 6

提示
  • 由于此时电脑接上 ESP32-S3-Touch-LCD-7 和 USB-CAN-A 模块,因此会识别出来两个串口,此时可以打开电脑的设备管理器,看到 USB 串行设备的就是 USB 口对应的下载接口,另一个则是 CAN 转 USB 的串口。上面的板子型号选择当中要选择** USB 串行设备**对应的 COM 口:
ESP32-S3-Touch-LCD-7 CAN demo 7

注意此时在工具菜单栏里面需要将 USB CDC On Boot 设置成 Disabled,因为在程序运行之后 USB 口将不能再使用(跟 CAN 接口共用了 GPIO19 和 GPIO20,在 CAN 接口运行期间 USB 口会被禁用),我们程序的串口 debug 信息的输出将会通过 UART 口:

ESP32-S3-Touch-LCD-7 CAN demo 8

然后点击箭头 Upload 按钮进行程序上传,等待上传完成即可:

ESP32-S3-Touch-LCD-7 CAN demo 9

运行结果

下载完成之后,把线从 ESP32-S3-Touch-LCD-7 的 USB 口拔出,换到 UART 口接入电脑:

ESP32-S3-Touch-LCD-7 CAN demo 10

此时打开串口调试器 sscom,选择端口号为 USB-Enhanced-SERIAL CH343,打开端口就会看到串口已经有 debug 输出了,但此时由于还没开始打开 CAN 口通信,所以会打印总线错误的信息:

ESP32-S3-Touch-LCD-7 CAN demo 11

此时我们开始配置 USB-CAN-A_TOOL 的软件工具,按照步骤依次设置。

ESP32-S3-Touch-LCD-7 CAN demo 12

通信连接成功之后,即可看到 sscom 这边 ESP32 的 debug 打印:信息发送成功

ESP32-S3-Touch-LCD-7 CAN demo 13

而 USB CAN 工具这边也显示出来 ESP32-S3-Touch-LCD-7 这边发送过来的 CAN 信息:

ESP32-S3-Touch-LCD-7 CAN demo 14

帧 ID 以及数据等都与我们程序里面配置的符合。

代码回顾

我们提供的这个 CAN 信息的发送例程 TWAItransmit.ino,包含其会用到的 waveshare_twai_port.h 头文件和 waveshare_twai_port.cpp 文件。

1. 在 waveshare_twai_port.h 中分别定义了 TWAI(即 CAN 总线)驱动相关的引脚与参数和一些外设扩展引脚:

    #define RX_PIN 19
#define TX_PIN 20

定义 ESP32-S3 的 GPIO19 用于 TWAI(CAN)接收,GPIO20 用于 TWAI(CAN)发送。

    #define TP_RST 1
#define LCD_BL 2
#define LCD_RST 3
#define SD_CS 4
#define USB_SEL 5

这个部分跟之前 SD 卡的例程一样,定义了 CH422G 的 GPIO 1~5 分别控制 LCD、SD 卡等多个外设。

    #define EXAMPLE_I2C_ADDR    (ESP_IO_EXPANDER_I2C_CH422G_ADDRESS)
#define EXAMPLE_I2C_SDA_PIN 8 // I2C data line pins
#define EXAMPLE_I2C_SCL_PIN 9 // I2C clock line pin

定义 CH422G 扩展芯片的 I2C 地址和引脚。

    #define TRANSMIT_RATE_MS 1000
#define POLLING_RATE_MS 1000

这里是设置了一些跟时间相关的参数,分别定义了 TWAI 信息的发送间隔:1000ms 发送一次;轮询周期:每 1000ms 执行一次轮询任务(这个是用于设置后续的读取 Alerts 事件的等待时间)

    bool waveshare_twai_init();
void waveshare_twai_transmit();

这里进行了两个 TWAI 功能函数的声明,这两个函数会在 waveshare_twai_port.cpp 文件里面进行实现,下面我们会分别进行讲解。

2. waveshare_twai_port.cpp 文件中各个函数的实现

    unsigned long previousMillis = 0;

设置定时器变量 previousMillis,用于 millis() 函数来判断是否到了发送时间(这里的发送时间就是我们在头文件里面设定的发送间隔 TRANSMIT_RATE_MS 为 1000ms)

static void send_message() 函数:用于构造并发送一帧 TWAI 消息

    twai_message_t message;
message.identifier = 0x0F6; // Set message identifier
message.data_length_code = 8; // Set data length
for (int i = 0; i < message.data_length_code; i++) {
message.data[i] = i; // Populate message data
}

构建一个 message 结构体message.identifier 用于设置 TWAI ID 为 0x0F6,也相当于仲裁字段(11 位标准帧);data_length_code 用于设置数据长度为 8 个字节,接着再用 for 循环将数据填充从 0 到 7,等下我们运行这个发送程序时就会一帧发送这八个字节的数据。

    if (twai_transmit(&message, pdMS_TO_TICKS(1000)) == ESP_OK) {
printf("Message queued for transmission\n"); // Print success message
} else {
printf("Failed to queue message for transmission\n"); // Print failure message
}

尝试发送这一帧消息并打印相关的串口信息,如果发送成功,则会打印 "Message queued for transmission",如果队列满、驱动未启动、总线异常,就会失败,并打印 "Failed to queue message for transmission"pdMS_TO_TICKS(1000) 表示 等待最多 1000 ms,如果 TX 队列满了,它会等待 1 秒,仍然满则返回失败。

    memset(message.data, 0, sizeof(message.data));

用于将整个 data[8] 数组全部清空,防止下一次使用旧数据。

waveshare_twai_init() 函数:用于初始化 TWAI 通信

    twai_general_config_t g_config = TWAI_GENERAL_CONFIG_DEFAULT((gpio_num_t)TX_PIN, (gpio_num_t)RX_PIN, TWAI_MODE_NORMAL);
twai_timing_config_t t_config = TWAI_TIMING_CONFIG_50KBITS(); // Set 50Kbps
twai_filter_config_t f_config = TWAI_FILTER_CONFIG_ACCEPT_ALL(); // Accept all messages

// Install TWAI driver
if (twai_driver_install(&g_config, &t_config, &f_config) != ESP_OK) {
Serial.println("Failed to install driver"); // Print error message
return false; // Return false if driver installation fails
}
Serial.println("Driver installed"); // Print success message
  • 配置 TWAI 的初始化首先要进行驱动的安装,用 twai_driver_install 函数分别配置三组结构体 g_configt_config,和 f_config
  • twai_driver_install() 的返回值为 ESP_OK 则表示驱动安装成功,串口打印 "Driver installed",如果失败则返回 false 并打印 "Failed to install driver"
信息
  • 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;

g_config = TWAI_GENERAL_CONFIG_DEFAULT((gpio_num_t)TX_PIN, (gpio_num_t)RX_PIN, TWAI_MODE_NORMAL); 这里我们将设置 TX 为 GOIP20,RX 为 GPIO19,工作模式为正常模式。

  • t_config:指向时序配置结构体(如波特率、时钟分频等)。 这里设置 t_config = TWAI_TIMING_CONFIG_50KBITS(), 表明设置设置速度为 50 kbps。常见的速度还有 125 kbps、 250 kbps、500 kbps。

  • f_config:指向过滤器配置结构体(如接收过滤规则) 这里设置 f_config = TWAI_FILTER_CONFIG_ACCEPT_ALL(); 表示接收所有帧。

  if (twai_start() != ESP_OK) {
Serial.println("Failed to start driver"); // Print error message
return false; // Return false if starting the driver fails
}
Serial.println("Driver started"); // Print success message

twai_start() 函数启动 TWAI 控制器。返回 ESP_OK 则表示启动成功,串口打印 "Driver started";启动失败则返回 false 并打印 "Failed to start driver"

    uint32_t alerts_to_enable = TWAI_ALERT_TX_IDLE | TWAI_ALERT_TX_SUCCESS | TWAI_ALERT_TX_FAILED | TWAI_ALERT_ERR_PASS | TWAI_ALERT_BUS_ERROR;
if (twai_reconfigure_alerts(alerts_to_enable, NULL) != ESP_OK) {
Serial.println("Failed to reconfigure alerts"); // Print error message
return false; // Return false if alert reconfiguration fails
}
Serial.println("CAN Alerts reconfigured"); // Print success message

这里将会开启监听以下 5 种事件:

  • TWAI_ALERT_TX_IDLE:TX 空闲
  • TWAI_ALERT_TX_SUCCESS:TX 成功
  • TWAI_ALERT_TX_FAILED:TX 失败
  • TWAI_ALERT_ERR_PASS:控制器进入 Error Passive
  • TWAI_ALERT_BUS_ERROR:发生总线错误

这些事件的宏都定义在了 driver /twai_types_legacy.h 头文件里面,这里我们用位运算将多个事件合并成一个 32 位整数 alerts_to_enable,即同时包含了多个事件,再用 twai_reconfigure_alerts() 开启这多个事件的监听,成功后输出 "CAN Alerts reconfigured"

至此完成所有初始化的操作并返回 ture 到主程序。

waveshare_twai_transmit() 函数:

    uint32_t alerts_triggered;
twai_read_alerts(&alerts_triggered, pdMS_TO_TICKS(POLLING_RATE_MS)); // Read triggered alerts
twai_status_info_t twaistatus; // Create status info structure
twai_get_status_info(&twaistatus); // Get status information

twai_read_alerts() 会读取发生的 TWAI 事件,并把触发的事件写进 alerts_triggeredpdMS_TO_TICKS(POLLING_RATE_MS) 是用于设置阻塞等待的时间,我们在 waveshare_twai_port.h 头文件当中已经将其设置成了 1000 ,表示最多等待 1000ms,如果 1000ms 内 TWAI 总线有事件,就返回事件。

twai_status_info_t twaistatus 新建一个用来保存 TWAI 总线状态信息的结构体,twai_get_status_info() 用于拿到当前的统计数据(错误次数、发送队列情况),里面包含很多统计字段,比如:

  • bus_error_count: 总线错误次数
  • tx_error_counter:发送错误计数器
  • msgs_to_tx:正在等待发送的消息数
  • tx_failed_count: 发送失败次数
    if (alerts_triggered & TWAI_ALERT_ERR_PASS) {
Serial.println("Alert: TWAI controller has become error passive."); // Print passive error alert
}
if (alerts_triggered & TWAI_ALERT_BUS_ERROR) {
Serial.println("Alert: A (Bit, Stuff, CRC, Form, ACK) error has occurred on the bus."); // Print bus error alert
Serial.printf("Bus error count: %d\n", twaistatus.bus_error_count); // Print bus error count
}
if (alerts_triggered & TWAI_ALERT_TX_FAILED) {
Serial.println("Alert: The Transmission failed."); // Print transmission failure alert
Serial.printf("TX buffered: %d\t", twaistatus.msgs_to_tx); // Print buffered TX messages
Serial.printf("TX error: %d\t", twaistatus.tx_error_counter); // Print TX error count
Serial.printf("TX failed: %d\n", twaistatus.tx_failed_count); // Print failed TX count
}
if (alerts_triggered & TWAI_ALERT_TX_SUCCESS) {
Serial.println("Alert: The Transmission was successful."); // Print successful transmission alert
Serial.printf("TX buffered: %d\t", twaistatus.msgs_to_tx); // Print buffered TX messages
}

判断发生了哪些 Alert 事件,并且做出对应的处理。

  • TWAI_ALERT_ERR_PASS 错误被动状态:当 TWAI 控制器的 TX 错误计数器 ≥ 128 或 RX 错误计数器 ≥ 128 时,会进入 Error Passive,控制器可能出现了严重的通信错误,但仍未完全离线。
  • TWAI_ALERT_BUS_ERROR 总线错误:TWAI 总线发生以下任意协议级别错误时会触发,例如 Bit Error(位错误),Stuff Error(填充位错误),CRC Error(CRC 错误),Form Error(帧格式错误),ACK Error(应答错误),并且接收计数器会增加
  • TWAI_ALERT_TX_FAILED 发送失败:发送仲裁失败或写入 ACK 失败导致整帧无法发送时会触发,表示该 TWAI 帧没有发送成功,需要重试,并且会打印相关的数据。
  • TWAI_ALERT_TX_SUCCESS 发送成功:TWAI 控制器成功把一帧 CAN 数据发送到总线,且 收到至少一个 ACK。

    unsigned long currentMillis = millis(); // Get current time in milliseconds
if (currentMillis - previousMillis >= TRANSMIT_RATE_MS) { // Check if it's time to send a message
previousMillis = currentMillis; // Update last send time
send_message(); // Call function to send message
}

之前在 waveshare_twai_port.h 头文件当中我们将 TRANSMIT_RATE_MS 设置成了 1000ms,这里就是设置每隔 1000ms 会发送一次数据帧。

3. 主程序 TWAItransmit.ino

    #include "waveshare_twai_port.h"

引入头文件 waveshare_twai_port

    static bool driver_installed = false; // Flag to check if the driver is installed
esp_expander::CH422G *expander = NULL;

新建一个布尔值 driver_installed 用来存放 TWAI 的驱动安装结果,用 esp_expander::CH422G *expander = NULL 定义一个指向 CH422G 对象的指针,稍后会用 new 动态创建它;

void setup() 函数:

    Serial.begin(115200); // Initialize serial communication at 115200 baud rate
Serial.println("Initialize IO expander");

启动串口并设置波特率为 115200,方便打印 debug 信息。

    expander = new esp_expander::CH422G(EXAMPLE_I2C_SCL_PIN, EXAMPLE_I2C_SDA_PIN, EXAMPLE_I2C_ADDR);
expander->init();
expander->begin();

建一个新的 CH422G 对象,并指定 I2C 时钟线(SCL)、数据线(SDA)和 I2C 地址,并调用 init()begin() 完成初始化。

    Serial.println("Set the IO0-7 pin to output mode.");
expander->enableAllIO_Output();
expander->multiDigitalWrite(TP_RST | LCD_RST | USB_SEL, HIGH);
expander->digitalWrite(LCD_BL , LOW);

设置所有的扩展 IO 口为输出口,并且设置各个引脚的状态。设置 USB_SEL = HIGH 时,启用 FSUSB42UMX 芯片,此时 GPIO19/20 用作 CAN_TX/CAN_RX。

    driver_installed = waveshare_twai_init();

初始化 TWAI 通信接口,并将结果储存在 driver_installed 当中。

    void loop() {
if (!driver_installed) {
// Driver not installed
delay(1000); // Wait for 1 second
return; // Exit the loop if the driver is not installed
}
waveshare_twai_transmit(); // Call the transmit function if the driver is installed
}

void loop() 函数,先判断初始化结果 driver_installed 是否成功,如果初始化失败,则退出 loop 循环程序;如果成功则启动 waveshare_twai_transmit() 传输程序,这个函数做几件事:

  • 检查是否有报警(发成功/失败/总线错误)
  • 每隔 TRANSMIT_RATE_MS 定时发送 CAN 消息
  • 打印日志,自动处理状态信息

CAN 信息接收例程讲解

上面的教程中尝试了如何使用 CAN 接口进行发送,接下来我们试下如何用 CAN 接口进行接收。

示例代码

打开对应的 \Arduino\examples\07_TWAIreceive 文件夹:

ESP32-S3-Touch-LCD-7 CAN demo 15

双击打开 07_TWAIreceive.ino

ESP32-S3-Touch-LCD-7 CAN demo 16

按住底板下方的 BOOT 按键,将 ESP32-S3-Touch-LCD-7 的 USB 口接回到电脑。

与之前的操作步骤一样,右上角选择好串口和开发板型号 Waveshare ESP32-S3-Touch-LCD-7,设置好下载的配置,USB CDC On Boot 选择 Disabled, 再点击烧录下载程序。

ESP32-S3-Touch-LCD-7 CAN demo 19

运行结果

烧录结束后,把线从 ESP32-S3-Touch-LCD-7 的 USB 口拔出,换到 UART 口接入电脑,同时通过 USB to CAN 模块将 CAN 口接到电脑。

ESP32-S3-Touch-LCD-7 CAN demo 10

如图设置 USB-CAN-A_TOOL 的参数,注意 CAN 速率是 500K:

ESP32-S3-Touch-LCD-7 CAN demo 20

点击手动发送,随机点任意 ID 的发送数据:

ESP32-S3-Touch-LCD-7 CAN demo 21

即可看到 ESP32 这边的串口打印,会依次显示发送信息的数据类型、设备 ID、8 个字节的数据

代码回顾

我们提供了一个 CAN 信息的发送例程 TWAIreceive.ino,包含其会用到的 waveshare_twai_port.h 头文件和 waveshare_twai_port.cpp 文件。

其中 TWAIreceive.ino 和 waveshare_twai_port.h 的结构跟前面的 TWAItransmit 发送例程基本差不多,所以这里就不进行讲解了,重点再讲解下 waveshare_twai_port.cpp 当中的 handle_rx_message()waveshare_twai_receive() 函数。

handle_rx_message() 处理接收信息的函数:

    static void handle_rx_message(twai_message_t &message)

twai_message_t &message:参数是 TWAI 消息结构体的引用,这样可以直接修改原消息而不需要拷贝

    if (message.extd)
{
Serial.println("Message is in Extended Format"); // Print if the message is in extended format
}
else
{
Serial.println("Message is in Standard Format"); // Print if the message is in standard format
}
Serial.printf("ID: %x\nByte:", message.identifier); // Print message ID

message.extd:检查消息是扩展格式还是标准格式,并分别输出对应的串口打印结果;Serial.printf("ID: %x\nByte:", message.identifier); 打印发送信息的设备的 ID,%x 表示以十六进制格式输出。

    if (!(message.rtr))
{ // Check if it is not a remote transmission request
for (int i = 0; i < message.data_length_code; i++)
{
Serial.printf(" %d = %02x,", i, message.data[i]); // Print each byte of the message data
}
Serial.println(""); // Print a new line
}

message.rtr 检查是否是远程传输请求帧;用!取反,如果不是远程帧(即数据帧),才执行下面的代码:用 for 循环所有数据字节,以两位十六进制显示数据值。

    Serial.println(""); // Print a new line

打印换行,使输出更清晰

waveshare_twai_receive() 函数:

    uint32_t alerts_triggered;
twai_read_alerts(&alerts_triggered, pdMS_TO_TICKS(POLLING_RATE_MS)); // Read triggered alerts
twai_status_info_t twaistatus; // Create status info structure
twai_get_status_info(&twaistatus); // Get status information

这里和前面那个传输程序一样,是用于读取发生的 TWAI 事件,和保存 TWAI 总线状态信息。

    if (alerts_triggered & TWAI_ALERT_ERR_PASS)
{
Serial.println("Alert: TWAI controller has become error passive."); // Print passive error alert
}
if (alerts_triggered & TWAI_ALERT_BUS_ERROR)
{
Serial.println("Alert: A (Bit, Stuff, CRC, Form, ACK) error has occurred on the bus."); // Print bus error alert
Serial.printf("Bus error count: %d\n", twaistatus.bus_error_count); // Print bus error count
}

确认是否发生了错误被动状态和总线错误事件。

    if (alerts_triggered & TWAI_ALERT_RX_QUEUE_FULL)
{
Serial.println("Alert: The RX queue is full causing a received frame to be lost."); // Print RX queue full alert
Serial.printf("RX buffered: %d\t", twaistatus.msgs_to_rx); // Print buffered RX messages
Serial.printf("RX missed: %d\t", twaistatus.rx_missed_count); // Print missed RX count
Serial.printf("RX overrun %d\n", twaistatus.rx_overrun_count); // Print RX overrun count
}

检查是否发生了接收队列满,数据丢失的情况,如果有,则打印出:

  • RX buffered:队列中现在还剩多少消息没有被读取
  • RX missed:因为队列满而丢掉的帧次数
  • RX overrun:接收溢出次数 产生这种情况的原因一般可能是代码读取不够快,或者对方发送频率太高,超过 ESP32 处理能力。

    if (alerts_triggered & TWAI_ALERT_RX_DATA)
{
// One or more messages received. Handle all.
twai_message_t message;
while (twai_receive(&message, 0) == ESP_OK)
{ // Receive messages
handle_rx_message(message); // Handle each received message
}
}

首先 if (alerts_triggered & TWAI_ALERT_RX_DATA) 检测是否有接收到数据帧,如果有,那么就会执行把 RX 队列里所有收到的 CAN 消息一条接一条读出来,然后交给 handle_rx_message() 处理。

twai_message_t message 是 TWAI 框架定义的消息结构体,每一帧 TWAI 数据/ID/长度/时间戳等都会存在这里;用 while 循环读取所有消息,twai_receive(&message, 0) 第二个参数是超时时间, 0 表示不等待,如果没有消息,立刻退出 while 循环。

handle_rx_message(message) 就是我们前面解析过的处理接收数据的函数,它会进行以下操作:判断帧类型,打印 TWAI ID,打印数据内容等