Files
STM8_Mesh_Control/make_factory_flash.py

125 lines
4.1 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""生成写入W25Q80BV的出厂固件合并镜像。
合并镜像布局如下:
0x000000:主程序裸二进制固件
0x002000:出厂固件元数据
脚本会自动读取主程序BIN、计算CRC-16/MODBUS,并把版本号写入输出文件名
和出厂元数据。CH341A打开生成的文件后,从W25Q80BV的0x000000地址开始烧录。
"""
from pathlib import Path
import re
# 当前汇编主程序Makefile生成的裸二进制文件,已经去除了Bootloader地址空间。
# 该文件从应用链接地址0x8500对应的第一个字节开始,不包含Bootloader。
FIRMWARE_PATH = Path("STM8_app_asm/build/stm8_app_asm.bin")
# 主程序版本定义文件,脚本会自动读取其中的FIRMWARE_VERSION宏。
VERSION_HEADER_PATH = Path("include/app_config.h")
# 出厂固件元数据在W25Q80BV中的固定地址。
FACTORY_METADATA_ADDRESS = 0x002000
def read_firmware_version() -> int:
"""从主程序配置头文件读取FIRMWARE_VERSION宏。"""
if not VERSION_HEADER_PATH.is_file():
raise SystemExit(f"找不到版本配置文件:{VERSION_HEADER_PATH}")
content = VERSION_HEADER_PATH.read_text(encoding="utf-8")
match = re.search(
r"^\s*#define\s+FIRMWARE_VERSION\s+((?:0[xX][0-9A-Fa-f]+)|(?:[0-9]+))",
content,
flags=re.MULTILINE,
)
if match is None:
raise SystemExit(
f"在{VERSION_HEADER_PATH}中找不到FIRMWARE_VERSION宏定义"
)
version = int(match.group(1), 0)
if not 0 <= version <= 0xFFFF:
raise SystemExit(f"FIRMWARE_VERSION超出16位范围:{version}")
return version
def calculate_crc16_modbus(data: bytes) -> int:
"""计算一段数据的CRC-16/MODBUS校验值。
Args:
data: 需要计算CRC的完整固件数据。
Returns:
16位CRC结果。发送和存储时由调用方按高字节、低字节写入。
"""
crc = 0xFFFF
for value in data:
crc ^= value
for _ in range(8):
if crc & 1:
crc = (crc >> 1) ^ 0xA001
else:
crc >>= 1
return crc & 0xFFFF
def build_factory_metadata(firmware: bytes, crc16: int, version: int) -> bytes:
"""按照Bootloader固定布局生成18字节出厂元数据。"""
metadata = bytearray(18)
# FACTORY_METADATA_MAGIC=0x46414331。
# 当前Bootloader兼容的魔数字节布局为31 43 41 46。
metadata[0:4] = bytes([0x31, 0x43, 0x41, 0x46])
# 固件长度、版本号和CRC16统一使用高字节在前。
metadata[4:8] = len(firmware).to_bytes(4, "big")
metadata[8:10] = version.to_bytes(2, "big")
metadata[10:12] = crc16.to_bytes(2, "big")
# 0x0C~0x0D为保留字段,保持0x00。
metadata[14] = 0xA5 # 镜像有效标志。
metadata[15] = 0x01 # 请求Bootloader处理出厂镜像。
metadata[16] = 0x00 # 初始升级结果。
metadata[17] = 0x00 # 保留字段。
return bytes(metadata)
def main() -> None:
"""读取固件、生成出厂镜像并输出烧录文件信息。"""
if not FIRMWARE_PATH.is_file():
raise SystemExit(
f"找不到固件文件:{FIRMWARE_PATH}\n"
"请先执行 make -C STM8_app_asm。"
)
firmware = FIRMWARE_PATH.read_bytes()
firmware_version = read_firmware_version()
crc16 = calculate_crc16_modbus(firmware)
metadata = build_factory_metadata(firmware, crc16, firmware_version)
# 输出文件名包含4位十六进制版本号,例如factory_flash_v0100.bin。
output_path = Path(f"factory_flash_v{firmware_version:04X}.bin")
# 文件覆盖到元数据末尾,空白区域填充0xFF,符合NOR Flash擦除状态。
image = bytearray([0xFF] * (FACTORY_METADATA_ADDRESS + len(metadata)))
image[0:len(firmware)] = firmware
image[FACTORY_METADATA_ADDRESS:] = metadata
output_path.write_bytes(image)
print(f"固件长度:{len(firmware)} 字节")
print(f"固件版本:0x{firmware_version:04X}")
print(f"固件CRC16:0x{crc16:04X}")
print(f"输出文件:{output_path}")
print(f"输出大小:{len(image)} 字节")
if __name__ == "__main__":
main()