BLOG8
跳转至

低功耗设计技术入门

学习目标

完成本教程学习后,你将能够:

  • 理解低功耗设计的基本原理和重要性
  • 掌握MCU的各种睡眠模式及其应用场景
  • 学会使用时钟门控技术降低功耗
  • 能够正确关闭和管理外设以节省功耗
  • 掌握功耗测量的方法和工具
  • 能够制定和实施功耗优化策略
  • 完成一个实际的低功耗应用项目

前置要求

在开始本教程学习之前,你需要:

知识要求: - 了解嵌入式系统电源设计基础 - 熟悉MCU的基本工作原理 - 了解时钟系统和中断机制 - 掌握基本的C语言编程

技能要求: - 能够使用开发环境编译和调试程序 - 会使用万用表测量电压和电流 - 了解示波器的基本使用方法

硬件要求: - STM32开发板(或其他MCU开发板) - USB转串口模块 - 万用表或电流表 - 面包板和杜邦线

推荐但非必需: - 功率分析仪或电流探头 - 逻辑分析仪 - 有电池供电设备的使用经验

概述

低功耗设计是现代嵌入式系统,特别是电池供电设备的核心技术。随着物联网、可穿戴设备和移动设备的普及,低功耗设计变得越来越重要。

为什么需要低功耗设计

  1. 延长电池寿命:
  2. 电池供电设备的核心需求
  3. 减少充电频率,提升用户体验
  4. 降低维护成本(如无线传感器网络)

  5. 降低发热:

  6. 功耗直接转化为热量
  7. 减少散热需求
  8. 提高系统可靠性

  9. 环保节能:

  10. 降低能源消耗
  11. 减少碳排放
  12. 符合绿色环保要求

  13. 降低成本:

  14. 使用更小容量的电池
  15. 减少散热器件
  16. 降低电源系统成本

低功耗设计的基本原则

低功耗设计三大原则:
1. 不用的就关掉(Turn off what you don't use)
2. 降低工作频率(Slow down when possible)
3. 降低工作电压(Lower voltage when possible)

功耗公式:
P = C × V² × f
其中:
- P:功耗
- C:负载电容
- V:工作电压
- f:工作频率

可见:
- 电压降低一半,功耗降低75%
- 频率降低一半,功耗降低50%

第一部分:睡眠模式详解

什么是睡眠模式

睡眠模式是MCU的一种低功耗工作状态,通过关闭部分或全部功能模块来降低功耗。不同的睡眠模式提供不同程度的功耗降低和唤醒速度。

STM32的睡眠模式

STM32系列MCU提供三种主要的低功耗模式:

1. 睡眠模式(Sleep Mode)

特点: - CPU停止运行 - 所有外设继续运行 - 所有寄存器和SRAM内容保持 - 唤醒速度最快

功耗: - 典型值:几mA(取决于外设) - 相比运行模式降低约30-50%

适用场景: - 需要快速响应的应用 - 外设需要持续工作 - 短时间等待

进入方式:

/**
 * @brief  进入睡眠模式
 * @note   使用WFI(Wait For Interrupt)指令
 */
void enter_sleep_mode(void) {
    printf("进入睡眠模式...\n");

    // 配置唤醒源(如外部中断、定时器等)
    // 这里以EXTI为例
    NVIC_EnableIRQ(EXTI0_IRQn);

    // 进入睡眠模式
    __WFI();  // Wait For Interrupt

    // 被中断唤醒后,从这里继续执行
    printf("从睡眠模式唤醒!\n");
}

/**
 * @brief  外部中断处理函数(唤醒源)
 */
void EXTI0_IRQHandler(void) {
    if (EXTI_GetITStatus(EXTI_Line0) != RESET) {
        // 清除中断标志
        EXTI_ClearITPendingBit(EXTI_Line0);

        printf("外部中断触发,唤醒系统\n");
    }
}

2. 停止模式(Stop Mode)

特点: - CPU和大部分外设停止 - 1.2V域的时钟停止 - RTC、IWDG、唤醒单元继续工作 - 所有寄存器和SRAM内容保持 - 唤醒后需要重新配置时钟

功耗: - 典型值:几十μA到几百μA - 相比运行模式降低99%以上

适用场景: - 长时间等待外部事件 - 需要保持数据 - 对唤醒时间要求不严格(ms级)

进入方式:

/**
 * @brief  进入停止模式
 * @param  regulator: 稳压器模式
 *         @arg PWR_Regulator_ON: 稳压器开启
 *         @arg PWR_Regulator_LowPower: 稳压器低功耗模式
 */
void enter_stop_mode(uint32_t regulator) {
    printf("进入停止模式...\n");

    // 1. 配置唤醒源
    // 使用RTC闹钟唤醒(5秒后)
    RTC_AlarmTypeDef alarm;
    alarm.AlarmTime.Hours = 0;
    alarm.AlarmTime.Minutes = 0;
    alarm.AlarmTime.Seconds = 5;
    HAL_RTC_SetAlarm_IT(&hrtc, &alarm, RTC_FORMAT_BIN);

    // 2. 清除唤醒标志
    __HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);

    // 3. 进入停止模式
    HAL_PWR_EnterSTOPMode(regulator, PWR_STOPENTRY_WFI);

    // 4. 唤醒后重新配置系统时钟
    SystemClock_Config();

    printf("从停止模式唤醒!\n");
}

/**
 * @brief  RTC闹钟中断处理函数
 */
void RTC_Alarm_IRQHandler(void) {
    HAL_RTC_AlarmIRQHandler(&hrtc);
}

/**
 * @brief  RTC闹钟回调函数
 */
void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc) {
    printf("RTC闹钟触发,唤醒系统\n");
}

3. 待机模式(Standby Mode)

特点: - 1.2V域断电 - 只有备份域和待机电路工作 - SRAM和寄存器内容丢失(除备份寄存器) - 功耗最低 - 唤醒后相当于复位

功耗: - 典型值:几μA - 相比运行模式降低99.9%以上

适用场景: - 长时间不工作 - 不需要保持数据(或使用备份寄存器) - 对功耗要求极高

进入方式:

/**
 * @brief  进入待机模式
 */
void enter_standby_mode(void) {
    printf("进入待机模式...\n");
    HAL_Delay(100);  // 等待串口发送完成

    // 1. 使能PWR时钟
    __HAL_RCC_PWR_CLK_ENABLE();

    // 2. 配置唤醒引脚(PA0)
    HAL_PWR_EnableWakeUpPin(PWR_WAKEUP_PIN1);

    // 3. 清除唤醒标志
    __HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);

    // 4. 进入待机模式
    HAL_PWR_EnterSTANDBYMode();

    // 注意:唤醒后会从复位向量开始执行,不会返回这里
}

/**
 * @brief  检查是否从待机模式唤醒
 */
void check_standby_wakeup(void) {
    if (__HAL_PWR_GET_FLAG(PWR_FLAG_SB) != RESET) {
        printf("从待机模式唤醒!\n");

        // 清除待机标志
        __HAL_PWR_CLEAR_FLAG(PWR_FLAG_SB);

        // 清除唤醒标志
        __HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
    } else {
        printf("正常上电启动\n");
    }
}

睡眠模式对比

/**
 * @brief  睡眠模式对比表
 */
typedef struct {
    char name[20];
    float current_ua;      // 电流(μA)
    float wakeup_time_us;  // 唤醒时间(μs)
    int keep_sram;         // 保持SRAM
    int keep_register;     // 保持寄存器
    char wakeup_source[50];
} SleepModeInfo;

void print_sleep_mode_comparison(void) {
    SleepModeInfo modes[] = {
        {"运行模式", 10000, 0, 1, 1, "N/A"},
        {"睡眠模式", 5000, 1, 1, 1, "任何中断"},
        {"停止模式", 50, 100, 1, 1, "EXTI, RTC, IWDG"},
        {"待机模式", 2, 1000, 0, 0, "WKUP引脚, RTC"}
    };

    printf("\n睡眠模式对比\n");
    printf("%-12s %10s %12s %8s %8s %20s\n",
           "模式", "电流(μA)", "唤醒(μs)", "SRAM", "寄存器", "唤醒源");
    printf("─────────────────────────────────────────────────────────────────────\n");

    for (int i = 0; i < 4; i++) {
        printf("%-12s %10.0f %12.0f %8s %8s %20s\n",
               modes[i].name,
               modes[i].current_ua,
               modes[i].wakeup_time_us,
               modes[i].keep_sram ? "保持" : "丢失",
               modes[i].keep_register ? "保持" : "丢失",
               modes[i].wakeup_source);
    }
}

