前言
做无人机的都知道,续航是永远的痛。电池就那么大,电机一转电量哗哗掉。很多人把注意力全放在电机和桨叶上,却忽略了飞控主控本身也在持续耗电。别小看这几十毫安的电流,在微型无人机上,主控功耗能占到整机待机功耗的一半以上。
本文以最常用的 STM32F103C8T6为主控,从低功耗的基本概念讲起,到三种低功耗模式的原理,再到无人机场景下的具体代码实现,一步步把飞控的功耗压下去。
一、什么是低功耗?为什么无人机需要它?
1.1 低功耗到底是什么
低功耗不是简单的 "少用电",而是在保证系统功能和响应速度的前提下,通过硬件设计和软件调度,让芯片在不需要全速运行的时候进入省电状态,需要的时候又能快速唤醒。
对 STM32F103C8T6来说,就是合理利用它的三种低功耗模式 ------ 睡眠、停机、待机,配合外设时钟门控,把平均电流降下来。
1.2 无人机低功耗的实际作用
- 延长待机时间:无人机开机后不一定马上起飞,地面等待阶段主控如果一直满速跑,电池白白消耗。进入低功耗后,待机电流可以从几十毫安降到微安级。
- 给动力系统留更多电量:主控省下来的电,就是电机能多飞的时间。
- 降低发热:芯片长时间运行会发热,高温影响 IMU 等传感器精度,低功耗模式能减少发热。
- 适应微型无人机:微型无人机电池容量只有几百毫安时,主控每省 10mA,可能就多飞几十秒。
二、STM32F103C8T6 的三种低功耗模式
STM32F103 系列有三种低功耗模式,功耗从高到低依次是:睡眠模式 > 停机模式 > 待机模式。
表格
| 模式 | 核心 | 时钟 | SRAM / 寄存器 | 唤醒方式 | 典型电流 |
|---|---|---|---|---|---|
| 睡眠 (Sleep) | 停止 | 外设继续 | 保持 | 任意中断 | ~10mA |
| 停机 (Stop) | 停止 | 全部停止 | 保持 | 外部中断 / RTC | ~20μA |
| 待机 (Standby) | 停止 | 全部停止 | 丢失 (仅备份寄存器) | WKUP 引脚 / RTC | ~2μA |
2.1 睡眠模式(Sleep Mode)
最简单的低功耗模式。CPU 内核停止运行,但所有外设(GPIO 、USART 、SPI 、I2C 、定时器等)继续工作,SRAM和寄存器内容全部保留。
特点:
- 进入和唤醒最快,唤醒后直接从断点继续执行;
- 任意中断都能唤醒;
- 适合短时间、高频次的省电场景。
无人机应用场景:飞控在两次姿态解算之间的空闲期,可以短暂进入睡眠,由定时器中断唤醒。
2.2 停机模式(Stop Mode)
所有 1.8V 区域的时钟停止,PLL 、HSI 、HSE 振荡器被关闭,但 SRAM和寄存器内容保持不变。唤醒后需要重新配置系统时钟。
特点:
- 功耗比睡眠低很多(微安级);
- 唤醒后程序从停止处继续,但要重新初始化时钟;
- 只能通过外部中断(EXTI)或 RTC 闹钟唤醒。
无人机应用场景:地面待机、任务间歇期,需要保留飞控参数和传感器配置时使用。
2.3 待机模式(Standby Mode)
功耗最低的模式,整个 1.8V 区域断电,PLL 、HSI 、HSE 全部关闭,SRAM和寄存器内容丢失,只有备份寄存器和待机电路保持供电。
特点:
- 功耗最低(约 2μA);
- 唤醒后相当于系统复位,程序从头开始执行;
- 只能通过 WKUP 引脚(PA0)上升沿、RTC 闹钟、IWDG 复位唤醒。
无人机应用场景:长时间存放、运输过程中,不需要保留运行状态时使用。
三、具体实现代码
3.1 睡眠模式实现
睡眠模式最简单,调用 WFI (Wait For Interrupt)指令即可。
#include "stm32f10x.h"
/**
* @brief 进入睡眠模式
* @note 任意中断唤醒,唤醒后继续执行
*/
void Enter_Sleep_Mode(void)
{
// 使能PWR时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
// 进入睡眠模式:WFI指令
__WFI();
// 唤醒后从这里继续执行
}
在飞控主循环中的典型用法:
int main(void)
{
System_Init(); // 系统初始化
Sensor_Init(); // 传感器初始化
Motor_Init(); // 电机初始化
while(1)
{
if(flight_state == FLYING)
{
Attitude_Calculate(); // 姿态解算
Motor_Control(); // 电机控制
}
else
{
// 未起飞时,进入睡眠,由1ms定时器中断唤醒
Enter_Sleep_Mode();
}
}
}
3.2 停机模式实现
停机模式需要配置 PWR寄存器,唤醒后要重新配置系统时钟。
#include "stm32f10x.h"
/**
* @brief 进入停机模式
* @note 外部中断或RTC唤醒,唤醒后需重新配置时钟
*/
void Enter_Stop_Mode(void)
{
// 使能PWR时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
// 进入停机模式,调节器低功耗模式
PWR_EnterSTOPMode(PWR_Regulator_LowPower, PWR_STOPEntry_WFI);
// ===== 唤醒后从这里继续 =====
// 重新配置系统时钟(因为HSE/PLL已关闭)
SystemInit();
SystemCoreClockUpdate();
}
配置外部中断唤醒(以 PA1引脚为例,接遥控信号):
/**
* @brief 配置EXTI用于唤醒停机模式
*/
void EXTI_Wakeup_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
EXTI_InitTypeDef EXTI_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
// 使能GPIOA和AFIO时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);
// PA1配置为上拉输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 连接EXTI1到PA1
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource1);
// 配置EXTI1为下降沿触发(遥控信号到来时唤醒)
EXTI_InitStructure.EXTI_Line = EXTI_Line1;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
// 配置NVIC
NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
// 中断服务函数
void EXTI1_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line1) != RESET)
{
EXTI_ClearITPendingBit(EXTI_Line1);
// 唤醒后执行的操作
}
}
3.3 待机模式实现
待机模式功耗最低,但唤醒后系统复位。需要配置 WKUP 引脚(PA0 )或 RTC闹钟。
#include "stm32f10x.h"
/**
* @brief 进入待机模式
* @note 唤醒后系统复位,程序从头执行
*/
void Enter_Standby_Mode(void)
{
// 使能PWR和BKP时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);
// 使能对备份区域的访问
PWR_BackupAccessCmd(ENABLE);
// 清除唤醒标志
PWR_ClearFlag(PWR_FLAG_WU);
// 使能WKUP引脚(PA0),上升沿唤醒
PWR_WakeUpPinCmd(ENABLE);
// 进入待机模式
PWR_EnterSTANDBYMode();
// 注意:待机模式唤醒后不会回到这里,而是从复位向量开始执行
}
在主函数中判断是否是待机唤醒:
int main(void)
{
// 检查是否是从待机模式唤醒
if(PWR_GetFlagStatus(PWR_FLAG_SB) != RESET)
{
// 清除待机标志
PWR_ClearFlag(PWR_FLAG_SB);
// 待机唤醒后的特殊处理(比如快速初始化,跳过自检)
Quick_Init();
}
else
{
// 正常上电初始化
Full_Init();
}
while(1)
{
// 主循环
}
}
3.4 RTC 闹钟唤醒停机模式
无人机在地面待机时,可以用 RTC定时唤醒做状态检测,平时处于停机模式。
/**
* @brief 配置RTC闹钟,N秒后唤醒
* @param seconds: 多少秒后唤醒
*/
void RTC_Alarm_Wakeup_Init(uint32_t seconds)
{
// 使能PWR和BKP时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);
// 使能备份区域访问
PWR_BackupAccessCmd(ENABLE);
// 复位备份区域
BKP_DeInit();
// 使能LSE(外部低速晶振32.768kHz)
RCC_LSEConfig(RCC_LSE_ON);
while(RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET);
// 选择LSE作为RTC时钟源
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
RCC_RTCCLKCmd(ENABLE);
// 等待RTC寄存器同步
RTC_WaitForSynchro();
RTC_WaitForLastTask();
// 设置RTC分频:32768 / 32768 = 1Hz
RTC_SetPrescaler(32767);
RTC_WaitForLastTask();
// 设置闹钟值:当前时间 + seconds
RTC_SetAlarm(RTC_GetCounter() + seconds);
RTC_WaitForLastTask();
// 使能RTC闹钟中断
RTC_ITConfig(RTC_IT_ALR, ENABLE);
RTC_WaitForLastTask();
// 配置NVIC
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = RTCAlarm_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
// 配置EXTI17(RTC闹钟连接到EXTI17)
EXTI_InitTypeDef EXTI_InitStructure;
EXTI_InitStructure.EXTI_Line = EXTI_Line17;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
}
// RTC闹钟中断服务函数
void RTCAlarm_IRQHandler(void)
{
if(RTC_GetITStatus(RTC_IT_ALR) != RESET)
{
RTC_ClearITPendingBit(RTC_IT_ALR);
EXTI_ClearITPendingBit(EXTI_Line17);
// 唤醒后处理
}
}
四、无人机系统中的低功耗策略
光会进低功耗模式还不够,要结合无人机的实际工作状态来设计策略。
4.1 状态机驱动的功耗管理
typedef enum {
STATE_POWER_ON, // 刚上电,完整初始化
STATE_STANDBY, // 地面待机,停机模式 + RTC周期唤醒
STATE_READY, // 已解锁,准备起飞,睡眠模式
STATE_FLYING, // 飞行中,全速运行
STATE_LANDING, // 降落中
STATE_LOW_BATTERY // 低电量,强制降功耗
} DroneState_t;
DroneState_t drone_state = STATE_POWER_ON;
void Power_Management(void)
{
switch(drone_state)
{
case STATE_STANDBY:
// 关闭不必要的外设时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, DISABLE); // 关闭SPI
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, DISABLE); // 关闭USART2
// 进入停机模式,RTC 5秒后唤醒检测状态
RTC_Alarm_Wakeup_Init(5);
Enter_Stop_Mode();
// 唤醒后恢复时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
break;
case STATE_READY:
// 已解锁但未起飞,用睡眠模式,保持快速响应
Enter_Sleep_Mode();
break;
case STATE_LOW_BATTERY:
// 低电量:关闭图传、LED等非必要外设
GPIO_ResetBits(GPIOB, GPIO_Pin_5); // 关闭LED
// 限制电机最大功率
Motor_SetMaxPower(60);
break;
default:
break;
}
}
4.2 外设时钟门控
STM32F103的每个外设都有独立的时钟开关,不用的外设一定要关掉时钟,这是最基本也是最有效的省电手段。
/**
* @brief 关闭所有未使用的外设时钟
*/
void Disable_Unused_Peripherals(void)
{
// 关闭APB2上未使用的外设
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIO_UNUSED, DISABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC2, DISABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, DISABLE); // 如果不用SPI
// 关闭APB1上未使用的外设
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, DISABLE); // 如果不用TIM2
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, DISABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, DISABLE); // 如果不用I2C
RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, DISABLE);
}
4.3 GPIO 配置优化
未使用的 GPIO不要悬空,配置成模拟输入可以最大限度降低功耗。
/**
* @brief 将未使用的GPIO配置为模拟输入(最低功耗)
*/
void GPIO_Analog_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
// 使能所有GPIO时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB |
RCC_APB2Periph_GPIOC | RCC_APB2Periph_AFIO, ENABLE);
// 所有引脚配置为模拟输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_All;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; // 模拟输入,无上下拉
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_Init(GPIOC, &GPIO_InitStructure);
// 关闭JTAG,释放PA13/PA14/PA15/PB3/PB4(如果不用JTAG调试)
GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);
}
注意 :配置模拟输入前,先确认哪些引脚已经被外设使用,不要把正在用的引脚也配成模拟输入。实际项目中应该逐个引脚配置,而不是直接**
GPIO_Pin_All**。
五、实测效果参考
以 STM32F103C8T6 最小系统板(外部 8MHz 晶振 + 32.768kHz晶振)为例,不同模式下的实测电流:
表格
| 工作状态 | 电流 | 说明 |
|---|---|---|
| 全速运行(72MHz) | ~30mA | 所有外设开启 |
| 睡眠模式 | ~15mA | CPU 停止,外设运行 |
| 停机模式 | ~25μA | 仅 SRAM 保持 |
| 待机模式 | ~3μA | 仅备份寄存器 |
可以看到,从全速运行到待机模式,电流差了整整 10000 倍。在无人机地面待机场景下,使用停机模式 + RTC 周期唤醒,平均电流可以控制在几十微安,待机时间从几小时延长到几个月。
六、注意事项
- 停机模式唤醒后必须重新配置系统时钟,因为 HSE 和 PLL 都停了,不重新配置的话系统会用内部 8MHz HSI 运行,性能大幅下降。
- 待机模式唤醒相当于复位,SRAM 数据全部丢失,重要参数要存在备份寄存器或 Flash 中。
- 调试接口注意:进入低功耗模式后,SWD/JTAG 调试器可能无法连接,这是正常现象,复位后即可恢复。
- 唤醒源要提前配置好:进入低功耗前必须配置好唤醒中断,否则就真的 "睡死" 过去了,只能硬复位。
- 传感器上电时序:停机模式下传感器也断电了,唤醒后要重新初始化 IMU、GPS 等传感器,不能直接读数据。
总结
STM32F103C8T6 的低功耗设计核心就是三句话:该跑的时候全速跑,该睡的时候彻底睡,外设不用就关时钟。在无人机系统中,结合飞行状态机,在地面待机时用停机模式、准备起飞时用睡眠模式、飞行中用外设时钟门控,就能在不影响飞行性能的前提下,把主控功耗降到最低。
低功耗优化是个细活,需要用万用表实际测量每个模式的电流,找到耗电大户,再针对性优化。希望这篇文章能帮到正在做无人机低功耗设计的朋友。