嵌入式小白跟随江科大学习笔记 -- 2024.4.8

一、PWR电源控制

PWR简介

PWR Power Control )电源控制
PWR 负责管理 STM32 内部的电源供电部分,可以实现可编程电压监测器和低功耗模式的功能
可编程电压监测器( PVD )可以监控 VDD 电源电压,当 VDD 下降到 PVD 阀值以下或上升到 PVD 阀值之上时, PVD 会触发中断,用于执行紧急关闭任务,在使用电池供电并需要高安全性的设备时监测其电压,在电压下降的时候停止运行,防备意外
低功耗模式包括睡眠模式( Sleep )、停机模式( Stop )和待机模式( Standby ),可在系统空闲时,降低 STM32 的功耗,延长设备使用时间

电源框图 

嵌入式小白跟随江科大学习笔记 -- 2024.4.8_第1张图片

电源分为三部分,VDDA模拟部分供电区域,VDD数字部分供电区域和VBAT后备供电区域

VDDA部分供电主要负责模拟部分的供电,包括图中设备吗,这些电路的供电正极是VDDA负极是VSSA,其中AD转换器还有两根参考电压的供电脚,VREF+和VREF-,这两个脚在内部分别接到正负极

VDD数字部分电路右侧部分是通过电压调节器降压到1.8V,需要注意两个区域中分别存在的部分

低功耗模式

嵌入式小白跟随江科大学习笔记 -- 2024.4.8_第2张图片

这三种模式从上到下是越来越省电的,同时也越来越难以唤醒
在停机模式下,电压调节器处于开启或者低功耗模式取决于LPDS位,LPDS=0开启,LPDS=1则进入低功耗,使用WFI需要使用外部中断的中断模式唤醒,WFE则是通过事件模式唤醒

模式选择:

嵌入式小白跟随江科大学习笔记 -- 2024.4.8_第3张图片

在执行WFI/WFE之前就需要将其他的寄存器都设置好才能进入指定的低功耗模式 ,睡眠模式(等待中断退出)的意思是会等待当前正在处理的所有中断结束再进入睡眠

模式特性:

        睡眠模式:

执行完 WFI/WFE 指令后, STM32 进入睡眠模式,程序暂停运行,唤醒后程序从暂停的地方继续运行,可以在主循环后加入WFI/WFE指令,每唤醒一次主循环执行一次
SLEEPONEXIT 位决定 STM32 执行完 WFI WFE 后,是立刻进入睡眠,还是等 STM32 从最低优先级的中断处理程序中退出时进入睡眠
在睡眠模式下,所有的 I/O 引脚都保持它们在运行模式时的状态
WFI 指令进入睡眠模式,可被任意一个 NVIC 响应的中断唤醒
WFE 指令进入睡眠模式,可被唤醒事件唤醒,事件唤醒参考手册

        

        停止模式:

执行完 WFI/WFE 指令后, STM32 进入停止模式,程序暂停运行,唤醒后程序从暂停的地方继续运行
1.8V 供电区域的所有时钟都被停止, PLL HSI HSE 被禁止, SRAM 和寄存器内容被保留下来
在停止模式下,所有的 I/O 引脚都保持它们在运行模式时的状态
当一个中断或唤醒事件导致退出停止模式时, HSI 被选为系统时钟
在芯片默认情况下是使用HSE+PLL倍频得到的72MHz的时钟频率,但在退出停止模式后不会自动使用HSE时钟,而是直接使用HSI的8MHz时钟,所以在停止模式唤醒后应当第一时间重启HSE,配置主频为72MHz
当电压调节器处于低功耗模式下,系统从停止模式退出时,会有一段额外的启动延时
WFI 指令进入停止模式,可被任意一个 EXTI 中断唤醒
WFE 指令进入停止模式,可被任意一个 EXTI 事件唤醒

        待机模式:

执行完 WFI/WFE 指令后, STM32 进入待机模式,唤醒后程序从头开始运行
整个 1.8V 供电区域被断电, PLL HSI HSE 也被断电, SRAM 和寄存器内容丢失,只有备份的寄存器和待机电路维持供电
在待机模式下,所有的 I/O引脚变为高阻态(浮空输入)
WKUP 引脚的上升沿、 RTC 闹钟事件的上升沿、 NRST 引脚上外部复位、 IWDG 复位退出待机模式

