简介:面向学习与开发DSP28335平台CAN通信的工程师、高校学生及嵌入式爱好者,这份完整的CCS工程示例聚焦eCAN模块的配置与数据收发实践,可用于理解CAN控制器初始化、波特率与滤波器设置、消息发送接收、中断响应及错误检测恢复等关键环节。压缩包共74个文件、约480KB,以C源文件、头文件、汇编启动代码、链接命令文件和编译输出文件为主,兼有工程配置、映射与日志文件,构成从源码到构建结果的完整工程结构,便于对照阅读模块组织与函数调用关系,适合具备C语言与嵌入式基础的中级读者。目前已有4029人学习下载,说明该示例在28335 CAN入门与调试中具有较高参考价值。读者可从中获得结构完整的工程框架,并借鉴其消息收发流程、中断服务组织及错误处理思路,快速搭建自己的CAN通信程序。
1. 从一台伺服驱动器的联调说起:28335 的 CAN 程序补上了哪块短板
接一台伺服驱动器做联调,上位机 500 kbps 的报文在示波器上波形很干净,从站却始终不回帧,最后查出来是 DSP28335 的邮箱根本没配成接收方向。这种问题在研究 DSP28335 CAN通信程序时,是非常容易踩到的第一道坎:芯片手册把 32 个邮箱、位定时、中断映射全摊开写,但要把「初始化—收发—中断—错误恢复」连成一份能编译、能跑通的工程,还得自己拼。这份程序针对的就是这块空缺:基于 TMS320F28335 的 eCAN-A 模块,用 C 语言实现位定时计算、标准帧收发、中断接收、回环自测和总线关闭恢复,并配套 CCS 工程。做电机控制、储能 BMS、变频器、工业网关的嵌入式开发者都能直接用,刚拿到板子、要跟 CAN 上位机对接的人照着跑一遍也能上手。
2. 工程骨架与寄存器视图:先把这堆文件理顺再动寄存器
拿到一份陌生的 DSP 工程,我一般不看 main.c,先看目录和链接脚本。因为 F28335 这类平台,90% 的“跑不起来”都出现在链接阶段和时钟配置阶段,而不是业务逻辑写错。这份工程的分层也是照这个思路组织的:芯片支持层只负责把寄存器结构体和位域定义清楚,应用层只负责邮箱怎么摆、收发怎么封,业务层只关心“我收到 0x100 该干什么”。
2.1 源文件分层:主程序、外设驱动、板级配置各管什么
工程目录大致长这样:
DSP28335_CAN/ ├── cmd/ │ ├── F28335.cmd // 固化到 Flash 的链接脚本 │ └── F28335_RAM_lnk.cmd // 在 RAM 里调试用的链接脚本 ├── include/ │ ├── DSP2833x_Device.h // 全局类型、宏、外设结构体声明 │ ├── DSP2833x_ECan.h // eCAN 寄存器位域定义 │ ├── DSP2833x_Gpio.h │ ├── DSP2833x_SysCtrl.h │ └── can_app.h // 应用层对外接口 ├── source/ │ ├── DSP2833x_ECan.c // 寄存器结构体实例化 │ ├── DSP2833x_Gpio.c │ ├── DSP2833x_SysCtrl.c // 150 MHz 时钟、外设时钟门控 │ ├── DSP2833x_GlobalVariableDefs.c │ ├── can_app.c // 邮箱配置、收发封装、中断服务 │ └── main.c // 初始化顺序与主循环 └── F28335.ccxml // 目标配置文件三层职责划得很清楚。DSP2833x_*.c里基本没有逻辑,全是通过#pragma DATA_SECTION把结构体实例映射到寄存器地址上,比如 eCAN 的 32 个邮箱在内存里就是一段连续的 512 字节,头文件里用一个ECanaMboxes数组把它盖住。can_app.c才是真正干活的地方,所有ECanaRegs.xxx的写操作都收在这里,main.c只调用CAN_Init()、CAN_SendFrame()这种函数名。这个边界的价值在改需求时才体现出来:上位机换了协议,只改can_app.c;换成 eCAN-B,改的也是这一处,主循环一行不动。
顺带说一句DSP2833x_GlobalVariableDefs.c,很多新手会把它删掉,觉得“里面啥也没有”。它其实是整个工程的地基,所有ECanaRegs、SysCtrlRegs的实体定义都在这个文件里,删了就是一堆 undefined symbol。
2.2 32 个邮箱的内存模型:MSGID、MSGCTRL、MDL、MDH 怎么排
eCAN 的邮箱不是“发一个缓冲区”,而是一个固定 32 项的表,每项 16 字节,由四个 32 位寄存器组成:MSGID(帧 ID 和标识位)、MSGCTRL(DLC、RTR、发送优先级)、MDL(数据字节 0~3)、MDH(数据字节 4~7)。32 个邮箱一共占 512 字节,正好是 eCAN 的一块 RAM 区。
方向的分配靠两个位图寄存器:CANMD决定每个邮箱是发还是收(0 发 1 收),CANME决定每个邮箱是否使能。这份工程把 MBOX0~15 划给接收、MBOX16~31 划给发送,这是个很实用的默认约定——接收邮箱编号靠前,中断服务里查表时索引小,代码读起来也直观。
// can_app.c —— 邮箱方向与使能,放在进入配置模式之后、清 CCR 之前 // CANMD 某位为 1 表示该邮箱是接收邮箱 ECanaRegs.CANMD.all = 0x0000FFFF; // MBOX0~15 接收,MBOX16~31 发送 ECanaRegs.CANME.all = 0xFFFFFFFF; // 32 个邮箱全部使能 // MBOX0:接收上位机下发的 0x100 标准帧 // MSGID 位域:bit31 IDE=0(标准帧), bit30 AME=0(不用掩码), bit29 AAM=0 // 标准 ID 放在 bit28~18,所以 0x100 << 18 = 0x04000000 ECanaMboxes.MBOX0.MSGID.all = 0x04000000; ECanaMboxes.MBOX0.MSGCTRL.all = 0x00000088; // EOB=1, DLC=8, TPL/RTR 均为 0这里最容易翻车的是MSGID的移位。标准帧的 11 位 ID 不在低 11 位,而是在 bit28~18 上,很多人直接写0x100下去,结果是邮箱收到了一堆 ID 对不上的报文却完全没反应,CANRMP也不置位——因为 ID 根本不匹配,硬件就当没听见。扩展帧则是 29 位 ID 占满 bit28~0,同时IDE置 1,两者的写法差别就在这一个位上。
MSGCTRL里的EOB位(bit7)容易被忽略。它标记“这是接收链的最后一个邮箱”,如果一组连续的接收邮箱都使能了,只有最后一个需要置EOB,中间的不置。要是全都不置,接收链就没有终点,硬件会一直往后找,行为会很怪。
发送邮箱还要关注TPL(bit12~8),五个位,值越大优先级越高。CAN 总线仲裁是按 ID 逐位比的,但 eCAN 内部多个发送邮箱同时挂起时,是先按 TPL 排序再按 ID 排的。做周期性报文(比如电机转速帧、故障码帧)时,把故障码的 TPL 调高,能保证它排在转速帧前面发出去。
2.3 拉进 CCS 后的编译配置:三个必查项
工程能打开不代表能编译。第一次导入之后我一般固定检查三处。
第一处是DSP2833x_Device.h里的CPU_FREQ宏。这份工程默认#define CPU_FREQ 150E6,因为 F28335 在 30 MHz 晶振经 PLL 倍频后跑 150 MHz。这个值一旦和实际时钟对不上,位定时的计算结果就整体偏移,表现出来就是“程序里写 500 kbps,示波器测出来是 480 kbps”,而且误差随波特率升高而变大,到 1 Mbps 基本就通不了。
第二处是链接脚本。RAM 调试用F28335_RAM_lnk.cmd,固化用F28335.cmd,这两个文件里的 MEMORY 段分配不一样。选错了要么程序跑飞,要么编译过了烧进去没反应。
第三处是头文件搜索路径。include/和source/都要加进去,另外如果用了浮点库,还要把rts2800_fpu32.lib挂上。这几个配置都是一次性的,配好了之后新建工程可以直接复制.cproject里的设置,比每次重来省事。
提示:CCS 里编译通过但下载报 “device not responding”,先确认
.ccxml里的芯片型号是 F28335 而不是 F2833x 系列的其它型号,再看仿真器驱动和板子供电。这类问题跟代码一点关系都没有。
3. 位定时与初始化序列:150 MHz 主频下把 500 kbps 算准
CAN 通信里最先出错、又最难查的就是波特率。总线上的位时间差一点点,短帧还能勉强过,长帧一出错就整帧丢,表现出来像是“偶尔通信异常”,非常难定位。这一节把时钟链路、寄存器字段和初始化顺序一次讲透。
3.1 时钟链路与 BRP/TSEG 的计算过程
F28335 的 eCAN 模块时钟不是 150 MHz,而是SYSCLKOUT / 2,也就是 75 MHz。这一点很多资料没写清楚,直接拿 150 MHz 去算,结果差整整一倍。
位时间的构成是:一个同步段(固定 1 tq)+ 相位缓冲段 1 + 相位缓冲段 2。相位缓冲段 1 包含了传播段,实际配置时合并处理。寄存器里写进去的值都要加 1 才是真实的时间份额数:
CANBTC.BRPREG写入值加 1 得到预分频系数CANBTC.TSEG1REG加 1 得到相位段 1 的 tq 数CANBTC.TSEG2REG加 1 得到相位段 2 的 tq 数
于是有一个可以直接套的公式:
tq = (BRPREG + 1) / 75MHz 位时间 = (1 + (TSEG1REG+1) + (TSEG2REG+1)) × tq 采样点 = (1 + TSEG1REG + 1) / (1 + TSEG1REG + 1 + TSEG2REG + 1)我想让每一位固定切成 15 个 tq,采样点压在 80%(CiA 推荐范围的上半段,抗干扰余量大一些),那么TSEG1REG = 10(11 tq)、TSEG2REG = 2(3 tq),1 + 11 + 3 = 15,采样点 12/15 = 80%。500 kbps 对应位时间 2 μs,tq 就是 2 μs / 15 = 133.33 ns,反推预分频 = 133.33 ns × 75 MHz = 10,所以BRPREG = 9。
按这个思路,几个常用波特率的参数可以直接查:
| 目标波特率 | BRPREG | TSEG1REG | TSEG2REG | SJWREG | tq | 采样点 |
|---|---|---|---|---|---|---|
| 1 Mbps | 4 | 10 | 2 | 2 | 66.7 ns | 80% |
| 500 kbps | 9 | 10 | 2 | 2 | 133.3 ns | 80% |
| 250 kbps | 19 | 10 | 2 | 2 | 266.7 ns | 80% |
| 125 kbps | 39 | 10 | 2 | 2 | 533.3 ns | 80% |
SJWREG是同步跳转宽度,用来吸收不同节点晶振之间的漂移,一般取 2 或 3 就够了,取值不能超过TSEG2REG + 1。
注意:表格里的 BRPREG 是寄存器写入值,不是预分频系数本身。写代码时如果按“预分频系数”理解,会少除一个 10,波特率直接差十倍。
3.2 初始化七步:顺序错一步就进不了配置模式
eCAN 的初始化有严格的时序:必须先让模块进入“配置模式”,才有资格改CANBTC、CANME、CANMD这些寄存器;在配置模式外面写它们,硬件会直接忽略,还不报错。这是最坑的一点,因为代码看起来完全正常。
完整的初始化序列是七步:
// can_app.c —— eCAN-A 初始化,SYSCLKOUT = 150 MHz,目标 500 kbps void CAN_Init(void) { // 第 1 步:开外设时钟、配引脚复用 EALLOW; SysCtrlRegs.PCLKCR0.bit.ECANAENCLK = 1; // 使能 eCAN-A 时钟 GpioCtrlRegs.GPAMUX2.bit.GPIO18 = 3; // GPIO18 -> CANTXA GpioCtrlRegs.GPAMUX2.bit.GPIO19 = 3; // GPIO19 -> CANRXA EDIS; // 第 2 步:软复位,把控制寄存器拉回干净状态 ECanaRegs.CANMC.all = 0x0000; // 第 3 步:请求进入配置模式,等 CPU 拿到权限 ECanaRegs.CANMC.bit.CCR = 1; while (ECanaRegs.CANMC.bit.CCR) { } // 权限没拿到就一直等 // 第 4 步:等 CCE 置位,表示 CANBTC 现在可以写 while (!ECanaRegs.CANES.bit.CCE) { } // 第 5 步:写位定时,先清空整个寄存器避免残留位 ECanaRegs.CANBTC.all = 0; ECanaRegs.CANBTC.bit.BRPREG = 9; // 预分频 10,tq = 133.3 ns ECanaRegs.CANBTC.bit.TSEG1REG = 10; // 相位段 1 = 11 tq ECanaRegs.CANBTC.bit.TSEG2REG = 2; // 相位段 2 = 3 tq ECanaRegs.CANBTC.bit.SJWREG = 2; // 同步跳转宽度 3 tq ECanaRegs.CANBTC.bit.SAM = 0; // 单次采样,总线短时用单次就够 // 第 6 步:配置邮箱方向、使能、ID(见 2.2 节,此处略) CAN_MailboxConfig(); // 第 7 步:退出配置模式,等 CCE 归零 ECanaRegs.CANMC.bit.CCR = 0; while (ECanaRegs.CANMC.bit.CCR) { } while (ECanaRegs.CANES.bit.CCE) { } }逐段说。第 1 步的PCLKCR0是外设时钟门控寄存器,eCAN-A 和 eCAN-B 各有独立使能位,不打开的话后面所有寄存器读写都返回 0,现象是“程序卡在 while 里出不来”。引脚复用这里要注意,GPIO18/19的MUX值 3 对应 eCAN-A,而 eCAN-B 是GPIO20/21,改模块的时候引脚也要跟着改,只改代码不改硬件等于没改。
第 3 步的CCR是“请求修改配置寄存器”,置 1 之后不是立刻生效,要等硬件把总线活动停下来,所以必须轮询等待。这是血泪经验:很多示例代码用delay()代替轮询,在主频改了或者编译器优化等级变了之后直接失效。
第 4 步等 `CANES.C
本文还有配套的精品资源,点击获取