4. CAN初始化编程:波特率计算、过滤器与工作模式
好,咱们进入CAN模块最核心的初始化环节。说实话,我见过不少工程师,硬件焊好了,信号也量了,结果程序一跑就是收不到数据。查到最后,十有八九是初始化参数配错了。这一节,我把波特率计算、过滤器设置、工作模式选择这三个硬骨头一次讲透。
4.1 波特率计算:Tq、BS1与BS2
CAN总线的波特率,说白了就是决定1秒钟能传多少位数据。但CMS32里配波特率不是直接写个“500k”就完事的,你得先理解三个基本概念:Tq(时间量子)、BS1(时间段1)、BS2(时间段2)。
核心公式:
波特率 = 1 / (Tq × (1 + BS1 + BS2))
其中 Tq = 系统时钟 / 分频系数
举个例子。假设系统时钟是48MHz,我想要500kbps的波特率。怎么算?
- 先确定Tq:我习惯把Tq设在125ns~250ns之间,这样采样点比较稳。取Tq=125ns,分频系数就是48MHz×125ns=6。嗯,分频系数6,整数,完美。
- 再算总Tq数:1/500kbps = 2μs,2μs / 125ns = 16个Tq。
- 分配BS1和BS2:总Tq数 = 1(同步段)+ BS1 + BS2。所以BS1+BS2=15。我个人习惯让采样点在70%~80%之间,取BS1=10,BS2=5,采样点位置 = (1+10)/16 = 68.75%。
你看,算出来刚刚好。但实际项目中没这么顺,我遇到过晶振有偏差的情况,那时候就得微调分频系数或者BS1/BS2的值。
| 参数 | 说明 | 典型值 |
|---|---|---|
| Tq | 时间量子,CAN控制器的最小时间单位 | 125ns~250ns |
| BS1 | 时间段1,决定采样点位置 | 4~12 Tq |
| BS2 | 时间段2,采样后的补偿时间 | 2~8 Tq |
| 采样点 | 推荐70%~80% | 75%左右最佳 |
我曾经踩过的坑:有一次在工控项目里,我图省事直接用了默认的BS1=8、BS2=4,结果总线长度超过50米就开始丢帧。后来换成BS1=10、BS2=5,采样点往后挪了挪,问题就解决了。记住,总线越长,采样点越要靠后。
4.2 过滤器模式设置:掩码模式与列表模式
过滤器是CAN控制器的“门卫”。你想想看,总线上那么多节点在发数据,如果每个报文都进中断,CPU得累死。过滤器的作用就是——只放行你关心的报文。
CMS32的CAN模块支持两种过滤模式:掩码模式和列表模式。我分别说一下。
4.2.1 掩码模式
掩码模式,说白了就是“模糊匹配”。你设一个ID,再设一个掩码。掩码位为1的,必须精确匹配;掩码位为0的,爱啥啥,不管。
// 掩码模式配置示例
// 只接收ID的高11位为0x7FF的报文
CAN_FilterConfig.FilterMode = CAN_FILTERMODE_MASK;
CAN_FilterConfig.FilterID = 0x7FF; // 期望的ID
CAN_FilterConfig.FilterMask = 0x7FF; // 掩码:全1,精确匹配
CAN_FilterConfig.FilterScale = CAN_FILTERSCALE_16BIT;
我在项目中常用掩码模式来过滤一组设备。比如,所有电机控制器的ID范围是0x100~0x10F,那我设掩码为0x1F0,只匹配高5位,低4位随便。这样一条过滤规则就能覆盖16个节点。
4.2.2 列表模式
列表模式就是“精确匹配”。你往列表里写几个ID,只有这些ID的报文能通过。好处是精准,坏处是——列表长度有限,CMS32每个过滤器组最多存4个ID。
// 列表模式配置示例
// 只接收ID为0x100、0x200、0x300、0x400的报文
CAN_FilterConfig.FilterMode = CAN_FILTERMODE_LIST;
CAN_FilterConfig.FilterID[0] = 0x100;
CAN_FilterConfig.FilterID[1] = 0x200;
CAN_FilterConfig.FilterID[2] = 0x300;
CAN_FilterConfig.FilterID[3] = 0x400;
我的建议:如果总线上节点少(比如10个以内),用列表模式,简单粗暴。如果节点多或者ID范围不固定,用掩码模式,灵活。我一般先画个ID分配表,再决定用哪种模式。
4.3 工作模式配置:正常、环回与静默
CMS32的CAN控制器有三种工作模式。很多新手上来就配成正常模式,结果一跑就出错,连是硬件问题还是软件问题都分不清。我建议你按这个顺序来:
- 先环回模式:自测,不依赖外部总线。
- 再静默模式:只听不说,确认总线状态。
- 最后正常模式:正式通信。
| 模式 | 发送 | 接收 | ACK应答 | 适用场景 |
|---|---|---|---|---|
| 正常模式 | √ | √ | √ | 正式通信 |
| 环回模式 | √(内部回环) | √(自己收自己发的) | × | 自测、调试 |
| 静默模式 | × | √ | × | 监听总线、故障分析 |
// 工作模式配置示例
// 先配成环回模式,确认发送接收通路正常
CAN_InitStructure.CAN_Mode = CAN_MODE_LOOPBACK;
CAN_Init(CAN1, &CAN_InitStructure);
// 自测通过后,切到正常模式
CAN_InitStructure.CAN_Mode = CAN_MODE_NORMAL;
CAN_Init(CAN1, &CAN_InitStructure);
注意:环回模式下,CAN控制器不会发送ACK应答,所以如果你用CAN分析仪抓包,是看不到环回模式发出的报文的。别问我怎么知道的——当年我拿着分析仪蹲了半小时,愣是没抓到数据,还以为芯片坏了。
4.4 初始化流程总览
说了这么多,我把整个初始化流程画成一张图,你照着走就行。
4.5 完整初始化代码示例
最后,我给你一个完整的初始化函数。这是我项目里一直在用的模板,你直接拿去改参数就行。
void CAN_Init_Config(void)
{
CAN_InitTypeDef CAN_InitStructure;
CAN_FilterConfigTypeDef CAN_FilterConfig;
GPIO_InitTypeDef GPIO_InitStructure;
// 1. 使能时钟
RCC_EnableAPB2PeriphClock(RCC_APB2_PERIPH_CAN1, TRUE);
RCC_EnableAPB2PeriphClock(RCC_APB2_PERIPH_GPIOB, TRUE);
// 2. 配置引脚:PB8=CAN_RX, PB9=CAN_TX
GPIO_InitStructure.GPIO_Pin = GPIO_PIN_8 | GPIO_PIN_9;
GPIO_InitStructure.GPIO_Mode = GPIO_MODE_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_SPEED_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
// 3. 波特率配置:500kbps
CAN_DeInit(CAN1);
CAN_StructInit(&CAN_InitStructure);
CAN_InitStructure.CAN_TTCM = DISABLE; // 时间触发模式
CAN_InitStructure.CAN_ABOM = ENABLE; // 自动离线管理
CAN_InitStructure.CAN_AWUM = ENABLE; // 自动唤醒
CAN_InitStructure.CAN_NART = DISABLE; // 自动重传
CAN_InitStructure.CAN_RFLM = DISABLE; // 接收FIFO锁定
CAN_InitStructure.CAN_TXFP = DISABLE; // 发送优先级
CAN_InitStructure.CAN_Mode = CAN_MODE_LOOPBACK; // 先环回
CAN_InitStructure.CAN_SJW = CAN_SJW_1TQ; // 同步跳转宽度
CAN_InitStructure.CAN_BS1 = CAN_BS1_10TQ; // BS1=10
CAN_InitStructure.CAN_BS2 = CAN_BS2_5TQ; // BS2=5
CAN_InitStructure.CAN_Prescaler = 6; // 分频系数
CAN_Init(CAN1, &CAN_InitStructure);
// 4. 过滤器配置:掩码模式,接收所有ID
CAN_FilterConfig.CAN_FilterNumber = 0;
CAN_FilterConfig.CAN_FilterMode = CAN_FILTERMODE_MASK;
CAN_FilterConfig.CAN_FilterScale = CAN_FILTERSCALE_32BIT;
CAN_FilterConfig.CAN_FilterIdHigh = 0x0000;
CAN_FilterConfig.CAN_FilterIdLow = 0x0000;
CAN_FilterConfig.CAN_FilterMaskIdHigh = 0x0000;
CAN_FilterConfig.CAN_FilterMaskIdLow = 0x0000;
CAN_FilterConfig.CAN_FilterFIFOAssignment = CAN_FIFO0;
CAN_FilterConfig.CAN_FilterActivation = ENABLE;
CAN_FilterConfig(CAN1, &CAN_FilterConfig);
// 5. 启动CAN
CAN_Start(CAN1);
}
一个小技巧:调试阶段,我习惯把过滤器设成“全通”(掩码全0),确保能收到所有报文。等通信调通了,再收紧过滤器。这样能快速排除“过滤器把数据挡了”这种低级错误。