ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

TMS320F28377D CAN通信调试全攻略:寄存器配置、中断链路与常见坑

TMS320F28377D CAN通信调试全攻略:寄存器配置、中断链路与常见坑 刚拿到TMS320F28377D/F28379D的板子兴致勃勃把CAN通信调通结果数据发不出去、发送返回成功但总线上一点反应都没有、中断死活进不去——这三个症状基本概括了刚开始调CAN的调试日常。原本以为配几个寄存器就能跑起来实际动手才发现D_CAN模块的初始化顺序、消息对象配置、中断使能链路都比想象中要刁钻任何一个环节没对齐表现出来就是“看起来一切正常但就是不通”。这篇文章就是冲着这些问题来的。我会按寄存器配置到中断处理的完整流程把每一步的关键寄存器和位域讲清楚再把我实际踩过的坑、排查过的故障一并整理出来。内容主要面向正在做TMS320F28377D/F28379D CAN通信开发的同学尤其是从STM32或者其他MCU平台转过来的第一次面对C2000系列DCAN模块时这份指南能帮你少走不少弯路。1. 整体设计与思路拆解1.1 为什么TMS320F28377D/F28379D的CAN模块总让人犯迷糊先把这个模块的底细摸清楚。F28377D和F28379D是TI C2000家族的Delfino系列双核MCU片上集成了两个CAN模块文档里叫DCAN1和DCAN2对应外设名是CANA和CANB。这两个模块不是简单的“CAN控制器”概念而是基于Bosch D_CAN架构的完整协议控制器内置消息RAM支持CAN 2.0B协议最高通信速率1Mbps。很多人拿到手就犯迷糊根源在于D_CAN的工作机制和传统SJA1000那套“发送缓冲区接收缓冲区”完全不同。D_CAN的核心是“消息对象Message Object”机制也就是在消息RAM里划分出若干个邮箱每个邮箱可以独立配置成发送邮箱或接收邮箱硬件根据ID匹配和掩码设置自动完成收发过滤。这意味着你要做的寄存器配置本质上是往消息RAM里写入一个个“邮箱描述符”。我见过不少同学把配置寄存器理解成“往寄存器里写几个值就完事”这个理解不能说错但容易忽略两个关键点一是消息对象配置必须通过IFxInterface寄存器组间接写入不是直接内存访问二是寄存器配置的生效时机有讲究比如位定时参数必须在初始化模式下才能修改。这就像装修房子图纸上画好之后你还得等物业允许动工才能进场施工。1.2 库函数和寄存器操作怎么选TI官方提供了C2000Ware里的CAN驱动库封装了CAN_setupMessageObject、CAN_sendMessage这类函数。我在早期项目里也用过库函数确实上手快但后来发现一个问题一旦通信出现异常库函数包的太严实你根本不知道底层哪个寄存器处于什么状态。比如CAN_sendMessage返回true你以为帧发出去了实际上可能只是把消息对象写入发送请求总线上有没有ACK完全取决于总线状态和其他节点。我的做法是寄存器操作打底库函数作为对照参考。调试阶段全部用寄存器方式把每个位的变化看清楚排查完毕后再决定要不要封装成自己的驱动接口。这听起来工作量更大但实际上排查问题的时间会大大缩短。举个例子配置位定时时库函数里写了一句CAN_setBitRate你在示波器上看到波特率不对根本不知道是预分频算错了还是时间段配置不合理。如果用寄存器操作打开CAN_BTR一看每个字段的值清清楚楚算一遍就知道问题出在哪。另外有一个前置条件值得多说一句动手写CAN驱动之前务必确认你用的是哪个时钟源。F2837x的CAN模块时钟来自系统时钟经过分频后的外设时钟默认情况下CAN时钟频率不等于CPU频率。如果你的系统主频是200MHz外设时钟可能是100MHz而CAN模块的位定时时间量子是以这个外设时钟为基准的。很多波特率算不对的案例根子都在这里。我会在下一节详细展开计算过程。2. 核心细节解析与实操要点2.1 波特率寄存器配置算错不会报错只会隐性问题CAN波特率配置是第一个大坑而且是那种“不会立刻暴露”的坑。寄存器写错往往不会导致初始化失败而是表现为通信偶发错误、丢帧、甚至两个节点正常收发但OTAN节点接收异常排查起来非常痛苦。F2837x的CAN波特率由三部分决定预分频值BRP、时间段1TSEG1、时间段2TSEG2。一个位时间由三部分组成同步段Sync_Seg固定为1个时间量子、传播段相位缓冲段1对应TSEG1、相位缓冲段2对应TSEG2。公式长这样CAN波特率 CAN模块时钟频率 / (预分频值 × (1 TSEG1 TSEG2))这里的时间量子就是BRP分频后的最小时间单位整个位时间就是若干个时间量子拼出来的。举个实际例子假设CAN模块时钟为20MHz我想要1Mbps波特率那么一个位时间需要20个时钟周期。设BRP1则时间量子为1个时钟周期位时间需要20个时间量子。取TSEG113TSEG26则113620正好得到1Mbps。采样点位置在同步段和TSEG1之后也就是(113)/2070%。采样点的选择有讲究。CAN总线标准里建议采样点在75%到85%之间因为总线上的信号经过线缆传输存在延迟物理层收发器也有延迟采样点太靠前或太靠后都会降低抗干扰能力。我实测下来500kbps及以上波特率时采样点设置在80%左右最稳。换算到上面的公式就是让TSEG1占总时间段的比重为80%左右。比如1Mbps波特率、20个时间量子的情况下TSEG115、TSEG24采样点在80%。这是我个人比较推荐的配置。还有个容易漏掉的细节F2837x的D_CAN模块支持自动重同步跳转宽度SJWSynchronization Jump Width要合理设置。SJW设太小总线时钟偏差容忍度低设太大又会影响采样点位置。实际项目中我通常把SJW设成与TSEG2相同或略小比如TSEG24时SJW2或4这样既保证重同步能力又不至于干扰正常采样。下面给一段通用的位定时配置代码以CAN模块时钟20MHz、目标波特率500kbps为例// 目标500kbpsCAN时钟20MHz // 一个位时间 20MHz / 500kbps 40个时钟周期 // BRP1时间量子1个时钟周期1TSEG1TSEG240 // TSEG131TSEG28采样点 (131)/40 80% #define CAN_BRP_VAL 0 // 预分频值为 BRP1这里BRP1 #define CAN_TSEG1_VAL 30 // TSEG1字段实际值 31-1 #define CAN_TSEG2_VAL 7 // TSEG2字段实际值 8-1 #define CAN_SJW_VAL 7 // SJW字段实际值 8-1 // 进入初始化模式使能寄存器配置访问CCE位 CANA_CTRL.BIT.Init 1; while(CANA_ES.BIT.InitAck ! 1); // 等待进入初始化模式 CANA_CTRL.BIT.CCE 1; // 使能位定时寄存器写入 // 配置位定时寄存器 CANA_BTR.BIT.BRP CAN_BRP_VAL; CANA_BTR.BIT.TSEG1 CAN_TSEG1_VAL; CANA_BTR.BIT.TSEG2 CAN_TSEG2_VAL; CANA_BTR.BIT.SJW CAN_SJW_VAL; // 退出配置模式回到正常模式 CANA_CTRL.BIT.CCE 0; CANA_CTRL.BIT.Init 0; while(CANA_ES.BIT.InitAck ! 0); // 等待退出初始化模式这里有一个非常关键的注意点必须在确认InitAck置位后才能修改位定时寄存器而不是设置Init位后立刻改。硬件需要几个时钟周期完成模式切换你要是直接往下写BTR值根本写不进去。这个坑我第一次调的时候踩过查了半天发现BTR一直是默认值后来才意识到是时序问题。2.2 消息对象配置详解发送邮箱和接收邮箱的细节D_CAN模块支持32个消息对象邮箱编号0到31分别存放在消息RAM中。每个邮箱可以独立配置为发送或接收配置的核心是通过IF1或IF2寄存器组读取和写入消息RAM。IF寄存器组相当于消息RAM的读写端口你先把要写的内容放到IF的命令掩码寄存器、消息控制寄存器、ID寄存器、数据寄存器里然后写IFxCMD触发传输硬件会自动把数据搬运到对应的消息RAM位置。这个机制有点像一个代办窗口你写好的表单交给窗口窗口工作人员帮你归档。好处是不用直接操作消息RAM地址坏处是每一步都得等Busy位清掉才能进行下一步。配置发送邮箱的基本流程// 以邮箱0为发送邮箱标准帧格式ID0x123 // 等待IF1空闲 while(CANA_IF1CMD.BIT.Busy ! 0); // 设置命令掩码访问完整邮箱描述符 CANA_IF1CMD.ALL 0x00000000; CANA_IF1CMD.BIT.DATA_A 1; // 写数据A寄存器 CANA_IF1CMD.BIT.DATA_B 1; // 写数据B寄存器 CANA_IF1CMD.BIT.CONTROL 1; // 写控制寄存器 CANA_IF1CMD.BIT.ARB 1; // 写仲裁寄存器 CANA_IF1CMD.BIT.MASK 1; // 写掩码寄存器 CANA_IF1CMD.BIT.TR 1; // 请求写入消息RAM // 配置消息控制寄存器使能发送中断不使能接收中断 CANA_IF1MCTL.ALL 0; CANA_IF1MCTL.BIT.UMASK 1; // 使用接收掩码 CANA_IF1MCTL.BIT.INT_EN 1; // 使能中断 CANA_IF1MCTL.BIT. TX_IE 1; // 发送中断使能 CANA_IF1MCTL.BIT. EOB 1; // 消息RAM中的最后一个消息对象 CANA_IF1MCTL.BIT.DLC 8; // 数据长度为8字节 // 配置仲裁寄存器标准帧ID0x123方向为发送 // 注意DIR位0发送1接收 CANA_IF1ARB.ALL 0; CANA_IF1ARB.BIT.ID 0x123; CANA_IF1ARB.BIT.DIR 0; // 发送方向 CANA_IF1ARB.BIT.MSG_VAL 1; // 使能该消息对象 CANA_IF1ARB.BIT.XTD 0; // 标准帧 // 写入消息对象编号 CANA_IF1CMD.BIT.MESSAGE_NUMBER 0; // 等待写入完成 while(CANA_IF1CMD.BIT.Busy ! 0);接收邮箱的配置思路类似但有三个容易错的地方第一DIR方向位必须置1表示接收。很多人在发送和接收邮箱共用一个初始化函数时忘了改这一位结果接收邮箱永远收不到数据。我调试时习惯在配置完邮箱后专门回读一次IFx状态把ID和方向打印出来核对一遍能省很多事。第二掩码寄存器的配置决定接收范围。如果掩码寄存器里Mask字段全部为0表示对ID不做匹配该邮箱可以接收总线上所有帧如果Mask字段置1则对应ID位必须完全匹配。这里很多人搞反了以为Mask置1才是“不关心”实际上D_CAN模块里掩码位为1表示“必须匹配”为0表示“无需匹配”。初次接触确实容易绕晕。第三接收邮箱的DLC可以不设。D_CAN模块在接收帧时会从总线帧里解析实际DLC并覆盖到消息控制寄存器里你只要在读取数据时以实际接收到的DLC为准。所以接收邮箱初始化时DLC配置成0就行没必要跟发送端保持一致。接收邮箱配置核心片段// 邮箱1为接收邮箱ID0x123带掩码 while(CANA_IF2CMD.BIT.Busy ! 0); CANA_IF2CMD.ALL 0; CANA_IF2CMD.BIT.DATA_A 1; CANA_IF2CMD.BIT.DATA_B 1; CANA_IF2CMD.BIT.CONTROL 1; CANA_IF2CMD.BIT.ARB 1; CANA_IF2CMD.BIT.MASK 1; CANA_IF2CMD.BIT.TR 1; CANA_IF2MCTL.ALL 0; CANA_IF2MCTL.BIT.UMASK 1; CANA_IF2MCTL.BIT.INT_EN 1; // 接收中断 CANA_IF2MCTL.BIT.EOB 1; CANA_IF2ARB.ALL 0; CANA_IF2ARB.BIT.ID 0x123; CANA_IF2ARB.BIT.DIR 1; // 接收方向 CANA_IF2ARB.BIT.MSG_VAL 1; CANA_IF2ARB.BIT.XTD 0; // 掩码配置Mask字段全0表示接收所有ID CANA_IF2MASK.ALL 0; CANA_IF2MASK.BIT.MD 1; // 使用ID掩码模式接收所有帧 CANA_IF2CMD.BIT.MESSAGE_NUMBER 1; while(CANA_IF2CMD.BIT.Busy ! 0);2.3 硬件电路的那些事收发器、终端电阻和供电问题软件配置固然重要但CAN通信调不通很多时候问题出在硬件电路上而不是寄存器上。我注意到有一些做项目的新手会问“CAN通信模块芯片能否给板子供电”这个问题的逻辑有点反了。CAN收发器的作用是电平转换它把MCU的TX/RX电平转换为CAN总线的差分电平它本身是一个用电设备不是一个供电设备。换句话说你需要给收发器供电它才能工作而不是靠它给板子供电。以最常见的PCA82C250/TJA1050收发器为例VCC引脚必须外部供电通常为3.3V或5V具体看芯片型号。CAN_TX和CAN_RX引脚连接MCU对应引脚CANH和CANL连接总线。如果收发器供电电压和MCU的CAN引脚电平不匹配通信必然出问题。F28377D/F28379D的CAN引脚是3.3V逻辑如果你的收发器是5V供电还用了推挽输出轻则逻辑混乱重则损坏MCU引脚。这种情况必须加电平转换或使用支持3.3V供电的收发器型号。终端电阻也是个大坑。CAN总线规范要求网络两端各接一个120欧姆终端电阻用于阻抗匹配避免信号反射。很多人图省事只接一端甚至不接短距离调试时可能也能通信但信号质量差距离一长就丢帧。我见过一个现场应用两个节点距离不到50米偶尔报错排查了半天最后发现终端电阻接到中间去了挪到两端后问题消失。位置也很重要不是接上就行。还有共地问题。CAN总线虽然是差分信号理论上有共模抑制能力但收发器的供电参考地必须一致。两个节点如果各自用隔离电源供电而不共地总线在雷击或电磁干扰下容易损坏收发器。通信异常时先用万用表量一下两个节点的地电位差超过一定范围就要考虑加隔离方案。我给一个常用的CAN节点硬件检查清单做参考收发器VCC引脚电压正常用万用表实测MCU TX引脚到收发器TXD引脚连线正确RX/TX不要接反CANH和CANL连线正确不要交叉总线上两端各有一个120欧姆终端电阻断电状态下量CANH和CANL之间阻值约为60欧姆两个节点的地电位差正常最好直接共地这些都是“常识”但每次项目出问题回头查总能从里面揪出一条没做到的。特别是CANH和CANL接反这个低级错误我调试时至少碰过两次症状是示波器能看到差分波形但就是不通因为A节点发的帧被B节点当成了错误帧。3. 实操过程与核心环节实现3.1 完整初始化流程从GPIO到CAN模块的步骤编排前面把原理和细节讲了不少这一节我给出一套完整的初始化流程可以直接套用到自己的项目里。我以F28377D为主控、CAN模块使用CANA外设、系统时钟配置完成后进入初始化程序为例场景是电机控制器通过CAN与上位机通信一个发送邮箱周期性上报状态一个接收邮箱接收控制指令。第一步使能GPIO和CANA模块的时钟。F2837x的GPIO和外设时钟默认是关闭的必须通过系统控制寄存器使能。// 使能GPIO和CANA外设时钟 CpuSysRegs.PCLKCR0.bit.CANA_EN 1; // 使能CANA模块时钟 CpuSysRegs.PCLKCR13.bit.GPIOA_EN 1; // 使能GPIOA端口时钟第二步配置GPIO为CAN功能引脚。F2837x的引脚复用功能较多GPIO30、GPIO31可以复用为CANA_RX和CANA_TX寄存器的配置方法是先解锁GPIO配置寄存器再设置复用功能最后设置内部上拉。// 解锁GPIO配置寄存器 EALLOW; // 配置GPIO30为CANA_RX输入GPIO31为CANA_TX输出 GpioCtrlRegs.GPAMUX2.bit.GPIO30 1; // 复用功能1对应CANA_RX GpioCtrlRegs.GPAMUX2.bit.GPIO31 1; // 复用功能1对应CANA_TX GpioCtrlRegs.GPADIR.bit.GPIO30 0; // 输入 GpioCtrlRegs.GPADIR.bit.GPIO31 1; // 输出 GpioCtrlRegs.GPAPUD.bit.GPIO30 0; // 使能内部上拉 GpioCtrlRegs.GPAPUD.bit.GPIO31 0; // 重新锁定GPIO配置寄存器 EDIS;第三步按照前面2.1节的方法配置位定时。第四步配置消息对象。这两步在2.1和2.2节已经有代码片段这里不再重复。第五步配置中断并使能整个CAN通信链路。3.2 发送与接收中断的完整处理链路CAN模块的中断处理是一个多层使能的过程每一层都必须打开缺一层中断就进不去。我的总结是“三级使能”第一级CAN模块自身中断使能。消息对象配置里INT_EN已经置位这只是让中断事件被记录到CAN_INT寄存器中真正想让它传输到中断控制器还需要设置CAN_IE寄存器的全局中断使能位和对应INT0/INT1的使能位。第二级PIE控制器使能。F2837x的中断架构是外设中断事件打包到PIE外设中断扩展模块再由PIE导向CPU。CAN模块的中断通常连接到PIE模块的INT9组需要使能对应组的使能位并在PIE向量表里注册中断服务函数。第三级CPU总中断使能。在main函数里调用EINT、IER相关操作打开全局中断。代码里是这样体现的// 使能CAN模块中断输出到PIE CANA_IE.BIT.INT0_EN 1; // 使能模块级中断输出INT0 CANA_IE.BIT.INT1_EN 1; // 使能模块级中断输出INT1 // PIE级使能 PieCtrlRegs.PIEIER9.bit.INTx1 1; // CANA_0中断对应PIE9.1 PieCtrlRegs.PIEIER9.bit.INTx2 1; // CANA_1中断对应PIE9.2 // 中断向量表注册 EALLOW; PieVectTable.CANA_0_INT CANARX_ISR; PieVectTable.CANA_1_INT CANATX_ISR; EDIS; // 使能PIE组9中断 IER | M_INT9; EINT;发送中断的服务函数里不要一上来就清标志先读CAN_ES里的TXOK位确认这次中断是发送成功再进入下次发送或置事件标志。否则中断触发条件没搞清楚容易误清标志导致发送状态丢失。interrupt void CANATX_ISR(void) { // 检查是哪个邮箱触发了发送中断 if (CANA_TXRQ.ALL (1 0)) // 邮箱0的发送请求还未完成 { // 发送还没结束直接返回 EINT; return; } // 读取TxOk位并清除 if (CANA_ES.BIT.TXOK 1) { CANA_ES.BIT.TXOK 1; // 写1清除 // 设置发送完成标志 CanSendDoneFlag 1; } // 清除中断标志 CANA_INT.BIT.INT0_ID 0; // 清除PIE模块级的应答 PieCtrlRegs.PIEACK.all PIEACK_GROUP9; EINT; }接收中断的服务函数思路类似但多一个步骤必须从CAN_NDAT寄存器确认哪些邮箱有新数据读完后写1清除NDAT位。同时因为消息对象可以配置成FIFO模式中断服务里还要判断当前是哪个邮箱并决定是否切换缓冲区。interrupt void CANARX_ISR(void) { Uint16 msgIndex; Uint16 data[8]; Uint16 i; // 检查NDAT寄存器确认哪个接收邮箱有新数据 if (CANA_NDAT.ALL (1 1)) // 邮箱1有新数据 { // 写入命令读取消息对象1的数据 while(CANA_IF2CMD.BIT.Busy); CANA_IF2CMD.ALL 0; CANA_IF2CMD.BIT.DATA_A 1; CANA_IF2CMD.BIT.DATA_B 1; CANA_IF2CMD.BIT.CONTROL 1; CANA_IF2CMD.BIT.ARB 1; CANA_IF2CMD.BIT.MASK 1; CANA_IF2CMD.BIT.TR 1; CANA_IF2CMD.BIT.MESSAGE_NUMBER 1; while(CANA_IF2CMD.BIT.Busy); // 读取ID和数据字段 msgIndex CANA_IF2ARB.BIT.ID; for (i 0; i 8; i) { data[i] CANA_IF2DATA.BIT.DATA[i]; } // 清除NDAT标志 CANA_NDAT.ALL (1 1); } // 清除模块中断标志 CANA_INT.BIT.INT0_ID 0; // 清除PIE应答 PieCtrlRegs.PIEACK.all PIEACK_GROUP9; EINT; }3.3 调试阶段的验证方法初始化全部写完不要急着和外部设备对接先在两个开发板之间做回环测试。F2837x的DCAN模块内置测试模式可以开启回环这样发出去的帧自己就能收回不用接总线时候也能验证收发路径。测试模式下发送和接收邮箱都配置好然后在主循环里定时调用发送函数在接收中断里打点观察计数这个自测通过后再接外部设备。如果手头只有一个节点没有对端节点CAN通信确实无法完成因为CAN收发必须要有总线回应ACK。D_CAN模块在无应答时会把发送错误计数器不断累加直到总线关闭BUS OFF状态。如果你在调试中看到CAN_ES寄存器的BO位为1说明之前发送一直没被应答这时候首先检查总线上有没有另一个节点在正常监听再看终端电阻是否匹配。这种“单节点发送一直报错”的情况非常常见我也不例外地踩过。当时以为是把某个寄存器配置错了反复检查代码最后发现是调试环境里只有一个节点在跑。后来我习惯在电脑上接入一个USB-CAN分析仪既是理想的第二节点还能直接看总线上的波形和报文调试工作效率能提升不少。4. 常见问题与排查技巧4.1 典型故障汇总速查表把实际项目中遇到的CAN通信故障整理成一张速查表。调试遇到问题先对照症状定位方向再逐项排查能比翻手册快得多。故障现象可能原因排查方法发送后TXOK为0错误计数持续增加总线上无其他节点或终端电阻缺失接入第二节点检查CANH/CANL之间阻值发送成功但对方收不到接收邮箱掩码配置错误ID被过滤检查接收邮箱掩码寄存器和ID配置接收中断始终不触发PIE使能缺失或CAN模块中断未正确配置按“三级使能”链路逐级排查偶发丢帧示波器看波形有毛刺终端电阻位置错误或采样点设置不当检查网络两端电阻重新计算采样点两节点通信第三节点加入后通信故障CAN总线拓扑问题分支线过长总线采用手拉手拓扑分支尽量短发送失败且日志报总线关闭总线长期无ACK进入BUS OFF检查总线连接软件中处理BUS OFF恢复表格只起到定位作用接下来把几个高频问题展开讲。4.2 中断进不去三级使能链路逐个击破中断进不去是调试中出现频率最高的问题。我的排查方法是从CPU往CAN模块方向逐级确认而不是反过来。先在CAN模块级看。消息对象配置中的INT_EN位是否置1CAN_IE寄存器的INT0_EN或INT1_EN是否置位再把断点设在CANARX_ISR入口用调试器手动把CANA_NDAT对应位置1看中断能不能触发。如果手动置位能触发说明模块级到PIE级的链路正常问题出在中断源事件上——大概率是消息对象的INT_EN没配好或接收邮箱根本没收到数据。如果手动置位也不触发检查PIE向量表注册是否正确PIEIER9对应位是否置1IER的M_INT9是否开启。这个“手动置位”的技巧非常管用。它能帮你把问题从“接收路径有问题”和“中断链路有问题”中区分开。我用这个方法排查出不止一次的“假中断问题”模块级寄存器配置都正常但PIE向量表里地址写错了导致即使中断源触发也跳转不到正确的服务函数。还有一种比较容易被忽略的情况中断服务函数退出前必须清除PIEACK位否则后续中断不会再被响应。有些同事写了中断函数的业务逻辑但忘了在末尾写PieCtrlRegs.PIEACK.all PIEACK_GROUP9结果所有中断只进一次。这个代码就藏在上面3.2节的例子里如果你的代码段里没有这一句可以仔细找找。4.3 总线关闭和验收滤波两个隐藏较深的坑总线关闭BUS OFF这个问题启动阶段最容易踩。具体表现是CAN模块初始化完成后第一次发送帧时对方还没准备好监听导致总线持续无ACK发送错误计数器超过255后就进入BUS OFF状态。BUS OFF状态下CAN模块会自动从总线上断开不再参与收发。很多人的处理方式是“重新初始化CAN”但这里有个坑单纯把初始化代码再跑一遍可能不够。因为D_CAN模块进入BUS OFF后需要等待协议规定的128个总线空闲周期才能自动恢复。在恢复之前你写再多次初始化指令也没用这是规范层面的行为。推荐的处理方式有两种。一种是在总线关闭中断服务函数里不重启模块而是设置标志位等总线恢复后再重新挂载消息对象。另一种方式是在初始化完成前先确认总线状态确保总线上有对端设备在监听再允许用户操作发送。实际项目中我两种都写过前者适合需要热插拔对端节点的场景后者适合系统上电后固定网络拓扑的场景。验收滤波是另一个隐藏较深的坑。如果你配置接收邮箱的掩码为全0接收所有帧但总线上同时存在多个ID的报文D_CAN模块会把这些报文全部存入接收邮箱但一次只能存一个后到的帧会覆盖先到的帧。这会导致“收到数据但总是丢帧”的错觉。解决办法是根据业务报文ID设置掩码或把接收邮箱配置成FIFO模式利用多个邮箱自动排队缓存。我还遇到过一个奇特的情况接收邮箱设置在验收滤波时把错误帧也接收进来了。EFF错误帧和RTR远程帧这些特殊帧在配置掩码时也要考虑。比如远程帧没有数据字段如果接收邮箱没有处理RTR帧的特殊逻辑会把DLC显示为正常但读到空数据。处理方式有两种一种是在接收中断里额外判断RTR位另一种是配置消息对象时不使能RTR帧的接收。4.4 几个容易被忽略的“小事”有些细节看起来小但出了故障往往能折腾好久。时钟使能顺序。我先使能GPIO外设时钟再配置GPIO引脚这个顺序没问题但如果你是先锁定了GPIO寄存器EALLOW/EDIS配对没做好后续配置全部无效而且代码还不报错。GPIO相关配置务必在EALLOW和EDIS之间完成并检查配对。发送函数不要放在中断服务函数里。有些人的逻辑是接收中断里解析报文然后直接回发一帧。这个做法在高波特率下很容易出问题。发送函数本身有等待Busy的循环放在中断里会阻塞其他中断处理系统实时性会受影响。更稳妥的做法是中断里只设置标志位或填充发送缓冲区主循环或高优先级任务里再执行发送。F28377D/F28379D是双核芯片如果你在用两个内核分别操作CANA和CANB注意外设归属。CANA模块归CPU1CANB模块归CPU2默认情况上下电时序、时钟使能要分别处理。双核同时访问同一个外设会导致总线冲突和不可预估的行为。还有一点关于调试器的。XDS系列仿真器在连接状态下CAN模块的时序会受到影响特别是断点暂停时CAN总线上的报文会中断。如果是调通信时序相关的bug建议把调试策略改成“日志输出分析”而不是“断点看变量”否则你观察到的总是通信异常时的假象查不到真正的问题。5. 最后再分享几个实测心得代码层面基本全讲完了最后补一些调试现场沉淀下来的经验。关于CAN通信电路我一直建议项目早期就按正式量产的标准来画板不要因为调试阶段就用杜邦线飞线。CAN总线对信号完整性敏感飞线过长、接触不良、收发器靠近干扰源等问题都会让通信间歇性异常这类问题往往比软件问题更难查。我做一个电机控制器的原型板时第一版为了省事用了杜邦线连接CAN收发器结果500kbps波特率下十个节点怎么调都有丢包最后换了正确的PCB布局问题直接消失。关于收发器供电再强调一遍之前提过的问题CAN收发器芯片不是电源芯片它不可能给板子供电它的供电必须单独给。有些市面上便宜的CAN模块会把VCC和GND引出来看起来好像能给MCU供电但那是为了给模块内部的信号隔离电路和电平转换电路供电用的不是设计来给外部系统供电的。使用前务必确认模块规格书否则供电能力不足会导致实际工作电压不稳进而引发乱码和总线错误。关于寄存器配置过程我建议每次修改配置后都回读确认。D_CAN模块的很多寄存器只支持写1清0、不支持读改写或者某些位只在特定模式下可写。调试器里单步执行、断点回读这些手段比直接跑起来观察现象更有效率。我做了这么久嵌入式一个体会是写驱动代码时多花时间把每条配置都验证一遍后面系统联调阶段会省下大把时间。最后提一个实用的小技巧把CAN_ES、CAN_TEC、CAN_REC、CAN_INT这几个寄存器在调试器里添加成Watch窗口的常驻变量运行过程中随时观察。发送错误计数器和接收错误计数器的数值变化能直观反映总线状态比盲目改代码甚至改硬件要靠谱得多。调试CAN通信本质上就是在“看得见”寄存器的前提下不断逼近问题真相这比任何玄学定位法都有效。
返回列表