第1章:串口通信脚本化——用pySerial实现自动收发、批量烧录与日志抓取
大家好,我是你们的老朋友。今天咱们聊聊嵌入式开发里最基础、也最磨人的一件事——串口通信。
说实话,我入行那会儿,串口调试全靠手动。打开SecureCRT,点一下发送,看一眼回显,再点一下……一天下来,眼睛都快瞎了。后来我琢磨着,这事儿能不能让Python替我干了?
嗯,答案就是pySerial。这玩意儿,说白了就是Python操作串口的瑞士军刀。今天我就带你把它玩明白。
1.1 pySerial是什么?为什么选它?
pySerial是Python的一个第三方库。它封装了底层串口操作,让你用几行代码就能完成收发数据。
我为什么推荐它?三个理由:
- 跨平台:Windows、Linux、macOS,一套代码通吃。我在Windows上写完,扔到树莓派上直接跑,没出过问题。
- API简洁:打开、读写、关闭,三句话搞定。新手十分钟就能上手。
- 生态成熟:社区活跃,遇到坑了,StackOverflow上一搜就有答案。
核心观点:pySerial不是万能的,但对付95%的串口自动化场景,绰绰有余。
1.2 安装与基础用法
安装没什么好说的,一行命令:
pip install pyserial
如果你在公司内网,记得配好代理。我曾经因为没配代理,卡了半小时……
装好之后,咱们写个最简单的例子:
import serial
# 打开串口
ser = serial.Serial(
port='COM3', # Windows下是COM口,Linux下是/dev/ttyUSB0
baudrate=115200, # 波特率,常见的有9600、115200
timeout=1 # 超时时间,单位秒
)
# 发送数据
ser.write(b'AT\r\n')
# 读取数据
response = ser.readline()
print(response)
# 关闭串口
ser.close()
你看,就这么几行。打开、写、读、关,完事儿。
小提示:timeout参数一定要设。不设的话,readline()会一直等,程序就卡死了。我刚开始就吃过这个亏。
1.3 自动收发数据——让脚本替你干活
手动收发太累了。咱们写个自动收发的脚本。
需求很简单:每隔1秒发一条指令,收到回复后打印出来。
import serial
import time
ser = serial.Serial('COM3', 115200, timeout=1)
commands = [b'AT\r\n', b'AT+CSQ\r\n', b'AT+CGMI\r\n']
for cmd in commands:
ser.write(cmd)
time.sleep(0.5) # 给设备一点反应时间
response = ser.read_all() # 读取所有可用数据
print(f"发送: {cmd}")
print(f"接收: {response}")
ser.close()
这里有个坑:read_all()不是阻塞的,它只读缓冲区里已有的数据。如果你需要确保收到完整回复,建议用read_until()或者自己写个循环。
注意:不同设备的回复格式不一样。有的以\r\n结尾,有的以\n结尾。写脚本前,先用串口助手摸清设备的脾气。
1.4 批量烧录——解放双手的关键
批量烧录,说白了就是让脚本替你反复执行“打开串口→发送固件→等待完成→关闭串口”这个流程。
我在项目里遇到过要烧录100块板子。手动烧?那得加班到天亮。写个脚本,泡杯咖啡,让它自己跑。
下面是一个简化版的批量烧录脚本:
import serial
import time
def flash_device(port, firmware_path):
ser = serial.Serial(port, 115200, timeout=10)
# 进入烧录模式(不同芯片指令不同)
ser.write(b'+++')
time.sleep(1)
# 发送固件
with open(firmware_path, 'rb') as f:
data = f.read()
ser.write(data)
# 等待烧录完成
while True:
line = ser.readline()
if b'SUCCESS' in line:
print(f"{port} 烧录成功")
break
elif b'FAIL' in line:
print(f"{port} 烧录失败")
break
ser.close()
# 批量烧录
ports = ['COM3', 'COM4', 'COM5']
for port in ports:
flash_device(port, 'firmware.bin')
time.sleep(2) # 给设备重启时间
关键点:批量烧录的核心是“状态机”。你要清楚每个阶段设备在干什么,脚本才能正确响应。我建议在脚本里加个日志,记录每块板的烧录结果,方便追溯。
1.5 日志抓取——自动记录,不留死角
日志抓取,就是让脚本24小时盯着串口,把所有输出都记下来。
我做过一个项目,设备跑着跑着就死机了。手动盯着看?不现实。写个日志抓取脚本,跑一晚上,第二天看日志,问题一目了然。
import serial
import datetime
ser = serial.Serial('COM3', 115200, timeout=1)
log_file = f"log_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}.txt"
with open(log_file, 'w') as f:
while True:
try:
data = ser.readline()
if data:
timestamp = datetime.datetime.now().strftime('%H:%M:%S.%f')
line = f"[{timestamp}] {data.decode('utf-8', errors='ignore').strip()}"
print(line)
f.write(line + '\n')
f.flush() # 立即写入磁盘,防止丢失
except KeyboardInterrupt:
print("日志抓取结束")
break
ser.close()
经验之谈:f.flush()这行很重要。不加的话,如果脚本中途崩溃,最后几秒的日志就丢了。我吃过这个亏,调试了一天才发现是缓存没刷。
1.6 常见问题与避坑指南
做串口脚本,有几个坑是绕不开的。我列出来,你记一下:
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 串口打不开 | 端口被占用或权限不足 | 检查是否有其他程序占用;Linux下加sudo |
| 读取数据乱码 | 波特率不匹配或编码问题 | 核对波特率;用errors='ignore'忽略非法字符 |
| 发送后无响应 | 设备未进入接收模式或指令格式不对 | 检查指令结尾是否有\r\n;确认设备状态 |
| 脚本运行一段时间后卡死 | 缓冲区溢出或线程阻塞 | 定期清空缓冲区;设置合理的timeout |
曾经踩过的坑:有一次我写了个长时间运行的日志脚本,跑了8小时后卡死了。查了半天,发现是串口缓冲区满了。从那以后,我每读一次数据就清一次缓冲区:ser.reset_input_buffer()。
1.7 小结
这一章咱们聊了pySerial的基础用法,包括自动收发、批量烧录和日志抓取。说白了,串口脚本化就是把重复劳动交给机器,让自己从枯燥的调试中解放出来。
下一章,我会带你深入pySerial的高级用法,比如多线程收发、自定义协议解析。到时候你会发现,串口脚本能做的事,比你想象的要多得多。
嗯,今天就到这儿。有什么问题,欢迎交流。
公众号:蓝海资料掘金营,微信deep3321