第二部分:时钟门控技术

什么是时钟门控

时钟门控(Clock Gating)是一种通过关闭不使用的模块时钟来降低功耗的技术。由于动态功耗与时钟频率成正比,关闭时钟可以显著降低功耗。

功耗公式:

动态功耗 = C × V² × f × α
其中:
- C:负载电容
- V:工作电压
- f:时钟频率
- α:翻转率(0-1)

关闭时钟 → f = 0 → 功耗 = 0

STM32时钟门控实现

1. 外设时钟控制

基本原理:

/**
 * @brief  外设时钟使能和关闭
 * @note   STM32通过RCC寄存器控制外设时钟
 */

// 使能外设时钟
void enable_peripheral_clock_example(void) {
    // 使能GPIOA时钟
    __HAL_RCC_GPIOA_CLK_ENABLE();

    // 使能USART1时钟
    __HAL_RCC_USART1_CLK_ENABLE();

    // 使能TIM2时钟
    __HAL_RCC_TIM2_CLK_ENABLE();

    // 使能ADC1时钟
    __HAL_RCC_ADC1_CLK_ENABLE();
}

// 关闭外设时钟
void disable_peripheral_clock_example(void) {
    // 关闭USART1时钟(不使用时)
    __HAL_RCC_USART1_CLK_DISABLE();

    // 关闭TIM2时钟
    __HAL_RCC_TIM2_CLK_DISABLE();

    // 关闭ADC1时钟
    __HAL_RCC_ADC1_CLK_DISABLE();

    // 注意:GPIO时钟通常保持使能,因为可能有多个引脚在使用
}

2. 动态时钟管理

实现策略:

/**
 * @brief  外设时钟管理结构
 */
typedef struct {
    void (*enable_clock)(void);
    void (*disable_clock)(void);
    uint32_t usage_count;
    char name[20];
} PeripheralClock;

/**
 * @brief  USART1时钟管理
 */
PeripheralClock usart1_clock = {
    .enable_clock = __HAL_RCC_USART1_CLK_ENABLE,
    .disable_clock = __HAL_RCC_USART1_CLK_DISABLE,
    .usage_count = 0,
    .name = "USART1"
};

/**
 * @brief  请求使用外设(自动使能时钟)
 * @param  clk: 外设时钟结构
 */
void peripheral_clock_request(PeripheralClock *clk) {
    if (clk->usage_count == 0) {
        clk->enable_clock();
        printf("%s 时钟使能\n", clk->name);
    }
    clk->usage_count++;
}

/**
 * @brief  释放外设(自动关闭时钟)
 * @param  clk: 外设时钟结构
 */
void peripheral_clock_release(PeripheralClock *clk) {
    if (clk->usage_count > 0) {
        clk->usage_count--;
        if (clk->usage_count == 0) {
            clk->disable_clock();
            printf("%s 时钟关闭\n", clk->name);
        }
    }
}

// 使用示例
void clock_management_example(void) {
    // 模块A需要使用USART1
    peripheral_clock_request(&usart1_clock);
    // 使用USART1...

    // 模块B也需要使用USART1
    peripheral_clock_request(&usart1_clock);
    // 使用USART1...

    // 模块A使用完毕
    peripheral_clock_release(&usart1_clock);
    // USART1时钟仍然开启(模块B还在使用)

    // 模块B使用完毕
    peripheral_clock_release(&usart1_clock);
    // USART1时钟自动关闭(没有模块在使用)
}

3. 时钟频率调整

动态调频(DVFS):

/**
 * @brief  系统时钟频率配置
 */
typedef enum {
    CLOCK_FREQ_LOW = 0,     // 低速:8MHz
    CLOCK_FREQ_MEDIUM = 1,  // 中速:36MHz
    CLOCK_FREQ_HIGH = 2     // 高速:72MHz
} ClockFrequency;

/**
 * @brief  设置系统时钟频率
 * @param  freq: 目标频率
 */
void set_system_clock_frequency(ClockFrequency freq) {
    RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
    RCC_OscInitTypeDef RCC_OscInitStruct = {0};

    switch(freq) {
        case CLOCK_FREQ_LOW:
            printf("切换到低速模式:8MHz\n");
            // 配置为8MHz(HSI)
            RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
            RCC_OscInitStruct.HSIState = RCC_HSI_ON;
            RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;

            RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | 
                                          RCC_CLOCKTYPE_SYSCLK |
                                          RCC_CLOCKTYPE_PCLK1 | 
                                          RCC_CLOCKTYPE_PCLK2;
            RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
            RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
            RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
            RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
            break;

        case CLOCK_FREQ_MEDIUM:
            printf("切换到中速模式:36MHz\n");
            // 配置为36MHz
            // ... 配置代码 ...
            break;

        case CLOCK_FREQ_HIGH:
            printf("切换到高速模式:72MHz\n");
            // 配置为72MHz
            // ... 配置代码 ...
            break;
    }

    HAL_RCC_OscConfig(&RCC_OscInitStruct);
    HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0);
}

/**
 * @brief  根据负载动态调整频率
 * @param  load: 负载百分比(0-100)
 */
void dynamic_frequency_scaling(uint8_t load) {
    static ClockFrequency current_freq = CLOCK_FREQ_HIGH;
    ClockFrequency target_freq;

    // 根据负载选择频率
    if (load < 20) {
        target_freq = CLOCK_FREQ_LOW;
    } else if (load < 60) {
        target_freq = CLOCK_FREQ_MEDIUM;
    } else {
        target_freq = CLOCK_FREQ_HIGH;
    }

    // 如果频率需要改变
    if (target_freq != current_freq) {
        set_system_clock_frequency(target_freq);
        current_freq = target_freq;

        // 重新配置依赖时钟的外设(如UART波特率)
        reconfigure_peripherals();
    }
}

时钟门控最佳实践

/**
 * @brief  低功耗时钟配置示例
 * @note   展示如何在不同场景下配置时钟
 */

// 场景1:初始化时只使能必要的时钟
void low_power_clock_init(void) {
    // 只使能必要的外设时钟
    __HAL_RCC_PWR_CLK_ENABLE();    // 电源控制
    __HAL_RCC_GPIOA_CLK_ENABLE();  // GPIO(按键、LED)
    __HAL_RCC_RTC_ENABLE();        // RTC(唤醒)

    // 不使能的外设:
    // - USART(需要时再使能)
    // - SPI(需要时再使能)
    // - ADC(需要时再使能)
    // - TIM(需要时再使能)
}

// 场景2:使用外设前使能时钟
void use_uart_with_clock_control(void) {
    // 使能UART时钟
    __HAL_RCC_USART1_CLK_ENABLE();

    // 初始化UART
    MX_USART1_UART_Init();

    // 发送数据
    HAL_UART_Transmit(&huart1, "Hello\n", 6, 100);

    // 使用完毕,关闭UART
    HAL_UART_DeInit(&huart1);

    // 关闭UART时钟
    __HAL_RCC_USART1_CLK_DISABLE();
}

// 场景3:进入睡眠前关闭所有可关闭的时钟
void prepare_for_sleep(void) {
    // 关闭所有外设时钟(除了唤醒源)
    __HAL_RCC_USART1_CLK_DISABLE();
    __HAL_RCC_SPI1_CLK_DISABLE();
    __HAL_RCC_TIM2_CLK_DISABLE();
    __HAL_RCC_ADC1_CLK_DISABLE();

    // 保持必要的时钟
    // - RTC(用于唤醒)
    // - GPIO(用于外部中断唤醒)

    printf("时钟已优化,准备进入睡眠\n");
}

第三部分:外设关闭策略

为什么要关闭外设

即使外设不工作,只要时钟使能,就会消耗功耗。正确关闭不使用的外设是低功耗设计的关键。

功耗来源:

