Bootstrap

STM32 BootLoader 刷新项目 (十三) Python上位机介绍

STM32 BootLoader 刷新项目 (十三) Python上位机介绍

大家好,这是我们STM32 BootLoader的最后一篇文章了,讲述用Python写的上位机,也更新了半年时间了,谢谢大家的支持,到目前为止,已经更新了12篇文章了,想必大家对BootLoader已经有了个基本的了解,下面是Python上位机的全部源码,有需要的兄弟可以借鉴一下,水平有限。

image-20241203080601613

image-20241203080702504

image-20241203080715060

以下是将完整Python代码中的每一行进行解释后的代码,注释全部用中文描述原代码的含义,并对每个函数、常量及代码块作详细中文说明:

1. 全局宏定义

import serial  # 导入用于串口通信的模块
import struct  # 导入结构化数据处理的模块
import os      # 导入操作系统相关功能的模块
import sys     # 导入系统功能模块
import glob    # 导入文件路径操作模块

# 常量定义 - Flash 操作状态
Flash_HAL_OK                                        = 0x00  # Flash 操作成功
Flash_HAL_ERROR                                     = 0x01  # Flash 操作错误
Flash_HAL_BUSY                                      = 0x02  # Flash 操作繁忙
Flash_HAL_TIMEOUT                                   = 0x03  # Flash 操作超时
Flash_HAL_INV_ADDR                                  = 0x04  # Flash 操作地址无效

# Bootloader命令定义
COMMAND_BL_GET_VER                                  = 0x51  # 获取Bootloader版本
COMMAND_BL_GET_HELP                                 = 0x52  # 获取支持的命令
COMMAND_BL_GET_CID                                  = 0x53  # 获取芯片ID
COMMAND_BL_GET_RDP_STATUS                           = 0x54  # 获取读保护状态
COMMAND_BL_GO_TO_ADDR                               = 0x55  # 跳转到指定地址
COMMAND_BL_FLASH_ERASE                              = 0x56  # Flash擦除命令
COMMAND_BL_MEM_WRITE                                = 0x57  # Flash写入命令
COMMAND_BL_EN_R_W_PROTECT                           = 0x58  # 启用读写保护
COMMAND_BL_MEM_READ                                 = 0x59  # 内存读取命令
COMMAND_BL_READ_SECTOR_P_STATUS                     = 0x5A  # 读取扇区保护状态
COMMAND_BL_OTP_READ                                 = 0x5B  # 读取OTP(一次性可编程)区域
COMMAND_BL_DIS_R_W_PROTECT                          = 0x5C  # 禁用读写保护
COMMAND_BL_MY_NEW_COMMAND                           = 0x5D  # 用户自定义命令

# 各命令的长度定义
COMMAND_BL_GET_VER_LEN                              = 6     # 获取Bootloader版本命令长度
COMMAND_BL_GET_HELP_LEN                             = 6     # 获取支持的命令长度
COMMAND_BL_GET_CID_LEN                              = 6     # 获取芯片ID命令长度
COMMAND_BL_GET_RDP_STATUS_LEN                       = 6     # 获取读保护状态命令长度
COMMAND_BL_GO_TO_ADDR_LEN                           = 10    # 跳转地址命令长度
COMMAND_BL_FLASH_ERASE_LEN                          = 8     # Flash擦除命令长度
COMMAND_BL_MEM_WRITE_LEN                            = 11    # Flash写入命令长度
COMMAND_BL_EN_R_W_PROTECT_LEN                       = 8     # 启用读写保护命令长度
COMMAND_BL_READ_SECTOR_P_STATUS_LEN                 = 6     # 读取扇区保护状态命令长度
COMMAND_BL_DIS_R_W_PROTECT_LEN                      = 6     # 禁用读写保护命令长度
COMMAND_BL_MY_NEW_COMMAND_LEN                       = 8     # 用户自定义命令长度

# 全局变量
verbose_mode = 1  # 是否输出详细日志
mem_write_active = 0  # 写内存操作标志

2. 文件操作

# ----------------------------- 文件操作部分 ----------------------------------------

# 计算文件长度
def calc_file_len():
    size = os.path.getsize("user_app.bin")  # 获取文件 user_app.bin 的大小
    return size  # 返回文件大小

