打印
[应用相关]

STM32中的电源控制(PWR: Power Control)

[复制链接]
374|0
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
tpgf|  楼主 | 2024-10-24 12:04 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
1 基本介绍
1.1 引言
电源控制(PWR: Power Control)。电源对电子设备来说非常重要,它是保证系统稳定运行的基础。在保证系统能稳定运行的同时,对嵌入式设备一般又有低功耗的需求。

1.2 电源框图





1.2.1 VDD供电区域
VDD是数字电的正极,VSS是数字电的负极,电压3.3V。给I/O电路,待机电路和电压调节器供电。

1.2.2 1.8V供电区域
CPU核心,内部存储器,内置的数字外设都是工作在1.8V的电压。1.8V的低电压是由从电压调节器得到的。

1.2.3 后备供电区域
VBAT引脚会接电池和其他电源,当VDD断电时,可以保存备份寄存器的内容和给RTC供电。

VBAT脚也为RTC、LSE振荡器和PC13至PC15供电,这保证当主要电源被切断时RTC能继续工作。切换到VBAT供电由复位模块中的掉电复位功能控制。如果应用中没有使用外部电池,VBAT必须连接到VDD引脚上。

1.2.4 电压调节器
复位后,电压调节器总是工作使能的。有3种不同的工作模式:

(1)开启模式:调节器以正常功耗模式提供1.8V电源(内核,内存和外设)。

(2)低功耗模式:调节器以低功耗模式提供1.8V电源,用于保存寄存器和SRAM的内容。

(3)关闭模式:调节器停止供电。除了备用电路和备份域外,寄存器和SRAM的内容全部丢失。

1.3 上电复位和掉电复位



复位和解除复位有一个40mv的迟滞电压。当电压大于VPOR时解除复位,当电压小于VPDR时进入复位。设置2个阈值的作用是当电压在附近抖动的时候防止频繁的复位和解除复位。

1.4 可编程电压检测器(PVD)



用户可以利用PVD对VDD电压与电源控制寄存器(PWR_CR)中的PLS[2:0]位进行比较来监控电源。当电压达到阈值的时候,不是产生复位,而是产生中断。可以在中断响应函数内做一些紧急处理。

1.5 低功耗
STM32F10xxx有三种低功耗模式:



1.5.1 睡眠模式



1.5.2 停机模式



1.5.3 待机模式



2 案例
2.1 低功耗案例1:睡眠模式
需求描述:

让MCU进入睡眠模式,然后通过串口发送消息来唤醒MCU退出睡眠模式。观察LED在进入休眠模式后是否仍然开启。

2.1.1 寄存器版本
/**
* 进入睡眠模式的效果 => 让代码运行到某个位置挂起等待  不再继续往下运行
* 等到有任意一个中断  =>  会自动唤醒睡眠模式  =>  代码会继续往下运行 前面运行的所有的东西都不会丢
*/
void enter_sleep_mode(void)
{
        /* 1. 将睡眠模式调整为普通睡眠模式 0是普通睡眠  1是深度睡眠 */
        SCB->SCR &= ~SCB_SCR_SLEEPDEEP;

        /* 2. 调用方法进入到睡眠模式 */
        __WFI();
}

int main(void)
{
        /*  测试开发板的睡眠模式 */
        Driver_USART1_Init();
        printf("hello sleep...\n");

        uint8_t count = 0;
        while (1)
        {
                printf("代码执行完毕,5s之后进入到睡眠模式%d\n",count);
                count++;
                Delay_ms(5000);// 一定不能省略   需要有一定的正常运行模式时间
                enter_sleep_mode();

                printf("被唤醒\n");
        }
}




2.1.2 HAL库版本
uint8_t tmp = 0;
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
  if (huart->Instance == USART1)
  {
    printf("%c\n", tmp);
      //确保每次接收数据都能进中断,否者不能由串口中断唤醒
    HAL_UART_Receive_IT(&huart1, &tmp, 1);
  }
}

int main(void)
{
    ...

/* USER CODE BEGIN 2 */
  printf("hello sleep hal...\n");

  // 调用usart的接收数据函数  =>  包含中断

  HAL_UART_Receive_IT(&huart1, &tmp, 1);

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    printf("正常代码运行完成  5s之后进入到睡眠模式\n");
    HAL_Delay(5000);

    // HAL默认设置 -> 系统滴答定时器的中断
    // hal使用睡眠模式  需要手动关闭系统滴答定时器
    HAL_SuspendTick();

    // 进入到睡眠模式
    // Regulator: 表示电压调节器的模式  -> 可以选择低功耗模式 -> 睡眠模式下这个参数没用
    // SLEEPEntry: 选择调用WFI还是WFE
    HAL_PWR_EnterSLEEPMode(0, PWR_SLEEPENTRY_WFI);

    // 重新打开系统滴答定时器
    HAL_ResumeTick();
    printf("被唤醒\n");

    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}