外设功耗 = 静态功耗 + 动态功耗
- 静态功耗:漏电流(nA-μA级)
- 动态功耗:时钟翻转(μA-mA级)

关闭外设可以同时降低两种功耗

外设关闭方法

1. GPIO配置

低功耗GPIO配置:

/**
 * @brief  GPIO低功耗配置
 * @note   未使用的GPIO应配置为模拟输入模式
 */
void configure_gpio_for_low_power(void) {
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    // 使能所有GPIO时钟
    __HAL_RCC_GPIOA_CLK_ENABLE();
    __HAL_RCC_GPIOB_CLK_ENABLE();
    __HAL_RCC_GPIOC_CLK_ENABLE();

    // 配置所有未使用的GPIO为模拟输入模式(最低功耗)
    GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
    GPIO_InitStruct.Pull = GPIO_NOPULL;

    // GPIOA未使用的引脚
    GPIO_InitStruct.Pin = GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    // GPIOB未使用的引脚
    GPIO_InitStruct.Pin = GPIO_PIN_All;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

    // GPIOC未使用的引脚
    GPIO_InitStruct.Pin = GPIO_PIN_All;
    HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);

    printf("GPIO已配置为低功耗模式\n");
}

/**
 * @brief  配置使用中的GPIO
 * @note   只配置实际使用的引脚
 */
void configure_used_gpio(void) {
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    // LED引脚(PA5)- 推挽输出
    GPIO_InitStruct.Pin = GPIO_PIN_5;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;  // 低速以降低功耗
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    // 按键引脚(PA0)- 上拉输入
    GPIO_InitStruct.Pin = GPIO_PIN_0;
    GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;  // 下降沿中断
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}

2. 串口外设

UART低功耗管理:

/**
 * @brief  UART外设低功耗管理
 */
typedef struct {
    UART_HandleTypeDef *huart;
    uint8_t is_active;
} UART_LowPower;

UART_LowPower uart1_lp = {
    .huart = &huart1,
    .is_active = 0
};

/**
 * @brief  使能UART(需要时)
 */
void uart_enable(UART_LowPower *uart_lp) {
    if (!uart_lp->is_active) {
        // 使能时钟
        __HAL_RCC_USART1_CLK_ENABLE();

        // 初始化UART
        HAL_UART_Init(uart_lp->huart);

        uart_lp->is_active = 1;
        printf("UART已使能\n");
    }
}

/**
 * @brief  关闭UART(不使用时)
 */
void uart_disable(UART_LowPower *uart_lp) {
    if (uart_lp->is_active) {
        // 等待发送完成
        while(__HAL_UART_GET_FLAG(uart_lp->huart, UART_FLAG_TC) == RESET);

        // 反初始化UART
        HAL_UART_DeInit(uart_lp->huart);

        // 关闭时钟
        __HAL_RCC_USART1_CLK_DISABLE();

        // 配置引脚为模拟模式
        GPIO_InitTypeDef GPIO_InitStruct = {0};
        GPIO_InitStruct.Pin = GPIO_PIN_9 | GPIO_PIN_10;  // TX, RX
        GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
        GPIO_InitStruct.Pull = GPIO_NOPULL;
        HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

        uart_lp->is_active = 0;
        // 注意:这里不能用printf,因为UART已关闭
    }
}

/**
 * @brief  使用UART发送数据(自动管理电源)
 */
void uart_send_with_power_management(const char *data, uint16_t len) {
    // 使能UART
    uart_enable(&uart1_lp);

    // 发送数据
    HAL_UART_Transmit(&huart1, (uint8_t*)data, len, 1000);

    // 关闭UART(可选:如果短时间内不再使用)
    // uart_disable(&uart1_lp);
}

3. ADC外设

ADC低功耗管理:

/**
 * @brief  ADC单次转换模式(低功耗)
 * @param  channel: ADC通道
 * @retval ADC转换结果
 */
uint16_t adc_single_conversion(uint32_t channel) {
    uint16_t adc_value;

    // 1. 使能ADC时钟
    __HAL_RCC_ADC1_CLK_ENABLE();

    // 2. 配置ADC
    ADC_HandleTypeDef hadc1;
    hadc1.Instance = ADC1;
    hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
    hadc1.Init.ContinuousConvMode = DISABLE;  // 单次转换
    hadc1.Init.DiscontinuousConvMode = DISABLE;
    hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
    hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
    hadc1.Init.NbrOfConversion = 1;
    HAL_ADC_Init(&hadc1);

    // 3. 配置通道
    ADC_ChannelConfTypeDef sConfig = {0};
    sConfig.Channel = channel;
    sConfig.Rank = ADC_REGULAR_RANK_1;
    sConfig.SamplingTime = ADC_SAMPLETIME_55CYCLES_5;
    HAL_ADC_ConfigChannel(&hadc1, &sConfig);

    // 4. 启动转换
    HAL_ADC_Start(&hadc1);

    // 5. 等待转换完成
    HAL_ADC_PollForConversion(&hadc1, 100);

    // 6. 读取结果
    adc_value = HAL_ADC_GetValue(&hadc1);

    // 7. 停止ADC
    HAL_ADC_Stop(&hadc1);

    // 8. 反初始化ADC
    HAL_ADC_DeInit(&hadc1);

    // 9. 关闭ADC时钟
    __HAL_RCC_ADC1_CLK_DISABLE();

    return adc_value;
}

/**
 * @brief  批量ADC转换(提高效率)
 * @param  channels: 通道数组
 * @param  count: 通道数量
 * @param  results: 结果数组
 */
void adc_batch_conversion(uint32_t *channels, uint8_t count, uint16_t *results) {
    // 使能ADC一次,转换多个通道
    __HAL_RCC_ADC1_CLK_ENABLE();

    // 初始化ADC
    ADC_HandleTypeDef hadc1;
    // ... 配置代码 ...

    // 转换所有通道
    for (uint8_t i = 0; i < count; i++) {
        // 配置通道
        ADC_ChannelConfTypeDef sConfig = {0};
        sConfig.Channel = channels[i];
        sConfig.Rank = ADC_REGULAR_RANK_1;
        sConfig.SamplingTime = ADC_SAMPLETIME_55CYCLES_5;
        HAL_ADC_ConfigChannel(&hadc1, &sConfig);

        // 转换
        HAL_ADC_Start(&hadc1);
        HAL_ADC_PollForConversion(&hadc1, 100);
        results[i] = HAL_ADC_GetValue(&hadc1);
        HAL_ADC_Stop(&hadc1);
    }

    // 关闭ADC
    HAL_ADC_DeInit(&hadc1);
    __HAL_RCC_ADC1_CLK_DISABLE();
}

4. 定时器外设

定时器低功耗管理:

/**
 * @brief  定时器按需使用
 */

// 全局定时器句柄
TIM_HandleTypeDef htim2;
uint8_t tim2_active = 0;

/**
 * @brief  启动定时器
 * @param  period_ms: 定时周期(毫秒)
 */
void timer_start(uint32_t period_ms) {
    if (!tim2_active) {
        // 使能时钟
        __HAL_RCC_TIM2_CLK_ENABLE();

        // 配置定时器
        htim2.Instance = TIM2;
        htim2.Init.Prescaler = 7200 - 1;  // 72MHz / 7200 = 10kHz
        htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
        htim2.Init.Period = period_ms * 10 - 1;  // 10kHz * ms
        htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
        HAL_TIM_Base_Init(&htim2);

        // 启动定时器中断
        HAL_TIM_Base_Start_IT(&htim2);

        tim2_active = 1;
        printf("定时器已启动:%lu ms\n", period_ms);
    }
}

/**
 * @brief  停止定时器
 */
void timer_stop(void) {
    if (tim2_active) {
        // 停止定时器
        HAL_TIM_Base_Stop_IT(&htim2);

        // 反初始化
        HAL_TIM_Base_DeInit(&htim2);

        // 关闭时钟
        __HAL_RCC_TIM2_CLK_DISABLE();

        tim2_active = 0;
        printf("定时器已停止\n");
    }
}

