4、CAN通信实战:CAN报文收发、波特率配置、CAN滤波器设置、CAN错误处理
好,咱们进入正题。UWB车钥匙最终要跟车身通信,靠的就是CAN总线。这一章我带你手把手把CAN通信跑起来。说白了,就是让UWB定位模块和BCM(车身控制器)能互相听懂对方在说什么。
4.1 CAN报文收发——别光看,动手发一条
CAN报文的结构其实不复杂。标准帧11位ID,扩展帧29位ID。咱们UWB车钥匙场景里,一般用标准帧就够了。我习惯把ID规划成三层:功能码+节点地址+消息序号。
举个例子,UWB模块上报位置信息,我常用0x3A0这个ID。数据段放8个字节:前4个字节是X坐标,后4个字节是Y坐标。嗯,这里要注意,CAN的数据段是小端模式,低字节在前。
// 发送一帧CAN数据(基于STM32 HAL库)
CAN_TxHeaderTypeDef TxHeader;
uint8_t TxData[8] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
uint32_t TxMailbox;
TxHeader.StdId = 0x3A0; // 标准ID
TxHeader.ExtId = 0; // 扩展ID不用
TxHeader.IDE = CAN_ID_STD; // 标准帧
TxHeader.RTR = CAN_RTR_DATA; // 数据帧
TxHeader.DLC = 8; // 数据长度
// 检查是否有空闲邮箱
if (HAL_CAN_GetTxMailboxesFreeLevel(&hcan) > 0) {
HAL_CAN_AddTxMessage(&hcan, &TxHeader, TxData, &TxMailbox);
// 发送成功,TxMailbox返回使用的邮箱号
}
接收呢?我建议用中断或者FIFO。轮询方式在UWB这种实时性要求高的场景下,容易丢帧。我在项目里吃过这个亏——UWB定位数据每10ms来一次,轮询稍微慢一点,缓冲区就溢出了。
// CAN接收中断回调
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) {
// 判断ID,0x3A0是UWB位置数据
if (RxHeader.StdId == 0x3A0) {
// 提取坐标
uint32_t x_pos = RxData[0] | (RxData[1] << 8) | (RxData[2] << 16) | (RxData[3] << 24);
uint32_t y_pos = RxData[4] | (RxData[5] << 8) | (RxData[6] << 16) | (RxData[7] << 24);
// 更新位置信息
uwb_position_update(x_pos, y_pos);
}
}
}
4.2 波特率配置——别小看这个参数
波特率配置不对,总线直接瘫痪。你想想看,车上那么多ECU,每个都得用同一个速率说话。UWB车钥匙模块一般用500kbps,这是汽车行业最常用的速率之一。
波特率怎么算?说白了就是CAN时钟频率除以分频系数,再除以(同步段+传播段+相位缓冲段1+相位缓冲段2)。我习惯用STM32的CAN外设举例:
| 参数 | 说明 | 推荐值(500kbps) |
|---|---|---|
| CAN时钟 | APB1时钟频率 | 42MHz |
| 分频系数 | BRP分频 | 6 |
| 同步段 | 固定为1个Tq | 1 |
| 传播段 | 补偿物理延迟 | 2 |
| 相位缓冲段1 | 采样点位置 | 7 |
| 相位缓冲段2 | 采样点后 | 4 |
| 采样点 | (同步段+传播段+PS1)/总Tq | 约71.4% |
// STM32 CAN波特率配置示例
hcan.Instance = CAN1;
hcan.Init.Prescaler = 6; // 分频系数
hcan.Init.SyncJumpWidth = CAN_SJW_1TQ; // 同步跳转宽度
hcan.Init.TimeSeg1 = CAN_BS1_9TQ; // 传播段+相位缓冲段1 = 2+7=9
hcan.Init.TimeSeg2 = CAN_BS2_4TQ; // 相位缓冲段2 = 4
hcan.Init.TimeTriggeredMode = DISABLE;
hcan.Init.AutoBusOff = DISABLE;
hcan.Init.AutoWakeUp = DISABLE;
hcan.Init.AutoRetransmission = ENABLE; // 自动重发,我建议开启
hcan.Init.ReceiveFifoLocked = DISABLE;
hcan.Init.TransmitFifoPriority = DISABLE;
HAL_CAN_Init(&hcan);
4.3 CAN滤波器设置——别让UWB模块收到无关消息
CAN总线上跑着几百条消息,发动机转速、车门状态、空调温度...你想想看,UWB模块要是每条都处理,CPU早就累趴了。滤波器就是干这个的——只让UWB关心的消息进来。
我一般用掩码模式。说白了就是设置一个ID模板和一个掩码,掩码为1的位必须匹配,为0的位可以忽略。
// CAN滤波器配置 - 只接收0x3A0~0x3AF
CAN_FilterTypeDef sFilterConfig;
sFilterConfig.FilterBank = 0; // 使用滤波器组0
sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK; // 掩码模式
sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT; // 32位
sFilterConfig.FilterIdHigh = (0x3A0 << 5) >> 8; // ID高16位
sFilterConfig.FilterIdLow = (0x3A0 << 5) | CAN_ID_STD; // ID低16位+IDE位
sFilterConfig.FilterMaskIdHigh = (0x7F0 << 5) >> 8; // 掩码高16位
sFilterConfig.FilterMaskIdLow = (0x7F0 << 5) | CAN_ID_STD; // 掩码低16位
sFilterConfig.FilterFIFOAssignment = CAN_RX_FIFO0; // 关联到FIFO0
sFilterConfig.FilterActivation = ENABLE;
HAL_CAN_ConfigFilter(&hcan, &sFilterConfig);
嗯,这里有个坑。滤波器配置时,ID和掩码的位对齐很容易搞错。我建议你先把ID左移5位(因为标准帧ID占11位,左移后对齐到16位的高11位),然后再拆分高低字节。我曾经因为位没对齐,滤波器死活不工作,排查了一整天。
4.4 CAN错误处理——别等出事了再后悔
CAN总线错误分五种:位错误、填充错误、CRC错误、格式错误、应答错误。UWB车钥匙模块在车上跑,最容易遇到的是位错误和应答错误。
为什么会这样?你想想看,UWB模块可能装在车门把手附近,线束长,干扰大。信号质量不好,位错误就来了。应答错误呢?一般是目标节点没收到消息或者挂了。
我的做法是:监控错误状态,主动干预。
// CAN错误处理示例
void CAN_ErrorHandler(CAN_HandleTypeDef *hcan) {
HAL_CAN_StateTypeDef canState = HAL_CAN_GetState(hcan);
uint32_t errorCode = HAL_CAN_GetError(hcan);
// 检查是否进入Bus Off
if (canState == HAL_CAN_STATE_BUS_OFF) {
// 主动恢复,不要等自动恢复
HAL_CAN_Stop(hcan);
HAL_Delay(10); // 等待总线安静
HAL_CAN_Start(hcan);
// 记录错误日志
can_error_log("CAN Bus Off detected, manually recovered");
}
// 检查错误计数器
if (errorCode & HAL_CAN_ERROR_TX_LAST) {
// 发送错误,可能是总线短路或干扰
can_error_log("CAN TX error, check bus wiring");
}
if (errorCode & HAL_CAN_ERROR_ACK) {
// 应答错误,目标节点可能不在线
can_error_log("CAN ACK error, target node offline");
}
}
还有一个实用技巧:在UWB模块的CAN驱动里加一个错误统计任务。每秒钟统计一次错误计数,如果增长太快,就主动降低发送频率,或者切换到备用通信通道。说白了,就是别让错误计数器爆掉。
好了,这一章的内容就这些。CAN通信看起来简单,但实际项目中坑不少。你把这些代码和配置吃透了,UWB车钥匙的CAN通信部分基本就稳了。下一章咱们聊聊UWB定位数据怎么通过CAN总线传给BCM,以及怎么保证实时性。