1. 硬件准备与电路设计
CAN通信节点的硬件搭建是项目的第一步,也是最容易踩坑的环节。我建议初学者从最基础的STM32F103C8T6最小系统板开始,搭配常见的TJA1050 CAN收发器模块。这种组合成本不到50元,但完全能满足实验需求。
1.1 元器件选型要点
TJA1050是工业级CAN收发器,工作电压4.5V-5.5V,最高支持1Mbps通信速率。我在实际项目中测试过,它在汽车电子环境下的抗干扰能力确实出色。选购时注意:
- 确认模块是否自带120Ω终端电阻(可通过跳线帽选择)
- 检查电源指示灯是否正常工作
- 推荐使用带DB9接口的模块,方便连接CAN分析仪
STM32F103的CAN引脚是固定的:
- CAN_RX → PA11
- CAN_TX → PA12
接线时一定要核对原理图,我有次把RX/TX接反导致调试了一整天。
1.2 电路连接示意图
完整的硬件连接应该包含:
code复制[STM32F103] [TJA1050模块]
PA11 -------- RX
PA12 -------- TX
3.3V -------- VCC
GND -------- GND
注意:TJA1050的VCC需要接5V!很多初学者误接3.3V会导致通信异常。STM32的I/O口是3.3V电平,但CAN收发器需要5V供电。
如果使用双节点测试,记得在总线两端各接一个120Ω终端电阻。我曾用三个节点组网时,忘记加终端电阻导致通信时好时坏,这个坑希望大家避开。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 开发环境搭建
2.1 工具链配置
推荐使用Keil MDK+STM32CubeMX组合:
- 安装Keil MDK-ARM(建议V5.36+)
- 从ST官网下载STM32F1的HAL库
- 安装STM32CubeMX并更新器件库
我习惯先用CubeMX生成基础工程,再导入Keil添加业务代码。这样做的好处是GPIO、时钟等底层配置可视化操作,不容易出错。
2.2 新建CubeMX工程
关键配置步骤:
- 选择STM32F103C8Tx器件
- 在Connectivity中启用CAN1
- 配置PA11为CAN_RX,PA12为CAN_TX
- 时钟树配置确保APB1时钟≤36MHz(CAN时钟源)
生成代码时记得勾选"Generate peripheral initialization as a pair of .c/.h files",这样代码结构更清晰。
3. CAN通信配置详解
3.1 初始化代码解析
CubeMX生成的初始化代码可能不够完善,建议手动优化:
c复制CAN_HandleTypeDef hcan;
void CAN_Init(void)
{
hcan.Instance = CAN1;
hcan.Init.Prescaler = 9; // APB1时钟36MHz时,4MHz/(9+1)=400kbps
hcan.Init.Mode = CAN_MODE_NORMAL;
hcan.Init.SyncJumpWidth = CAN_SJW_1TQ;
hcan.Init.TimeSeg1 = CAN_BS1_5TQ; // 相位段1
hcan.Init.TimeSeg2 = CAN_BS2_3TQ; // 相位段2
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();
}
}
波特率计算公式很重要:
code复制波特率 = APB1时钟 / (Prescaler * (TimeSeg1 + TimeSeg2 + 1))
比如APB1=36MHz,Prescaler=9,TimeSeg1=5,TimeSeg2=3时:
36MHz / (9*(5+3+1)) = 400kbps
3.2 过滤器配置技巧
CAN过滤器是初学者的难点,我的经验是:
c复制CAN_FilterTypeDef filter;
filter.FilterBank = 0;
filter.FilterMode = CAN_FILTERMODE_IDMASK;
filter.FilterScale = CAN_FILTERSCALE_32BIT;
filter.FilterIdHigh = 0x123 << 5; // 标准ID左移5位
filter.FilterIdLow = 0;
filter.FilterMaskIdHigh = 0xFFFF;
filter.FilterMaskIdLow = 0xFFFF;
filter.FilterFIFOAssignment = CAN_FILTER_FIFO0;
filter.FilterActivation = ENABLE;
HAL_CAN_ConfigFilter(&hcan, &filter);
这样配置后,只有ID=0x123的消息会被接收。调试阶段可以暂时关闭过滤器,等通信正常后再添加过滤规则。
4. 数据收发实战
4.1 发送数据帧最佳实践
发送消息时要注意数据对齐问题:
c复制uint8_t CAN_Send(uint32_t id, uint8_t* data, uint8_t len)
{
CAN_TxHeaderTypeDef header;
uint32_t mailbox;
header.StdId = id;
header.ExtId = 0;
header.IDE = CAN_ID_STD; // 标准帧
header.RTR = CAN_RTR_DATA;
header.DLC = len;
header.TransmitGlobalTime = DISABLE;
if(HAL_CAN_AddTxMessage(&hcan, &header, data, &mailbox) != HAL_OK)
{
return 1; // 发送失败
}
// 等待发送完成
while(HAL_CAN_GetTxMailboxesStatus(&hcan) & (1 << mailbox));
return 0;
}
实测发现,连续发送时建议检查邮箱状态,避免数据堆积。我通常会在发送函数中加入超时判断,防止死等。
4.2 接收中断处理
推荐使用中断方式接收数据:
c复制void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
{
CAN_RxHeaderTypeDef header;
uint8_t data[8];
HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &header, data);
if(header.StdId == 0x123 && header.DLC == 2)
{
// 处理数据
uint16_t value = (data[0] << 8) | data[1];
}
}
// 别忘了在main中启动中断
HAL_CAN_Start(&hcan);
HAL_CAN_ActivateNotification(&hcan, CAN_IT_RX_FIFO0_MSG_PENDING);
中断方式比轮询更高效,但要注意:
- 中断服务函数中不要做耗时操作
- 必要时使用信号量通知主程序
- 记得在CubeMX中开启CAN中断
5. 调试技巧与常见问题
5.1 必备调试工具
我强烈建议准备一个USB-CAN分析仪,比如周立功的CANPro或便宜的MCP2515模块。它们可以:
- 监控总线数据
- 模拟其他节点发送数据
- 检测总线错误帧
没有硬件工具时,可以用两个STM32开发板互发互收测试。我曾用LED闪烁频率来表示接收状态,虽然土但很实用。
5.2 典型故障排查
-
通信完全无反应:
- 检查终端电阻是否接好
- 用万用表测量CAN_H-CAN_L间电阻应为60Ω左右
- 确认TJA1050的VCC是5V
-
能发不能收:
- 检查过滤器配置
- 确认波特率设置一致
- 查看CAN控制器是否进入总线关闭状态
-
数据错乱:
- 检查发送和接收的DLC是否匹配
- 确认数据字节序处理正确
- 降低波特率测试是否改善
记得在代码中加入错误回调函数:
c复制void HAL_CAN_ErrorCallback(CAN_HandleTypeDef *hcan)
{
uint32_t err = HAL_CAN_GetError(hcan);
if(err & HAL_CAN_ERROR_BOF) {
// 总线关闭错误
}
}
6. 进阶应用实例
6.1 实现简单的CAN协议
基于CAN设计应用层协议时,我推荐这样的帧结构:
code复制| 帧类型(4bit) | 节点地址(4bit) | 数据长度(1字节) | 数据(0-8字节) | CRC(1字节) |
具体实现示例:
c复制#define FRAME_TYPE_DATA 0x01
#define FRAME_TYPE_ACK 0x02
typedef struct {
uint8_t type_addr; // 高4位类型,低4位地址
uint8_t len;
uint8_t data[8];
uint8_t crc;
} CANProtocol;
uint8_t CalcCRC(uint8_t* data, uint8_t len)
{
uint8_t crc = 0;
for(int i=0; i<len; i++) crc ^= data[i];
return crc;
}
6.2 多节点组网测试
搭建三个节点的测试环境:
- 数据采集节点(ID:0x101)
- 数据处理节点(ID:0x102)
- 数据显示节点(ID:0x103)
每个节点需要设置不同的过滤器:
c复制// 采集节点只接收0x102的应答
filter.FilterIdHigh = 0x102 << 5;
// 处理节点接收0x101和0x103
filter.FilterIdHigh = (0x101 << 5) | (0x103 << 5);
filter.FilterMaskIdHigh = 0x1FF << 5; // 检查所有ID位
这种架构模拟了典型的工业控制场景,我在智能家居项目中就采用过类似设计。
