打印
[STM32L4]

STM32L4系列一之时钟系统简介

[复制链接]
761|11
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
jcky001|  楼主 | 2022-11-18 09:08 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
主要内容
1)STM32L4 时钟树概述;
2)STM32L4 时钟初始化配置;
3)STM32L4 时钟使能和配置。

一、STM32L4 时钟树概述
时钟系统是 CPU 的脉搏。
不同于51单片机一个系统时钟解决一切问题,STM32 有多个时钟源。这是因为STM32本身的外设非常多,但并不是所有外设都需要系统时钟这么高的频率。比如看门狗以及 RTC 只需几十 k 的时钟即可。
同一电路,时钟越快功耗越大,同时抗电磁干扰能力也会越弱。
所以对于较为复杂的 MCU 一般采取多时钟源方法解决这些问题。

STM32L4 的时钟系统图如下:




使用特权

评论回复
沙发
jcky001|  楼主 | 2022-11-18 09:09 | 只看该作者
在 STM32L475 中,有 6 个重要的时钟源,为 HSI、HSE、LSI、LSE、MSI、PLL。
其中PLL 实际为三个时钟源,分别为主 PLL 和、PLLISAI1 和 PLLSAI2。
按照时钟频率,可分为高速时钟源和低速时钟源,其中 HSI,HSE,MSI 及 PLL 是高速时钟,LSI 和 LSE 是低速时钟。
按照来源,可分为外部时钟源和内部时钟源,外部时钟源是从外部通过接晶振的方式获取时钟源,HSE 和 LSE 是外部时钟源,其他是内部时钟源。
按上图中红圈标示的顺序分别介绍这6个时钟源:
① LSI 是低速内部时钟,RC 振荡器,频率为 32kHz 左右。供独立看门狗、RTC 和 LCD使用。
② LSE 是低速外部时钟,接频率为 32.768kHz 的石英晶体。这个主要是 RTC 的时钟源。
③ HSE 是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为4MHz-48MHz。本开发板接的是 8MHz 的晶振。HSE 也可以直接做为系统时钟或者 PLL 输入。
④ HSI 是高速内部时钟,RC 振荡器,频率为 16MHz。可以直接作为系统时钟或者用作PLL 输入。
⑤ MSI 时钟信号由内部 RC 振荡器产生。其频率范围可通过时钟控制寄存器(RCC_CR)中的 MSIRANGE[3:0]位进行调整。
⑥ PLL 为锁相环倍频输出。STM32L4 有三个 PLL:
1) 主 PLL(PLL)可由 HSE、HSI 或者 MSI 提供时钟信号,并具有三个不同的输出时钟:
第一个输出 PLLR,用于生成高速的系统时钟(SYSTEM,最高 80MHz);
第二个输出 PLLQ,可为 USB、RNG 和 SDMMC 提供时钟源 ;
第三个输出 PLLP,可用于 SAI1 和 SAI2 时钟。
2) PLLSAI1 用于生成精确时钟,同样具有三个不同的输出时钟:
第一个输出 PLLSAI1P,可用于 SAI1 和 SAI2 时钟;
第二个输出 PLLSAI1Q,可为 USB、RNG 和 SDMMC 提供时钟源;
第三个输出 PLLSAI1R,可为 ADC 提供时钟。
3) PLLSAI2 用于生成精确时钟,具有两个不同的输出时钟:
第一个输出 PLLSAI2P,可用于 SAI1 和 SAI2 时钟;
第二个输出 PLLSAI2R,可为 ADC 提供时钟。

这里重点分析 PLL 时钟第一个高速时钟输出 PLLR 的计算方法。先把图局部放大,如下图所示:


上图中,主 PLL 的时钟源要先经过一个分频系数为 M 的分频器,然后经过倍频系数为 N 的倍频器,出来之后还需要经过分频系数为 R(输出 PLLR 时钟)、或者 P(输出 PLLP时钟)、或者 Q(输出 PLLQ 时钟),最后才生成最终的主 PLL 时钟。

举个栗子:
外部的晶振选择为 8MHz,同时设置分频器 M=1,倍频器倍频系数N=20,分频器分频系数 R=2,那么主 PLL 生成的PLLR 为:
PLL=8MHz*N/(M*R)=8MHz*20/(1*2)=80MHz

如果选择HSE为PLL时钟源,同时SYSCLK时钟源为PLL,那么SYSCLK时钟为80MHz。