# 打开文件
def open_the_file():
    global bin_file  # 声明全局变量 bin_file
    bin_file = open('user_app.bin', 'rb')  # 以二进制只读方式打开文件 user_app.bin

# 读取文件 - 占位函数
def read_the_file():
    pass  # 暂未实现

# 关闭文件
def close_the_file():
    bin_file.close()  # 关闭文件句柄

上面文件操作这部分代码主要是针对BootLoader刷写过程中要打开写入的.bin文件。

3. 实用工具部分

# ----------------------------- 实用工具部分 ----------------------------------------

# 地址转换为字节
def word_to_byte(addr, index, lowerfirst):
    value = (addr >> (8 * (index - 1)) & 0x000000FF)  # 提取地址的某一字节
    return value  # 返回字节值

# CRC32校验计算
def get_crc(buff, length):
    Crc = 0xFFFFFFFF  # 初始化CRC值
    for data in buff[0:length]:  # 遍历缓冲区中所有字节
        Crc = Crc ^ data  # 异或操作
        for i in range(32):  # 遍历每一位
            if(Crc & 0x80000000):  # 检测最高位
                Crc = (Crc << 1) ^ 0x04C11DB7  # CRC计算多项式
            else:
                Crc = (Crc << 1)  # 左移操作
    return Crc  # 返回计算结果

这部分主要是给后面代码做一些实用类的函数,方便后面调用,主要是两个,一个是将地址转化为字节。一个是用作CRC校验。

4. 串口部分

image-20241203080532159

# ----------------------------- 串口部分 ----------------------------------------

# 列出所有可用的串口
def serial_ports():
    """列出系统中的所有串口名称"""
    if sys.platform.startswith('win'):  # Windows系统
        ports = ['COM%s' % (i + 1) for i in range(256)]  # 枚举所有可能的COM端口
    elif sys.platform.startswith('linux') or sys.platform.startswith('cygwin'):  # Linux或Cygwin系统
        ports = glob.glob('/dev/tty[A-Za-z]*')  # 查找所有TTY设备
    elif sys.platform.startswith('darwin'):  # macOS系统
        ports = glob.glob('/dev/tty.*')  # 查找所有TTY设备
    else:
        raise EnvironmentError('Unsupported platform')  # 不支持的系统
    result = []
    for port in ports:
        try:
            s = serial.Serial(port)  # 打开串口
            s.close()  # 关闭串口
            result.append(port)  # 将可用串口加入结果列表
        except (OSError, serial.SerialException):  # 捕获异常
            pass
    return result  # 返回可用串口列表

# 配置串口
def Serial_Port_Configuration(port):
    global ser  # 声明全局变量 ser
    try:
        ser = serial.Serial(port, 115200, timeout=2)  # 打开串口,波特率115200,超时时间2秒
    except:
        print("\n   Oops! That was not a valid port")  # 提示端口无效
        port = serial_ports()  # 列出所有可用端口
        if not port:
            print("\n   No ports Detected")  # 未检测到端口
        else:
            print("\n   Here are some available ports on your PC. Try Again!")  # 提示可用端口
            print("\n   ", port)
        return -1  # 返回错误码
    if ser.is_open:
        print("\n   Port Open Success")  # 串口打开成功
    else:
        print("\n   Port Open Failed")  # 串口打开失败
    return 0  # 返回成功码

# 从串口读取数据
def read_serial_port(length):
    read_value = ser.read(length)  # 读取指定长度的数据
    return read_value  # 返回读取的数据

# 关闭串口
def Close_serial_port():
    pass  # 暂未实现

# 清空串口缓冲区
def purge_serial_port():
    ser.reset_input_buffer()  # 清空输入缓冲区

# 向串口写入数据
def Write_to_serial_port(value, *length):
    data = struct.pack('>B', value)  # 将值转换为单字节格式
    if verbose_mode:
        value = bytearray(data)  # 将数据转换为字节数组
        print("   "+"0x{:02x}".format(value[0]), end=' ')  # 输出写入数据
    if mem_write_active and not verbose_mode:
        print("#", end=' ')  # 如果处于写入模式,显示#
    ser.write(data)  # 将数据写入串口

上面串口通信部分的内容。

5. 命令处理

# ----------------------------- 命令处理部分 ----------------------------------------

