专栏中心

EEPW首页 > 专栏 > RS485 串行通信调试:工具链挑战与 POSIX 兼容性问题

RS485 串行通信调试:工具链挑战与 POSIX 兼容性问题

发布人:toradex 时间:2026-09-04 来源:工程师 发布文章

By Toradex Vitor Soares

引言

起初只是关于 linux-serial-test(一款用于通过发送和接收数据流对串行端口进行压力测试的开源工具)的一份简单问题报告,却演变成了一个涉及回归分析、交叉编译的差异、glibc 版本不兼容以及更底层的 POSIX 合规性问题等多层面的调试挑战。该工具被用于我们的持续集成(CI)管道中,以验证 Toradex 模块上的 UART 功能;但在多台设备上报告出现故障后,这促使我们对整个工具链展开了更广泛的调查。

这是一个关于我们 master build 中突发故障(起初怀疑是内核回归)的故事:它将我们引入了优化迷局,然后遭遇了工具链问题,最终发现了一个显而易见却被忽视的POSIX合规性违规问题。

MIPI DSI - Toradex, Torizon
起因:RS485 配置失效

问题
由于多个设备上都出现了故障,最初怀疑是最近的一次上游提交。上游提交 77320571e63c (“检查端口上是否已配置 RS485”)引入了一项出于良好意图的更改:优先使用通过设备树在端口上已配置的 RS485 设置。
然而,它却违背了一个基本假设: 应始终尊重用户的命令行意图.

有问题的代码:
if (rs485.flags & SER_RS485_ENABLED) {
    printf("RS485 already enabled on port, ignoring delays if set\n");
} else {
    // Configure RS485 based on user flags
}

为什么会出问题
用户无法实现下面操作:

  • 如果 RS485 此前已被启用,则将其禁用

  • 更改延迟设置

  • 运行 UART 环回测试(此操作需要禁用 RS485)

  • 正确测试流量控制

教训一:优先考虑用户意图,而非设备配置。


第一项改进:基于用户意图的重构
MIPI DSI - Toradex, Torizon
我们实施了一个合理的修复方案:
if (_cl_rs485) {
    /* User explicitly wants RS485 */
    if ((_cl_rs485_after_delay < 0) && (rs485.flags & SER_RS485_ENABLED)) {
        printf("RS485 already enabled on port\n");
    } else {
        /* Configure RS485 with user settings */
        // ... configuration code ...
    }
} else {
    /* User wants RS485 disabled - ALWAYS disable */
    rs485.flags &= ~(SER_RS485_ENABLED | ...);
    // ... disable code ...
}

这效果非常理想。测试全部通过。一切都很顺利。

但接着出现了 第二个问题.


第二幕: master build 失败

相同的源代码,在本地使用 aarch64-none-linux-gnu-gcc 编译时运行正常。但在通过 OpenEmbedded(OE)构建时,-c 标志(RTS/CTS 流程控制) 在无提示的情况下失效了:

tx=272, rx=0  ← Data sent but not received!
排查路径 1:标志和编译优化

误区一:加固标志

OE 默认会应用安全标记:
-D_FORTIFY_SOURCE=2
-fstack-protector-strong
-mbranch-protection=standard

我们已将其禁用,但仍然失败了。

误区二:编译优化级别

OE: -O2
Local: default

我们改成了 -O0,但仍然失败。

误区三:PIE(Position Independent Executable)

Local:  "executable" (no PIE)
OE:     "pie executable" (PIE enabled)

我们通过以下方式禁用了 PIE: -fno-pie -no-pie. 还是失败了。

教训: 不要只治症状,而要找出根本原因。


排查路径 2:二进制比较

最后,我们对比了正常运行和无法运行的二进制文件中的实际符号:

nm ./linux-serial-test-local | sort > local.sym
nm ./linux-serial-test-oe | sort > oe.sym
diff local.sym oe.sym

太棒了!发现区别:

Local (works):
- strtol (C99)
- cxa_finalize@GLIBC_2.17

OE (fails):
- __isoc23_strtol (ISO C23!)
- __wrap_main
- Missing cxa_finalize

OE 工具链在编译的开启了 支持 ISO C23,从而改变了运行时的基本行为。


排查路径 3:使用 SDK 进行重现

我们在本地构建了 OE SDK,以重现 CI 环境:

bitbake -c populate_sdk core-image-minimal
source /opt/sdk/environment-setup-aarch64-oe-linux

现在我们能够重现该故障了, 本地 使用相同的工具链。这一点至关重要,它证明了问题出在工具链上,而非 OE 构建基础设施中。

调查路径 4:strace——runtime 运行时的真相

由于符号分析指向了 glibc 方面的问题,我们需要了解运行时究竟发生了什么。此时,strace 就显得尤为重要。

对比系统调用:
strace linux-serial-test -o2 -i3 -b 115200 -p /dev/verdin-uart1 -c -a10 -w16

我们的发现:

可运行版本显示如下:
ioctl(3, TCSETS2,
{c_iflag=, c_oflag=NL0|CR0|TAB0|BS0|VT0|FF0|,
 c_cflag=B115200|B115200<<IBSHIFT|CS8|CREAD|CLOCAL|CRTSCTS,
 c_lflag=,
 ...}) = 0
出错的版本显示如下:
ioctl(3, TCSETS2,
{c_iflag=, c_oflag=NL0|CR0|TAB0|BS0|VT0|FF0|,
 c_cflag=B0|B50<<IBSHIFT|CS8|CREAD|PARODD|CLOCAL|CRTSCTS|0xc000,  ← WRONG DATA!
 c_lflag=,
 ...}) = 0

