backup codes

This commit is contained in:
2025-12-19 22:25:54 +08:00
parent 8f711a4a2d
commit 397033e293
6 changed files with 1008 additions and 9 deletions

View File

@@ -40,7 +40,7 @@ CFLAGS += --stack-auto --noinduction --use-non-free
## Disable lospre (workaround for bug 2673)
#CFLAGS += --nolospre
LDFLAGS = -m$(ARCH) -l$(ARCH) --out-fmt-ihx
OPTFLAGS = -Wl-bOPTION=0x4800 -Wl-bOPTION_BOOT=0x4812
OPTFLAGS = -Wl-bOPTION=0x4800 -Wl-bOPTION_BOOT=0x480D
# Conditionally add ENABLE_OPTION_BOOTLOADER macro
ifneq ($(ENABLE_OPTION_BOOTLOADER),0)

View File

@@ -16,6 +16,8 @@ __asm
addw X, #3 ; [1C 00 03]
cpw X, (1,SP) ; [13 01]
jrne _exit ; [26 01]
// save SP to 0x0000
ldw 0x0000, Y
// jump to ram
ret ; [81]
_exit:

View File

@@ -1,9 +1,9 @@
;; M Code
;; option O0 O1 N1 O2 N2 O3 N3 O4 N4 O5 N5
;; 4800 00 00 ff 00 ff 00 ff 00 ff 00 ff -- -- -- -- --
;; 03D0|4810 -- -- 00 03 d5 a6 0d c7 52 32 c7 52 35 c7 52 31
;; 03E0|4820 a6 80 5f 5c 27 14 72 0b 52 30 f8 3b 52 31 4c 26
;; 03F0|4830 f1 96 5c 5c 5c 13 01 26 e7 81 72 cc 48 3e 80 04
;; option O0 O1 N1 O2 N2 O3 N3 O4 N4 O5 N5
;; 4800 00 00 ff 00 ff 00 ff 00 ff 00 ff -- -- 00 03 d0
;; 03D0|4810 a6 0d c7 52 32 c7 52 35 c7 52 31 a6 80 5f 5c 27
;; 03E0|4820 17 72 0b 52 30 f8 3b 52 31 4c 26 f1 c7 52 31 96
;; 03F0|4830 5c 5c 5c 13 01 26 e4 81 fe 94 72 cc 48 3e 80 04
;; UART1 register address definitions
UART1_SR = 0x5230 ; Status register
@@ -20,7 +20,7 @@ _boot_start_data:
;; Termination flag (copy process stops when encountering 0)
.db 0x00 ; [00] Termination byte
;; [03 D5] RAM address for ret execution
;; [03 D0] RAM address for ret execution
.db (RAM_SIZE-(_boot_go_adr-_boot_start+2))>>8
.db (RAM_SIZE-(_boot_go_adr-_boot_start+2))&0xFF
@@ -50,6 +50,9 @@ _boot_rx_wait:
inc A ; [4C] Increment A, used as receive counter
jrne _boot_rx_byte ; [26 F1] If A is not 0, continue receiving
;; Receive 128 bytes success
ld UART1_DR, A ; [C7 52 31] Send Ack 0x00
;; Check address
ldw X, SP ; [96]
incw X ; [5c]
@@ -62,6 +65,8 @@ _boot_rx_wait:
_boot_exit:
;; Timeout exit, jump to user program
ldw X, (X) ; [FE]
ldw SP, X ; [94]
jp [_boot_go_adr] ; [72 CC 48 3E] Indirect jump
_boot_go_adr:

464
scripts/boot2.s Normal file
View File