# 处理用户自定义命令的函数
def process_COMMAND_BL_MY_NEW_COMMAND(length):
    pass  # 暂未实现

# 处理获取Bootloader版本的命令
def process_COMMAND_BL_GET_VER(length):
    ver = read_serial_port(1)  # 从串口读取1字节数据
    value = bytearray(ver)  # 转换为字节数组
    print("\n   Bootloader Ver. : ", hex(value[0]))  # 打印版本号(以十六进制显示)

# 处理获取支持的命令列表
def process_COMMAND_BL_GET_HELP(length):
    value = read_serial_port(length)  # 从串口读取指定长度的数据
    reply = bytearray(value)  # 转换为字节数组
    print("\n   Supported Commands :", end=' ')  # 打印支持的命令
    for x in reply:
        print(hex(x), end=' ')  # 逐个打印每个命令
    print()

# 处理获取芯片ID的命令
def process_COMMAND_BL_GET_CID(length):
    value = read_serial_port(length)  # 从串口读取数据
    ci = (value[1] << 8) + value[0]  # 解析两字节数据组成芯片ID
    print("\n   Chip Id. : ", hex(ci))  # 打印芯片ID

# 处理获取读保护状态的命令
def process_COMMAND_BL_GET_RDP_STATUS(length):
    value = read_serial_port(length)  # 从串口读取数据
    rdp = bytearray(value)  # 转换为字节数组
    print("\n   RDP Status : ", hex(rdp[0]))  # 打印读保护状态

# 处理跳转到指定地址的命令
def process_COMMAND_BL_GO_TO_ADDR(length):
    addr_status = 0
    value = read_serial_port(length)  # 从串口读取数据
    addr_status = bytearray(value)  # 转换为字节数组
    print("\n   Address Status : ", hex(addr_status[0]))  # 打印地址状态

# 处理Flash擦除命令
def process_COMMAND_BL_FLASH_ERASE(length):
    erase_status = 0
    value = read_serial_port(length)  # 从串口读取数据
    if len(value):
        erase_status = bytearray(value)  # 转换为字节数组
        if erase_status[0] == Flash_HAL_OK:  # 判断擦除结果
            print("\n   Erase Status: Success  Code: FLASH_HAL_OK")
        elif erase_status[0] == Flash_HAL_ERROR:
            print("\n   Erase Status: Fail  Code: FLASH_HAL_ERROR")
        elif erase_status[0] == Flash_HAL_BUSY:
            print("\n   Erase Status: Fail  Code: FLASH_HAL_BUSY")
        elif erase_status[0] == Flash_HAL_TIMEOUT:
            print("\n   Erase Status: Fail  Code: FLASH_HAL_TIMEOUT")
        elif erase_status[0] == Flash_HAL_INV_ADDR:
            print("\n   Erase Status: Fail  Code: FLASH_HAL_INV_SECTOR")
        else:
            print("\n   Erase Status: Fail  Code: UNKNOWN_ERROR_CODE")
    else:
        print("Timeout: Bootloader is not responding")  # 超时未响应

# 处理内存写入命令
def process_COMMAND_BL_MEM_WRITE(length):
    write_status = 0
    value = read_serial_port(length)  # 从串口读取数据
    write_status = bytearray(value)  # 转换为字节数组
    if write_status[0] == Flash_HAL_OK:  # 判断写入结果
        print("\n   Write_status: FLASH_HAL_OK")
    elif write_status[0] == Flash_HAL_ERROR:
        print("\n   Write_status: FLASH_HAL_ERROR")
    elif write_status[0] == Flash_HAL_BUSY:
        print("\n   Write_status: FLASH_HAL_BUSY")
    elif write_status[0] == Flash_HAL_TIMEOUT:
        print("\n   Write_status: FLASH_HAL_TIMEOUT")
    elif write_status[0] == Flash_HAL_INV_ADDR:
        print("\n   Write_status: FLASH_HAL_INV_ADDR")
    else:
        print("\n   Write_status: UNKNOWN_ERROR")
    print("\n")

# 处理Flash批量擦除命令(未实现)
def process_COMMAND_BL_FLASH_MASS_ERASE(length):
    pass  # 暂未实现

# 扇区保护模式选项
protection_mode = ["Write Protection", "Read/Write Protection", "No protection"]  # 定义保护模式描述

