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_filebin_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):  # 捕获异常passreturn result  # 返回可用串口列表# 配置串口
def Serial_Port_Configuration(port):global ser  # 声明全局变量 sertry: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]  # 解析两字节数据组成芯片IDprint("\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 = 0value = 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 = 0value = 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 = 0value = 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):  # 检查是否启用PCROPif 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 = 0value = 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 = 0value = 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 = 0value = 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 = 6data_buf[0] = COMMAND_BL_GET_HELP_LEN - 1data_buf[1] = COMMAND_BL_GET_HELPcrc32 = get_crc(data_buf, COMMAND_BL_GET_HELP_LEN - 4)crc32 = crc32 & 0xffffffffdata_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:获取芯片IDelif command == 3:print("\n   Command == > BL_GET_CID")COMMAND_BL_GET_CID_LEN = 6data_buf[0] = COMMAND_BL_GET_CID_LEN - 1data_buf[1] = COMMAND_BL_GET_CIDcrc32 = get_crc(data_buf, COMMAND_BL_GET_CID_LEN - 4)crc32 = crc32 & 0xffffffffdata_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 - 1data_buf[1] = COMMAND_BL_GET_RDP_STATUScrc32 = get_crc(data_buf, COMMAND_BL_GET_RDP_STATUS_LEN - 4)crc32 = crc32 & 0xffffffffdata_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 - 1data_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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/61952.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ArUco识别定位原理

1. ArUco是什么 ArUco marker是一种汉明码方格图。它由一个宽的黑边和一个内部的二进制矩阵组成&#xff0c;黑色的边界有利于快速检测到图像&#xff0c;Marker ID是他的二进制矩阵编码&#xff0c;Marker size是图片的大小。黑色方块对应0&#xff0c;白色方块对应1&#xf…

每天五分钟机器学习:平行和重合

本文重点 在前面的课程中,我们学习了超平面分离定理,这里面有一个超平面的概念,那么本文学习下,什么情况下超平面是重合的,什么情况下超平面是平行的,这对后面我们学习支持向量机特别重要。 超平面的定义 超平面是指在n维空间中,余维度为1的子空间,即超平面是n维空间…

【学习总结|DAY011】Java数组、二维数组

一、数组概述 在Java编程中&#xff0c;数组是一种用于存储固定大小同类型元素的集合。它提供了随机访问元素的能力&#xff0c;使得处理大量数据变得更加高效。 二、一维数组 1. 定义与初始化 一维数组是最简单的数组形式&#xff0c;其定义方式如下&#xff1a; dataTyp…

Unity 基于Collider 组件在3D 物体表面放置3D 物体

实现 从鼠标点击的屏幕位置发送射线&#xff0c;以射线监测点击到的物体&#xff0c;根据点击物体的法线向量调整放置物体的位置及朝向。 Ray ray Camera.main.ScreenPointToRay(Input.mousePosition); if (Physics.Raycast(ray, out RaycastHit hit, 100)) {obj.transform.…

uniapp页面不跳转问题!(使用uni.$u.route或者原生uni.navigateTo)页面跳转ios无效果(既不报错也不跳转页面)

1.问题描述: 通常使用添加事件来触发页面跳转都没问题,但是现在业务需求,在一个方法中自动去携带参数跳转到另外一个页面,android真机无问题,就ios一直无法跳转过去! 2.解决方法: 2.1 必须使用setTimeout来延迟跳转 2.2 setTimeout的延迟时间必须要大于300 不要问为什么…

递归 算法

递归、搜索与回溯算法 1. 汉诺塔2. 合并两个有序链表3. 反转链表4. 两两交换链表中的节点5. Pow&#xff08;x,n&#xff09;-快速幂 1. 汉诺塔 题目链接&#xff1a; 面试题 08.06. 汉诺塔问题 解题思路&#xff1a; 首先观察有一个、两个、三个盘子时的情况&#xff0c;手…

深度学习常用指标

1. 混淆矩阵&#xff08;误差矩阵&#xff09; 2. 准确率&#xff08;overall accuracy&#xff09; 代表了所有预测正确的样本占所有预测样本总数的比例 这里分类正确代表了正样本被正确分类为正样本&#xff0c;负样本被正确分类为负样本 3. 平均精度&#xff08;average…

黑马JavaWeb-day06、07、08(SQL部分) _

文章目录 MYSQL概述数据模型SQL简介SQL分类 DDL数据库操作表操作 DML增&#xff08;INSERT&#xff09;改&#xff08;UPDATE&#xff09;删&#xff08;DELETE&#xff09; DQL基本查询条件查询&#xff08;where&#xff09;分组查询&#xff08;group by&#xff09;排序查询…

D87【python 接口自动化学习】- pytest基础用法

day87 pytest运行参数 -m -k 学习日期&#xff1a;20241203 学习目标&#xff1a;pytest基础用法 -- pytest运行参数-m -k 学习笔记&#xff1a; 常用运行参数 pytest运行参数-m -k pytest -m 执行特定的测试用例&#xff0c;markers最好使用英文 [pytest] testpaths./te…

