侧边栏壁纸
  • 累计撰写 6 篇文章
  • 累计创建 33 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

ROS 2 串口调试终极一招:用 strace 直接抓 write() 真相

猿有味
2026-04-05 / 0 评论 / 3 点赞 / 4 阅读 / 0 字

摘要

实际项目里,ROS 2 节点接串口设备时,经常会遇到一种情况:节点日志看起来一切正常,设备却一点反应都没有。

这时候很多人会先去怀疑协议、设备或者业务代码,但真正该先确认的,往往是更底层的一件事:

程序到底有没有把你以为的那帧数据写到串口。

只看日志不够。日志只能说明代码走到了某一段逻辑,不能说明最终写进 /dev/ttyUSB0 的字节流就是对的。

这篇文章只做一件事:用 strace 直接看 ROS 2 节点到底往串口写了什么,然后顺着这条线继续排。

一、问题现象

先看一个很常见的现场:

  • ROS 2 节点通过串口控制 Modbus RTU 设备
  • 日志显示命令已经执行
  • 设备没有响应

这时候真正要排的,通常不是一句“程序有没有跑起来”,而是下面几件事:

  • 程序到底有没有发数据
  • 发出去的是不是你以为的那一帧
  • 串口参数对不对
  • 还是硬件、接线、485 收发方向有问题

这里最容易把人带偏的一点,就是把“日志正常”当成“串口发送正常”。这两件事不是一回事。

二、为什么用 strace

Linux 下串口发送最后都会落到系统调用,比如:

write(fd, buffer, len)

strace 看的就是这层,所以它很适合干这种活。

它不依赖你代码怎么封装,也不依赖日志有没有漏。进程最后到底调了什么系统调用,它就给你打出来。对串口排障来说,这一步往往比继续补业务日志更直接。

常用的几个参数:

  • -f:跟踪子进程
  • -xx:把字符串按十六进制完整打印出来
  • -yy:把 fd 对应的设备路径也一起打出来
  • -e trace=write:只盯 write,先把噪声压下去

三、怎么挂 strace

很多人第一反应会这么试:

strace ros2 launch xxx.launch.py

这条命令不是绝对不行,但对串口排障来说通常不太合适。因为 ros2 launch 只是启动器,真正做串口发送的是节点进程。直接 trace 整个 launch 过程,输出会很杂,真正有用的 write() 很容易被淹掉。

更稳的做法是直接 trace 节点。

1. ros2 run 直接挂

如果你平时是用 ros2 run 启动节点,可以直接加 --prefix

ros2 run --prefix 'strace -f -xx -e trace=write' \
  <package_name> <executable_name> --ros-args --params-file config/devices.yaml

这是 ROS 2 官方文档里常见的调试思路:通过 --prefix 在节点前面挂调试器。这里只是把常见的 gdb 换成了 strace

2. launch 文件里挂

如果你走的是 launch 文件,工程里更顺手的写法是给 Nodeprefix

Node(
    package="your_pkg",
    executable="your_node",
    parameters=[config],
    prefix=["strace -f -xx -e trace=write"],
    output="screen",
)

这样不用手改节点代码,也不用单独去找可执行文件路径,直接复用现有 launch 结构就行。

3. attach 到运行中进程

如果设备已经在现场跑着,不方便重启,那就直接 attach:

ps -ef | grep your_node
sudo strace -p <PID> -xx -e trace=write

这也是现场里很常用的一招。

四、怎么认串口数据

一跑起来,输出里通常会同时混着日志、socket、pipe 和串口写操作。比如:

write(2, "[INFO] ...", 109)
write(24, "\x01\x00\x00...", 32)
write(21, "\x01\x05\x00\x00\xff\x00\x8c\x3a", 8)

1. 先排除日志输出

先记一个最基础的判断:

  • fd=1 一般是 stdout
  • fd=2 一般是 stderr
  • 其它 fd 才可能是 socket、pipe 或串口

所以像这种:

write(2, "[INFO] ...", 109)

通常只是日志,不是串口数据。

2. 直接对上 /dev/ttyUSB0

如果你想直接确认某个 fd 对应的是不是 /dev/ttyUSB0,把命令改成:

strace -f -xx -yy -e trace=write ...

输出会变成这样:

write(21</dev/ttyUSB0>, "\x01\x05\x00\x00\xff\x00\x8c\x3a", 8)

这时候就不用再猜了,数据确实是往串口设备写的。

五、怎么看一帧 Modbus 数据

比如你抓到了这一条:

write(21</dev/ttyUSB0>, "\x01\x05\x00\x00\xff\x00\x8c\x3a", 8)

把它按十六进制展开:

01 05 00 00 FF 00 8C 3A

如果这是 Modbus RTU,通常可以这么看:

  • 01:从站地址
  • 05:写单线圈
  • 00 00:线圈地址
  • FF 00:写入 ON
  • 8C 3A:CRC

看到这里,其实已经能先回答一个很关键的问题:程序确实发了“打开某个线圈”的命令,而且不是空写,不是日志,也不是别的 fd。

这一步的意义不在于把协议分析得多细,而是先把“程序根本没发”这种怀疑排掉。

六、有 write 但设备不回怎么继续排

1. 先查常见方向

如果你已经确认:

  • 节点确实有 write
  • 数据帧看起来也对

但设备还是没反应,那问题通常就不在“有没有发”了,而在下面这些地方:

  • 波特率、校验位、停止位不匹配
  • RS485 收发方向控制有问题
  • 从站地址写错
  • 寄存器或线圈地址写错
  • A/B 线接反、终端电阻、共地这类硬件问题

现场里更常见的情况不是“代码一行都没发出去”,而是发出去了,但设备不认

2. 把 trace 范围放大

只看 write 不够时,可以把范围放大一点:

strace -f -xx -s 256 -e trace=openat,ioctl,read,write ...

这条命令主要多看三类信息:

  • openat(...):进程打开的是不是你以为的串口设备
  • ioctl(...):串口参数有没有按预期设置
  • read(...):设备到底有没有返回数据

这里的 -s 256 是把字符串打印长度放大,避免帧太长被截断。

如果输出里能看到:

openat(..., "/dev/ttyUSB0", ...)

再配合 ioctl(TCSETS...) 和后续 read(...) / write(...),基本就能把“打开了哪个口、设了什么参数、有没有发、有没有收”这一整条链路串起来。

结论

ROS 2 串口排障里,很多时候最该先做的,不是继续猜协议,也不是继续补业务日志,而是先把这件事看清楚:

节点到底往串口写了什么。

strace 的价值就在这里。它不关心你的代码写得多漂亮,也不关心日志说了什么,它只看进程最后到底调了什么系统调用。

对串口这种问题来说,这一步通常比继续读代码更快。

参考

3

评论区