2.1.3 现象—register
进入睡眠模式,任意中断即可唤醒,这里我采用串口中断发送任意数据。



2.2 低功耗案例2:停机模式
需求描述:让MCU进入停机模式。停机模式需要外部中断才能唤醒,所以要开启外部中断。(串口不是外部中断)

2.2.1 寄存器版本

void enter_stop_mode(void)
{
        /* 1. 将睡眠模式调整为深度睡眠模式 */
        SCB->SCR |= SCB_SCR_SLEEPDEEP;

        /* 2. 设置深度睡眠模式对应停机模式 */
        RCC->APB1ENR |= RCC_APB1ENR_PWREN;
        // 选择停机模式
        PWR->CR &= ~PWR_CR_PDDS;
        // 设置电压调节器在停机模式下的情况
        PWR->CR &= ~PWR_CR_LPDS;

        /* 3. 设备进入睡眠模式  */
        __WFI();
}

void system_clock_config(void)
{
        // RCC_CR 和 RCC_CFGR中管理系统时钟的配置选择
        // 1. 使能高速外部时钟
        RCC->CR |= RCC_CR_HSEON;

        // 等待外部高速时钟准备完成
        while ((RCC->CR & RCC_CR_HSERDY) == 0)
                ;

        // 2. 使能PLL倍频器
        RCC->CR |= RCC_CR_PLLON;
        // 等待倍频器准备完成
        while ((RCC->CR & RCC_CR_PLLRDY) == 0)
                ;

        // 3. 选择PLL作为系统时钟的接入  系统时钟切换
        RCC->CFGR |= RCC_CFGR_SW_1;
        RCC->CFGR &= ~RCC_CFGR_SW_0;

        // 等待系统时钟切换完成
        while ((RCC->CFGR & RCC_CFGR_SWS) == 0)
                ;
}

void get_system_clock(uint32_t *system_clock,
                                          uint32_t *AHB_clock,
                                          uint32_t *APB1_clock,
                                          uint32_t *APB2_clock)
{
        /* 1. 获取系统时钟的频率 */
        // 1.1 系统时钟源
        uint32_t system_clock_source = RCC->CFGR & RCC_CFGR_SWS;
        if (system_clock_source == RCC_CFGR_SWS_HSE)
        {
                *system_clock = HSE_VALUE;
        }
        else if (system_clock_source == RCC_CFGR_SWS_HSI)
        {
                *system_clock = HSI_VALUE;
        }
        else if (system_clock_source == RCC_CFGR_SWS_PLL)
        {
                // 1.2 获取PLL倍频
                uint32_t mul = ((RCC->CFGR & RCC_CFGR_PLLMULL) >> 18) + 2  ;
                *system_clock = HSE_VALUE * mul;
        }

        /* 2. 获取总线AHB时钟 */
        // 2.1 获取分频系数 => 超过16分频会直接到64分频
        uint32_t AHB_pre = (RCC->CFGR & RCC_CFGR_HPRE) >> 4;
        if (AHB_pre & 8)
        {
                // 有分频
                *AHB_clock = *system_clock >> ((AHB_pre & 7) + 1);
        }
        else
        {
                // 不分频
                *AHB_clock = *system_clock;
        }

        /* 3. 获取高速外设APB2时钟 */
        // 3.1 获取APB2分频系数
        uint32_t APB2_pre = (RCC->CFGR & RCC_CFGR_PPRE2) >> 11;
        if (APB2_pre & 4)
        {
                // 需要分频
                *APB2_clock = *AHB_clock >> ((APB2_pre & 3) + 1);
        }
        else
        {
                // 没有分频
                *APB2_clock = *AHB_clock;
        }
       
        /* 4. 获取低速外设APB1时钟 */
        // 4.1 获取低速外设分频
        uint32_t APB1_pre = (RCC->CFGR & RCC_CFGR_PPRE1) >> 8;
        if (APB1_pre & 4)
        {
                // 需要分频
                *APB1_clock = *AHB_clock >> ((APB1_pre & 3) + 1);
        }
        else
        {
                // 没有分频
                *APB1_clock = *AHB_clock;
        }
       
}

int main(void)
{

        /*  测试开发板的停机模式 */
        Driver_USART1_Init();
        Driver_LED_Init();
        Driver_Key_Init();
        printf("hello stop...\n");

        Driver_LED_On(LED1);

        while (1)
        {
                printf("代码运行完成 5s之后进入停机模式\n");
                Delay_ms(5000);
                enter_stop_mode();

                // 停机唤醒之后还没有配置系统时钟
                uint32_t pre_system_clock,pre_AHB_clock,pre_APB1_clock,pre_APB2_clock;
                get_system_clock(&pre_system_clock,&pre_AHB_clock,&pre_APB1_clock,&pre_APB2_clock);

                // 必须单独手动去配置系统时钟  =>  调整为进入停止模式之前的时钟频率
                system_clock_config();

                uint32_t system_clock,AHB_clock,APB1_clock,APB2_clock;
                get_system_clock(&system_clock,&AHB_clock,&APB1_clock,&APB2_clock);

                printf("配置时钟之前的系统时钟: sys:%d,AHB:%d,APB1:%d,APB2:%d\n",pre_system_clock,pre_AHB_clock,pre_APB1_clock,pre_APB2_clock);

                printf("配置时钟之后的系统时钟: sys:%d,AHB:%d,APB1:%d,APB2:%d\n",system_clock,AHB_clock,APB1_clock,APB2_clock);

                // 串口通信出现乱码 => 波特率不相同
                printf("被唤醒,可以继续使用\n");
        }
}


