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的波特率。怎么算?

  1. 先确定Tq:我习惯把Tq设在125ns~250ns之间,这样采样点比较稳。取Tq=125ns,分频系数就是48MHz×125ns=6。嗯,分频系数6,整数,完美。
  2. 再算总Tq数:1/500kbps = 2μs,2μs / 125ns = 16个Tq。
  3. 分配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控制器有三种工作模式。很多新手上来就配成正常模式,结果一跑就出错,连是硬件问题还是软件问题都分不清。我建议你按这个顺序来:

  1. 先环回模式:自测,不依赖外部总线。
  2. 再静默模式:只听不说,确认总线状态。
  3. 最后正常模式:正式通信。
模式 发送 接收 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 初始化流程总览

说了这么多,我把整个初始化流程画成一张图,你照着走就行。

CAN初始化流程图 CAN初始化流程图 1. 使能CAN时钟 2. 配置CAN_TX/RX引脚 3. 配置波特率(Tq/BS1/BS2) 4. 配置过滤器(掩码/列表) 5. 选择工作模式 6. 启动CAN通信 调试建议 ① 先环回自测 ② 再静默监听 ③ 最后正常模式 常见错误 ① 波特率不匹配 ② 过滤器全拒 ③ 引脚配置错误

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),确保能收到所有报文。等通信调通了,再收紧过滤器。这样能快速排除“过滤器把数据挡了”这种低级错误。