前言:为什么CAN总线是嵌入式通信的"硬通货"?
在嵌入式通信领域,CAN(Controller Area Network)总线凭借其高可靠性 、实时性 和多节点通信能力,成为汽车电子、工业控制、智能设备等领域的"标配"。想象一下:一辆汽车中有几十个ECU(电子控制单元),从发动机控制到车窗调节,都需要实时交换数据------CAN总线正是为这种多节点、高干扰环境设计的通信协议。
STM32几乎全系列都集成了高性能CAN控制器(bxCAN),支持标准帧/扩展帧、中断/DMA传输、灵活的滤波功能,完美适配工业级应用。本文将从CAN总线的基本原理讲起,深入解析STM32的CAN外设结构、配置方法、通信流程和实战案例,帮助你彻底掌握STM32 CAN通信开发。
一、CAN总线基础:为什么它能在恶劣环境中可靠通信?
在学习STM32的CAN外设前,我们需要先理解CAN总线的核心特性------这是掌握后续内容的基础。
1.1 CAN总线的核心优势
CAN总线由博世公司在1980年代为汽车电子开发,经过30多年发展,已成为国际标准(ISO 11898),其核心优势包括:
- 多主通信:总线上的每个节点都可主动发送数据,无需中央控制器,避免单点故障;
- 非破坏性仲裁:多个节点同时发送时,通过ID优先级仲裁,优先级高的节点继续发送,低优先级的自动退让,不会破坏数据;
- 差分信号传输:通过CAN_H和CAN_L两根线传输差分信号,抗电磁干扰能力极强(适合工业和汽车环境);
- 远距离传输:速率125kbps时传输距离可达500m,满足大多数工业场景;
- 错误检测与自动重传:内置CRC校验、位填充、应答机制,确保数据可靠传输,错误帧会自动重传。
1.2 CAN总线的基本概念
(1)帧结构:CAN数据的"包装格式"
CAN总线通过"帧"传输数据,最常用的是数据帧(用于传输有效数据),其结构如下(标准帧):
字段 | 长度(位) | 功能描述 |
---|---|---|
起始位 | 1 | 帧开始标志(低电平) |
仲裁 | 11 | 包含标准ID(11位),用于仲裁 |
控制场 | 6 | 包含数据长度(DLC,0~8字节) |
数据场 | 0~64 | 有效数据(0~8字节,扩展帧支持更多) |
CRC场 | 16 | 循环冗余校验,检测数据错误 |
应答场 | 2 | 接收节点确认收到正确数据 |
结束位 | 7 | 帧结束标志(高电平) |
扩展帧与标准帧的区别是仲裁场包含29位ID(11位标准ID+18位扩展ID),支持更多节点和更复杂的ID规划。
(2)位时序:如何保证不同节点的同步?
CAN总线是异步通信,节点间没有统一的时钟线,通过位时序实现同步。每个位被分为4个时间段:
- 同步段(SS):用于同步各节点时钟,长度1TQ(Time Quantum,时间量子);
- 传播段(PS):补偿信号传输延迟,长度1~8TQ;
- 相位缓冲段1(PBS1):可延长,用于重同步,长度1~8TQ;
- 相位缓冲段2(PBS2):等于PBS1或信号传播时间,长度1~8TQ。
波特率计算:波特率 = 1 / (总TQ),其中总TQ = SS + PS + PBS1 + PBS2(通常总TQ=8~25)。
例如:STM32的CAN时钟为36MHz,若总TQ=18,则波特率=36MHz / 18 = 2Mbps。
1.3 CAN节点的硬件组成
一个完整的CAN节点包括:
- MCU中的CAN控制器(如STM32的bxCAN):负责帧的组装、发送、接收和错误检测;
- CAN收发器(如SN65HVD230):将控制器输出的TTL电平转换为CAN总线的差分信号(CAN_H/CAN_L);
- 终端电阻(120Ω):接在总线两端,匹配阻抗,防止信号反射。
典型电路 :STM32的CAN_TX(如PB6)和CAN_RX(如PB5)连接到SN65HVD230的TXD和RXD,SN65HVD230的CAN_H和CAN_L接总线,两端各接120Ω电阻。
二、STM32的CAN外设:bxCAN控制器的强大之处
STM32的CAN控制器称为bxCAN(Basic Extended CAN),支持CAN 2.0A/B标准,不同系列(F1/F4/L4)的CAN外设功能略有差异,但核心结构一致。
2.1 bxCAN控制器的核心特性
- 支持标准帧(11位ID)和扩展帧(29位ID);
- 3个发送邮箱:可缓存3帧待发送数据,支持优先级发送;
- 2个接收FIFO:每个FIFO有3级深度,可缓存3帧接收数据,减轻CPU负担;
- 灵活的滤波功能:14个滤波器组,支持屏蔽位模式和列表模式,精准过滤目标报文;
- 多种中断源:发送完成、接收FIFO满、错误警告等,支持中断和DMA传输;
- 总线错误管理:检测总线错误(位错误、CRC错误等),自动进入错误状态。
2.2 bxCAN的硬件结构
理解bxCAN的结构有助于后续配置,核心模块包括:
(1)发送部分:3个发送邮箱
发送邮箱(Tx Mailbox)是发送数据的"缓冲区",每个邮箱包含:
- 标识符寄存器(TXID):存储标准/扩展ID;
- 数据长度和数据寄存器(TXDLR、TXDATA):存储数据长度和有效数据;
- 控制寄存器(TXCTRL):配置发送优先级、帧类型等。
发送流程:当邮箱状态为"空"时,CPU填充邮箱数据,设置"发送请求",CAN控制器会自动 arbitration(仲裁)并发送,发送完成后邮箱状态变为"空"。
(2)接收部分:2个FIFO + 14个滤波器
- 接收FIFO:FIFO0和FIFO1,用于缓存接收的有效报文。当FIFO中的报文数达到阈值(如3帧),会触发中断;
- 滤波器组:共14个(STM32F103),每个滤波器可配置为屏蔽位模式(按ID掩码过滤)或列表模式(精确匹配ID),过滤后的报文才会存入FIFO。
(3)波特率发生器
根据输入时钟(APB1时钟,最高36MHz for F1)和配置的位时序参数(预分频器、同步段、相位段),生成CAN总线需要的波特率。
三、STM32 CAN配置步骤:从CubeMX到代码实现
本节以STM32F103C8T6 和STM32CubeMX 6.6.0为例,结合HAL库,详细讲解CAN通信的配置流程。
3.1 硬件准备
-
开发板:STM32F103C8T6最小系统板;
-
CAN收发器:TJA1050模块(带120Ω终端电阻,可通过跳线选择);
-
接线:STM32的PA11(CAN_RX)接TJA1050的RXD,PA12(CAN_TX)接TJA1050的TXD,TJA1050的CAN_H和CAN_L接总线(若单节点测试,可短接CAN_H和CAN_L,或接2个节点形成回路)。
3.2 CubeMX配置步骤
步骤1:新建工程,选择芯片
打开CubeMX,搜索"STM32F103C8T6",创建新工程。
步骤2:配置时钟树
CAN外设挂载在APB1总线上,需确保APB1时钟正确(最高36MHz for F1):
- 配置RCC:HSE选择"Crystal/Ceramic Resonator"(8MHz外部晶振);
- 配置PLL:PLLMUL=×9,使系统时钟=72MHz;
- APB1 Prescaler=×2,使APB1时钟=36MHz(满足CAN时钟需求)。
步骤3:配置CAN外设
- 引脚配置:在Pinout视图中,将PA11配置为"CAN_RX",PA12配置为"CAN_TX";
- 配置CAN模式:在Configuration→Connectivity→CAN中,设置:
- Mode:"Normal"(正常模式,收发数据)或"Loopback"(回环模式,用于自测,发送的报文会自己接收);
- Prescaler(预分频器):根据波特率需求设置,例如"6"(后续计算波特率);
- 位时序参数(Bit Timing):
- Sync Jump Width (SJW):"1tq";
- Time Segment 1 (BS1):"8tq";
- Time Segment 2 (BS2):"3tq";
(总TQ = Prescaler × (SJW+BS1+BS2) = 6×(1+8+3)=72 → 波特率=36MHz/72=500kbps)
步骤4:配置中断(可选)
若需要通过中断处理收发,需配置NVIC:
- 在Configuration→NVIC中,勾选"CAN_RX0_IRQn"(FIFO0接收中断)和"CAN_TX_IRQn"(发送完成中断);
- 设置中断优先级(如抢占优先级1,子优先级0)。
步骤5:生成代码
设置工程路径和IDE(如MDK-ARM),点击"Generate Code"生成初始化代码。
3.3 HAL库CAN核心函数解析
生成的代码中,CAN相关核心函数位于can.c
和stm32f1xx_hal_can.h
,主要包括:
(1)初始化函数:MX_CAN_Init()
自动生成的初始化函数,配置CAN模式、波特率、滤波器等(后续需手动完善滤波器配置):
c
CAN_HandleTypeDef hcan;
static void MX_CAN_Init(void)
{
hcan.Instance = CAN1;
hcan.Init.Prescaler = 6;
hcan.Init.Mode = CAN_MODE_NORMAL; // 正常模式
hcan.Init.SyncJumpWidth = CAN_SJW_1TQ;
hcan.Init.TimeSeg1 = CAN_BS1_8TQ;
hcan.Init.TimeSeg2 = CAN_BS2_3TQ;
hcan.Init.TimeTriggeredMode = DISABLE;
hcan.Init.AutoBusOff = ENABLE; // 自动退出总线关闭状态
hcan.Init.AutoRetransmission = ENABLE; // 自动重传
hcan.Init.ReceiveFifoLocked = DISABLE;
hcan.Init.TransmitFifoPriority = DISABLE;
if (HAL_CAN_Init(&hcan) != HAL_OK)
{
Error_Handler();
}
}
(2)滤波器配置函数:CAN_Filter_Config()
滤波器需要手动配置,用于过滤目标ID的报文,示例:
c
void CAN_Filter_Config(void)
{
CAN_FilterTypeDef can_filter_st;
can_filter_st.FilterActivation = ENABLE; // 使能滤波器
can_filter_st.FilterMode = CAN_FILTERMODE_IDMASK; // 屏蔽位模式
can_filter_st.FilterScale = CAN_FILTERSCALE_32BIT; // 32位滤波器
// 配置滤波器ID和掩码(只接收ID=0x123的标准帧)
can_filter_st.FilterIdHigh = 0x123 << 5; // 标准ID的高16位(左移5位是因为标准ID占11位)
can_filter_st.FilterIdLow = 0x0000;
can_filter_st.FilterMaskIdHigh = 0xFFF << 5; // 掩码高16位(全1表示严格匹配)
can_filter_st.FilterMaskIdLow = 0x0000;
can_filter_st.FilterBank = 0; // 使用第0个滤波器组
can_filter_st.FilterFIFOAssignment = CAN_FILTER_FIFO0; // 匹配的报文存入FIFO0
if(HAL_CAN_ConfigFilter(&hcan, &can_filter_st) != HAL_OK)
{
Error_Handler();
}
}
滤波器配置说明:
- 屏蔽位模式:
FilterId
是目标ID,FilterMaskId
是掩码,掩码为1的位必须严格匹配,为0的位可忽略; - 列表模式:
FilterId
是需要匹配的ID列表,只有完全匹配的ID才会被接收; - 32位/16位模式:32位模式可同时过滤标准帧和扩展帧,16位模式适合单独过滤。
(3)发送函数:CAN_Send_Message()
封装发送流程,填充报文并启动发送:
c
uint8_t CAN_Send_Message(uint32_t id, uint8_t *data, uint8_t len)
{
CAN_TxHeaderTypeDef tx_header;
uint8_t tx_mailbox; // 存储使用的发送邮箱编号
// 配置发送头部
tx_header.StdId = id; // 标准ID
tx_header.ExtId = 0; // 扩展ID(不使用)
tx_header.RTR = CAN_RTR_DATA; // 数据帧
tx_header.IDE = CAN_ID_STD; // 标准帧
tx_header.DLC = len; // 数据长度(0~8)
tx_header.TransmitGlobalTime = DISABLE;
// 等待发送邮箱空闲
if(HAL_CAN_GetTxMailboxesFreeLevel(&hcan) == 0)
{
return 1; // 邮箱满,发送失败
}
// 发送数据
if(HAL_CAN_AddTxMessage(&hcan, &tx_header, data, &tx_mailbox) != HAL_OK)
{
return 2; // 发送失败
}
return 0; // 发送成功
}
(4)接收函数:CAN_Receive_Message()
从FIFO接收报文(查询方式):
c
uint8_t CAN_Receive_Message(uint32_t *id, uint8_t *data, uint8_t *len)
{
CAN_RxHeaderTypeDef rx_header;
// 检查FIFO0是否有数据
if(HAL_CAN_GetRxMessage(&hcan, CAN_RX_FIFO0, &rx_header, data) != HAL_OK)
{
return 1; // 接收失败
}
*id = rx_header.StdId; // 获取接收的ID
*len = rx_header.DLC; // 获取数据长度
return 0; // 接收成功
}
(5)中断服务函数
若使用中断接收,需实现中断服务函数和回调函数:
c
// FIFO0接收中断服务程序
void CAN1_RX0_IRQHandler(void)
{
HAL_CAN_IRQHandler(&hcan);
}
// 接收回调函数
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
{
uint32_t id;
uint8_t data[8], len;
// 接收数据
if(CAN_Receive_Message(&id, data, &len) == 0)
{
// 处理接收的数据(如打印)
printf("收到ID:0x%X, 数据:", id);
for(uint8_t i=0; i<len; i++)
{
printf("%02X ", data[i]);
}
printf("\r\n");
}
}
四、实战案例:STM32 CAN节点通信测试
本节通过两个案例(回环测试和双节点通信),验证CAN配置的正确性。
4.1 回环模式测试(单节点)
回环模式下,STM32发送的CAN报文会被自己接收,无需外部节点,适合调试:
步骤1:修改CAN模式为回环
在MX_CAN_Init()
中,将hcan.Init.Mode
改为CAN_MODE_LOOPBACK
。
步骤2:主函数代码
c
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init(); // 初始化串口(用于打印信息)
MX_CAN_Init();
// 配置滤波器
CAN_Filter_Config();
// 启动CAN
if(HAL_CAN_Start(&hcan) != HAL_OK)
{
Error_Handler();
}
// 使能FIFO0接收中断
if(HAL_CAN_ActivateNotification(&hcan, CAN_IT_RX_FIFO0_MSG_PENDING) != HAL_OK)
{
Error_Handler();
}
uint8_t send_data[8] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88};
uint8_t send_cnt = 0;
while (1)
{
// 每1秒发送一次数据
if(send_cnt >= 100)
{
if(CAN_Send_Message(0x123, send_data, 8) == 0)
{
printf("发送成功: ID=0x123, 数据:11 22 33 44 55 66 77 88\r\n");
}
else
{
printf("发送失败\r\n");
}
send_cnt = 0;
}
HAL_Delay(10);
send_cnt++;
}
}
测试结果
程序运行后,串口会打印"发送成功"和"收到数据"的信息,说明回环通信正常。
4.2 双节点通信(多节点)
准备两个STM32节点(Node A和Node B),配置相同波特率(500kbps),实现双向通信:
- Node A:发送ID=0x123的报文,接收ID=0x456的报文;
- Node B:发送ID=0x456的报文,接收ID=0x123的报文。
Node A核心代码(发送0x123,接收0x456)
c
// 滤波器配置(接收0x456)
can_filter_st.FilterIdHigh = 0x456 <<5;
can_filter_st.FilterMaskIdHigh = 0xFFF<<5;
// 主循环发送0x123
while(1)
{
CAN_Send_Message(0x123, send_data, 8);
HAL_Delay(1000);
}
Node B核心代码(发送0x456,接收0x123)
c
// 滤波器配置(接收0x123)
can_filter_st.FilterIdHigh = 0x123 <<5;
can_filter_st.FilterMaskIdHigh = 0xFFF<<5;
// 主循环发送0x456
while(1)
{
CAN_Send_Message(0x456, send_data, 8);
HAL_Delay(1000);
}
测试结果
两个节点通过CAN总线连接后,Node A会收到Node B发送的0x456报文,Node B会收到Node A发送的0x123报文,实现双向通信。
五、CAN高级特性:中断、DMA与错误处理
5.1 中断与DMA:提高通信效率
- 中断方式:适合报文数量少、实时性要求高的场景,接收/发送完成后立即触发中断,CPU及时处理;
- DMA方式:适合大量报文传输,通过DMA直接将数据搬运到内存,减少CPU干预(仅部分STM32系列支持,如F4)。
中断配置补充:除了接收FIFO中断,还可配置以下中断:
CAN_IT_TX_MAILBOX_EMPTY
:发送邮箱空(可用于连续发送);CAN_IT_ERROR
:总线错误(用于错误处理);CAN_IT_WAKEUP
:休眠唤醒(低功耗场景)。
5.2 CAN错误处理:保证总线可靠性
CAN控制器会检测总线错误,并根据错误计数进入不同状态:
- 主动错误状态:错误较少,可正常发送错误帧,通知其他节点;
- 被动错误状态:错误较多,只能接收,发送前需等待总线空闲;
- 总线关闭状态 :错误严重,无法参与通信,需软件复位(
HAL_CAN_ResetError
)恢复。
错误处理示例:
c
void CAN_Error_Handler(void)
{
if((hcan.Instance->ESR & CAN_ESR_BOFF) != 0) // 检测到总线关闭
{
printf("CAN总线关闭,尝试恢复...\r\n");
HAL_CAN_ResetError(&hcan); // 复位错误状态
HAL_CAN_Start(&hcan); // 重新启动CAN
}
}
// 在主循环中定期检查
while(1)
{
if(HAL_CAN_GetError(&hcan) != HAL_CAN_ERROR_NONE)
{
CAN_Error_Handler();
}
// ... 其他代码 ...
}
六、常见问题与解决方案:避坑指南
6.1 通信失败:波特率不匹配
现象:发送报文后,接收方收不到,或收到乱码。
原因:
- 两个节点的波特率计算错误,导致时钟不同步;
- 位时序参数(BS1、BS2、SJW)配置不一致。
解决方案:
- 重新计算波特率:确保
APB1时钟 / (Prescaler × (SJW+BS1+BS2))
在两个节点完全一致; - 推荐位时序参数:对于500kbps,可使用
Prescaler=6, SJW=1, BS1=8, BS2=3
(总TQ=12,36MHz/6/12=500kbps)。
6.2 接收不到报文:滤波器配置错误
现象:总线有数据,但本节点收不到。
原因:
- 滤波器未使能(
FilterActivation=DISABLE
); - 滤波器ID或掩码配置错误,目标ID被过滤;
- FIFO溢出(报文过多未及时处理,导致新报文被丢弃)。
解决方案:
- 检查滤波器使能状态,确保
FilterActivation=ENABLE
; - 用回环模式测试滤波器:发送目标ID,若能收到,说明滤波器配置正确;
- 及时处理FIFO数据,避免溢出(可增加FIFO满中断)。
6.3 总线错误:硬件或接线问题
现象:频繁进入总线错误状态,甚至总线关闭。
原因:
- CAN_H和CAN_L接反或短路;
- 终端电阻缺失或阻值错误(应为120Ω);
- 总线长度过长,超出对应波特率的最大距离;
- 电源干扰(未接地或纹波过大)。
解决方案:
- 用万用表检查CAN_H和CAN_L是否短路,接线是否正确;
- 确保总线两端各接一个120Ω终端电阻;
- 降低波特率(如从1Mbps降为500kbps),延长传输距离;
- 加强电源滤波,确保接地良好。
七、扩展
- CAN FD:支持更高数据速率(8Mbps)和更长数据帧(64字节),适合大数据量传输;
- CANopen协议:在CAN基础上的高层协议,定义了标准化的通信对象和设备模型;
- 多节点网络管理:学习如何设计CAN网络的ID分配、优先级规划和故障诊断。
掌握STM32 CAN通信,能为工业控制、汽车电子等领域的开发打下坚实基础------这是嵌入式工程师进阶的重要技能。建议结合实际硬件多做测试,尤其是滤波器配置和错误处理,才能真正理解CAN总线的精髓。