关键点: 写操作正常进行,但读取操作返回的是乱码或空值。这将问题锁定在串口配置步骤上,具体而言是 TCSETS2 ioctl 操作。

我们可以看到实际的 termios 结构被设置。波特率字段(在使用旧方法时通常包含在 c_cflag 中)并未被正确设置。

这证实了我们的假设:旧代码 newtio.c_cflag = baud | CS8 | CLOCAL | CREAD 在新的 glibc 环境下会悄无声息地失败,而串口使用了默认但错误的波特率设置,导致 RTS/CTS 时序出现问题。

调查路线5:真正的元凶

经过大量排查、对比头文件和运行时行为,尤其是利用 strace 观察实际发生了什么,我们终于找到了问题所在: glibc 2.42 及更高版本中 termios 波特率的处理方式发生了变化.

其根本原因在于 该提交 ,它在 termios 中实现了任意速率和预定义速率。这意味着波特率不是通过位掩码表示的,而是直接用整数表示的。

旧代码:
newtio.c_cflag = baud | CS8 | CLOCAL | CREAD;

这会直接与波特率常数进行“或”运算(B115200, B9600,等等)填入标志字段中。当这些是位模式时,这种方法是有效的,但 现代 glibc 将其视为普通的整数 以提高灵活性。

符合 POSIX 标准的方法:
newtio.c_cflag = CS8 | CLOCAL | CREAD;
cfsetispeed(&newtio, baud);
cfsetospeed(&newtio, baud);

RTS/CTS为何受到影响:
旧方法可能会在未报错的情况下无法正确设置波特率,从而导致时序异常,进而破坏 RTS/CTS 握手协议。

第二个修复:符合 POSIX 标准
static void setup_serial_port(int baud)
{
    struct termios newtio;
    tcgetattr(_fd, &newtio);
    
    // OLD WAY (broken with glibc 2.42+):
    // newtio.c_cflag = baud | CS8 | CLOCAL | CREAD;
    
    // NEW WAY (POSIX-compliant):
    newtio.c_cflag = CS8 | CLOCAL | CREAD;
    cfsetispeed(&newtio, baud);
    cfsetospeed(&newtio, baud);
    
    tcsetattr(_fd, TCSANOW, &newtio);
}

额外修复:类型安全

在调查过程中,我们还修复了一个类型安全问题:
// OLD: ssize_t actual_write_size
// NEW: size_t actual_write_size

该 write() 系统调用预期 size_t (unsigned),但当时我们正路过 ssize_t (signed),这会在启用严格类型检查时触发编译器警告。

linux-serial-test/linux-serial-test.c:634:29: warning: 'write' specified
size between 9223372036854775808 and 18446744073709551615 exceeds maximum
object size 9223372036854775807 [-Wstringop-overflow=]
  634 |            ssize_t c = write(_fd, _write_data, actual_write_size);
      |                        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
主要经验教训
#教训描述
1尊重用户意图命令行选项需覆盖设备树的默认设置。
2调查二进制文件,而不仅仅是代码nm, objdump,以及 strace 揭示运行时差异。
3优先考虑符合 POSIX 标准使用 cfsetispeed() / cfsetospeed() 用于串行设置。
4使用真正的工具链OE SDK 复现问题可避免假阴性结果。
5避免陷于编译标志符 flag编译优化和 PIE 极少会导致细微的运行时不匹配。
结论

起初看似是我们整个设备系列中普遍存在的内核回归问题,实际上却是工具链和库的兼容性问题。这一案例提醒我们,在现代嵌入式系统的调试过程中,问题很少出现在最初检查的地方。

此次调查证实了 API 正确性比走捷径更为重要,符号分析和运行时分析工具的价值,以及为支持未来的维护人员而必须确保合规性和完善文档的必要性。

参考文献
  • RS485

  • Linux SDK


专栏文章内容及配图由作者撰写发布,仅供工程师学习之用,如有侵权或者其他违规问题,请联系本站处理。 联系我们

关键词: ARM Linux RS485 Debug

相关推荐

IBM 与 Arm:拓展 Arm 生态,开启企业计算新时代

ARM嵌入式系统开发

ARM开发详解

安谋将扩大采用阿尔蒂斯安全技术

ARM嵌入式软件编程经验谈

Arm 与 Unity 中国达成战略合作,以 Arm 神经技术加速中国手游创新

WinCE+ARM开发及关键技术 上

视频 2009-10-22

基于 Arm 平台的 NVIDIA RTX Spark 重新定义智能体时代的 PC

最新ARM技术和嵌入式技术发展动态 中

视频 2009-10-22

IBM公布双架构大型机处理器路线:单核原生并发执行IBM Z与Arm指令

从Token 到任务:智能体AI如何改变基础设施的关注焦点

ARM嵌入式系统开发:软件设计与优化

贸泽推出全新电子书探讨如何利用热门Arduino UNO Q开发板进行AI嵌入式设计

最新ARM技术和嵌入式技术发展动态 下

视频 2009-10-22

安谋科技Arm China闪耀WAIC AI前瞻分享

arm学习资料

资源下载 2007-12-03

机器人迈向自适应智能:为什么系统能力正在成为新的竞争焦点

机器人 2026-08-24

英伟达新一代Rosa处理器搭载参宿四 Rigel(Arm v9.2)内核

ARM 展示小型低功耗上网本样品

视频 2009-03-16

WinCE+ARM开发及关键技术 下

视频 2009-10-22
更多 培训课堂
更多 焦点
更多 视频

技术专区