下面,介绍这 6 个时钟源是怎么给各个外设以及系统提供时钟的
观察之前给出的时钟系统图中标出的9个字母(A~I):
A. 这是看门狗时钟。从图中可以看出看门狗时钟源只能是低速的 LSI 时钟;
B. 这是 RTC 与 LCD 时钟源,可以选择 LSI、LSE 以及 HSE 分频后的时钟,HSE 分频系数可设为 2~31;
C. 这是 STM32L475 输出时钟 MCO。MCO 是向芯片 PA8 引脚输出时钟。它有七个时钟来源分别为:LSE、LSI、HSE、SYSCLK、MSI、HSI 和 PLL 时钟;
D. 这是系统时钟。SYSCLK 系统时钟来源有四个方面:HSI,HSE、MSI 和 PLL;
E. 这是 PWR 时钟、AHB 时钟、APB1 时钟和 APB2 时钟。这些时钟都是来源于SYSCLK系统时钟。其中AHB、APB1和APB2时钟都是经过SYSCLK时钟分频得来,且这三个时钟最大频率为 80MHz;
F. 这是 48MHz 时钟,主要用于 USB、RNG、SDMMC 时钟。这里的时钟源来自三个方面:MSI、PLLQ 和 PLLSAI1Q;
G. 这是 ADC 的时钟,这里的时钟源来自三个方面:SYSCLK、PLLSAI1R 和 PLLSAI2R;
H. 这里是 SAI1 的时钟,这里的时钟源来自四个方面:PLLP、PLLSAI1P、PLLSAI2P 和SAI1_EXTCLK;
I. 这里是 SAI2 的时钟,这里的时钟源来自四个方面:PLLP、PLLSAI2P、PLLSAI2P 和SAI2_EXTCLK。

使用特权

评论回复
板凳
jcky001|  楼主 | 2022-11-18 09:10 | 只看该作者
备注
1)Cortex系统定时器Systick的时钟源可以是AHB时钟HCLK或HCLK的 8 分频。具体配置参考 Systick 定时器配置。
2)在以上的时钟输出中,有很多是带使能控制的,例如 AHB 总线时钟、内核时钟、各种 APB1 外设、 APB2 外设等等。当需要使用某模块时,记得一定要先使能对应的时钟。

二、STM32L4 时钟系统初始化配置
在系统启动之后,程序会先执行 HAL 库定义的 SystemInit 函数(该函数位于system_stm32l4xx.c源文件里),进行一些初始化配置:

  • /*********************************************************************
  • 函数名称:void SystemInit(void)
  • 函数功能:1)  FPU 设置
  •             2)  复位 RCC 时钟配置为默认复位值(默认开启 MSI)
  •             3)  中断向量表地址配置
  • 入口参数:无
  • 返回参数:无
  • **********************************************************************/
  • void SystemInit(void)
  • {
  •   #if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
  •     SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2));   // 如果需要 FPU 的话就使能 FPU,设置 CP10 和 CP11 为全访问
  •   #endif
  •   /*  复位 RCC 时钟配置为默认配置-----------*/
  •   RCC->CR |= RCC_CR_MSION;                                             // 打开 MSION 位
  •   RCC->CFGR = 0x00000000U;                                              // 复位 CFGR 寄存器
  •   RCC->CR &= 0xEAF6FFFFU;                                              // 清除 HSEON,CSSON,HSION ,PLLON 位
  •   RCC->PLLCFGR = 0x00001000U;                                   // 复位 PLLCFGR 寄存器
  •   RCC->CR &= 0xFFFBFFFFU;                                           // 复位 HSEBYP 位
  •   RCC->CIER = 0x00000000U;                                           // 关闭所有的中断
  • /*  配置中断向量表地址=基地址+偏移地址  ------------------*/
  •   #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
  • }

复制代码


