资讯详情

资讯详情

CAN 位时序与 500kbps 参数推导:HAL 里看不到 0x001A0005,但它就在那

做车载 ECU 诊断工具链第一步绕不过去的是把 CAN 波特率配准。我用的是STM32F407ZGT6最小系统板, 它的CAN_BTR寄存器有0x001A0005这个值要把它从 42MHz 推导出来才敢说会 CAN。一、先确认你的时钟到底是多少CAN 挂在 APB1 上。STM32F407 在 168MHz 主频下APB1 分频是 4所以PCLK1 168MHz / 4 42MHz这一步必须实测不能凭记忆。时钟配置必须对根据具体型号的板子去配置不然会浪费很多时间在调试上。在 main() 里加这一行先把它打印出来//HAL 替你算42MHz 板上就是 42000000 uint32_t pclk1 HAL_RCC_GetPCLK1Freq(); printf(PCLK1%lu\r\n, (unsigned long)pclk1);看到PCLK142000000再往下走。不是这个数后面每个参数都是错的。二、一个 CAN 位由几段组成一个比特时间被切成若干时间份额tq由四段组成段作用本项目取值对应 HAL 字段/宏同步段 SYNC_SEG固定 1 tq用于硬同步1固定无需配置传播段 相位段1合称 TS1补偿总线传播延迟11TimeSeg1 CAN_BS1_11TQ相位段2TS2吸收时钟误差2TimeSeg2 CAN_BS2_2TQ同步跳转宽度 SJW重同步允许的最大调整量1不算在14TQ内SyncJumpWidth CAN_SJW_1TQ合计14TQ于是一个位的时间 14 tq × BRP预分频三、把 84 拆成 14 × 6CAN 位速率 500 kbps意味着一位占 1/500000 0.000002秒42,000,000 / 500,000 84 个 PCLK1 时钟周期也就是说一个 CAN 位要吃掉 84 个 APB1 时钟。而一位 14 tq所以每个 tq 6 个时钟84 14 × 6这 6 就是预分频值prescaler写进寄存器时要减 1BRP 字段 5。四、写进 BTR 的四个字段CAN_BTR的位定义RM0090 §32.9.5字段位值含义BRP[9:0]9:05预分频 6TS1[3:0]19:1610段长 11 tqTS2[2:0]22:201段长 2 tqSJW[1:0]25:2401 tq拼起来:BRP5 → 0b0000000101 TS110 → 0b1010 16 0x000A0000 TS21 → 0b001 20 0x00100000 SJW0 0x000A0000 | 0x00100000 | 0x5 0x001A0005这就是0x001A0005的全部来历。不是魔法数字是三个字段位移拼出来的。HAL 等价写法hcan2.Init.Prescaler 6; TimeSeg1 CAN_BS1_11TQ; TimeSeg2 CAN_BS2_2TQ; SyncJumpWidth CAN_SJW_1TQ;—— HAL 会在HAL_CAN_Init()里自动完成上面所有位移与拼装把0x001A0005写进CAN_BTR。你在.c里搜不到这个数是因为拼装发生在 HAL 内部stm32f4xx_hal_can.c的CAN_Init()。想看实际值在调试器里展开CAN2 → BTR或打印CAN2-BTR。五、采样点为什么是 85.7%采样点 在一位的第几个 tq 去读电平(1 11) / 14 12/14 85.7%CAN 行业惯例是把采样点放在 75%~87.5%。偏后是为了抵消总线传播延迟和节点间时钟偏差但也不能太靠后否则留给相位段2的余量不足时钟稍微偏一点就采错。我的取值是 12/14 85.7%落在惯用区间靠后的位置。六、代码我的can.c里实际写法/* 位时序42MHz / 500k 84 (1 11 2) × 6 采样点 12/14 85.7% */ #define CAN_PRESCALER_500K 6 #define CAN_PRESCALER_250K 12 #define CAN_PRESCALER_125K 24 #define CAN_BS1_TQ CAN_BS1_11TQ #define CAN_BS2_TQ CAN_BS2_2TQ #define CAN_SJW_TQ CAN_SJW_1TQ void CAN2_Init(uint8_t baud_sel, uint8_t loopback) { uint32_t presc; switch (baud_sel) { case CAN_250K: presc CAN_PRESCALER_250K; break; /* 12 */ case CAN_125K: presc CAN_PRESCALER_125K; break; /* 24 */ default: presc CAN_PRESCALER_500K; break; /* 6 */ } hcan2.Instance CAN2; hcan2.Init.Prescaler presc; /* → BTR.BRP presc-1 */ hcan2.Init.Mode loopback ? CAN_MODE_LOOPBACK : CAN_MODE_NORMAL; hcan2.Init.SyncJumpWidth CAN_SJW_TQ; /* → BTR.SJW 0 */ hcan2.Init.TimeSeg1 CAN_BS1_TQ; /* → BTR.TS1 10 */ hcan2.Init.TimeSeg2 CAN_BS2_TQ; /* → BTR.TS2 1 */ hcan2.Init.AutoBusOff ENABLE; /* → MCR.ABOM 1 */ hcan2.Init.AutoWakeUp DISABLE; hcan2.Init.AutoRetransmission ENABLE; hcan2.Init.ReceiveFifoLocked DISABLE; hcan2.Init.TransmitFifoPriority DISABLE; HAL_CAN_Init(hcan2); /* 内部自动拼出 0x001A0005 写进 CAN_BTR */ HAL_CAN_Start(hcan2); printf(PCLK1%lu\r\n, (unsigned long)HAL_RCC_GetPCLK1Freq()); }HAL 视角上面 4 个Init字段最终被HAL_CAN_Init()拼成CAN_BTR 0x001A0005。想亲眼看到在HAL_CAN_Init()之后打断点Keil 的 Register 窗口展开CAN2 → BTR即可或加一行printf(BTR0x%08lX\r\n, (unsigned long)CAN2-BTR);。这三个字段就是原来那三个寄存器Prescaler→BTR.BRPTimeSeg1/TimeSeg2→BTR.TS1/TS2AutoBusOff→MCR.ABOMMode→MCR.INRQ。HAL 在HAL_CAN_Init()里替你做了「进初始化 → 写 BTR → 退出初始化」这三步所以你看不到while (INAK)的等待。顺序不能反寄存器版的坑BTR只能在 INAK1 时写在 HAL 里被封装掉了但改波特率必须重新调HAL_CAN_Init()不能只改结构体——它不会自动生效。再次强调BTR只有在初始化模式INAK1下才能写正常模式下写进去不生效而且不报错——这是最容易浪费一晚上的坑。七、三个我踩过的坑坑 1只用 CAN2也必须开 CAN1 的时钟。STM32F407 有 CAN1 和 CAN2CAN1 是主机CAN2 是它的从机CAN2 的滤波器组和共享 SRAM 都要通过 CAN1 管理。我的can.h里已用宏封装#define CAN_CAN1_CLK_ENABLE() do { __HAL_RCC_CAN1_CLK_ENABLE(); } while(0) #define CAN_CAN2_CLK_ENABLE() do { __HAL_RCC_CAN2_CLK_ENABLE(); } while(0)在HAL_CAN_MspInit()里两个都调用。不开 CAN1 时钟一个报文都收不到而且不报任何错。坑 2CAN2 的接收中断必须用 CAN2 自己的向量。STM32F407 上 CAN1 和 CAN2 各有独立的中断向量CAN1_RX0_IRQn编号 20、CAN2_RX0_IRQn编号 64。用 CAN2 就必须开CAN2_RX0_IRQn、写CAN2_RX0_IRQHandler。我的can.h里用宏做了一层封装#define CAN_CAN2_RX_IRQn CAN2_RX0_IRQn #define CAN_CAN2_RX_IRQHandler CAN2_RX0_IRQHandler写成CAN1_RX0_IRQHandler永远进不来——现象是发得出去、一帧收不到。坑 3两端各一个 120Ω少一个就丢帧。CAN 是差分总线终端电阻用来消除信号反射。两个节点时两个 120Ω 分别在最远的两端并联后总线阻抗约 60Ω。我用万用表量过断电状态量 CANH 和 CANL 之间就是 60Ω 左右。只接一端会出现什么偶发丢帧然后随着错误计数上升变成完全收不到。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →