CAN 发送示例(can_tx)
源码路径: example/hal/can/can_tx
支持的平台
例程可以运行在以下开发板上.
sf32lb56-lcd
sf32lb58-lcd
概述
本例程演示了如何使用芯片的 CAN 控制器完成 CAN 数据发送功能,覆盖主发送缓冲区(PTB)单帧发送和副发送缓冲区(STB)多帧批量发送两种方式。
本例程作为 CAN 通信的发送端(Board A),与 can_rx 例程(接收端,Board B)配合,在两块开发板之间进行单向 CAN 通信:can_tx 发送,can_rx 接收。
本例程支持轮询与中断两种工作方式,通过
USE_CAN_IT宏切换。中断方式下由 NVIC 中断和 HAL 回调通知发送完成,无需轮询寄存器。基于本例程可以实现周期性数据上报、多帧批量下发等场景。
硬件要求
两块支持 CAN 功能的 SiFli 开发板(如 sf32lb56-lcd 或 sf32lb58-lcd):一块烧录本例程作为发送端,另一块烧录 can_rx 例程作为接收端
两个 CAN 收发器模块(如 TJA1050,需 5V 供电)
杜邦线若干
硬件连接
如果开发板把 CAN 使用的引脚复用为 UART,需先拆除该 UART 上的串阻。
sf32lb56-lcd需移除下图中的两个电阻(位于 PA03/PA04 上),具体位置可以参考下图
sf32lb56-lcd 与 CAN 收发器连接:
开发板引脚 |
CAN 收发器 |
|---|---|
PA03 (8) |
TX |
PA04 (9) |
RX |
5V |
VCC |
GND |
GND |
sf32lb58-lcd 与 CAN 收发器连接:
开发板引脚 |
CAN 收发器 |
|---|---|
PA00 |
TX |
PA03 |
RX |
5V |
VCC |
GND |
GND |
两块开发板的 CAN 收发器之间连接:
CAN 收发器(can_tx 端) |
CAN 收发器(can_rx 端) |
|---|---|
CANH |
CANH |
CANL |
CANL |
TJA1050 模块丝印为
CHAN/CHAL,与标准 CAN 的CANH/CANL等同CANH 需对 CANH、CANL 需对 CANL,不可交叉;总线两端各需一个 120Ω 终端电阻
CAN收发器(TJA1050)

用法
下面的小节仅提供绝对必要的信息。有关配置 SiFli-SDK 及使用其构建和运行项目的完整步骤,请参阅 SiFli-SDK 快速入门。
编译和烧录
分别编译烧录 can_tx 和 can_rx 两个工程到对应的板子:本例程(can_tx)烧录到发送端(Board A),can_rx 例程烧录到接收端(Board B)
切换到例程的
project目录(例如example/hal/can/can_tx/project),运行 scons 命令执行编译:
scons --board=sf32lb56-lcd_a128r12n1 -j8
编译完成后,运行
build_sf32lb56-lcd_a128r12n1_hcpu/download.bat,程序通过 JLink 自动下载或使用串口下载:
build_sf32lb56-lcd_a128r12n1_hcpu/uart_download.bat
配置项目
CAN 的码率和轮询/中断方式均在源码宏中配置(can_tx/src/main.c):
宏 |
默认值 |
说明 |
|---|---|---|
|
400000 |
CAN 目标码率, |
|
1 |
工作方式,0=轮询,1=中断 |
如需修改 console 串口或日志开关,可在例程目录下进入 menuconfig(proj.conf 中已将 console 切换到 UART4):
sdk.py menuconfig --board=<board_name>
示例输出
如果示例运行成功,您将在串口看到以下输出:
can_tx的log输出(中断模式
USE_CAN_IT=1)
01-13 17:07:26:527 \ | /
01-13 17:07:26:530 - SiFli Corporation
01-13 17:07:26:532 / | \ build on Dec 25 2025, 2.4.0 build befba178
01-13 17:07:26:536 2020 - 2022 Copyright by SiFli team
01-13 17:07:26:539 mount /dev sucess
01-13 17:07:26:542 [I/drv.audprc] init 00 ADC_PATH_CFG0 0x924
01-13 17:07:26:546 [I/drv.audprc] HAL_AUDPRC_Init res 0
01-13 17:07:26:548 [I/drv] HAL_AUDCODEC_Init res 0
01-13 17:07:26:550 [I/TOUCH] Regist touch screen driver, probe=1001aa51
01-13 17:07:26:552 call par CFG1(35bb)
01-13 17:07:26:553 fc 9, xtal 2000, pll 2026
01-13 17:07:26:555 call par CFG1(35bb)
01-13 17:07:26:556 fc 9, xtal 2000, pll 2026
01-13 17:07:26:557 CAN IT mode: Board A (send-first).
01-13 17:07:26:559 [TX-IT] Primary submit: ID=0x123, Data=0x11223344 0x55667788
01-13 17:07:27:061 [TX-IT] Secondary submit: 3 frames: 0x101, 0x102, 0x103
01-13 17:07:29:062 [TX-IT] Primary submit: ID=0x123, Data=0x11223344 0x55667788
01-13 17:07:29:564 [TX-IT] Secondary submit: 3 frames: 0x101, 0x102, 0x103
01-13 17:07:31:565 [TX-IT] Primary submit: ID=0x123, Data=0x11223344 0x55667788
01-13 17:07:32:067 [TX-IT] Secondary submit: 3 frames: 0x101, 0x102, 0x103
轮询模式(
USE_CAN_IT=0)下输出为[Primary SEND]、[Secondary SEND]格式(此处省略串口时间戳):
CAN Primary+Secondary test start (Board A: send-first).
Primary filter: 0x234, Secondary filter: 0x203
[Primary SEND] ID=0x123, Data=0x11223344 0x55667788
[Secondary SEND] 3 frames: 0x101, 0x102, 0x103
[Primary SEND] ID=0x123, Data=0x11223344 0x55667788
[Secondary SEND] 3 frames: 0x101, 0x102, 0x103
...
中断模式下 can_tx 只负责发送,不接收数据;接收函数仅在轮询模式(
USE_CAN_IT=0)下被调用。
CAN读写波形
本例程只负责发送,下图是两块开发板通信时的 CAN 总线波形

