简介:面向DSP28335浮点数字信号处理器的CAN通信程序包,适合嵌入式开发工程师、自动化及汽车电子方向学习者,可解决eCAN模块初始化、消息收发、中断响应与错误处理等实际难题。压缩包共74个文件,内含30个h头文件、12个c源程序、3个asm汇编代码,另有cmd链接脚本、out可执行文件、map映射文件及log编译日志,完整覆盖从工程配置、代码编写到链接调试的完整流程。程序示例以C语言实现,涉及标准帧与扩展帧格式、总线波特率与滤波器配置、发送缓冲区写入与接收FIFO读取、中断服务例程设计,以及位错误和CRC错误检测后的恢复策略;同时包含DSP2833x标准外设库与eCAN驱动源码,便于对照寄存器操作理解底层机制。已有4006人学习下载,适合正在调试CAN总线、需要参考DSP2833x eCAN实现的中级开发者快速上手或移植到自有项目。 做DSP28335的CAN通信程序,很多人第一反应就是去抄一份TI官方例程改改ID,结果一上板就发现问题:初始化卡死、帧发不出去、接收中断不触发、和STM32对不上波特率。我前前后后调过好几版28335的eCAN程序,从寄存器裸写到中断收发再到和别的设备联调,这里把整个过程按实际踩坑顺序整理出来。文章不是抄手册,而是告诉你每个配置项为什么这样设、出了问题从哪查起,适合正在用DSP28335做CAN通信、或者准备从其他平台转过来的同学参考。
1. 动手前先盘一下DSP28335的CAN家底
1.1 eCAN模块到底给了你什么
DSP28335内部集成的是TI的eCAN模块,兼容CAN 2.0B协议,支持最高1Mbps的经典CAN速率,也支持标准帧和扩展帧。和STM32的bxCAN不太一样,28335的eCAN提供32个邮箱(Mailbox),每个邮箱可以独立配置成发送或者接收,这一点在需要多路报文收发的场景里非常灵活,比如一个邮箱收转速、一个邮箱收状态字、一个邮箱专门发控制命令,互不干扰。
需要特别说明的是,28335本身只有CAN控制器,芯片引脚上不存在所谓的CAN_H和CAN_L,GPIO18和GPIO19复用为CANRXA和CANTXA,输出的是数字电平,必须外接CAN收发器才能挂到总线上去。很多人第一次调程序,拿示波器点在CANTXA引脚上看不到差分波形,以为程序有问题,其实就是缺少收发器这一步。
另外,eCAN有一个很容易被忽略的点:所有寄存器访问都被EALLOW/EDIS保护。在CCS里调试,如果你直接打开寄存器窗口修改值,改完会被保护机制挡回去,看起来改了实际没写进去。程序里每次操作eCAN相关寄存器,都要包在EALLOW和EDIS之间,这是新手最常见的问题之一。
1.2 外部收发器和保护电路怎么配
收发器选型方面,常见的有TI的SN65HVD230、NXP的TJA1050、PCA82C250。SN65HVD230是3.3V供电,和28335电平匹配最方便,适合短距离板间通信;TJA1050和PCA82C250是5V供电,抗干扰能力和驱动能力更强,适合长距离或者工业现场,但和28335连接时要注意TXD/RXD引脚电平,必要时加电平转换或限流处理。
总线两端各需要一个120欧终端电阻,这个不是可选项。CAN总线是差分传输,终端电阻用来匹配阻抗、消除反射。做过一致性测试或者用示波器看过波形的人应该深有体会:没终端电阻时波形上升沿过冲明显,显性电平平台变形,长线场景下甚至会直接导致采样错误。
如果现场环境干扰比较重,比如电机驱动旁边走线、有变频器,建议在CANH和CANL上对地加TVS管,再串共模电感,有条件的话用带屏蔽的双绞线。工业总线接口常讨论的“TVS+TSS方案”在这里同样适用,总线端口要做浪涌防护,不然一次静电或者雷击感应就可能烧掉收发器和DSP的IO。
2. 波特率计算:SJW和位时间不是随便填的
2.1 位时间结构和采样点
CAN总线的波特率配置,本质上是配置一个位时间被分成多少个时间量子(TQ),以及每个字段的占比。28335的位时间由同步段、TSEG1、TSEG2三部分组成,同步段固定占1个TQ,TSEG1和TSEG2由CANBTC寄存器的TSEG1字段和TSEG2字段决定。寄存器里的值都要加1才是实际TQ数,这个特别容易搞错。
同步跳跃宽度SJW的作用是处理总线上的时钟偏差和信号抖动。当节点检测到总线边沿和本地时钟不同步时,会通过SJW调整采样点。SJW设得太小,重同步能力不够,长线或强干扰下偶发错误帧概率变大;设得太大,采样点位置会跳来跳去,反而得不偿失。工程上SJW一般取1到2个TQ就够用。
波特率计算公式可以简单写成:
波特率 = SYSCLKOUT / (BRP + 1) / (1 + (TSEG1 + 1) + (TSEG2 + 1))28335的主频SYSCLKOUT一般是150MHz,我实际调试时喜欢把总TQ数固定成15,这样采样点稳定在80%左右,和大多数CAN节点的采样点接近,联调时兼容性最好。
2.2 150MHz主频下的常用参数表
| 波特率 | BRP | TSEG1 | TSEG2 | SJW | 总TQ数 | 采样点 |
|---|---|---|---|---|---|---|
| 1Mbps | 9 | 10 | 2 | 1 | 15 | 80% |
| 500Kbps | 19 | 10 | 2 | 1 | 15 | 80% |
| 250Kbps | 39 | 10 | 2 | 1 | 15 | 80% |
| 125Kbps | 79 | 10 | 2 | 1 | 15 | 80% |
这个表是我自己算过、也在板子上验证过的,核心思路是保持位时间结构完全一致,只改变分频系数。比如1Mbps时,BRP=9表示分频10倍,TQ是66.7ns,15个TQ正好是1us;500Kbps时把BRP改成19,分频20倍,TQ变成133.3ns,15个TQ就是2us。这样所有速率下的采样点位置都是80%,不会因为切换波特率而需要重新评估采样点是否合适。
如果用的不是150MHz主频,把150替换成你的SYSCLKOUT,BRP重新算一遍就行。这里要强调一点:很多人喜欢把TSEG1设得很小,比如3或者4,总TQ数也少,这样做波特率照样能对上,但采样点会在70%以下。对单机和短总线没什么感觉,一旦挂多节点、走线稍长,错误帧就出来了,而且不好排查。
3. 收发程序实操:从初始化到能跑
3.1 时钟、引脚和eCAN初始化
完整初始化分四步:使能eCAN的外设时钟、配置GPIO复用为CAN功能、请求进入配置模式、写CANBTC波特率寄存器。进入配置模式是关键步骤,必须先置CANMC的CCR位,然后等待CANES的CCE位变成1,确认芯片已经进入配置模式之后再写CANBTC,否则寄存器写入无效。
void InitECan(void) { struct ECAN_REGS ECanaShadow; // 1. 使能eCAN-A外设时钟 EALLOW; SysCtrlRegs.PCLKCR0.bit.ECANAENCLK = 1; // 2. GPIO18 -> CANRXA, GPIO19 -> CANTXA GpioCtrlRegs.GPAMUX1.bit.GPIO18 = 1; GpioCtrlRegs.GPAMUX1.bit.GPIO19 = 1; EDIS; // 3. 请求进入配置模式 EALLOW; ECanaShadow.CANMC.all = 0; ECanaShadow.CANMC.bit.CCR = 1; ECanaRegs.CANMC.all = ECanaShadow.CANMC.all; EDIS; // 等待CCE置位,确认进入配置模式 do { ECanaShadow.CANES.all = ECanaRegs.CANES.all; } while (ECanaShadow.CANES.bit.CCE != 1); // 4. 配置波特率为1Mbps,主频150MHz EALLOW; ECanaShadow.CANBTC.all = ECanaRegs.CANBTC.all; ECanaShadow.CANBTC.bit.BRP = 9; ECanaShadow.CANBTC.bit.TSEG1 = 10; ECanaShadow.CANBTC.bit.TSEG2 = 2; ECanaShadow.CANBTC.bit.SJW = 1; ECanaRegs.CANBTC.all = ECanaShadow.CANBTC.all; // 5. 退出配置模式,回到正常模式 ECanaShadow.CANMC.bit.CCR = 0; ECanaRegs.CANMC.all = ECanaShadow.CANMC.all; EDIS; // 等待CCE清零 do { ECanaShadow.CANES.all = ECanaRegs.CANES.all; } while (ECanaShadow.CANES.bit.CCE != 0); }这套流程里最容易出问题的是第3步到第4步的衔接。有些代码把CCR置1之后马上写CANBTC,没有等CCE确认,这在某些芯片版本上可能碰巧成功,但在28335上大概率寄存器没写进去,导致实际波特率还是上电默认值。调试时如果发现波特率怎么配都不对,先检查这一步有没有等待CCE。
3.2 邮箱发送数据
发送过程可以分解成三个动作:配置邮箱为发送模式并写入ID和数据、使能邮箱、请求发送并等待应答。28335的每个邮箱都有一个TRS请求位和一个TA应答位,请求发送后硬件会自动把数据移位输出,发送成功之后TA位置1。
Uint8 msgTx[8] = {0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0}; void CanSend_Box1(void) { // 邮箱1配置为发送 EALLOW; ECanaRegs.CANME.all = 0; // 先关闭所有邮箱 ECanaRegs.CANMD.bit.MD(1) = 0; // 0=发送,1=接收 ECanaRegs.MBOX(1).CANMSGID.bit.MSGID = 0x123; ECanaRegs.MBOX(1).CANMSGID.bit.IDE = 0; // 标准帧 ECanaRegs.MBOX(1).CANMSGCTRL.bit.DLC = 8; // 8字节数据 // 数据写入邮箱数据区 ECanaRegs.MBOX(1).CANMDL.all = (msgTx[0] << 24) | (msgTx[1] << 16) | (msgTx[2] << 8) | msgTx[3]; ECanaRegs.MBOX(1).CANMDH.all = (msgTx[4] << 24) | (msgTx[5] << 16) | (msgTx[6] << 8) | msgTx[7]; ECanaRegs.CANME.bit.ME(1) = 1; // 使能邮箱1 ECanaRegs.CANTRS.bit.TRS(1) = 1; // 请求发送 EDIS; // 等待发送完成 while (ECanaRegs.CANTA.bit.TA(1) == 0) {} // 清除发送应答标志,写1清除 EALLOW; ECanaRegs.CANTA.all = 0xFFFFFFFF; EDIS; }这段代码里我故意把数据字节的拼接顺序写得很直观:CANMDL寄存器的高24位到低8位对应Byte0到Byte3。这里有一个和STM32对比容易混淆的地方,28335默认不保证CAN数据域字节序和MCU内存字节序一致,如果总线上的对端设备拆包后发现数据顺序反了,检查CANMC寄存器的DBO位设置,同时看对端解析器的字节序约定。
发送等待TA标志这一段,正式项目中不建议用空轮询。如果总线处于bus-off或者没有其他节点应答ACK,TA一直不置位,程序就死等在这里。项目里一般加上超时处理或者配合中断来做,调试阶段呢,裸轮询其实反而方便,因为代码路径简单,出问题一眼能看出来。
3.3 中断接收与验收滤波
接收邮箱建议直接用中断方式。28335的eCAN中断在PIE中断控制器中挂在第9组,eCAN0INTA对应PIEIER9的INTx5。接收中断的ISR里,第一件事是读CANRMP寄存器判断是哪个邮箱收到了数据,然后把数据从对应的CANMDL和CANMDH拷贝出来,最后必须清除接收挂起标志RMP,否则这个邮箱再也不会收下一帧数据。
interrupt void eCAN0INTA_ISR(void) { Uint8 i; Uint16 rxLen; if (ECanaRegs.CANRMP.bit.RMP(0) == 1) { rxLen = ECanaRegs.MBOX(0).CANMSGCTRL.bit.DLC; if (rxLen > 8) rxLen = 8; rxData[0] = (ECanaRegs.MBOX(0).CANMDL.all >> 24) & 0xFF; rxData[1] = (ECanaRegs.MBOX(0).CANMDL.all >> 16) & 0xFF; rxData[2] = (ECanaRegs.MBOX(0).CANMDL.all >> 8) & 0xFF; rxData[3] = ECanaRegs.MBOX(0).CANMDL.all & 0xFF; rxData[4] = (ECanaRegs.MBOX(0).CANMDH.all >> 24) & 0xFF; rxData[5] = (ECanaRegs.MBOX(0).CANMDH.all >> 16) & 0xFF; rxData[6] = (ECanaRegs.MBOX(0).CANMDH.all >> 8) & 0xFF; rxData[7] = ECanaRegs.MBOX(0).CANMDH.all & 0xFF; rxDoneFlag = 1; // 清接收挂起标志,写1清除 ECanaRegs.CANRMP.all = 0xFFFFFFFF; } // 清邮箱中断标志 ECanaRegs.CANINT.all = 0xFFFFFFFF; // 应答PIE第9组 PieCtrlRegs.PIEACK.all = PIEACK_GROUP9; }接收邮箱在初始化时要做几件事:配置邮箱方向为接收、写入期望接收的报文ID、配置该邮箱的局部验收屏蔽寄存器LAM、使能邮箱、打开该邮箱的中断屏蔽MIM。这里我想单独说一下LAM,因为网上很多资料会把SJA1000或者STM32的验收滤波概念直接搬过来,说28335有ACCCode和AccMask寄存器,这在eCAN上是不准确的。
28335每个邮箱有一个独立的LAM寄存器,置1的位表示不比较该ID位,置0的位表示必须匹配。如果要把邮箱0设置成只收标准ID为0x100的帧,MSGID填0x100,LAM低11位全部置0;如果不想过滤任何ID,就把LAM相关位置成1。它的思路和STM32的屏蔽位模式类似,但寄存器名和位定义完全不同,查手册时认准“Local Acceptance Mask”即可。
3.4 和VOFA等调试工具的配合
热词里提到dsp28335和VOFA通讯,这里补充一个常见误区:VOFA+这类上位机工具一般是走串口的示波器,而28335的CAN走的完全是另一套协议栈,DSP的CANTXA引脚输出不能直接接USB转TTL的串口工具。想用VOFA看CAN波形,正确链路是:
DSP28335的CAN收发器 -> 总线 -> USB-CAN分析仪 -> PC上的VOFA或CAN上位机USB-CAN分析仪在调试阶段几乎是必需品,它能帮你抓总线上的原始报文,确认DSP发出的帧格式对不对,也能模拟一个节点回数据,验证DSP的接收逻辑。很多设备同时支持CAN转串口功能,这时候再把数据送到VOFA做波形显示,是商业项目里很常见的调试组合。
4. 实测排坑:波形、总线错误和跨芯片对接
4.1 用示波器判断通信好坏
判断CAN通信好坏,最直接的工具就是示波器。测差分波形要接在CANH和CANL之间,不能只对地量一路信号。以PCA82C250这类5V供电的收发器为例,隐性状态时CANH和CANL都接近2.5V,差分电压接近0V;显性状态时CANH被拉高到约3.5V,CANL被拉到约1.5V,差分电压约2V。如果测到的差分电压明显偏低,比如只有0.5V,先查收发器供电和终端电阻,再看是不是线缆太长导致信号衰减。
波形质量方面,注意看上升沿和下降沿是否平滑。标准CAN波形会有明显的梯形边沿,因为收发器内部有斜率控制或必须外接斜率电阻。边沿太陡会产生高频辐射,边沿太缓或出现平台状台阶,说明总线阻抗不匹配、分支线过长或节点数量过多。另外,从波形上位时间的宽度反过来能算出实际波特率,怀疑波特率配置不对时,拿示波器量一个显性位的时间,乘以总TQ数,看是否和理论位时间一致。
4.2 常见总线状态与错误计数器
CAN控制器内部有两套错误计数器:发送错误计数TEC和接收错误计数REC。28335的CANES寄存器里有错误状态位,可以读出当前是否处于错误主动、错误被动或者bus-off状态。协议规定,TEC或REC超过127就会进入错误被动,超过255就会进入bus-off。
实际调试中,最常见的错误是ACK错误。发送节点发出帧后,如果在应答槽没有检测到显性电平,就说明总线上没有其他节点成功接收这一帧。原因是多方面的:总线上只有一个节点、对端没有配置好、波特率不一致、没有终端电阻导致信号畸形。其次是位填充错误和CRC错误,这类错误十有八九是波特率两边其实不同。注意,CAN是CSMA/CRC+ACK的机制,波特率不一致时,不仅数据收不到,发送端还会因为收不到ACK而反复重发,最终进入bus-off。
进入bus-off之后,控制器会停止参与总线通信。恢复逻辑不能粗暴地整片DSP复位,而是要让eCAN重新走一遍初始化流程:进入配置模式、清空错误计数器、退出配置模式。中断处理函数里如果检测到BO位,建议置一个标志位,在主循环里做恢复操作,不要在ISR里做耗时动作。
4.3 和STM32对测时的几个坑
DSP28335和STM32做CAN互联是特别常见的组合,两边CAN控制器都是CAN 2.0兼容,协议层面通信没问题,但配置习惯不同会带来一些坑。
第一是采样点不一致。STM32 bxCAN配置波特率时使用的是BS1和BS2,分别对应28335的TSEG1和TSEG2。很多STM32例程默认BS1为9、BS2为2,采样点在87.5%左右;而28335如果TSEG1和TSEG2配成10和2,采样点就是80%,两边差距不算大,短距离没问题。但如果两边参数差异大,一个采样点70%、一个85%,在总线负载高或者布线质量差时,就会随机出现错误帧。所以联调时,优先把两边的采样点配置到接近的值,不要只看波特率数字一样就以为万事大吉。
第二是帧格式和ID长度。28335的邮箱在配置MSGID时,要显式设置IDE位:标准帧IDE=0,扩展帧IDE=1。STM32那边同样要区分标准ID和扩展ID。如果一边发标准帧、一边用扩展帧滤波,报文永远进不了接收邮箱,但发送端看着是成功的,因为CAN应答只关心帧有没有被正确接收,不关心接收方滤波是否通过。
第三是两个节点也要考虑终端电阻。很多人在桌面上用两个开发板对测,结果每块板子都自带120欧电阻,连接在一起等效终端电阻变成60欧,波形会变得非常难看。这时候要么去掉一块板的电阻,要么改用外部电阻,保证总线上只有两端各一个120欧。
4.4 问题速查表
| 现象 | 可能原因 | 排查顺序 |
|---|---|---|
| 完全发不出去,TA一直不置位 | 总线无ACK(单节点、收发器没接、终端电阻缺失) | 示波器看TXD、CANTXA波形,再接CAN分析仪确认总线电平 |
| 能发送但对端收不到 | 对端接收邮箱未使能、验收滤波不匹配、ID/帧格式不一致 | 用USB-CAN分析仪抓包,看报文ID和帧类型 |
| 接收中断不触发 | 邮箱未使能、MIM未打开、PIEIER9未使能、RMP标志未清 | 检查邮箱状态寄存器,单步看CANRMP和CANINT值 |
| 偶发错误帧、总线故障率高 | 波特率配置不对、SJW太小、终端电阻缺失、分支线过长 | 示波器看波形边沿,用CAN分析仪统计错误计数器 |
| 一上电就bus-off | GPIO复用配置错误、CANTXA引脚被其它外设抢占、总线电平被拉死 | 检查GPAMUX配置,断开总线单独看TXD波形 |
| 和STM32通信走动时好时坏 | 两侧采样点不一致、共地不良、波特率精度差异 | 统一采样点配置,测量两侧参考地压差,必要时加共模电感 |
这张表是我调试时实际对照过的排查顺序。遇到问题先不要急着改代码,先确认物理层正常,再查配置,最后才是逻辑问题。CAN这种东西,软件配置来回就那么几个寄存器,真正的坑大多在接线、波形和采样点上。
最后分享一个我自己的习惯:每次初始化eCAN,都会在CCR置1后加一个延时再等CCE,不要把等待循环写得太紧,这样虽然代码看起来多几行,但在某些主频配置不稳定、调试器介入的场合,可以有效避免CCE还没稳定就误判超时的问题。另外,多邮箱项目里建议把邮箱号固定下来并写在注释里,比如邮箱0接收心跳、邮箱1发送控制字、邮箱2接收状态反馈,不然调试一段时间后自己都会忘记哪个邮箱在干什么。
本文还有配套的精品资源,点击获取