STM32修改主频及三种睡眠模式实例演示

文章目录

  • 前言
  • 修改主频
  • 测试
  • 睡眠模式+串口收发
  • 接线图
  • 关于配置立刻睡眠和等待睡眠模式的寄存器
  • 串口配置
  • 测试
  • 执行流程
  • 停止模式+对射式红外传感器计数
  • 接线图
  • 配置红外传感器与外部中断
  • 测试
  • 注意
  • 待机模式+RTC实时时钟
  • 接线图
  • 时钟配置
  • 测试
  • 注意

  • 前言

    本内容主要实操修改主频与使用三种睡眠模式,需要理论介绍需见本专栏:https://blog.csdn.net/qq_53922901/article/details/138720115?spm=1001.2014.3001.5502

    编写代码前先看注意事项(末尾)


    修改主频

    主要查看system_stm32f10x.c与system_stm32f10x.h这两个文件

    system_stm32f10x.c中的注释提到,这个文件提供了两个外部可调用的函数和一个全局变量:

  • SystemInit():用于配置系统时钟
  • SystemCoreClock variable:选择时钟主频值
  • SystemCoreClockUpdate():用于更新时钟主频值
  • 可修改的频率:
    使用哪个就解除哪个注释

    主要流程
    SystemInit()会先设置HSI时钟,HSE出问题了就会变回HSI

    SystemCoreClockUpdate()再更新时钟频率值

    判断选择的主频进入对应的函数

    配置HSE,选择对应的锁相环得到对应的频率

    测试

    首先显示一下主频,然后定义一个1s周期的闪烁,程序正常运行

    #include "stm32f10x.h"                  // Device header
    #include "Delay.h"
    #include "OLED.h"
    
    int main(void)
    {
    	OLED_Init();
    	OLED_ShowString(1,1,"SYSCLK:");
    	OLED_ShowNum(1,8,SystemCoreClock,8);
    	while (1)
    	{
    		OLED_ShowString(2,1,"Running");
    		Delay_ms(500);
    		OLED_ShowString(2,1,"       ");
    		Delay_ms(500);
    	}
    }
    
    

    然后再把主频改为36MHz,代码不变,会发现Running的闪烁变得更慢了,因为主频降低了一半,而延时函数是对应72MHz写的,所以使闪烁的周期也变为了2s

    延时函数内容:

    睡眠模式+串口收发

    串口详情内容见本专栏:https://blog.csdn.net/qq_53922901/article/details/136078032?spm=1001.2014.3001.5502

    接线图

    关于配置立刻睡眠和等待睡眠模式的寄存器

    没有库函数快速配置,所以需要通过寄存器配置,不配置默认为立即睡眠模式

    串口配置

    Serial.c

    #include "stm32f10x.h"                  // Device header
    #include <stdio.h>
    #include <stdarg.h>
    
    uint8_t RxData;
    uint8_t RxFlag;
    
    void Serial_Init(void){
    	// 开启时钟
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
    	
    	// 初始化引脚
    	GPIO_InitTypeDef GPIO_InitStructure;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;		// 复用推挽输出
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;	// A9 发送数据
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		// 50Hz翻转速度
    	GPIO_Init(GPIOA, &GPIO_InitStructure);
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;		// 上拉输出
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;	// A10 接收数据
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		// 50Hz翻转速度
    	GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    	// 初始化串口配置
    	USART_InitTypeDef USART_InitStructure;
    	USART_InitStructure.USART_BaudRate = 9600; // 串口波特率
    	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // 不使用流控
    	USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; // 串口模式,发送+接收
    	USART_InitStructure.USART_Parity = USART_Parity_No; // 无校验
    	USART_InitStructure.USART_StopBits = USART_StopBits_1; // 选择一位停止位
    	USART_InitStructure.USART_WordLength = USART_WordLength_8b; // 不需要校验位,八位字长
    	USART_Init(USART1,&USART_InitStructure);
    	
    	// 开启中断
    	USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);
    	
    	//初始化NVIC
    	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);	// 分组
    	NVIC_InitTypeDef NVIC_InitStructure;
    	// 中断通道
    	NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
    	// 中断通道使能
    	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    	// 抢占优先级
    	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    	// 响应优先级
    	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    	NVIC_Init(&NVIC_InitStructure);
    
    	
    	// USART1使能
    	USART_Cmd(USART1,ENABLE);
    }
    
    // 发送函数
    void USART_SendByte(uint8_t Byte){
    	USART_SendData(USART1,Byte);
    	// 等待写入完成,写入完成之后会将标志位自动清0
    	while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);
    }
    
    // 发送数组函数
    void USART_SendArray(uint8_t *Array,uint16_t Length){
    	uint8_t i = 0;
    	for(i=0;i<Length;i++){
    		USART_SendData(USART1,Array[i]);
    		// 等待写入完成,写入完成之后会将标志位自动清0
    		while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);
    	}
    }
    
    // 发送字符串函数
    void USART_SendString(uint8_t *String){
    	uint8_t i = 0;
    	for(i=0;String[i]!='\0';i++){
    		USART_SendData(USART1,String[i]);
    		// 等待写入完成,写入完成之后会将标志位自动清0
    		while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);
    	}
    }
    
    // 返回X的Y次方
    uint32_t Serial_Pow(uint32_t X,uint32_t Y){
    	uint32_t Result = 1;
    	while(Y--){
    		Result *= X;
    	}
    	return Result;
    }
    // 发送数字函数
    void USART_SendNum(uint32_t Num,uint16_t Length){
    	uint8_t i = 0;
    	for(i=0;i<Length;i++){
    		USART_SendByte(Num / Serial_Pow(10,Length-i-1) % 10 + 0x30);
    		// 等待写入完成,写入完成之后会将标志位自动清0
    		while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);
    	}
    }
    
    //重定向fputc函数,fputc是printf函数的底层,printf通过不停的调用fputc来达到输出的效果
    //重定向到串口
    int fputc(int ch,FILE *f){
    	USART_SendByte(ch);
    	return ch;
    }
    
    // 封装使用sprintf输出到串口
    void Serial_Printf(char *format, ...)
    {
    	char String[100];
    	va_list arg;							// 可变参数列表
    	va_start(arg, format);		// 从format开始接收可变参数
    	vsprintf(String, format, arg);
    	va_end(arg);
    	USART_SendString((uint8_t*)String);
    }
    
    // 获取RxFlag
    uint8_t USART_GetRxFlag(void){
    	if(RxFlag == 1){
    		RxFlag = 0;
    		return 1;
    	}
    	return 0;
    }
    
    // 获取RxData
    uint8_t USART_GetRxData(void){
    	return RxData;
    }
    
    //中断函数
    void USART1_IRQHandler(void){
    	if(USART_GetITStatus(USART1,USART_IT_RXNE)==SET){
    		RxData = USART_ReceiveData(USART1);
    		RxFlag = 1;
    		USART_ClearITPendingBit(USART1,USART_IT_RXNE);
    	}
    }
    
    
    

    Serial.h

    #ifndef __SERIAL_H
    #define __SERIAL_H
    
    #include <stdio.h>
    
    void Serial_Init(void);
    void USART_SendByte(uint8_t Byte);
    void USART_SendArray(uint8_t *Array,uint16_t Length);
    void USART_SendString(uint8_t *String);
    void USART_SendNum(uint32_t Num,uint16_t Length);
    void Serial_Printf(char *format, ...);
    uint8_t USART_GetRxFlag(void);
    uint8_t USART_GetRxData(void);
    
    
    #endif
    
    

    测试

    利用串口发送数据进入中断来唤醒程序
    main.c

    #include "stm32f10x.h"                  // Device header
    #include "Delay.h"
    #include "OLED.h"
    #include "Serial.h"
    
    uint8_t Serial_RxData;
    
    int main(void)
    {
    	OLED_Init();
    	OLED_ShowString(1, 1, "RxData:");
    	
    	Serial_Init();
    	
    	while (1)
    	{
    		if (USART_GetRxFlag() == 1)
    		{
    			Serial_RxData = USART_GetRxData();
    			USART_SendByte(Serial_RxData);
    			OLED_ShowHexNum(1, 8, Serial_RxData, 2);
    		}
    		OLED_ShowString(2,1,"Running");
    		OLED_ShowString(2,1,"       ");
    		
    		// config sleep mode
    		// wait for interrupt
    		__WFI();
    	}
    }
    
    

    执行流程

    执行通过串口发送数据if(USART_GetITStatus(USART1,USART_IT_RXNE)== SET)成立,接收数据,RxFlag = 1,进入主函数while循环执行
    if (USART_GetRxFlag() == 1)
    {
    Serial_RxData = USART_GetRxData();
    USART_SendByte(Serial_RxData);
    OLED_ShowHexNum(1, 8, Serial_RxData, 2);
    }
    直到下一个__WFI();再次进入睡眠

    停止模式+对射式红外传感器计数

    停止模式需要使用外部中断唤醒,需要涉及内核外的电路,所以会使用到PWR库函数
    红外传感器与外部中断详情见本专栏:https://blog.csdn.net/qq_53922901/article/details/136007977?spm=1001.2014.3001.5502

    接线图

    配置红外传感器与外部中断

    IRSensor.c

    #include "stm32f10x.h"                  // Device header
    
    void IRSensor_Init(void){
    	// 配置时钟
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
    	// 初始化端口
    	GPIO_InitTypeDef GPIO_InitStructure;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_Init(GPIOB,&GPIO_InitStructure);
    	// 配置AFIO引脚选择
    	GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource14);
    	
    	EXTI_InitTypeDef EXTI_InitStructure;
    	// 选择中断线,14号端口对应14号线
    	EXTI_InitStructure.EXTI_Line = EXTI_Line14;
    	// 是否开启这条中断线
    	EXTI_InitStructure.EXTI_LineCmd = ENABLE;
    	// 中断模式还是事件模式
    	EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
    	// 触发方式,下降沿触发
    	EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
    	EXTI_Init(&EXTI_InitStructure);
    	
    	// 设置中断优先级组
    	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    	NVIC_InitTypeDef NVIC_InitStructure;
    	// 中断通道
    	NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;
    	// 是否打开通道
    	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    	// 抢占优先级
    	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    	// 相应优先级
    	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    	NVIC_Init(&NVIC_InitStructure);
    }
    
    uint16_t Count = 0;
    // 获取计数器的值
    uint16_t GetCount(void){
    	return Count;
    }
    
    // 中断函数
    void EXTI15_10_IRQHandler(void){
    	// 获取中断线是否打开
    	if(EXTI_GetITStatus(EXTI_Line14) == SET){
    		/*如果出现数据乱跳的现象,可再次判断引脚电平,以避免抖动*/
    		if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_14) == 0){
    			// 计数器+1
    			Count++;
    		}
    		// 清除中断
    		EXTI_ClearITPendingBit(EXTI_Line14);
    	}
    }
    
    

    IRSensor.h

    #ifndef __IRSENSOR_H
    #define __IRSENSOR_H
    void IRSensor_Init(void);
    void EXTI15_10_IRQHandler(void);
    uint8_t GetCount(void);
    
    
    
    #endif
    

    测试

    进入停止模式,当发生外部中断,执行中断函数

    然后到主函数完成数据的显示,直到下一个PWR_EnterSTOPMode(PWR_Regulator_ON,PWR_STOPEntry_WFI);重新进入停止模式

    main.c

    #include "stm32f10x.h"                  // Device header
    #include "Delay.h"
    #include "OLED.h"
    #include "IRSensor.h"
    
    int main(void)
    {
    	OLED_Init();
    	IRSensor_Init();
    	
    	// 开启PWR时钟
    	RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE);
    	
    	OLED_ShowString(1,1,"Count:");
    	while (1)
    	{
    		OLED_ShowNum(2,1,GetCount(),4);
    		
    		OLED_ShowString(3,1,"Running");
    		Delay_ms(200);
    		OLED_ShowString(3,1,"       ");
    		Delay_ms(200);
    		
    		// 进入停止模式(电压调节器状态:打开,唤醒模式:中断唤醒)
    		PWR_EnterSTOPMode(PWR_Regulator_ON,PWR_STOPEntry_WFI);
    		SystemInit();
    	}
    }
    
    

    PWR_EnterSTOPMode函数大致内容:

    注意

    当一个中断或唤醒事件导致退出停止模式时,HSI被选为系统时钟,即主频变为了8MHz,所以当按下复位键后,再次唤醒时程序执行速度会变慢,所以使用SystemInit();重新配置一次72MHz的主频

    待机模式+RTC实时时钟

    使用闹钟唤醒与WakeUp引脚(GPIOA0)唤醒

    接线图

    时钟配置

    ThisRTC.c

    #include "stm32f10x.h"                  // Device header
    #include <time.h>
    struct ThisRTC_Time{
    	uint16_t year;	// 年
    	uint8_t month;	// 月
    	uint8_t day;		// 日
    	uint8_t hour;		// 时
    	uint8_t min;		// 分
    	uint8_t sec;		// 秒	
    };
    extern struct ThisRTC_Time ThisRTC_Time1;
    
    void ThisRTC_Init(void){
    	// 打开PWR,BKP时钟
    	RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE);
    	RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP,ENABLE);
    	
    	// 使能PWR
    	PWR_BackupAccessCmd(ENABLE);
    	// 如果备份寄存器数据丢失则重新初始化
    	if(BKP_ReadBackupRegister(BKP_DR1) != 0X9999){
    	
    		// 启动LSE时钟源
    		RCC_LSEConfig(RCC_LSE_ON);
    		
    		// 等待LSE启动完成
    		while(RCC_GetFlagStatus(RCC_FLAG_LSERDY)!=SET);
    		
    		// 选择RTC时钟源为LSE
    		RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
    		
    		// 使能RTC时钟
    		RCC_RTCCLKCmd(ENABLE);
    		
    		// 等待同步
    		RTC_WaitForSynchro();
    		// 等待上一步写操作完成
    		RTC_WaitForLastTask();
    		
    		// 配置分频系数,自动进入配置模式并退出
    		RTC_SetPrescaler(32768-1);
    		// 等待上一步写操作完成
    		RTC_WaitForLastTask();
    		
    		void ThisRTC_SetTime(void);
    		ThisRTC_SetTime();
    		
    		// 写入备份寄存器
    		BKP_WriteBackupRegister(BKP_DR1,0X9999);
    	}else{
    		// 等待同步
    		RTC_WaitForSynchro();
    		// 等待上一步写操作完成
    		RTC_WaitForLastTask();
    	}
    	
    }
    
    
    void ThisRTC_SetTime(void){
    	time_t time_cnt;
    	struct tm time_date;
    	time_date.tm_year = ThisRTC_Time1.year - 1900;
    	time_date.tm_mon = ThisRTC_Time1.month - 1;
    	time_date.tm_mday = ThisRTC_Time1.day;
    	time_date.tm_hour = ThisRTC_Time1.hour;
    	time_date.tm_min = ThisRTC_Time1.min;
    	time_date.tm_sec = ThisRTC_Time1.sec;
    	// 将日期类型转换为秒计数器类型,并设置为RTC时间
    	time_cnt = mktime(&time_date) - 8 * 60 * 60;
    	RTC_SetCounter(time_cnt);
    	// 等待上一步写操作完成
    	RTC_WaitForLastTask();
    }
    
    
    void ThisRTC_ReadTime(void){
    	time_t time_cnt;
    	struct tm time_date;
    	time_cnt = RTC_GetCounter() + 8 * 60 * 60;	// 加时区偏移变为北京时间
    	time_date = *localtime(&time_cnt);
    	ThisRTC_Time1.year = time_date.tm_year + 1900;
    	ThisRTC_Time1.month = time_date.tm_mon + 1;
    	ThisRTC_Time1.day = time_date.tm_mday;
    	ThisRTC_Time1.hour = time_date.tm_hour;
    	ThisRTC_Time1.min = time_date.tm_min;
    	ThisRTC_Time1.sec = time_date.tm_sec;
    }
    
    

    ThisRTC.h

    #ifndef __THISRTC_H
    #define __THISRTC_H
    // 存放时间结构体
    struct ThisRTC_Time{
    	uint16_t year;	// 年
    	uint8_t month;	// 月
    	uint8_t day;		// 日
    	uint8_t hour;		// 时
    	uint8_t min;		// 分
    	uint8_t sec;		// 秒	
    };
    struct ThisRTC_Time ThisRTC_Time1 = {2024,10,27,5,2,0};
    
    void ThisRTC_Init(void);
    void ThisRTC_SetTime(void);
    void ThisRTC_ReadTime(void);
    
    
    #endif
    
    

    测试

    在待机模式下屏幕不显示,每达到闹钟值或WakeUp引脚有上升沿(用跳线连接GPIOA0与3.3v产生上升沿)唤醒程序显示内容
    main.c

    #include "stm32f10x.h"                  // Device header
    #include "Delay.h"
    #include "OLED.h"
    #include "ThisRTC.h"
    
    int main(void)
    {
    	// 开启PWR时钟
    	RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE);
    	
    	OLED_Init();
    	ThisRTC_Init();
    	OLED_ShowString(1,1,"CNT :");
    	OLED_ShowString(3,1,"AlarmF:");
    	OLED_ShowString(2,1,"ALM :");
    	
    	// 设置闹钟,10s后,闹钟寄存器只读
    	uint32_t Alarm = RTC_GetCounter()+10;
    	RTC_SetAlarm(Alarm);
    	while (1)
    	{
    		// WakeUp引脚上升沿唤醒
    		PWR_WakeUpPinCmd(ENABLE);
    		
    		ThisRTC_ReadTime();
    		OLED_ShowNum(1,6,RTC_GetCounter(),10);
    		OLED_ShowNum(2,6,Alarm,10);
    		OLED_ShowNum(3,9,RTC_GetFlagStatus(RTC_FLAG_ALR),2); // 显示闹钟标志位
    		
    		OLED_ShowString(4,1,"Running");
    		Delay_ms(200);
    		OLED_ShowString(4,1,"       ");
    		Delay_ms(200);
    		
    		OLED_ShowString(4,8,"Standby");
    		Delay_ms(1000);
    		OLED_ShowString(4,1,"       ");
    		Delay_ms(200);
    		
    		OLED_Clear();
    		
    		// 设置待机模式
    		PWR_EnterSTANDBYMode();
    	}
    }
    
    

    PWR_EnterSTANDBYMode()内容:

    补充:


    注意

    在所有的睡眠模式下,程序下载也是禁止的,所以要下载程序时需要先按下复位键唤醒程序,并在此期间进行下载程序

    作者:CC Cian

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32修改主频及三种睡眠模式实例演示

    发表回复