backup codes
This commit is contained in:
2
Makefile
2
Makefile
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
@@ -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
464
scripts/boot2.s
Normal 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:
|
||||
523
scripts/stm8_bootloader_test.py
Normal file
523
scripts/stm8_bootloader_test.py
Normal 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()
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user