配置STM32F407的时钟配置:实现最佳性能
文章目录
时钟源
时钟源用来为环形脉冲发生器提供频率稳定且电平匹配的方波时钟脉冲信号。它通常由石英 晶体振荡器和与非门组成的正反馈振荡电路组成,其输出送至环形脉冲发生器。
为什么 STM32 要有多个时钟源
F4开发指南P107
F407时钟地图
F4开发指南P108
F407的五个时钟源
HSI高速内部时钟源,High Speed Internal
F407的PLL锁相环

LSE低速外部时钟源,Low Speed External
LSI低速内部时钟源,Low Speed Internal
HSE高速外部时钟源,High Speed External
F4开发指南4.3.1,P108与P109
外部晶振/陶瓷谐振器

MCO引脚
选择一个时钟信号输出到MCO引脚
F4开发指南P109C
CSS时钟监控系统
检测到HSE失败,就会切换到HSI
AHB预分频器
是Advanced High performance Bus的缩写,译作高级高性能总线,这是一种“系统总线”。
APB预分频器
Advanced Peripheral Bus,外围总线
在使用任何的外设之前,都要使相应的时钟使能位开启,否则就没法使用
USB预分频器
PLL锁相环时钟经过USB分频器最终输出的USB时钟,分1或者1.5;
USB时钟48M
系统时钟(D部分)
系统时钟的来源
系统时钟SYSCLK可来源于三个时钟源:
①、HSI振荡器时钟
②、HSE振荡器时钟
③、PLL时钟
系统时钟的设置

