#!/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()