搜索
bottom↓
回复: 12

解决 CDBUS Bridge 小概率死机的问题 - Linux 环境开发调试

[复制链接]

出615入1076汤圆

发表于 2023-10-16 14:10:29 | 显示全部楼层 |阅读模式
## 问题描述

使用 CDPNP 贴片机的时候 CDBUS Bridge 会非常小概率死机,如果每次使用贴片机 4 小时,平均贴片 5 次会遇到 1 次死机。
如果每个礼拜贴片 2 次,相当于半个多月会遇到 1 次死机。(Windows 下概率据说要高一点点。)

怀疑是 USB HAL 部分的代码 bug 导致。
因为 STM32 的 HAL 有很多 bug,譬如 SPI DMA 传输结束偶尔会丢 callback.


## 历史经验

我以前在 Ingenic 工作的时候,经常要解决嵌入式 Linux 小概率死机的问题,
譬如 Android 平板的 USB 插拔几千次会死机 1 次,又譬如 Hi-Fi 音频播放器连续播放几天会死机一次。

解决这些问题得到的经验是:

1. 想一切办法尽量提高死机或 bug 复现概率。
2. 加调试打印,死机或判断出问题的时候要打印出函数调用关系(backtrace)。


如果不提高 bug 复现概率,会很难抓到 bug 的相关打印,
而且还要根据 bug 当前触发的打印,进一步定位和增加打印内容,要重复很多次,耗时会很久。
而且,即便找到和解决了问题,也很难通过测试来确认问题真的被修复。
与之相反的做法是:稍微改变一下无关代码,让 bug 出现概率更低、甚至消失,从而不去解决问题,
这样日后再次遇到问题,就会 N 多问题混在一起,更加没有可能去解决。

打印函数调用关系,是为了通过极少次数的 bug 复现的打印,来推断问题出自何处。
经常一次死机的 log 就可以定位到问题点。

## 准备调试工具

之所以拖了很久没有解决 CDBUS Bridge 死机的 bug,有一个原因是它使用的是 Cortex M0+ 的芯片,和 M3 不同的是,
进入异常的时候,无法自动打印出函数调用关系,因为 M0+ 内核为了节约成本,有些调试相关的寄存器 CPU 自身无法读取,
只能通过 JTAG/SWD 读取。

之前抓到一次 log,显示进入了 hardfault 异常,没有其它多余信息,所以很难定位问题。
由于事情太多,又不熟 STM32 的 USB 代码,所以当时没有第一时间解决,但一直放在较高优先级的计划安排中。
(早期 CDBUS Bridge 使用 STM32F1 的时候,死机时会自动打印函数调用关系,使用 gcc 自带的 backtrace.)

由于我从事嵌入式 Linux 开发之后,就告别了 JTAG 调试,因为单步调试在很多场景都会受到限制(譬如 halt 的时候,电机驱动不再换相直接烧毁电机线圈),
而且断点信息很难保存下来和代码一起分享给其它参与者。而 log 打印则非常灵活和高效,是终极调试方案。

但是由于 M0+ 的硬件限制,只能借助 JTAG 来显示函数调用关系。


### JTAG/SWD

由于我平时使用 Linux 桌面系统,所以代码是 STM32CubeMX 生成的 Makefile 工程,直接敲一下 `make` 就能得到 hex 固件,非常方便和适合开源项目。
编辑代码我习惯使用 eclipse,仅用来编辑和阅读代码,不负责编译和调试。

下载程序使用命令行的开源工具 st-flash(IAP 更新代码则可以用 CDBUS GUI 工具)。

