简介:面向STM32F429嵌入式开发者的双CAN通道测试程序,用于验证CAN1与CAN2两个独立控制器的初始化配置、报文收发和中断处理,适合汽车电子、工业自动化领域的中初级开发者学习。程序基于HAL库实现,围绕ISO 11898协议标准,详细覆盖位定时与波特率设定、报文头结构体填充、发送与接收FIFO管理、滤波器规则配置、中断服务函数编写,以及位错误、帧错误等异常的检测与恢复流程。通过该程序,读者可以直观理解双路CAN并行工作时的消息仲裁机制,学习如何让两个CAN通道同时收发不同优先级的报文,并快速搭建起自己的双通道通信测试环境。资源压缩包约9.52MB,已有169人学习下载;开发者可借这套示例程序掌握双CAN通道的初始化与收发流程,并将其中的代码逻辑直接移植到实际项目的第二路CAN应用场景。
1. STM32F429双CAN通道测试程序,为什么先要处理CAN2依赖CAN1这个前提
STM32F429内部带有两个bxCAN控制器,但设计成主从结构:CAN1为主,CAN2为从。所谓“从”不只是名称上的区别——CAN2没有独立完整的过滤器存储与位时序配置,必须依赖CAN1已经被正确使能并退出初始化状态,才能进入正常工作模式。我见过不少工程师拿到“CAN2Channels_STM32F429的双CAN通道测试程序”这类需求时,直接在HAL库中单独初始化CAN2,结果启动时报错,最后发现CAN1根本没有使能。本文按最常见的STM32CubeMX加HAL库方案,把双CAN测试程序的落地路径讲清楚:引脚怎么映射、位时序参数怎么填、过滤器如何在CAN1与CAN2之间切分,最后落到波形验证与Bus Off恢复策略。适合做车载网关、工业CAN节点或产线工装板的软硬件工程师,新手也能按步骤在F429开发板上跑通双通道互收。
2. STM32F429双CAN测试程序的引脚映射与收发器电路
2.1 CAN1/CAN2在STM32F429上的引脚复用选项对比
搭建测试程序的第一步,是确定CAN1与CAN2各自用哪组引脚。STM32F429上两个CAN控制器都复用AF9功能,每个控制器都有多组可选引脚,但不同封装和不同板卡上可用的组合不一样。CAN1常用PA11(RX)/PA12(TX)、PB8/PB9,以及144脚及以上封装的PD0/PD1;CAN2常用PB5/PB6和PB12/PB13。下表是常用组合。
| CAN通道 | RX引脚 | TX引脚 | AF编号 | 板级注意点 |
|---|---|---|---|---|
| CAN1 | PA11 | PA12 | AF9 | 多数F429核心板默认引出 |
| CAN1 | PB8 | PB9 | AF9 | 与I2C1、SPI1部分引脚重叠 |
| CAN1 | PD0 | PD1 | AF9 | 144脚及以上封装才可用 |
| CAN2 | PB5 | PB6 | AF9 | 与I2C1、SPI1引脚邻近 |
| CAN2 | PB12 | PB13 | AF9 | 与SPI2的NSS/SCK接近 |
我在实际项目中的选择顺序是:CAN1用PA11/PA12,CAN2用PB5/PB6。原因是这两组在大多数开发板上已经引出或容易飞线。选引脚时先看复用冲突,再看PCB布线是否方便。CubeMX中的操作很简单,在Pinout视图里把对应引脚用鼠标拉成CAN1_RX、CAN1_TX或CAN2_RX、CAN2_TX,软件会自动把复用功能设成AF9。需要注意的是,同一个控制器不要两组引脚混用,比如CAN1的RX用PA11、TX用PB9,这在功能上可行但容易让后续排查的人困惑,测试程序里尽量保持一对引脚。
2.2 双路CAN收发器与120Ω终端电阻怎么接
CAN控制器输出的是3.3V逻辑电平,不能直接挂总线,必须经过收发器。常用的有TJA1050、SN65HVD230或隔离型的ISO1050。TJA1050是5V供电,输入阈值兼容3.3V逻辑,可以直接与F429的GPIO相连;SN65HVD230是3.3V供电,适合低压系统。双通道的连接方式完全一致,以TJA1050为例:
PA12 (CAN1_TX) --> TJA1050 TXD PA11 (CAN1_RX) <-- TJA1050 RXD PB6 (CAN2_TX) --> 另一片TJA1050 TXD PB5 (CAN2_RX) <-- 另一片TJA1050 RXD CANH 与 CANL 之间跨接120Ω终端电阻这里的核心是终端电阻布局。CAN总线协议要求物理线路两端各有一个120Ω电阻,而不是每个节点都加。当两个CAN通道通过同一根双绞线互测时,只需要在双绞线两端各跨接一个120Ω电阻。如果只在板内测试且线缆很短,可以先不接终端电阻,但此时总线信号反射会让波形边沿变差,逻辑分析仪看到的位宽度会不稳定。要验证物理层时,终端电阻不能省。
2.3 共地与供电对can通信测试的影响
测试程序跑不通时,我一般先量三个点:收发器供电电压是否正确、TXD引脚静态电平是否为高、两个测试节点是否真正共地。CAN是差分信号,但收发器有一个共模输入范围,TJA1050约为-7V到+12V。如果两端设备的地电位差超出这个范围,总线上会出现大量错误帧,表现就是“can初始化失败”或者发送一直报错。双通道在同一块板上互测时,两片收发器必须共地;不同板卡通过长线连接时,最好使用隔离收发器。还有一个经常被忽略的点:CAN控制器空闲时TXD应为高电平,如果量到低电平,说明硬件复位后引脚状态没有正确配置,或者控制器正处于Bus Off状态。
3. 用CubeMX完成STM32F429双CAN配置与位时序设定
3.1 使能CAN2前必须使能CAN1:时钟树与初始化顺序
在CubeMX中勾选CAN1和CAN2只解决了外设时钟使能问题。F429的bxCAN模块中,CAN2作为从控制器,只有在CAN1正常工作后才能真正参与总线通信。具体到CubeMX的Clock Configuration页面,需要确认CAN时钟源来自APB1,且APB1时钟不超过42MHz。当SYSCLK设为168MHz时,APB1分频系数为4,得到42MHz,这是F429上最常见的组合。如果外部晶振是25MHz,需要在RCC配置里选择对应的HSE值,否则PLL算出来的系统时钟会偏离预期。代码层面,初始化顺序是先HAL_CAN_Init(&hcan1),再HAL_CAN_Init(&hcan2),不要反过来。启动顺序也一样:先HAL_CAN_Start(&hcan1),再HAL_CAN_Start(&hcan2)。关闭时顺序反过来,先停CAN2,再停CAN1。
3.2 500kbps下BS1、BS2、SJW参数与采样点计算
CAN位时间由四段组成:同步段、传播时间段、相位缓冲段1和相位缓冲段2。在STM32的HAL库中,传播时间段和相位缓冲段1合并为TimeSeg1,也就是BS1;相位缓冲段2单独为TimeSeg2,即BS2。同步段固定为1个时间量子tq。采样点位于BS1结束后、BS2开始前,这个位置直接关系通信的可靠性。核心公式是:
- tq = (BRP + 1) / fAPB1
- 位时间 = (1 + BS1 + BS2) 个tq
- 波特率 = fAPB1 / ((BRP+1) × (1+BS1+BS2))
- 采样点 = (1 + BS1) / (1 + BS1 + BS2) × 100%
以fAPB1=42MHz、目标波特率500kbps为例。令BS1=11、BS2=2,位时间就是14个tq,此时BRP+1 = 42MHz / (500k × 14) = 6,所以BRP取5。采样点 = (1+11)/14 = 85.7%,落在CAN协议推荐的80%到87.5%区间内。按这个思路可以得到一组常用的完整参数。
| 目标波特率 | Prescaler(BRP) | BS1 | BS2 | SJW | 实际波特率 | 采样点 |
|---|---|---|---|---|---|---|
| 1 Mbps | 2 | 11 | 2 | 1 | 1.000 Mbps | 85.7% |
| 500 kbps | 5 | 11 | 2 | 1 | 500 kbps | 85.7% |
| 250 kbps | 11 | 11 | 2 | 1 | 250 kbps | 85.7% |
| 125 kbps | 23 | 11 | 2 | 1 | 125 kbps | 85.7% |
SJW(同步跳跃宽度)用于补偿时钟偏差,取值一般不超过BS2,这里设成1tq即可。如果追求更低的采样点,可以改成BS1=10、BS2=3,位时间同样是14tq,采样点变为78.6%,适合线缆较长、信号边沿较缓的场景。这里需要说明,STM32F429的双CAN只支持CAN2.0帧,若项目后续要评估canfd和can的区别,CAN FD的采样点和位速率切换逻辑与这里不同,需要换支持CAN FD的控制器和收发器。
3.3 28个过滤器在CAN1与CAN2之间的切分
F429的bxCAN模块共享28个硬件过滤器组,编号0到27,不是每个CAN独立拥有。硬件通过CAN_FMR寄存器中的CAN2SB位决定CAN2从哪一组开始使用。例如CAN2SB=14时,绝对编号0到13给CAN1,绝对编号14到27给CAN2。CubeMX的NVIC设置页里会自动生成这部分配置,但测试程序中还要手动确认结构体字段一致。HAL库中给CAN2配置过滤器时,FilterBank字段写的是相对CAN2起始组的偏移量,CAN2的FilterBank=0对应绝对组号14。给CAN1配置时才是绝对编号。这个差异几乎每个双CAN项目都会遇到,很多“can初始化失败”其实只是过滤器配置越界。
4. STM32F429双CAN测试程序的核心HAL代码:从回环到交叉收发
4.1 先跑Loopback回环自测,验证控制器本身没问题
不要在未验证控制器的情况下就去检查外部总线。先把CAN1和CAN2都配置为Loopback模式,此时发送报文不经过收发器,直接在芯片内部回到本节点的接收FIFO。回环模式可以验证CAN内核、FIFO、中断和过滤器配置是否正常。需要特别注意的是,回环模式仍然受硬件过滤器控制,因此要先配一个全通过滤器,掩码全部置0表示接收所有ID。
CAN_FilterTypeDef filterAll; filterAll.FilterActivation = ENABLE; filterAll.FilterBank = 0; filterAll.FilterMode = CAN_FILTERMODE_IDMASK; filterAll.FilterScale = CAN_FILTERSCALE_32BIT; filterAll.FilterIdHigh = 0x0000U; filterAll.FilterIdLow = 0x0000U; filterAll.FilterMaskIdHigh = 0x0000U; filterAll.FilterMaskIdLow = 0x0000U; filterAll.FilterFIFOAssignment = CAN_RX_FIFO0; HAL_CAN_ConfigFilter(&hcan1, &filterAll);这段代码的作用是让CAN1接收所有标准帧。FilterMode选择IDMASK,掩码为0表示不漏过任何ID;如果把掩码设为0x7FF左移5位,则只匹配一个指定ID。FilterBank设为0,是CAN1可用的过滤器组。回环模式下发送端和接收端是同一个CAN节点,所以需要同时调用HAL_CAN_Start启动控制器,并打开接收中断。
CAN_TxHeaderTypeDef txHeader; txHeader.StdId = 0x123; txHeader.ExtId = 0; txHeader.IDE = CAN_ID_STD; txHeader.RTR = CAN_RTR_DATA; txHeader.DLC = 4; uint8_t txData[4] = {0x11, 0x22, 0x33, 0x44}; uint32_t mailbox = 0; if (HAL_CAN_AddTxMessage(&hcan1, &txHeader, txData, &mailbox) == HAL_OK) { // 回环模式下,发送完成后本节点会收到同一帧 }HAL_CAN_AddTxMessage的四个参数分别是CAN句柄、发送报头结构体、数据缓冲区和邮箱索引。函数执行成功后,报文首先进入硬件邮箱,总线仲裁成功后发送。回环模式下CANH/CANL引脚上不会有信号波形,属于预期行为,不要拿示波器去量。
4.2 Normal模式交叉测试:CAN1发送、CAN2接收中断
回环验证通过后,将两个CAN都切换到Normal模式,接入外部收发器和双绞线。同一个总线上有两个发送节点时,can总线仲裁由ID位值决定,ID小的优先级高。我们的测试程序固定由CAN1发送ID=0x123的报文,CAN2设置过滤器只接收这个ID,从而验证双通道链路。
// CAN1 发送节点 hcan1.Init.Mode = CAN_MODE_NORMAL; HAL_CAN_Init(&hcan1); HAL_CAN_Start(&hcan1); // CAN2 接收节点 hcan2.Init.Mode = CAN_MODE_NORMAL; HAL_CAN_Init(&hcan2); HAL_CAN_Start(&hcan2); HAL_CAN_ActivateNotification(&hcan2, CAN_IT_RX_FIFO0_MSG_PENDING);启动CAN2后调用HAL_CAN_ActivateNotification,把FIFO0有报文挂起的中断通知打开。这一步容易漏,漏掉后即使硬件接收到了报文,也不会进入中断回调。
void CAN2_RX_IRQHandler(void) { HAL_CAN_IRQHandler(&hcan2); } void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan) { CAN_RxHeaderTypeDef rxHeader; uint8_t rxData[8]; if (hcan->Instance == CAN2) { HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &rxHeader, rxData); // 检查 rxHeader.StdId,应为 0x123 } }中断回调里通过判断hcan->Instance区分是哪个CAN控制器触发的,避免CAN1的报文进入接收逻辑。HAL_CAN_GetRxMessage每次从FIFO0弹出一帧,并把报头信息填入rxHeader、数据拷贝到rxData。由于FIFO深度只有3个邮箱,回调中应尽快把rxData复制到用户缓冲区,否则连续大量报文到来时可能溢出。
4.3 双CAN过滤器配置:FilterBank从相对编号写
下面这段是给CAN2配置只接收ID=0x123的过滤器。双CAN环境下,过滤器组划分必须与CAN2SB一致,否则CAN2的报文会落到CAN1的FIFO里。
CAN_FilterTypeDef filter; filter.FilterActivation = ENABLE; filter.FilterBank = 0; // 相对CAN2起始组的偏移,对应绝对组号14 filter.FilterMode = CAN_FILTERMODE_IDMASK; filter.FilterScale = CAN_FILTERSCALE_32BIT; filter.FilterIdHigh = (0x123U << 5) & 0xFFFFU; filter.FilterIdLow = 0x0000U; filter.FilterMaskIdHigh = (0x7FFU << 5) & 0xFFFFU; filter.FilterMaskIdLow = 0x0000U; filter.FilterFIFOAssignment = CAN_RX_FIFO0; filter.SlaveStartFilterBank = 14; // CAN2从绝对组号14开始 HAL_CAN_ConfigFilter(&hcan2, &filter);为什么ID要左移5位?因为bxCAN的32位过滤器寄存器布局中,标准ID占用位28到位18,写入高16位时需要将11位ID整体左移5位。屏蔽位的0x7FF左移5位表示只比较ID字段,其他位全部置1表示不关心。FilterBank字段在CAN2句柄下写的是相对偏移,HAL库内部会自动加上SlaveStartFilterBank的值。如果HAL_CAN_ConfigFilter返回HAL_ERROR,优先检查该组号是否超出当前通道可用的范围。
4.4 双CAN初始化失败的高频原因排查
如果HAL_CAN_Init或HAL_CAN_Start返回错误,按顺序检查五项:一是CAN1有没有同时使能并先启动;二是APB1时钟是否超过42MHz,超出后CAN位时序计算基准就错了;三是两个hcan句柄的Instance是否接反;四是过滤器组索引是否越界或重叠;五是波特率参数中BS1加BS2是否至少为2tq。这五点在单CAN项目里不一定全部触发,在双CAN测试中却是高频根因。另有一个容易忽略的坑:CAN2启动失败时,hcan2.ErrorCode可能标记为HAL_CAN_ERROR_NOT_INITIALIZED,容易让人误以为初始化函数本身没执行,实际原因是CAN1还未进入正常模式。
5. 双CAN测试程序的验证技巧:波形检查与Bus Off恢复
5.1 在TXD/RXD上抓位时序,核对2微秒位宽
上位机显示“发送成功”不代表物理总线没有问题。把逻辑分析仪接到CAN1_TXD引脚,触发方式选下降沿,因为帧起始SOF是显性电平,对应逻辑0。以500kbps为例,单个位时间应为2微秒,发送数据0xAA时波形应当是10101010的交替序列。如果测量到的位宽偏差超过5%,或波形边沿明显抖动,回到第三章的参数表检查BS1和BS2,必要时把采样点调整为80%。双通道互测时,可以在CAN2的RXD引脚上抓到几乎相同的帧,与CAN1_TXD之间的延迟只有几十纳秒。若在CAN2_RXD上抓不到帧,问题出在总线连接或收发器,而不是CAN1的发送代码。
5.2 主动制造Bus Off并编写恢复回调
测试Bus Off恢复的常用方法,是把CANH与CANL短接一到两秒,造成持续显性电平错误。发送节点的TEC寄存器会不断累加,超过255后进入Bus Off状态,表现为TXD引脚持续高电平,不再发送任何帧。HAL库通过错误回调上报该状态,回调中可以对故障通道做重新初始化。
void HAL_CAN_ErrorCallback(CAN_HandleTypeDef *hcan) { if (hcan->Instance == CAN2 && (hcan->ErrorCode & HAL_CAN_ERROR_BOF) != 0U) { HAL_CAN_Stop(hcan); HAL_CAN_Start(hcan); } }这段代码的作用是将Bus Off状态的CAN2重新拉回正常。HAL_CAN_Stop先停止控制器并清除错误状态,HAL_CAN_Start再重新激活。也可以在Init结构体中把AutoBusOff设为ENABLE,由硬件在总线空闲后自动恢复,但回调中的日志记录仍然有用,方便确认哪一路发生过故障。实际项目中我倾向AutoBusOff=ENABLE加回调记录,兼顾自动恢复与可观测性。
5.3 连续压测与负载率自检
只跑一帧不能说明问题。把发送端改成分批次循环发送1000帧,接收端累加收到的帧数,最后比对计数并记录最大连续丢帧数。若计数一致,说明双CAN链路稳定。也可以借助CAN上位机工具,观察总线负载率是否与理论计算一致。例如每秒发送200帧、每帧约120位时,理论负载率为200×120/500k = 4.8%。实测值如果明显高于这个数字,往往是有其他节点在不停重发错误帧,这时优先检查终端电阻和电位差。测试程序的最后一步,把上述波形、计数和负载率三个数据填入测试记录,双CAN通道的验证才算完整。
本文还有配套的精品资源,点击获取