一、系统概述
CAN(Controller Area Network,控制器局域网) 是一种高可靠、多主机的串行通信协议,广泛应用于工业控制、汽车电子、航空航天等领域。本例程基于STM32F103C8T6(Cortex-M3,72MHz)和HAL库,实现CAN总线通信,支持标准帧(11位标识符) 和扩展帧(29位标识符),核心功能包括:CAN控制器初始化、报文发送(单帧/多帧)、报文接收(中断/轮询)、错误处理(总线关闭、错误计数)。
二、硬件设计
2.1 核心组件
| 模块 | 型号/参数 | 功能说明 |
|---|---|---|
| 主控 | STM32F103C8T6(64KB Flash,20KB RAM) | CAN控制器(bxCAN)、报文处理、数据调度 |
| CAN收发器 | TJA1050(高速CAN,5V供电) | 转换TTL电平与CAN差分信号(CAN_H/CAN_L) |
| 终端电阻 | 120Ω(两端节点) | 消除信号反射,确保总线稳定 |
| 电源 | 5V USB供电(或3.3V LDO) | 为STM32、TJA1050供电 |
2.2 硬件连接
| 模块 | 引脚(STM32F103C8T6) | TJA1050引脚 | 说明 |
|---|---|---|---|
| CAN_TX | PA12(CAN1_TX) | TXD | 发送数据(TTL→CAN) |
| CAN_RX | PA11(CAN1_RX) | RXD | 接收数据(CAN→TTL) |
| TJA1050 | VCC(5V) | 5V | 电源正极 |
| GND | GND | 电源负极 | |
| CAN_H | CAN_H | 差分信号正线(接另一节点CAN_H) | |
| CAN_L | CAN_L | 差分信号负线(接另一节点CAN_L) | |
| STB(待机) | GND | 正常工作模式(低电平) |
三、软件设计(STM32CubeIDE + HAL库)
3.1 开发环境
-
IDE:STM32CubeIDE 1.13.0+
-
库:STM32Cube_FW_F1_V1.8.0(HAL库)
-
工具链:GCC ARM Embedded
3.2 CubeMX配置(关键步骤)
-
时钟配置:
- 选择HSE(8MHz),配置系统时钟72MHz,APB1时钟36MHz(CAN控制器挂载于APB1)。
-
CAN配置:
-
使能CAN1(全功能模式),配置参数:
-
模式:正常模式(Normal);
-
波特率:500kbps(计算公式:波特率=APB1时钟/(分频系数×(BS1+BS2+SJW)),如36MHz/(6×(5+3+1))=36MHz/54≈666kbps,需调整分频系数=8,BS1=5,BS2=3,SJW=1→36MHz/(8×(5+3+1))=36MHz/72=500kbps);
-
工作模式:自发自收(Loopback,测试用)或正常模式(双节点通信);
-
自动重传:使能(Auto Retransmission)。
-
-
-
GPIO配置:
-
PA11(CAN_RX):输入模式(下拉);
-
PA12(CAN_TX):复用推挽输出。
-
-
NVIC配置:
- 使能CAN1_RX0中断(接收中断),优先级配置为中等。
-
生成代码:选择"Generate peripheral initialization as a pair of .c/.h files per peripheral",生成工程。
3.3 核心代码实现
3.3.1 CAN初始化(自动生成+补充)
CubeMX生成的can.c文件包含CAN初始化函数,需补充波特率计算和过滤器配置:
c
#include "can.h"
#include "stm32f1xx_hal.h"
CAN_HandleTypeDef hcan1;
// CAN初始化(500kbps,正常模式)
void MX_CAN1_Init(void) {
hcan1.Instance = CAN1;
hcan1.Init.Prescaler = 8; // 分频系数=8(36MHz/8=4.5MHz)
hcan1.Init.Mode = CAN_MODE_NORMAL; // 正常模式(双节点通信)
hcan1.Init.SyncJumpWidth = CAN_SJW_1TQ; // 同步跳转宽度=1TQ
hcan1.Init.TimeSeg1 = CAN_BS1_5TQ; // BS1=5TQ
hcan1.Init.TimeSeg2 = CAN_BS2_3TQ; // BS2=3TQ
hcan1.Init.TimeTriggeredMode = DISABLE;
hcan1.Init.AutoBusOff = DISABLE;
hcan1.Init.AutoWakeUp = DISABLE;
hcan1.Init.AutoRetransmission = ENABLE; // 自动重传
hcan1.Init.ReceiveFifoLocked = DISABLE;
hcan1.Init.TransmitFifoPriority = DISABLE;
if (HAL_CAN_Init(&hcan1) != HAL_OK) {
Error_Handler();
}
// 配置CAN过滤器(接收所有标准帧)
CAN_FilterTypeDef filter;
filter.FilterBank = 0; // 过滤器组0
filter.FilterMode = CAN_FILTERMODE_IDMASK; // 掩码模式
filter.FilterScale = CAN_FILTERSCALE_32BIT; // 32位宽
filter.FilterIdHigh = 0x0000; // 标识符高16位(标准帧:11位ID)
filter.FilterIdLow = 0x0000; // 标识符低16位
filter.FilterMaskIdHigh = 0x0000; // 掩码高16位(0=不关心)
filter.FilterMaskIdLow = 0x0000; // 掩码低16位
filter.FilterFIFOAssignment = CAN_RX_FIFO0; // 接收FIFO0
filter.FilterActivation = ENABLE; // 使能过滤器
filter.SlaveStartFilterBank = 14; // 单CAN控制器,无需从机
if (HAL_CAN_ConfigFilter(&hcan1, &filter) != HAL_OK) {
Error_Handler();
}
// 启动CAN
if (HAL_CAN_Start(&hcan1) != HAL_OK) {
Error_Handler();
}
// 使能接收中断
if (HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING) != HAL_OK) {
Error_Handler();
}
}
3.3.2 CAN报文发送函数
c
// 发送CAN报文(标准帧,11位ID)
HAL_StatusTypeDef CAN_SendMessage(uint16_t id, uint8_t *data, uint8_t len) {
CAN_TxHeaderTypeDef tx_header;
uint32_t tx_mailbox;
tx_header.StdId = id; // 标准标识符(11位)
tx_header.ExtId = 0x00; // 扩展标识符(29位,不用)
tx_header.RTR = CAN_RTR_DATA; // 数据帧(非远程帧)
tx_header.IDE = CAN_ID_STD; // 标准帧
tx_header.DLC = len; // 数据长度(0~8字节)
tx_header.TransmitGlobalTime = DISABLE;
return HAL_CAN_AddTxMessage(&hcan1, &tx_header, data, &tx_mailbox);
}
3.3.3 CAN报文接收(中断回调)
c
// CAN接收中断回调函数(FIFO0消息 pending)
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan) {
CAN_RxHeaderTypeDef rx_header;
uint8_t rx_data[8];
if (HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &rx_header, rx_data) == HAL_OK) {
// 打印接收信息(示例:ID=0x123,数据=0x01 0x02)
printf("CAN Received: ID=0x%03X, Len=%d, Data=", rx_header.StdId, rx_header.DLC);
for (uint8_t i=0; i<rx_header.DLC; i++) {
printf("%02X ", rx_data[i]);
}
printf("\r\n");
}
}
3.3.4 主函数(发送测试报文)
c
#include "main.h"
#include "can.h"
#include "usart.h" // 用于printf调试(重定向USART1)
int main(void) {
HAL_Init();
SystemClock_Config(); // 时钟配置(72MHz)
MX_GPIO_Init();
MX_CAN1_Init(); // CAN初始化(500kbps)
MX_USART1_UART_Init(); // USART1初始化(115200bps,调试用)
uint8_t tx_data[8] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}; // 测试数据
while (1) {
// 发送CAN报文(ID=0x123,数据长度8字节)
if (CAN_SendMessage(0x123, tx_data, 8) == HAL_OK) {
printf("CAN Sent: ID=0x123, Data=01 02 03 04 05 06 07 08\r\n");
} else {
printf("CAN Send Failed!\r\n");
}
HAL_Delay(1000); // 每秒发送一次
}
}
参考代码 基于STM32的一个完整CAN通信例程 www.youwenfan.com/contentcsu/56567.html
四、关键问题与解决方案
4.1 波特率计算错误
-
现象:通信乱码或无数据。
-
原因:APB1时钟配置错误(如未配置为36MHz)、分频系数/BS1/BS2设置不当。
-
解决:
-
确认APB1时钟:
SystemClock_Config()中RCC_CFGR_SW_PLL,APB1分频系数=2(72MHz/2=36MHz); -
波特率公式:
波特率=APB1时钟/(Prescaler×(BS1+BS2+SJW)),如500kbps=36MHz/(8×(5+3+1))=36MHz/72=500kbps(Prescaler=8,BS1=5,BS2=3,SJW=1)。
4.2 接收不到数据
-
现象:发送成功但接收回调不触发。
-
原因:CAN过滤器配置错误(未接收目标ID)、终端电阻缺失(120Ω)、总线电平异常。
-
解决:
-
配置过滤器为"接收所有帧"(掩码=0x0000);
-
总线两端并联120Ω终端电阻;
-
用示波器测量CAN_H/CAN_L差分电压(显性电平≈3.5V,隐性电平≈2.5V)。
4.3 总线关闭(Bus Off)
-
现象:CAN控制器进入总线关闭状态(错误计数器超限)。
-
原因:总线短路、节点故障、波特率不匹配。
-
解决:
-
检查硬件连接(CAN_H/CAN_L是否短路);
-
调用
HAL_CAN_ResetErrorCounter(&hcan1)重置错误计数器; -
配置
hcan1.Init.AutoBusOff = ENABLE(自动退出总线关闭)。
五、测试与验证
5.1 单节点自发自收测试
-
配置:CubeMX中CAN模式设为"Loopback(自发自收)";
-
现象:发送报文后,接收回调触发,打印接收数据(ID和数据与发送一致)。
5.2 双节点通信测试
-
硬件:两个STM32节点(Node A、Node B),均按2.2节连接,总线两端接120Ω电阻;
-
步骤:
-
Node A发送ID=0x123的报文;
-
Node B接收并打印数据;
-
Node B回复ID=0x456的报文;
-
Node A接收并打印数据。
5.3 工具辅助测试
-
CAN分析仪(如USB-CAN适配器):监控总线报文,验证ID、数据、波特率正确性;
-
串口助手 :通过USART1打印调试信息(需重定向
printf到USART1)。
六、总结
本例程基于STM32F103和HAL库实现了CAN通信,核心是CAN控制器初始化、波特率配置、报文收发、中断接收。通过CubeMX快速配置+HAL库函数调用,可快速实现双节点或多节点通信,适用于工业传感器网络、电机控制等场景。