/**
 * @brief  定时器中断回调
 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
    if (htim->Instance == TIM2) {
        // 定时任务
        printf("定时器触发\n");

        // 如果是一次性任务,可以停止定时器
        // timer_stop();
    }
}

外设关闭检查清单

/**
 * @brief  进入低功耗模式前的外设检查
 * @retval 0: 可以进入低功耗,1: 有外设正在使用
 */
uint8_t check_peripherals_before_sleep(void) {
    uint8_t busy = 0;

    printf("\n外设状态检查:\n");

    // 检查UART
    if (__HAL_RCC_USART1_IS_CLK_ENABLED()) {
        if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_TC) == RESET) {
            printf("  [×] UART正在发送数据\n");
            busy = 1;
        } else {
            printf("  [√] UART空闲\n");
        }
    } else {
        printf("  [√] UART已关闭\n");
    }

    // 检查SPI
    if (__HAL_RCC_SPI1_IS_CLK_ENABLED()) {
        if (__HAL_SPI_GET_FLAG(&hspi1, SPI_FLAG_BSY)) {
            printf("  [×] SPI正在传输\n");
            busy = 1;
        } else {
            printf("  [√] SPI空闲\n");
        }
    } else {
        printf("  [√] SPI已关闭\n");
    }

    // 检查ADC
    if (__HAL_RCC_ADC1_IS_CLK_ENABLED()) {
        printf("  [!] ADC时钟使能(建议关闭)\n");
    } else {
        printf("  [√] ADC已关闭\n");
    }

    // 检查定时器
    if (__HAL_RCC_TIM2_IS_CLK_ENABLED()) {
        printf("  [!] TIM2时钟使能\n");
    } else {
        printf("  [√] TIM2已关闭\n");
    }

    return busy;
}

第四部分:功耗测量方法

为什么要测量功耗

"不能测量就不能改进"。功耗测量是低功耗设计的重要环节,可以:

  1. 验证设计:确认功耗是否达到目标
  2. 发现问题:找出功耗异常的模块
  3. 优化指导:为优化提供数据支持
  4. 性能评估:评估不同方案的效果

功耗测量工具

1. 万用表测量

适用场景: - 稳态功耗测量 - 平均功耗估算 - 简单快速的验证

测量方法:

测量步骤:
1. 断开电源
2. 将万用表串联在电源回路中
3. 选择合适的电流档位(mA或μA)
4. 上电,读取电流值
5. 计算功耗:P = V × I

注意事项:
- 万用表有内阻,会影响测量
- 无法测量瞬态电流
- 精度有限(通常±1%)

实际操作:

/**
 * @brief  功耗测量辅助函数
 * @note   在不同状态下测量功耗
 */
void measure_power_consumption(void) {
    printf("\n=== 功耗测量 ===\n");
    printf("请使用万用表测量电流\n\n");

    // 测试1:运行模式
    printf("测试1:运行模式(全速运行)\n");
    printf("  - 系统时钟:72MHz\n");
    printf("  - 所有外设使能\n");
    printf("  - 请记录电流值:_____ mA\n");
    HAL_Delay(5000);  // 保持5秒以便测量

    // 测试2:空闲模式
    printf("\n测试2:空闲模式(降低时钟)\n");
    set_system_clock_frequency(CLOCK_FREQ_LOW);  // 8MHz
    printf("  - 系统时钟:8MHz\n");
    printf("  - 关闭不必要的外设\n");
    printf("  - 请记录电流值:_____ mA\n");
    HAL_Delay(5000);

    // 测试3:睡眠模式
    printf("\n测试3:睡眠模式\n");
    printf("  - CPU停止\n");
    printf("  - 外设运行\n");
    printf("  - 请记录电流值:_____ mA\n");
    printf("  - 5秒后自动唤醒\n");
    HAL_Delay(1000);
    // 配置定时器5秒后唤醒
    // ... 进入睡眠 ...

    // 测试4:停止模式
    printf("\n测试4:停止模式\n");
    printf("  - CPU和大部分外设停止\n");
    printf("  - 请记录电流值:_____ μA\n");
    printf("  - 10秒后自动唤醒\n");
    HAL_Delay(1000);
    // ... 进入停止模式 ...

    // 测试5:待机模式
    printf("\n测试5:待机模式\n");
    printf("  - 最低功耗模式\n");
    printf("  - 请记录电流值:_____ μA\n");
    printf("  - 按WKUP键唤醒\n");
    HAL_Delay(1000);
    // ... 进入待机模式 ...
}

2. 示波器 + 电流探头

适用场景: - 动态功耗测量 - 瞬态电流分析 - 功耗波形观察

测量方法:

测量步骤:
1. 使用电流探头夹在电源线上
2. 连接到示波器
3. 设置合适的时基和电流档位
4. 观察电流波形
5. 使用示波器的测量功能获取参数

可测量参数:
- 峰值电流
- 平均电流
- RMS电流
- 电流波形
- 瞬态响应

3. 功率分析仪

适用场景: - 精确功耗测量 - 长时间功耗记录 - 复杂功耗分析

推荐设备: - Nordic PPK2(Power Profiler Kit II) - Joulescope - Keysight N6705C

使用示例(Nordic PPK2):

/**
 * @brief  使用PPK2进行功耗分析
 * @note   在代码中添加标记,便于在PPK2中识别
 */

// 使用GPIO输出标记不同的工作状态
#define MARKER_PIN GPIO_PIN_6

void power_profiling_with_markers(void) {
    // 标记1:运行模式开始
    HAL_GPIO_WritePin(GPIOA, MARKER_PIN, GPIO_PIN_SET);
    HAL_Delay(10);
    HAL_GPIO_WritePin(GPIOA, MARKER_PIN, GPIO_PIN_RESET);

    // 运行模式工作
    for (int i = 0; i < 1000; i++) {
        // 执行一些任务
    }
    HAL_Delay(1000);

    // 标记2:进入睡眠模式
    HAL_GPIO_WritePin(GPIOA, MARKER_PIN, GPIO_PIN_SET);
    HAL_Delay(10);
    HAL_GPIO_WritePin(GPIOA, MARKER_PIN, GPIO_PIN_RESET);
    HAL_Delay(10);
    HAL_GPIO_WritePin(GPIOA, MARKER_PIN, GPIO_PIN_SET);
    HAL_Delay(10);
    HAL_GPIO_WritePin(GPIOA, MARKER_PIN, GPIO_PIN_RESET);

    // 进入睡眠
    enter_sleep_mode();
}

功耗计算方法

1. 平均功耗计算

/**
 * @brief  计算平均功耗
 * @param  states: 工作状态数组
 * @param  count: 状态数量
 * @retval 平均功耗(W)
 */
typedef struct {
    char name[30];
    float current_ma;    // 电流(mA)
    float duration_s;    // 持续时间(s)
    float voltage_v;     // 电压(V)
} PowerState;

float calculate_average_power(PowerState *states, uint8_t count) {
    float total_energy = 0;  // 总能量(J)
    float total_time = 0;    // 总时间(s)

    printf("\n平均功耗计算\n");
    printf("%-20s %10s %10s %10s %12s\n",
           "状态", "电流(mA)", "时间(s)", "电压(V)", "能量(mJ)");
    printf("────────────────────────────────────────────────────────────\n");

    for (uint8_t i = 0; i < count; i++) {
        // 计算能量:E = P × t = V × I × t
        float energy = states[i].voltage_v * 
                      (states[i].current_ma / 1000.0) * 
                      states[i].duration_s;

        total_energy += energy;
        total_time += states[i].duration_s;

        printf("%-20s %10.2f %10.2f %10.2f %12.2f\n",
               states[i].name,
               states[i].current_ma,
               states[i].duration_s,
               states[i].voltage_v,
               energy * 1000);  // 转换为mJ
    }

    float avg_power = total_energy / total_time;
    float avg_current = (avg_power / states[0].voltage_v) * 1000;  // mA

    printf("────────────────────────────────────────────────────────────\n");
    printf("总时间: %.2f s\n", total_time);
    printf("总能量: %.2f mJ\n", total_energy * 1000);
    printf("平均功耗: %.3f W = %.2f mW\n", avg_power, avg_power * 1000);
    printf("平均电流: %.2f mA\n", avg_current);

    return avg_power;
}

