资讯详情

资讯详情

DSP 28069 CAN通讯实战:DCAN模块与邮箱机制避坑指南

简介面向使用TI的C28x系列DSP_28069进行嵌入式开发的工程师这份CAN通讯代码例程提供了从底层硬件配置到上层协议处理的完整参考可直接在CCS集成开发环境中学习与复用。压缩包共含2个文件分别是1个C语言头文件.h和1个C源文件.c整体大小仅4KB文件精简便于快速阅读和移植到实际项目中。代码覆盖IO口配置、波特率分频、滤波器设置、报文发送接收、中断服务及故障处理等关键环节其中发送函数会完成ID选择、数据填充与错误检查接收函数配合中断服务例程实时解析总线帧滤波器还能按特定ID过滤消息降低CPU负担帮助开发者理清CAN控制器在28069上的初始化与收发流程。目前已有1619人学习下载对于希望快速掌握DSP与CAN总线通信原理及编码方法的入门与中级开发者而言是一份轻量而实用的参考例程能够有效缩短CAN通信模块的开发周期。1. 先说结论28069 的 CAN 为什么让不少人翻车提到 DSP-28069 的 CAN 通讯代码例程很多人的第一反应是“照着厂商例程改改 ID 就能跑”。实际动手会发现真正卡壳的地方不是 CAN 协议本身而是 28069 这颗芯片的 DCAN 模块配置——邮箱机制、位时序换算、中断链路任何一个环节没对齐总线就是静默的。本文把这套例程拆开讲清楚从 DCAN 模块原理、初始化代码、收发逻辑到常见踩坑给出一份能直接照着抄的工程模板。适合正在用 28069 做电机控制、BMS 通讯、工业设备互联的开发者以及想把 CAN 通讯从“能跑”调到“稳定跑”的嵌入式工程师。2. DCAN 模块与邮箱机制动手前必须看懂的寄存器逻辑2.1 28069 的 CAN 控制器到底长什么样DCAN 与邮箱的对应关系28069 内部集成的 DCAN 模块兼容 CAN 2.0B 协议支持标准帧和扩展帧最大波特率跟外设时钟相关。它和很多 MCU 上的 CAN 控制器有个显著区别数据收发不是简单的“发一个字节、收一个字节”而是通过 32 个邮箱对象来管理的。每个邮箱可以独立配置成发送邮箱、接收邮箱或者自动应答邮箱这既是 DCAN 的灵活之处也是初用时最容易绕晕的地方。可以这么理解邮箱就是 32 个带编号的信箱格子。发送时你把报文内容填进某个邮箱然后告诉 DCAN“把这个邮箱的内容发出去”接收时你先告诉 DCAN“我只关心某个 ID 的报文”DCAN 收到匹配报文后自动放进对应邮箱。整个操作不涉及 CAN 收发器的底层时序那些由 DCAN 硬件自己处理。和邮箱配套的关键寄存器有 CONFIG 区使能、位时序、MSGID报文 ID 和帧类型、MSGCTRL数据长度、发送优先级、MDL/MDH数据内容以及状态寄存器组发送应答 TA、接收挂起 RMP、消息丢失 RML。刚开始写代码时不需要把 32 个邮箱全部配好通常的做法是0 号邮箱做发送1~3 号邮箱做接收剩下保持禁用。2.2 位时序与波特率从 VCLK 到 500 kbit/s 的换算公式波特率配置是 CAN 通讯里最需要耐心的一步。28069 的 DCAN 外设时钟不是直接等于系统主频而是由 VCLK 提供的。VCLK 是系统时钟分频后的结果具体分频系数要看系统初始化里怎么配置。如果不确认 VCLK 是多少算出来的波特率就是错的总线会一直报 Bus-Off。波特率的计算公式是波特率 VCLK / ((BRP 1) × (1 TSEG1 1 TSEG2 1))。这里 BRP 是波特率预分频值TSEG1 和 TSEG2 是时间段寄存器值。一个位时间被分成同步段、传播段、相位段 1 和相位段 2其中同步段固定为 1 个时间量子TSEG1 对应传播段加相位段 1TSEG2 对应相位段 2。以 VCLK 45 MHz、目标波特率 500 kbit/s 为例一个位时间 45 MHz / 500 k 90 个时间量子。取 BRP 4则时间量子 (4 1) / 45 MHz ≈ 111 ns90 个量子正好凑成 2000 ns 的位时间。此时 TSEG1 TSEG2 15常用分配是 TSEG1 13、TSEG2 2。下面给出两组常用参数VCLK (MHz)目标波特率 (kbit/s)BRPTSEG1TSEG2SJW45500413214525091321注意 SJW同步跳转宽度一般取 1~2用于补偿总线时钟偏差。若 VCLK 不是 45 MHz按上面的公式反推即可不需要死记参数。2.3 消息对象与收发流程TA、RMP、RML 三个标志位怎么配合DCAN 的状态标志位是收发流程的核心。发送邮箱有发送请求位 TRS 和发送应答位 TA接收邮箱有接收挂起位 RMP 和消息丢失位 RML。这些标志位的特点是“写 1 清除”也就是读取之后向对应位写 1 来复位状态而不是写 0。发送流程的典型套路先把报文 ID、数据长度和数据写入邮箱然后置位该邮箱的 TRSDCAN 硬件开始发报文发送成功后硬件自动置位 TA。软件检测到 TA 置位后写 1 清除 TA再发下一帧。如果不清 TA下一次发送请求会异常这是初学阶段最容易碰到的逻辑问题。接收流程则是使能接收邮箱后DCAN 每收到一个匹配报文硬件就把报文存进邮箱并置位 RMP。软件读到 RMP 为 1 就取走数据然后写 1 清除 RMP。如果报文来得太快软件还没取走新报文覆盖旧报文硬件会置位 RML表示“丢消息了”。RML 是诊断总线负载和软件实时性的重要依据。标志位所在寄存器置位条件清除方式TACANTA报文发送完成写 1 清除TRSCANTRS软件请求发送硬件发送完成后自动清除RMPCANRMP收到匹配报文软件读取后写 1 清除RMLCANRML新报文覆盖未读报文写 1 清除熟悉这四个标志位的配合收发逻辑就成功了一大半。接下来进入代码部分。3. 初始化配置从 GPIO 到波特率的可抄作业代码3.1 时钟与 GPIO MUX先把 CAN 外设从“未上电”唤醒初始化第一步不是写 CAN 寄存器而是打开外设时钟和配置引脚复用。28069 的 CAN-A 外设引脚固定为 GPIO30CANRXA和 GPIO31CANTXA这两个引脚默认是普通 GPIO 功能必须通过 GPAMUX2 寄存器切换为 CAN 功能否则 CAN 模块再怎么配置引脚上也不会出现差分信号。时钟方面需要在外设时钟控制寄存器里使能 CAN-A 模块。不同的头文件版本对这个位的命名略有差异有的叫 CANAENCLK有的叫 ECANAENCLK功能一致。代码如下// 函数InitCAN_Gpio和时钟使能 // 说明在使用CAN模块之前先让外设时钟和引脚复用到位 void InitCAN_Gpio(void) { // 1. 使能 CAN-A 外设时钟 // 28069 的外设时钟控制寄存器 PCLKCR0 的 bit14 控制 CAN-A SysCtrlRegs.PCLKCR0.bit.CANAENCLK 1; // 若头文件为旧版位名可能叫 ECANAENCLK // 2. 配置 CAN 引脚为复用功能 // GPIO30 - CANRXAGPIO31 - CANTXA // GPAMUX2 每个 GPIO 占 2 位GPIO30 对应 bit28-29GPIO31 对应 bit30-31 // 值 01 表示该引脚切换为 CAN 功能 GpioCtrlRegs.GPAMUX2.bit.GPIO30 1; // CANRXA GpioCtrlRegs.GPAMUX2.bit.GPIO31 1; // CANTXA // 3. 设置方向 // CANRXA 是输入CANTXA 是输出 GpioCtrlRegs.GPADIR.bit.GPIO30 0; // 输入 GpioCtrlRegs.GPADIR.bit.GPIO31 1; // 输出 }这段代码的关键在于 GPIO 复用值必须写成 1很多例程写 0 导致引脚仍旧是普通 IO。另外 GPADIR 的方向设置同样要匹配否则即使复用功能选对了信号通路也不完整。PCLKCR0 的 CANAENCLK 位如果不置位后续所有 CAN 寄存器操作都会在调试器里表现为“写了没反应”。3.2 波特率寄存器计算给出 500 kbit/s 与 250 kbit/s 两组参数接下来配置 CAN 模块的主控制寄存器和位时序寄存器。28069 的 DCAN 有一个配置保护机制要修改位时序寄存器必须先设置 CANMC 的 CCE 位进入配置模式改完后再清除 CCE。这个机制是为了防止程序运行中意外篡改波特率导致总线故障。CANBTC 寄存器里需要填写的字段包括 BRP、TSEG1、TSEG2 和 SJW。要注意的是寄存器里存放的值是“实际值减 1”也就是如果你想表达 TSEG1 13寄存器写入 12。这是 DCAN 硬件设计决定的很多人在这个地方栽跟头算出来的波特率总是偏离目标值。// 函数InitCAN_Bitrate // 说明配置 500 kbit/s 波特率VCLK 45 MHz // 参数无波特率耦合在寄存器值里改波特率时按表格换参数 void InitCAN_Bitrate(void) { // 1. 请求进入配置模式 // CCE1 时允许修改 BTR 寄存器 CANRegs.CANMC.bit.CCE 1; // 2. 配置位时序对应 2.2 小节表格第一行 CANRegs.CANBTC.bit.BRP 4; // 预分频值 BRP1 5 CANRegs.CANBTC.bit.TSEG1 12; // 寄存器值 实际值 - 1 13 - 1 CANRegs.CANBTC.bit.TSEG2 1; // 寄存器值 实际值 - 1 2 - 1 CANRegs.CANBTC.bit.SJW 0; // SJW 实际值 寄存器值 1 // 3. 退出配置模式 CANRegs.CANMC.bit.CCE 0; // 4. 启用自动总线恢复总线故障后硬件自动恢复 CANRegs.CANMC.bit.ABO 1; }如果 VCLK 不是 45 MHz直接把 BRP、TSEG1、TSEG2 按比例换算即可公式在第 2.2 节。改参数后建议先用示波器看 CAN_TX 引脚波形确认波特率再挂总线设备。需要 250 kbit/s 时把 BRP 改成 9、TSEG1 保持 12、TSEG2 保持 1其余不变。3.3 邮箱初始化写成 InitCAN 函数并解释每一步GPIO 和波特率就绪后开始初始化邮箱。这里的策略是固定角色0 号邮箱用于发送1~3 号邮箱用于接收。初始化邮箱的核心操作是先在 CANME 寄存器中禁用对应邮箱配置好 ID 和数据长度后再重新使能。这个顺序不能反否则在邮箱使能状态下改写配置硬件可能响应异常。// 函数InitCAN // 说明完整的 CAN 模块初始化带 3 个接收邮箱 // 返回值无 void InitCAN(void) { // 1. 引脚和波特率基础配置 InitCAN_Gpio(); InitCAN_Bitrate(); // 2. 配置发送邮箱 Mailbox 0 CANRegs.CANME.bit.ME0 0; // 先禁用邮箱0 CANRegs.CANMSGID.bit.AME 0; // 发送邮箱不需要接收滤波 CANRegs.CANMSGID.bit.AAM 0; // 正常发送模式非自动应答 CANRegs.CANMSGID.bit.IDE 0; // 标准帧 CANRegs.CANMSGCTRL.bit.DLC 8; // 默认发送长度 8 字节 CANRegs.CANME.bit.ME0 1; // 重新使能邮箱0 // 3. 配置接收邮箱 Mailbox 1~3 // 三个邮箱都接收 0x100 这个标准帧 ID for (int i 1; i 3; i) { // 按位操作方式使能/禁用邮箱 CANRegs.CANME.all ~(1UL i); // 先禁用当前邮箱 // 配置消息 ID CANRegs.CANMSGID.bit.AME 1; // 使能接收滤波 CANRegs.CANMSGID.bit.ID 0x100; // 要接收的标准帧 ID CANRegs.CANMSGID.bit.IDE 0; // 标准帧 // 接收邮箱的 DLC 硬件不参与校验配置为 0 即可 CANRegs.CANMSGCTRL.bit.DLC 0; // 重新使能当前邮箱 CANRegs.CANME.all | (1UL i); } }代码里CANMSGID.bit.ID在不同头文件中可能叫STDMSGID取决于厂商头文件的命名版本。接收邮箱的 AME 位必须置 1表示启用 ID 滤波否则该邮箱会接收总线上所有报文3 个邮箱很快就会被无关报文填满。发送邮箱的 AME 保持 0 即可因为发送操作不需要滤波。至此初始化部分完成编译烧录后如果一切正常CAN 模块已经具备收发条件。接下来看发送和接收的具体实现。4. 发送与接收轮询方式和中断方式各给一套例程4.1 发送例程邮箱写入、CANTRS 置位、等待 CANTA 的完整流程发送一帧报文的常规思路先检查目标邮箱是否空闲然后把数据拆成高 32 位和低 32 位写入 MDL/MDH设置 DLC置位 TRS 请求发送最后等待 TA 置位确认发送完成。28069 的数据寄存器是 8 字节一组MDL 存储第 0~3 字节MDH 存储第 4~7 字节。// 函数CAN_SendMessage // 说明通过邮箱 0 发送一帧标准帧报文 // 参数id - 标准帧 ID11 位; data - 数据指针; len - 数据长度最多 8 // 返回1 表示发送成功0 表示邮箱忙 int CAN_SendMessage(unsigned int id, unsigned char *data, unsigned int len) { unsigned long low_word 0, high_word 0; // 1. 检查邮箱0是否还在忙TRS 未清除说明发送未完成 if (CANRegs.CANTRS.bit.TRS0 1) return 0; // 2. 填充数据到 MDL/MDH 寄存器 // 低 32 位放第 0~3 字节高 32 位放第 4~7 字节 for (int i 0; i len i 4; i) low_word | (unsigned long)data[i] (8 * i); for (int i 4; i len i 8; i) high_word | (unsigned long)data[i] (8 * (i - 4)); // 3. 先禁用邮箱再改内容避免硬件在改写过程中误发送 CANRegs.CANME.bit.ME0 0; CANRegs.CANMSGID.bit.ID id; // 写入目标 ID CANRegs.CANMSGCTRL.bit.DLC len; // 数据长度 CANRegs.CANMDL.all low_word; // 低 4 字节 CANRegs.CANMDH.all high_word; // 高 4 字节 CANRegs.CANME.bit.ME0 1; // 重新使能 // 4. 请求发送并等待完成 CANRegs.CANTRS.bit.TRS0 1; // 置位发送请求 while (CANRegs.CANTA.bit.TA0 0); // 等待硬件置位发送应答 // 5. 清 TA 标志写 1 清除 CANRegs.CANTA.bit.TA0 1; return 1; }写 1 清除 TA 这一步经常被省略省略后的表现是第一帧发送正常第二帧开始 TRS 一直无法正常触发因为残留的 TA 状态干扰了硬件逻辑。另外 while 等待 TA 属于阻塞式发送如果总线上没有 ACK 应答比如对端节点未就绪这个循环会一直卡住。工程上建议加超时判断用计数器方式退出循环避免单点故障拖死整个主循环。4.2 接收例程轮询 RMP 与中断两种写法接收的轮询写法比发送更简单检测 RMP 标志位置位说明邮箱里来了新报文读取后清标志。下面代码同时检测 RML 位顺带反馈丢帧情况。// 函数CAN_ReceiveMessage // 说明轮询方式接收邮箱 1 的报文 // 返回1 表示有新报文且读取成功0 表示无新报文 int CAN_ReceiveMessage(unsigned int *id, unsigned char *data) { unsigned long low_word 0, high_word 0; // 1. 检查接收挂起标志 RMP1 if (CANRegs.CANRMP.bit.RMP1 0) return 0; // 2. 检查是否有丢消息 if (CANRegs.CANRML.bit.RML1 1) { // 丢帧标志这里做记录实际使用时可置位某个全局错误标志 CANRegs.CANRML.bit.RML1 1; // 写 1 清除 } // 3. 读取 ID 和数据 low_word CANRegs.CANMDL.all; // 低 32 位 high_word CANRegs.CANMDH.all; // 高 32 位 // 拆出 8 字节数据 for (int i 0; i 4; i) data[i] (low_word (8 * i)) 0xFF; for (int i 0; i 4; i) data[i 4] (high_word (8 * i)) 0xFF; // 4. 清除 RMP1写 1 清除 CANRegs.CANRMP.bit.RMP1 1; return 1; }轮询方式适合主循环频率足够高的场景比如电机控制主循环跑到 10 kHzCAN 报文速率 500 kbit/s 下完全来得及。但如果主循环里有耗时操作比如 Flash 擦写、长时间等待外设轮询就可能丢帧。RML 标志正是这种场景的第一个报警信号不建议忽略。4.3 中断链路PIE、IER、CAN 模块三者谁没开都会静默失败中断接收方式适合对实时性要求高的场景。28069 的 CAN 接收中断链路分三层CAN 模块本身的中断使能寄存器、PIE 控制器的第 9 组、以及 CPU 的 IER 寄存器。三层中任何一层没开中断都不会触发而且程序看起来一切正常——这是最让人头疼的“静默失败”。// 函数InitCAN_Interrupt // 说明使能邮箱 1 的接收中断 void InitCAN_Interrupt(void) { // 1. CAN 模块级使能邮箱 1 的接收中断标志 // DCAN 的中断使能寄存器按邮箱分布这里置位对应位 CANRegs.CANIER.bit.IE1 1; // 邮箱1接收中断使能 // 2. PIE 级使能第 9 组第 1 个通道CAN-A 中断 PieCtrlRegs.PIEIER9.bit.INTx1 1; // 3. CPU 级使能 IER 的 INT9 IER | M_INT9; // 4. 全局中断使能 EINT; }// 中断服务函数CAN 接收中断 // 说明进入中断后读取报文并清标志 __interrupt void CAN_ISR(void) { unsigned int rx_id; unsigned char rx_data[8]; // 读取邮箱1报文 // 实际使用中建议把读取逻辑和业务处理分离中断里只做拷贝 rx_id CANRegs.CANMSGID.bit.ID; // 读取数据的逻辑同轮询方式省略重复代码 // 清除接收标志 CANRegs.CANRMP.bit.RMP1 1; // 清除 PIE 响应标志 PieCtrlRegs.PIEACK.all PIEACK_GROUP9; }中断服务函数的末尾必须写PieCtrlRegs.PIEACK.all PIEACK_GROUP9;这句话和 28069 的 PIE 机制强相关。如果遗漏PIE 不会响应同组的下一次中断表现为“中断只进一次”。调试时如果发现中断只触发一回优先检查 PIEACK 是否被正确写回。中断服务函数里的业务处理要尽量精简建议只做数据拷贝把解析和协议处理放到主循环。CAN 中断频率高时ISR 里做耗时操作会直接影响主循环的实时性甚至导致看门狗超时复位。5. 避坑与常见问题排查5 个真实翻车记录5.1 现象代码全对但总线一直无波形不进中断原因收发器芯片的外围引脚没配置或者没上电。很多 CAN 收发器有静音模式引脚或待机引脚比如 STB 引脚拉高后芯片直接进入待机CAN 差分信号完全不出来。代码里把 DCAN 配得再好物理层没有工作总线上自然看不到东西。解决先用示波器或逻辑分析仪抓 CANTXA 引脚单端电平看有没有波形。如果 CANTXA 有波形而总线无波形基本可以断定问题出在收发器外围。把 STB 引脚拉低或按收发器手册配置再检查终端电阻是否安装。5.2 现象波特率算着没问题但总线一直报 Bus-Off原因VCLK 的实际值和你算波特率时用的假设值不一致。比如你以为 VCLK 是 45 MHz实际初始化代码里 PLL 配置不同VCLK 可能是 40 MHz 或 50 MHz。DCAN 的位时序寄存器完全按照错误的 VCLK 计算硬件收发逻辑错位错误累计到一定数量就触发 Bus-Off。解决在初始化代码里加一句读取 VCLK 或系统频率的调试语句先确认频率再算 BRP。也可以反过来先用示波器测量 CANTXA 引脚的单端波形数一个位的时间宽度倒推实际波特率再调整寄存器值。5.3 现象环回模式收发正常接上外部节点就废原因两边波特率不一致或者共地问题。环回模式不经过对外收发器波特率错误在环回下不一定暴露接外部节点后双方位时序偏差直接被总线识别出来。另外 28069 的 GND 和外部 CAN 节点的 GND 没有连接时CAN 差分信号虽然看起来有电平变化但共模电压差太大接收端无法解析。解决先用 CAN 分析仪或者示波器测一下总线的实际波特率确认两边一致再检查两个节点之间是否有共地线不要只靠三根线CANH、CANL、GND中的信号线和地线地线接触不良在 CAN 通讯里表现得比 RS-485 更隐蔽。5.4 现象自己发的报文把自己接收邮箱填满程序逻辑错乱原因接收邮箱的滤波 ID 配置得和发送 ID 一样且收发器工作在环回或监听模式下。更多情况是同一个总线上有多个节点而本机接收邮箱的 AME 位没有正确置位导致接收邮箱接收了所有 ID 的报文。解决接收邮箱必须明确设置 AME 1 和期望的 ID如果确实需要接收本机发出的报文比如做环回测试用一个独立的接收邮箱专门处理业务逻辑里对这个邮箱做特殊标记避免和正常接收数据混淆。5.5 现象仿真器打断点就死机全速运行又正常原因DCAN 模块在调试暂停时邮箱状态和总线时序被强行冻结总线上的其他节点还在继续发报文导致 DCAN 内部状态出现不可恢复的冲突。28069 的 DCAN 有调试模式位 DBM默认值可能在你的工程里没有配置正确。解决在 CANMC 寄存器里显式设置 DBM 1让 DCAN 在调试暂停时继续维持内部状态或者关闭 CAN 中断后打断点。更稳妥的做法是调试时把 CAN 节点从总线上摘掉或者用环回模式调试逻辑避免影响整个总线网络。6. 进阶技巧用内部环回把新板子的 CAN 底噪先测出来新板子第一次上电直接挂总线调试风险很高接线错误、收发器问题、波特率不匹配任何一个问题都可能导致总线上一片混乱。我现在的习惯是上来先写一个内部环回自测函数不经过对外收发器让 DCAN 自己发自己收。这一步能快速验证 MCU 内部的 CAN 模块、邮箱配置和中断链路是否正常。环回模式的开启方式是把 CANMC 的 STM 位置 1 进入测试模式然后设置 CAN 测试寄存器使能环回标志。具体代码片段如下// 函数CAN_SelfTest // 说明内部环回自测邮箱1发、邮箱0收验证基本通路 int CAN_SelfTest(void) { unsigned char tx_data[8] {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88}; unsigned char rx_data[8] {0}; // 1. 进入测试模式 CANRegs.CANMC.bit.STM 1; // 2. 配置内部环回 // 测试控制寄存器中使能回环标志具体位名看头文件定义 CANRegs.CANTST.bit.BACK 1; // 3. 用邮箱0发送邮箱1接收发送函数和接收函数复用上文代码 CAN_SendMessage(0x100, tx_data, 8); CAN_ReceiveMessage(id, rx_data); // 4. 退出测试模式 CANRegs.CANMC.bit.STM 0; // 5. 比对收发数据长度不一致则自测失败 for (int i 0; i 8; i) if (tx_data[i] ! rx_data[i]) return 0; return 1; }自测通过后再把接收邮箱的数据和主循环里的业务逻辑对接。调试过程中用一个 8 字节的报文结构体做数据拷贝避免每次都在中断里解析结构体字段能降低写错指针的风险。CAN 这一块的经验积累大部分是靠翻车翻出来的。比如 RML 丢帧标志、PIEACK 写回、TA 写 1 清除这些细节手册里都有但不到实际调试时是不会真正理解的。希望这篇基于 28069 的 CAN 通讯代码例程梳理出来的实战笔记能帮你少走一段弯路。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

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

立即咨询 →