简介:一套基于Code Composer Studio与TI DSP28335处理器的CAN通信实验工程,面向学习嵌入式实时通信的开发者,重点演示eCAN模块的初始化、报文发送与接收、ID过滤及中断处理流程。工程汇总了DSP2833x标准外设库源码、eCAN驱动实现和CCS工程配置文件,环境搭建与代码定位都很直观。资源包共97个文件,以C源码、H头文件、OBJ编译中间文件及CCS工程配置为主,同时包含MAP镜像与OUT可执行文件,可对照分析编译链接和存储分配;压缩包仅582KB,轻量易下载。目前已有214人学习下载。实验从CAN控制器寄存器配置入手,覆盖帧结构、波特率设置、消息邮箱管理和错误处理,再结合CCS调试器进行在线仿真,能够帮助读者快速掌握在DSP28335平台上开发CAN应用的完整路径,对工业控制、车载电子等场景极具参考价值。
1. 一个导入就能跑通 eCAN 收发的 DSP28335 工程
lab27-CAN_CCS_dsp28335 这套工程,第一次打开时会觉得文件杂:.pjt、.cdtproject、eCAN_back.c、DSP2833x_ECan.c、两个.cmd脚本和一堆.obj。但真正干过 CAN 开发的人会明白,这正是 C2000 平台比较完整的工程形态。和串口点灯不同,CAN 一旦跑起来,意味着三四块板子能在一条双绞线上按优先级仲裁、实时交换数据,而不是靠主从轮询撑着。这个实验工程把 DSP2833x 外设库、eCAN 驱动、链接脚本和 CCS 工程元数据都打包好了,导入、编译、烧进 28335 就能看到邮箱收发,适合正在从串口通信转向总线通信的嵌入式工程师,也适合需要快速确认 DSP28335 eCAN 模块时序和寄存器行为的现场调试人员。它能直接回答三个问题:eCAN 邮箱怎么映射、位时序怎么算、收发标志位怎么处理。
2. eCAN 模块的邮箱机制与位时序设计
2.1 邮箱映射:32 个 Mailbox 怎么分工
DSP28335 上的 eCAN 模块和很多 MCU 上只有一个发送缓冲、两三个接收缓冲的 CAN 控制器不一样,它有 32 个完整的邮箱,每个邮箱都带独立的 MSGID、控制字段、数据字段和状态位。这个设计的价值在于:总线上的每一种报文类型都可以独占一个邮箱,不需要在中断里做复杂的缓存管理。比如工业现场常见的做法是 Mailbox 1 固定发心跳帧,Mailbox 2 发控制指令,Mailbox 3 到 Mailbox 6 接收不同节点的状态帧,每个邮箱只维护自己的 ID 和长度,逻辑上干净很多。
邮箱的方向由CANMD寄存器控制,置 1 为发送邮箱,置 0 为接收邮箱。发送侧用CANTRS(发送请求置位)发起发送,发送完成后CANTA对应位置 1;接收侧收到报文后CANRMP对应位置 1。这三个状态寄存器是 eCAN 编程的核心,调试时用 CCS 的 Registers 窗口直接观察这几个值,就能判断数据卡在哪一步。需要注意的是,eCAN 的邮箱支持标准帧和扩展帧混用,MSGID寄存器里 IDE 位决定帧格式,AR 位决定是否为远程帧。实际项目中如果既有 11 位 ID 又有 29 位 ID,配置邮箱时要逐个确认这些控制位,不能只填 ID 数字。
CAN 总线仲裁的原理是 ID 越小优先级越高,多个节点同时发送时会按位仲裁,隐性位让位于显性位。eCAN 的 32 个邮箱本质上就是 32 条并行的发送通道,芯片内部会根据邮箱编号做优先级仲裁,编号小的邮箱优先级更高。这意味着CANTRS同时置了多个位时,不一定按置位顺序发送,而是按邮箱号从小到大发。初学时容易忽略这一点,导致两条报文的实际发送顺序和执行顺序不一致。
2.2 位时序换算与采样点选择
eCAN 的位时序由四段组成:同步段(固定 1 TQ)、传播段加相位缓冲段 1(合并为 TSEG1)、相位缓冲段 2(TSEG2)、以及重同步跳转宽度 SJW。波特率计算公式为:
波特率 = SYSCLKOUT / ((BRP + 1) * (1 + TSEG1 + TSEG2)) 采样点 = (1 + TSEG1) / (1 + TSEG1 + TSEG2)DSP28335 的 SYSCLKOUT 典型值是 150 MHz,eCAN 模块直接使用这个时钟,不分频。要让波特率算得准,关键是 BRP、TSEG1、TSEG2 的组合能让总时间份额整除。下面是三种常用波特率的配置参考,TSEG1 和 TSEG2 的取值以位时间份额为单位:
| 波特率 | BRP | 实际预分频 | TSEG1 | TSEG2 | SJW | 位时间份额 | 采样点 |
|---|---|---|---|---|---|---|---|
| 1 Mbps | 9 | 10 | 11 | 3 | 1 | 15 | 80% |
| 500 kbps | 18 | 19 | 11 | 3 | 1 | 15 | 80% |
| 250 kbps | 38 | 39 | 11 | 3 | 1 | 15 | 80% |
采样点选在 80% 是工程上比较保守的做法。CAN 总线信号在长距离传输后会有边沿变缓、相位偏移的问题,采样点偏中后部可以让接收端在信号稳定后再采样。SJW 取 1 意味着每次重同步最多调整 1 TQ,对于线束较短、节点不多的系统足够;如果总线上有多个不同晶振精度的节点,SJW 可以放宽到 2 到 3,代价是抗干扰能力略微下降。
实际配置时,eCAN 头文件里提供位域结构,可以直接对寄存器字段赋值,避免手算子寄存器数值:
// 500 kbps 位时序配置(SYSCLKOUT = 150 MHz) EALLOW; eCAN_Regs.CANBTC.bit.BRP = 18; // 预分频 = 18 + 1 = 19 eCAN_Regs.CANBTC.bit.SJW = 1; // 重同步跳转宽度 1 TQ eCAN_Regs.CANBTC.bit.TSEG1 = 11; // 相位段1 加传播段共 11 TQ eCAN_Regs.CANBTC.bit.TSEG2 = 3; // 相位段2 为 3 TQ EDIS;BRP 决定了时间份额的基础粒度,BRP 越小每个 TQ 时间越短,但 TSEG1 和 TSEG2 的可取值范围有限,所以波特率越低,BRP 必须相应增大。TSEG1 同时包含传播段和相位缓冲段 1,总线长度越长、收发器延迟越大,TSEG1 需要留的空间越多。这里给出的 80% 采样点对应 TSEG1 占比 73%,对 5 米以内的 CAN 线足够。注意 eCAN 模块不支持 CAN FD,它只能跑 Classic CAN 的 1 Mbps 上限,做 CAN FD 方案时不能沿用这套寄存器配置。
2.3 波特率误差对同步的影响
CAN 总线没有独立的时钟线,接收方靠每一位的跳变沿做同步。两个节点的实际波特率如果偏差超过一定范围,连续多个相同电平的位就会让采样点逐渐偏移,最终出现填充位错误或 CRC 错误。eCAN 的错误计数器会把这种持续错误累计到 Bus-Off,然后节点自动脱离总线。
判断波特率是否匹配,最快的办法是用示波器抓 CAN_TX 引脚,测量一帧报文的实际位宽,再和理论位宽对比。误差在 0.5% 以内通常问题不大,超过 1% 就要检查 BRP 计算是不是有进位问题,或者 SYSCLKOUT 是不是被 PLL 配置改过。很多 CCS 工程里 PLL 配置代码被注释掉,芯片跑在 30 MHz 内部振荡器上,这时按 150 MHz 算出的 CAN 波特率会差 5 倍,现象就是总线上完全收不到数据。
3. CCS 工程骨架:CMD 脚本与编译环境
3.1 工程目录里的关键文件与导入流程
这个 lab27 工程的根目录下有.project、.ccsproject、.cdtproject三个工程描述文件,它们是 CCS 识别工程类型的依据。Debug目录里放着编译产物,包括.obj目标文件、.out可执行文件、.map内存映射文件,还有cc_build_Debug.log编译日志。.map文件很值得看,它能告诉你每个段被放到了哪段内存、占了多少空间,遇到链接溢出时先打开它查是哪个段超了。
源文件分两类:一类是 TI 提供的 DSP2833x 外设库文件,如DSP2833x_GlobalVariableDefs.c、DSP2833x_PieVect.c、DSP2833x_SysCtrl.c、DSP2833x_ECan.c,这些文件提供寄存器定义、中断向量表和外设驱动函数;另一类是工程自身的应用代码,核心是eCAN_back.c,里面通常是 eCAN 初始化、消息收发测试的主流程。CCS环境中导入这套工程时,先确认安装时勾选了 C2000 组件,然后在菜单栏选 Project → Import CCS Projects,指定工程所在目录,CCS 会读取.ccsproject和.project自动还原工程配置。
导入后要检查两个地方:一是工程的 Include Options 里是否包含DSP2833x_headers/include和DSP2833x_common/include这两个头文件路径,头文件找不到是这类工程最常见的导入报错;二是预定义符号里有没有DSP28335或DEBUG,这个符号会决定DSP2833x_Device.h里外设头文件的选择,符号缺失时寄存器结构体可能是错误的。工程文件里SRC和INCLUDE两个文件夹是逻辑分组,CCS 的 Virtual Folder 机制,实际路径并不在工程目录下,不要把工程文件直接复制走。
3.2 两个 CMD 链接脚本的分工
工程里有两个 CMD 文件:28335_RAM_lnk.cmd和DSP2833x_Headers_nonBIOS.cmd。前者负责把代码段、数据段分配到 28335 片内 SARAM 和外部 Zone 内存,后者把 eCAN、GPIO、ADC 等外设寄存器映射到对应的内存地址。很多初学者只注意前者,忽略后者,导致编译链接时报错说外设符号 undefined。
28335_RAM_lnk.cmd的核心是 MEMORY 和 SECTIONS 两段。MEMORY 定义内存区域的起始地址和长度,SECTIONS 把输出段(如.text、.ebss、.stack)映射到这些区域。下面是一段典型的 RAM 链接片段:
MEMORY { PAGE 0: RAML0 : origin = 0x008000, length = 0x001000 RAML1 : origin = 0x009000, length = 0x001000 PAGE 1: RAMM0 : origin = 0x000000, length = 0x000400 RAMM1 : origin = 0x000400, length = 0x000400 } SECTIONS { .text : > RAML0, PAGE = 0 .stack : > RAMM1, PAGE = 1 .ebss : > RAML1, PAGE = 1 }这个文件的名字已经说明了用途:RAM 版本,代码全部放在片内 RAM 里,掉电即失,适合调试阶段反复烧写。真正做产品时要换成 Flash 版本的链接脚本,把.text放到 0x33FFF6 起始的 Flash 区域,并在启动代码里做 RAM 拷贝。DSP2833x_Headers_nonBIOS.cmd里的 nonBIOS 表示不使用 TI 的 BIOS 实时操作系统,外设寄存器直接以绝对地址映射方式访问。改外设寄存器映射时,会同时改DSP2833x_GlobalVariableDefs.c里的结构体定义和这个 CMD 里的地址,两边不一致会导致写寄存器写到了错误地址。
3.3 编译期常见的三类报错
这套工程用 CCS 自带构建系统编译时,常见的报错集中在三处。第一类是找不到头文件,报错形如#10008: could not open file "DSP2833x_Device.h",这是 Include Options 缺路径;第二类是外设结构体成员不识别,比如写了eCAN_Regs.CANBTC.bit.BRP但编译报错,通常是预定义符号DSP28335没加,导致编译器走了DSP2833x_Device.h里的默认分支,寄存器结构体被替换成了空定义;第三类是链接阶段报错program will not fit into available memory,原因是.text或.ebss超出了对应 SRAM 段的容量,需要回到.map文件里查具体段的占用。
批量验证时可以在命令行用 headless 方式构建工程,CCS 安装目录下的eclipse可执行文件支持-noSplash -application com.ti.ccstudio.apps.projectBuild -ccs.projects参数,脚本化编译对 CI 场景比较友好。不过这类 DSP 工程一般还是用 IDE 交互调试,命令行构建主要用于确认不同代码版本能不能编译通过。
4. eCAN 初始化序列与收发驱动拆解
4.1 初始化顺序:从默认配置模式切到 eCAN 模式
DSP28335 的 eCAN 模块上电后默认工作在 SCC(标准 CAN 控制器)模式,只支持有限的邮箱,必须先切到 eCAN 模式才能使用全部 32 个邮箱。切换过程是写CANMC寄存器的 SCB 位置 1,然后立即设置 CCR 请求进入配置模式,等待CANES的 CCE 位置 1。很多移植过来的代码会漏掉这个顺序,直接配置邮箱和位时序,结果寄存器写进去不生效。
初始化序列不能随意调换顺序:先切模式,再请求配置,等待 CCE 确认,然后才能写位时序和邮箱配置。退出配置模式时同理,清 CCR 后要等 CCE 清零,表示模块已经回到正常运行模式,之后才能收发报文。这个等待确认的过程必须做,不能用固定延时代替,因为 eCAN 模式切换需要等待当前总线活动结束,在总线忙时可能要多等几个位时间。
// eCAN_back.c 中 eCAN_init 的核心步骤 void eCAN_init(void) { EALLOW; // 切到 eCAN 模式,否则只有 16 个 SCC 邮箱可用 eCAN_Regs.CANMC.bit.SCB = 1; // 请求进入配置模式 eCAN_Regs.CANMC.bit.CCR = 1; EDIS; // 等待 eCAN 进入配置模式 while (eCAN_Regs.CANES.bit.CCE != 1) {} EALLOW; // 位时序配置,500 kbps eCAN_Regs.CANBTC.bit.BRP = 18; eCAN_Regs.CANBTC.bit.SJW = 1; eCAN_Regs.CANBTC.bit.TSEG1 = 11; eCAN_Regs.CANBTC.bit.TSEG2 = 3; // 邮箱 0 发送,邮箱 1 接收 eCAN_Regs.CANME.all = 0x00000003; // 使能邮箱 0、1 eCAN_Regs.CANMD.all = 0x00000001; // bit0 = 1 发送,bit1 = 0 接收 eCAN_Regs.CANMSGID[0].all = 0x12340000; // 标准帧,ID = 0x1234 eCAN_Regs.CANMSGID[1].all = 0x12350000; // 标准帧,ID = 0x1235 // 退出配置模式 eCAN_Regs.CANMC.bit.CCR = 0; EDIS; // 等待配置模式退出 while (eCAN_Regs.CANES.bit.CCE == 1) {} }EALLOW和EDIS是 TI 对关键寄存器写的保护机制,中间写的寄存器都属于 EALLOW 保护区域,不加会触发非法访问。CCE 位是配置模式使能的确认位,置 1 表示进入,清 0 表示退出。CANME决定哪些邮箱被使能,CANMD决定方向,这两个寄存器会直接影响后续收发逻辑,配置前最好先清掉旧值,避免上电残留数据影响。MSGID 的低 18 位对应标准帧 ID,标准帧 11 位 ID 存放在 bit17 到 bit6,所以0x1234 << 6才对,直接左移 18 位会错位。
4.2 发送路径:TRS 置位、TAA 确认与数据区写入
发送一帧数据,先把 ID、控制字和数据写入对应邮箱的CANMSGID、CANMCF、CANMDL、CANMDH寄存器,然后在CANTRS里把该邮箱对应的位置 1,硬件会自动发起发送。发送完成后硬件把CANTA对应位置 1,软件读到这个标志后要写 1 清除,否则下一帧数据进来时无法区分新旧状态。
发送代码的核心逻辑:
// 通过邮箱 0 发送一帧标准帧 void can_send_message(Uint32 id, Uint32 dlc, Uint32 data_lo, Uint32 data_hi) { EALLOW; // 标准帧 ID 放在 MSGID 低 18 位的高 11 位 eCAN_Regs.CANMSGID[0].all = (id & 0x7FF) << 6; // 数据长度寄存器,低 4 位有效 eCAN_Regs.CANMCF[0].all = dlc & 0x0F; // 8 字节数据:低 32 位和地 32 位各占一个寄存器 eCAN_Regs.CANMDL[0].all = data_lo; eCAN_Regs.CANMDH[0].all = data_hi; // 请求发送 eCAN_Regs.CANTRS.all = 0x00000001; EDIS; // 等待发送完成,TAA 位置 1 while (eCAN_Regs.CANTA.all != 0x00000001) {} EALLOW; // 清发送确认标志,写 1 清除 eCAN_Regs.CANTA.all = 0x00000001; EDIS; }CANTRS是只写寄存器,读回来的值可能全是 0,不能依赖它判断发送状态。发送完成的判定要用CANTA。发送请求置位后,硬件会先判断邮箱里有没有配置有效的 ID,若 MSGID 全 0 且数据也为 0,部分版本的 eCAN 会直接报发送错误,所以发送前要确保 ID 和 DLC 都写正确。CANMCF的 bit4 是 RTR 远程帧请求位,普通数据帧保持 0,需要请求对方节点发数据时才置 1。
4.3 接收路径:RMP 标志与数据读取
接收路径和发送路径对称。邮箱使能且配置为接收方向后,硬件自动匹配总线上的 ID,匹配成功就把数据锁存到邮箱数据寄存器,同时置位CANRMP对应位。软件轮询到CANRMP位置 1,就可以读取 ID、DLC 和数据,读完必须写 1 清标志。
// 轮询邮箱 1 的接收标志 void can_poll_receive(void) { if (eCAN_Regs.CANRMP.all & 0x00000002) { // 读取标准帧 ID Uint32 rx_id = (eCAN_Regs.CANMSGID[1].all >> 6) & 0x7FF; // 读取数据长度 Uint32 rx_dlc = eCAN_Regs.CANMCF[1].all & 0x0F; // 读取 8 字节数据 Uint32 rx_lo = eCAN_Regs.CANMDL[1].all; Uint32 rx_hi = eCAN_Regs.CANMDH[1].all; // 清接收挂起标志,允许下一次接收 EALLOW; eCAN_Regs.CANRMP.all = 0x00000002; EDIS; } }CANRMP清标志的时序很关键:如果不清,后续相同 ID 的报文会一直留在邮箱里不更新,数据看起来像卡住了。如果读取数据后清标志前又来了新报文,硬件会把新报文丢弃,此时CANRML相应位置 1,表示有报文丢失。实际项目里要检查CANRML,一旦置位说明接收处理速度跟不上总线速率,需要在中断里尽快读取或增加缓冲区。轮询方式适合测试代码,产品级代码建议把 RMP 接到 eCAN 中断,设置好后PieCtrl里挂对应的中断服务函数。
接收邮箱的 ID 配置相对灵活,可以配置成只接收指定 ID,也可以配合验收滤波接收一组 ID。如果多个接收邮箱配置了相同 ID,eCAN 会按邮箱编号从低到高匹配,编号小的邮箱优先接收。这个优先级规则在调试时会带来困惑,建议每个接收邮箱只用一个 ID,保持行为可预期。
4.4 回环自测:不看总线也能验证驱动
eCAN 支持外部回环和内部回环两种测试方式。内部回环模式不用接 CAN 收发器,发送数据直接在芯片内部回到接收邮箱,适合在没有总线的条件下验证驱动代码和寄存器配置。配置方法是写CANMC的测试模式位,让 eCAN 进入回环模式,然后正常调用发送函数,观察接收邮箱的 RMP 位是否置位。
外部回环是把 CANH 和 CANL 短接,或者通过收发器的测试模式实现,数据会经过外部收发器回到本节点。外部回环能验证 28335 和收发器之间的硬件连接,内部回环则完全绕开外部电路。常规步骤是:先跑内部回环确认驱动逻辑正确,再接上收发器跑外部回环,最后挂到真实总线和其他节点联调。这三个阶段分别对应不同的问题域,跳过任何一步,出问题时的排查范围都会变大。CAN 总线两端需要 120 欧终端电阻,测试时至少一端要接上,否则波形反射会导致采样错误。
5. 调 CAN 时值得留意的三个细节
5.1 用采样点公式反推错误原因
当总线上两个节点互通失败,但每个节点单独收发都正常时,问题往往出在采样点不一致。排查时先用示波器量出实际位宽,再根据公式反推对方的采样点位置。采样点过大接近 90% 时,对下降沿后的连续显性位容错差;过小接近 60% 时,容易采到上升沿附近的振荡。eCAN 的 TSEG1 和 TSEG2 分别调整相位段 1 和 2,调采样点就是平移这两个值。同一网络里所有节点的采样点要尽量一致,差值控制在 5% 以内比较安全。
5.2 Bus-Off 状态下的恢复处理
eCAN 的错误状态寄存器CANES里有一个 BO 位,置 1 表示模块进入了 Bus-Off。进入 Bus-Off 后,模块不会再参与总线通信,直到完成协议规定的 128 个总线空闲位检测。恢复有两种方式:硬件自动恢复,在CANMC里设置 ABO 位为 1;软件恢复,则要在检测到 BO 后重新初始化模块。
// Bus-Off 恢复处理:清发送请求、重新初始化 if (eCAN_Regs.CANES.bit.BO == 1) { EALLOW; // 停止当前发送请求和发送复位 eCAN_Regs.CANTRS.all = 0; eCAN_Regs.CANTRR.all = 0; // 请求进入配置模式并重配 eCAN_Regs.CANMC.bit.CCR = 1; EDIS; while (eCAN_Regs.CANES.bit.CCE != 1) {} eCAN_init(); // 重新走一遍初始化 // 配置模式退出 EALLOW; eCAN_Regs.CANMC.bit.CCR = 0; EDIS; while (eCAN_Regs.CANES.bit.CCE == 1) {} }Bus-Off 恢复期间,应用层优先处理的是发送队列里的待发报文。eCAN 的发送请求寄存器在模块复位后会丢失,恢复完成后需要重新置位所有未完成的报文。如果项目对数据连续性要求高,应该在 Bus-Off 期间把待发数据缓存在 RAM 里,恢复后按顺序补发。CANTRR是发送请求复位寄存器,置 1 可以取消正在等待的发送请求,恢复时统一清一下可以避免残存的发送请求在重配完成后自动发出旧数据。
5.3 验收滤波与 AMR 屏蔽位
接收邮箱的报文筛选由MSGID里的 AME 位和AMR寄存器共同控制。AME 置 1 启用验收滤波,这时AMR逐位决定 ID 的每一位是否参与匹配:AMR 对应位为 0 表示必须匹配,为 1 表示屏蔽该位。比如只接收 ID 为 0x1234 的标准帧,AMR 应该把 ID 区域之外的位置 1,ID 位置 0,写成十六进制就是0xFFFE003F这类形式。
// 邮箱 2 只接收 ID = 0x1234 的标准帧 EALLOW; eCAN_Regs.CANMSGID[2].all = (0x1234 << 6) | (1 << 30); // AME = 1 eCAN_Regs.CANAMR[2].all = 0xFFFE003F; // 校验 bit17-6 EDIS;这里(1 << 30)置的就是 MSGID 寄存器里的 AME 位,CANAMR的 bit17 到 bit6 对应标准帧 11 位 ID,必须设 0 参与匹配,其他位屏蔽。验收滤波配置错误时,现象是永远收不到特定 ID 的报文,但把 AMR 改成全 0xFFFFFFFF 后却能收到全部报文。这时基本可以确定是 AMR 的值计算有误,逐位核对 ID 位置和屏蔽位即可。把验收滤波和回环模式做成调试宏,挂线测试时先放开 ID 过滤,确认链路通了再收紧滤波规则,能省掉大半排查时间。
本文还有配套的精品资源,点击获取