HAL 库的 SystemInit 函数除了打开 MSI 之外,没有任何时钟相关配置,所以必须编写自己的时钟配置函数。这里,看下正点原子在工程SYSTEM分组下提供的sys.c文件中的时钟初始化函数 SystemClock_Config 的内容:

  • /*********************************************************************
  • 函数名称:void SystemClock_Config(void)
  • 函数功能:SYSCLK = HSE / PLLM * PLLN / PLLR
  •             SYSCLK = 8M  /   1  *  20  /2  =  80M
  • 入口参数:无
  • 返回参数:无
  • **********************************************************************/
  • void SystemClock_Config(void)
  • {
  •     HAL_StatusTypeDef  ret = HAL_OK;
  •     RCC_OscInitTypeDef RCC_OscInitStruct;
  •     RCC_ClkInitTypeDef RCC_ClkInitStruct;
  •     __HAL_RCC_PWR_CLK_ENABLE();                                                         // 第一步,使能PWR时钟
  •     /*Initializes the CPU, AHB and APB busses clocks*/
  •     RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;// 时钟源选择为 HSE
  •     RCC_OscInitStruct.HSEState = RCC_HSE_ON;                                // 打开 HSE
  •     RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;                        // 打开 PLL
  •     RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;      // PLL 时钟源为 HSE
  •     RCC_OscInitStruct.PLL.PLLM = 1;
  •     RCC_OscInitStruct.PLL.PLLN = 20;
  •     RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV7;
  •     RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;
  •     RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
  •     ret = HAL_RCC_OscConfig(&RCC_OscInitStruct);              // 第二步,配置时钟源相关参数
  •     /* 第二步主要功能,开启了HSE时钟源,同时选择PLL时钟源为HSE,同时也配置了PLL的参数 M,N,M,P和Q的值,这样就达到了设置PLL时钟源相关参数的目的。设置好PLL时钟源参数,也就是确定了PLL的时钟频率 */
  •     if(ret != HAL_OK) while(1);
  •     /*选中 PLL 作为系统时钟源并且配置 AHB、APB1、APB2*/
  •     RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK
  •                                                         | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2; // 配置SYSCLK,HCLK、PCLK2、PCLK1四个时钟。
  •     RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;                         // 选择系统时钟源为 PLL
  •     RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;                                          // AHB 分频系数为 1
  •     RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;                                          // APB1 分频系数为 1
  •     RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;                                          // APB2 分频系数为 1
  •     ret = HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4); // 第三步, 配置系统时钟源以及SYSCLK、AHB,APB1和APB2的分频系数
  •     /* 第三步配置后,可以计算出:
  •                     PLL时钟为 PLLCLK=HSE*N/(M*R)=8MHz*20/(1*2)=80MHz;
  •                     选择系统时钟源为 PLL,所以系统时钟SYSCLK=80MHz;
  •                     AHB 分频系数为 1,故其频率为 HCLK=SYSCLK/1=80MHz;
  •                     APB1 分频系数为 1,故其频率为PCLK1=HCLK/1=80MHz;
  •                     APB2 分频系数为 1,故其频率为 PCLK2=HCLK/1=80MHz。*/
  •     if(ret != HAL_OK) while(1);
  •     /*配置主内部调压器输出电压级别*/
  •     ret = HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1);  // 第四步, 设置调压器输出电压级别
  •     if(ret != HAL_OK) while(1);
  • }

复制代码


使用特权

评论回复
地板
jcky001|  楼主 | 2022-11-18 09:15 | 只看该作者
函数 SystemClock_Config 的作用是进行时钟系统配置,除了配置 PLL 相关参数确定SYSCLK值之外,还配置了AHB,APB1和APB2的分频系数,也就是确定了HCLK,PCLK1 和 PCLK2 时钟值。
使用 HAL 库配置 STM32L4 时钟系统的一般步骤:
1.使能 PWR 时钟:调用函数__HAL_RCC_PWR_CLK_ENABLE() ,因为后面要设置调压器输出电压级别是电源控制相关配置;
2.配置时钟源相关参数:调用函数 HAL_RCC_OscConfig()。
3.配置系统时钟源以及 SYSCLK 、 AHB,APB1 和 APB2 的分频系数:调用函数HAL_RCC_ClockConfig()。
4.设置调压器输出电压级别:调用函数 HAL_PWREx_ControlVoltageScaling ()。
对于步骤2,使用HAL库来配置时钟源相关参数,调用了HAL_RCC_OscConfig()函数,该函数在HAL库头文件stm32l4xx_hal_rcc.h中声明如下:
  • HAL_StatusTypeDef HAL_RCC_OscConfig(RCC_OscInitTypeDef   *RCC_OscInitStruct)

复制代码


HAL_RCC_OscConfig()函数只有一个入口参数,即RCC_OscInitTypeDef类型指针,定义如下:

  • typedef struct
  • {
  •    uint32_t OscillatorType;      // 需要选择配置的振荡器类型
  •    uint32_t HSEState;            // HSE 状态
  •    uint32_t LSEState;            // LSE 状态
  •    uint32_t HSIState;            // HSI 状态
  •    uint32_t HSICalibrationValue; // HSI 校准值
  •    uint32_t LSIState;            // LSI 状态
  •    uint32_t MSIState             // MSI 的状态
  •    uint32_t MSICalibrationValue; // MSI 校准值
  •    uint32_t MSIClockRange;       // MSI 时钟范围
  •    uint32_t HSI48State;          // HSI48 状态
  •    RCC_PLLInitTypeDef PLL;       // PLL 配置
  • }
  • RCC_OscInitTypeDef;

