ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Linux串口调试利器:stty命令详解与实战经验

Linux串口调试利器:stty命令详解与实战经验 我最早用 stty是因为一个 GPS 模块。厂家手册写得明明白白9600 波特率8N1可一到 Linux 下我就卡住了——开 minicom 手工调是能读到数据但想写个脚本定时抓取、自动分析minicom 这种交互式工具根本没法用。后来被人点了一句“你用 stty 先把串口参数设好再 cat 读不就行了”一试确实通。从那以后stty就成了我调试串口设备时最常敲的命令之一。如果你要在 Linux 下跟串口设备打交道不管是路由器 console 口、单片机日志口、GPS 模块还是各种传感器stty 都是一个绕不开的基础工具。它不装任何额外软件系统自带一条命令就能把波特率、数据位、校验位、停止位、流控这些串口参数全部设好配合 cat、echo、dd 就能完成读写。这篇就是我从日常调试里整理出来的 stty 串口配置经验。1. 先弄清楚stty 到底管的是哪一层1.1 串口设备不是普通文件背后是 termios 体系很多 Linux 新手会把/dev/ttyUSB0当成普通文件来理解觉得往里面写数据、从里面读数据就行了这其实是个误解。串口设备在 Linux 里虽然以设备节点的形式暴露在/dev目录下但它真正的工作链路要比“读写文件”复杂得多内核里有 tty 核心、线路规程line discipline、串口驱动这些模块层层配合才把一个字节一个字节的数据从物理线上收进来或者发出去。stty 的名字是 “set tty”它操作的对象正是这套 tty 体系里的参数。具体来说Linux 沿用了 POSIX 标准里的 termios 接口stty 把用户输入的命令翻译成对 termios 结构体各个标志位的修改然后通过 ioctl 调用 TCSETS 把配置下发给内核驱动。也就是说你用stty -F /dev/ttyUSB0 115200设的不只是一个“波特率数值”而是把整个终端通道的行为都改了。当你执行stty -F /dev/ttyUSB0 -a时看到的那一大堆cs8、parenb、ixon、opost之类的单词其实都对应 termios 结构体里的具体位域比如c_cflag、c_iflag、c_oflag、c_lflag。理解了这一层你就能明白为什么串口调试不能只盯着波特率。波特率只是 c_cflag 里的一个速度字段而收发数据的每一个环节都可能被其他标志位影响。比如输出处理标志位opost可以把\n自动转换成\r\n在文本场景看起来是方便但在二进制通信场景就是灾难。stty 的价值恰恰在于它把这一整套复杂的状态集中暴露在一条命令下你可以逐个关掉不需要的“加工步骤”让串口回归到“原始管道”的本色。1.2 为什么“波特率对了”仍然读不出数据我见过不少同事拿着 USB 转串口模块接单片机开发板波特率明明选对了打开 cat 却什么都看不到或者出现一堆乱码。这时候最容易被忽视的就是行规则line discipline在中间“捣乱”。线路规程可以理解成串口数据和应用之间的一个中间层它负责做一些“终端语义”处理收到的回车符要不要转成换行符按 CtrlC 这种控制字符要不要触发中断信号从键盘输入的内容要不要回显到屏幕这些处理在正常操作终端时是合理的但对于串口设备通信来说很多都是多余甚至有害的。举个例子你从 GPS 模块收到的数据流里如果恰好有0x11、0x13这样的字节而软件流控ixon/ixoff又没有关那内核就会把它们当成 XON/XOFF 控制信号数据直接就被吞掉了。所以真正规范的串口初始化通常要在设置波特率的基础上再用 raw 模式把行规则里那些自动转换、信号生成、回显之类的东西全部关掉。raw 是 stty 里一个很有用的参数它相当于批量关闭了多种输入输出处理。很多人调不通串口不是波特率设错了而是忘了加 raw或者忘了关流控。这也是我在下面实操部分会反复强调的一点串口参数不光是“物理层配置”还包括“驱动行为配置”两者都对了数据才干净。2. 基础用法查看与设置串口参数2.1 先看清当前状态stty -a 要怎么看调参数之前先搞清楚当前串口到底是什么状态。查看串口当前参数的命令是stty -F /dev/ttyUSB0 -a注意这里一定不能少了-F。-F的作用是指定一个设备节点如果不加它stty 默认操作的是“当前进程的终端”也就是你正在敲命令的那个终端。直接执行stty -a看的是自己终端的设置不涉及串口。加了-F /dev/ttyUSB0之后输出信息很长我这里挑几个跟串口通信最相关的字段出来speed 9600 baud当前波特率是 9600。cs88 位数据位如果是cs7就是 7 位数据位。-parenb没有启用校验parenb表示启用校验位。-cstopb使用 1 个停止位cstopb表示使用 2 个停止位。clocal忽略调制解调器控制信号比如 DCD、CTS在直连设备时通常需要。-crtscts没有开启硬件流控crtscts表示启用 RTS/CTS 硬件流控。-ixon -ixoff没有开启软件流控ixon/ixoff对应 XON/XOFF 流控。-echo没有回显echo会把收到的数据在发送端显示有时会造成数据重复。opost输出处理开启这个参数会把\n转成\r\n对二进制通信很不利。我建议你拿到一个未知串口设备时第一件事就是先执行这条查看命令把当前参数记录下来。这不仅能帮你确认设备是否可用也能避免之后误判比如你明明设置过流控但插拔一次设备后参数被驱动重置了看一眼输出就能发现问题。2.2 常用参数速查从“8N1”反推 stty 命令串口通信里最常说的“8N1”完整含义是8 个数据位、无校验位、1 个停止位。在 stty 里对应这样一组参数stty -F /dev/ttyUSB0 115200 cs8 -cstopb -parenb这里115200是波特率cs8是 8 位数据位-cstopb是 1 个停止位-parenb是无校验。下面这张表是几种常见串口格式对应的 stty 参数你可以直接保存下来遇到不同设备时对照使用常见格式含义stty 参数组合8N18 数据位无校验1 停止位cs8 -parenb -cstopb8E18 数据位偶校验1 停止位cs8 parenb -parodd -cstopb8O18 数据位奇校验1 停止位cs8 parenb parodd -cstopb7E17 数据位偶校验1 停止位cs7 parenb -parodd -cstopb8N28 数据位无校验2 停止位cs8 -parenb cstopb这里有一个容易记混的点-cstopb和cstopb的关系。POSIX 标准里 CSTOPB 置位代表“使用两个停止位”清除则代表“使用一个停止位”所以不要被-cstopb里那个减号吓到它才是我们最常用的“1 位停止位”。校验位同理parenb是启用校验-parenb是禁用启用校验后parodd表示奇校验-parodd表示偶校验。实际设置时还要根据设备需求带上流控参数。大多数单片机、传感器模块、路由器 console 口都是无流控也就是把硬件流控和软件流控都关掉stty -F /dev/ttyUSB0 115200 cs8 -cstopb -parenb -crtscts -ixon -ixoff如果你不确定设备是否用流控就先全部关闭。流控这东西接错线或者设备不支持时最常见的症状就是“能发不能收”或者“收几包就卡死”先关掉通常能排除一大半问题。2.3 给脚本用的三个小技巧-F、raw、stty -g日常调试里我一般不只是手动设置参数还会写脚本做一些自动化采集。这时候有三个小技巧非常有用。第一个是始终用-F指定设备。这不仅能避免误操作当前终端还能让脚本的意图更清晰。对串口设备的任何 stty 操作都写成stty -F /dev/ttyUSB0 ...不要省略-F。第二个是设置 raw 模式。读取二进制数据或不确定数据内容时建议在设置完波特率后追加 raw 参数stty -F /dev/ttyUSB0 115200 raw -echoraw 会关闭输入输出的各种字符转换和信号处理-echo会关闭回显。为什么关了回显重要因为有些设备会把收到的数据再原样返回如果你开着 echo屏幕上可能同时混入自己发出去的数据造成解析干扰。这个组合基本是我调试任何设备时的“黄金起点”。第三个是保存和恢复原参数。在一个自动化脚本里如果你在脚本中途要改变串口参数结束后最好恢复原来的状态。stty 提供了一个-g选项可以输出一行“当前参数编码”之后再用这行内容恢复# 保存当前串口参数 ORIG$(stty -F /dev/ttyUSB0 -g) # 修改成目标参数 stty -F /dev/ttyUSB0 115200 cs8 -cstopb -parenb raw # 做你的读写操作... # 恢复原参数 stty -F /dev/ttyUSB0 $ORIG我之前在一个嵌入式测试脚本里就这么干过设备驱动对 termios 状态很敏感恢复原参数之后后续其他程序再打开设备时就不会莫名其妙报错。3. 实操用 stty 把串口“铺好路”再读写数据3.1 从 USB 转串口到读出第一帧数据完整流程下面我用一个最常见的场景演示电脑上插了一个 USB 转串口模块接了一块以 115200 波特率输出日志的单片机开发板。第一步确认设备节点。插上模块后先看系统是否识别到了设备ls /dev/ttyUSB* ls /dev/ttyACM*不同芯片生成的节点名不一样常见的有/dev/ttyUSB0CH340、CP210x、FTDI 等和/dev/ttyACM0STM32 虚拟串口、Arduino、4G 模块 AT 口等。如果/dev下没有出现新设备再用dmesg | tail查看内核日志一般能看到类似ch341-uart converter now attached to ttyUSB0的提示。第二步查看当前参数。执行stty -F /dev/ttyUSB0 -a确认设备可访问。如果提示Permission denied说明当前用户没有权限后面我会专门讲解决办法。第三步初始化串口参数。根据开发板手册设置波特率、数据格式并关闭流控stty -F /dev/ttyUSB0 115200 cs8 -cstopb -parenb -crtscts -ixon -ixoff为了保险我通常会再补一条stty -F /dev/ttyUSB0 raw -echo不要担心 raw 会把波特率清掉raw 只改行规则相关的标志位不影响已经设置好的 speed、cs8 这些物理层参数。第四步读取数据。直接 catcat /dev/ttyUSB0如果设备持续输出日志你会看到内容在终端里滚动。如果设备只在收到请求后才回复cat 会一直阻塞等待这很正常。担心卡死的话可以加 timeout 限制timeout 10 cat /dev/ttyUSB010 秒后自动退出适合快速确认设备是否有数据。第五步如果需要把日志保存到文件直接重定向timeout 30 cat /dev/ttyUSB0 device_log.txt整套流程最核心的思路就是先把 stty 配置好再打开设备读写。你可以把 stty 理解成“铺路”cat、echo 这些命令只是在路上跑的车路没铺好车跑得越快越容易翻。3.2 往串口发数据AT 指令、printf 和换行符的坑读串口只是半条路很多时候还要往设备发命令比如发 AT 指令给 4G 模块或者发控制指令给传感器。最直观的写法是echo AT /dev/ttyUSB0但这条命令十有八九会踩坑因为echo会在字符串末尾自动加一个换行符\n而很多串口设备尤其是 AT 指令集设备要求每条指令以\r\n回车换行结束。如果只收到\n设备可能完全没反应或者把指令当成半个命令丢弃。所以我更推荐用printf明确控制输出格式printf AT\r\n /dev/ttyUSB0如果用echo也得用-e参数开启转义echo -e AT\r /dev/ttyUSB0注意有些 shell 的 echo 默认不开-e所以跨脚本使用时不建议依赖 echo 的转义行为用 printf 更稳。还有一个细节如果要发送的是二进制数据比如给某个传感器下发配置字节不要用 echo直接用 printf 发十六进制也行写成printf \x01\x02\x03\x00 /dev/ttyUSB0这种方式能精确控制每个字节不受终端换行转换的影响。不过要注意如果你之前没有关闭流控或者没有进入 raw 模式某些字节仍然可能被行规则拦截或转换所以发数据前最好把 stty 参数确认一遍。3.3 三个真实场景GPS、单片机日志、路由器 console 口第一个场景是 GPS 模块。很多 GPS 模块默认 9600 波特率输出 NMEA 0183 语句每行以$开头比如$GPRMC、$GPGGA。我当时的采集脚本就是先执行stty -F /dev/ttyUSB0 9600 cs8 -cstopb -parenb -crtscts -ixon -ixoff raw -echo然后timeout 60 cat /dev/ttyUSB0 gps_raw.log采集完再慢慢分析。整个过程完全不需要人为干预stty 一条命令就把串口准备利索了。第二个场景是单片机开发板的调试日志。开发板通过 USB 转串口或者 ST-Link 自带的虚拟串口往外打日志波特率常见 115200 或 460800。直接 stty 设置后 cat 就能看。这里我还遇到过一个问题某些开发板的串口芯片 DTR/RTS 引脚和复位电路连在一起串口工具一打开或者一关闭开发板就被复位。这时候可以尝试加上-hupcl参数它会让关闭文件描述符时不向串口发送挂断信号部分板子加了这个参数后就不会乱复位了。第三个场景是路由器、交换机的 console 口。网络设备 console 口大多默认 9600 8N1而且要求无流控。登录时需要交互输入用户名密码所以纯用 stty 加 cat 并不方便这种场景直接上 screen 或者 minicom 更实用。但有一个值得记住的点如果在 minicom 里发现键盘输入无效、屏幕出现乱码往往不是波特率的问题而是设备要求关闭硬件流控。我在调试一台老交换机时9600 波特率、8N1 全对就是开着流控结果登录界面出来了敲命令却完全没反应关掉 crtscts 之后立刻正常。这种经验用 stty 也可以验证先用 stty 把流控关了再用screen /dev/ttyUSB0 9600打开能减少很多玄学问题。4. 谁动了我的串口常见问题与排查技巧实录4.1 找不到设备或权限不足先说设备节点不存在。插上 USB 转串口模块后如果/dev/ttyUSB0一直不出现先确认用的什么芯片。常见的 CH340、CP210x、FTDI、PL2303 在 Linux 下都有对应的内核驱动一般插上就能识别。可以执行lsusb看 USB 总线里有没有对应设备。如果能看到设备但/dev下没有节点多半是驱动没加载执行dmesg | tail看内核报错信息必要时手动加载模块比如sudo modprobe ch341。权限问题更常见。执行stty -F /dev/ttyUSB0 -a时如果提示Permission denied说明当前用户不在有串口访问权限的用户组里。Debian/Ubuntu 系列一般是 dialout 组Fedora 也可能是 dialout 或 uucp 组。解决办法sudo usermod -aG dialout $USER然后重新登录一次或者执行newgrp dialout临时生效。之后再用groups确认。注意改用户组后如果不开新的 shell当前会话可能不会自动生效我遇到过不少人改完组名后还在旧终端里试结果一直报 Permission denied。4.2 设置完没生效或设备被其他进程占用stty 设置完成后如果马上用 cat 读数据还是不对第一反应不是怀疑参数而是查一下有没有其他进程也在占用这个串口。串口设备的 termios 配置是全局共享的后打开设备的进程如果修改了参数前面设置的配置就可能被覆盖。排查占用进程lsof /dev/ttyUSB0如果什么输出都没有说明没有进程打开它。如果列出来了一大堆或者你发现某个进程一直占着设备可以用fuser -v /dev/ttyUSB0查看详细情况。这里要单独提醒一点桌面版 Linux 里的 ModemManager 服务会自动扫描并探测串口设备尤其是/dev/ttyS*这类主板串口可能会把设备打开、发 AT 指令、修改参数导致你调试时出现“刚设置好过一会又变回去”的诡异现象。如果确认是 ModemManager 在干扰调试期间可以临时停掉它sudo systemctl stop ModemManager测试完再启动。服务器上如果跑着业务动手前一定先评估不要随手 disable尽量只针对具体设备做排查。4.3 乱码、丢字节、回显导致的怪现象乱码是串口调试里最常见的现象。排查顺序我建议固定下来先检查波特率是否匹配再检查数据位、校验位、停止位是否匹配最后检查流控。乱码大多是波特率不匹配几十块钱的 USB 转串口模块跑 115200 以上时也可能因为线材质量问题出现随机乱码换一根短一点的杜邦线或屏蔽线往往就好了。丢字节的问题和乱码不同通常是流控或行规则引起的。二进制数据里如果出现0x11、0x13而软件流控没有关闭数据会被内核当流控字符吞掉。所以抓二进制数据时一定要保证-ixon -ixoff。另外 raw 模式也要开否则输出处理可能把数据里的某个字节改掉比如把\n展开成\r\n无形中多了字节看起来就像数据出错。还有一种很容易被忽略的回显问题。如果设备的串口是“半双工”或者它会把收到的数据原样返回而你又在终端里开了 echo那么你看到的输出里会混入自己刚发出去的命令数据。表现形式是你发一条 AT回来两遍 AT一遍是自己回显的一遍是设备回复的。遇到这种情况检查 stty 输出里是不是echo而不是-echo只要加上-echo就能解决。这个坑在刚开始接触串口时特别容易踩因为很多人以为是设备不正常其实只是终端回显。4.4 常见问题速查表我根据这些年调试串口的经验整理了一张问题速查表遇到问题可以对着查现象可能原因排查方式设备节点不存在驱动未加载lsusb、dmesg | tail权限不足用户不在 dialout/uucp 组groups、usermod -aG dialout $USER乱码波特率/校验/停止位不匹配逐个尝试常见波特率收不到数据软件流控未关闭确认-ixon -ixoff数据里多了字节输出处理或回显未关闭加raw -echo接收几包后卡住硬件流控开启但线没接关闭crtscts配置被覆盖其他进程占用lsof、fusershell 卡住无法输入误在终端执行了 stty raw盲打stty sane后回车最后一行要单独解释一下。如果你没有指定-F直接在当前终端敲了stty raw -echo你的 shell 会进入“原始模式”按回车不换行、敲命令不显示、甚至 CtrlC 都可能失效。这时候别慌键盘输入其实还是能进系统的只是不显示。盲打stty sane再按回车一般就能恢复正常。这个坑我在第一次学 stty 时就踩过后来再也不敢不写-F了。5. 调试几年串口后我最先检查的几件事5.1 固定一套“黄金配置”不来回试现在每次接新的串口设备我基本不会拿 minicom 一点点试而是先固定用一套最保守的参数9600 或 115200 波特率、8N1、关闭所有流控、raw 模式、关闭回显。对应命令就是stty -F /dev/ttyUSB0 115200 cs8 -cstopb -parenb -crtscts -ixon -ixoff raw -echo这套配置能匹配市面上大部分单片机模块、GPS 设备、路由器 console 口。设完之后用timeout 3 cat /dev/ttyUSB0看有没有输出。如果能收到再根据设备手册微调收不到再换波特率试。这样做的好处是变量少排查步骤清晰不会出现“明明调了好几项却不知道是哪一项生效”的情况。5.2 别急着装串口工具stty 加 cat 已经够用很多人一提到串口调试就想着装 minicom、screen、putty 之类的工具但在服务器或者精简系统上有时没有网络、没有图形界面也不方便装额外软件。stty 属于系统基础工具几乎每个 Linux 发行版默认都有cat、echo、dd 更是 shell 自带。所以不管你是在树莓派上采集传感器还是在服务器上跑嵌入式设备的自动化测试stty 加这几个基础命令完全可以覆盖大部分需求。我经常这样做一个 shell 脚本里先用 stty 初始化串口然后用循环读取数据、解析数据最后把结果写入数据库或者日志文件。整个过程不依赖任何第三方串口库部署起来非常省事。如果以后要处理更复杂的串口协议再考虑上 Python 的 pyserial。5.3 最后一个小技巧从波特率往上试如果完全不知道对面设备的参数我个人的经验是先用stty -F /dev/ttyUSB0 -a看默认配置然后从 9600 开始试波特率。很多模块出厂默认就是 9600这是串口世界最经典的速度试完 9600 再试 115200然后是 57600、38400、19200。每设一个波特率都用一个“已知会持续输出的设备”来验证比如 GPS 模块它上电后会周期性输出 NMEA 数据如果波特率对了cat 会立刻刷屏判断非常直观。如果是只答不问的设备比如 AT 模块你可以发一条AT看看有没有OK回复。但要注意AT 指令必须用\r\n结尾不要犯我之前说的 echo 自动加\n的问题。调试串口这件事说到底是“耐心加细心”的活参数就那几个挨个试一遍总能找到正确的组合。用 stty 的好处就是试错成本极低一条命令、一次收放几秒钟就能完成一次验证。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进