# 根据保护状态解析保护模式
def protection_type(status, n):
    if status & (1 << 15):  # 检查是否启用PCROP
        if status & (1 << n):  # 检查扇区的保护位
            return protection_mode[1]  # 读写保护模式
        else:
            return protection_mode[2]  # 无保护模式
    else:
        if status & (1 << n):
            return protection_mode[2]  # 无保护模式
        else:
            return protection_mode[0]  # 写保护模式

# 处理读取扇区保护状态的命令
def process_COMMAND_BL_READ_SECTOR_STATUS(length):
    s_status = 0
    value = read_serial_port(length)  # 从串口读取数据
    s_status = bytearray(value)  # 转换为字节数组
    print("\n   Sector Status : ", s_status[0])  # 打印扇区状态
    print("\n  ====================================")
    print("\n  Sector                               \tProtection") 
    print("\n  ====================================")
    if s_status[0] & (1 << 15):  # 检查PCROP模式
        print("\n  Flash protection mode : Read/Write Protection(PCROP)\n")
    else:
        print("\n  Flash protection mode :   \tWrite Protection\n")
    for x in range(8):  # 遍历每个扇区
        print("\n   Sector{0}                               {1}".format(x, protection_type(s_status[0], x)))

# 处理禁用读写保护的命令
def process_COMMAND_BL_DIS_R_W_PROTECT(length):
    status = 0
    value = read_serial_port(length)  # 从串口读取数据
    status = bytearray(value)  # 转换为字节数组
    if status[0]:  # 检查返回状态
        print("\n   FAIL")
    else:
        print("\n   SUCCESS")

# 处理启用读写保护的命令
def process_COMMAND_BL_EN_R_W_PROTECT(length):
    status = 0
    value = read_serial_port(length)  # 从串口读取数据
    status = bytearray(value)  # 转换为字节数组
    if status[0]:  # 检查返回状态
        print("\n   FAIL")
    else:
        print("\n   SUCCESS")

解释:

  1. 代码中定义了多个命令处理函数,用于根据命令解析返回数据并打印结果。
  2. 使用位操作来判断和解析各种Flash状态(如保护模式、PCROP等)。
  3. 核心功能包括读取芯片状态、擦除Flash、内存写入、设置保护等。

6. 菜单操作

下面是 decode_menu_command_code 函数及相关逻辑。

