rt_device_write
中 此问题出现在RTT4.0.3之前的版本中。
drv_can.c中的 CAN2_SCE_IRQHandler()
中,case RT_CAN_BUS_ACK_ERR
中的if
中将drv_can2写成了drv_can1,改正之后即可解决CAN2在发送遇到设备无应答时出现程序卡死的问题。
原因分析:这里的CAN2_SCE_IRQHandler函数为发送出错的处理中断函数,当CAN2没有连接CAN设备(或连接的设备未上电)时,如果CAN2发送了报文,则一段时间后就会进入这个错误处理函数,而这里的if写成了can1,导致错误没有正常处理,导致程序卡死。
此问题出现在RTT4.0.3以及之前的版本中。笔者在4.0.2和4.0.3中均进行过测试。
类似的卡死问题,有些资料给出的解决方案是打开CAN初始化时的AutoRetransmission
功能,这个方法看似也能解决问题,但其实是治标不治本。在2.2.2 问题分析中进行详细原因说明。
在drv_can.c中找到CAN1_TX_IRQHandler
,该函数主要部分为if-elseif-elseif,我们需要在最后一个elseif结束后添加else,如下代码中的倒数第4行:
同理,需要在CAN2_TX_IRQHandler
中的最后添加else,注意是drv_can2
:
接着,需要在_can_sendmsg
中注释掉Change CAN state
对应的程序,如下程序中的#if(0)—-#endif
完成以上修改之后就已经解决CAN卡死的问题了。
当CAN发送时遇到CAN线被短接的情况或者接触不良的情况,则stm32会产生发送完成中断,但相关标志位均为0,这个情况在STM32的手册里也没有描述,但是测试发现确实存在,下文称这种情况为没有标志位的中断。这个现象是导致CAN短接或接触不良时程序卡死的根本原因。
如果初始化CAN时没有打开CAN外设的发送失败自动重发功能(RTT初始化CAN时默认不会打开自动重发),则CAN线的硬件连接恢复后STM32也不会再次产生发送完成中断,即这次发送已经失败了,并且出现了一次没有标志位的发送完成中断,此后也不会再有这次发送的发送完成中断了。
RTT的STM32CAN发送函数rt_device_write(...)
被调用时,启动发送之后,线程会挂起在一个completion信号量上,这个信号量将在CAN发送完成中断服务函数里释放,但中断服务函数中写的释放信号量是有条件的,条件是查到对应的标志位为1时释放对应的信号量。则如果出现了没有标志位的中断,该次中断中程序是不会释放completion信号量的,而由于CAN发送线程现在已经挂起,也不会发起下一轮发送,不再次启动发送则STM32不会再次产生发送完成中断,只有中断中释放信号量才能让CAN发送线程继续发送,而只有CAN发送线程再次正常启动发送时才能进入中断并释放信号量。这就陷入了死循环,导致发送CAN的线程被永远挂在了这个信号量上。
尝试解决这个问题的第一步就是在发送完成中断中,程序进行标志位判断的地方加个else,遇到没有标志位的中断时也得发信号量,并且返回RT_CAN_EVENT_TX_FAIL
,后续的0<<8
表示的是失败事件是由第一个CAN发送邮箱产生的。此时虽然无法通过标志位来判断产生发送完成中断的邮箱,但是由于RTT的CAN驱动实际只使用了第一个发送邮箱,所以只要中断就一定是第一个发送邮箱产生的。
这样可以解决发CAN线程被永远挂起在completion的问题,但是改了之后发现CAN还是会卡死。再次debug发现,当出现没有标志位的中断时,由于我们刚才改的程序会发送一个RT_CAN_EVENT_TX_FAIL
标志,而原有的CAN驱动会在接收到这个标志的时候将HAL的CANstate置位为ERROR,这会导致以后都是ERROR了,后续发送之前CAN驱动会检查CANstate是否为ERROR,如果是ERROR就不会发送。所以把这个置位ERROR的代码注释掉就好了,大量实测证明这样做不会导致其它问题。
完成以上修改后,CAN驱动效果非常好,接触不良和can线临时短路等情况都不会造成程序卡住。
一些文档中通过修改CAN初始化代码以开启AutoRetransmission
功能,也可以从表面上解决这个问题,因为如果失败之后自动重发,则重新启动发送且发送成功后,总会产生正常的中断,此时就能释放信号量,发送线程就不再被挂起在completion信号量中了。但是这样的修改方式有缺点,即当CAN线硬件异常导致有一段时间不能正常发送时,rt_device_write函数将一直处于挂起在信号量上的状态,直到上次要发送的数据被成功发送,且由于实际发送次数>调用发送函数的次数,发出的CAN报文没有ACK时容易出现信号量溢出的问题。
采用自动重发的解决方法,则只要调用rt_device_write函数,函数返回时这帧数据就一定已经正常发送了,如果发送不正常的话CAN外设就会自动重发。这种情况会导致一帧发送失败后会一直等待CAN线恢复后再发,会导致CAN线硬件异常时调用rt_device_write函数的用户线程被挂起较长时间,且这段时间内有可能出现信号量溢出的问题,导致程序卡死。
如果按照本文介绍的方法修改底层,想实现类似发送失败重发的功能,可以自己通过软件实现,每次rt_device_write之后检查返回值,如果发送不成功,则重新发送当前数据。
本文修改的drv_can.c文件为rtt底层\library中的通用文件,如果多个bsp共用该library文件夹,则修改后多个bsp都会受到影响,修改时需要仔细检查,做好记录或备份。
很棒,描述的过程也很详细。
很棒,描述的过程也很详细。
前段时间调RTT的CAN踩了不少的坑,楼主真棒,很有帮助的帖子!
分析很详细,最近使用 AT32F403A 的 CAN 正好遇到问题,按照你的分析修改确实好用了
还发现一个地方,drv_can.c里,当CAN总线拔掉发送时,会进入ACK错误中断SCE,这时TSR的TXOK都是0,另CAN_FLAG_TXOK0/1/2值的定义,还要移位才能对应,READ_BIT没有进行移位,所以下面这三个条件并不会执行,改为下面方式就可以执行了,并且清理了发送请求位
CAN_TSR_RQCP0/1/2
———————-改为以下代码———————
楼主,你说的这个bug官方主分支是不是已经恢复了啊
@吕蛋蛋
没有, 至少在4.1.0中没有修复。
添加后,若再开启AutoRetransmission功能,会导致CAN发送卡死,显示失败。
但是实际上,成功发送了。
@mhxsoft
多谢多谢,这个修改办法确实可行。
可以解决can总线断开后发送can数据。然后恢复can总线时,发送can数据显示发送失败,实际分析仪显示成功问题
@mhxsoft
更改代码确实解决了问题。
最新的RTT代码里没有更新。
你有提交修复不?
我没提交,不会PR,还有一个关于过滤器索引的问题,drv_can.c里也有问题,我只改了我自己的代码,以前在gitee里提过,不知道现在改了没@用户名由3_15位
@aozima
@mhxsoft
好吧,那我提交一下
@mhxsoft
已pr,你看一下 https://github.com/RT-Thread/rt-thread/pull/6511
赞
can过滤器索引,ST 双CAN 28个过滤器的,默认前0~13个过滤器是CAN1使用,14~27个过滤器是CAN2使用
学习学习,目前也卡住了😅
早两天发就好了
遇到过类似的
确实解决了问题,谢谢。但是有个问题是,在短路或者断路出现被动错误后,size = rt_device_write(can_dev, 0, &msg, sizeof(msg));
size就返回0,然后单步调试发现size又正常返回0x10了。这个是怎么回事呢?有没有隐患?
这个好像也不行,他会出现另一个问题,就是它好像不能自动恢复!也就是说它不会再发了,是我弄错了吗?F1系列的单片机@mhxsoft
挺优秀
@大佬什么都会
感谢!这个问题调试了挺久的