二、低功耗模式实验

本章节实验都是对过去实验的改变,因此分为四部分

1.改变主频

系统时钟原理

在进行实验之前我们需要知道改变主频的方法是什么,而在system_stm32f10x.c文件中有如下宏定义方法:

//如果使用超值形态的话
#if defined (STM32F10X_LD_VL) || (defined STM32F10X_MD_VL) || (defined STM32F10X_HD_VL)
/* #define SYSCLK_FREQ_HSE    HSE_VALUE */
 #define SYSCLK_FREQ_24MHz  24000000
#else  //否则
/* #define SYSCLK_FREQ_HSE    HSE_VALUE */
/* #define SYSCLK_FREQ_24MHz  24000000 */ 
/* #define SYSCLK_FREQ_36MHz  36000000 */
/* #define SYSCLK_FREQ_48MHz  48000000 */
/* #define SYSCLK_FREQ_56MHz  56000000 */
#define SYSCLK_FREQ_72MHz  72000000
#endif



void SystemInit (void)
{
  /* Reset the RCC clock configuration to the default reset state(for debug purpose) */
  /* Set HSION bit */
  RCC->CR |= (uint32_t)0x00000001;

  /* Reset SW, HPRE, PPRE1, PPRE2, ADCPRE and MCO bits */
#ifndef STM32F10X_CL
  RCC->CFGR &= (uint32_t)0xF8FF0000;
#else
  RCC->CFGR &= (uint32_t)0xF0FF0000;
#endif /* STM32F10X_CL */   
  
  /* Reset HSEON, CSSON and PLLON bits */
  RCC->CR &= (uint32_t)0xFEF6FFFF;

  /* Reset HSEBYP bit */
  RCC->CR &= (uint32_t)0xFFFBFFFF;

  /* Reset PLLSRC, PLLXTPRE, PLLMUL and USBPRE/OTGFSPRE bits */
  RCC->CFGR &= (uint32_t)0xFF80FFFF;

在这段宏定义中,需要使用多少赫兹的主频便将对应宏定义进行去注释即可,而这个文件最初是只读的文件,解除只读的方法是在文件夹中查看属性解除只读,而在去注释需要的宏定义后,则是通过以下的函数进行的时钟配置:

#ifdef SYSCLK_FREQ_HSE
  uint32_t SystemCoreClock         = SYSCLK_FREQ_HSE;        /*!< System Clock Frequency (Core Clock) */
#elif defined SYSCLK_FREQ_24MHz
  uint32_t SystemCoreClock         = SYSCLK_FREQ_24MHz;        /*!< System Clock Frequency (Core Clock) */
#elif defined SYSCLK_FREQ_36MHz
  uint32_t SystemCoreClock         = SYSCLK_FREQ_36MHz;        /*!< System Clock Frequency (Core Clock) */
#elif defined SYSCLK_FREQ_48MHz
  uint32_t SystemCoreClock         = SYSCLK_FREQ_48MHz;        /*!< System Clock Frequency (Core Clock) */
#elif defined SYSCLK_FREQ_56MHz
  uint32_t SystemCoreClock         = SYSCLK_FREQ_56MHz;        /*!< System Clock Frequency (Core Clock) */
#elif defined SYSCLK_FREQ_72MHz
  uint32_t SystemCoreClock         = SYSCLK_FREQ_72MHz;        /*!< System Clock Frequency (Core Clock) */
#else /*!< HSI Selected as System Clock source */
  uint32_t SystemCoreClock         = HSI_VALUE;        /*!< System Clock Frequency (Core Clock) */
#endif

可以看出,以上代码就是根据宏定义的值对时钟频率进行的配置,在选择响应的宏定义后进入时钟配置的函数,此处用72Mhz作为实例:

static void SetSysClockTo72(void)
{
  __IO uint32_t StartUpCounter = 0, HSEStatus = 0;
  
  /* SYSCLK, HCLK, PCLK2 and PCLK1 configuration ---------------------------*/    
  /* Enable HSE */    
  RCC->CR |= ((uint32_t)RCC_CR_HSEON);
 
  /* Wait till HSE is ready and if Time out is reached exit */
  do
  {
    HSEStatus = RCC->CR & RCC_CR_HSERDY;
    StartUpCounter++;  
  } while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));

  if ((RCC->CR & RCC_CR_HSERDY) != RESET)
  {
    HSEStatus = (uint32_t)0x01;
  }
  else
  {
    HSEStatus = (uint32_t)0x00;
  }  

  if (HSEStatus == (uint32_t)0x01)
  {
    /* Enable Prefetch Buffer */
    FLASH->ACR |= FLASH_ACR_PRFTBE;

    /* Flash 2 wait state */
    FLASH->ACR &= (uint32_t)((uint32_t)~FLASH_ACR_LATENCY);
    FLASH->ACR |= (uint32_t)FLASH_ACR_LATENCY_2;    

 
    /* HCLK = SYSCLK */
    RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;
      
    /* PCLK2 = HCLK */
    RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1;
    
    /* PCLK1 = HCLK */
    RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV2;

#ifdef STM32F10X_CL
    /* Configure PLLs ------------------------------------------------------*/
    /* PLL2 configuration: PLL2CLK = (HSE / 5) * 8 = 40 MHz */
    /* PREDIV1 configuration: PREDIV1CLK = PLL2 / 5 = 8 MHz */
        
    RCC->CFGR2 &= (uint32_t)~(RCC_CFGR2_PREDIV2 | RCC_CFGR2_PLL2MUL |
                              RCC_CFGR2_PREDIV1 | RCC_CFGR2_PREDIV1SRC);
    RCC->CFGR2 |= (uint32_t)(RCC_CFGR2_PREDIV2_DIV5 | RCC_CFGR2_PLL2MUL8 |
                             RCC_CFGR2_PREDIV1SRC_PLL2 | RCC_CFGR2_PREDIV1_DIV5);
  
    /* Enable PLL2 */
    RCC->CR |= RCC_CR_PLL2ON;
    /* Wait till PLL2 is ready */
    while((RCC->CR & RCC_CR_PLL2RDY) == 0)
    {
    }
    
   
    /* PLL configuration: PLLCLK = PREDIV1 * 9 = 72 MHz */ 
    RCC->CFGR &= (uint32_t)~(RCC_CFGR_PLLXTPRE | RCC_CFGR_PLLSRC | RCC_CFGR_PLLMULL);
    RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLXTPRE_PREDIV1 | RCC_CFGR_PLLSRC_PREDIV1 | 
                            RCC_CFGR_PLLMULL9); 
