核心目标:掌握 STM32 低功耗模式配置(解决电池供电设备续航问题),实现 OTA(Over-The-Air)远程固件升级(解决设备现场更新难题)—— 这两项是嵌入式产品从 “实验室原型” 走向 “商用化” 的核心技能,直接影响产品实用性和维护成本。
一、STM32 低功耗模式:让设备 “省着用电”(40 分钟)
1. 为什么低功耗是刚需?
- 嵌入式设备(如无线传感器、智能手环、遥控器)常依赖电池供电,功耗直接决定续航(比如 100mAh 电池,1mA 功耗能用 100 小时,0.1mA 就能用 1000 小时);
- STM32 默认运行在 “全速模式”(功耗约 50-100mA),远高于电池供电需求,必须通过低功耗模式降低功耗。
2. STM32 三大低功耗模式(重点掌握前两种)
| 模式 | 核心特点(以 STM32F103 为例) | 典型功耗 | 唤醒源 | 适用场景 |
|---|---|---|---|---|
| 睡眠模式 | CPU 停止,外设(定时器、串口)正常运行,SRAM 数据保留 | 1-5mA | 任何外设中断(如定时器、串口) | 需要外设定时工作(如 1 分钟采集一次数据) |
| 停止模式 | CPU 和外设时钟关闭,SRAM 和寄存器数据保留,功耗极低 | 10-50μA | 外部中断(按键、传感器)、RTC | 长时间休眠,需快速唤醒(如按键唤醒) |
| 待机模式 | 几乎所有电路关闭,仅保留唤醒电路,SRAM 数据丢失 | <1μA | WKUP 引脚、RTC 闹钟 | 深度休眠(如年功耗设备) |
3. 停止模式(Stop Mode)配置实战(最常用)
目标:设备在无操作时进入停止模式(功耗≈30μA),通过按键(PA0)唤醒,唤醒后执行 1 秒任务再重新休眠。
步骤 1:CubeIDE 配置低功耗相关外设
- 配置唤醒源:PA0 设为 “GPIO_EXTI0”(外部中断 0),触发方式 “Falling Edge”(下降沿,按键按下时接地触发);
- 配置 RTC(可选,用于定时唤醒):启用 RTC 时钟,配置闹钟中断(如每 10 秒唤醒一次);
- 关闭 unused 外设:禁用未使用的 GPIO、定时器、串口(避免额外功耗)。
步骤 2:编写低功耗进入与唤醒代码
在main.c中添加低功耗控制函数:
c
/* USER CODE BEGIN 0 */
#include "stm32f1xx_hal_pwr.h"
// 进入停止模式
void EnterStopMode(void) {
// 1. 关闭所有未使用的外设时钟(关键!)
__HAL_RCC_GPIOA_CLK_DISABLE(); // 仅保留唤醒引脚所在GPIO时钟
__HAL_RCC_GPIOB_CLK_DISABLE();
// ...(根据实际使用情况关闭其他外设时钟)
// 2. 配置PWR(电源管理)模块
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
// 说明:WFI=Wait For Interrupt,CPU等待中断唤醒;LOWPOWERREGULATOR=低功耗稳压器
// 3. 唤醒后需要重新配置系统时钟(停止模式会关闭高速时钟)
SystemClock_Config(); // 复用工程自动生成的时钟配置函数
}
// 外部中断回调(按键唤醒处理)
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
if (GPIO_Pin == GPIO_PIN_0) { // PA0按键唤醒
// 唤醒后执行的短任务(如点亮LED 1秒)
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET);
HAL_Delay(1000);
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET);
}
}
/* USER CODE END 0 */
步骤 3:主循环逻辑(休眠 - 唤醒循环)
c
int main(void) {
HAL_Init();
SystemClock_Config();
MX_GPIO_Init(); // 仅初始化必要的GPIO(唤醒引脚、LED)
while (1) {
// 执行完必要任务后,进入停止模式
EnterStopMode();
// 唤醒后会从SystemClock_Config()后的代码继续执行,再次进入循环
}
}
测试效果
- 用万用表测量电流:全速运行时约 50mA,进入停止模式后降至 30μA 左右(降低约 1600 倍);
- 按下 PA0 按键,LED 点亮 1 秒后熄灭,设备重新进入休眠。
二、OTA 升级:远程更新固件(50 分钟)
1. OTA 核心原理
OTA(空中下载技术)允许设备通过串口、WiFi、蓝牙等通信方式接收新固件,无需物理连接(如拆壳插线)即可更新程序。核心流程:
- 分区 Flash:将 STM32 的 Flash 划分为 3 个区域:
- Bootloader 区(启动程序,约 8KB):负责检查是否有新固件,引导执行新固件;
- App 区(应用程序,约 64KB):存放当前运行的固件;
- Flag 区(标志位,约 1KB):记录是否有新固件待更新(如 “0xAA55” 表示需要更新)。
- 传输新固件:设备通过串口 / WiFi 接收新固件数据,写入 App 区;
- 启动更新:设置 Flag 区标志,重启设备;
- Bootloader 引导:设备重启后先运行 Bootloader,检测到标志位后,将 App 区的新固件复制到运行区,完成更新。
2. Bootloader 程序设计(关键)
Bootloader 是 OTA 的 “引导者”,必须先烧录到 STM32 的起始地址(0x08000000)。
步骤 1:CubeIDE 配置 Bootloader 工程
- 设置 Flash 起始地址:0x08000000,大小:0x2000(8KB,足够简单 Bootloader);
- 配置串口(USART1,用于接收固件数据)和 GPIO(LED 指示状态)。
步骤 2:Bootloader 核心代码
c
/* Bootloader主逻辑 */
int main(void) {
HAL_Init();
SystemClock_Config();
MX_USART1_UART_Init();
MX_GPIO_Init();
// 1. 检查Flag区是否有更新标志(假设Flag区地址:0x0800F000)
uint32_t *flag_addr = (uint32_t*)0x0800F000;
if (*flag_addr == 0xAA55) { // 需要更新
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET); // LED亮表示更新中
// 2. 跳转到App区执行新固件(App区起始地址:0x08002000)
void (*AppFunction)(void) = (void (*)(void))(*((uint32_t*)(0x08002000 + 4)));
// 配置栈指针(新固件的栈顶地址存放在0x08002000)
__set_MSP(*(uint32_t*)0x08002000);
AppFunction(); // 跳转执行新固件
} else {
// 无更新,直接跳转到当前App
void (*AppFunction)(void) = (void (*)(void))(*((uint32_t*)(0x08002000 + 4)));
__set_MSP(*(uint32_t*)0x08002000);
AppFunction();
}
while (1) {}
}
3. App 程序设计(接收新固件)
App 程序负责接收新固件数据并写入 Flash,设置更新标志。
步骤 1:CubeIDE 配置 App 工程
- 设置 Flash 起始地址:0x08002000(避开 Bootloader 区),大小:0xFA00(约 63KB);
- 配置串口(与 Bootloader 同串口),用于接收电脑发送的新固件。
步骤 2:App 核心代码(接收固件并更新)
c
/* USER CODE BEGIN 0 */
#include "stm32f1xx_hal_flash.h"
// Flash擦除函数(擦除App区)
void Flash_EraseAppArea(void) {
FLASH_EraseInitTypeDef erase_init;
uint32_t page_error = 0;
erase_init.TypeErase = FLASH_TYPEERASE_PAGES;
erase_init.PageAddress = 0x08002000; // App区起始页
erase_init.NbPages = 31; // 擦除31页(63KB)
HAL_FLASH_Unlock(); // 解锁Flash
HAL_FLASHEx_Erase(&erase_init, &page_error);
HAL_FLASH_Lock(); // 锁定Flash
}
// Flash写入函数(写入固件数据)
void Flash_WriteData(uint32_t addr, uint8_t *data, uint32_t len) {
HAL_FLASH_Unlock();
for (uint32_t i = 0; i < len; i += 4) { // Flash按4字节写入
HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, addr + i, *(uint32_t*)(data + i));
}
HAL_FLASH_Lock();
}
/* USER CODE END 0 */
// 主循环接收固件
int main(void) {
HAL_Init();
SystemClock_Config();
MX_USART1_UART_Init();
MX_GPIO_Init();
uint8_t firmware_buf[1024]; // 固件接收缓冲区
uint32_t firmware_len = 0;
uint32_t app_addr = 0x08002000; // App区起始地址
// 1. 擦除旧App区
Flash_EraseAppArea();
// 2. 接收新固件(假设电脑先发送固件长度,再发送数据)
HAL_UART_Receive(&huart1, (uint8_t*)&firmware_len, 4, 10000); // 接收长度
for (uint32_t i = 0; i < firmware_len; i += 1024) {
uint16_t recv_len = (firmware_len - i) > 1024 ? 1024 : (firmware_len - i);
HAL_UART_Receive(&huart1, firmware_buf, recv_len, 10000); // 接收数据
Flash_WriteData(app_addr + i, firmware_buf, recv_len); // 写入Flash
}
// 3. 设置更新标志,重启设备
uint32_t *flag_addr = (uint32_t*)0x0800F000;
HAL_FLASH_Unlock();
HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, (uint32_t)flag_addr, 0xAA55); // 写标志
HAL_FLASH_Lock();
HAL_NVIC_SystemReset(); // 重启,触发Bootloader更新
}
4. OTA 测试流程
- 烧录 Bootloader 程序到 0x08000000;
- 烧录旧 App 程序到 0x08002000;
- 电脑通过串口助手发送新 App 固件(先发送长度,再发送.bin 文件数据);
- 设备接收完成后重启,Bootloader 检测到标志位,引导新固件运行 ——OTA 升级完成。
三、第十一天必掌握的 2 个核心技能
- 低功耗优化:能配置停止模式,通过外部中断 / RTC 唤醒,理解 “关闭 unused 外设时钟” 是降低功耗的关键;
- OTA 原理:掌握 Flash 分区(Bootloader+App+Flag)、Bootloader 跳转逻辑、App 接收固件并写入 Flash 的流程。
总结
今天的技能直接面向 “产品化”:低功耗解决续航痛点(让电池设备用得更久),OTA 解决维护难题(无需现场拆机更新)。这两项技能在物联网设备、可穿戴设备、工业传感器中应用广泛。

726

被折叠的 条评论
为什么被折叠?