@@ -0,0 +1,464 @@
; ================================================
; STM8 RAM Bootloader for SDCC - Optimized Version
;
; 命令帧格式:
; 字节0: 帧头 (0x5A/0xA5)
; 字节1: 命令类型 (见CMD_*常量)
; 字节2-3: 目标地址 (高字节在前)
; 字节4: 数据长度 (0-64)
; 字节5-68: 数据内容 (最多64字节)
; 字节69: 校验和 (所有字节XOR)
; ================================================
BOOT2_SP = 0x03CF ; start of boot1 ram address
;; Register address definitions
UART1_SR = 0x5230 ; Status register
UART1_DR = 0x5231 ; Data register
UART1_BRR1 = 0x5232 ; Baud rate register 1
UART1_BRR2 = 0x5233 ; Baud rate register 2
UART1_CR2 = 0x5235 ; Control register 2
FLASH_DUKR = 0x5064 ; Data EEPROM unprotect
FLASH_PUKR = 0x5062 ; Flash unprotect
FLASH_CR2 = 0x505B ; Flash control 2
FLASH_NCR2 = 0x505C ; Flash control 2 complement
FLASH_IAPSR = 0x505F ; Flash in-application program status
OPT0_ROP = 0x4800 ; Option byte: ROP
WWDG_CR = 0x50D1 ; WWDG control register
;; Const vars
CMD_READ = 0xF1 ; 读内存命令
CMD_WRITE = 0xF2 ; 写内存命令
CMD_ERASE = 0xF3 ; 整片擦除命令
CMD_RESET = 0xF4 ; 复位命令
CMD_GO = 0xF5 ; 跳转执行命令
CMD_HEADER = 0x5A ; 帧头
ACK_HEADER = 0xA5 ; 应答帧头
SUCCESS_CODE = 0x00 ; 成功响应码
ERR_CHECKSUM = 0xE1 ; 校验错误
ERR_INVCMD = 0xE2 ; 非法命令
ERR_PGDIS = 0xE3 ; 编程受保护的地址
MAX_DATA_SIZE = 64 ; 最大数据长度
;; Vars
DEFAULT_SP_H = 0x0000 ; ram top address
DEFAULT_SP_L = 0x0000 ; ram top address
tx_buffer = 0x0002 ; protocol tx buffer
rx_buffer = 0x0002 ; protocol rx buffer
rx_state = 72 ; 接收状态
rx_length = 73 ; 接收长度
tx_state = 74 ; 发送状态
tx_data_length = 75 ; 待发送的数据长度
calc_checksum = 76 ; 计算的校验和
temp_var1 = 77 ; 临时变量
temp_var2 = 78 ; 临时变量
temp_var3 = 79 ; 临时变量
;; Bootloader body (load in ram ?-0x03D2)
.area RAM_BOOT
.db (BOOT2_SP-(_end-_start)+1)>>8
.db (BOOT2_SP-(_end-_start)+1)&0xFF
_start:
; 配置UART1: 128000波特率, 8N1, 启用TX/RX
mov UART1_BRR2, #0
mov UART1_BRR1, #1
mov UART1_CR2, #0x0C ; TEN=1, REN=1
_main_loop:
; 接收命令帧
call receive_frame
; 验证校验和
call verify_checksum
jrne _checksum_error
; 根据命令类型跳转
ld A, rx_buffer+1 ; 命令类型
cp A, #CMD_READ
jreq _cmd_read
cp A, #CMD_WRITE
jreq _cmd_write
cp A, #CMD_ERASE
jreq _cmd_erase
cp A, #CMD_RESET
jreq _cmd_reset
cp A, #CMD_GO
jreq _cmd_go
_invalid_cmd_error:
; 未知命令,发送错误响应
mov tx_state, #ERR_INVCMD
call send_ack_state_response
jra _main_loop
_checksum_error:
; 校验和错误响应
mov tx_state, #ERR_CHECKSUM
call send_ack_state_response
jra _main_loop
_cmd_read:
; 读取内存命令
call read_memory
jra _main_loop
_cmd_write:
; 写入内存命令
call write_memory
jra _main_loop
_cmd_erase:
; 擦除命令
call erase_memory
jra _main_loop
_cmd_reset:
; 复位命令 - 软件复位
call software_reset
; 注意: software_reset 不返回
_cmd_go:
; 跳转执行命令
call jump_to_address
; 注意: jump_to_address 不返回
receive_frame:
; 初始化接收状态
clr rx_state
mov rx_length, #6
ldw X, #rx_buffer
_receive_data:
; 接收数据部分, 等待RXNE=1
btjf UART1_SR, #5, _receive_data
; 根据状态接收不同部分
ld A, rx_state
cp A, #0
jreq _receive_header
cp A, #4
jreq _receive_length
ld A, UART1_DR
_save_data:
ld (X), A
incw X
inc rx_state
dec rx_length
jrne _receive_data ; 还有数据,继续接收
; 数据接收完成
mov rx_length, rx_state
ret
_receive_header:
ld A, UART1_DR
cp A, #CMD_HEADER
jrne _receive_data
jra _save_data
_receive_length:
ld A, UART1_DR
cp A, #MAX_DATA_SIZE
; length<=MAX_DATA_SIZE
jrugt receive_frame
; save length
ld rx_length, A
inc rx_length
inc rx_length
jra _save_data
verify_checksum:
; 初始化
clr calc_checksum
; 计算需要校验的字节数
ld A, rx_length
dec A
ld temp_var1, A
; 设置指针
ldw X, #rx_buffer
_verify_loop:
ld A, (X)
xor A, calc_checksum
ld calc_checksum, A
incw X
dec temp_var1
jrne _verify_loop
; 比较校验和
ld A, calc_checksum
cp A, (X) ; X现在指向校验和位置
ret
send_response_pkg:
clr calc_checksum
ldw X, #tx_buffer
ld A, tx_data_length
add A, #5
ld temp_var1, A
; send data
_send_loop:
ld A, (X)
ld UART1_DR, A
; calc checksum
xor A, calc_checksum
ld calc_checksum, A
_wait_tx1:
btjf UART1_SR, #7, _wait_tx1
; move to next
incw X
dec temp_var1
jrne _send_loop
; send checksum
ld A, calc_checksum
ld UART1_DR, A
_wait_tx2:
btjf UART1_SR, #7, _wait_tx2
; finish
ret
; 发送应答状态帧
send_ack_state_response:
; set header
ldw X, #tx_buffer
ld A, #ACK_HEADER
ld (X), A
; set length
addw X, #4
ld A, #1
ld (X), A
; set data
incw X
ld A, tx_state
ld (X), A
call send_response_pkg
ret
; 发送应答数据帧
send_ack_data_response:
; set header
ldw X, #tx_buffer
ld A, #ACK_HEADER
ld (X), A
; set length
addw X, #4
ld A, tx_data_length
ld (X), A
; already set data
call send_response_pkg
ret
read_memory:
; A = 读取长度
ldw X, #rx_buffer+5
ld A, (X)
ld tx_data_length, A
ld temp_var1, A
; Y = 缓存地址
ldw Y, X
; X = 读取地址
ldw X, #rx_buffer+2
ldw X, (X)
_read_loop:
; 读取字节
ld A, (X)
ld (Y), A
incw X
incw Y
dec temp_var1
jrne _read_loop
call send_ack_data_response
ret
; temp_var1: 待写入长度
; temp_var2: 单次写长度
; temp_var3: FLASH_IAPSR
write_memory:
; A = 写入长度
ldw X, #rx_buffer+4
ld A, (X)
ld temp_var1, A
; 检查长度为0直接返回
tnz A
jreq _write_success
; Y = src
incw X
ldw Y, X
; X = dst
ldw X, #rx_buffer+2
ldw X, (X)
; 检查是否为ram地址 (0x0000-0x3FFF)
ld A, XH
cp A, #0x40
jrult _mem_write
; 检查是否为flash地址 (0x8000-0xFFFF)
cp A, #0x80
jruge _flash_write
; 检查是否为eeprom/opt地址 (0x4000-0x4FFF)
cp A, #0x50
jrult _flash_write
_mem_write:
ld A, (Y)
ld (X), A
incw X
incw Y
dec temp_var1
jrne _mem_write
call send_ack_state_response
ret
_flash_write:
; unlock FLASH/DATA
call unlock_flash
; Word Program
mov FLASH_CR2, #0xC0
mov FLASH_NCR2, #0x3F
_word_write:
mov temp_var2, #4
_write_loop:
; Write a Word
ld A, (Y)
ld (X), A
incw X
incw Y
dec temp_var2
jrne _write_loop
; Wait write done
_wait_flash_done:
mov temp_var3, FLASH_IAPSR
btjt temp_var3, #0, _write_error
btjf temp_var3, #2, _wait_flash_done
ld A, temp_var1
cp A, #4
jrule _write_success
sub A, #4
ld temp_var1, A
jra _word_write
_write_error:
mov tx_state, #ERR_PGDIS
jra _write_end
_write_success:
mov tx_state, #SUCCESS_CODE
_write_end:
; Word programming off
mov FLASH_CR2, #0x00
mov FLASH_NCR2, #0xFF
; lock FLASH/DATA
call lock_flash
call send_ack_state_response
ret
unlock_flash:
; 检查是否为Flash地址 (0x8000-0xFFFF)
ld A, XH
cp A, #0x80
jrult _do_unlock_data
_do_unlock_flash:
mov FLASH_PUKR, #0x56 ; KEY1
mov FLASH_PUKR, #0xAE ; KEY2
ret
_do_unlock_data:
mov FLASH_DUKR, #0xAE ; KEY1
mov FLASH_DUKR, #0x56 ; KEY2
ret
lock_flash:
; 检查是否为Flash地址 (0x8000-0xFFFF)
ld A, XH
cp A, #0x80
jrult _do_lock_data
_do_lock_flash:
bres FLASH_IAPSR, #1
ret
_do_lock_data:
bres FLASH_IAPSR, #3
ret
; 两个字节长度
erase_memory:
; unlock option byte
mov FLASH_CR2, #0xC0
mov FLASH_NCR2, #0x3F
ld A, #0xAA ; lock chip
_write_rop:
ld OPT0_ROP, A
_erase_loop:
mov temp_var1, FLASH_IAPSR
btjt temp_var1, #0, _lock_opt
btjf temp_var1, #2, _erase_loop
cp A, #0
jreq _lock_opt
ld A, #0 ; unlock chip
jra _write_rop
_lock_opt:
; lock option byte
mov FLASH_CR2, #0x00
mov FLASH_NCR2, #0xFF
ret
software_reset:
; STM8软件复位: 写0x80到WWDG_CR
mov WWDG_CR, #0x80
; 复位需要时间,这里无限循环
jra software_reset
jump_to_address:
mov tx_state, #SUCCESS_CODE
call send_ack_state_response
; 使用X作为延时计数器
ldw X, #1000
_delay_loop:
decw X
jrne _delay_loop
; 读取跳转地址
ld A, rx_buffer+2
ld XH, A
ld A, rx_buffer+3
ld XL, A
; 跳转到指定地址
jp (X)
_end:

