从零开始的STM32学习笔记——时钟精讲
首先看到的是5个时钟源,其中有三个高速时钟源(频率产生的来源)
系统时钟
时钟源相关
-
HSI时钟:高速内部时钟,由于是RC振荡器故频率不够稳定,大约是8MHz,可两分频后做选择器2的输入
-
HSE时钟:高速外部时钟,接外部晶振(4~16MHz),可以直接做选择器1的输入,另外两分频后做选择器1的输入,选择器1又是选择器2的输入,选择器2作为PLL(锁相环倍频输出)的时钟输入,PLL输出的频率为72MHz,通过系统时钟的选择器后可做系统时钟的选择也可直接做系统时钟的时钟源
以上,系统时钟共有三个来源HSI、PLLCLK、HSE -
LSE时钟:低速外部时钟,接32.768kHz石英晶体,主要是RTC的时钟源
-
LSI时钟:低俗内部时钟,内部RC振荡器产生,约为40kHz独立看门狗的时钟来源只能是LSI 也可做RTC时钟源
-
PA8引脚可在MCO输出内部时钟,其来源系统时钟、HSI、HSE、PLL/2.
时钟源的分配使用
- USBCLK:由PLL时钟源提供。该时钟端只能从PLL获取,选择1分频或1.5分频可获得相应频率
- AHB预分频器:将系统频率进行分频提供给其他所有外设。分频后送给APB1与APB2分频器。
- APB1分频器:输出一路供APB1外设使用(PCLK1,最大频率36MHz),另一路送给定时器(Timer)2、3、4倍频器使用;上面连接低速外设
- APB2分频器:输出一路供APB2外设使用(PCLK2,最大频率72MHz),另一路送给定时器(Timer)1倍频器使用;上面连接高速外设
- SYSCLK以及PLLCLK
任何一个外设在使用前都必须使能
typedef struct
{
__IO uint32_t CR; //HSI,HSE,CSS,PLL等使能和就绪标志位
__IO uint32_t CFGR; //PLL等的时钟源选择,分频系数设定;控制选择器选择时钟源的输出,另外还用来配置PLL倍频系数和AHB分频因子
__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
RCC相关头文件和固件库源文件
/***时钟使能配置***/
RCC_LSEConfig()
RCC_HSEConfig()
RCC_HSICmd()
RCC_LSICmd()
RCC_PLLCmd()
/***时钟源相关配置***/
RCC_PLLConfig()
RCC_SYSCLKConfig()
RCC_RTCCLKConfig()
/***分频系数相关配置***/
RCC_HCLKConfig()
RCC_PCLK1Config()
RCC_PCLK2Config()
/***外设时钟使能***/
RCC_APB1PeriphClockCmd(); //APB1线上外设时钟使能
RCC_APB2PeriphClockCmd(); //APB2线上外设时钟使能
RCC_AHBPeriphClockCmd(); //AHB线上外设时钟使能
/***其他外设时钟使能***/
RCC_ADCCLKConfig()
RCC_RTCCLKConfig()
/***状态参数获取参数***/
RCC_GetClocksFreq();
RCC_GetSYSCLKSource();
RCC_GetFlagStatus();
/***RCC中断相关函数***/
RCC_ITConfig()
RCC_GetITStatus()
RCC_ClearITPendingBit()
SystemInit时钟系统初始化函数刨析
SystemInit(); //设置系统复位后状态的初始配置
void SystemInit (void)
{
/* Reset the RCC clock configuration to the default reset state(for debug purpose) */
/* Set HSION bit 打开HSICLK*/
RCC->CR |= (uint32_t)0x00000001; //CR寄存器最后一位设置为1即可打开HSICLK
/* Reset SW, HPRE, PPRE1, PPRE2, ADCPRE and MCO bits */
#ifndef STM32F10X_CL
RCC->CFGR &= (uint32_t)0xF8FF0000;
#else
RCC->CFGR &= (uint32_t)0xF0FF0000;
#endif /* STM32F10X_CL */
/* Reset HSEON, CSSON and PLLON bits Reset即把这些位设置为0*/
RCC->CR &= (uint32_t)0xFEF6FFFF;
/* Reset HSEBYP bit */
RCC->CR &= (uint32_t)0xFFFBFFFF;
/* Reset PLLSRC, PLLXTPRE, PLLMUL and USBPRE/OTGFSPRE bits */
RCC->CFGR &= (uint32_t)0xFF80FFFF;
#ifdef STM32F10X_CL
/* Reset PLL2ON and PLL3ON bits */
RCC->CR &= (uint32_t)0xEBFFFFFF;
/* Disable all interrupts and clear pending bits */
RCC->CIR = 0x00FF0000;
/* Reset CFGR2 register */
RCC->CFGR2 = 0x00000000;
#elif defined (STM32F10X_LD_VL) || defined (STM32F10X_MD_VL) || (defined STM32F10X_HD_VL)
/* Disable all interrupts and clear pending bits */
RCC->CIR = 0x009F0000;
/* Reset CFGR2 register */
RCC->CFGR2 = 0x00000000;
#else
/* Disable all interrupts and clear pending bits */
RCC->CIR = 0x009F0000;
#endif /* STM32F10X_CL */
#if defined (STM32F10X_HD) || (defined STM32F10X_XL) || (defined STM32F10X_HD_VL)
#ifdef DATA_IN_ExtSRAM
SystemInit_ExtMemCtl();
#endif /* DATA_IN_ExtSRAM */
#endif
/* Configure the System clock frequency, HCLK, PCLK2 and PCLK1 prescalers */
/* Configure the Flash Latency cycles and enable prefetch buffer */
SetSysClock(); //重点函数
#ifdef VECT_TAB_SRAM
SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM. */
#else
SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH. */
#endif
/*****************************
在这中间系统是通过宏定义来判断系统的频率是多少
如果系统定义了72MHz
也就是
SystemCoreClock = SYSYCLK_FREQ_72MHz = 72000000
则在系统文件中会判断出来以后的函数入口
也就是 SetSysClockTo72()
下面就是这个函数的讲解
****************************/
static void SetSysClockTo72(void)
{
__IO uint32_t StartUpCounter = 0, HSEStatus = 0;
/* SYSCLK, HCLK, PCLK2 and PCLK1 configuration ---------------------------*/
/* Enable HSE */ //使能HSE
RCC->CR |= ((uint32_t)RCC_CR_HSEON);
/* Wait till HSE is ready and if Time out is reached exit */
do
{
HSEStatus = RCC->CR & RCC_CR_HSERDY; //RCC_CR_HSERDY为1,即外部高速时钟已就绪
StartUpCounter++; //即将跳出循环
} while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT)); //HSE_STARTUP_TIMEOUT就是说启动超时
if ((RCC->CR & RCC_CR_HSERDY) != RESET)
{
HSEStatus = (uint32_t)0x01; //一种标识状态
}
else
{
HSEStatus = (uint32_t)0x00;
}
if (HSEStatus == (uint32_t)0x01)
{
/* Enable Prefetch Buffer */
FLASH->ACR |= FLASH_ACR_PRFTBE;
/* Flash 2 wait state */
FLASH->ACR &= (uint32_t)((uint32_t)~FLASH_ACR_LATENCY);
FLASH->ACR |= (uint32_t)FLASH_ACR_LATENCY_2;
/*****************************
由于,CPU的处理速度是要比闪存要快的,
所以需要进行等待,系统时钟为72MHz
需要两个等待状态
*****************************/
/* HCLK = SYSCLK */ //AHB预分频器设置为1分频
RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;
/* PCLK2 = HCLK */
RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1;
/* PCLK1 = HCLK/2 */
RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV2;
#ifdef ......
#else
/* PLL configuration: PLLCLK = HSE * 9 = 72 MHz */
RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE |RCC_CFGR_PLLMULL)); //通过设置CFGR寄存器来设置PLL倍频系数和时钟来源
RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLMULL9);
#endif /* STM32F10X_CL */
/* Enable PLL */ //使能PLL
RCC->CR |= RCC_CR_PLLON;
/* Wait till PLL is ready */
while((RCC->CR & RCC_CR_PLLRDY) == 0)
{
//这是在跑空循环等待PLL就绪
}
/* Select PLL as system clock source */
RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL;
//把PLL时钟切换到SYSCLK上
/* Wait till PLL is used as system clock source */
while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08)
{
}
}
else
{ /* If HSE fails to start-up, the application will have wrong clock
configuration. User can add here some code to deal with this error */
}
}
#endif
}
系统初始化函数——SystemInit的执行
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT __main
IMPORT SystemInit //可以看出系统实现执行系统初始化函数,再来执行main函数的
LDR R0, =SystemInit
BLX R0
LDR R0, =__main
BX R0
ENDP
Systick系统滴答定时器
Systick定时器基础知识
- Systick定时器,是一个简单的定时器,对于CM3,CM4内核芯片,都有Systick定时器·
- Systick定时器常用来做延时,或者实时系统的心跳时钟。这样可以节省MCU资源,不用浪费一个定时器。比如UCOS中,分时复用,需要一个最小的时间戳,一般在STM32+UCOS系统中,都采用Systick做UCOS心跳时钟。
- Systick定时器就是系统滴答定时器,一个24位的倒计数定时器,计到0时,将从RELOAD寄存器中自动重装载定时初值。只要不把它在SysTick控制及状态寄存器中的使能位清除,就永不停息,即使在睡眠模式下也能工作。
- SysTick定时器被捆绑在NVIC中,用于产生SYSTICK异常(异常号:15)。;SysTick也可以产生中断
- Systick中断的优先级也可以设置。
Systick相关寄存器库函数
-
4个Systick定时器
- CTRL Systick控制和状态计时器
TCIKINT:当设置为1则产生中断
- SysTick 自动重装载数值寄存器(LOAD寄存器) 时钟周期设置
+
从第0位到第24位有效 - SysTick当前值寄存器(VAL)
- SysTick 校准值寄存器
- CTRL Systick控制和状态计时器
-
固件中的相关库函数
-
SysTick_CLKSourceConfig() //Systick时钟源选择
void SysTick_CLKSourceConfig(uint32_t SysTick_CLKSource) { /* Check the parameters */ assert_param(IS_SYSTICK_CLK_SOURCE(SysTick_CLKSource)); //这里面SysTick_CLKSource只能选择两种,一分频(由最开始的图可以知道这选定的是内部时钟)和把分频(由上面的图可得知这选定的是AHB分频后的9MHz) if (SysTick_CLKSource == SysTick_CLKSource_HCLK) { SysTick->CTRL |= SysTick_CLKSource_HCLK; //SysTick的定义也是结构体指针,地址映射到Systick_BASE } else { SysTick->CTRL &= SysTick_CLKSource_HCLK_Div8; } }
-
SysTick_Config(uint32_t ticks) //初始化SysTick,时钟为HCLK,并开启中断
static __INLINE uint32_t SysTick_Config(uint32_t ticks) //Tick就是在两次中断之间的时钟周期 { if (ticks > SysTick_LOAD_RELOAD_Msk) return (1); /* Reload value impossible;对有效性的判断 */ SysTick->LOAD = (ticks & SysTick_LOAD_RELOAD_Msk) - 1; /* set reload register;类似于溢出,装载那一次也算一个时钟周期 */ NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1); /* set Priority for Cortex-M0 System Interrupts */ SysTick->VAL = 0; /* Load the SysTick Counter Value */ SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_TICKINT_Msk | SysTick_CTRL_ENABLE_Msk; /* Enable SysTick IRQ and SysTick Timer */ return (0); /* Function successful */ }
-
-
Systick中断服务函数
- void SysTick_Handler(void)
delay延时函数(Systick应用)
这里以us为例
#endif
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); //选择外部时钟 HCLK/8
fac_us=SystemCoreClock/8000000;
/********为系统时钟的1/8,查询定义可以知道SystemCoreClock为72000000,故系统8分频后需要跑9次(ms就是9000次)才能跑满1us(10^(-6)) ****************/
void delay_us(u32 nus)
{
u32 temp;
SysTick->LOAD=nus*fac_us; //时间加载,赋值给LOAD加载,注意不能超过24位
SysTick->VAL=0x00; //清空当前计数器准备开始倒数
SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ; //开始倒数,使能CTRL。
do
{
temp=SysTick->CTRL;
}while((temp&0x01)&&!(temp&(1<<16))); //等待时间到达,第一个条件是CTRL被使能,第二个条件是CTRL标志位不为1(如果为1就跳出循环了)
SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk; //关闭计数器(按位取反后由于SysTick_CTRL_ENABLE_Msk第一位是0,CTRL按位与后关闭)
SysTick->VAL =0X00; //清空计数器
}
SysTick_CTRL_ENABLE_Msk ; //开始倒数,使能CTRL。
do
{
temp=SysTick->CTRL;
}while((temp&0x01)&&!(temp&(1<<16))); //等待时间到达,第一个条件是CTRL被使能,第二个条件是CTRL标志位不为1(如果为1就跳出循环了)
SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk; //关闭计数器(按位取反后由于SysTick_CTRL_ENABLE_Msk第一位是0,CTRL按位与后关闭)
SysTick->VAL =0X00; //清空计数器
}