// 使用示例
void power_calculation_example(void) {
    PowerState states[] = {
        {"深度睡眠", 0.002, 50.0, 3.3},   // 2μA, 50s
        {"待机", 0.010, 5.0, 3.3},         // 10μA, 5s
        {"工作", 50.0, 4.0, 3.3},          // 50mA, 4s
        {"峰值", 200.0, 1.0, 3.3}          // 200mA, 1s
    };

    float avg_power = calculate_average_power(states, 4);

    // 计算电池寿命
    float battery_capacity = 2000;  // mAh
    float avg_current = (avg_power / 3.3) * 1000;  // mA
    float battery_life_hours = battery_capacity / avg_current;

    printf("\n电池寿命估算(2000mAh电池):\n");
    printf("  理论寿命: %.1f 小时 = %.1f 天\n",
           battery_life_hours, battery_life_hours / 24);
}

2. 实时功耗监控

/**
 * @brief  实时功耗监控(使用ADC测量电流)
 * @note   通过测量电流采样电阻上的压降计算电流
 */

#define SHUNT_RESISTOR 0.1  // 采样电阻:0.1Ω
#define ADC_VREF 3.3        // ADC参考电压:3.3V
#define ADC_RESOLUTION 4096 // 12位ADC

/**
 * @brief  读取当前电流
 * @retval 电流值(mA)
 */
float read_current(void) {
    // 读取ADC值
    uint16_t adc_value = adc_single_conversion(ADC_CHANNEL_0);

    // 计算电压
    float voltage = (float)adc_value * ADC_VREF / ADC_RESOLUTION;

    // 计算电流:I = V / R
    float current = voltage / SHUNT_RESISTOR;

    // 转换为mA
    return current * 1000;
}

/**
 * @brief  功耗监控任务
 */
void power_monitoring_task(void) {
    static uint32_t sample_count = 0;
    static float total_current = 0;

    // 读取当前电流
    float current = read_current();

    // 累加
    total_current += current;
    sample_count++;

    // 每10秒输出一次
    if (sample_count >= 1000) {  // 假设10ms采样一次
        float avg_current = total_current / sample_count;
        float power = avg_current * 3.3 / 1000;  // W

        printf("平均电流: %.2f mA, 功耗: %.2f mW\n",
               avg_current, power * 1000);

        // 重置
        total_current = 0;
        sample_count = 0;
    }
}

功耗测量最佳实践

/**
 * @brief  功耗测量检查清单
 */
void power_measurement_checklist(void) {
    printf("\n功耗测量检查清单:\n\n");

    printf("测量前准备:\n");
    printf("  □ 确认测量工具精度满足要求\n");
    printf("  □ 校准测量设备\n");
    printf("  □ 准备记录表格\n");
    printf("  □ 了解预期功耗范围\n\n");

    printf("测量过程:\n");
    printf("  □ 断开调试器(避免额外功耗)\n");
    printf("  □ 使用稳定的电源\n");
    printf("  □ 等待系统稳定后测量\n");
    printf("  □ 多次测量取平均值\n");
    printf("  □ 记录环境温度\n\n");

    printf("测量内容:\n");
    printf("  □ 运行模式功耗\n");
    printf("  □ 睡眠模式功耗\n");
    printf("  □ 停止模式功耗\n");
    printf("  □ 待机模式功耗\n");
    printf("  □ 峰值电流\n");
    printf("  □ 平均电流\n\n");

    printf("数据分析:\n");
    printf("  □ 对比设计目标\n");
    printf("  □ 分析异常数据\n");
    printf("  □ 计算电池寿命\n");
    printf("  □ 识别优化空间\n");
}

第五部分:功耗优化策略

系统级优化策略

1. 工作周期优化

间歇工作模式:

/**
 * @brief  间歇工作模式实现
 * @note   工作一段时间,然后睡眠,周期性重复
 */

#define WORK_DURATION_MS 100    // 工作时间:100ms
#define SLEEP_DURATION_MS 9900  // 睡眠时间:9900ms
// 占空比:1%

void intermittent_work_mode(void) {
    while (1) {
        // 工作阶段
        printf("开始工作...\n");

        // 执行任务
        read_sensors();
        process_data();
        send_data();

        printf("工作完成,进入睡眠\n");

        // 睡眠阶段
        // 配置RTC在9.9秒后唤醒
        configure_rtc_wakeup(SLEEP_DURATION_MS);

        // 进入停止模式
        enter_stop_mode(PWR_Regulator_LowPower);

        // 被唤醒后继续下一个周期
        printf("唤醒,开始新周期\n");
    }
}

/**
 * @brief  计算间歇工作模式的功耗
 */
void calculate_intermittent_power(void) {
    float work_current = 50.0;    // 工作电流:50mA
    float sleep_current = 0.05;   // 睡眠电流:50μA
    float work_time = 0.1;        // 工作时间:0.1s
    float sleep_time = 9.9;       // 睡眠时间:9.9s
    float voltage = 3.3;          // 电压:3.3V

    // 计算平均电流
    float avg_current = (work_current * work_time + 
                        sleep_current * sleep_time) / 
                       (work_time + sleep_time);

    // 计算平均功耗
    float avg_power = avg_current * voltage / 1000;  // W

    printf("\n间歇工作模式功耗分析:\n");
    printf("工作电流: %.1f mA, 时间: %.1f s\n", work_current, work_time);
    printf("睡眠电流: %.2f mA, 时间: %.1f s\n", sleep_current, sleep_time);
    printf("占空比: %.1f%%\n", work_time / (work_time + sleep_time) * 100);
    printf("平均电流: %.2f mA\n", avg_current);
    printf("平均功耗: %.2f mW\n", avg_power * 1000);
    printf("节能: %.1f%%\n", (1 - avg_current / work_current) * 100);

    // 电池寿命
    float battery_capacity = 2000;  // mAh
    float battery_life = battery_capacity / avg_current / 24;  // 天
    printf("电池寿命: %.1f 天\n", battery_life);
}

2. 事件驱动架构

中断驱动 vs 轮询:

/**
 * @brief  轮询方式(高功耗)
 */
void polling_method(void) {
    while (1) {
        // 持续检查按键状态
        if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET) {
            printf("按键按下\n");
            handle_button_press();
        }

        // CPU一直运行,功耗高
        HAL_Delay(10);  // 10ms轮询间隔
    }
}

/**
 * @brief  中断驱动方式(低功耗)
 */
void interrupt_driven_method(void) {
    // 配置按键中断
    configure_button_interrupt();

    while (1) {
        // 进入睡眠,等待中断唤醒
        enter_sleep_mode();

        // 被中断唤醒后处理事件
        if (button_pressed_flag) {
            printf("按键按下\n");
            handle_button_press();
            button_pressed_flag = 0;
        }
    }
}

/**
 * @brief  按键中断处理函数
 */
void EXTI0_IRQHandler(void) {
    if (EXTI_GetITStatus(EXTI_Line0) != RESET) {
        button_pressed_flag = 1;
        EXTI_ClearITPendingBit(EXTI_Line0);
    }
}

/**
 * @brief  对比两种方法的功耗
 */
void compare_polling_vs_interrupt(void) {
    printf("\n轮询 vs 中断驱动功耗对比:\n\n");

    printf("轮询方式:\n");
    printf("  - CPU持续运行\n");
    printf("  - 电流:~50mA\n");
    printf("  - 响应延迟:10ms\n");
    printf("  - 功耗:165mW @ 3.3V\n\n");

    printf("中断驱动:\n");
    printf("  - CPU大部分时间睡眠\n");
    printf("  - 电流:~5mA(睡眠)+ 50mA(处理,<1%%时间)\n");
    printf("  - 平均电流:~5.5mA\n");
    printf("  - 响应延迟:<1ms\n");
    printf("  - 功耗:18mW @ 3.3V\n");
    printf("  - 节能:89%%\n");
}

3. 数据缓存策略

批量处理 vs 实时处理:

/**
 * @brief  数据缓存结构
 */
#define BUFFER_SIZE 100

typedef struct {
    uint16_t data[BUFFER_SIZE];
    uint16_t count;
    uint32_t timestamp[BUFFER_SIZE];
} DataBuffer;

DataBuffer sensor_buffer = {0};

