3、脚本化烧录:使用Python脚本实现一键烧录与校验
说实话,很多工程师做了好几年嵌入式,烧录固件还是老一套——打开IDE,点下载,等进度条跑完,然后手动重启看灯闪没闪。一次两次还行,一天烧几十次板子,手都点酸了。
我个人习惯是:凡是重复超过三次的操作,必须用脚本干掉。烧录这件事,太适合自动化了。今天我们就聊聊怎么用Python写一个靠谱的一键烧录脚本,顺便把校验也带上。
核心目标:一条命令完成“编译→烧录→校验→日志归档”全流程,中间不需要人盯着。
3.1 烧录脚本的基本架构
先搭个架子。一个成熟的烧录脚本,至少需要四个模块:
| 模块 | 职责 | 我常用的工具/库 |
|---|---|---|
| 参数解析 | 接收目标芯片、固件路径、烧录器类型等 | argparse |
| 烧录引擎 | 调用底层烧录工具(OpenOCD、JLink、ST-Link CLI) | subprocess |
| 校验模块 | 读回Flash内容,与原始固件比对 | hashlib + 读寄存器 |
| 日志与状态 | 记录每次烧录结果,方便追溯 | logging + JSON |
你想想看,这四个模块拆开写,每个都不复杂。但合在一起,就是一个能跑在生产环境里的自动化工具。
3.2 核心实现:调用烧录工具
底层烧录工具通常都提供命令行接口。以STM32 + OpenOCD为例,烧录一条命令就够了:
openocd -f interface/stlink.cfg -f target/stm32f4x.cfg \
-c "program firmware.bin 0x08000000 verify reset exit"
Python里用subprocess.run()包一下:
import subprocess
import sys
def flash_openocd(firmware_path, chip_config):
cmd = [
"openocd",
"-f", f"interface/{chip_config['interface']}.cfg",
"-f", f"target/{chip_config['target']}.cfg",
"-c", f"program {firmware_path} {chip_config['base_addr']} verify reset exit"
]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
if result.returncode != 0:
raise RuntimeError(f"烧录失败: {result.stderr}")
return result.stdout
小技巧:timeout=60 一定要加。我曾经遇到过烧录器接触不良,脚本卡在那里半小时没反应。设个超时,至少能快速失败。
3.3 校验:不只是读回来比一下
很多人觉得校验就是“读Flash vs 原始bin”,其实没那么简单。我遇到过三种情况:
- 全片校验:读回全部Flash内容,逐字节比对。最可靠,但慢。
- CRC校验:芯片硬件支持CRC计算,读回CRC值比对。快,但依赖芯片。
- 关键段校验:只校验向量表、启动代码等关键区域。折中方案。
我个人习惯用全片校验 + 快速CRC双重确认。脚本里这样写:
import hashlib
def verify_flash(firmware_path, readback_path):
with open(firmware_path, "rb") as f:
original = f.read()
with open(readback_path, "rb") as f:
readback = f.read()
if len(original) != len(readback):
return False
orig_hash = hashlib.sha256(original).hexdigest()
read_hash = hashlib.sha256(readback).hexdigest()
return orig_hash == read_hash
注意:读回Flash时,要确保读回的地址范围和长度与固件完全一致。偏移一个字节,校验必失败。我曾经踩过这个坑,排查了半天才发现是读回时地址写错了。
3.4 一键烧录的完整流程
把上面这些拼起来,就是一条命令搞定的事。我画了个流程图,你看看:
嗯,这里要注意:校验失败不要直接放弃。我一般让脚本自动重试一次,因为有时候是烧录器接触问题,重插一下就好了。如果重试还失败,再报错。
3.5 实战:一个完整的烧录脚本骨架
下面这个脚本,我项目里一直在用。你改改芯片参数就能跑:
#!/usr/bin/env python3
"""一键烧录脚本 - 支持校验与重试"""
import argparse
import subprocess
import hashlib
import logging
import json
from pathlib import Path
logging.basicConfig(level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s")
log = logging.getLogger("flasher")
def parse_args():
parser = argparse.ArgumentParser(description="一键烧录工具")
parser.add_argument("firmware", type=Path, help="固件路径 (.bin)")
parser.add_argument("--chip", default="stm32f4", help="芯片型号")
parser.add_argument("--retry", type=int, default=1, help="失败重试次数")
return parser.parse_args()
def flash(fw_path, chip):
# 这里调用实际烧录命令
log.info(f"烧录 {fw_path} 到 {chip}...")
# 示例:subprocess.run(["openocd", ...], check=True)
return True
def verify(fw_path, readback_path):
if not readback_path.exists():
return False
fw_hash = hashlib.sha256(fw_path.read_bytes()).hexdigest()
rb_hash = hashlib.sha256(readback_path.read_bytes()).hexdigest()
return fw_hash == rb_hash
def main():
args = parse_args()
if not args.firmware.exists():
log.error("固件文件不存在")
return 1
for attempt in range(args.retry + 1):
try:
flash(args.firmware, args.chip)
# 假设读回文件为 readback.bin
if verify(args.firmware, Path("readback.bin")):
log.info("✅ 烧录成功,校验通过")
return 0
else:
log.warning("⚠️ 校验失败")
except Exception as e:
log.error(f"烧录异常: {e}")
if attempt < args.retry:
log.info(f"重试第 {attempt+1} 次...")
log.error("❌ 烧录失败,请检查硬件连接")
return 1
if __name__ == "__main__":
exit(main())
避坑指南:我曾经在生产线上遇到过一个问题——固件路径里有中文,OpenOCD直接报错。后来我统一用Path.resolve()转成绝对路径,再也不用担心编码问题。
3.6 多芯片支持与配置管理
项目多了,不可能每个芯片写一个脚本。我习惯用JSON配置文件管理:
{
"stm32f4": {
"interface": "stlink",
"target": "stm32f4x",
"base_addr": "0x08000000",
"flash_size": "1M",
"verify_method": "sha256"
},
"nrf52840": {
"interface": "jlink",
"target": "nrf52840",
"base_addr": "0x00000000",
"flash_size": "1M",
"verify_method": "crc"
},
"esp32": {
"interface": "esptool",
"target": "esp32",
"base_addr": "0x10000",
"flash_size": "4M",
"verify_method": "sha256"
}
}
脚本启动时读这个配置,--chip参数一传,自动匹配烧录参数。说白了,这就是把硬编码变成了可配置,维护成本直线下降。
3.7 日志与追溯
每次烧录的结果,我建议都记下来。格式很简单:
{
"timestamp": "2025-03-15T14:32:10",
"firmware": "app_v2.1.bin",
"chip": "stm32f4",
"status": "success",
"hash": "a1b2c3d4...",
"duration_sec": 12.5
}
为什么要记?因为出问题的时候,你能回溯——是哪次烧录、哪个版本、哪个芯片。我在客户现场排查问题时,靠这个日志半小时就定位到了问题,而不是靠猜。
总结一下:脚本化烧录的核心就三件事——调工具、做校验、记日志。把这三件事用Python串起来,你就拥有了一条可靠的一键烧录流水线。下次烧录的时候,喝杯咖啡,让脚本干活。