这个开源工具其实名为 stlink ( https://github.com/texane/stlink ), 这两天又想起来,它自带了 jtag server 工具:st-util.

启动 gdb server(和烧录代码一样使用 ST-LINK v2 硬件、SWD 接口):  
`$ st-util`

连接 gdb:  
`$ arm-none-eabi-gdb -ex "target remote localhost:4242" /path/to/xxx.elf`

输入 `continue` 或 `c` 恢复运行程序,首次执行设备会重启。

`Ctrl + C` 可以挂起 MCU.

MCU 挂起后,输入 `where` 可以打印 backtrace, 查看函数调用关系。

`info locals` 可以查看所有局部变量。

`p xxx` 可以查看指定的全局变量,可显示结构体内部数据。

还有其它很多命令,譬如读写指定内存地址的数据,gdb 还自带了命令行版本的图形界面 TUI,有兴趣可以自己查一下。

首次打印 backtrace,显示的函数关系不全,提示 "Backtrace stopped: Cannot access memory at address 0x20023fdc".
从代码安装最新 git 版本的 stlink 就解决了(因为 stm32g0b1 比较新)。

已经想不起来十多年前我刚在 Linux 下开发 stm32 的时候为何要使用 openocd 了。


## 提高死机复现概率

提高复现概率也尝试了很多方法,第一次有效果是:

借用 Windows 电脑,在 CDPNP 网页界面中,首先打开摄像头,拍摄画面内容比较丰富的电路板(让传输的 jpg 图片大一些),
然后在网页调试终端,用定时器每 0.1 秒移动一下 X 轴(移动速度预先设置为 2 档,步进 0.1mm)。

等吸嘴快移动到最右边,我再用键盘长按左键抢控制权,把吸嘴移动到最左边(大概每左移两三次,会右移一次)。

移动的比较慢,左右来回两次差不多就会死机一次。

半个小时不到,抓到两次死机:





由于两次死机都是访问链表的时候死掉,怀疑是 USB 接收电脑数据的时候,偶尔越界写 rx buffer 窜改了其它数据,导致内存数据错乱。
(`APP_RX_DATA_SIZE` 定义的是 2K 大小,而我提供的 rx buffer 只有 512 字节,虽然以前大概查过 `APP_RX_DATA_SIZE` 定义这么大没什么用。)


第二种效果非常好的方法是:

回到 Linux 电脑,改用 CDBUS GUI 工具,同时打开 CDPNP 机器的两个摄像头,且都拍摄比较复杂的图案。
此时,构建一个 CDBUS 数据帧(使用 echo 命令),带 CRC 共 10 bytes,发送目标是不存在的设备地址,数据重复很多次(使用 cat 命令),生成一个 5MBytes 左右的文件。
然后用 dd 命令把这个数据传输到 CDBUS Bridge 的串口节点。(串口节点同时被 CDBUS GUI 和 dd 程序打开和读写。)  
(由于数据量太大,CPU 比较慢,来不及处理,会打印很多 rx_lost 错误,临时屏蔽掉该行打印。由 rx_lost 导致的 crc error 打印相对较少则没有屏蔽。)

基本上十来秒就会死机一次:



## 问题分析

由于第 3 次依然是链表出错,于是打开链表检查和打印,打开 `LIST_DEBUG` 定义即可(这就是打印调试的便捷之处)。  
(会顺便打开 gcc 自带的 backtrace,需要增加一些编译参数,否则编译不过,临时屏蔽掉 unwind 相关代码即可。)  



第 4 次果然链表检查提示出错,链表 `len` 成员和实际 `nodes` 数量不符。

由于很多次提示 `cdbus_uart.c` 和 `frame_free_head` 链表出错,以及提示的链表操作函数是 `list_get`, 而非 `list_get_it`, 引起了我的注意。
我打开 `cdbus_uart.c` 文件,找到出错的 169 行:





`list_get_it` 最终也是通过 inline function 调用 `list_get` 函数,所以 backtrace 显示 `list_get` 函数也是正常的,
不能说明我没有使用 `list_get_it` 中断安全的版本。
(很多时候,backtrace 显示的内容会比较精减,要进一步查看出错位置的汇编,也很难和 c 文件对应的很好,是因为编译器优化导致,可以考虑临时设置成不优化或小优化等级,或仅部分函数或文件改优化等级。查看出错汇编和 c 程序对应关系,使用命令:`arm-none-eabi-objdump -S xxx.elf`.)

不过,以上还是引起了我的注意,毕竟是在找 bug,任何有可能出错的地方都要再三检查一下,不想一检查,真的发现我没有定义 `CDUART_IRQ_SAFE`.
因为我用 `cdbus_uart` 生成的两个虚拟设备,都是用于数据中转,仅在 main 循环中调用,没有涉及中断调用,所以没有开 `CDUART_IRQ_SAFE`.
然而,我漏掉一件事情:`cdbus_uart` 和 `cdctl_fast` 代码共用同一个 `frame_free_head` 链表,`cdctl_fast` 会在中断里面操作 `frame_free_head` 链表。
如果 `cdbus_uart` 在主循环中正在操作 `frame_free_head` 链表,`cdctl_fast` 来了中断也操作了同一个链表,那么链表肯定就会错乱,至此破案。

同样的方法,又找到另外一处最近新写的代码没有以 irq safe 方式访问 `frame_free_head` 链表:  
(`local_irq_save` 和 `_restore` 分别是关闭和打开全局中断使能。)



看来各种 `free_head` 很容易被忽视,全面检查了一遍其它各种 `free_head`,没有问题,多次使用同样的测试方法,长时间不再死机。

看来这次是错怪 STM HAL 代码了,复盘想了一下,CDPNP 用户复现概率高也不是因为 Windows 操作系统,
而是用户的贴片机配置的是高帧率的新版本摄像头,而我自己暂时用的还是老版本摄像头,数据量不同,相当于我同时打开两个老摄像头。
(我之所以还没用上新版本摄像头,是因为新摄像头要做 CDPNP 升级包赠送老客户,因为事情多,拖了比较久,已经联络了部分老客户,打算一个月内全部通知到位。)

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出100入312汤圆

发表于 2023-10-16 14:13:33 | 显示全部楼层
大神厉害!

出0入42汤圆

发表于 2023-10-16 15:21:13 | 显示全部楼层
这个帖子 值得精华

好多年不用gdb了, 刚毕业那几年, 在solaris上还真记住了不少gdb命令

出110入0汤圆

发表于 2023-10-16 15:46:45 | 显示全部楼层
强! 最近看了cdbus相关的文档。

私以为推广总线最好的方式就是做一个完善的应用,楼主的贴片机很赞

出615入1076汤圆

 楼主| 发表于 2023-10-16 17:00:14 | 显示全部楼层
本帖最后由 dukelec 于 2023-10-16 17:01 编辑

谢谢各位支持

贴一份 bridge 通过 cdbus gui 工具升级的文档:





在线版本: https://github.com/dukelec/cdbus_bridge/discussions/2

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入228汤圆

发表于 2023-10-16 18:29:24 来自手机 | 显示全部楼层
本帖最后由 wxws 于 2023-10-16 18:34 编辑

所以有时还不如用现成的usb转串口

我用rp2040的usb ,如果放有刷电机边,就有可能断线,只能重新插拨usb .单片机本身正常跑。
换个usb 转串口再接2040一切正常

rp2040是外部供电的,非usb供电。所以说本身程序没跑飞。

出1310入193汤圆

发表于 2023-10-16 21:56:39 来自手机 | 显示全部楼层
经验之谈,
解决bug第一步,就是复现bug。
往往耗时耗力都很难复现出来。

出615入1076汤圆

 楼主| 发表于 2023-10-17 11:15:59 | 显示全部楼层
wxws 发表于 2023-10-16 18:29
所以有时还不如用现成的usb转串口

我用rp2040的usb ,如果放有刷电机边,就有可能断线,只能重新插拨usb . ...
(引用自6楼)

我们的做法其实是一致的

都是尽量不要在电机驱动板上使用 usb 通讯

这也是为何我做的 cdfoc、cdstep 等电机驱动板都避免使用 usb 接口,
而其它家的 odrive、vesc 等电机驱动板则是使用 usb 接口

usb 数据包受干扰连续出错 3 次,usb 端点就会被挂起,需要重新插拔 usb 了

usb 转串口之所以不使用现成的,是因为现成的不支持 多主 485 通讯,不能发挥 cdbus 总线的最大优势,且现成的串口速率不够快

出0入309汤圆

发表于 2023-10-17 16:51:22 | 显示全部楼层
dukelec 发表于 2023-10-17 11:15
我们的做法其实是一致的

都是尽量不要在电机驱动板上使用 usb 通讯
(引用自8楼)

usb 数据包受干扰连续出错 3 次,usb 端点就会被挂起,需要重新插拔 usb 了.

求详细信息? 在哪能找到这个说法的详细信息?我快速搜了一下没找到什么有用信息。

出615入1076汤圆

 楼主| 发表于 2023-10-17 18:26:22 | 显示全部楼层
iamseer 发表于 2023-10-17 16:51
usb 数据包受干扰连续出错 3 次,usb 端点就会被挂起,需要重新插拔 usb 了.

求详细信息? 在哪能找到这 ...
(引用自9楼)

你搜索下面这句话,可以找到出处:

“USB允许连续3次以下的传输错误,会重试该传输。超过三次后,HOST 认为该端点功能错误(STALL),放弃该端点的传输任务。”

放弃端点,就无法通讯了。

CDBUS bridge 最早期遇到过偶尔数据不通死掉的问题,查了很久,用逻辑分析仪抓到了连续 3 次出错(且另一方没有回覆),后来发现 USB 不要接台式机前面 USB 口,改接后面就没问题了。

出0入309汤圆

发表于 2023-10-18 12:40:47 | 显示全部楼层
dukelec 发表于 2023-10-17 18:26
你搜索下面这句话,可以找到出处:

“USB允许连续3次以下的传输错误,会重试该传输。超过三次后,HOST  ...
(引用自10楼)

感谢回复,我在USB官网的USB 2.0 Specification(usb_20_20230224.zip)里的usb_20.pdf也找到了对应的信息:

4.5.2 Error Handling
The protocol allows for error handling in hardware or software. Hardware error handling includes reporting
and retry of failed transfers. A USB Host Controller will try a transmission that encounters errors up to
three times before informing the client software of the failure. The client software can recover in an
implementation-specific way.

In the host controller error cases, the host controller implements the “three strikes and you’re out”
mechanism.

以及

In the host controller error cases, the host controller implements the “three strikes and you’re out”
mechanism. That is, it increments an error count (err_count) and, if the count is less than three (transition
“se4”), it will retry the transaction. If the err_count is greater or equal to three (transition “se5”), the host
controller does endpoint halt processing and does not retry the transaction.

和其他的three strikes之类。

我之所以对这一点这么感兴趣是因为沁恒提供的USB示例代码中对于stall什么处理也没有。我在想如果能妥善的通过重枚举或者别的什么方式处理好因为出错而导致的stall,可能就能解决或者部分解决干扰的问题。

出215入118汤圆

发表于 2023-10-18 17:28:02 | 显示全部楼层
成功升级!

出0入228汤圆

发表于 2023-10-19 04:08:10 来自手机 | 显示全部楼层
我一台三相电的 1.5kw伺服,一上电使能,就会影响周边usb,后来加了个滤波器搞定。  想来做测试环境非常好
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-4-29 08:13

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表