diff --git a/Makefile b/Makefile index 614b55f..a69f4c3 100644 --- a/Makefile +++ b/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) diff --git a/bsp/init0.c b/bsp/boot0.c similarity index 93% rename from bsp/init0.c rename to bsp/boot0.c index 8809a46..6aa5f66 100644 --- a/bsp/init0.c +++ b/bsp/boot0.c @@ -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: diff --git a/scripts/bootloader.opt b/scripts/boot1.opt similarity index 81% rename from scripts/bootloader.opt rename to scripts/boot1.opt index c853e2f..57421ce 100644 --- a/scripts/bootloader.opt +++ b/scripts/boot1.opt @@ -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: diff --git a/scripts/boot2.s b/scripts/boot2.s new file mode 100644 index 0000000..dc8d17c --- /dev/null +++ b/scripts/boot2.s @@ -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: diff --git a/scripts/stm8_bootloader_test.py b/scripts/stm8_bootloader_test.py new file mode 100644 index 0000000..59c439a --- /dev/null +++ b/scripts/stm8_bootloader_test.py @@ -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 读取内存") + print(f"{Fore.YELLOW} write 写入内存") + print(f"{Fore.YELLOW} erase 擦除Flash") + print(f"{Fore.YELLOW} reset 复位MCU") + print(f"{Fore.YELLOW} go 跳转到指定地址执行") + print(f"{Fore.YELLOW} echo 回显文本") + 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() diff --git a/src/main.c b/src/main.c index 10c162a..941dddc 100644 --- a/src/main.c +++ b/src/main.c @@ -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); } }