1. 从一块开发板的调试口说起tx1串口到底特殊在哪很多人第一次接触tx1的串口都是因为系统起不来、网络不通、或者想进控制台看看内核打印。这块板子的串口不像普通单片机那样随便找个UART就能用它的调试串口和通用串口在设备树里是分开配置的默认情况下你能在系统里看到的ttyTHS1、ttyTHS2这些节点背后对应的是不同的物理引脚和不同的控制器实例。我当初就是没搞清楚这一点拿着USB转TTL模块接到板子的调试口上结果cutecom里一片空白折腾了大半天才发现是引脚映射和设备树配置的问题。先把结论放在前面tx1上常用的串口节点主要有ttyTHS1和ttyTHS2其中ttyTHS1通常被系统控制台占用ttyTHS2才是留给用户自由使用的那个。这个区分非常关键因为如果你直接去操作ttyTHS1很可能会和系统日志输出打架出现数据错乱甚至把控制台搞挂。而ttyTHS2在默认的设备树里一般是禁用状态需要手动使能才能用。这就是为什么很多人插上线、打开串口工具却什么都收不到的根本原因。这篇文章适合谁看如果你手上有tx1系列的板子想用串口做调试、做数据通信、或者单纯想搞清楚Linux下串口设备是怎么组织和使用的那这篇内容应该能帮你省下不少查资料的时间。我会从设备节点识别、工具选型、参数配置、实际收发测试、常见故障排查这几个角度把整个流程拆开讲清楚。中间会穿插一些我在实际项目里踩过的坑比如权限问题、波特率不匹配、流控误开、DMA接收丢数据这些都是真实遇到过的。另外说明一下文中涉及的具体操作步骤和参数配置一部分来自我自己的实践记录一部分是基于Linux串口编程的通用做法做的合理补充。因为原始记录比较零散我会把逻辑串起来让每一步都有明确的意图和验证方法而不是照搬命令。2. 先搞清楚设备节点ttyTHS1和ttyTHS2的区别与识别方法2.1 为什么ls /dev/ttyTHS*看到的节点不一样在tx1上执行ls /dev/ttyTHS*你可能会看到ttyTHS1、ttyTHS2、ttyTHS3等几个节点。这些节点的编号并不是随便分配的它们对应的是芯片内部不同的UART控制器实例。tx1的UART控制器有多个其中一部分被引到了外部引脚上另一部分可能根本没引出或者被其他功能复用了。ttyTHS1在大多数默认配置里是作为系统调试控制台使用的也就是说内核的启动日志、登录提示符都会从这个口输出。如果你用串口工具连上这个口能看到系统启动时的打印信息但如果你想用它来发自定义数据就会和系统输出混在一起非常混乱。ttyTHS2则通常是空闲的但在默认设备树里可能没有使能需要修改设备树或者通过其他方式打开。我当时的做法是先用dmesg | grep tty看一下内核启动时识别到了哪些串口设备输出里会显示每个串口控制器的基地址和注册的tty节点。然后再对照板子的引脚定义确认哪个节点对应哪个物理接口。这一步虽然看起来麻烦但能避免后面很多无效调试。2.2 用udevadm确认设备属性和驱动绑定光看节点名字还不够有时候节点存在但驱动没绑定成功或者被其他驱动占用了。这时候可以用udevadm info -a -n /dev/ttyTHS2来查看设备的详细属性包括它挂在哪个总线上、驱动名称是什么、有没有被其他子系统引用。这个命令的输出比较长重点看KERNEL、SUBSYSTEM、DRIVER这几个字段。如果DRIVER显示的是tegra-uart或者类似的名称说明驱动已经正常绑定了。如果显示为空或者显示的是其他驱动那就要进一步排查设备树配置。还有一个实用技巧是用cat /proc/tty/driver/serial或者cat /proc/tty/drivers来看系统里所有串口驱动的注册情况。这个文件会列出每个串口设备的中断号、波特率基准、类型等信息能帮你快速判断某个节点是不是真的可用。2.3 权限问题为什么普通用户打不开串口Linux下串口设备默认属于dialout组普通用户如果没有加入这个组打开串口时会报Permission denied。解决办法很简单执行sudo usermod -aG dialout $USER然后重新登录或者重启一下。但这里有个坑有些系统里串口设备的组是tty而不是dialout具体要看ls -l /dev/ttyTHS2的输出。另外如果你是在Docker容器里操作串口还需要把设备节点映射进容器并且确保容器内的用户有对应权限。这个场景在嵌入式开发里很常见因为很多人喜欢在容器里搭交叉编译环境但串口调试还是要在宿主机或者特权容器里做。提示修改用户组之后一定要重新登录光执行newgrp dialout有时候不够彻底因为已经打开的shell会话不会自动继承新组。3. 串口工具怎么选cutecom之外还有哪些靠谱选择3.1 cutecom的安装与基本配置cutecom是一个图形化的串口终端工具在Ubuntu系上可以直接sudo apt install cutecom安装。它的优点是界面简单支持十六进制显示、时间戳、日志保存这些常用功能适合快速验证串口能不能通。打开之后在Device下拉框里选择/dev/ttyTHS2Baud rate选115200这是大多数嵌入式系统的默认控制台波特率Data bits选8Parity选NoneStop bits选1Flow control选None。这些参数必须和板子那边的配置完全一致否则收到的就是乱码或者什么都收不到。我遇到过一个问题cutecom打开串口后如果板子那边还没开始发数据界面会一直空着这时候很容易误以为连接失败。其实可以先用回环测试验证一下把USB转TTL模块的TX和RX短接然后在cutecom里发一串字符如果能原样收到说明工具和驱动都没问题问题出在板子那边。3.2 命令行工具minicom、screen、picocom的取舍图形工具虽然直观但在远程SSH会话里用不了。这时候就需要命令行工具。minicom功能最全但配置稍微复杂第一次用要sudo minicom -s进去设置串口设备和参数。screen最简单直接screen /dev/ttyTHS2 115200就能用退出是CtrlA然后按K。picocom介于两者之间支持一些便捷的快捷键退出是CtrlA然后CtrlQ。我个人在服务器上调试时更倾向用picocom因为它对终端环境的兼容性更好不会像screen那样偶尔把终端搞乱。但如果你需要保存日志、做十六进制收发那还是minicom更合适。工具安装命令优点缺点cutecomsudo apt install cutecom图形界面功能直观需要桌面环境minicomsudo apt install minicom功能全面支持脚本配置稍繁琐screensudo apt install screen即开即用退出方式不直观picocomsudo apt install picocom轻量兼容性好功能相对简单3.3 用Python快速搭一个串口收发脚本如果你需要做自动化测试或者数据记录用Python的pyserial库会更灵活。安装很简单pip install pyserial。下面是一个基本的收发示例import serial import time ser serial.Serial( port/dev/ttyTHS2, baudrate115200, bytesizeserial.EIGHTBITS, parityserial.PARITY_NONE, stopbitsserial.STOPBITS_ONE, timeout1 ) if ser.is_open: print(串口已打开) ser.write(bhello from host\n) time.sleep(0.5) data ser.read(ser.in_waiting or 1) print(收到:, data) ser.close()这个脚本的好处是你可以把它嵌到更大的测试框架里比如循环发送指令、解析返回数据、记录时间戳。我在做批量板卡测试时就是用类似的方式把串口操作封装成函数然后配合多线程或者异步IO来处理多个串口。注意pyserial的timeout参数很重要设成0会导致读操作立即返回空设成None会一直阻塞。一般设0.5到1秒比较合适。4. 参数配置与流控那些容易配错的地方4.1 波特率、数据位、校验位、停止位的匹配逻辑串口通信的本质是双方按照约定的时序来采样数据线。波特率决定了每一位持续的时间数据位决定了每帧传几个比特校验位用于简单的错误检测停止位标志一帧结束。这四项必须完全一致否则接收方采样点会偏移导致数据错乱。tx1的调试串口默认是115200-8-N-1也就是波特率115200、8位数据、无校验、1位停止。但如果你用其他设备跟它通信比如某些工控模块默认是9600或者19200那就必须改成一模一样。我见过有人把波特率设成115200但数据位设成7结果收到的全是乱码排查了半天才发现是数据位的问题。4.2 硬件流控和软件流控什么时候该开流控的作用是防止发送方发得太快接收方缓冲区满了导致数据丢失。硬件流控用RTS/CTS两根线来握手软件流控用XON/XOFF字符来控制。在tx1的调试串口场景下一般不需要开流控因为数据量不大而且很多USB转TTL模块只引出了TX、RX、GND三根线根本没有RTS/CTS。但如果你要做高速数据传输比如通过串口传文件或者大量传感器数据那就需要考虑流控。这时候要确认板子那边的串口控制器支持哪种流控以及线缆是否接对了。我遇到过一个问题软件流控开启后如果数据里恰好包含XON或XOFF对应的字节就会被误认为是流控字符导致通信中断。所以二进制数据传输一般不用软件流控。4.3 用stty查看和修改当前串口参数在Linux下可以用stty -F /dev/ttyTHS2 -a来查看当前串口的所有参数包括波特率、数据位、流控设置等。修改的话用stty -F /dev/ttyTHS2 115200 cs8 -cstopb -parenb -crtscts这条命令的意思是波特率115200、8位数据、1位停止、无校验、关闭硬件流控。这个命令在脚本里很有用因为有时候程序打开串口时会覆盖之前的设置你可以在程序启动前先用stty把参数固定下来。但要注意stty的设置只在当前打开的文件描述符有效程序重新打开串口后可能会恢复默认值。5. 实际收发测试从回环验证到板卡通信5.1 先做回环测试排除工具和线缆问题回环测试是最基本的验证手段。把USB转TTL模块的TX和RX用一根杜邦线短接然后在cutecom或者Python脚本里发送数据如果能原样收到说明从主机到串口模块这一段是通的。这一步能排除掉驱动问题、权限问题、工具配置问题。如果回环测试都收不到数据那就要检查USB转TTL模块的驱动有没有装好。在Linux下用lsusb看能不能识别到芯片用dmesg | tail看有没有报错。常见的CH340、CP2102、FT232这些芯片较新的内核一般都自带驱动但有些廉价模块可能需要手动装驱动。5.2 连接tx1后的双向通信验证回环测试通过后把USB转TTL模块接到tx1的对应引脚上。注意TX要接板子的RXRX要接板子的TXGND要共地。如果板子那边是3.3V电平USB转TTL模块也要选3.3V档位否则可能烧坏引脚。接好之后先在主机端打开串口然后在板子端执行echo test /dev/ttyTHS2看主机端能不能收到。反过来主机端发送数据板子端用cat /dev/ttyTHS2看能不能收到。这个双向验证能确认硬件连接和基本配置都没问题。我当初遇到过一个情况主机端能收到板子的数据但板子收不到主机的数据。后来发现是USB转TTL模块的TX引脚虚焊了换了一个模块就好了。所以如果单向不通优先检查线缆和焊接。5.3 用cat和echo做最简单的收发测试在板子端cat /dev/ttyTHS2会一直阻塞等待数据收到什么就打印什么。这个命令适合快速验证接收功能。发送的话用echo hello /dev/ttyTHS2但要注意echo默认会加换行符如果对方对换行敏感可以用printf代替。这两个命令虽然简单但在排查问题时非常有用。因为它们不涉及复杂的缓冲和配置能直接反映底层串口的状态。如果cat能收到数据但你的程序收不到那问题大概率在程序配置上而不是硬件或驱动。6. 踩坑记录权限、DMA丢数据、节点被占用6.1 串口被系统控制台占用导致无法打开前面提到ttyTHS1通常被系统控制台占用但有时候ttyTHS2也可能被其他服务占用。判断方法是用sudo lsof /dev/ttyTHS2看有没有进程打开着这个设备。如果有要么停掉那个服务要么换一个串口节点。还有一种情况是getty服务在串口上启动了登录会话。可以用systemctl status serial-gettyttyTHS2查看如果确实启动了用sudo systemctl stop serial-gettyttyTHS2停掉并sudo systemctl disable serial-gettyttyTHS2禁止开机自启。6.2 DMA接收首帧丢失的排查思路这个问题在STM32等单片机上也常见在tx1上同样可能出现。现象是串口配置成DMA接收后第一帧数据经常收不到或者收不全。原因通常是DMA通道还没准备好数据就已经到了。解决办法是在使能DMA接收之前先清空接收缓冲区或者用中断方式接收第一帧后续再切到DMA。在Linux下串口驱动一般已经处理好了这些底层细节但如果你自己写内核模块或者用裸机程序就要注意这个时序问题。我当时的做法是在打开串口后先延时一小段时间再开始发送数据给驱动足够的初始化时间。6.3 接收数据乱码的几种典型原因乱码是串口调试中最常见的问题原因主要有几类波特率不匹配、数据位或校验位设置错误、电平不匹配、地线没接好、线缆过长导致信号衰减。排查顺序建议从软件配置开始确认双方参数完全一致然后再检查硬件连接。有一个容易被忽略的点是时钟精度。有些廉价USB转TTL模块的晶振精度不够在高波特率下误差累积会导致采样偏移。如果115200下乱码但9600下正常那很可能就是模块的时钟问题换一个好点的模块试试。7. 进阶用法把串口封装成可复用的C模块7.1 串口初始化的标准流程如果你要在C程序里操作串口标准流程是open打开设备文件tcgetattr获取当前配置修改termios结构体中的波特率、数据位等参数tcsetattr写回配置然后就可以用read和write进行收发了。下面是一个简化的初始化函数#include termios.h #include fcntl.h #include unistd.h int serial_open(const char *device, int baud) { int fd open(device, O_RDWR | O_NOCTTY | O_NDELAY); if (fd 0) return -1; struct termios options; tcgetattr(fd, options); cfsetispeed(options, baud); cfsetospeed(options, baud); options.c_cflag | (CLOCAL | CREAD); options.c_cflag ~PARENB; options.c_cflag ~CSTOPB; options.c_cflag ~CSIZE; options.c_cflag | CS8; options.c_lflag ~(ICANON | ECHO | ECHOE | ISIG); options.c_iflag ~(IXON | IXOFF | IXANY); options.c_oflag ~OPOST; tcsetattr(fd, TCSANOW, options); return fd; }这个函数把常用的配置都设好了返回文件描述符供后续读写。注意O_NDELAY会让read立即返回如果你想要阻塞读可以去掉这个标志或者在read之前用fcntl改回来。7.2 用select实现非阻塞接收在实际项目里串口接收通常不能阻塞主线程这时候可以用select或者poll来监听文件描述符的可读事件。下面是一个简单的select示例fd_set readfds; struct timeval tv; FD_ZERO(readfds); FD_SET(fd, readfds); tv.tv_sec 1; tv.tv_usec 0; int ret select(fd 1, readfds, NULL, NULL, tv); if (ret 0 FD_ISSET(fd, readfds)) { char buf[256]; int n read(fd, buf, sizeof(buf)); // 处理收到的数据 }这种方式的好处是不会一直卡在read上可以同时处理其他任务。如果超时返回0说明没有数据到达可以继续做别的事情。7.3 ringbuffer在串口接收中的应用高速串口接收时如果处理不及时数据可能会被覆盖。用ringbuffer环形缓冲区可以缓解这个问题中断或者select收到数据后先写入ringbuffer主循环再从ringbuffer里取数据处理。这样即使处理速度暂时跟不上也不会丢数据。ringbuffer的实现要点是维护读指针和写指针写入时检查剩余空间读取时检查可用数据量。在嵌入式场景下ringbuffer通常用数组加两个索引来实现不需要动态内存分配适合资源受限的环境。8. 一些零散但实用的经验关于波特率的容错范围一般串口能容忍2%到3%的误差超过这个范围就会开始出现误码。所以如果你用非标准波特率比如某些传感器用的自定义速率要确认双方的时钟源精度足够。关于线缆长度RS232标准下15米左右是可靠传输的极限RS485可以到1200米。tx1的调试串口是TTL电平一般只适合板级或者短距离连接超过几十厘米就可能不稳定。如果要做长距离通信需要加电平转换芯片转成RS485或者RS232。关于热插拔串口线不支持热插拔带电插拔可能会损坏引脚或者导致系统异常。我一般都是在断电状态下接线确认无误后再上电。关于日志记录长时间调试时建议把串口输出保存到文件方便事后分析。cutecom有日志保存功能命令行工具可以用tee或者重定向。Python脚本里可以直接写文件加上时间戳更利于排查。最后说一个我自己的习惯每次换一块新板子或者新模块我都会先用回环测试确认工具链没问题然后再接板子。这个习惯帮我排除了很多次“以为是板子问题其实是线缆问题”的情况。串口调试看起来简单但细节很多耐心一点按步骤来基本都能搞定。