第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