【嵌入式系统】STM32时钟系统+时钟配置函数解析
1、时钟系统 时钟系统为整个硬件系统的各个模块提供时钟信号。时钟是整个数字电路的驱动之源,所有数字部件的运行都依赖时钟信号的输入才得以向前推进。
由于系统复杂性,各硬件模块可能对时钟信号有不同要求,因此在系统中应按需分别提供时钟信号。这些时钟信号或者来自不同振荡器,或者是从一个主振荡器开始,经过多次的倍频、分频、锁相环等电路而生成的独立时钟信号。不同时钟信号而非单一时钟的设计还有助于实现系统的低功耗:一些低速外设可以使用功耗更低的低速时钟,部分硬件没有使用时也可除能时钟位达到关闭的效果。
2、时钟树
文章图片
图1 STM32时钟树
文章图片
图2 STM32时钟树图例解析 注
在实际应用时,为避免因为外部时钟源失效造成MCU内部紊乱导致无可挽回的损失,引入CSS来监控外部时钟源,这是STM32作为一个可靠系统的必要条件。当外部时钟源出现故障失效时,CSS会主动切断外部时钟并开启内部时钟作为替代,同时产生一个时钟安全中断。此中断属于NMI,将不断执行直到CSS中断挂起位被清除。
3、时钟配置函数解析 ①时钟配置常用寄存器组
用结构体复位与时钟控制**(RCC, Reset and Clock Control)**来表示。
typedef struct
{
__IO uint32_t CR;
//HSI、HSE、CSS、PLL的使能和就绪标志位
__IO uint32_t CFGR;
//PLL、USB等时钟源的选择和分频系数的设置
__IO uint32_t CIR;
//清除/使能时钟就绪中断
__IO uint32_t APB2RSTR;
//APB2上的外设复位寄存器
__IO uint32_t APB1RSTR;
// APB1上的外设复位寄存器
__IO uint32_t AHBENR;
//DMA、CRC等模块时钟使能
__IO uint32_t APB2ENR;
//APB2外设时钟使能
__IO uint32_t APB1ENR;
//APB1外设时钟使能
__IO uint32_t BDCR;
//备份域控制寄存器
__IO uint32_t CSR;
//控制状态寄存器
} RCC_TypeDef;
要使用某个模块或外设时,务必保证其驱动时钟被使能。
②系统初始化函数(战舰版)void SystemInit(void)
下面仅截取SystemInit()中与RCC配置相关,且采用STM32F10X_HD预编译头的代码
void SystemInit (void)
{
/* Reset the RCC clock configuration to the default reset state(for debug purpose) */
/* Set HSION bit */
RCC->CR |= (uint32_t)0x00000001;
/* Reset SW, HPRE, PPRE1, PPRE2, ADCPRE and MCO bits */
RCC->CFGR &= (uint32_t)0xF8FF0000;
/* Reset HSEON, CSSON and PLLON bits */
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;
/* Disable all interrupts and clear pending bits*/
RCC->CIR = 0x009F0000;
#endif /* STM32F10X_CL *//* Configure the System clock frequency, HCLK, PCLK2 and PCLK1 prescalers */
/* Configure the Flash Latency cycles and enable prefetch buffer */
SetSysClock();
}
这个初始化函数对时钟系统的作用是:初始化RCC中的相关寄存器,并配置系统时钟。
对于RCC中寄存器的配置可以完全对照手册与注释进行解读,例如:
RCC->CFGR &= (uint32_t)0xF8FF0000
值得注意,位配置的 |= 运算一般是置位Set;&=运算一般是复位Reset。根据0xF8FF0000位向量知,此条指令是将CFGR中的[15:0]以及[26:24]清空。如图3所示为参考手册给出的CFGR位模式,清零位对应的是SW, HPRE, PPRE1, PPRE2, ADCPRE和MCO。
文章图片
图3 下面考察系统时钟设置函数SetSysClock()
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
}
系统时钟频率的设置是通过条件编译进行的,若宏定义了SYSCLK_FREQ_72MHz,则SetSysClock()中就只编译执行SetSysClockTo72()
为了研究更底层的封装,再考察SetSysClockTo72()
static void SetSysClockTo72(void)
{
__IO uint32_t StartUpCounter = 0, HSEStatus = 0;
/* SYSCLK, HCLK, PCLK2 and PCLK1 configuration ---------------------------*/
/* Enable 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;
StartUpCounter++;
} while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));
if ((RCC->CR & RCC_CR_HSERDY) != RESET) HSEStatus = (uint32_t)0x01;
else HSEStatus = (uint32_t)0x00;
if (HSEStatus == (uint32_t)0x01)
{
/* HCLK = SYSCLK */
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;
/* PLL configuration: PLLCLK = HSE * 9 = 72 MHz */
RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE |
RCC_CFGR_PLLMULL));
RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLMULL9);
/* Enable PLL */
RCC->CR |= RCC_CR_PLLON;
/* Wait till PLL is ready */
while((RCC->CR & RCC_CR_PLLRDY) == 0)
{
}/* Select PLL as system clock source */
RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL;
/* 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 */
}
}
此函数首先使能了时钟源HSE
RCC->CR |= ((uint32_t)RCC_CR_HSEON);
其中#define RCC_CR_HSEON ((uint32_t)0x00010000)
#define RCC_CR_HSERDY ((uint32_t)0x00020000)
因此这条指令作用是将RCC->CR的Bit16置1,结合图参考手册中图4可以确认,这条指令确实使能了HSE
文章图片
文章图片
图4 接下来很重要的一步操作是等待使能成功,例如上述执行使能HSE指令后,硬件开始根据指令执行置位操作,这里存在软硬件时间差 ? t ?t ?t,在 ? t ?t ?t内HSE仍然是除能状态,因此等待使能成功的目的就是起到对 ? t ?t ?t的过渡作用,防止硬件误动作。
do
{
HSEStatus = RCC->CR & RCC_CR_HSERDY;
StartUpCounter++;
} while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));
这个while循环首先用HSEStatus获取HSE状态:若HSERDY=1即HSE已经使能,则HSEStatus=1;否则HSEStatus=0。
确认使能了HSE后,便开始配置一系列的时钟,这里将HCLK、PCLK2、PLLCLK配置为72MHz,将PCLK1配置为36MHz,例如:
RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE |
RCC_CFGR_PLLMULL));
RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLMULL9);
①通过&=进行复位。出于安全性考虑,置位操作中可能用到的位先清0,以防置位时产生进位引发错误。可以看到,这里将时钟源选择位、预分频位和倍频位清0,即此时PLL处于无时钟源、不分频、不倍频的状态。
②通过|=进行置位。RCC_CFGR_PLLSRC_HSE即选择HSE作为PLL时钟源,HSE一般接8MHz晶振;RCC_CFGR_PLLMULL9选择倍频系数为9,因此PLLCLK为8×9=72MHz的时钟。
【【嵌入式系统】STM32时钟系统+时钟配置函数解析】至此,SystemInit()的原理讲解完毕。事实上,用户也可根据需要,自行选择相应寄存器来设计并封装初始化函数
推荐阅读
- 课程笔记|uCos中的邮箱和消息队列
- 课程笔记|嵌入式系统复习题
- STM32的串口空闲中断
- 【嵌入式系统】工作模式与任务特权级
- 【嵌入式系统】存储器映射与寄存器映射原理
- 【嵌入式系统】DMA工作原理与常用函数解析
- 电子模块实验|看看那些来自电路造型挑战赛中的作品
- led.S:12: Error: invalid constant -- `mov r0,#300000'