STM32 BootLoader 刷新项目 (十三) Python上位机介绍
STM32 BootLoader 刷新项目 (十三) Python上位机介绍
大家好,这是我们STM32 BootLoader的最后一篇文章了,讲述用Python写的上位机,也更新了半年时间了,谢谢大家的支持,到目前为止,已经更新了12篇文章了,想必大家对BootLoader已经有了个基本的了解,下面是Python上位机的全部源码,有需要的兄弟可以借鉴一下,水平有限。
以下是将完整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. 串口部分
# ----------------------------- 串口部分 ----------------------------------------# 列出所有可用的串口
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")
解释:
- 代码中定义了多个命令处理函数,用于根据命令解析返回数据并打印结果。
- 使用位操作来判断和解析各种Flash状态(如保护模式、PCROP等)。
- 核心功能包括读取芯片状态、擦除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
功能总结:
decode_menu_command_code
函数是主命令处理入口,根据用户选择的命令代码执行对应操作。- 每个命令都包括以下步骤:
- 填充数据缓冲区,包括命令代码和参数。
- 计算数据缓冲区的 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() # 清空串口缓冲区
功能说明:
-
read_bootloader_reply
函数:- 用于解析Bootloader的返回数据。
- 先读取2字节的数据(ACK/NACK和后续数据长度)。
- 如果CRC校验成功(ACK为
0xA5
),调用相应的处理函数。 - 如果接收到
0x7F
,表示CRC校验失败。 - 如果没有任何返回,表示设备超时未响应。
-
菜单循环:
- 通过
while True
创建无限循环,用于显示Bootloader命令菜单。 - 提示用户输入命令代码(如获取版本、擦除Flash等)。
- 验证用户输入是否有效(必须是数字)。
- 调用
decode_menu_command_code
函数根据用户输入执行相应命令。 - 提供按任意键继续的功能,以便用户可以反复选择命令。
- 每次循环结束后清空串口缓冲区。
- 通过
整体逻辑:
- 程序启动时,要求用户输入设备的串口号。
- 程序与目标设备建立通信后,进入菜单界面。
- 用户根据提示输入命令代码,程序解析并发送相应的Bootloader命令。
- 根据设备返回的数据,显示操作结果。
- 用户可以重复操作,直到输入退出命令(命令代码
0
)为止。
至此,整个代码的功能和实现已经完全解释。如果还有特定部分需要详细说明或补充,请向我联系!
9. 实战操作
下面是上位机的命令菜单,通过在终端调用Python脚本,然后在终端输入下位机连接的串口号,即可进入命令界面,目前可支持如下命令:
其他的操作也在之前的文章中做了介绍,有兴趣的可以查找我之前的文章。
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
相关文章:
STM32 BootLoader 刷新项目 (十三) Python上位机介绍
STM32 BootLoader 刷新项目 (十三) Python上位机介绍 大家好,这是我们STM32 BootLoader的最后一篇文章了,讲述用Python写的上位机,也更新了半年时间了,谢谢大家的支持,到目前为止,已经更新了12篇文章了&am…...
美畅物联丨智能监控,高效运维:视频汇聚平台在储能领域的实践探索
在当今全球能源格局不断变化的大背景下,对清洁能源的需求正以惊人的速度增长。储能项目作为平衡能源供需、提升能源利用效率的关键环节,其规模和复杂度也在不断攀升。在储能项目的运营管理过程中,安全监控、设备运维以及数据管理等方面面临着…...
T C P
文章目录 基于UDP应用场景 TCP协议TCP 协议段格式确认应答机制16位窗口大小 下定义32位序号和32位确认序号序号是什么?确认序号 基于UDP应用场景 UDP,tcp这样的协议根本不是直接谈UDP。tcp的应用场景,一定是上层写了应用层协议,所…...
MongoDB的简单使用
MongoDB(文档数据库)的简单使用 MongoDB最好的学习资料就是他的官方文档:SQL 到 MongoDB 的映射图表 - MongoDB 手册 v8.0 1.MongoDB CRUD操作 1.1Insert操作 基本方法: db.collection.insertOne() 将单个文档(document)插入集合中 db.collectio…...
【Exp】# Microsoft Visual C++ Redistributable 各版本下载地址
Microsoft官方页面 https://support.microsoft.com/en-us/help/2977003/the-latest-supported-visual-c-downloads Redistributable 2019 X86: https://aka.ms/vs/16/release/VC_redist.x86.exe X64: https://aka.ms/vs/16/release/VC_redist.x64.exe Redistributable 201…...
【MySQL】表的约束
目录 一、非空约束not null 二、默认值约束default 三、列描述comment 四、填充零zerofill 五、主键primary key 六、自增长auto_increment 七、唯一键unique 八、外键foreign key 一、非空约束not null 如果不对一个字段做非空约束,则默认为空。但空数据无…...
c++高级篇(四) ——Linux下IO多路复用之epoll模型
IO多路复用 —— epoll 前言 在之前我们就已经介绍过了select和poll,在作为io多路复用的最后一个的epoll,我们来总结一下它们之间的区别: a select 实现原理 select 通过一个文件描述符集合(fd_set)来工作,该集合可以包含需要监控的文件…...
基于Java Springboot环境保护生活App且微信小程序
一、作品包含 源码数据库设计文档万字PPT全套环境和工具资源部署教程 二、项目技术 前端技术:Html、Css、Js、Vue、Element-ui 数据库:MySQL 后端技术:Java、Spring Boot、MyBatis 三、运行环境 开发工具:IDEA/eclipse 微信…...
.NET 9 中 LINQ 新增功能实现过程
本文介绍了.NET 9中LINQ新增功能,包括CountBy、AggregateBy和Index方法,并提供了相关代码示例和输出结果,感兴趣的朋友跟随我一起看看吧 LINQ 介绍 语言集成查询 (LINQ) 是一系列直接将查询功能集成到 C# 语言的技术统称。 数据查询历来都表示为简单的…...
【Vue3中Router使用】
Vue3中Router使用 1. 安装vue-router组件2. 建两个测试页面2.1 测试页面Home.vue2.2 测试页面Category.vue 3. 创建路由对象4. 在入口main.js中引入router把App.vue改成路由页面5. 测试5.1 关闭检查解决ESlint报错5.2 改文件名解决ESlint检查报错测试WebHashHistory 和WebHisto…...
性能测试攻略(一):需求分析
性能测试成为软件开发和运维过程中不可或缺的一环。性能测试不仅能够帮助我们了解系统在特定条件下的表现,还能帮助我们发现并解决潜在的性能问题。那么我们怎么做一次完整的性能测试呢?首先,我们需要进行需求分析,来明确我们的测…...
android WebRtc 无法推流以及拉流有视频无声音问题
最近在开发使用WebRtc进行视频通话和语音通话,我使用的设备是MTK的手机,期间后台的技术人员几乎没法提供任何帮助,只有接口和测试的web端,有遇到不能推流。推流成功网页端有画面有声音,但是安卓端有画面,没…...
Socket编程TCP
【Linux】TCP编程 实验:通过TCP通信—在客户端输入要执行的指令,接收执行结果,另服务端接收指令并执行,向客户端发送执行结果 //主函数 #include<iostream> #include<string> #include"log.hpp" #include…...
《以 C++为笔,绘就手势识别人机交互新画卷》
在科技浪潮汹涌澎湃的当下,人机交互领域正处于深刻变革的前沿阵地。从古老的命令行输入到图形化界面的鼠标点击,再到如今风靡全球的触摸操控,每一次交互方式的革新都重塑了我们与电子设备的沟通模式。而近年来,手势识别技术作为一…...
【CSS】小球旋转loading加载动画
效果 css小球旋转loading动画 代码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewport" content"widthdevice-width, initial-scale1.0" /><title>Document<…...
Leetcode经典题6--买卖股票的最佳时机
买卖股票的最佳时机 题目描述: 给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。 你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。…...
BA是什么?
目录 1.EKF的步骤 一、问题定义与模型建立 二、线性化处理 三、应用卡尔曼滤波 四、迭代与收敛 五、结果评估与优化 注意事项 2.BA问题的步骤 一、问题定义与数据准备 二、构建优化模型 三、选择优化算法 四、执行优化过程 五、结果评估与优化 六、应用与验证 1.…...
【IDEA】报错:Try to run Maven import with -U flag (force update snapshots)
问题 IDEA运行项目报错:Try to run Maven import with -U flag (force update snapshots) 原因 IDEA 的项目运行绑定的maven有问题, 解决问题 检查项目绑定的maven配置...
MATLAB提供的窗函数
加窗法 为什么使用加窗法? 在数字滤波器设计和频谱估计中,加窗函数的选择对于整体结果的质量有重大影响。加窗的主要作用是减弱因无穷级数截断而产生的吉布斯现象的影响。 windowDesigner 六种常见的窗函数 根据离散时间傅里叶变换的乘法性质&a…...
git 使用配置
新拿到机器想配置git 获取代码权限,需要的配置方法 1. git 配置用户名和邮箱 git config --global user.name xxxgit config --global user.email xxemail.com 2. 生成ssh key ssh-keygen -t rsa -C "xxemail.com" 3. 获取ssh key cat ~/.ssh/id_rsa.…...
【深度学习】深入解析长短期记忆网络(LSTMs)
长短期记忆网络(Long Short-Term Memory networks, LSTMs)是一种特殊的递归神经网络(RNN),专门设计用来解决标准 RNN 在处理长序列数据时的梯度消失和梯度爆炸问题。LSTMs 在许多序列数据任务中表现出色,如…...
vue watch和computed的区别,computed和method的区别
发现宝藏 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。【宝藏入口】。 在 Vue 中,watch、computed 和 methods 都是常用的响应式功能,它们的用途和工作方式有所不同。下面分别解…...
搭建高可用负载均衡系统:Nginx 与云服务的最佳实践
搭建高可用负载均衡系统:Nginx 与云服务的最佳实践 引言 在项目开发过程中,我们通常在开发和测试阶段采用单机架构进行开发和测试。这是因为在这个阶段,系统的主要目的是功能实现和验证,单机架构足以满足开发人员的日常需求&…...
FFmpeg 4.3 音视频-多路H265监控录放C++开发十九,ffmpeg复用
封装就是将 一个h264,和一个aac文件重新封装成一个mp4文件。 这里我们的h264 和 aac都是来源于另一个mp4文件,也就是说,我们会将 in.mp4文件解封装成一路videoavstream 和 一路 audioavstream,然后 将这两路的 avstream 合并成一…...
Node.js JWT认证教程
Node.js JWT认证教程 1. 项目介绍 JSON Web Token (JWT) 是一种安全的跨域身份验证解决方案,在现代Web应用中广泛使用。本教程将详细讲解如何在Node.js中实现JWT认证。 2. 项目准备 2.1 初始化项目 # 创建项目目录 mkdir nodejs-jwt-auth cd nodejs-jwt-auth# …...
nn.utils.clip_grad_value_
nn.utils.clip_grad_value_ 是 PyTorch 中的一个函数,用于在训练过程中对模型的梯度进行裁剪,以防止梯度爆炸(gradient explosion)问题。该函数对梯度的每个元素进行裁剪,将其限制在一个指定的最大绝对值范围内。裁剪后…...
Java后端面试模板(技术面)
1、自我介绍模板 面试官您好!我是来自----大学计算机学院的一名大三学生,我的名字叫—。 在大学期间,我主要自学了一些主流的Java技术栈,其中主要包括:Java主流的框架:Spring MVC Spring Boot Spring Clou…...
【大语言模型】ACL2024论文-24 图像化歧义:Winograd Schema 挑战的视觉转变
【大语言模型】ACL2024论文-24 图像化歧义:Winograd Schema 挑战的视觉转变 目录 文章目录 【大语言模型】ACL2024论文-24 图像化歧义:Winograd Schema 挑战的视觉转变目录摘要研究背景问题与挑战如何解决核心创新点算法模型实验效果(包含重要…...
Docker 安装和使用
#Docker 安装和使用 文章目录 1. 安装2. 干掉讨厌的 sudo3. 使用镜像源3.1. 使用 upstart 的系统3.2. 使用 systemd 的系统 4. 基本使用4.1. 容器操作4.2. 镜像操作 5. 网络模式说明5.1. bridge 模式5.2. host 模式5.3. container 模式5.4. none 模式 6. 查看 Docker run 启动参…...
nginx网站服务
nginx介绍: 1、高并发,轻量级的web服务软件 2、稳定性高,系统资源消耗率低 对http的高并发处理能力高,单台物理服务器可以支持30000-50000个并发。 一般来说在工作中,单台的并发一般在20000. nginx的功能介绍&…...
MATLAB 手写判断点在多边形内外的2种方法(87)
MATLAB 手写判断点在多边形内外-方法1(87) 一、算法介绍二、算法实现1.方法1(代码+测试)2.方法2(代码+测试)三、结果一、算法介绍 手动实现两种方法,判断点在多边形的内部还是外部, 具体实现和测试代码如下,使用前请自行验证。(代码复制粘贴即可使用) 二、算法实现…...
Android SurfaceFlinger layer层级
壁纸作为显示的最底层窗口它是怎么显示的 1. SurfaceFlinger layer层级 锁屏状态dump SurfaceFlinger ,adb shell dumpsys SurfaceFlinger Display 0 (active) HWC layers: -----------------------------------------------------------------------------------…...
零基础快速掌握——【c语言基础】数组的操作,冒泡排序,选择排序
1.数组 内存空间连续: 2.定义格式 数组的定义格式: 数组分为一维数组、二维数组、以及多维数组,不同类型的数组定义格式时不一样 2.1 一维数组的定义 数据类型 数组名 [数组长度]; 解释: 数据类型࿱…...
个人IP建设:简易指南
许多个体创业者面临的一个关键挑战是如何为其企业创造稳定的需求。 作为个体创业者,您无法使用营销团队,因此许多人通过推荐和他们的网络来产生需求。因此,扩大您的网络是发展您的业务和产生持续需求的最佳策略。 这就是个人IP和品牌发挥作…...
【Unity高级】如何获取着色器(Shader)的关键词
在动态设置Shader时,会需要通过EnableKeyword, DisableKeyword来完成。但一个Shader有哪些关键词呢?Unity的文档中并没有列出来,但我们可以通过遍历Shader的KeywordSpace来查看。 1. 代码如下 using UnityEngine;public class KeywordExamp…...
OSS文件上传
1、我们这个系统对接的阿里云OSS需要先对接小鹏OSS系统获取accessKeyId、accessKeySecret,这个可以忽略 aliyun:oss:endpoint: https://oss-cn-hangzhou.aliyuncs.combucketName: xp-xpd-experiencedomain: https://xp-xpd-experience.oss-cn-hangzhou.aliyuncs.co…...
时序预测算法TimeXer代码解析
在时序预测领域,如何有效地利用外部变量(exogenous variables)来提升内部变量(endogenous variables)的预测性能一直是一个挑战。 在上一篇文章中,我结合论文为大家解读了TimeXer框架,今天&…...
【无标题】建议用坚果云直接同步zotero,其他方法已经过时,容易出现bug
created: 2024-12-06T16:07:45 (UTC 08:00) tags: [] source: https://zotero-chinese.com/user-guide/sync author: 数据与文件的同步 | Zotero 中文社区 Excerpt Zotero 中文社区,Zotero 中文维护小组,Zotero 插件,Zotero 中文 CSL 样式 数…...
Hive 分桶表的创建与填充操作详解
Hive 分桶表的创建与填充操作详解 在 Hive 数据处理中,分桶表是一个极具实用价值的功能,它相较于非分桶表能够实现更高效的采样,并且后续还可能支持诸如 Map 端连接等节省时间的操作。不过,值得注意的是,在向表写入数…...
docker怎么commit tag push?
在 Docker 中,commit、tag 和 push 是用于创建和推送自定义镜像到仓库的三个不同步骤。以下是每个命令的详细说明和使用方法: ### 1. docker commit 当你对一个运行中的容器做了修改,并希望将这些修改保存为一个新的镜像时,可以使…...
全面替换VMware,南昌大学一卡通的硬核智慧
将一昼夜分为十二时辰 是古人的博大智慧 晨光熹微,门扉轻启,负笈而行 智慧校园的“十二时辰”启幕新章 一、数字南大:一卡通打卡校园十二时辰 时辰轮转,一时有一时的使命师生们是如何高效、便捷地度过每个时辰?一张充…...
SpringMVC ,ioc和aop
IOC和AOP IOC 控制反转,将应用程序的控制权交给spring容器管理,而不是应用程序本身 1.创建一个mapper,测试用, 就写个普通方法 public class UserMapper {public void addUser(){System.out.println("dao层新增");} …...
3GPP R18 LTM(L1/L2 Triggered Mobility)是什么鬼?(三) RACH-less LTM cell switch
这篇看下RACH-less LTM cell switch。 相比于RACH-based LTM,RACH-less LTM在进行LTM cell switch之前就要先知道target cell的TA信息,进而才能进行RACH-less过程,这里一般可以通过UE自行测量或者通过RA过程获取,而这里的RA一般是通过PDCCH order过程触发。根据38.300中的描…...
Ansys Maxwell:Qi 无线充电组件
Qi 无线充电采用感应充电技术,无需物理连接器或电缆,即可将电力从充电站传输到兼容设备。由 WPC 管理的 Qi 标准确保了不同无线充电产品之间的互操作性。以下是 Qi v1.3 标准的核心功能: Qi v1.3 标准的主要特点 身份验证:确保充…...
Neo4j 图数据库安装与操作指南(以mac为例)
目录 一、安装前提条件 1.1 Java环境 1.2 Homebrew(可选) 二、下载并安装Neo4j 2.1 从官方网站下载 2.1.1 访问Neo4j的官方网站 2.1.2 使用Homebrew安装 三、配置Neo4j 3.1 设置环境变量(可选) 3.2 打开配置文件(bash_profile) 3.2.1 打开终端…...
基于MFC绘制门电路
MFC绘制门电路 1. 设计内容、方法与难点 本课题设计的内容包括了基本门电路中与门和非门的绘制、选中以及它们之间的连接。具体采用的方法是在OnDraw函数里面进行绘制,并设计元器件基类,派生出与门和非门,并组合了一个引脚类,在…...
Gitee上获取renren-fast-vue install并run dev错误处理
目的:获取一个手脚架、越简约越好、越干净越好、于是看上了renren-fast-vue… 前端:vue2 后端:jdk1.8 mysql 5.7 SpringBoot单体架构 一开始只是下载前后端项目到本地,一堆乱七八糟的错误,网上找的资料也参差不齐… …...
sdk项目的git 标记新tag的版本号
在 Git 中,tag 是用来标记某个特定的提交点(通常是发布版本或重要的里程碑)的工具。通过 git tag,你可以为版本号创建标记,帮助团队跟踪不同版本的代码。 如果你想创建一个新的版本号标签,可以按照以下步骤…...
学习日志022 -- python事件机制
作业: 1】思维导图 2】完成闹钟 main.py import sysfrom PySide6.QtCore import QTimerEvent, QTime,Qt from PySide6.QtGui import QMovie,QMouseEvent from PySide6.QtWidgets import QApplication, QWidget from Form import Ui_Formclass MyWidget(Ui_Form,Q…...
JAVA八股文-运行篇-创建项目运行(1)
前置环境搭建:jdk、maven、idea、linux环境 一、创建一个java项目 File->New->Project 二、填写基本信息 三、完成,写了一段代码 四、打包 五、本地运行,运行和debug二选一 六、上传至linux环境 七、linux环境下命令执行 7.1 指定Main方法类 …...