ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

重温STM32基础:时钟树

重温STM32基础:时钟树 文章目录时钟产生电路4个时钟源SystemInit()时钟配置SystemInit()的调用SystemInit()时钟配置流程RM中关于系统时钟的描述SystemInit()代码简析使能HSI并初始化SetSysClockTo72()设置vector table频率限制安全机制摘要本文深入解析 STM32F103 的时钟树配置机制。首先介绍 4 个时钟源HSI、HSE、LSI、LSE及其用途随后详细剖析SystemInit()的调用时机与配置流程包括使能 HSI、通过SetSysClockTo72()将系统时钟从 8MHz 倍频到 72MHz、配置 Flash 预取与等待周期、设置总线分频系数以及向量表VTOR的重定位接着讨论 72MHz 主频的物理限制与超频风险最后介绍 CSS 时钟安全机制如何在外部时钟失效时自动切回 HSI保障系统稳定运行。时钟树是在STM32中产生时钟并且发配到STM32中各个外设的系统。时钟是一切外设运行的基础。配置流程有点复杂。STM32会提供时钟树配置代码就在SystemInit()中。相关代码是在我们的main函数之前运行的。上面是datasheet提供的时钟树结构图。中间有个红色虚线左边是时钟产生电路右边是时钟分配电路。时钟产生电路4个时钟源内部8MHz 高速RC振荡器外部4~16MHz高速石英晶体振荡器就是晶振。一般接8MHz外部32.768KHz低速晶振一般给RTC提供时钟内部40KHz低速RC振荡器一般给看门狗用1和2两个高速时钟都是提供系统时钟的。AHB,APB1和APB2都是来源于这两个高速晶振。这两个晶振都可以使用但是外部石英振荡器比内部RC振荡器更加稳定所以一般选外部石英晶振作为时钟源但是如果系统很简单而且不需要精确时钟那可以选择内部RC振荡器作为这些总线的时钟源这样可以省下外部石英晶振电路了。SystemInit()时钟配置SystemInit()的调用首先可以注意一下的是SystemInit()是在Reset_Handler()中被调用的Reset_Handler PROC EXPORT Reset_Handler[WEAK]IMPORT SystemInit IMPORT __main LDR R0,SystemInit BLX R0 LDR R0,__main BX R0 ENDP这里面有几个armasm汇编器的语法PROC…ENDP表明这是一个子程序procedure块是一个结构性标记EXPORT“导出这个符号”表示这个符号全局可见相当于C语言里把一个符号设置为“全局/外部”可见。不标明EXPORT表示这个符号只有当前文件可见IMPORT表明这个符号来自于另一个文件是另一个文件的EXPORT符号让链接器去其他文件里面找。相当于C里面的extern声明。WEAK表示这是一个弱符号。如果别的地方有强符号则用别的地方的定义。一般startup代码里面已经给Reset_Handler一个默认实现了。但是用户可以自己去实现同名的Reset_Handler来覆盖整个默认实现。SystemInit()时钟配置流程老师在课程中提到“在系统复位之后首先会使用HSI8MHz作为系统时钟然后启动HSE到PLL锁相环进行倍频9倍得到72MHz等到PLL稳定之后选择PLL为系统时钟。此时就把系统时钟从8MHz切换到了72MHz”。关于这个描述从RM和代码中都证实了一下。RM中关于系统时钟的描述After a system reset, the HSI oscillator is selected as system clock. When a clock source is used directly or through the PLL as the system clock, it is not possible to stop it.意思是在系统复位之后HSI会被选为系统时钟。当一个时钟源被直接或者被PLL用作系统时钟的时候是无法停止它的。对于后半句话开始我理解成了“一个时钟源运行起来之后就无法停止”但是其实不是。只有当它作为系统时钟的时候也就是被使用的时候不能停止。我们可以把系统时钟切换成其他时钟源然后停掉这个没有被使用的时钟。对于“被PLL使用的时候无法停止它”也很好理解因为时钟源是PLL的上游下游还在被使用的时候当然不能把源头停掉。A switch from one clock source to another occurs only if the target clock source is ready (clock stable after startup delay or PLL locked). If a clock source which is not yet ready is selected, the switch will occur when the clock source will be ready. Status bits in the Clock control register (RCC_CR) indicate which clock(s) is (are) ready and which clock is currently used as system clock.这一段的描述也可以在SystemInit()的代码中得到体现如下SystemInit()代码简析使能HSI并初始化上来就是这个操作/* Set HSION bit */RCC-CR|(uint32_t)0x00000001;就是把HSI打开。然后做了一大堆的配置和reset。切换到使用HSE并且配置成72MHz的过程是在最后的一个SetSysClock()中做的。基于system clock frequence的一个宏定义调用到SetSysClockTo72()。SetSysClockTo72()总体流程使能HSE并等待它其稳定配置Flash的prefetch和latency配置AHB, APB1和APB2的时钟分频系数使能PLL并等待它锁定把SYSCLK切换到PLL时钟这里主要分析1~3,4和5就是简单的配置寄存器。/* Enable HSE */RCC-CR|((uint32_t)RCC_CR_HSEON);/* Wait till HSE is ready and if Time out is reached exit */do{HSEStatusRCC-CRRCC_CR_HSERDY;StartUpCounter;}while((HSEStatus0)(StartUpCounter!HSE_STARTUP_TIMEOUT));首先使能了HSE然后开始等待一直到RCC_CR中的某个bit显示HSE准备好了。这和RM中的描述一致。后面一段代码看得我比较懵但是这一段是从系统角度考虑的/* 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;最开始是对FLASH的配置。从代码看这应该也是一个寄存器。但是在RM中没有找到。后来发现ST专门提供了另一个文档“STM32F10x闪存编程手册”这是一个programming manual而不受reference manual。也大概了解了一下这两类文档的区别RM主要描述系统架构、一般的外设它们的硬件和配置。主要告诉如何使用。总体来说如何配置寄存器简单理解而PM如其名字所示告诉如何编程编程模型指令和操作序列。涉及怎么解锁、发指令、怎么等待等。不是配置几个寄存器就完事的。ST对Flash和Cortex内核都提供了PM。具体的Flash操作看起来有点复杂以后研究吧。这里先看一下这个ACR。这里之所以要动Flash相关内容是因为MCU从Flash取指令但是Flash本身读写速度跟不上72MHz也就是说Flash控制器不可能在1/72^6 秒内获取一条指令。一开始8MHz的时候还能跟上所以需要配置Flash控制器让它在72MHz的时候多等几个周期保证一条完整指令被拿出来。另外还配置了开启prefetch buffer也是提升指令读取速度。后面三行代码是配置AHB, APB1和APB2的总线时钟的分频系数也就是第一幅图中的三个紫色的框的配置。同时从这幅图可以看到AHB的时钟由SYSCLK72MHz分频得到APB1和APB2的时钟是由AHB的时钟分频得到。根据几个宏定义的值AHB的分频系数是1也就是72MHzAPB1的分频系数是2也就是36MHzAPB2的分频系数是1也就是72MHz注之前在说定时器的时候一直说定时器的时钟源都是72MHz。但是从框图中看有些TIM是在APB1上的而APB1的频率是36MHz。是之前说错了么并不是可以发现框图中青色的框是从APB1之后分出来的它是把之前非1的分频再2倍频了它后面是TIM234所以这一路分支是专门为了TIM2,3,4配置的频率。之前2分频这里再2倍频所以TIM2,3,4的频率还是72MHz。TIM1也是一样的待遇不过它在APB2的分支上。所以在STM32F103中默认配置下用默认的systemInit()所有定时器的基准频率都是72MHz.框图中的粉红色框是“RCC使能”也就是对应代码里的诸如“RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);” 这样的代码后面就是设置PLL到72MHz并且在它稳定之后把它设置为系统时钟。设置vector tableSCB是Cortex 内核寄存器所以如果要细看就要看Cortex PMVTOR是vector table offset register。在systemInit的最后VTOR寄存器的值更新到RAM或者Flash。#ifdefVECT_TAB_SRAMSCB-VTORSRAM_BASE|VECT_TAB_OFFSET;/* Vector Table Relocation in Internal SRAM. */#elseSCB-VTORFLASH_BASE|VECT_TAB_OFFSET;/* Vector Table Relocation in Internal FLASH. */#endif之前听说ARM Cortex-M的core上来默认从0x00000000地址读取MSP从0x00000004得到Reset_Handler并跳转去执行。运行的起始地址不是应该是固定的么为什么这里还要更新VTORSTM32有存储器映射机制它的0x00000000是另一个地址的别名。这个零地址实际表示什么取决于两个Boot引脚在复位时的状态。BOOT1BOOT00x0000 0000 实际指向x0Flash0x0800 000001系统存储器内置 Bootloader11内部 SRAM0x2000 0000正常情况我们设置的时指向Flash也就是程序是在0x08000000这里的。程序上电之后读取MSP和Reset_Handler看上去是从0x00000000和0x00000004读实际读的是0x08000000和0x08000004。这里就是把SCB-VTOR指向真正的中断向量表也就是代码开始的地方。如果我们只有一个application程序直接放在FLASH里这一句赋值没有也没关系。但是如果我们自己的程序开始之前有bootloader程序比如0x08000000是bootloader程序app程序在0x08004000这个地方那么我们就必须指定SCB-VTOR了。其实ST的代码也为我们留好了接口就是设置VECT_TAB_OFFSET。这个值默认为0。如果app程序在0x08004000这个地方那么这个宏应该设置为0x4000。频率限制在STM32F103的datasheet上上来就说“支持最大72MHz”主频。这个最大频率哪儿来的从程序看起来系统时钟72MHz是通过程序设置的从电路结构看起来它还取决于外部时钟频率。那我们可以挂一个更高频的外部晶振比如16MHz或者调大PLL倍频来一个大于72MHz的频率可以么这个操作本身当然可以甚至可以让MCU跑在144MHz超频但是这已经不是STM32的规格保证很不建议这么做。72MHz是ST官方在芯片设计、制造和测试中确定的保证稳定工作的最高频率它取决于MCU中的晶体管速、Flash访问时间和总线时序等这是一个物理限制。所以在设计板子时要基于MCU的主频来选择外部晶振并且如果有必要需要修改PLL倍频系数。而不是反过来用PLL和外部晶振决定MCU主频另外PLL本身也是对输入的时钟频率有限制的。这是硬件相关的特性相关信息可以在DS中找到而不是RM。从这个表格可以看出PLL的输入范围是1~25MHz。之前想过为什么不直接用一个72MHz的外部晶振。。。因为这里PLL顶不住啊。要么就是先降频再倍频的操作不过这样就多此一举了。所以外部晶振的选择要迁就MCU的主频以及它内部的电路限制而不是外部晶振决定MCU的频率上限。安全机制CSS模块可以监测外部时钟的状态如果外部时钟失效CSS会把系统时钟源切换回HSI保证系统时钟的运行防止卡死造成事故。包括再高级定时器的刹车系统处也有CSS的身影。目的就是为了在外部时钟异常的时候让输出控制的电机立刻停止。课程中老师也提了一个他曾经踩过的坑如果HSE坏了我们可能看到的现象是定时器报定时中断频率比我们设置的要慢将近10倍。因为STM32会切换到默认的HSI时钟HSI的频率是8MHz只有期望值的1/9。所以如果感觉系统时钟慢了可以怀疑一些HSE是不是正常被使用或者是否正常工作。
返回列表