#else    
    /*  PLL configuration: PLLCLK = HSE * 9 = 72 MHz */
    RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE |
                                        RCC_CFGR_PLLMULL));
    RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLMULL9);
#endif /* STM32F10X_CL */

    /* Enable PLL */
    RCC->CR |= RCC_CR_PLLON;

    /* Wait till PLL is ready */
    while((RCC->CR & RCC_CR_PLLRDY) == 0)
    {
    }
    
    /* Select PLL as system clock source */
    RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
    RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL;    

    /* Wait till PLL is used as system clock source */
    while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08)
    {
    }
  }
  else
  { /* If HSE fails to start-up, the application will have wrong clock 
         configuration. User can add here some code to deal with this error */
  }
}
#endif

配置的逻辑是:第一步,使能HSE外部高速时钟,即8Mhz晶振,第二步,等待HSERDY,若为1则表示晶振开启成功进入if,第三步,配置HCLK(AHB)、PCLK2(APB2)和PCLK1(APB1)的分频器分别为1、1、2,所以AHB和APB2的时钟频率为72Mhz,APB1是36M,define中的配置是对CL型号的配置,我们使用归的芯片是MD型号,第四步,配置锁相环为9倍频,HSE为8M,因此锁相环输出时钟为72Mhz,然后使能锁相环,等待其准备就绪,选择锁相环输出作为系统时钟,等待锁相环成为系统时钟。注:如果HSE没接或者坏了则上述代码都不执行系统会使用默认开启的HSI内部8M时钟作为主频,而在上述代码最后的else中加入操作也可以检测HSE时钟是否出现问题

不同的时钟主频都可以在时钟树上找到对应的主线进行配置

