BootLoader-蓝牙(远程串口)
2025/10/20大约 5 分钟
BootLoader-蓝牙(远程串口)
注意事项:HC-05的VCC接3V3,实测5V无法使用
蓝牙模块只有在通信状态才会显示已连接蓝牙,打不开蓝牙串口参考:HC05和电脑蓝牙通讯
HC05-python-stm32的波特率必须都设置为同样的速率,我这里为9600
蓝牙模块讲解
总结:当成远程串口用就行
常用AT指令
波特率为38400
| 指令 | 返回 | 意义 |
|---|---|---|
| AT | ok | 检测固件是否可用 |
| AT+RESET | ok | 模块软件复位 |
| AT+VERSION? | +VERSION:<Param> OK | 获取固件版本 |
| AT+NAME=name | ok | 设置模块名字 |
| AT+NAME? | +NAME:<Param> OK | 返回模块名字 |
| AT+PSWD="passwd" | OK | 设置模块密码 |
| AT+PSWD? | +PSWD:<Param> OK | 获取模块密码 |
| AT+UART=115200,0,0 | ok | 设置通信时的波特率 |
| AT+UART? | +UART:<Param1>,<Param2>,<Param3> OK | 获取串口参数 |
模块指示灯标志
- 定时闪两下:已经连接蓝牙,处于通信状态
- 快闪:未连接
- 慢闪:工作模式,可使用AT指令调整参数(进入:保持按键按下,通电后松开按键即可)
测试流程
/file-20251011201852607.png)
- 先进入工作模式,使用
AT检查是否可用, - 设置模块名字:
AT+NAME=MyBluetooth - 获取密码:
AT+PSWD? - 查看波特率:
AT+UART?
嵌入式Flash操作
void Flash_Erase_App(void)
{
FLASH_EraseInitTypeDef EraseInit;
uint32_t PageError;
// 解除Flash保护
HAL_FLASH_Unlock();
// F407: sector2(扇区2) = 0x08008000 开始
EraseInit.TypeErase = FLASH_TYPEERASE_SECTORS;
EraseInit.Sector = FLASH_SECTOR_2;
EraseInit.NbSectors = 6; // 到 sector7(扇区7)
EraseInit.VoltageRange = FLASH_VOLTAGE_RANGE_3;
if (HAL_FLASHEx_Erase(&EraseInit, &PageError) != HAL_OK) {
// 错误处理
}
// 开启保护
HAL_FLASH_Lock();
}
HAL_StatusTypeDef Flash_Write(uint32_t addr, uint8_t *data, uint32_t len)
{
HAL_StatusTypeDef status;
HAL_FLASH_Unlock();
for(uint32_t i=0; i<len; i+=4) {
uint32_t word = *(uint32_t*)(data+i);
status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, addr+i, word);
if(status != HAL_OK) break;
}
HAL_FLASH_Lock();
return status;
}主要逻辑
/file-20251018141930738.png)
固件发送与接收
/file-20251011211739002.png)
Bootloader代码逻辑
void Bootloader_Update(void)
{
uint8_t cmd;
uint8_t buf[256];
uint32_t addr = APP_ADDR;
Flash_Erase_App();
while(1)
{
HAL_UART_Receive(&huart1, &cmd, 1, HAL_MAX_DELAY);
if(cmd == CMD_DATA) {
uint16_t len;
HAL_UART_Receive(&huart1, (uint8_t*)&len, 2, HAL_MAX_DELAY);
HAL_UART_Receive(&huart1, buf, len, HAL_MAX_DELAY);
Flash_Write(addr, buf, len);
addr += len;
HAL_UART_Transmit(&huart1, (uint8_t*)"OK", 2, HAL_MAX_DELAY);
}
else if(cmd == CMD_END) {
HAL_UART_Transmit(&huart1, (uint8_t*)"DONE", 4, HAL_MAX_DELAY);
break;
}
}
}电脑_python--发送
python要先安装pyserial跑:pip install pyserial
固件采用分批的形式发送,使用python脚本进行发送ser = serial.Serial("COM4", 9600)这里的COM4需要改成你的com端口
import serial
import struct
# 初始化一个串口对象
ser = serial.Serial("COM4", 9600)
def send_data(data):
ser.write(b"\x02") # CMD_DATA
# 把当前数据块的长度打包成两个字节的无符号整数(小端序)。
# 比如 len(data) = 256,那么结果是 b'\x00\x01',开发板端可以据此知道这包数据有多长。
ser.write(struct.pack("<H", len(data)))
ser.write(data)
# 阻塞读取开发板发送的信息,确保是否传递成功
print(ser.read(2)) # OK
with open("app.bin", "rb") as f:
while True:
# 每次循环读取256个数据
chunk = f.read(256)
if not chunk:
break
send_data(chunk)
ser.write(b"\x03") # CMD_END
print(ser.read(4)) # DONEstm32_蓝牙--接收
#define APP_ADDR 0x08008000
#define CMD_START 0x01
#define CMD_DATA 0x02
#define CMD_END 0x03
void Bootloader_Update(void)
{
uint8_t cmd;
uint8_t buf[256];
uint32_t addr = APP_ADDR;
// 擦除老APP,准备迎接新王
Flash_Erase_App();
while(1)
{
// 堵塞接收,根据先接收到的命令决定怎么做
HAL_UART_Receive(&huart1, &cmd, 1, HAL_MAX_DELAY);
// 数据命令,表示接收到的是数据
if(cmd == CMD_DATA) {
uint16_t len;
// 获取长度
HAL_UART_Receive(&huart1, (uint8_t*)&len, 2, HAL_MAX_DELAY);
// 获取内容
HAL_UART_Receive(&huart1, buf, len, HAL_MAX_DELAY);
// 写入内容
Flash_Write(addr, buf, len);
// 位移地址,准备写入后续内容
addr += len;
// 回应OK
HAL_UART_Transmit(&huart1, (uint8_t*)"OK", 2, HAL_MAX_DELAY);
}
// 结束命令,表示接收完成,退出该函数
else if(cmd == CMD_END) {
HAL_UART_Transmit(&huart1, (uint8_t*)"DONE", 4, HAL_MAX_DELAY);
break;
}
}
}增加校验-CRC32
一、CRC32 是什么
CRC 全称 Cyclic Redundancy Check(循环冗余校验)。CRC32 表示它使用的是 32位的多项式运算结果。
它不是“加法求和”那种简单校验,而是用一种二进制多项式除法的思想:
- 把整个数据看作一个很长的二进制数;
- 用一个固定的“生成多项式”去做模2除法;
- 最后得到一个 32 位的余数;
- 这个余数就是 CRC 校验码。
🧠 模2除法:相当于普通除法,但所有加减都用“异或(XOR)”。
二、CRC32 的作用
CRC32 不是用来“防黑客”或“加密”的,而是为了 检测传输或存储过程中的错误。
例如:
- 你通过串口发送 100KB 的固件;
- 中途某一字节丢了、翻转了;
- STM32 收到的文件计算出的 CRC 不等于原始文件的 CRC;
- → 说明传输错误,不执行更新。
可以检测出几乎所有单比特错误、突发错误,非常可靠。
电脑端-python增加校验
import serial
import struct
import binascii
from tqdm import tqdm # pip install tqdm
# 打开串口
ser = serial.Serial("COM4", 9600, timeout=2)
def send_data(data):
# 发送数据包
ser.write(b"\x02") # CMD_DATA
ser.write(struct.pack("<H", len(data))) # 长度
ser.write(data) # 数据本体
resp = ser.read(2) # Bootloader 回复 OK
if resp != b"OK":
raise Exception("Bootloader写入失败,返回: %s" % resp)
def send_end(crc32, total_size):
# 通知 Bootloader 固件结束 + CRC
ser.write(b"\x03") # CMD_END
ser.write(struct.pack("<I", crc32)) # 4字节 CRC32
ser.write(struct.pack("<I", total_size)) # 固件总长度(可选)
resp = ser.read(4)
if resp != b"DONE":
raise Exception("Bootloader校验失败,返回: %s" % resp)
# ---------------- 主流程 -----------------
with open("app.bin", "rb") as f:
data = f.read()
# 计算 CRC32(和 Bootloader 保持一致)
crc32 = binascii.crc32(data) & 0xFFFFFFFF
total_size = len(data)
print(f"固件大小: {total_size} 字节, CRC32 = 0x{crc32:08X}")
# 分块发送(带进度条)
chunk_size = 256
with tqdm(total=total_size, unit="B", unit_scale=True, desc="升级中") as pbar:
for i in range(0, total_size, chunk_size):
chunk = data[i:i+chunk_size]
send_data(chunk)
pbar.update(len(chunk))
# 通知结束
send_end(crc32, total_size)
print("固件升级完成 ✅")stm32端-增加校验
在CRC校验算法中,数据被视为一个巨大的二进制数,代表一个多项式的系数。生成多项式(CRC32_POLY)就是用来与这个数据多项式进行“模2除法”运算的除数,最终得到的余数就是CRC校验值。
// 多项式 (Ethernet, ZIP, etc. 通用): 0x04C11DB7
#define CRC32_POLY 0xEDB88320UL
uint32_t crc32_calc(uint8_t *data, uint32_t length) {
uint32_t crc = 0xFFFFFFFFUL;
for (uint32_t i = 0; i < length; i++) {
crc ^= data[i];
for (uint8_t j = 0; j < 8; j++) {
if (crc & 1) {
crc = (crc >> 1) ^ CRC32_POLY;
} else {
crc >>= 1;
}
}
}
return ~crc;
}bootloader逻辑改变
#define APP_ADDR 0x08008000
#define CMD_START 0x01
#define CMD_DATA 0x02
#define CMD_END 0x03
void Bootloader_Update(void)
{
uint8_t cmd;
uint8_t buf[256];
uint32_t addr = APP_ADDR;
Flash_Erase_App();
while(1)
{
HAL_UART_Receive(&huart1, &cmd, 1, HAL_MAX_DELAY);
if(cmd == CMD_DATA) {
uint16_t len;
HAL_UART_Receive(&huart1, (uint8_t*)&len, 2, HAL_MAX_DELAY);
HAL_UART_Receive(&huart1, buf, len, HAL_MAX_DELAY);
Flash_Write(addr, buf, len);
addr += len;
HAL_UART_Transmit(&huart1, (uint8_t*)"OK", 2, HAL_MAX_DELAY);
}
else if(cmd == CMD_END) {
uint32_t host_crc, host_size;
HAL_UART_Receive(&huart1, (uint8_t*)&host_crc, 4, HAL_MAX_DELAY);
HAL_UART_Receive(&huart1, (uint8_t*)&host_size, 4, HAL_MAX_DELAY);
// 自己计算 CRC32
uint32_t calc_crc = crc32_calc((uint8_t*)APP_ADDR, host_size);
if (calc_crc == host_crc) {
HAL_UART_Transmit(&huart1, (uint8_t*)"DONE", 4, HAL_MAX_DELAY);
} else {
HAL_UART_Transmit(&huart1, (uint8_t*)"FAIL", 4, HAL_MAX_DELAY);
}
break;
}
}
}