Python+PySerial实现多串口嵌入式设备的批量固件烧录工具
在产品量产阶段,给几十块电路板逐个烧录固件是效率瓶颈。人工操作不仅慢,还容易漏烧或烧错版本。用Python的PySerial库结合多线程,可以在一台PC上同时控制多个串口,实现批量并行烧录,将烧录效率提升数倍。本文展示一个可复用的工具框架,支持设备检测、固件下发、校验和日志汇总。
核心思路:多线程 + 串口池
每个烧录通道独立运行在一个线程中,共享一个串口列表。主线程负责发现可用串口、分发固件文件、收集结果。为避免串口竞争,每个线程独占一个串口对象。
import threading, serial, time, glob, sys
class FlasherThread(threading.Thread):
def __init__(self, port, firmware, baud=115200):
super().__init__()
self.port = port
self.firmware = firmware
self.baud = baud
self.result = None
def run(self):
try:
ser = serial.Serial(self.port, self.baud, timeout=5)
self._enter_download_mode(ser)
self._send_firmware(ser)
self._verify(ser)
ser.close()
self.result = (self.port, "成功")
except Exception as e:
self.result = (self.port, f"失败: {str(e)}")
进入下载模式
不同芯片进入烧录模式的方式不同。以ESP32为例,需要拉低GPIO0并复位。这里通过串口发送特定握手信号:
def _enter_download_mode(self, ser):
# 发送同步指令(以ESP32 ROM bootloader为例)
ser.setRTS(False)
ser.setDTR(True)
time.sleep(0.1)
ser.setDTR(False)
ser.write(b'\x07\x07\x12\x20' + 32*b'\x55') # 同步魔数
time.sleep(0.5)
response = ser.read(10)
if b'OK' not in response:
raise RuntimeError("进入下载模式失败")
实际使用时,应根据目标芯片(STM32、NXP、TI等)替换对应的握手协议。
发送固件与校验
固件通常以二进制块形式发送,每块后等待应答。这里简化实现,使用XMODEM或YMODEM协议较为复杂,可改用简单的“发送全部数据+CRC校验”:
def _send_firmware(self, ser):
with open(self.firmware, 'rb') as f:
data = f.read()
chunk_size = 256
total = len(data)
for i in range(0, total, chunk_size):
chunk = data[i:i+chunk_size]
ser.write(chunk)
ack = ser.read(1)
if ack != b'\x06': # ACK
raise RuntimeError(f"块 {i//chunk_size} 发送失败")
progress = min(100, int((i+chunk_size)/total*100))
print(f"[{self.port}] 进度: {progress}%")
def _verify(self, ser):
ser.write(b'CHECK')
time.sleep(0.5)
resp = ser.read(4)
expected = bytes([sum(data) % 256]) * 4 # 简化校验
if resp != expected:
raise RuntimeError("校验和不匹配")
主程序:扫描串口并启动线程
def find_serial_ports():
"""返回可用串口列表(Windows/Linux兼容)"""
if sys.platform.startswith('win'):
ports = [f'COM{i}' for i in range(1, 257)]
else:
ports = glob.glob('/dev/ttyUSB*') + glob.glob('/dev/ttyACM*')
result = []
for port in ports:
try:
s = serial.Serial(port)
s.close()
result.append(port)
except:
pass
return result
def main():
firmware_file = 'firmware.bin'
ports = find_serial_ports()
if not ports:
print("未检测到串口设备")
return
print(f"发现 {len(ports)} 个设备: {ports}")
threads = []
for port in ports:
t = FlasherThread(port, firmware_file)
t.start()
threads.append(t)
for t in threads:
t.join()
print(f"{t.port}: {t.result[1]}")
if __name__ == '__main__':
main()
实用增强
超时重试:每个线程可设置最多3次重试,避免单次失败导致整板报废。
日志记录:将每个串口的输出写入独立日志文件,便于事后追溯。
进度条:使用tqdm库显示总体烧录进度。
白名单过滤:通过--ports COM3,COM4参数指定烧录哪些串口,避免误烧其他设备。
注意事项
电源稳定性:同时烧录多块板子时,USB Hub供电可能不足,建议使用带外供电的工业Hub。
串口冲突:确保没有其他程序(如IDE监视器)占用串口。
波特率选择:高速烧录(如921600)可能受线缆质量影响,量产建议用115200以保证稳定。
芯片差异:不同芯片的握手协议差异很大,务必先单板验证再批量。
写在最后
这套工具已在某IoT模组产线投入使用,一台PC同时管理8路串口,烧录128MB固件平均每路耗时45秒,总耗时仅比单路多10%(线程调度开销)。相比人工逐板烧录,效率提升约6倍。Python+PySerial的组合让定制烧录工具的门槛降到了半天之内——你只需替换芯片特定的握手协议,其余框架代码几乎可以复用。对于中小批量生产,这无疑是性价比最高的自动化方案。





