本帖最后由 王小琪 于 2023-1-9 15:42 编辑
一、时钟简介先来个时钟总览图片,时钟的介绍一时半会也说不完,就挑重点的简单说说
①HSI 内部高速时钟,RC振荡器,频率为8MHz,精度不高。
②HSE 外部高速时钟,可接石英/陶瓷谐振器,频率范围为4MHz~16MHz,一般是8MHZ。
③LSI 内部低速时钟,RC振荡器,频率为40kHz,精度不高。
④LSE 外部低速时钟,接频率为32.768kHz的石英晶体。
⑤SYSCLK系统时钟,三个来源HSI(8M)、PLLCLK(4M-128M)、HSE(4-16M)。一般是通过PLLCK倍频设置为72M,即SYSCLK=PLLCK=72M
二、为什么STM32F103开发板系统时钟SYSCLK为72MHZ?
首先我们随便打开一个开发板的标准库函数历程,此处以正点原子战舰开发板LED为例子
我们可以看到在main.c中并没有配置时钟,那么SYSCLK是在哪配置的呢。
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
int main(void)
{
delay_init(); //延时函数初始化
LED_Init(); //初始化与LED连接的硬件接口
while(1)
{
LED0=0;
LED1=1;
delay_ms(300); //延时300ms
LED0=1;
LED1=0;
delay_ms(300); //延时300ms
}
}
那是因为时钟是靠SystemInit函数来配置的,而且SystemInit函数是在main函数之前执行的。我们可以打开启动文件startup_stm32f10x_hd.s 如下图,可以很清楚的看出来在main()之前就配置好了时钟
那么为什么时钟是72M,不是36M呢,继续往下看,我们可以看到在system_stm32f10x.c里面看到官方固件库默认定义了SYSCLK_FREQ_72MHz,所以会调用SetSysClockTo72这个函数。
如果要使用其它频率,那就解开其他相应的注释(只保留一个不被注释)。
但是由于这是官方固件库的文件,建议不要随意修改。
至此,我们找到了72MHZ的来源,并没有在main()函数里面写出来,而是在启动之后,main()之前就将时钟配置好了。
三、如何修改系统时钟SYCSLK,并测量
上篇文章写了为什么STM32的系统时钟默认为72MHZ,ST官方固件库的启动文件会在main之前调用SystemInit(),初始化时钟,由于我们不可能去修改官方库文件,所以想要修改系统时钟,只能自己重新写时钟函数,需要引入一个源文件clk.c和一个头文件clk.h
//clk.h
#ifndef __CLKCONFIG_H
#define __CLKCONFIG_H
#include "stm32f10x.h"
void HSE_SetSysClock(uint32_t pllmul);
void HSI_SetSysClock(uint32_t pllmul);
#endif
//clk.c
#include "clk.h"
#include "stm32f10x_rcc.h"
void HSE_SetSysClock(uint32_t pllmul)
{
__IO uint32_t StartUpCounter = 0, HSEStartUpStatus = 0;
SystemInit();
RCC_DeInit();// 把RCC外设初始化成复位状态,这句是必须的
RCC_HSEConfig(RCC_HSE_ON);//使能HSE,开启外部晶振8M
HSEStartUpStatus = RCC_WaitForHSEStartUp();// 等待 HSE 启动稳定
if (HSEStartUpStatus == SUCCESS)// 只有 HSE 稳定之后则继续往下执行
{
FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);
FLASH_SetLatency(FLASH_Latency_2);
RCC_HCLKConfig(RCC_SYSCLK_Div1); // AHB预分频因子设置为1分频,HCLK = SYSCLK
RCC_PCLK2Config(RCC_HCLK_Div1);// APB2预分频因子设置为1分频,PCLK2 = HCLK
RCC_PCLK1Config(RCC_HCLK_Div2);// APB1预分频因子设置为1分频,PCLK1 = HCLK/2
RCC_PLLConfig(RCC_PLLSource_HSE_Div1, pllmul);// PLLCLK = 8MHz * pllmul
RCC_PLLCmd(ENABLE);// 开启PLL
while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET)// 等待 PLL稳定
{
}
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);// 当PLL稳定之后,把PLL时钟切换为系统时钟SYSCLK
while (RCC_GetSYSCLKSource() != 0x08)// 读取时钟切换状态位,确保PLLCLK被选为系统时钟
{
}
}
else
{
while (1)
{
}
}
}
需要修改系统时钟的时候,只需要在main()里面加入相关函数即可,如下:
int main(void)
{
HSE_SetSysClock(RCC_PLLMul_9);//SYSCLK为8*9=72M
while(1)
{
}
}
修改完了之后,怎么知道我的时钟有没有修改过来呢,有什么方法可以测量呢?
如下图,PA8有一个功能是MCO,在STM32F10系列中主要作用是可以对外提供时钟,相当于一个有源晶振。MCO的时钟来源可以是:PLLCLK/2、HSI、HSE、SYSCLK。除了对外提供时钟这个作用之外,我们还可以通过示波器监控 MCO 引脚的时钟输出来验证我们的系统时钟配置是否正确。
所以我们需要先将PA8设置为时钟输出脚,然后用示波器测量PA8引脚即可观察到时钟的频率。
//配置PA8作为MCO输出IO配置
void MCO_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;// 开启GPIOA的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;// 选择GPIO8引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//设置为复用功能推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//设置IO的翻转速率为50M
GPIO_Init(GPIOA, &GPIO_InitStructure);// 初始化GPIOA8
}
int main(void)
{
HSE_SetSysClock(RCC_PLLMul_9);//SYSCLK为8*9=72M
MCO_GPIO_Config();//配置IO
RCC_MCOConfig(RCC_MCO_SYSCLK);
//RCC_MCOConfig(RCC_MCO_PLLCLK_Div2);
while(1)
{
}
}
从上图可以看到MCO输出需小于50MHZ,这个是单片机的性能决定的,实测外部晶振为8M时,7倍频后依旧可以测得56MHZ的频率,但为了稳定性,建议MCO输出不要超过50MHZ。
下图为设置频率和实测频率图片,注意第二张图和第三张图,因为MCO输出需小于50MHZ,所以当频率为72MHZ的时候,MCO只能输出五十几兆,而MCO也可以输出PLLCLK/2,即SYSCLK/2=36M<50M,所以也可以证明SYSCLK为72M。
四、如何通过串口调试助手打印系统时钟SYSCLK
首先代码如下
int main(void)
{
RCC_ClocksTypeDef rcc_clocks; //定义结构体变量
HSE_SetSysClock(RCC_PLLMul_9);//SYSCLK?8*9=72M
delay_init(); //延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
uart_init(115200); //串口初始化为115200
RCC_GetClocksFreq(&rcc_clocks);//获取不同时钟频率
while(1)
{
printf("\r\nSYSCLK = %d MHz\n",rcc_clocks.SYSCLK_Frequency/1000000);
delay_ms(1000);
}
}
首先需要定义一个结构体变量RCC_ClocksTypeDef rcc_clocks;,结构体中的成员可在stm32f2xx_rcc.c中看到。分别是SYSCLK 、HCLK 、PCLK1 、PCLK2 、ADCCLK ,所以上面五个频率均可以打印出来。我们只打印SYSCLK
代码中其他的就很常规了,配置时钟、初始化延时函数、配置中断、串口初始化。在大循环中间隔1s打印各个时钟频率,可以看到结果如下。
|