【嘟嘟早教卡】 小程序源码分享带后台管理

【嘟嘟早教卡】是专门为 3-6 岁婴幼儿童学习普通话、英语研发的早教启蒙认知识字的小程序 小程序由 Taro 及 Tailwind CSS 构建而成&#xff0c;后台管理使用 Laravel 及 Tailwind CSS 想法源于小时候玩的认知卡片&#xff0c;基本大部分家庭都买过认知卡片&#xff0c;我按照…

黑马微服务开发与实战学习笔记_MybatisPlus_P1介绍与快速入门

系列博客目录 文章目录 系列博客目录MybatisPlus介绍快速入门Part1:入门案例Part1.1:MyBatis项目Part1.2:实现MP Part2:常见注解Part2.1:约定Part2.2:常见注解 Part3:常见配置MyBatisPlus使用的基本流程是什么? MybatisPlus介绍 在Mybatis上加了Plus&#xff0c;表示对Mybati…

虚幻引擎---材质篇

一、基础知识 虚幻引擎中的材质&#xff08;Materials&#xff09; 定义了场景中对象的表面属性&#xff0c;包括颜色、金属度、粗糙度、透明度等等&#xff1b;可以在材质编辑器中可视化地创建和编辑材质&#xff1b;虚幻引擎的渲染管线的着色器是用高级着色语言&#xff08;…

爬虫专栏第一篇:深入探索爬虫世界:基础原理、类型特点与规范要点全解析

本专栏会对爬虫进行从0开始的讲解&#xff0c;每一步都十分的细致&#xff0c;如果你感兴趣希望多多点赞收藏关注支持 简介&#xff1a;文章对爬虫展开多方面剖析。起始于爬虫的基本概念&#xff0c;即依特定规则在网络抓取信息的程序或脚本&#xff0c;在搜索引擎信息提取上作…

Y20030028 JAVA+SSM+MYSQL+LW+基于JAVA的考研监督互助系统的设计与实现 源代码 配置 文档

基于JAVA的考研监督互助系统 1.项目描述2. 课题开发背景及意义3.项目功能4.界面展示5.源码获取 1.项目描述 随着高等教育的普及和就业竞争的加剧&#xff0c;越来越多的学生选择继续深造&#xff0c;参加研究生入学考试。考研人数的不断增加&#xff0c;使得考研过程中的学习监…

【AI系统】推理流程全景

推理流程全景 本文介绍神经网络模型在部署态中的两种方式&#xff1a;云侧部署和边缘侧部署。其中&#xff0c;云侧部署适用于云服务器等具备强大计算能力和存储空间的环境&#xff0c;可以实现高吞吐量和集中的数据管理&#xff0c;但可能面临高成本、网络延迟和数据隐私等挑…

9.13[debug]

这个错误表明 Git 尝试通过 HTTPS 协议连接到 Gitee 上的仓库时&#xff0c;实际上却尝试连接到了本地的 127.0.0.1&#xff08;即 localhost&#xff09;的 7890 端口&#xff0c;这通常是因为 HTTP 代理配置错误或全局 Git 配置中的代理设置不正确 如果这些命令返回了代理设…

Linux-GPIO应用编程

本章介绍应用层如何控制 GPIO&#xff0c;譬如控制 GPIO 输出高电平、或输出低电平。 只要是用到GPIO的外设&#xff0c;都有可能用得到这些操作方法。 照理说&#xff0c;GPIO的操作应该是由驱动层去做的&#xff0c;使用寄存器操作或者GPIO子系统之类的框架。 但是&#xff0…

Altium Designer学习笔记 28 扇孔处理

基于Altium Designer 23学习版&#xff0c;四层板智能小车PCB 更多AD学习笔记&#xff1a;Altium Designer学习笔记 1-5 工程创建_元件库创建Altium Designer学习笔记 6-10 异性元件库创建_原理图绘制Altium Designer学习笔记 11-15 原理图的封装 编译 检查 _PCB封装库的创建Al…

XiYan-SQL:⼀种多⽣成器集成的Text-to-SQL框架

发布于:2024 年 12 月 03 日 星期二 北京 #NL2SQL #阿里巴巴 #Text-to-SQL 文提出了一种用于自然语言到 SQL 转换的多生成器集成框架 ——XiYan-SQL,旨在应对大型语言模型在 NL2SQL 任务中的挑战。该框架融合提示工程与监督微调(SFT)方法,利用 SFT 的可控性与上下文学习(…

qtcanpool 知 08:Docking

文章目录 前言口味改造后语 前言 很久以前&#xff0c;作者用 Qt 仿照前端 UI 设计了一个 ministack&#xff08;https://gitee.com/icanpool/qtcanpool/blob/release-1.x/src/libs/qcanpool/ministack.h&#xff09; 控件&#xff0c;这个控件可以折叠。部分用户体验后&#…