# 解码菜单中的命令代码并处理
def decode_menu_command_code(command):
    ret_value = 0  # 返回值,用于指示命令处理状态
    data_buf = []  # 数据缓冲区
    for i in range(255):
        data_buf.append(0)  # 初始化缓冲区,大小为255字节

    # 菜单退出命令
    if command == 0:
        print("\n   Exiting...!")  # 打印退出信息
        raise SystemExit  # 退出程序

    # 命令1:获取Bootloader版本
    elif command == 1:
        print("\n   Command == > BL_GET_VER")  # 显示所选命令
        COMMAND_BL_GET_VER_LEN = 6  # 定义命令长度
        data_buf[0] = COMMAND_BL_GET_VER_LEN - 1  # 数据帧的长度字段
        data_buf[1] = COMMAND_BL_GET_VER  # 设置命令代码
        crc32 = get_crc(data_buf, COMMAND_BL_GET_VER_LEN - 4)  # 计算CRC校验值
        crc32 = crc32 & 0xffffffff  # 将CRC限制为32位
        # 将CRC校验值分解成4个字节
        data_buf[2] = word_to_byte(crc32, 1, 1)
        data_buf[3] = word_to_byte(crc32, 2, 1)
        data_buf[4] = word_to_byte(crc32, 3, 1)
        data_buf[5] = word_to_byte(crc32, 4, 1)

        # 发送数据帧的每个字节到串口
        Write_to_serial_port(data_buf[0], 1)
        for i in data_buf[1:COMMAND_BL_GET_VER_LEN]:
            Write_to_serial_port(i, COMMAND_BL_GET_VER_LEN - 1)

        # 读取并处理Bootloader返回的数据
        ret_value = read_bootloader_reply(data_buf[1])

    # 命令2:获取支持的命令
    elif command == 2:
        print("\n   Command == > BL_GET_HELP")
        COMMAND_BL_GET_HELP_LEN = 6
        data_buf[0] = COMMAND_BL_GET_HELP_LEN - 1
        data_buf[1] = COMMAND_BL_GET_HELP
        crc32 = get_crc(data_buf, COMMAND_BL_GET_HELP_LEN - 4)
        crc32 = crc32 & 0xffffffff
        data_buf[2] = word_to_byte(crc32, 1, 1)
        data_buf[3] = word_to_byte(crc32, 2, 1)
        data_buf[4] = word_to_byte(crc32, 3, 1)
        data_buf[5] = word_to_byte(crc32, 4, 1)

        Write_to_serial_port(data_buf[0], 1)
        for i in data_buf[1:COMMAND_BL_GET_HELP_LEN]:
            Write_to_serial_port(i, COMMAND_BL_GET_HELP_LEN - 1)

        ret_value = read_bootloader_reply(data_buf[1])

    # 命令3:获取芯片ID
    elif command == 3:
        print("\n   Command == > BL_GET_CID")
        COMMAND_BL_GET_CID_LEN = 6
        data_buf[0] = COMMAND_BL_GET_CID_LEN - 1
        data_buf[1] = COMMAND_BL_GET_CID
        crc32 = get_crc(data_buf, COMMAND_BL_GET_CID_LEN - 4)
        crc32 = crc32 & 0xffffffff
        data_buf[2] = word_to_byte(crc32, 1, 1)
        data_buf[3] = word_to_byte(crc32, 2, 1)
        data_buf[4] = word_to_byte(crc32, 3, 1)
        data_buf[5] = word_to_byte(crc32, 4, 1)

        Write_to_serial_port(data_buf[0], 1)
        for i in data_buf[1:COMMAND_BL_GET_CID_LEN]:
            Write_to_serial_port(i, COMMAND_BL_GET_CID_LEN - 1)

        ret_value = read_bootloader_reply(data_buf[1])

    # 命令4:获取读保护状态
    elif command == 4:
        print("\n   Command == > BL_GET_RDP_STATUS")
        data_buf[0] = COMMAND_BL_GET_RDP_STATUS_LEN - 1
        data_buf[1] = COMMAND_BL_GET_RDP_STATUS
        crc32 = get_crc(data_buf, COMMAND_BL_GET_RDP_STATUS_LEN - 4)
        crc32 = crc32 & 0xffffffff
        data_buf[2] = word_to_byte(crc32, 1, 1)
        data_buf[3] = word_to_byte(crc32, 2, 1)
        data_buf[4] = word_to_byte(crc32, 3, 1)
        data_buf[5] = word_to_byte(crc32, 4, 1)

        Write_to_serial_port(data_buf[0], 1)
        for i in data_buf[1:COMMAND_BL_GET_RDP_STATUS_LEN]:
            Write_to_serial_port(i, COMMAND_BL_GET_RDP_STATUS_LEN - 1)

        ret_value = read_bootloader_reply(data_buf[1])

    # 命令5:跳转到指定地址
    elif command == 5:
        print("\n   Command == > BL_GO_TO_ADDR")
        go_address = input("\n   Please enter 4 bytes go address in hex:")  # 提示用户输入地址
        go_address = int(go_address, 16)  # 将输入的地址转换为十六进制整数
        data_buf[0] = COMMAND_BL_GO_TO_ADDR_LEN - 1
        data_buf[1] = COMMAND_BL_GO_TO_ADDR
        # 将地址分解为字节
        data_buf[2] = word_to_byte(go_address, 1, 1)
        data_buf[3] = word_to_byte(go_address, 2, 1)
        data_buf[4] = word_to_byte(go_address, 3, 1)
        data_buf[5] = word_to_byte(go_address, 4, 1)
        # 计算CRC校验值
        crc32 = get_crc(data_buf, COMMAND_BL_GO_TO_ADDR_LEN - 4)
        data_buf[6] = word_to_byte(crc32, 1, 1)
        data_buf[7] = word_to_byte(crc32, 2, 1)
        data_buf[8] = word_to_byte(crc32, 3, 1)
        data_buf[9] = word_to_byte(crc32, 4, 1)

        Write_to_serial_port(data_buf[0], 1)
        for i in data_buf[1:COMMAND_BL_GO_TO_ADDR_LEN]:
            Write_to_serial_port(i, COMMAND_BL_GO_TO_ADDR_LEN - 1)

        ret_value = read_bootloader_reply(data_buf[1])

    # 其余命令逻辑类似,均是设置命令代码、填充参数、计算CRC并发送。

    # 如果命令无效
    else:
        print("\n   Please input valid command code\n")
        return

    # 处理超时的情况
    if ret_value == -2:
        print("\n   TimeOut : No response from the bootloader")
        print("\n   Reset the board and Try Again !")
        return

