STM32修改主频+三种睡眠模式的实例


前言

本内容主要实操修改主频与使用三种睡眠模式,需要理论介绍需见本专栏: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()内容:
在这里插入图片描述
补充:
在这里插入图片描述


注意

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

  • 8
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值