文章目录
- 前言
- 修改主频
- 测试
- 睡眠模式+串口收发
- 接线图
- 关于配置立刻睡眠和等待睡眠模式的寄存器
- 串口配置
- 测试
- 执行流程
- 停止模式+对射式红外传感器计数
- 接线图
- 配置红外传感器与外部中断
- 测试
- 注意
- 待机模式+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()内容:
补充:
注意
在所有的睡眠模式下,程序下载也是禁止的,所以要下载程序时需要先按下复位键唤醒程序,并在此期间进行下载程序