/**
 * @brief  实时发送方式(高功耗)
 */
void realtime_send_method(void) {
    while (1) {
        // 读取传感器
        uint16_t data = read_sensor();

        // 立即发送
        uart_enable(&uart1_lp);
        send_data_via_uart(data);
        uart_disable(&uart1_lp);

        // 每次都要使能/关闭UART,开销大
        HAL_Delay(1000);
    }
}

/**
 * @brief  批量发送方式(低功耗)
 */
void batch_send_method(void) {
    while (1) {
        // 读取传感器并缓存
        uint16_t data = read_sensor();
        sensor_buffer.data[sensor_buffer.count] = data;
        sensor_buffer.timestamp[sensor_buffer.count] = HAL_GetTick();
        sensor_buffer.count++;

        // 缓存满了才发送
        if (sensor_buffer.count >= BUFFER_SIZE) {
            printf("缓存已满,批量发送\n");

            // 使能UART一次
            uart_enable(&uart1_lp);

            // 批量发送所有数据
            for (uint16_t i = 0; i < sensor_buffer.count; i++) {
                send_data_via_uart(sensor_buffer.data[i]);
            }

            // 关闭UART
            uart_disable(&uart1_lp);

            // 清空缓存
            sensor_buffer.count = 0;

            printf("发送完成\n");
        }

        HAL_Delay(1000);
    }
}

/**
 * @brief  对比两种方法
 */
void compare_realtime_vs_batch(void) {
    printf("\n实时发送 vs 批量发送功耗对比:\n\n");

    printf("实时发送(每秒1次):\n");
    printf("  - UART使能/关闭:100次/100秒\n");
    printf("  - 每次开销:~10ms\n");
    printf("  - 总开销:1秒/100秒 = 1%%\n");
    printf("  - 额外功耗:~0.5mA\n\n");

    printf("批量发送(100秒1次):\n");
    printf("  - UART使能/关闭:1次/100秒\n");
    printf("  - 每次开销:~1秒(发送100个数据)\n");
    printf("  - 总开销:1秒/100秒 = 1%%\n");
    printf("  - 额外功耗:~0.5mA\n");
    printf("  - 但减少了99次使能/关闭的开销\n");
    printf("  - 实际节能:~20%%\n");
}

硬件级优化策略

1. 电压优化

/**
 * @brief  动态电压调整(DVS)
 * @note   根据性能需求调整电压
 */

typedef enum {
    VOLTAGE_LOW = 0,     // 低电压:降低功耗
    VOLTAGE_NORMAL = 1,  // 正常电压
    VOLTAGE_HIGH = 2     // 高电压:提高性能
} VoltageLevel;

/**
 * @brief  设置工作电压
 * @param  level: 电压等级
 * @note   需要硬件支持(如PMIC)
 */
void set_voltage_level(VoltageLevel level) {
    switch(level) {
        case VOLTAGE_LOW:
            // 设置为1.8V(示例)
            printf("设置电压:1.8V(低功耗模式)\n");
            // 通过I2C配置PMIC
            // pmic_set_voltage(1.8);
            break;

        case VOLTAGE_NORMAL:
            // 设置为3.0V
            printf("设置电压:3.0V(正常模式)\n");
            // pmic_set_voltage(3.0);
            break;

        case VOLTAGE_HIGH:
            // 设置为3.3V
            printf("设置电压:3.3V(高性能模式)\n");
            // pmic_set_voltage(3.3);
            break;
    }
}

/**
 * @brief  电压对功耗的影响
 * @note   P = C × V² × f
 */
void voltage_power_relationship(void) {
    float capacitance = 100e-12;  // 100pF
    float frequency = 72e6;       // 72MHz

    printf("\n电压对功耗的影响(C=100pF, f=72MHz):\n\n");

    float voltages[] = {1.8, 2.5, 3.0, 3.3};

    for (int i = 0; i < 4; i++) {
        float v = voltages[i];
        float power = capacitance * v * v * frequency;

        printf("电压 %.1fV: 功耗 %.2f mW\n", v, power * 1000);

        if (i > 0) {
            float saving = (1 - power / 
                          (capacitance * voltages[0] * voltages[0] * frequency)) * 100;
            printf("  相比%.1fV节能: %.1f%%\n", voltages[0], -saving);
        }
    }
}

2. 外部器件优化

/**
 * @brief  外部器件功耗优化建议
 */
void external_device_optimization(void) {
    printf("\n外部器件功耗优化建议:\n\n");

    printf("1. LED指示灯:\n");
    printf("   - 使用高效LED(低正向电压)\n");
    printf("   - 增大限流电阻(降低亮度)\n");
    printf("   - 使用PWM调光(降低平均功耗)\n");
    printf("   - 不使用时完全关闭\n");
    printf("   - 典型功耗:2-20mA → 优化后:0.5-5mA\n\n");

    printf("2. 上拉/下拉电阻:\n");
    printf("   - 使用MCU内部上拉(节省外部电阻)\n");
    printf("   - 增大电阻值(降低漏电流)\n");
    printf("   - 典型值:10kΩ → 优化:100kΩ\n");
    printf("   - 功耗降低:10倍\n\n");

    printf("3. 传感器:\n");
    printf("   - 选择低功耗传感器\n");
    printf("   - 使用电源控制引脚\n");
    printf("   - 不使用时完全断电\n");
    printf("   - 降低采样频率\n");
    printf("   - 典型功耗:1-10mA → 优化后:<100μA\n\n");

    printf("4. 无线模块:\n");
    printf("   - 使用低功耗模式\n");
    printf("   - 降低发射功率\n");
    printf("   - 减少通信频率\n");
    printf("   - 使用睡眠模式\n");
    printf("   - 典型功耗:50-200mA → 优化后:<1mA\n");
}

软件级优化策略

/**
 * @brief  代码优化建议
 */
void code_optimization_tips(void) {
    printf("\n代码优化建议:\n\n");

    printf("1. 减少循环和计算:\n");
    printf("   - 避免不必要的循环\n");
    printf("   - 使用查表代替计算\n");
    printf("   - 优化算法复杂度\n");
    printf("   - 使用硬件加速(如DMA)\n\n");

    printf("2. 优化数据结构:\n");
    printf("   - 使用合适的数据类型\n");
    printf("   - 减少内存访问\n");
    printf("   - 利用缓存局部性\n");
    printf("   - 对齐数据结构\n\n");

    printf("3. 减少中断:\n");
    printf("   - 合并多个中断\n");
    printf("   - 使用DMA代替中断\n");
    printf("   - 降低中断优先级\n");
    printf("   - 批量处理中断\n\n");

    printf("4. 优化外设使用:\n");
    printf("   - 按需使能外设\n");
    printf("   - 使用低功耗模式\n");
    printf("   - 共享外设资源\n");
    printf("   - 及时关闭外设\n");
}

/**
 * @brief  低功耗编程模式
 */
void low_power_programming_pattern(void) {
    // 模式1:资源获取即初始化(RAII)
    void task_with_uart(void) {
        // 获取资源
        uart_enable(&uart1_lp);

        // 使用资源
        send_data("Hello");

        // 释放资源
        uart_disable(&uart1_lp);
    }

    // 模式2:作用域保护
    void task_with_scope_guard(void) {
        // 进入临界区
        __disable_irq();

        // 执行任务
        critical_section_code();

        // 退出临界区
        __enable_irq();
    }

    // 模式3:状态机
    typedef enum {
        STATE_SLEEP,
        STATE_WAKEUP,
        STATE_WORK,
        STATE_IDLE
    } SystemState;

    void state_machine(void) {
        static SystemState state = STATE_SLEEP;

        switch(state) {
            case STATE_SLEEP:
                enter_sleep_mode();
                state = STATE_WAKEUP;
                break;

            case STATE_WAKEUP:
                system_init();
                state = STATE_WORK;
                break;

            case STATE_WORK:
                do_work();
                state = STATE_IDLE;
                break;

            case STATE_IDLE:
                if (work_done()) {
                    state = STATE_SLEEP;
                }
                break;
        }
    }
}

第六部分:实战项目 - 低功耗温度记录器

项目需求

