vivo手机商城免费检测网站seo
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_CLRCC->CFGR &= (uint32_t)0xF8FF0000;
#elseRCC->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_ExtSRAMSystemInit_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_SRAMSCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM. */
#elseSCB->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_HSESetSysClockToHSE();
#elif defined SYSCLK_FREQ_24MHzSetSysClockTo24();
#elif defined SYSCLK_FREQ_36MHzSetSysClockTo36();
#elif defined SYSCLK_FREQ_48MHzSetSysClockTo48();
#elif defined SYSCLK_FREQ_56MHzSetSysClockTo56();
#elif defined SYSCLK_FREQ_72MHzSetSysClockTo72();
#endif/* If none of the define above is enabled, the HSI is used as System clocksource (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 PROCEXPORT Reset_Handler [WEAK]IMPORT __mainIMPORT SystemInitLDR R0, =SystemInitBLX R0 LDR R0, =__mainBX R0ENDP
LDR 是传送指令, 看格式: LDR {条件} 目的寄存器 <存储器地址>
BLX : 将下一个指令的地址复制到lr(R14 链接寄存器)
LDR R0, =SystemInit
BLX R0
LDR R0, =__main
把 SystemInit 放到R0 中, 然后指向 下一条指令, 把 main 放到 R0中, 程序到这一步,就开始进入我们写的主函数中。