尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
Shell脚本驱动串口自动化:从配置到批量操作的实战指南
1. 串口操作与 Shell 脚本的碰撞为什么值得深挖搞嵌入式、工控或者硬件调试的朋友对串口这两个字肯定不陌生。板子一插终端一开敲几个命令看看输出这是日常。但如果你每天要重复十几次甚至几十次同样的串口交互动作比如给一批设备烧录参数、定时抓取传感器数据、自动回复模组指令纯靠手敲就有点折磨人了。我最初也是手动党后来设备数量一多实在扛不住才开始琢磨用 Shell 脚本把串口操作给自动化掉。这个项目的核心就是用 Shell 脚本去驱动串口设备完成命令的发送、接收、解析和循环控制。说白了就是让脚本代替你的手去跟串口那头的东西对话。它解决的是重复劳动和人为失误的问题——人敲命令会敲错、会忘、会手抖脚本不会。适合谁看如果你是在 Linux 环境下做硬件调试、设备批量配置、串口协议验证的从业者或者是个想把手头重复串口活儿自动化掉的新手这篇内容应该能帮你少走不少弯路。我下面要聊的不是那种教科书式的“串口是什么”科普而是实打实的脚本怎么写、参数怎么调、坑怎么避。从工具选型到脚本骨架从命令收发到异常处理一层层拆开讲。你跟着看完基本能自己搭出一套能跑、能改、能复用的串口操作脚本。2. 整体设计思路与工具选型拆解2.1 为什么用 Shell 而不是 Python很多人第一反应是串口操作上 Python 多方便pyserial 一装几行代码搞定。这话没错但在这个场景下我最终选了 Shell原因有几个。第一是环境依赖很多工控机、嵌入式 Linux 板子上Python 环境不一定齐全装包还可能受网络限制而 Shell 是系统自带的stty、echo、cat这些命令开箱即用。第二是轻量脚本几十行扔到设备上就能跑不需要额外运行时。第三是和系统命令的无缝衔接串口操作往往要和grep、awk、sed、timeout这些工具配合做文本处理Shell 天然就是干这个的。当然Shell 做串口也有短板比如二进制数据处理麻烦、复杂协议解析吃力。所以我的选型原则很明确文本型、命令应答型、批量配置型的串口交互用 Shell涉及复杂二进制协议、高速数据流的老老实实上 Python 或 C。这个边界得先划清楚不然用错工具会很难受。2.2 核心工具链stty 打底配合读写命令Shell 操作串口底层靠的是设备文件比如/dev/ttyUSB0、/dev/ttyS0这类。串口本质是个字符设备你往里写就是发送从里读就是接收。但直接读写之前必须先配置串口参数——波特率、数据位、停止位、校验位、流控这些全靠stty命令来设。stty是这套方案的地基。它负责把串口设备配置成你要的通信参数配置好之后用echo或printf往设备文件写数据就是发送用cat或read从设备文件读就是接收。这套组合看起来简陋但胜在稳定、通用、不依赖任何第三方库。我整理了一下这套工具链里各命令的分工方便你建立整体印象工具/命令职责关键点stty配置串口参数波特率、数据位等每次打开设备后都要重新配置echo/printf向串口发送数据printf对转义字符支持更好cat/read从串口读取数据需配合timeout防止阻塞timeout控制读写超时避免脚本卡死grep/awk解析返回内容提取关键字段做判断2.3 脚本的整体架构设计一个能用的串口脚本我一般拆成四层参数配置层、发送层、接收层、逻辑控制层。参数配置层负责用stty把串口设好发送层负责把命令按格式写出去注意要补上回车换行接收层负责在限定时间内把返回数据抓回来逻辑控制层负责判断返回是否符合预期、是否重试、是否进入下一步。这个分层的好处是每层职责单一改波特率只动配置层改命令格式只动发送层互不干扰。我见过不少人把所有逻辑揉在一个大函数里后来要加个重试机制或者换个设备改起来牵一发动全身。分层设计虽然前期多写几行但后期维护省心太多。提示串口设备文件在 Linux 下通常需要当前用户有读写权限。如果不想每次都用 root可以把用户加入dialout组或者用 udev 规则固定设备权限。这一步不做脚本跑起来会报 Permission denied。3. 核心细节解析与实操要点3.1 stty 参数配置一个字符都不能错stty配置串口参数写错一个通信就全乱。最典型的一条配置长这样stty -F /dev/ttyUSB0 115200 cs8 -cstopb -parenb -crtscts raw -echo我逐个拆解一下每个参数的含义因为这是最容易出错的地方。115200是波特率必须和对面设备一致差一点都收不到正确数据。cs8表示 8 位数据位这是绝大多数设备的默认值。-cstopb表示 1 位停止位前面加负号是“关闭两位停止位”的意思。-parenb表示无校验位。-crtscts关闭硬件流控很多 USB 转串口设备不支持硬件流控开着反而会卡住。raw是关键它让串口进入原始模式不做任何字符转换避免特殊字符被系统吞掉。-echo关闭回显防止发出去的东西又被读回来。这里有个特别容易踩的坑raw模式必须加。不加的话系统会对某些控制字符做处理比如你发个0x0d可能被转成0x0a导致对面收到的数据跟你发的对不上。我早期调试一个模组死活收不到正确应答查了半天才发现是没加raw数据被终端驱动偷偷改了。3.2 发送数据printf 比 echo 更靠谱发送命令时很多人习惯用echo但我更推荐printf。原因是printf对转义字符的支持更明确、更可控。串口通信里命令通常要以回车换行结尾也就是\r\n。用echo的话不同 Shell 对-e参数的支持不一致有的默认就解释转义有的不解释行为不统一。而printf是标准化的printf ATVER\r\n /dev/ttyUSB0这行命令把ATVER加上回车换行发出去干净利落。如果你要发的命令里带变量可以这样cmdATSET${value} printf %s\r\n $cmd /dev/ttyUSB0用%s占位再传变量能避免变量里的特殊字符被printf误解释。这个细节在处理带%或者反斜杠的参数时特别重要我吃过亏——有次参数里带了个%直接printf就报格式错误了。注意发送完命令后如果对面设备处理需要时间别立刻去读中间加个sleep 0.1到sleep 0.5的短延时给设备留出响应时间。延时多长取决于设备模组类的一般 100ms 够慢速设备可能要 500ms 甚至更久。3.3 接收数据timeout 是防卡死的命门从串口读数据最怕的就是阻塞。如果对面没数据返回cat /dev/ttyUSB0会一直挂着脚本就卡死了。所以读操作必须套timeoutresponse$(timeout 2 cat /dev/ttyUSB0)这行的意思是最多等 2 秒2 秒内读到什么算什么超时就强制结束。这样脚本永远不会因为等不到数据而挂死。但这里有个细节timeout结束后cat被杀死已经读到的数据会通过$()捕获到response变量里。不过要注意cat是读到 EOF 才返回的串口正常情况下不会产生 EOF所以实际上就是靠timeout来结束读取。更精细的做法是用read配合超时或者用dd指定读取字节数。比如你知道应答固定是 20 个字节可以这样response$(timeout 2 dd if/dev/ttyUSB0 bs1 count20 2/dev/null)dd读到指定字节数就返回比cat更可控。但前提是你得清楚应答的长度不然读多了或读少了都麻烦。我一般先用cat加超时的方式抓原始数据看格式摸清楚规律后再决定要不要换成dd。3.4 数据解析grep 和 awk 是你的左右手拿到返回数据后通常要判断里面有没有预期关键字或者提取某个字段。这时候grep和awk就派上用场了。比如判断返回里是否包含OKif echo $response | grep -q OK; then echo 命令执行成功 else echo 命令执行失败返回$response fi如果要提取VER:1.2.3里的版本号可以这样version$(echo $response | grep -oP \VER:\K[0-9.])grep -oP用 Perl 正则\K表示丢弃前面匹配的部分只保留后面的。这样version拿到的就是纯版本号。不过-P参数不是所有grep实现都支持嵌入式环境里可能是精简版grep这时候就得退回sed或awkversion$(echo $response | awk -F: /\VER:/{print $2})用awk按冒号分割取第二段兼容性更好。这种“先保兼容、再求简洁”的思路在嵌入式环境里特别实用。4. 完整实操流程与核心环节实现4.1 从零搭一个可复用的串口脚本骨架我把前面这些零散的点串起来给你一个可以直接改改就用的脚本骨架。这个骨架包含了配置、发送、接收、判断四个环节结构清晰方便你往里填自己的业务逻辑。#!/bin/bash # 配置区 PORT/dev/ttyUSB0 BAUD115200 TIMEOUT2 # 串口初始化 init_serial() { stty -F $PORT $BAUD cs8 -cstopb -parenb -crtscts raw -echo if [ $? -ne 0 ]; then echo 串口初始化失败$PORT exit 1 fi } # 发送命令 send_cmd() { local cmd$1 printf %s\r\n $cmd $PORT } # 接收应答 recv_resp() { timeout $TIMEOUT cat $PORT } # 主流程 init_serial send_cmd ATVER sleep 0.2 resp$(recv_resp) if echo $resp | grep -q OK; then echo 版本查询成功$resp else echo 版本查询失败$resp fi这个骨架跑起来之后你只需要改send_cmd里的命令和判断逻辑就能适配不同的设备。我建议把配置区单独拎出来端口、波特率、超时都做成变量换设备时只改这几行不用动逻辑代码。4.2 批量设备操作的循环控制单设备跑通之后下一步往往是批量操作。比如你有 10 个设备接在不同的串口上要挨个配置。这时候加个循环就行for port in /dev/ttyUSB0 /dev/ttyUSB1 /dev/ttyUSB2; do PORT$port init_serial send_cmd ATCONFIG1 sleep 0.3 resp$(recv_resp) if echo $resp | grep -q OK; then echo $port 配置成功 else echo $port 配置失败$resp fi done这里有个实操心得批量操作一定要加失败记录。哪个口成功了、哪个口失败了得记下来不然跑完一轮你都不知道哪些设备没配上。我一般会把结果写到一个日志文件里方便事后核对echo $port: $status /tmp/serial_batch.log另外批量操作时设备之间最好留点间隔别一个接一个猛发有些 USB 转串口芯片在快速切换时会出现数据错乱。加个sleep 0.5到sleep 1的间隔稳当很多。4.3 带重试机制的健壮性增强串口通信受干扰的概率不低偶尔丢一帧、应答超时是常事。如果脚本一遇到失败就退出批量操作会很难受。所以重试机制很有必要。我一般写个带重试的发送函数send_with_retry() { local cmd$1 local max_retry3 local i0 while [ $i -lt $max_retry ]; do send_cmd $cmd sleep 0.2 resp$(recv_resp) if echo $resp | grep -q OK; then return 0 fi i$((i 1)) echo 第 $i 次重试$cmd sleep 0.5 done return 1 }这个函数最多重试 3 次每次失败后等 0.5 秒再试。重试间隔不能太短得给设备恢复的时间。实测下来大部分偶发失败在第二次重试就能成功。如果 3 次都失败那基本可以判定是设备或线路问题了这时候再报错退出才合理。提示重试次数不是越多越好。设太多遇到真正故障的设备会卡很久。我一般设 3 次配合每次 0.5 秒间隔单设备最坏情况也就多等 1.5 秒整体效率可以接受。4.4 参数计算波特率与延时的关系这里补充一个容易被忽略的点波特率和延时的关系。波特率决定了每秒能传多少位115200 波特率下一个字节10 位含起始停止位大约需要 87 微秒。如果你发一条 20 字节的命令传输时间约 1.7 毫秒几乎可以忽略。但对面设备的处理时间往往远大于传输时间所以延时主要取决于设备处理速度而不是波特率。不过有个场景要注意高速连续发送时如果发送速度超过设备处理速度缓冲区会溢出。比如你 115200 波特率下不停发命令中间不加延时设备来不及处理就会丢数据。这时候要么降速要么加延时。我一般按“命令长度 × 10 / 波特率 设备处理时间”来估算最小间隔实际再加点余量。这个计算不复杂但能帮你避免很多莫名其妙的丢包问题。5. 常见问题与排查技巧实录5.1 收不到数据先查这五个地方串口脚本最常见的故障就是“发出去了但收不到”。我整理了一个排查顺序按这个顺序查基本能定位到问题排查项检查方法常见原因设备文件是否存在ls -l /dev/ttyUSB0设备没插好或驱动没加载权限是否足够ls -l看属主和权限用户不在 dialout 组参数是否匹配核对波特率、数据位、校验位和对面设备不一致接线是否正确检查 TX/RX 是否交叉TX 接 TX 导致收不到流控是否误开确认-crtscts硬件流控导致阻塞这五项里接线 TX/RX 交叉是最容易被忽略的。串口通信是交叉连接你的 TX 要接对面的 RXRX 接对面的 TX。如果直连发得出去但收不到现象就是“发送正常、接收全空”。我第一次搭硬件的时候就栽在这上面查了半天软件最后发现是线接错了。5.2 数据乱码多半是参数或模式问题收到乱码通常是三个原因。第一是波特率不匹配这是最常见的115200 对 9600收到的全是乱码。第二是没加 raw 模式系统对特殊字符做了转换。第三是数据位或校验位设置错误比如对面是 7 位数据位带校验你设成 8 位无校验高位就会错乱。排查乱码我的做法是先用最简单的配置试115200 cs8 -cstopb -parenb raw确认能收到可读字符后再逐步调整。如果换了配置还是乱码那就要怀疑是不是设备本身输出的就是二进制数据而不是文本。二进制数据用cat看当然是乱码这时候得用xxd或hexdump看十六进制timeout 2 cat /dev/ttyUSB0 | xxd这样能看到原始字节判断是不是协议数据。这个技巧在调试二进制协议时特别有用。5.3 脚本卡死不动timeout 没加或加错位置脚本卡死九成是读操作没加超时。cat一个没有数据的串口会永远阻塞。解决办法就是前面说的所有读操作都套timeout。但要注意timeout的位置它要包住整个读命令# 正确 response$(timeout 2 cat /dev/ttyUSB0) # 错误timeout 只包了 echocat 还是会阻塞 response$(timeout 2 echo x | cat /dev/ttyUSB0)第二种写法里timeout管的是echocat不受控照样卡死。这种错误很隐蔽因为看起来像是加了超时实际没起作用。我建议养成习惯凡是出现cat /dev/ttyXXX的地方前面必须有timeout直接修饰它。5.4 独家避坑设备热插拔后的设备名漂移这个坑我踩过不止一次。USB 转串口设备拔了再插设备名可能从/dev/ttyUSB0变成/dev/ttyUSB1脚本里写死的端口就失效了。解决办法有两个一是用 udev 规则给设备绑定固定名字二是脚本里做设备名探测。udev 规则的做法是写一条规则文件根据设备的 VID/PID 或序列号绑定固定符号链接。这个稍微复杂点但一劳永逸。简单点的做法是脚本启动时扫描可用串口PORT$(ls /dev/ttyUSB* 2/dev/null | head -n 1) if [ -z $PORT ]; then echo 未找到串口设备 exit 1 fi取第一个找到的串口。这个方法简单但多设备场景下不靠谱因为顺序不固定。所以我的建议是单设备用扫描多设备用 udev 绑定。别偷懒多设备场景下设备名漂移会让你调试到怀疑人生。5.5 常见问题速查表把上面这些整理成一张速查表出问题时对着查现象最可能原因快速验证解决完全无返回接线错/权限不足换线、查权限交叉接线、加组返回乱码波特率或模式错换波特率试统一参数、加 raw脚本卡死读操作无超时看有无 timeout补 timeout偶发丢数据无重试/间隔太短加日志观察加重试和延时设备名失效热插拔漂移ls /dev/ttyUSB*udev 绑定这张表覆盖了我实际遇到的大部分情况新手照着查能省不少时间。6. 脚本进阶把串口操作封装成可维护的工具6.1 函数库化让脚本从一次性变成可复用写到后面你会发现配置、发送、接收这几个动作在每个脚本里都要重复。与其每次复制粘贴不如抽成一个函数库文件用source引入。我一般建一个serial_lib.sh把通用函数放进去# serial_lib.sh serial_init() { stty -F $1 $2 cs8 -cstopb -parenb -crtscts raw -echo } serial_send() { printf %s\r\n $2 $1 } serial_recv() { timeout ${3:-2} cat $1 }业务脚本里这样用source ./serial_lib.sh serial_init /dev/ttyUSB0 115200 serial_send /dev/ttyUSB0 ATVER resp$(serial_recv /dev/ttyUSB0 2)这样职责分离库文件稳定后基本不用动业务脚本只管调。团队协作时尤其有用大家用同一套库行为一致不会出现“你的脚本能跑我的跑不了”的情况。6.2 日志记录出问题时唯一的救命稻草串口操作出问题时现场往往已经过去了没有日志就只能靠猜。所以我在脚本里一定会加日志把发送的命令、收到的原始数据、时间戳都记下来log() { echo [$(date %Y-%m-%d %H:%M:%S)] $* /tmp/serial.log } log SEND: $cmd log RECV: $resp日志里记原始数据很重要别只记“成功/失败”。有次我排查一个偶发失败就是靠日志里记录的原始返回发现设备偶尔会返回一个非标准的错误码才定位到是设备固件的问题。如果只记成功失败这个线索就丢了。6.3 用 expect 处理交互式串口场景有些设备的串口交互不是简单的“发一条收一条”而是有提示符、需要按提示逐步输入比如登录式的命令行界面。这种场景纯靠printf加cat很难处理因为要判断提示符再决定发什么。这时候可以引入expect它专门处理这种交互式对话expect EOF set timeout 5 spawn -open [open /dev/ttyUSB0 r] send admin\r expect Password: send 123456\r expect send show status\r expect eof EOFexpect能匹配特定字符串再触发下一步动作适合处理带提示符的交互流程。不过它需要额外安装嵌入式环境不一定有。所以我的原则是简单命令应答用纯 Shell复杂交互式流程才上 expect。工具选型跟着场景走别为了用而用。6.4 性能与稳定性的平衡取舍最后聊聊性能和稳定性的取舍。串口脚本跑批量任务时速度和稳定往往矛盾。延时加长了稳但慢延时短了快但容易丢。我的经验值是单条命令间隔不低于 100ms批量设备切换间隔不低于 500ms。这个值不是拍脑袋来的是实测下来在大多数设备上能兼顾速度和稳定的平衡点。如果追求更高稳定性可以在发送前先清空接收缓冲区避免上一次的残留数据干扰判断# 清空缓冲区读一下丢弃 timeout 0.1 cat $PORT /dev/null 21这行在发送新命令前执行把缓冲区里可能残留的旧数据读掉。代价是多花 100ms但能避免“读到了上一条命令的应答”这种坑。我在调试阶段一定会加这行稳定后再根据实际情况决定是否保留。7. 我在实际项目中的几点体会这套 Shell 串口方案我在好几个批量配置和调试场景里用过最大的感受是简单的东西用对了地方比复杂的方案更省心。Shell 脚本没有花哨的语法但胜在透明、可控、随处可跑。设备上有什么问题bash -x一开每一步执行都看得清清楚楚排查起来比调试一个 Python 程序还直接。另一个体会是串口脚本的健壮性八成靠超时和重试撑着。我早期写的脚本没加这些跑批量任务时经常中途卡死或者漏配设备后来把timeout和重试机制补上成功率肉眼可见地提升。这两个机制看起来简单但它们是脚本从“能跑”到“可靠”的分水岭。还有个小技巧分享给你调试阶段我会在脚本里临时加一行set -x把每条命令的执行过程打印出来配合日志一起看问题定位快很多。等脚本稳定了再把这行去掉。这个习惯帮我省了大量“猜哪里出错”的时间。最后说个扩展方向。这套骨架跑通后你可以往上叠更多东西比如把串口数据实时转发到网络端口做远程监控或者把配置结果汇总成报表。但别一上来就搞复杂先把单设备的收发跑稳再逐步加功能。串口这东西底层不稳上层叠再多都是空中楼阁。
RELATED