设计一个低功耗温度记录器,要求:

  1. 功能需求:
  2. 每10分钟采集一次温度
  3. 存储最近100个数据点
  4. 通过按键可以查看数据
  5. LED指示工作状态

  6. 功耗需求:

  7. 使用2节AA电池(3V,2000mAh)
  8. 目标寿命:6个月(180天)
  9. 允许平均电流:≤0.46mA

项目设计

1. 硬件选型

/**
 * @brief  硬件清单
 */
void hardware_list(void) {
    printf("硬件清单:\n\n");

    printf("1. MCU: STM32L051(超低功耗)\n");
    printf("   - 运行:~1mA @ 2MHz\n");
    printf("   - 停止:~0.5μA\n");
    printf("   - 待机:~0.3μA\n\n");

    printf("2. 温度传感器: DS18B20\n");
    printf("   - 工作:~1mA\n");
    printf("   - 待机:~1μA\n");
    printf("   - 转换时间:750ms\n\n");

    printf("3. 存储: 内部Flash\n");
    printf("   - 无额外功耗\n\n");

    printf("4. 显示: 无(通过串口输出)\n\n");

    printf("5. 按键: 1个(外部中断唤醒)\n\n");

    printf("6. LED: 1个(可选,低功耗模式)\n");
}

2. 功耗预算

/**
 * @brief  功耗预算分析
 */
void power_budget_analysis(void) {
    printf("\n功耗预算分析:\n\n");

    // 工作周期:10分钟 = 600秒
    float cycle_time = 600.0;  // s

    // 各阶段功耗
    float sleep_current = 0.0005;  // 0.5μA
    float wakeup_current = 1.0;    // 1mA
    float measure_current = 2.0;   // 2mA(MCU + 传感器)
    float store_current = 5.0;     // 5mA(Flash写入)

    // 各阶段时间
    float sleep_time = 599.0;      // 599s
    float wakeup_time = 0.1;       // 0.1s
    float measure_time = 0.8;      // 0.8s(传感器转换)
    float store_time = 0.1;        // 0.1s

    // 计算平均电流
    float avg_current = (sleep_current * sleep_time +
                        wakeup_current * wakeup_time +
                        measure_current * measure_time +
                        store_current * store_time) / cycle_time;

    printf("工作周期:%.0f 秒\n\n", cycle_time);

    printf("%-15s %10s %10s %12s\n", "阶段", "电流(mA)", "时间(s)", "电荷(mAs)");
    printf("───────────────────────────────────────────────\n");
    printf("%-15s %10.4f %10.1f %12.4f\n", "睡眠", sleep_current, sleep_time, 
           sleep_current * sleep_time);
    printf("%-15s %10.2f %10.1f %12.2f\n", "唤醒", wakeup_current, wakeup_time,
           wakeup_current * wakeup_time);
    printf("%-15s %10.2f %10.1f %12.2f\n", "测量", measure_current, measure_time,
           measure_current * measure_time);
    printf("%-15s %10.2f %10.1f %12.2f\n", "存储", store_current, store_time,
           store_current * store_time);
    printf("───────────────────────────────────────────────\n");

    printf("\n平均电流:%.3f mA\n", avg_current);
    printf("目标电流:≤0.46 mA\n");

    if (avg_current <= 0.46) {
        printf("✓ 满足功耗要求!\n");
    } else {
        printf("✗ 超出功耗预算 %.2f%%\n", 
               (avg_current / 0.46 - 1) * 100);
    }

    // 计算电池寿命
    float battery_capacity = 2000;  // mAh
    float battery_life_hours = battery_capacity / avg_current;
    float battery_life_days = battery_life_hours / 24;

    printf("\n电池寿命:%.0f 小时 = %.0f 天\n",
           battery_life_hours, battery_life_days);
}

3. 软件实现

/**
 * @brief  温度数据结构
 */
#define MAX_RECORDS 100

typedef struct {
    uint16_t temperature;  // 温度 × 10(如25.6°C存储为256)
    uint32_t timestamp;    // 时间戳
} TempRecord;

typedef struct {
    TempRecord records[MAX_RECORDS];
    uint16_t count;
    uint16_t write_index;
} TempDataBuffer;

TempDataBuffer temp_buffer = {0};

/**
 * @brief  主程序
 */
int main(void) {
    // 系统初始化
    HAL_Init();
    SystemClock_Config();  // 配置为低功耗时钟

    // 外设初始化
    GPIO_Init();
    RTC_Init();

    // 配置GPIO为低功耗模式
    configure_gpio_for_low_power();

    // 配置唤醒源
    configure_wakeup_sources();

    printf("温度记录器启动\n");
    printf("目标:每10分钟记录一次温度\n");

    while (1) {
        // 采集温度
        float temperature = read_temperature();

        // 存储数据
        store_temperature(temperature);

        printf("温度:%.1f°C,已存储\n", temperature);

        // 配置RTC在10分钟后唤醒
        configure_rtc_wakeup(600);  // 600秒 = 10分钟

        // 进入停止模式
        printf("进入睡眠模式...\n");
        HAL_Delay(100);  // 等待串口发送完成

        enter_stop_mode(PWR_Regulator_LowPower);

        // 被唤醒
        SystemClock_Config();  // 恢复时钟
        printf("唤醒!\n");
    }
}

/**
 * @brief  读取温度
 * @retval 温度值(°C)
 */
float read_temperature(void) {
    // 使能传感器电源(如果有控制引脚)
    // HAL_GPIO_WritePin(SENSOR_PWR_GPIO, SENSOR_PWR_PIN, GPIO_PIN_SET);
    // HAL_Delay(10);  // 等待传感器稳定

    // 读取DS18B20
    float temperature = ds18b20_read_temperature();

    // 关闭传感器电源
    // HAL_GPIO_WritePin(SENSOR_PWR_GPIO, SENSOR_PWR_PIN, GPIO_PIN_RESET);

    return temperature;
}

/**
 * @brief  存储温度数据
 * @param  temperature: 温度值
 */
void store_temperature(float temperature) {
    // 转换为整数(×10)
    uint16_t temp_int = (uint16_t)(temperature * 10);

    // 存储到缓冲区
    temp_buffer.records[temp_buffer.write_index].temperature = temp_int;
    temp_buffer.records[temp_buffer.write_index].timestamp = HAL_GetTick();

    // 更新索引(循环缓冲区)
    temp_buffer.write_index = (temp_buffer.write_index + 1) % MAX_RECORDS;

    // 更新计数
    if (temp_buffer.count < MAX_RECORDS) {
        temp_buffer.count++;
    }

    // 每10个数据点写入Flash(减少Flash写入次数)
    if (temp_buffer.count % 10 == 0) {
        save_to_flash();
    }
}

/**
 * @brief  配置唤醒源
 */
void configure_wakeup_sources(void) {
    // 1. RTC唤醒(定时唤醒)
    // 已在main函数中配置

    // 2. 按键唤醒(外部中断)
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    GPIO_InitStruct.Pin = GPIO_PIN_0;
    GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    HAL_NVIC_SetPriority(EXTI0_1_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(EXTI0_1_IRQn);
}

/**
 * @brief  按键中断处理(查看数据)
 */
void EXTI0_1_IRQHandler(void) {
    if (__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_0) != RESET) {
        __HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_0);

        // 唤醒后显示数据
        display_temperature_data();
    }
}

/**
 * @brief  显示温度数据
 */
void display_temperature_data(void) {
    printf("\n=== 温度记录 ===\n");
    printf("共 %d 条记录\n\n", temp_buffer.count);

    printf("%-5s %-10s %-10s\n", "序号", "温度(°C)", "时间(s)");
    printf("────────────────────────────\n");

    // 显示最近10条记录
    uint16_t start = temp_buffer.count > 10 ? temp_buffer.count - 10 : 0;

    for (uint16_t i = start; i < temp_buffer.count; i++) {
        uint16_t index = (temp_buffer.write_index - temp_buffer.count + i) % MAX_RECORDS;
        float temp = temp_buffer.records[index].temperature / 10.0;
        uint32_t time = temp_buffer.records[index].timestamp / 1000;

        printf("%-5d %-10.1f %-10lu\n", i + 1, temp, time);
    }

    printf("\n");
}

项目测试