复制代码


该结构体前几个参数用来选择配置的振荡器类型,如要开启 HSE,那么设置 OscillatorType 的值为 RCC_OSCILLATORTYPE_HSE,然后设置 HSEState 的值为 RCC_HSE_ON 开启 HSE。对于其他时钟源 HSI,LSI 和 LSE,配置方法类似。
该结构体还有一个很重要的成员变量是 PLL,它是结构体 RCC_PLLInitTypeDef 类型。它的作用是配置 PLL相关参数,定义如下:

  • typedef struct
  • {
  •    uint32_t PLLState;     // PLL 状态
  •    uint32_t PLLSource;    // PLL 时钟源
  •    uint32_t PLLM;         // PLL 分频系数 M
  •    uint32_t PLLN;         // PLL 倍频系数 N
  •    uint32_t PLLP;         // PLL 分频系数 P
  •    uint32_t PLLQ;         // PLL 分频系数 Q
  •    uint32_t PLLR;         // PLL 分频系数 R
  • }
  • RCC_PLLInitTypeDef;

复制代码


该结构体主要用来设置 PLL 时钟源以及相关分频倍频参数。

对于步骤3,HAL_RCC_ClockConfig()函数,声明如下:

  • HAL_StatusTypeDef   HAL_RCC_ClockConfig(RCC_ClkInitTypeDef   *RCC_ClkInitStruct,   uint32_t FLatency);

复制代码


该函数有两个入口参数,第一个入口参数 RCC_ClkInitStruct 是结构体 RCC_ClkInitTypeDef指针类型,用来设置 SYSCLK 时钟源以及 SYSCLK、AHB,APB1 和 APB2 的分频系数。第二个入口参数 FLatency 用来设置 FLASH 延迟。

对于步骤3中函数 HAL_RCC_ClockConfig 第二个入口参数 FLatency的含义和步骤四,只需要知道调压器输出电压级别 VOS 和FLASH的
延迟Latency 两个参数,在我们芯片电源电压和 HCLK 固定之后,他们两个参数也是固定的。
首先我们来看看调压器输出电压级别 VOS,它是由 PWR 控制寄存器 CR1 的位 10:9 来确定的:

  • 位 15:14 VOS[1:0]
  • 00: Cannot be written
  • 01:Range 1
  • 10:Range 2
  • 11: Cannot be written

复制代码


级别数值越小工作频率越高,所以如果要配置 L4 的主频为 80MHz,那么必须配置调压器输出电压级别 VOS 为级别 1。
配置好调压器输出电压级别 VOS 之后,如果需要 L4 主频要达到 80MHz,还需要配置FLASH 延迟 Latency。对于 STM32L4 系列,FLASH 延迟配置参数值是通过下表来确定的:



可以看出,在 Vcore Range 1 时如果 HCLK 为 80Mhz,那么等待周期要 4WS 也就是5个CPU周期。在SystemClock_Config()函数中,设置值为 FLASH_LATENCY_4,也就是4WS,5个CPU 周期。

三、STM32L4 时钟使能和配置
在配置好时钟系统之后,如果要使用某些外设,例如 GPIO,ADC 等,还要使能这些外设时钟。注意,如果在使用外设之前没有使能外设时钟,这个外设是不可能正常运行的。STM32 的外设时钟使能是在 RCC 相关寄存器中配置的。

通过STM32L4的HAL库使能外设时钟的方法
在 STM32L4 的 HAL 库中,外设时钟使能操作都是在 RCC 相关固件库文件头文件stm32l4xx_hal_rcc.h 定义的。设时钟使能在 HAL 库中都是通过宏定义标识符来实现的。

使用特权

评论回复
5
jcky001|  楼主 | 2022-11-18 09:15 | 只看该作者
举个栗子
看看 GPIOA 的外设时钟使能宏定义标识符:

  • #define __HAL_RCC_GPIOA_CLK_ENABLE()            do { \
  •                                                                      __IO uint32_t tmpreg; \
  •                                                                      SET_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN); \
  •                                                                             tmpreg = READ_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN); \
  •                                                                             UNUSED(tmpreg); \
  •                                                 } while(0)

复制代码


