我最早用 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)在中间“捣乱”。
线路规程可以理解成串口数据和应用之间的一个中间层,它负责做一些“终端语义”处理:收到的回车符要不要转成换行符?按 Ctrl+C 这种控制字符要不要触发中断信号?从键盘输入的内容要不要回显到屏幕?这些处理在正常操作终端时是合理的,但对于串口设备通信来说,很多都是多余甚至有害的。举个例子:你从 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。cs8:8 位数据位;如果是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 参数组合 |
|---|---|---|
| 8N1 | 8 数据位,无校验,1 停止位 | cs8 -parenb -cstopb |
| 8E1 | 8 数据位,偶校验,1 停止位 | cs8 parenb -parodd -cstopb |
| 8O1 | 8 数据位,奇校验,1 停止位 | cs8 parenb parodd -cstopb |
| 7E1 | 7 数据位,偶校验,1 停止位 | cs7 parenb -parodd -cstopb |
| 8N2 | 8 数据位,无校验,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/ttyUSB0(CH340、CP210x、FTDI 等)和/dev/ttyACM0(STM32 虚拟串口、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 这些物理层参数。
第四步,读取数据。直接 cat:
cat /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、fuser |
| shell 卡住无法输入 | 误在终端执行了 stty raw | 盲打stty sane后回车 |
最后一行要单独解释一下。如果你没有指定-F,直接在当前终端敲了stty raw -echo,你的 shell 会进入“原始模式”,按回车不换行、敲命令不显示、甚至 Ctrl+C 都可能失效。这时候别慌,键盘输入其实还是能进系统的,只是不显示。盲打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 的好处就是试错成本极低,一条命令、一次收放,几秒钟就能完成一次验证。