/**
 * @brief  功耗测试程序
 */
void power_consumption_test(void) {
    printf("\n=== 功耗测试 ===\n\n");

    printf("测试1:睡眠模式功耗\n");
    printf("  1. 连接电流表\n");
    printf("  2. 进入睡眠模式\n");
    printf("  3. 记录电流值\n");
    printf("  4. 预期:<1μA\n");
    printf("  5. 按任意键继续...\n");
    getchar();

    // 进入睡眠
    configure_rtc_wakeup(60);  // 60秒后唤醒
    enter_stop_mode(PWR_Regulator_LowPower);

    printf("\n测试2:工作模式功耗\n");
    printf("  1. 测量温度采集时的电流\n");
    printf("  2. 预期:~2mA\n");
    printf("  3. 持续时间:~1秒\n");

    float temp = read_temperature();
    printf("  温度:%.1f°C\n", temp);

    printf("\n测试3:完整周期功耗\n");
    printf("  1. 运行完整的采集周期\n");
    printf("  2. 使用示波器 + 电流探头\n");
    printf("  3. 观察电流波形\n");
    printf("  4. 计算平均电流\n");

    printf("\n测试4:长期测试\n");
    printf("  1. 使用满电电池\n");
    printf("  2. 记录初始电压\n");
    printf("  3. 运行24小时\n");
    printf("  4. 记录最终电压\n");
    printf("  5. 估算电池寿命\n");
}

优化建议

/**
 * @brief  进一步优化建议
 */
void optimization_suggestions(void) {
    printf("\n进一步优化建议:\n\n");

    printf("1. 硬件优化:\n");
    printf("   □ 使用更低功耗的MCU(如STM32L0系列)\n");
    printf("   □ 添加传感器电源控制开关\n");
    printf("   □ 使用低功耗稳压器\n");
    printf("   □ 优化PCB布局减少漏电流\n\n");

    printf("2. 软件优化:\n");
    printf("   □ 使用更低的时钟频率\n");
    printf("   □ 优化Flash写入策略\n");
    printf("   □ 减少串口调试输出\n");
    printf("   □ 使用更深的睡眠模式\n\n");

    printf("3. 算法优化:\n");
    printf("   □ 实现数据压缩\n");
    printf("   □ 智能采样(温度变化大时增加频率)\n");
    printf("   □ 异常检测(只记录异常数据)\n");
    printf("   □ 预测算法(减少不必要的采样)\n\n");

    printf("4. 系统优化:\n");
    printf("   □ 使用更大容量的电池\n");
    printf("   □ 添加能量收集(太阳能)\n");
    printf("   □ 实现电池电量监测\n");
    printf("   □ 低电量保护机制\n");
}

总结

本教程全面介绍了嵌入式系统低功耗设计的核心技术。

核心要点回顾

  1. 睡眠模式:
  2. 睡眠模式:快速响应,中等功耗
  3. 停止模式:低功耗,保持数据
  4. 待机模式:最低功耗,数据丢失
  5. 根据应用需求选择合适的模式

  6. 时钟门控:

  7. 关闭不使用的外设时钟
  8. 动态调整系统时钟频率
  9. 实现智能时钟管理
  10. 功耗与频率成正比

  11. 外设管理:

  12. 按需使能外设
  13. 使用完毕及时关闭
  14. 配置GPIO为低功耗模式
  15. 批量处理提高效率

  16. 功耗测量:

  17. 使用合适的测量工具
  18. 测量不同工作模式的功耗
  19. 计算平均功耗和电池寿命
  20. 持续监控和优化

  21. 优化策略:

  22. 间歇工作模式
  23. 事件驱动架构
  24. 数据缓存策略
  25. 硬件和软件协同优化

设计流程总结

1. 需求分析
   ├─ 功能需求
   ├─ 功耗目标
   ├─ 电池寿命要求
   └─ 性能要求

2. 功耗预算
   ├─ 列出所有功耗源
   ├─ 计算各模式功耗
   ├─ 估算平均功耗
   └─ 验证是否满足目标

3. 架构设计
   ├─ 选择低功耗MCU
   ├─ 设计工作模式
   ├─ 规划睡眠策略
   └─ 设计唤醒机制

4. 详细设计
   ├─ 实现睡眠模式
   ├─ 配置时钟门控
   ├─ 管理外设电源
   └─ 优化代码

5. 测试验证
   ├─ 测量实际功耗
   ├─ 对比设计目标
   ├─ 识别问题
   └─ 持续优化

最佳实践

  1. 设计原则:
  2. 不用的就关掉
  3. 能慢就不快
  4. 能低压就不高压
  5. 能睡就不醒

  6. 常见错误:

  7. ❌ 忘记关闭外设时钟
  8. ❌ GPIO配置不当
  9. ❌ 睡眠模式选择不当
  10. ❌ 唤醒源配置错误
  11. ❌ 功耗测量不准确

  12. 设计建议:

  13. ✅ 从一开始就考虑低功耗
  14. ✅ 使用低功耗MCU和器件
  15. ✅ 实现完善的电源管理
  16. ✅ 持续测量和优化
  17. ✅ 预留优化空间

延伸阅读

推荐进一步学习的内容:

  1. 进阶主题:
  2. 电池供电系统设计要点
  3. 动态电源管理(DPM)技术
  4. 超低功耗传感器节点设计

  5. 相关知识:

  6. 嵌入式系统电源设计基础
  7. 电源监控与保护电路设计
  8. 电池管理系统(BMS)设计实战

  9. 实践项目:

  10. 低功耗物联网节点
  11. 可穿戴设备电源管理

参考资料

  1. 《Ultra-Low-Power Wireless Technologies for Sensor Networks》
  2. 《Low-Power Design Essentials》- Jan M. Rabaey
  3. STM32L系列超低功耗应用笔记 - STMicroelectronics
  4. 《嵌入式系统低功耗设计》- 清华大学出版社
  5. ARM Cortex-M低功耗技术白皮书

练习题:

  1. 计算平均功耗:
  2. 工作:50mA,1秒
  3. 睡眠:0.5mA,59秒
  4. 计算平均电流和功耗(@3.3V)
  5. 估算2000mAh电池的寿命

  6. 设计睡眠策略:

  7. 系统需要每小时采集一次数据
  8. 采集时间约2秒
  9. 选择合适的睡眠模式
  10. 设计唤醒机制

  11. 优化GPIO配置:

  12. 有10个未使用的GPIO
  13. 当前配置为浮空输入
  14. 测量功耗并优化配置
  15. 对比优化前后的功耗

  16. 实现间歇工作模式:

  17. 工作1秒,睡眠9秒
  18. 使用RTC定时唤醒
  19. 测量平均功耗
  20. 计算节能比例

实践任务:

  1. 测量开发板在不同模式下的功耗
  2. 实现一个简单的低功耗应用
  3. 优化现有项目的功耗
  4. 设计一个电池供电的传感器节点
  5. 使用功率分析仪分析功耗波形

下一步:建议学习 电池供电系统设计要点,深入了解电池管理和充电技术。


常见问题解答:

Q: 如何选择合适的睡眠模式?

A: 考虑以下因素: - 唤醒时间要求:快速响应用睡眠模式 - 功耗要求:极低功耗用待机模式 - 数据保持:需要保持数据用停止模式 - 唤醒源:确认睡眠模式支持所需唤醒源

Q: 为什么实际功耗比预期高?

A: 常见原因: - GPIO配置不当(浮空输入) - 外设时钟未关闭 - 有外设仍在工作 - 漏电流(PCB设计问题) - 测量方法不准确

Q: 如何进一步降低功耗?

A: 优化方向: - 使用更低功耗的MCU - 降低工作频率和电压 - 优化工作周期(减少工作时间) - 使用更深的睡眠模式 - 关闭所有不必要的功能

Q: 睡眠模式下如何调试?

A: 调试方法: - 使用LED指示状态 - 使用GPIO输出标记 - 唤醒后输出日志 - 使用低功耗调试模式 - 使用功率分析仪观察波形

Q: 如何验证低功耗设计?

A: 验证方法: - 测量各模式功耗 - 计算平均功耗 - 长期运行测试 - 电池寿命测试 - 温度测试(不同温度下的功耗)