这几行代码主要定义了一个宏定义标识符__HAL_RCC_GPIOA_CLK_ENABLE(),其核心操作是通过下面这行代码实现的:

  • SET_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN);

复制代码


这行代码的作用是,设置寄存器 RCC->AHB2ENR 的相关位为 1,至于是哪个位,是由宏定义标识符 RCC_AHB2ENR_GPIOAEN 的值决定的,而它的值为:

  • #define RCC_AHB2ENR_GPIOAEN_Pos               (0U)
  • #define RCC_AHB2ENR_GPIOAEN_Msk     (0x1UL << RCC_AHB2ENR_GPIOAEN_Pos)
  • #define RCC_AHB2ENR_GPIOAEN                   RCC_AHB2ENR_GPIOAEN_Msk

复制代码


上面三行代码很容易计算出来 RCC_AHB2ENR_GPIOAEN= 0x00000001,因此上面代码的作用是设置寄存器 RCC->AHB2ENR 寄存器的最低位为 1。从 STM32L4 的参考手册中搜索 AHB2ENR 寄存器定义,最低位的作用是用来使用 GPIOA 时钟。AHB2ENR 寄存器的位 0
描述如下:

  • Bit 0 GPIOAEN: IO port A clock enable   // GPIOA 时钟使能
  • Set and cleared by software.                       // 由软件置 1 和清零
  • 0: IO port A clock disabled                       // 禁止 GPIOA 时钟
  • 1: IO port A clock enabled                       // 使能 GPIOA 时钟

复制代码


那么只要在用户程序中调用宏定义标识符__HAL_RCC_GPIOA_CLK_ENABLE()就可以实现 GPIOA 时钟使能。使用方法为:

  • __HAL_RCC_GPIOA_CLK_ENABLE();        // 使能 GPIOA 时钟

复制代码


对于其他外设,同样都是在 stm32l4xx_hal_rcc.h 头文件中定义,只需要找到相关宏定义标识符即可,这里列出几个常用使能外设时钟的宏定义标识符使用方法:

  • __HAL_RCC_DMA1_CLK_ENABLE();   // 使能 DMA1 时钟
  • __HAL_RCC_USART2_CLK_ENABLE(); // 使能串口 2 时钟
  • __HAL_RCC_TIM1_CLK_ENABLE();   // 使能 TIM1 时钟

复制代码


使用外设的时候需要使能外设时钟,如果不需要使用某个外设,同样可以禁止某个外设时钟。禁止外设时钟使用方法和使能外设时钟非常类似,同样是头文件中定义的宏定义标识符。以 GPIOA 为例,宏定义标识符为:

  • #define __HAL_RCC_GPIOA_CLK_DISABLE()
  • CLEAR_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN)

复制代码


同样,宏定义标识符__HAL_RCC_GPIOA_CLK_DISABLE() 的作用是设置RCC->AHB2ENR 寄存器的最低位为0,也就是禁止GPIOA时钟。这里同样列出几个常用的禁止外设时钟的宏定义标识符使用方法:

  • __HAL_RCC_DMA1_CLK_DISABLE();   // 禁止 DMA1 时钟
  • __HAL_RCC_USART2_CLK_DISABLE(); // 禁止串口 2 时钟
  • __HAL_RCC_TIM1_CLK_DISABLE();   // 禁止 TIM1 时钟

复制代码


使用特权

评论回复
6
OKAKAKO| | 2024-6-25 15:13 | 只看该作者
时钟系统是MCU运行的基础

使用特权

评论回复
7
童雨竹| | 2024-12-1 08:18 | 只看该作者

如果防护与热设计欠佳

使用特权

评论回复
8
Wordsworth| | 2024-12-1 09:21 | 只看该作者

由于共模电流的同向性,会在线圈内产生同向的磁场而增大线圈的感抗

使用特权

评论回复
9
公羊子丹| | 2024-12-1 11:17 | 只看该作者

缩合型灌封硅胶由于固化过程有体积收缩一般不使用在模块电源的灌封中

使用特权

评论回复
10
万图| | 2024-12-1 12:20 | 只看该作者

在交流电频率一定情况下,电感量越大,其对交流电阻碍能力越大

使用特权

评论回复
11
帛灿灿| | 2024-12-1 15:19 | 只看该作者

电路的结构类似于全桥式,只是把其中的两只开关管(T3、T4)换成了两只等值大电容C1、C2。

使用特权

评论回复
12
周半梅| | 2024-12-1 18:18 | 只看该作者

脉冲变压器磁能被积累的问题容易解决,

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

1522

主题

4615

帖子

6

粉丝