STM32属于低功耗芯片,和51相比,51在使用外设如某个IO口的时候,只需要给指定的IO口高低电平,然而STM32在给指定的IO口电平之前,还需要配置时钟,这就使从51转过来的同学,配置起来比较麻烦,现在我来给大家分析下 STM3的时钟, 有错误的地方请大家多多指正。
下面是STM32的时钟树
从图中可以看到,时钟有四个来源:
- HSI 高速的内部时钟 (8MHZ)
- HSE 高速的外部时钟 (4~16MHZ ,一般接8M,为了后面的PLL(锁相环,用来倍频用的))
- LSE 低速的外部时钟 (32.768kHZ)
- LSI 低速的内部时钟 (40KHZ)
时钟经过PLL后,给SYSCLK后 给AHB经过倍频分频 给APB2,APB1在给各个外设,也有一些芯片内通过AHB给时钟。
从图中我们可以看到, SYSCLK有三个来源:
- HSI 高速内部时钟
- PLLCLK 锁相环时钟
- HSE 高速外部时钟
SYSCLK 最是72MHZ。 APB2最大是72MHZ, APB1是36MHZ所以APB2相比而言是高速外设。
好了,时钟这里的理论部分分析完毕,我们接下来,具体是怎么来实现的。
单片机,说白了,就是在填写寄存器,特别是我们现在这样只是驱动外设就是在填写寄存器。
时钟控制的部分是RCC(Reset and clock control),RCC有如下寄存器
{
RCC_CR (时钟控制寄存器)
RCC_CFGR(时钟配置寄存器)
RCC_CIR(时钟中断寄存器)
RCC_APB2RSTR(APB2外设复位寄存器)
RCC_APB1RSTR(APB1外设复位寄存器)
RCC_AHBENR(AHB外设时钟使能寄存器)
RCC_APB2ENR(APB2外设时钟使能寄存器)
RCC_APB1ENR(APB1外设时钟使能寄存器)
RCC_BDCR(备份域控制寄存器)
RCC_CSR(控制/状态 寄存器)
}
开始看STM32的固件库是如何实现。
首先我们来看看程序是如何开始运行的。
int main(void)
{
led1_init();
key_gpio_init();
while(1)
{
if(key_scan(GPIOA,GPIO_Pin_0)==KEY_ON)
{
GPIOB->ODR ^=GPIO_Pin_0;
}
}
打开调试 会看到,程序一开始并不是直接进入***MAIN函数***里面的,而是进入 system_stm32f10x.c(位于Libraries//CMSIS)这个文件里面的
void SystemInit(void ) 里 在这个函数里面就会把ST32的时钟初始化,但是例如我们呢要用GPIOA的PIN2 这个外设是挂载在APB2上的。所以在使用的时候还是要 开启APB2中的GPIOA的时钟。
现在开始分析 SystemInit(void) 主要是 在外部高速时钟和内部高速时钟之间切换,在系统上电之初,使用内部的时钟,等到外部的稳定后,使用外部的时钟作为系统的时基。
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 */
#ifndef STM32F10X_CL
RCC->CFGR &= (uint32_t)0xF8FF0000;
#else
RCC->CFGR &= (uint32_t)0xF0FF0000;
#endif /* STM32F10X_CL */
/* 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;
#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
}
RCC->CR |= (uint32_t)0x0000 0001;
内部8MHZ时钟启动,(目前没有使用外部的高速时钟).
RCC->CFGR &= (uint32_t) 0xf8ff 0000;
效果是: 微控制器时钟没有输出,PLL时钟直接作为USB的时钟,PLL16倍频输出,HSE (8M) 2分频后作为PLL的输入。PLCK2(APB2) 二分频后作为ADC时钟, HCLK(AHB2)不分频, HCLK(AHB1)低速不分频, AHB(SYSCLK)不分频,
系统时钟切换 HSI(高速内部时钟)作为系统时钟,
RCC->CR &=(uint32_t) 0xfef6ffff; (1111 1110 1111 0110 )
效果是: PLL锁定,PLL关闭,时钟检测器关闭,外部高速晶体震荡器被旁路(没有被使用),外部高速晶体振荡器就绪,HSE振荡器关闭, 内部8M时钟就绪,内部8M开启。
RCC->CR &= 0XFFFB FFFF ; ( 1111 1111 1111 1011 )
效果是: PLL锁定,PLL使能,如果外部8M就绪,启动监测,外部高速晶体 没有旁路,外部高速就绪, HSE振荡器开启。
RCC->CFGR &= (uint32_t) 0xff80ffff 😭 1111 1111 1000 0000 )
效果是: PLL时钟2分频后输出, PLL时钟1.5倍分频后作为USB的时钟, PLL 2 倍频后输出, HSE不分频, HSI时钟2分频后作为PLL的输入时钟。 PLCK2 8分频后作为ADC时钟, HCLK(APB2)16分频, HCLK(APB1)16分频,SYSCLK 512分频,
之后,设置了一些安全时钟的,不去解释,可以自己去分析源码,把值填进相应的寄存器。
程序 接着进入了 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
/* If none of the define above is enabled, the HSI is used as System clock
source (default after reset) */
}
程序继而跳入 : SetSysClockTo72(); 可以从程序的名字了解到,把系统的时钟设置成了72MHZ。
/**
* @brief Sets System clock frequency to 72MHz and configure HCLK, PCLK2
* and PCLK1 prescalers.
* @note This function should be used only after reset.
* @param None
* @retval None
*/
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)
{
/* 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;
/* HCLK = SYSCLK */
RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;
/* PCLK2 = HCLK */
RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1;
/* PCLK1 = HCLK */
RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV2;
#ifdef STM32F10X_CL
/* Configure PLLs ------------------------------------------------------*/
/* PLL2 configuration: PLL2CLK = (HSE / 5) * 8 = 40 MHz */
/* PREDIV1 configuration: PREDIV1CLK = PLL2 / 5 = 8 MHz */
RCC->CFGR2 &= (uint32_t)~(RCC_CFGR2_PREDIV2 | RCC_CFGR2_PLL2MUL |
RCC_CFGR2_PREDIV1 | RCC_CFGR2_PREDIV1SRC);
RCC->CFGR2 |= (uint32_t)(RCC_CFGR2_PREDIV2_DIV5 | RCC_CFGR2_PLL2MUL8 |
RCC_CFGR2_PREDIV1SRC_PLL2 | RCC_CFGR2_PREDIV1_DIV5);
/* Enable PLL2 */
RCC->CR |= RCC_CR_PLL2ON;
/* Wait till PLL2 is ready */
while((RCC->CR & RCC_CR_PLL2RDY) == 0)
{
}
/* PLL configuration: PLLCLK = PREDIV1 * 9 = 72 MHz */
RCC->CFGR &= (uint32_t)~(RCC_CFGR_PLLXTPRE | RCC_CFGR_PLLSRC | RCC_CFGR_PLLMULL);
RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLXTPRE_PREDIV1 | RCC_CFGR_PLLSRC_PREDIV1 |
RCC_CFGR_PLLMULL9);
#else
/* 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);
#endif /* STM32F10X_CL */
/* 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 */
}
}
分析代码:
RCC->CR |= (uint32_t) RCC_CR_HSEON ; RCC_CR_HSEON 是一个宏定义 0x00010000;
效果是: 把 HSE开启, 在这里,我们应该可以看到了,芯片使用时钟的变化了,从内部的高速时钟转换成了外部的高速时钟,
这个函数看起来很麻烦,其实里面都是些条件编译条件,在我们使用的 STM32F103VE 芯片时只是用了几句话,要是你有兴趣,可以分析一下, 跳出这个函数后, 我们芯片里面的时钟也基本已经设置完成了。
继续往下看 程序跳到了这里 : startup_stm32f10x_hd.s 注意这是一个 汇编文件,
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT __main
IMPORT SystemInit
LDR R0, =SystemInit
BLX R0
LDR R0, =__main
BX R0
ENDP
LDR 是传送指令, 看格式: LDR {条件} 目的寄存器 <存储器地址>
BLX : 将下一个指令的地址复制到lr(R14 链接寄存器)
LDR R0, =SystemInit
BLX R0
LDR R0, =__main
把 SystemInit 放到R0 中, 然后指向 下一条指令, 把 main 放到 R0中, 程序到这一步,就开始进入我们写的主函数中。