总结一下:首先SystemInit()函数中开启HSI并进行恢复缺省配置,然后调用SetSysClock分配函数根据宏定义选择执行不同的配置函数,72M的配置函数为SetSysClockTo72(),在这其中就是真正的配置。

功能代码:

改变主频的代码就是将system中的主频宏定义根据需要进行去注释再利用固定默认72M的Delay函数观察变化频率即可

int main(void)
{
	OLED_Init();
	
	OLED_ShowString(1,1,"SYSCLK:");
	OLED_ShowNum(1,8,SystemCoreClock,8);//显示主频,SystemCoreClock代指主频值
	
	while(1)
	{
		//在将72M主频改为36M后闪烁速度也慢了一倍,原因就是Delay函数中是以72作为默认计算,如果想要时间正确匹配则可以将函数中的72换为SystemCoreClock
		OLED_ShowString(2,1,"Running");
		Delay_ms(500);
		OLED_ShowString(2,1,"       ");
		Delay_ms(500);
	}
	
}

2.睡眠模式+串口发送+接收

工作原理

我们需要使用STM32作为下位机来接收从电脑发送的指令,执行相应功能,电脑随时都有可能发送指令,但是如果长时间不发送指令就会多出无意义的耗电,因此对于这种靠中断触发并且除了中断外无其他操作的额代码就可以加入低功耗模式以省电,而因为要使用中断唤醒所以可以使用睡眠模式和停机模式,本实验使用睡眠模式

程序代码

因为只是在源代码基础上加入低功耗,所以只在9-2代码基础上更改了主函数

uint8_t RXData;

int main(void)
{
	OLED_Init();
	Serial_Init();
	
	while(1)
	{
		if(Serial_GetRXFlag()==1){//对RXNE标志位不停查询检测
			RXData=Serial_GetRXData();
			Serial_SendByte(RXData);
			OLED_ShowHexNum(1,1,RXData,2);
		}
		OLED_ShowString(2,1,"Running");
		Delay_ms(100);
		OLED_ShowString(2,1,"       ");
		Delay_ms(100);
		
		__WFI(); //在每次接收并发送数据后进入睡眠模式,直接执行WFI为中断唤醒的睡眠模式
		//如果想要进入其他的低功耗模式则需要直接对寄存器进行配置
	}
	
}
 

3.停止模式+红外传感器模式

PWR部分库函数指引

//使能后备区域访问
void PWR_BackupAccessCmd(FunctionalState NewState);

//使能PVD功能
void PWR_PVDCmd(FunctionalState NewState);

//指定PVD阈值
void PWR_PVDLevelConfig(uint32_t PWR_PVDLevel);

//使能WKUP引脚唤醒功能,配合待机模式使用
void PWR_WakeUpPinCmd(FunctionalState NewState);

//使能停止模式,调用即进入停止模式
void PWR_EnterSTOPMode(uint32_t PWR_Regulator, uint8_t PWR_STOPEntry);

//调用进入待机模式
void PWR_EnterSTANDBYMode(void);

普通的睡眠模式只需要配置内核,而使用跟进一步的停止模式和待机模式则需要借助外设PWR的库函数进行

程序代码

本实验仍是只在5-1的基础上更改了主程序

int main(void)
{
	OLED_Init();
	OLED_ShowString(1,1,"Count:");
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE);//想要使用PWR外设先要开启时钟
	
	CountSensor_Init();
	while(1)
	{
		OLED_ShowNum(1,7,GetCount(),5);//非低功耗模式下这个get就是无意义的耗电
		
		OLED_ShowString(2,1,"Running");
		Delay_ms(100);
		OLED_ShowString(2,1,"       ");
		Delay_ms(100);
		
		//配置停止模式,第一个参数指定电压调节器在停止模式里的状态,第二个参数是配置唤醒的方式
		PWR_EnterSTOPMode(PWR_Regulator_ON,PWR_STOPEntry_WFI);
		
		//因为每次从停止模式唤醒会优先使用HSI的8M主频导致卡顿,所以此处在唤醒后优先设置主频
		SystemInit();
	}
	//因为此程序是外部中断触发所以可以使用更省电的停止模式
	
}

你可能感兴趣的:(学习,笔记)