1. CAN通信基础与STM32实战准备
第一次接触CAN总线是在大四的机器人比赛中,当时需要控制四个大疆的3508电机。看着队友用STM32通过两根线就能同时指挥这么多电机,感觉特别神奇。后来才知道,这就是CAN总线的威力。
CAN(Controller Area Network)是一种多主机工作的串行通信协议,最早由德国博世公司开发用于汽车电子。它的核心优势在于:
- 多设备协同:一条总线上可以挂载多个设备,每个设备都能主动发送数据
- 优先级仲裁:通过ID标识优先级,冲突时高优先级设备自动获得总线控制权
- 强抗干扰:差分信号传输(CAN_H和CAN_L)能有效抑制共模干扰
在STM32CubeMX中配置CAN时,有三个关键参数需要特别注意:
- 波特率设置:常见的有125Kbps、250Kbps、500Kbps和1Mbps。根据总线长度选择,我的经验是1米内可以用1Mbps,超过5米建议降到125Kbps
- 工作模式:通常选择Normal模式,调试时可以用Loopback模式自测
- 自动重传:建议开启,确保重要数据不会因偶尔的干扰丢失
硬件连接有个容易踩的坑:CAN总线两端必须接120Ω终端电阻!我第一次调试时因为漏接这个电阻,通信距离连50cm都不到,加上电阻后直接稳定传输10米。
2. STM32CubeMX配置详解
打开CubeMX后,按照这个流程操作:
2.1 基础参数配置
- 在Pinout界面启用CAN1
- Configuration标签页设置:
- Mode: Normal
- Prescaler: 根据时钟计算(比如APB1时钟54MHz,要配500Kbps时填6)
- Time Quanta in Bit Segment 1: 通常设13-15个时间单元
- Time Quanta in Bit Segment 2: 设2-3个时间单元
- ReSynchronization Jump Width: 设为1
小技巧:点击"Bit Timing Calculation"可以自动计算参数,但要注意实际测试时可能需要微调
2.2 过滤器配置
过滤器是CAN的"安检系统",决定接收哪些ID的消息。我的推荐配置:
CAN_FilterTypeDef filter; filter.FilterBank = 0; filter.FilterMode = CAN_FILTERMODE_IDMASK; filter.FilterScale = CAN_FILTERSCALE_32BIT; filter.FilterIdHigh = 0x0000; // 不筛选ID高位 filter.FilterIdLow = 0x0000; // 不筛选ID低位 filter.FilterMaskIdHigh = 0x0000; // 不屏蔽任何位 filter.FilterMaskIdLow = 0x0000; filter.FilterFIFOAssignment = CAN_RX_FIFO0; filter.FilterActivation = ENABLE; HAL_CAN_ConfigFilter(&hcan1, &filter);2.3 中断设置
在NVIC Settings中启用这两个中断:
- CAN1 RX0 interrupts
- CAN1 SCE interrupt
这样当收到消息或发生错误时,CPU会立即处理。
3. 核心代码实现与调试
3.1 发送函数封装
这个发送函数支持发送4个short类型数据:
void CAN_Send_Msg(CAN_HandleTypeDef *hcan, uint32_t id, short i1, short i2, short i3, short i4) { static CAN_TxHeaderTypeDef tx_header; uint8_t tx_data[8]; tx_header.StdId = id; // 标准ID tx_header.IDE = CAN_ID_STD; // 标准格式 tx_header.RTR = CAN_RTR_DATA; // 数据帧 tx_header.DLC = 8; // 8字节数据 // 将short拆分为两个byte tx_data[0] = i1 >> 8; tx_data[1] = i1; tx_data[2] = i2 >> 8; tx_data[3] = i2; tx_data[4] = i3 >> 8; tx_data[5] = i3; tx_data[6] = i4 >> 8; tx_data[7] = i4; HAL_CAN_AddTxMessage(hcan, &tx_header, tx_data, (uint32_t *)CAN_TX_MAILBOX0); }3.2 接收回调函数
接收处理放在中断回调中:
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan) { CAN_RxHeaderTypeDef rx_header; uint8_t rx_data[8]; HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &rx_header, rx_data); // 示例:处理电机反馈数据 if(rx_header.StdId == 0x201) { // 电机1的反馈ID int16_t angle = (rx_data[0] << 8) | rx_data[1]; int16_t speed = (rx_data[2] << 8) | rx_data[3]; // 更新电机状态... } }3.3 调试技巧
- 逻辑分析仪抓包:用Saleae观察CAN_H和CAN_L波形,检查波特率是否准确
- 终端电阻检测:用万用表测量CAN_H和CAN_L间电阻,应为60Ω左右(两个120Ω并联)
- 错误计数器:调用
HAL_CAN_GetError()查看错误状态,常见问题:- 波特率不匹配:错误计数器快速增加
- 接线错误:显性电平电压异常(正常CAN_H=3.5V,CAN_L=1.5V)
4. 多电机控制实战
以控制四个大疆电机为例,需要实现:
4.1 电机ID分配
| 电机 | 控制ID | 反馈ID |
|---|---|---|
| 电机1 | 0x200 | 0x201 |
| 电机2 | 0x200 | 0x202 |
| 电机3 | 0x200 | 0x203 |
| 电机4 | 0x200 | 0x204 |
4.2 控制指令发送
// 发送速度控制指令 void Motor_Speed_Ctrl(CAN_HandleTypeDef *hcan, int16_t spd1, int16_t spd2, int16_t spd3, int16_t spd4) { CAN_Send_Msg(hcan, 0x200, spd1, spd2, spd3, spd4); }4.3 数据解析优化
使用联合体提升数据处理效率:
typedef union { uint8_t data[8]; struct { int16_t angle; int16_t speed; int16_t torque; int16_t temp; } motor; } MotorData; // 在接收回调中: MotorData md; memcpy(md.data, rx_data, 8); printf("角度:%d 速度:%d\n", md.motor.angle, md.motor.speed);5. 典型故障排查案例
5.1 通信时断时续
现象:电机偶尔不响应控制指令
排查:
- 用示波器检查电源电压,发现电机启动时电压跌落严重
- 在CAN收发器电源端增加100μF电容
- 改用粗线径电源线降低压降
5.2 数据错乱
现象:收到的角度值突然跳变
解决:
- 检查发现未启用CAN硬件CRC校验
- 在CubeMX中启用
hcan1.Init.AutoRetransmission = ENABLE - 在代码中添加软件校验:
if(HAL_CALC_CRC(rx_data, 8) != expected_crc) { // 请求重发 }5.3 总线死锁
现象:所有设备无法通信
应急处理:
- 断开所有设备电源
- 用万用表检查总线是否有短路
- 逐个设备上电,定位故障节点
记得第一次调通CAN控制四个电机同步转动时,那种成就感至今难忘。虽然中途因为接线问题烧过一个CAN收发器,但这些经验让我深刻理解了差分信号和总线仲裁的精妙设计。