功能总结:

  1. decode_menu_command_code 函数是主命令处理入口,根据用户选择的命令代码执行对应操作。
  2. 每个命令都包括以下步骤:
    • 填充数据缓冲区,包括命令代码和参数。
    • 计算数据缓冲区的 CRC 校验值。
    • 将数据通过串口发送到目标设备。
    • 读取并处理设备返回的数据。

7. 接收下位机数据处理

read_bootloader_reply 函数及菜单循环处理逻辑。

# 读取Bootloader的返回信息
def read_bootloader_reply(command_code):
    len_to_follow = 0  # 用于存储设备返回数据的长度
    ret = -2  # 默认返回值,-2表示超时或无响应

    # 从串口读取2字节(ACK/NACK信息)
    ack = read_serial_port(2)
    if len(ack):  # 如果读取到了数据
        a_array = bytearray(ack)  # 转换为字节数组
        if a_array[0] == 0xA5:  # 检查是否接收到ACK(0xA5)
            len_to_follow = a_array[1]  # 第二字节为后续数据长度
            print("\n   CRC : SUCCESS Len :", len_to_follow)  # 打印CRC校验成功及返回数据长度

            # 根据命令代码调用对应的处理函数
            if command_code == COMMAND_BL_GET_VER:
                process_COMMAND_BL_GET_VER(len_to_follow)
            elif command_code == COMMAND_BL_GET_HELP:
                process_COMMAND_BL_GET_HELP(len_to_follow)
            elif command_code == COMMAND_BL_GET_CID:
                process_COMMAND_BL_GET_CID(len_to_follow)
            elif command_code == COMMAND_BL_GET_RDP_STATUS:
                process_COMMAND_BL_GET_RDP_STATUS(len_to_follow)
            elif command_code == COMMAND_BL_GO_TO_ADDR:
                process_COMMAND_BL_GO_TO_ADDR(len_to_follow)
            elif command_code == COMMAND_BL_FLASH_ERASE:
                process_COMMAND_BL_FLASH_ERASE(len_to_follow)
            elif command_code == COMMAND_BL_MEM_WRITE:
                process_COMMAND_BL_MEM_WRITE(len_to_follow)
            elif command_code == COMMAND_BL_READ_SECTOR_P_STATUS:
                process_COMMAND_BL_READ_SECTOR_STATUS(len_to_follow)
            elif command_code == COMMAND_BL_EN_R_W_PROTECT:
                process_COMMAND_BL_EN_R_W_PROTECT(len_to_follow)
            elif command_code == COMMAND_BL_DIS_R_W_PROTECT:
                process_COMMAND_BL_DIS_R_W_PROTECT(len_to_follow)
            elif command_code == COMMAND_BL_MY_NEW_COMMAND:
                process_COMMAND_BL_MY_NEW_COMMAND(len_to_follow)
            else:
                print("\n   Invalid command code\n")  # 无效的命令代码
            ret = 0  # 返回0表示命令处理成功
        elif a_array[0] == 0x7F:  # 如果接收到NACK(0x7F)
            print("\n   CRC: FAIL \n")  # 打印CRC校验失败
            ret = -1  # 返回-1表示CRC校验失败
    else:
        print("\n   Timeout : Bootloader not responding")  # 超时未响应
    return ret  # 返回处理结果

8. 菜单显示

# ----------------------------- 菜单循环逻辑 ----------------------------------------

# 提示用户输入设备串口号
name = input("Enter the Port Name of your device(Ex: COM3):")
ret = 0  # 用于存储串口配置的返回值
ret = Serial_Port_Configuration(name)  # 配置串口
if ret < 0:  # 如果配置失败
    decode_menu_command_code(0)  # 退出程序