View File

@@ -0,0 +1,523 @@
#!/usr/bin/env python3
"""
STM8 RAM Bootloader测试脚本
用于测试bootloader功能
工作流程:
1. 默认加载同级目录下的boot2.bin发送可通过选项指定
2. 执行后,等待用户按下开发板的复位键
3. MCU复位后会以波特率9600发送0x00 0x0D到python
4. 收到后开始发送boot2.bin从最后一块开始向前发送每发送128字节后MCU会应答一个0x01
5. 发送完成后进入交互shell用户可以输入命令执行响应的操作先不实现
用法:
python3 stm8_bootloader_test.py [-p PORT] [-b BAUDRATE] [--bin BIN_FILE]
"""
import sys
import os
import time
import argparse
import serial
import struct
import colorama
from colorama import Fore, Style
from typing import Optional, List
colorama.init(autoreset=True)
class STM8BootloaderTester:
def __init__(self, port: str, baudrate: int = 9600, bin_file: str = None):
self.port = port
self.baudrate = baudrate
# 如果未指定bin文件使用脚本同级目录下的boot2.bin
if bin_file is None:
script_dir = os.path.dirname(os.path.abspath(__file__))
self.bin_file = os.path.join(script_dir, "boot2.bin")
else:
self.bin_file = bin_file
self.ser = None
self.bootloader_size = 0
def open_serial(self) -> bool:
"""打开串口"""
try:
print(f"{Fore.CYAN}打开串口 {self.port}, 波特率 {self.baudrate}...")
self.ser = serial.Serial(
port=self.port,
baudrate=self.baudrate,
bytesize=serial.EIGHTBITS,
parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_ONE,
timeout=1.0, # 读取超时1秒
write_timeout=1.0 # 写入超时1秒
)
if self.ser.is_open:
print(f"{Fore.GREEN}串口打开成功!")
return True
else:
print(f"{Fore.RED}串口打开失败!")
return False
except serial.SerialException as e:
print(f"{Fore.RED}串口错误: {e}")
return False
def close_serial(self):
"""关闭串口"""
if self.ser and self.ser.is_open:
self.ser.close()
print(f"{Fore.YELLOW}串口已关闭")
def check_file_size(self, file_size: int, chunk_size: int = 128) -> bool:
"""检查文件大小是否为chunk_size的整数倍"""
if file_size % chunk_size != 0:
print(f"{Fore.RED}错误: 文件大小 {file_size} 不是 {chunk_size} 的整数倍!")
print(f"{Fore.YELLOW}请确保bootloader大小为 {chunk_size} 字节的整数倍")
return False
return True
def read_bin_file(self) -> Optional[bytes]:
"""读取bin文件"""
try:
if not os.path.exists(self.bin_file):
print(f"{Fore.RED}错误: 文件 {self.bin_file} 不存在!")
return None
with open(self.bin_file, 'rb') as f:
data = f.read()
self.bootloader_size = len(data)
print(f"{Fore.GREEN}读取 {self.bootloader_size} 字节来自 {self.bin_file}")
# 检查文件大小是否为128的整数倍
if not self.check_file_size(self.bootloader_size):
return None
# 显示文件信息
if self.bootloader_size > 0:
print(f"{Fore.CYAN}文件首16字节: {data[:16].hex(' ')}")
if self.bootloader_size > 16:
print(f"{Fore.CYAN}文件尾16字节: {data[-16:].hex(' ')}")
return data
except Exception as e:
print(f"{Fore.RED}读取文件错误: {e}")
return None
def wait_for_mcu_ready(self, timeout: int = 30) -> bool:
"""等待MCU发送就绪信号 0x00 0x0D"""
print(f"\n{Fore.YELLOW}等待MCU就绪信号...")
print(f"{Fore.YELLOW}请按下开发板上的复位键!")
print(f"{Fore.YELLOW}等待超时: {timeout}")
start_time = time.time()
last_print_time = start_time
expected_bytes = b'\x00\x0D'
buffer = b''
try:
while time.time() - start_time < timeout:
current_time = time.time()
elapsed = int(current_time - start_time)
# 每5秒打印一次状态
if current_time - last_print_time >= 5:
print(f"{Fore.YELLOW}已等待 {elapsed} 秒...")
last_print_time = current_time
# 检查串口是否有数据
if self.ser.in_waiting > 0:
# 读取一个字节
byte = self.ser.read(self.ser.in_waiting)
if byte:
buffer += byte
# 检查是否匹配就绪信号
if buffer[-2:] == expected_bytes:
print(f"{Fore.GREEN}收到MCU就绪信号: {buffer.hex(' ')}")
return True
# 显示接收到的数据(调试用)
if buffer:
print(f"{Fore.CYAN}[调试] 接收缓冲区: {buffer.hex(' ')}")
# 保留缓冲区最后一个字节
buffer = buffer[-1:]
time.sleep(0.01)
except Exception as e:
print(f"{Fore.RED}等待MCU就绪时出错: {e}")
print(f"{Fore.RED}超时! 未收到MCU就绪信号")
return False
def reverse_bytes_within_chunks(self, data: bytes, chunk_size: int = 128) -> bytes:
"""在每个128字节块内反转字节顺序"""
if len(data) % chunk_size != 0:
raise ValueError(f"数据长度({len(data)})必须是{chunk_size}的整数倍")
result = bytearray()
num_chunks = len(data) // chunk_size
for i in range(num_chunks):
start_idx = i * chunk_size
end_idx = start_idx + chunk_size
chunk = data[start_idx:end_idx]
# 反转块内字节顺序
reversed_chunk = bytes(reversed(chunk))
result.extend(reversed_chunk)
return bytes(result)
def send_bootloader_reverse(self, data: bytes, chunk_size: int = 128) -> bool:
"""反向发送bootloader数据从最后一块开始每块内字节也反转"""
if not data:
print(f"{Fore.RED}错误: 没有数据可发送")
return False
# 首先在每个128字节块内反转字节顺序
reversed_data = self.reverse_bytes_within_chunks(data, chunk_size)
total_size = len(reversed_data)
num_chunks = total_size // chunk_size
print(f"\n{Fore.CYAN}开始反向发送bootloader...")
print(f"{Fore.CYAN}总大小: {total_size} 字节")
print(f"{Fore.CYAN}分块大小: {chunk_size} 字节")
print(f"{Fore.CYAN}总块数: {num_chunks}")
print(f"{Fore.CYAN}发送顺序: 从最后一块到第一块,每块内字节顺序已反转")
success_count = 0
fail_count = 0
try:
# 从最后一块开始发送
for i in range(num_chunks - 1, -1, -1):
start_idx = i * chunk_size
end_idx = start_idx + chunk_size
chunk = reversed_data[start_idx:end_idx]
# 原始块索引(反转前)
original_chunk_index = i
original_start_addr = original_chunk_index * chunk_size
print(f"\n{Fore.YELLOW}[块 {num_chunks-i}/{num_chunks}]")
print(f"{Fore.CYAN} 原始位置: 0x{original_start_addr:04X}-0x{original_start_addr+chunk_size-1:04X}")
print(f"{Fore.CYAN} 发送大小: {len(chunk)} 字节")
print(f"{Fore.CYAN} 数据(已反转): {chunk[:16].hex(' ')}" +
("..." if len(chunk) > 16 else ""))
# 发送数据块
bytes_sent = self.ser.write(chunk)
if bytes_sent != len(chunk):
print(f"{Fore.RED} 发送失败! 期望 {len(chunk)} 字节, 实际 {bytes_sent} 字节")
fail_count += 1
continue
print(f"{Fore.GREEN} 发送成功: {bytes_sent} 字节")
# 等待MCU应答
print(f"{Fore.YELLOW} 等待MCU应答...")
try:
# 读取应答
ack = self.ser.read(1)
if ack == b'\x00':
print(f"{Fore.GREEN} 收到确认: 0x00")
success_count += 1
elif ack:
print(f"{Fore.RED} 收到错误应答: {ack.hex()}")
fail_count += 1
else:
print(f"{Fore.RED} 应答超时!")
fail_count += 1
except Exception as e:
print(f"{Fore.RED} 读取应答时出错: {e}")
fail_count += 1
# 显示进度
progress = (num_chunks - i) / num_chunks * 100
print(f"{Fore.CYAN} 进度: {progress:.1f}% ({num_chunks-i}/{num_chunks})")
except Exception as e:
print(f"{Fore.RED}发送过程中出错: {e}")
return False
print(f"\n{Fore.CYAN}=== 发送完成 ===")
print(f"{Fore.GREEN}成功块数: {success_count}")
print(f"{Fore.RED}失败块数: {fail_count}")
return fail_count == 0
def interactive_shell(self):
"""交互式命令行"""
print(f"\n{Fore.CYAN}{'='*60}")
print(f"{Fore.CYAN}进入交互模式")
print(f"{Fore.CYAN}输入 'help' 查看可用命令")
print(f"{Fore.CYAN}输入 'exit' 退出")
print(f"{Fore.CYAN}{'='*60}")
commands = {
'help': self._cmd_help,
'read': self._cmd_read,
'write': self._cmd_write,
'erase': self._cmd_erase,
'reset': self._cmd_reset,
'go': self._cmd_go,
'echo': self._cmd_echo,
'info': self._cmd_info,
}
while True:
try:
# 获取用户输入
cmd_input = input(f"\n{Fore.GREEN}boot> ").strip()
if not cmd_input:
continue
# 检查是否为退出命令
if cmd_input.lower() in ['exit', 'quit', 'q']:
print(f"{Fore.YELLOW}退出交互模式")
break
# 分割命令和参数
parts = cmd_input.split()
cmd = parts[0].lower()
args = parts[1:] if len(parts) > 1 else []
# 执行命令
if cmd in commands:
commands[cmd](args)
else:
print(f"{Fore.RED}未知命令: {cmd}")
print(f"{Fore.YELLOW}输入 'help' 查看可用命令")
except KeyboardInterrupt:
print(f"\n{Fore.YELLOW}检测到Ctrl+C退出交互模式")
break
except Exception as e:
print(f"{Fore.RED}命令执行错误: {e}")
def _cmd_help(self, args):
"""显示帮助"""
print(f"{Fore.CYAN}可用命令:")
print(f"{Fore.YELLOW} help 显示此帮助信息")
print(f"{Fore.YELLOW} read <addr> <len> 读取内存")
print(f"{Fore.YELLOW} write <addr> <data> 写入内存")
print(f"{Fore.YELLOW} erase 擦除Flash")
print(f"{Fore.YELLOW} reset 复位MCU")
print(f"{Fore.YELLOW} go <addr> 跳转到指定地址执行")
print(f"{Fore.YELLOW} echo <text> 回显文本")
print(f"{Fore.YELLOW} info 显示信息")
print(f"{Fore.YELLOW} exit 退出交互模式")
def _cmd_read(self, args):
"""读取内存命令"""
if len(args) < 2:
print(f"{Fore.RED}用法: read <地址> <长度>")
print(f"{Fore.YELLOW}示例: read 0x8000 16")
return
try:
addr = int(args[0], 0)
length = int(args[1], 0)
print(f"{Fore.CYAN}读取内存:")
print(f"{Fore.CYAN} 地址: 0x{addr:04X}")
print(f"{Fore.CYAN} 长度: {length} 字节")
# TODO: 实现读取命令
print(f"{Fore.YELLOW}[待实现] 读取功能")
except ValueError as e:
print(f"{Fore.RED}参数错误: {e}")
def _cmd_write(self, args):
"""写入内存命令"""
if len(args) < 2:
print(f"{Fore.RED}用法: write <地址> <数据>")
print(f"{Fore.YELLOW}示例: write 0x8000 0x01 0x02 0x03")
return
try:
addr = int(args[0], 0)
data = [int(x, 0) for x in args[1:]]
print(f"{Fore.CYAN}写入内存:")
print(f"{Fore.CYAN} 地址: 0x{addr:04X}")
print(f"{Fore.CYAN} 数据: {bytes(data).hex(' ')}")
print(f"{Fore.CYAN} 长度: {len(data)} 字节")
# TODO: 实现写入命令
print(f"{Fore.YELLOW}[待实现] 写入功能")
except ValueError as e:
print(f"{Fore.RED}参数错误: {e}")
def _cmd_erase(self, args):
"""擦除Flash命令"""
print(f"{Fore.YELLOW}擦除Flash...")
# TODO: 实现擦除命令
print(f"{Fore.YELLOW}[待实现] 擦除功能")
def _cmd_reset(self, args):
"""复位命令"""
print(f"{Fore.YELLOW}复位MCU...")
# TODO: 实现复位命令
print(f"{Fore.YELLOW}[待实现] 复位功能")
def _cmd_go(self, args):
"""跳转执行命令"""
if len(args) < 1:
print(f"{Fore.RED}用法: go <地址>")
print(f"{Fore.YELLOW}示例: go 0x8000")
return
try:
addr = int(args[0], 0)
print(f"{Fore.YELLOW}跳转到地址: 0x{addr:04X}")
# TODO: 实现跳转命令
print(f"{Fore.YELLOW}[待实现] 跳转功能")
except ValueError as e:
print(f"{Fore.RED}参数错误: {e}")
def _cmd_echo(self, args):
"""回显命令"""
if args:
text = ' '.join(args)
print(f"{Fore.CYAN}回显: {text}")
else:
print(f"{Fore.RED}用法: echo <文本>")
def _cmd_info(self, args):
"""显示信息命令"""
print(f"{Fore.CYAN}Bootloader测试工具信息:")
print(f"{Fore.CYAN} 串口: {self.port}")
print(f"{Fore.CYAN} 波特率: {self.baudrate}")
print(f"{Fore.CYAN} Bootloader文件: {self.bin_file}")
print(f"{Fore.CYAN} Bootloader大小: {self.bootloader_size} 字节")
def run(self):
"""运行测试"""
print(f"{Fore.CYAN}{'='*60}")
print(f"{Fore.CYAN}STM8 RAM Bootloader 测试工具")
print(f"{Fore.CYAN}{'='*60}")
# 1. 打开串口
if not self.open_serial():
return False
try:
# 2. 读取bin文件
bin_data = self.read_bin_file()
if not bin_data:
return False
# 3. 等待MCU就绪
if not self.wait_for_mcu_ready():
return False
# 4. 发送bootloader反向发送
if not self.send_bootloader_reverse(bin_data):
print(f"{Fore.RED}Bootloader发送失败!")
return False
# 5. 进入交互模式
self.interactive_shell()
return True
except KeyboardInterrupt:
print(f"\n{Fore.YELLOW}用户中断!")
return False
except Exception as e:
print(f"{Fore.RED}运行时错误: {e}")
import traceback
traceback.print_exc()
return False
finally:
self.close_serial()
def list_serial_ports():
"""列出可用串口"""
import serial.tools.list_ports
ports = serial.tools.list_ports.comports()
if not ports:
print(f"{Fore.YELLOW}未找到可用串口!")
return []
print(f"{Fore.CYAN}可用串口:")
for i, port in enumerate(ports):
print(f"{Fore.GREEN} [{i}] {port.device}: {port.description}")
return ports
def main():
parser = argparse.ArgumentParser(description="STM8 RAM Bootloader测试工具")
parser.add_argument("-p", "--port", help="串口号 (如 COM3, /dev/ttyUSB0)")
parser.add_argument("-b", "--baudrate", type=int, default=9600,
help="串口波特率 (默认: 9600)")
parser.add_argument("--bin", default=None,
help="Bootloader bin文件路径 (默认: 脚本目录下的boot2.bin)")
parser.add_argument("--list", action="store_true",
help="列出可用串口")
args = parser.parse_args()
# 如果指定了--list列出串口后退出
if args.list:
list_serial_ports()
return
# 如果没有指定串口,列出并让用户选择
if not args.port:
ports = list_serial_ports()
if not ports:
print(f"{Fore.RED}请使用 -p 参数指定串口!")
return
try:
choice = input(f"\n{Fore.CYAN}请选择串口号 [0-{len(ports)-1}]: ")
idx = int(choice)
if 0 <= idx < len(ports):
args.port = ports[idx].device
else:
print(f"{Fore.RED}无效的选择!")
return
except (ValueError, IndexError):
print(f"{Fore.RED}无效的输入!")
return
# 创建测试器并运行
tester = STM8BootloaderTester(
port=args.port,
baudrate=args.baudrate,
bin_file=args.bin
)
success = tester.run()
if success:
print(f"\n{Fore.GREEN}测试完成!")
else:
print(f"\n{Fore.RED}测试失败!")
# 等待用户按键退出
input(f"\n{Fore.YELLOW}按Enter键退出...")
if __name__ == "__main__":
main()

View File

@@ -9,8 +9,13 @@ void main(void) {
PB_DDR |= (1 << LED_PIN);
PB_CR1 |= (1 << LED_PIN);
while (1) {
UART1_BRR1 = 0x0D;
UART1_BRR2 = 0x00;
UART1_CR2 = 0x0D;
while(1) {
PB_ODR ^= (1 << LED_PIN);
delay_ms(1000);
UART1_DR = *ptr++;
delay_ms(500);
}
}