1. STM32F103RC CAN总线开发环境搭建
在开始CAN总线开发前,我们需要准备完整的硬件和软件环境。STM32F103RC作为一款经典的Cortex-M3内核微控制器,其CAN控制器支持CAN 2.0A和2.0B协议标准。
1.1 硬件准备清单
- STM32F103RC开发板(需带CAN收发器)
- CAN分析仪(如PCAN-USB、CANable等)
- 120Ω终端电阻(双绞线两端各一个)
- 杜邦线若干
- USB转串口调试工具
注意:CAN总线必须两端接120Ω终端电阻,否则会导致信号反射影响通信质量。这是新手最容易忽略的问题。
1.2 软件工具链配置
- STM32CubeMX:6.0+版本(图形化配置工具)
- Keil MDK-ARM:5.30+(或IAR Embedded Workbench)
- CAN分析工具:PCAN-View/CANalyzer(或开源替代品)
- 串口调试助手:如SecureCRT、Putty等
安装STM32CubeMX时,务必勾选F1系列的HAL库支持。建议同时安装对应版本的STM32CubeF1软件包,其中包含所有外设驱动和示例代码。
2. CubeMX CAN外设配置详解
2.1 时钟树配置
CAN总线对时钟精度要求较高,建议配置步骤:
- 在RCC配置中启用外部高速晶振(HSE)
- 系统时钟设为72MHz(最大频率)
- CAN时钟源选择PCLK1(36MHz)
- 确保APB1 Prescaler为2(PCLK1=36MHz)
c复制// 生成的时钟配置代码示例
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
// HSE配置
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
// ...其他时钟配置
2.2 CAN参数配置
在Connectivity选项卡中选择CAN1,关键参数设置:
| 参数项 | 推荐值 | 说明 |
|---|---|---|
| Mode | Normal | 正常工作模式 |
| Prescaler | 9 | 波特率分频系数 |
| Time Quantum | 15tq | 一个位时间的时间量子数 |
| Time Seg1 | 13tq | 相位缓冲段1 |
| Time Seg2 | 2tq | 相位缓冲段2 |
| SJW | 1tq | 同步跳转宽度 |
波特率计算公式:
code复制CAN波特率 = APB1时钟 / (Prescaler * (TimeSeg1 + TimeSeg2 + 1))
= 36MHz / (9 * (13 + 2 + 1)) = 250kbps
2.3 GPIO引脚配置
CAN默认引脚映射:
- CAN_RX: PA11
- CAN_TX: PA12
在CubeMX中配置这两个引脚为:
- Mode: Alternate Function Push Pull
- Pull-up/Pull-down: Pull-up
- Speed: High
实际项目中我曾遇到CAN通信不稳定的情况,后来发现是GPIO速度设为Low导致边沿速率不足。建议所有CAN相关GPIO都设为High Speed。
3. CAN通信代码实现
3.1 初始化代码解析
CubeMX生成的CAN初始化代码需要做以下关键修改:
c复制CAN_HandleTypeDef hcan;
static void MX_CAN_Init(void) {
hcan.Instance = CAN1;
hcan.Init.Prescaler = 9;
hcan.Init.Mode = CAN_MODE_NORMAL;
hcan.Init.SyncJumpWidth = CAN_SJW_1TQ;
hcan.Init.TimeSeg1 = CAN_BS1_13TQ;
hcan.Init.TimeSeg2 = CAN_BS2_2TQ;
hcan.Init.TimeTriggeredMode = DISABLE;
hcan.Init.AutoBusOff = DISABLE;
hcan.Init.AutoWakeUp = DISABLE;
hcan.Init.AutoRetransmission = ENABLE; // 自动重传
hcan.Init.ReceiveFifoLocked = DISABLE;
hcan.Init.TransmitFifoPriority = DISABLE;
if (HAL_CAN_Init(&hcan) != HAL_OK) {
Error_Handler();
}
// 过滤器配置 - 接收所有帧
CAN_FilterTypeDef sFilterConfig;
sFilterConfig.FilterBank = 0;
sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK;
sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT;
sFilterConfig.FilterIdHigh = 0x0000;
sFilterConfig.FilterIdLow = 0x0000;
sFilterConfig.FilterMaskIdHigh = 0x0000;
sFilterConfig.FilterMaskIdLow = 0x0000;
sFilterConfig.FilterFIFOAssignment = CAN_RX_FIFO0;
sFilterConfig.FilterActivation = ENABLE;
sFilterConfig.SlaveStartFilterBank = 14;
if (HAL_CAN_ConfigFilter(&hcan, &sFilterConfig) != HAL_OK) {
Error_Handler();
}
}
3.2 中断接收实现
在CubeMX中启用CAN中断后,需要实现以下回调函数:
c复制// 接收中断回调
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan) {
CAN_RxHeaderTypeDef RxHeader;
uint8_t RxData[8];
if(HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &RxHeader, RxData) == HAL_OK) {
// 处理接收到的数据
printf("ID: 0x%03X, DLC: %d, Data: ", RxHeader.StdId, RxHeader.DLC);
for(int i=0; i<RxHeader.DLC; i++) {
printf("%02X ", RxData[i]);
}
printf("\n");
}
}
// 错误中断处理
void HAL_CAN_ErrorCallback(CAN_HandleTypeDef *hcan) {
uint32_t error = HAL_CAN_GetError(hcan);
printf("CAN Error: 0x%lX\n", error);
}
3.3 发送函数封装
建议封装一个更易用的发送函数:
c复制HAL_StatusTypeDef CAN_SendMsg(uint32_t id, uint8_t* data, uint8_t len, uint32_t timeout) {
CAN_TxHeaderTypeDef TxHeader;
uint32_t TxMailbox;
TxHeader.StdId = id;
TxHeader.ExtId = 0;
TxHeader.RTR = CAN_RTR_DATA;
TxHeader.IDE = CAN_ID_STD;
TxHeader.DLC = len > 8 ? 8 : len;
TxHeader.TransmitGlobalTime = DISABLE;
return HAL_CAN_AddTxMessage(&hcan, &TxHeader, data, &TxMailbox);
}
4. 常见问题与调试技巧
4.1 CAN通信失败排查步骤
-
物理层检查
- 测量CANH-CANL间电阻(应为60Ω左右)
- 检查接线是否正确(CANH接CANH,CANL接CANL)
- 确保终端电阻已正确连接
-
信号质量检查
- 用示波器观察波形:
- 隐性电平:~2.5V
- 显性电平:CANH~3.5V, CANL~1.5V
- 检查是否有明显振铃或畸变
- 用示波器观察波形:
-
软件配置检查
- 确认波特率与所有节点一致
- 检查过滤器设置是否过于严格
- 验证时钟配置是否正确
4.2 典型错误代码处理
| 错误代码 | 可能原因 | 解决方案 |
|---|---|---|
| HAL_CAN_ERROR_NONE | 无错误 | - |
| 0x00000001 | 位填充错误 | 检查波特率配置和信号质量 |
| 0x00000002 | 格式错误 | 检查帧格式设置 |
| 0x00000004 | ACK错误 | 检查终端电阻和节点连接 |
| 0x00000008 | 隐性位错误 | 检查总线短路情况 |
| 0x00000010 | 显性位错误 | 检查总线对地/电源短路 |
4.3 性能优化建议
-
中断优化
- 在接收中断中只做必要的数据拷贝
- 将数据处理移到主循环或低优先级任务
- 使用DMA接收(F1系列不支持CAN DMA)
-
过滤器配置技巧
- 合理使用掩码模式减少软件过滤负担
- 对重要ID使用列表模式精确过滤
- 动态调整过滤器以适应多协议需求
-
总线负载管理
- 监控错误计数器(HAL_CAN_GetError)
- 实现自动重连机制
- 添加看门狗定时器检测通信异常
5. 进阶应用实例
5.1 CANopen协议移植
在基础CAN驱动上实现CANopen协议栈:
- 下载CanFestival开源协议栈
- 适配STM32硬件抽象层:
c复制// 硬件相关函数实现示例 void setTimer(TIMEVAL value) { // 使用STM32硬件定时器 } UNS8 canSend(CAN_PORT notused, Message *m) { // 调用HAL_CAN_AddTxMessage } - 配置对象字典
- 实现NMT状态机
5.2 多节点组网测试
搭建包含3个节点的测试网络:
-
节点1:STM32F103RC作为主节点
- 周期发送同步帧
- 处理其他节点的PDO
-
节点2:STM32F407作为从节点
- 实现心跳协议
- 响应SDO请求
-
节点3:PC通过USB-CAN适配器
- 使用CANalyzer监控总线
- 模拟异常情况测试
测试项目应包含:
- 总线负载测试(逐步增加报文频率)
- 错误注入测试(人工制造位错误)
- 热插拔测试(运行时插拔节点)
5.3 低功耗设计
当设备需要电池供电时:
- 配置CAN为睡眠模式:
c复制
HAL_CAN_RequestSleep(&hcan); - 使用唤醒中断:
c复制void HAL_CAN_WakeUpFromRxMsgCallback(CAN_HandleTypeDef *hcan) { // 唤醒后重新初始化CAN } - 动态调整波特率(降低功耗)
- 配合STM32低功耗模式使用
我在实际项目中通过合理配置CAN睡眠模式,使设备待机电流从15mA降至2mA,显著延长了电池寿命。关键是要平衡响应速度和功耗需求。