2.2.2 HAL库版本
前置准备:1.设置PF10为外部中断,设置PF10为下拉输入,因为PF10的电路图如下。



/* USER CODE BEGIN 2 */
  printf("hello stop hal...\n");

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    printf("正常代码运行完成  5s之后进入到停机模式\n");
    HAL_Delay(5000);

    // 关闭滴答定时器
    HAL_SuspendTick();
    // 进入到停机模式
    // (1) 电压调节器  低功耗  还是  正常模式
    // (2) 使用中断唤醒  还是  事件唤醒
    HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON,PWR_STOPENTRY_WFI);

    // 打开滴答定时器
    HAL_ResumeTick();
    // 重新配置时钟
    SystemClock_Config();
    printf("被唤醒 可以正常使用\n");

    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */



2.2.3 现象
register

按下按键3,触发外部中断,停机模式被唤醒,并且打印各个时钟。



hal库
按下按键3,触发外部中断,停机模式被唤醒。



2.3 低功耗案例3:待机模式
需求描述:寄存器操作进入待机模式。待机模式的唤醒方式比较有限。我们这次使用WKUP引脚的上升沿唤醒。PA0就是WKUP引脚。

2.3.1 寄存器版本
void enter_standby_mode(void)
{
        // 开启PWR的RCC时钟需要提前进行
        /* 1. 选择睡眠模式为深度睡眠  */
        SCB->SCR |= SCB_SCR_SLEEPDEEP;

        /* 2. 深睡眠模式对应的是待机模式 */
        PWR->CR |= PWR_CR_PDDS;

        /* 3. 使用PA0的wake up功能退出待机模式 */
        PWR->CSR |= PWR_CSR_EWUP;

        /* 4. 进入到待机模式 */
        __WFI();

}

int main(void)
{
        /*  测试开发板的待机模式 */
        Driver_USART1_Init();
        Driver_LED_Init();
        Driver_Key_Init();
        printf("hello standby...\n");

        Driver_LED_On(LED3);
        //提前打开RCC中的PWR时钟
        RCC->APB1ENR |= RCC_APB1ENR_PWREN;

    //判断是由复位按键还是待机模式下被唤醒
        if (PWR->CSR & PWR_CSR_WUF)
        {
                printf("从待机模式被唤醒\n");
                // 从待机模式唤醒  需要清除对应的标志位
                PWR->CR |= PWR_CR_CSBF;
                PWR->CR |= PWR_CR_CWUF;
        }
        else
        {
                printf("复位键按下运行\n");
        }
       

        while (1)
        {
                printf("代码执行完毕,5s后进入待机模式\n");
                Delay_s(5); // 一定不能省略   需要有一定的正常运行模式时间
                // 进入待机模式的后面是不能够有代码的
                // 如果从待机模式中唤醒  ->  是从整个main方法的开头来运行的
                enter_standby_mode();
        }
}



2.3.2 HAL库版本
使用PA0的上升沿将其唤醒,所以我们需要打开SYS中的Wake-Up



/* USER CODE BEGIN 2 */
HAL_GPIO_WritePin(LED2_GPIO_Port,LED2_Pin,GPIO_PIN_RESET);
printf("hello standby hal...\n");

if (__HAL_PWR_GET_FLAG(PWR_CSR_WUF))
{
  //从待机模式被唤醒
  printf("从待机模式被唤醒\n");
  __HAL_PWR_CLEAR_FLAG(PWR_CSR_WUF);
  __HAL_PWR_CLEAR_FLAG(PWR_CSR_SBF);
}
else
{
    printf("从复位启动\n");

}

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    printf("代码运行完成 5s之后进入待机模式 \n");
    HAL_Delay(5000);
    // 开启wake up唤醒功能
    HAL_PWR_EnableWakeUpPin(PWR_WAKEUP_PIN1);
    HAL_PWR_EnterSTANDBYMode();
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}



2.3.3 现象—hal
使用杜邦线连接PA0,然后触碰GND后触碰3v3得到一个上升沿触发待机唤醒。



3 总结



————————————————

                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/m0_74967868/article/details/143062163

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

2028

主题

15903

帖子

14

粉丝