相关推荐

Qt QProcess进程管理详解与实战技巧

Qt QProcess进程管理详解与实战技巧

1. QProcess基础概念与核心价值 在Qt框架中,QProcess是一个强大的进程管理类,它允许我们在应用程序中启动和控制外部程序。作为一名有多年Qt开发经验的工程师,我发现QProcess在实际项目中有着不可替代的作用——无论是调用系统工具、执行脚本…

📅 2026/11/3 17:15:06
缺芯时代深度学习框架选型指南:飞桨、昇思、MNN三国杀与迁移优化实践

缺芯时代深度学习框架选型指南:飞桨、昇思、MNN三国杀与迁移优化实践

1. 从“缺芯”说起:ICT行业的底层逻辑正在被重写“缺芯”这个词,从2020年开始就成了ICT行业绕不开的话题。我在一线做系统集成和AI项目交付这些年,最直观的感受就是:以前选型看性能,现在选型先看供货。一颗原本几十块钱…

📅 2026/11/3 17:15:06
安卓电量校准真相:库仑计、charge_full与换电池容量修正

安卓电量校准真相:库仑计、charge_full与换电池容量修正

换过电池的朋友大概都遇到过这种别扭事:明明换了一块全新的大容量电芯,系统里的健康度还是显示原来那块老电池的数字,电量百分比也依旧我行我素,充到100%没用多久就开始往下掉,甚至从30%直接跳到0%自动关机。这时候网上…

📅 2026/11/3 17:15:06
MORE NEWS

更多资讯

📰

xray与fofa联动(附脚本)

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

虚拟环境的配置/pytorch的安装/在Jupyter notebook 添加虚拟环境

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

2021年Java进阶总结

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

【基础-配置文件】:hadoop配置文件作用概述ing

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

【运维与安装】Hadoop 3.3.1 :Setting up a Single Node Cluster.

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

工业SPI隔离实战:用数字隔离器MAX14431与PIC24HJ设计稳定通信链路

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