F103系统初始化时钟的大小
static void SetSysClock(void)
{
#ifdef SYSCLK_FREQ_HSE
SetSysClockToHSE();
#elif defined SYSCLK_FREQ_24MHz
SetSysClockTo24();
#elif defined SYSCLK_FREQ_36MHz
SetSysClockTo36();
#elif defined SYSCLK_FREQ_48MHz
SetSysClockTo48();
#elif defined SYSCLK_FREQ_56MHz
SetSysClockTo56();
#elif defined SYSCLK_FREQ_72MHz
SetSysClockTo72();
#endif
}
总结一下 SystemInit()函数默认设置的系统时钟大小:
SYSCLK(系统时钟) =72MHz
AHB 总线时钟(使用 SYSCLK) =72MHz
APB1 总线时钟(PCLK1) =36MHz
APB2 总线时钟(PCLK2) =72MHz
PLL 时钟 =72MHz
F407系统初始化时钟的大小
系统时钟分频产生三种时钟。FCLK,HCLK,PCLK都称为系统时钟,但区别如下,
总结一下 SystemInit()函数中设置的系统时钟大小:
F4开发指南4.3.2,P113
RCC时钟控制
何为RCC
RCC,Reset and Clock Control(复位和时钟控制),在绝大部分MCU芯片中都包含复位和时钟控制模块,也是MCU重要的组成部分。
主要用来设置系统时钟 SYSCLK 、设置 AHB 分频因子(决定 HCLK 等于多少)、设置 APB2 分频因子(决定 PCLK2 等于多少)、设置 APB1 分频因子(决定 PCLK1 等于多少)、设置各个外设的分频因子;控制 AHB 、 APB2 和 APB1 这三条总线时钟的开启、控制每个外设的时钟的开启。
RCC寄存器
AHB1
AHB2
APB1 外设时钟使能寄存器(RCC_APB1ENR)
APB2 外设时钟使能寄存器(RCC_APB2ENR)
CR寄存器
可以使能HSI、HSE、CSS、PLL,使能之后才能被打开和使用。使能之后不能立马稳定,所以需要一个标志位判断时钟是否稳定,叫做就绪标志位。
CFGR寄存器
F4中文参考手册,6.3.3,P118
设置时钟源的选择和分频系数。
PPRE2,APB2分频
PPRE1,APB1分频
HPRE,AHB分频
F407外设时钟使能:
官方库提供了五个打开 GPIO 和外设时钟的函数分别为:
void RCC_AHB1PeriphClockCmd(uint32_t RCC_AHB1Periph, FunctionalState NewState);
void RCC_AHB2PeriphClockCmd(uint32_t RCC_AHB2Periph, FunctionalState NewState);
void RCC_AHB3PeriphClockCmd(uint32_t RCC_AHB3Periph, FunctionalState NewState);
void RCC_APB1PeriphClockCmd(uint32_t RCC_APB1Periph, FunctionalState NewState);
void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState);
AHB1
AHB2
AHB3
APB1
APB2
其他外设时钟配置:
RCC_ADCCLKConfig ()
RCC_RTCCLKConfig();
状态参数获取参数:
RCC_GetClocksFreq();
RCC_GetSYSCLKSource();
RCC_GetFlagStatus()
RCC中断相关函数 :
RCC_ITConfig()
RCC_GetITStatus()
RCC_ClearITPendingBit()…
RCC配置结构体
typedef struct
{
__IO uint32_t CR; //HSI,HSE,CSS,PLL等的使能和就绪标志位
__IO uint32_t CFGR; //PLL等的时钟源选择,分频系数设定
__IO uint32_t CIR; // 清除/使能 时钟就绪中断
__IO uint32_t APB2RSTR; //APB2线上外设复位寄存器
__IO uint32_t APB1RSTR; //APB1线上外设复位寄存器
__IO uint32_t AHBENR; //DMA,SDIO等时钟使能
__IO uint32_t APB2ENR; //APB2线上外设时钟使能
__IO uint32_t APB1ENR; //APB1线上外设时钟使能
__IO uint32_t BDCR; //备份域控制寄存器
__IO uint32_t CSR; //控制状态寄存器
} RCC_TypeDef;
RTC时钟
RTC时钟介绍
RTC时钟框图及解析
RTC 由两个主要部分组成
第一部分(APB1 接口)用来和 APB1 总线相连。
此单元还包含一组 16 位寄存器,可通过 APB1 总线对其进行读写操作。
APB1 接口由 APB1 总线时钟驱动,用来与 APB1 总线连接。
另一部分(RTC 核心)由一组可编程计数器组成,分成两个主要模块。
第一个模块是 RTC 的预分频模块,它可编程产生 1 秒的 RTC 时间基准 TR_CLK。
RTC 的预分频模块包含了一个 20位的可编程分频器(RTC 预分频器)。
如果在 RTC_CR 寄存器中设置了相应的允许位,则在每个TR_CLK 周期中 RTC 产生一个中断(秒中断)。
第二个模块是一个 32 位的可编程计数器(RTC_CNT),可被初始化为当前的系统时间,一个 32 位的时钟计数器,按秒钟计算,可以记录 4294967296 秒,约合 136 年左右。
RTC 还有一个闹钟寄存器 RTC_ALR,用于产生闹钟。系统时间按 TR_CLK 周期累加并与存储在 RTC_ALR 寄存器中的可编程时间相比较,如果 RTC_CR 控制寄存器中设置了相应允许位,比较匹配时(即:RTC_CNT=RTC_ALR 时)将产生一个闹钟中断,从而实现闹钟功能。
软件是通过 APB1 接口访问 RTC 的预分频值、计数器值和闹钟值的
但RTC 内核完全独立于 RTC APB1 接口
相关可读寄存器只在 RTC APB1 时钟进行重新同步的 RTC 时钟的上升沿被更新,RTC 标志也是如此。
这就意味着,如果 APB1 接口刚刚被开启之后,在第一次的内部寄存器更新之前,从 APB1 上读取的 RTC 寄存器值可能被破坏了(通常读到 0)。
因此,若在读取 RTC 寄存器曾经被禁止的 RTC APB1 接口,软件首先必须等待 RTC_CRL 寄存器的 RSF位(寄存器同步标志位,bit3)被硬件置 1。
RTC的寄存器
RTC 的控制寄存器RTC_CR
RTC总共有 2 个控制寄存器 RTC_CRH 和 RTC_CRL,该寄存器用来控制中断的,若要用到秒钟中断,该寄存器必须设置最低位为 1,以允许秒钟中断。
RTC_CRL 寄存器
第 0 位是秒钟标志位,我们在进入 RTC中断的时候,通过判断这位来决定是不是发生了秒钟中断。
然后必须通过软件将该位清零(写0)。
第 1 位是闹钟标志位,当 RTC_CNT 的值等于 RTC_ALR 的值时,此位将由硬件置 1(可以判断此位是否为 1 来判定是否产生了闹钟),如果设置了闹钟中断(ALRIE=1),则将产生 RTC闹钟中断,该位也必须软件写 0 清除。
第 3 位为寄存器同步标志位,我们在修改控制寄存器 RTC_CRH/CRL 之前,必须先判断该位,是否已经同步了,如果没有则等待同步,在没同步的情况下修改 RTC_CRH/CRL 的值是不行的。
第 4 位为配置标位,在软件修改 RTC_CNT/RTC_ALR/RTC_PRL 的值的时候,必须先软件置位该位,以允许进入配置模式。
第 5 位为 RTC 操作位,该位由硬件操作,软件只读。通过该位可以判断上次对 RTC 寄存器的操作是否完成,如果没有,我们必须等待上一次操作结束才能开始下一次操作。
RTC 预分频装载寄存器
2 个寄存器组成,RTC_PRLH 和RTC_PRLL。
这两个寄存器用来配置 RTC 时钟的分频数的。
比如我们使用外部 32.768K 的晶振作为时钟的输入频率,那么我们要设置这两个寄存器的值为 32767,以得到一秒钟的计数频率。
RTC_PRLH 的各位描述
RTC_PRLH 只有低四位有效,用来存储 PRL 的 19~16 位。而 PRL的前 16 位,存放在 RTC_PRLL 里面,寄存器 RTC_PRLL 的各位描述
RTC 预分频器余数寄存器
该寄存器也有 2 个寄存器组成 RTC_DIVH 和 RTC_DIVL
这两个寄存器的作用就是用来获得比秒钟更为准确的时钟,
比如可以得到 0.1 秒,或者 0.01 秒等。
该寄存器的值自减的,用于保存还需要多少时钟周期获得一个秒信号。
在一次秒钟更新后,由硬件重新装载。这两个寄存器和 RTC 预分频装载寄存器的各位是一样的
RTC 计数器寄存器 RTC_CNT
该寄存器由 2 个 16位的寄存器组成 RTC_CNTH 和 RTC_CNTL,总共 32 位,用来记录秒钟值(TR_CLK=1Hz 的
情况下)。一般我们设置时间,就是设置 RTC_CNTH/RTC_CNTL 寄存器的值。
假定我们以 1970年为起始时间,那么当 RTC_CNTH=RTC_CNTL=0 的时候,就代表 1970 年 1 月 1 日 0 时 0 分,
这样就可以很方便的根据 RTC_CNT 的值计算当前时间了。
反过来,如果要设置时间,则只需要将当前时间的年份减去 1970,然后剩下的时间换算成秒钟,写入 RTC_CNT 即可完成时间设置。
闹钟寄存器 RTC_ALR
该寄存器同 RTC_CNT 一样,也是由 2 个 16位的寄存器组 RTC_ALRH 和 RTC_ALRL 组成,总共 32 位,用来记录闹钟时刻,实际上,RTC_ALR 就是一个用于同 RTC_CNT 比较的寄存器,当 RTC_CNT=RTC_ALR 的时候,就说明闹钟时间到了,需要闹铃。
因此 RTC_ALR 的设置和读取完全同 RTC_CNT 一模一样。
假定我们设置RTC_CNTH=RTC_CNTL=0,然后设置RTC_ALRH=0且RTC_ALRL=30,然后启动RTC,
那么 30 秒钟后,ALRF 将为 1,表示有闹钟产生,如果开启了闹钟中断(ALRIE=1),那么将产生闹钟中断,这就是 STM32F1 的 RTC 闹钟原理。
备份区域控制寄存器RCC_BDCR
RTC 的时钟源选择及使能设置都是通过这个寄存器来实现的,所以我们在 RTC 操作之前先要通过这个寄存器选择 RTC 的时钟源,然后才能开始其他的操作。
RTC 配置步骤
使能电源时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);//使能 PWR 时钟
PWR:F4中文参考手册6.3.15,P139,Bit28
取消备份区写保护
PWR_BackupAccessCmd(ENABLE); //使能后备寄存器访问
PWR_BackupAccessCmd:F1固件库使用手册14.2.2,P189
开启外部低速振荡器
RCC_LSEConfig(RCC_LSE_ON);// 开启外部低速振荡器
F1固件库使用手册15.2.16,P204,注意参数不完全一致。
选择 RTC 时钟,并使能
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); //设选择 LSE 作为 RTC 时钟
RCC_RTCCLKCmd(ENABLE); //使能 RTC 时钟
RCC_RTCCLKConfig F1固件库使用手册15.2.18,P205
初始化 RTC,设置 RTC 的分频
ErrorStatus RTC_Init(RTC_InitTypeDef* RTC_InitStruct);
typedef struct
{
uint32_t RTC_HourFormat;
uint32_t RTC_AsynchPrediv;
uint32_t RTC_SynchPrediv;
}RTC_InitTypeDef;
RTC_HourFormat 如果设置为 24 小时格式参数值可选择 RTC_HourFormat_24,12 小时格式,参数值可以选择 RTC_HourFormat_24。 F4中文参考手册23.6.3控制寄存器CR,P589,Bit6
0:24 小时/天格式
1:AM/PM 小时格式
参数 RTC_AsynchPrediv 用来设置 RTC 的异步预分频系数,也就是设置 RTC_PRER 预分频器寄存器的 PREDIV_A 相关位。同时,因为异步预分频系数是 7 位,所以最大值为 0x7F,不能超过这个值。
参数 RTC_SynchPrediv 用来设置 RTC 的同步预分频系数,也就是设置 RTC_PRER 寄存器的 PREDIV_S 相关位。同时,因为同步预分频系数也是 15 位,所以最大值为 0x7FFF,不能超过这个值。
要想明白同步和异步要达到的目的,可以参考F4中文参考手册23.3.1,P574。
同步和异步通道的区别在于同步发过去消息会发生阻塞,直到返回值才继续运行。
设置 RTC 的时间
ErrorStatus RTC_SetTime(uint32_t RTC_Format, RTC_TimeTypeDef* RTC_TimeStruct);
typedef struct
{
uint8_t RTC_Hours;
uint8_t RTC_Minutes;
uint8_t RTC_Seconds;
uint8_t RTC_H12;
}RTC_TimeTypeDef;
最后一个参数的取值范围:
别用来设置 RTC 时间参数的小时,分钟,秒钟,以及 AM/PM 符号
设置 RTC 的日期
ErrorStatus RTC_SetDate(uint32_t RTC_Format, RTC_DateTypeDef* RTC_DateStruct);
typedef struct
{
uint8_t RTC_WeekDay;
uint8_t RTC_Month;
uint8_t RTC_Date;
uint8_t RTC_Year;
}RTC_DateTypeDef;
设置日期的星期几,月份,日期,年份。
获取 RTC 当前日期和时间
获取当前 RTC 时间的函数为:
void RTC_GetTime(uint32_t RTC_Format, RTC_TimeTypeDef* RTC_TimeStruct);
获取当前 RTC 日期的函数为:
void RTC_GetDate(uint32_t RTC_Format, RTC_DateTypeDef* RTC_DateStruct);
最终代码
u8 My_RTC_Init(void)
{
RTC_InitTypeDef RTC_InitStructure;
u16 retry=0X1FFF;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);//使能 PWR 时钟
PWR_BackupAccessCmd(ENABLE); //使能后备寄存器访问
if(RTC_ReadBackupRegister(RTC_BKP_DR0)!=0x5050)//是否第一次配置?
{
RCC_LSEConfig(RCC_LSE_ON);//LSE 开启
while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET)
//检查指定的 RCC 标志位设置与否,等待低速晶振就绪
{ retry++;
delay_ms(10);
}
if(retry==0)return 1; //LSE 开启失败.
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); //选择 LSE 作为 RTC 时钟
RCC_RTCCLKCmd(ENABLE); //使能 RTC 时钟
RTC_InitStructure.RTC_AsynchPrediv = 0x7F;//RTC 异步分频系数(1~0X7F)
RTC_InitStructure.RTC_SynchPrediv = 0xFF;//RTC 同步分频系数(0~7FFF)
RTC_InitStructure.RTC_HourFormat = RTC_HourFormat_24;//24 小时格式
RTC_Init(&RTC_InitStructure);//初始化 RTC 参数
RTC_Set_Time(23,59,56,RTC_H12_AM); //设置时间
RTC_Set_Date(14,5,5,1); //设置日期
RTC_WriteBackupRegister(RTC_BKP_DR0,0x5050); //标记已经初始化过了
}
return 0;
}
看门狗时钟
两种看门狗
独立看门狗适合应用于需要看门狗作为一个在主程序之外 能够完全独立工作,并且对时间精度要求低的场合。
窗口看门狗最适合那些要求看门狗在精确计时窗口起作用的程序。
IWDG结构图
作用
IWDG Independent watch door dog独立看门狗
IWDG_KR键值寄存器
开启IWDG
IWDG_PR预分频寄存器
F407中文参考手册18.4.2
该寄存器用来设置看门狗时钟的分频系数。
2-0 PR预分频器系数
IWDG_RLR重装载寄存器
该寄存器用来保存重装载到计数器中的值。该寄存器也是一个 32位寄存器,但是只有低 12 位是有效的。
IWDG_SR状态寄存器
1RVU 重载值更新
0 PVU 预分频值更新
注意
写保护
F407中文参考手册18.3.2
IWDG 在一旦启用,就不能再被关闭!想要关闭,只能重启
独立看门狗相关的库函数和定义分布在文件 stm32f10x_iwdg.h 和stm32f10x_iwdg.c 中
超时时间
溢出时间计算:
Tout=((4×2^prer) ×rlr) /32 (M4)
使用步骤
取消寄存器 写保护
(向 IWDG_KR 写入 0X5555 )
IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable);
设置预分频系数和重装载值
设置看门狗的分频系数的函数是
void IWDG_SetPrescaler(uint8_t IWDG_Prescaler); //设置 IWDG 预分频值
设置看门狗的重装载值的函数是:
void IWDG_SetReload(uint16_t Reload); //设置 IWDG 重装载值
看门狗溢出时间(单位为 ms)
Tout=((4×2^prer) ×rlr) /32
喂狗
(向IWDG_KR 写入 0XAAAA )
库函数里面重载计数值的函数是:
IWDG_ReloadCounter(); //按照 IWDG 重装载寄存器的值重装载 IWDG 计数器
启动看门狗
(向 向 IWDG_KR 写入 0XCCCC)
库函数里面启动独立看门狗的函数是:
IWDG_Enable(); //使能 IWDG
程序
主程序
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组2
delay_init(168); //初始化延时函数
LED_Init(); //初始化LED端口
KEY_Init(); //初始化按键
delay_ms(100); //延时100ms
IWDG_Init(4,500); //与分频数为64,重载值为500,溢出时间为1s
LED0=0; //先点亮红灯
while(1)
{
if(KEY_Scan(0)==WKUP_PRES)//如果WK_UP按下,则喂狗
{
IWDG_Feed();//喂狗
}
delay_ms(10);
};
}
函数
//初始化独立看门狗
//prer:分频数:0~7(只有低3位有效!)
//rlr:自动重装载值,0~0XFFF.
//分频因子=4*2^prer.但最大值只能是256!
//rlr:重装载寄存器值:低11位有效.
//时间计算(大概):Tout=((4*2^prer)*rlr)/32 (ms).
void IWDG_Init(u8 prer,u16 rlr)
{
IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable); //使能对IWDG->PR IWDG->RLR的写
IWDG_SetPrescaler(prer); //设置IWDG分频系数
IWDG_SetReload(rlr); //设置IWDG装载值
IWDG_ReloadCounter(); //reload
IWDG_Enable(); //使能看门狗
}
//喂独立看门狗
void IWDG_Feed(void)
{
IWDG_ReloadCounter();//reload
}
窗口看门狗时钟
介绍

