电源分为三部分,VDDA模拟部分供电区域,VDD数字部分供电区域和VBAT后备供电区域
VDDA部分供电主要负责模拟部分的供电,包括图中设备吗,这些电路的供电正极是VDDA负极是VSSA,其中AD转换器还有两根参考电压的供电脚,VREF+和VREF-,这两个脚在内部分别接到正负极
VDD数字部分电路右侧部分是通过电压调节器降压到1.8V,需要注意两个区域中分别存在的部分
在执行WFI/WFE之前就需要将其他的寄存器都设置好才能进入指定的低功耗模式 ,睡眠模式(等待中断退出)的意思是会等待当前正在处理的所有中断结束再进入睡眠
睡眠模式:
停止模式:
待机模式:
本章节实验都是对过去实验的改变,因此分为四部分
在进行实验之前我们需要知道改变主频的方法是什么,而在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);
}
}
我们需要使用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为中断唤醒的睡眠模式
//如果想要进入其他的低功耗模式则需要直接对寄存器进行配置
}
}
//使能后备区域访问
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();
}
//因为此程序是外部中断触发所以可以使用更省电的停止模式
}