fix boot2.s

This commit is contained in:
2025-12-20 19:16:13 +08:00
parent 397033e293
commit 29edad3437
4 changed files with 37 additions and 628 deletions

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 -- -- 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
;; 03D0|4810 a6 0d c7 52 32 c7 52 35 c7 52 31 4f 5f 5c 27 0b
;; 03E0|4820 72 0b 52 30 f8 3b 52 31 4c 20 f1 4d 27 09 96 5c
;; 03F0|4830 5c 5c 13 01 26 01 81 5f fe 94 72 cc 48 3e 80 04
;; UART1 register address definitions
UART1_SR = 0x5230 ; Status register
@@ -33,14 +33,12 @@ _boot_start:
;; Send BREAK signal and version number $0D
ld UART1_DR, A ; [C7 52 31] Send version number 0x0D
;; Receive 128-byte data block and push onto stack
_boot_rx_block:
ld A, #0x80 ; [A6 80]
clr A
_boot_rx_byte:
clrw X ; [5F] Reset X (for timeout detection)
_boot_rx_wait:
incw X ; [5C] Increment X, check for overflow (timeout detection)
jreq _boot_exit ; [27 14] If X overflows (receive timeout), exit
jreq _boot_timeout ; [27 14] If X overflows (receive timeout), exit
;; Wait for data reception
btjf UART1_SR, #5, _boot_rx_wait ;[72 0B 52 30 F8]
@@ -48,23 +46,24 @@ _boot_rx_wait:
;; Data received, push onto stack
push UART1_DR ; [3B 52 31]
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
jra _boot_rx_byte ; [26 F1] If A is not 0, continue receiving
_boot_timeout:
tnz A
jreq _boot_exit
;; Check address
ldw X, SP ; [96]
incw X ; [5c]
incw X ; [5c]
incw X ; [5c]
cpw X, (1, SP) ; [13 01]
jrne _boot_rx_block ; [26 E7]
jrne _boot_exit ; [26 E7]
;; Reception complete, jump to received code
ret ; [81] Jump to address at top of stack via ret instruction
_boot_exit:
;; Timeout exit, jump to user program
clrw X
ldw X, (X) ; [FE]
ldw SP, X ; [94]
jp [_boot_go_adr] ; [72 CC 48 3E] Indirect jump

View File

@@ -10,7 +10,7 @@
; 字节69: 校验和 (所有字节XOR)
; ================================================
BOOT2_SP = 0x03CF ; start of boot1 ram address
BOOT2_SP = 0x03CD ; start of boot1 ram address
;; Register address definitions
UART1_SR = 0x5230 ; Status register
@@ -31,9 +31,7 @@ WWDG_CR = 0x50D1 ; WWDG control register
;; Const vars
CMD_READ = 0xF1 ; 读内存命令
CMD_WRITE = 0xF2 ; 写内存命令
CMD_ERASE = 0xF3 ; 整片擦除命令
CMD_RESET = 0xF4 ; 复位命令
CMD_GO = 0xF5 ; 跳转执行命令
CMD_GO = 0xF3 ; 跳转执行命令
CMD_HEADER = 0x5A ; 帧头
ACK_HEADER = 0xA5 ; 应答帧头
@@ -59,7 +57,7 @@ temp_var1 = 77 ; 临时变量
temp_var2 = 78 ; 临时变量
temp_var3 = 79 ; 临时变量
;; Bootloader body (load in ram ?-0x03D2)
;; Bootloader body (load in ram ?-0x03D0)
.area RAM_BOOT
.db (BOOT2_SP-(_end-_start)+1)>>8
@@ -67,16 +65,15 @@ temp_var3 = 79 ; 临时变量
_start:
; 配置UART1: 128000波特率, 8N1, 启用TX/RX
mov UART1_BRR2, #0
mov UART1_BRR1, #1
mov UART1_CR2, #0x0C ; TEN=1, REN=1
;mov UART1_BRR2, #0
;mov UART1_CR2, #0x0C ; TEN=1, REN=1
_main_loop:
; 接收命令帧
call receive_frame
callr receive_frame
; 验证校验和
call verify_checksum
callr verify_checksum
jrne _checksum_error
; 根据命令类型跳转
@@ -88,12 +85,6 @@ _main_loop:
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
@@ -119,19 +110,10 @@ _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
ldw X, rx_buffer+2
jp (X)
; 注意: jump_to_address 不返回
receive_frame:
@@ -252,7 +234,7 @@ send_ack_state_response:
ld A, tx_state
ld (X), A
call send_response_pkg
callr send_response_pkg
ret
; 发送应答数据帧
@@ -269,7 +251,7 @@ send_ack_data_response:
; already set data
call send_response_pkg
callr send_response_pkg
ret
read_memory:
@@ -296,7 +278,7 @@ _read_loop:
dec temp_var1
jrne _read_loop
call send_ack_data_response
callr send_ack_data_response
ret
; temp_var1: 待写入长度
@@ -337,12 +319,12 @@ _mem_write:
incw Y
dec temp_var1
jrne _mem_write
call send_ack_state_response
callr send_ack_state_response
ret
_flash_write:
; unlock FLASH/DATA
call unlock_flash
callr unlock_flash
; Word Program
mov FLASH_CR2, #0xC0
@@ -381,7 +363,7 @@ _write_end:
mov FLASH_CR2, #0x00
mov FLASH_NCR2, #0xFF
; lock FLASH/DATA
call lock_flash
callr lock_flash
call send_ack_state_response
ret
@@ -411,54 +393,4 @@ _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

@@ -1,523 +0,0 @@
#!/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,13 +9,14 @@ void main(void) {
PB_DDR |= (1 << LED_PIN);
PB_CR1 |= (1 << LED_PIN);
/* 9600bps, 8N1, TEN, REN */
UART1_BRR1 = 0x0D;
UART1_BRR2 = 0x00;
UART1_CR2 = 0x0D;
UART1_CR2 = 0x0C;
while(1) {
PB_ODR ^= (1 << LED_PIN);
UART1_DR = *ptr++;
UART1_DR = PB_ODR;
delay_ms(500);
}
}