窗口看门狗的必要性
复位原理
W[6:0]即是 WWDG->CFR 的低七位。T[6:0]就是窗口看门狗的计数器,而 W[6:0]则是窗口看门狗的上窗口,下窗口值是固定的(0X40)。

窗口看门狗超时时间
窗口看门狗的超时公式如下:
Twwdg=(4096×2^WDGTB×(T[5:0]+1)) /Fpclk1;
其中:
Twwdg:WWDG 超时时间(单位为 ms)
Fpclk1:APB1 的时钟频率(单位为 Khz)
WDGTB:WWDG 的预分频系数
T[5:0]:窗口看门狗的计数器低 6 位
窗口看门狗的寄存器
控制寄存器(WWDG_CR)
T6-0 看门狗计数器值
WWDG_CR 只有低八位有效,T[6:0]用来存储看门狗的计数器值,随时更新的,每个窗口看门狗计数周期(4096×2^ WDGTB)减 1。当该计数器的值从 0X40 变为 0X3F 的时候,将产生看门狗复位。
WDGA 看门狗激活位
看门狗的激活位,该位由软件置 1,以启动看门狗,并且一定要注意的是该位一旦设置,就只能在硬件复位后才能清零了。
配置寄存器(WWDG_CFR)
中文参考手册19.6.2
9 EWI early wakeup interrupt
如果启动了看门狗并且允许中断,当递减计数器等于0x40时产生早期唤醒中断(EWI),它可以用于喂狗以避免WWDG复位。
8-7 WDGTB 预分频
6-0 W[6:0] 7位窗口值
状态寄存器(WWDG_SR)
用来记录当前是否有提前唤醒的标志。该寄存器仅有位 0 有效,其他都是保留位。
当计数器值达到 40h 时,此位由硬件置 1。它必须通过软件写 0 来清除。对此位写 1 无效。即使中断未被使能,在计数器的值达到 0X40的时候,此位也会被置 1。
使用步骤
使能看门狗时钟
RCC_APB1PeriphClockCmd();
设置分频系数
WWDG_SetPrescaler();
设置上窗口值
WWDG_SetWindowValue();
开启提前唤醒中断并分组(可选)
WWDG_EnableIT();
NVIC_Init();
使能看门狗
WWDG_Enable();
喂狗
WWDG_SetCounter();
编写中断服务函数
WWDG_IRQHandler();