CAN参数修改
static void CAN_Config(void)
{
hcan.Instance = CAN1;
/* Derive the prescaler and bit-timing segments from the target bitrate */
if (HAL_CAN_CalcTiming(&hcan, CAN_BITRATE) < 0)
{
rt_kprintf("Failed to calculate CAN timing.\n");
Error_Handler();
}
if (HAL_CAN_Init(&hcan) != HAL_OK)
{
rt_kprintf("CAN initialization failed.\n");
Error_Handler();
}
CAN_FilterTypeDef can_filter_config;
// Configure standard frame filter (ID=0x234, FilterBank=0)
can_filter_config.FilterId = 0x234;
can_filter_config.FilterMask = 0x7FF; // Exact match for standard ID
can_filter_config.FilterBank = 0;
can_filter_config.FilterActivation = ENABLE;
can_filter_config.IDECheckEnable = ENABLE;
can_filter_config.IDEValue = CAN_ID_STD; // Only accept standard frames
if (HAL_CAN_ConfigFilter(&hcan, &can_filter_config) != HAL_OK)
{
rt_kprintf("Failed to configure CAN filter 0.\n");
Error_Handler();
}
// Configure standard frame filter (ID=0x203, FilterBank=1)
can_filter_config.FilterId = 0x203;
can_filter_config.FilterMask = 0x7FF; // Exact match for standard ID
can_filter_config.FilterBank = 1;
can_filter_config.FilterActivation = ENABLE;
can_filter_config.IDECheckEnable = ENABLE;
can_filter_config.IDEValue = CAN_ID_STD; // Only accept standard frames
if (HAL_CAN_ConfigFilter(&hcan, &can_filter_config) != HAL_OK)
{
rt_kprintf("Failed to configure CAN filter 1.\n");
Error_Handler();
}
}
首先调用
HAL_CAN_CalcTiming,按CAN_BITRATE(默认 400kbps)反算出预分频与位时间段参数(该函数内部按 48MHz CAN 时钟计算),不再写死预分频值随后配置两条过滤器,
can_filter_config.FilterId分别为 0x234(FilterBank=0)和 0x203(FilterBank=1),即只接收这两个 ID 的标准帧通过
can_filter_config.FilterMask设置过滤掩码,0x7FF 表示标准帧 11 位 ID 全部精确匹配,扩展帧使用 0x1FFFFFFF通过
can_filter_config.FilterBank,来配置过滤器编号(0~15)通过
can_filter_config.FilterActivation,来控制过滤器是否启用通过
can_filter_config.IDECheckEnable,来控制是否启用 IDE 检查,以区分标准帧与扩展帧通过
can_filter_config.IDEValue,用来控制接收标准帧还是扩展帧注意:默认的中断模式(
USE_CAN_IT=1)下 can_tx 不读取这两个过滤器,只有轮询模式(USE_CAN_IT=0)才会调用接收函数
CAN中断模式说明
本例程通过
USE_CAN_IT宏切换工作方式(0=轮询,1=中断)。中断模式下,CAN 控制器通过硬件中断通知 CPU,无需轮询寄存器,能大幅降低 CPU 占用。测试需要两块开发板:can_tx(发送端,Board A)和 can_rx(接收端,Board B),两块板子均烧录中断模式固件。
ISR 基础设施
void CAN1_IRQHandler(void)
{
rt_interrupt_enter();
HAL_CAN_IRQHandler(&hcan); // 统一入口,识别中断源并分发到对应回调
rt_interrupt_leave();
}
void HAL_CAN_MspInit(CAN_HandleTypeDef *hcan)
{
if (hcan->Instance == hwp_can1)
{
NVIC_EnableIRQ(CAN1_IRQn);
}
}
HAL_CAN_Init 首次调用时自动回调 HAL_CAN_MspInit,在此使能 NVIC。
发送端(can_tx / Board A)
Primary 发送(单帧) — 使能 CAN_IT_TX_PTB_EMPTY 通知(对应手册 TPIF 标志,主发送缓冲区 PTB)后写入主发送缓冲区,硬件发送完成后自动进入 HAL_CAN_TxPrimaryCompleteCallback:
void HAL_CAN_TxPrimaryCompleteCallback(CAN_HandleTypeDef *hcan)
{
/* 发送完成,关闭本次通知,避免重复进入中断 */
HAL_CAN_DeactivateNotification(hcan, CAN_IT_TX_PTB_EMPTY);
}
/* 使用 */
HAL_CAN_ActivateNotification(&hcan, CAN_IT_TX_PTB_EMPTY);
HAL_CAN_AddPrimaryTxMessage(&hcan, &tx_header, tx_data);
Secondary 批量发送(多帧) — 连续写入多帧到副发送缓冲区(手册称 STB),最后一次自动触发全部发出。硬件只产生一次 TSIF 中断,HAL 内部循环调用 HAL_CAN_TxSecondaryCompleteCallback(次数=帧数):
void HAL_CAN_TxSecondaryCompleteCallback(CAN_HandleTypeDef *hcan)
{
/* HAL 每完成一帧调用一次,关闭本次通知 */
HAL_CAN_DeactivateNotification(hcan, CAN_IT_TX_STB_EMPTY);
}
/* 使用 — 连续写入 3 帧,依次提交到副发送缓冲区 */
HAL_CAN_ActivateNotification(&hcan, CAN_IT_TX_STB_EMPTY);
HAL_CAN_AddSecondaryTxMessage(&hcan, &tx_header, tx_data); /* 帧1: ID=0x101, 被对端过滤掉 */
HAL_CAN_AddSecondaryTxMessage(&hcan, &tx_header, tx_data); /* 帧2: ID=0x102, 被对端过滤掉 */
HAL_CAN_AddSecondaryTxMessage(&hcan, &tx_header, tx_data); /* 帧3: ID=0x103, 被对端接收 */
/* TSALL 触发,3 帧批量发出 */
主循环交替使用 Primary 和 Secondary 发送(间隔 500ms / 2000ms)。
中断类型汇总
参数宏 |
手册标志 |
回调函数 |
触发时机 |
|---|---|---|---|
|
RIF |
|
每收到一帧 |
|
RFIF |
|
RBUF(接收缓冲)满 |
|
ROIF |
|
接收溢出* |
|
RAFIF |
|
RBUF(接收缓冲)接近满 |
|
TPIF |
|
主发送缓冲区(PTB)发送完成 |
|
TSIF |
|
副发送缓冲区(STB)发送完成(每帧调用一次) |
* 当前 HAL 实现中溢出分支会触发 HAL_ASSERT(0),请勿依赖该回调做正常运行时的溢出处理。
上表列出 CAN 支持的全部中断类型,本例程(can_tx)实际只使能了 CAN_IT_TX_PTB_EMPTY 和 CAN_IT_TX_STB_EMPTY。
「手册标志」列为芯片手册中 IR 寄存器的中断标志位名称(TPIF/TSIF/RIF 等即为缩写原文),PTB/STB 分别指 Primary/Secondary Transmit Buffer(主/副发送缓冲区),RBUF 指接收缓冲(Receive Buffer)。
通过按位或 | 可同时使能多个中断,如 CAN_IT_RX_MSG_PENDING | CAN_IT_RX_FIFO_FULL。
完整收发流程
本例程只演示单向通信:can_tx 发送,can_rx 接收。
Board A (can_tx) Board B (can_rx)
───────────────── ─────────────────
① Primary: ActivateNotification(TPIF)
AddPrimaryTxMessage()
→ 帧(0x123) 发送
→ TPIF → PrimaryCompleteCallback ② RIF → RxMsgPendingCallback
→ sem_release
→ GetRxMessage → 收到 0x123
③ Secondary: ActivateNotification(TSIF)
AddSecondaryTxMessage() × 3
→ 帧(0x101, 0x102, 0x103) 批量发出
→ TSIF → SecondaryCompleteCallback×3 ④ RIF → RxMsgPendingCallback
→ sem_release
→ 读出 0x103(其余被 filter 过滤)
异常诊断
如果跟log打印不一致或者波形不一致,请检查以下内容:
检查硬件接线是否正确,是否跟上述说明的接线是一致(CANH 接 CANH、CANL 接 CANL,TX/RX 不要接反)
检查
sf32lb56-lcd上 PA03/PA04 复用 UART 的两个串阻是否已拆除检查 CAN 总线两端是否各接了一个 120Ω 终端电阻
检查发送端与接收端的 CAN 码率是否一致(默认均为 400kbps)
检查 CAN 收发器模块是否已 5V 供电,且与开发板共地
对端未上电或未接入总线时,发送方收不到 ACK,发送会失败,请确认两块板均已上电
如有任何技术疑问,请在 GitHub 上提出 issue。