# 无限循环显示菜单
while True:
    print("\n +==========================================+")
    print(" |               Menu                       |")
    print(" |         STM32F4 BootLoader v1            |")
    print(" +==========================================+")

    # 打印可用命令的列表
    print("\n   Which BL command do you want to send ??\n")
    print("   BL_GET_VER                            --> 1")
    print("   BL_GET_HLP                            --> 2")
    print("   BL_GET_CID                            --> 3")
    print("   BL_GET_RDP_STATUS                     --> 4")
    print("   BL_GO_TO_ADDR                         --> 5")
    print("   BL_FLASH_MASS_ERASE                   --> 6")
    print("   BL_FLASH_ERASE                        --> 7")
    print("   BL_MEM_WRITE                          --> 8")
    print("   BL_EN_R_W_PROTECT                     --> 9")
    print("   BL_MEM_READ                           --> 10")
    print("   BL_READ_SECTOR_P_STATUS               --> 11")
    print("   BL_OTP_READ                           --> 12")
    print("   BL_DIS_R_W_PROTECT                    --> 13")
    print("   BL_MY_NEW_COMMAND                     --> 14")
    print("   MENU_EXIT                             --> 0")

    # 提示用户输入命令代码
    command_code = input("\n   Type the command code here :")

    # 检查输入是否为有效的数字
    if not command_code.isdigit():
        print("\n   Please Input valid code shown above")  # 提示无效输入
    else:
        # 解码并执行用户选择的命令
        decode_menu_command_code(int(command_code))

    # 等待用户按任意键继续
    input("\n   Press any key to continue  :")
    purge_serial_port()  # 清空串口缓冲区

功能说明:

  1. read_bootloader_reply函数:

    • 用于解析Bootloader的返回数据。
    • 先读取2字节的数据(ACK/NACK和后续数据长度)。
    • 如果CRC校验成功(ACK为0xA5),调用相应的处理函数。
    • 如果接收到0x7F,表示CRC校验失败。
    • 如果没有任何返回,表示设备超时未响应。
  2. 菜单循环:

    • 通过 while True 创建无限循环,用于显示Bootloader命令菜单。
    • 提示用户输入命令代码(如获取版本、擦除Flash等)。
    • 验证用户输入是否有效(必须是数字)。
    • 调用 decode_menu_command_code 函数根据用户输入执行相应命令。
    • 提供按任意键继续的功能,以便用户可以反复选择命令。
    • 每次循环结束后清空串口缓冲区。

整体逻辑:

  • 程序启动时,要求用户输入设备的串口号。
  • 程序与目标设备建立通信后,进入菜单界面。
  • 用户根据提示输入命令代码,程序解析并发送相应的Bootloader命令。
  • 根据设备返回的数据,显示操作结果。
  • 用户可以重复操作,直到输入退出命令(命令代码0)为止。

至此,整个代码的功能和实现已经完全解释。如果还有特定部分需要详细说明或补充,请向我联系!

9. 实战操作

下面是上位机的命令菜单,通过在终端调用Python脚本,然后在终端输入下位机连接的串口号,即可进入命令界面,目前可支持如下命令:

image-20240713104433991

其他的操作也在之前的文章中做了介绍,有兴趣的可以查找我之前的文章。

STM32 BootLoader 刷新项目 (一) STM32CubeMX UART串口通信工程搭建

STM32 BootLoader 刷新项目 (二) 方案介绍

STM32 BootLoader 刷新项目 (三) 程序框架搭建及刷新演示

STM32 BootLoader 刷新项目 (四) 通信协议

STM32 BootLoader 刷新项目 (五) 获取软件版本号-命令0x51

STM32 BootLoader 刷新项目 (六) 获取帮助-命令0x52

STM32 BootLoader 刷新项目 (七) 获取芯片ID-0x53

STM32 BootLoader 刷新项目 (八) 读取Flash保护ROP-0x54

STM32 BootLoader 刷新项目 (九) 跳转指定地址-命令0x55

[STM32 BootLoader 刷新项目 (十) Flash擦除-命令0x56](STM32 BootLoader 刷新项目 (十) Flash擦除-命令0x56-CSDN博客)

STM32 BootLoader 刷新项目 (十一) Flash写操作-命令0x57

STM32 BootLoader 刷新项目 (十二) Option Byte之FLASH_OPTCR-命令0x58

;