该函数只有一个入口参数,就是结构体 RCC_OscInitTypeDef 类型指针。接下来我们看看结构体 RCC_OscInitTypeDef 的定义: - typedef struct
- {
- uint32_t OscillatorType; //需要选择配置的振荡器类型
- uint32_t HSEState; //HSE 状态
- uint32_t LSEState; //LSE 状态
- uint32_t HSIState; //HIS 状态
- uint32_t HSICalibrationValue; //HIS 校准值
- 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;V
复制代码
从 RCC_PLLInitTypeDef;结构体的定义很容易看出该结构体主要用来设置 PLL 时钟源以及相关分频倍频参数。接下来我们看看我们的时钟初始化函数SystemClock_Config 中的配置内容: - 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 的时钟频率。 接下来我们就需要设置系统时钟,以及SYSCLK、AHB, APB1 和 APB2 相关参数,也就是我们前面提到的步骤 3接下来我们来看看步骤 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 延迟,这个参数我们放在后面跟步骤 4 一起讲解。 RCC_ClkInitTypeDef 结构体类型定义非常简单,就不列出来了, 我们来看看SystemClock_Config 函数中的配置内容: - *选中 PLL 作为系统时钟源并且配置 SYSCLK、 PCLK1、 PCLK2*/
- RCC_ClkInitStruct.ClockType=RCC_CLOCKTYPE_HCLK |
- RCC_CLOCKTYPE_SYSCLK |
- RCC_CLOCKTYPE_PCLK1 |
- RCC_CLOCKTYPE_PCLK2;
- RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
- RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
- RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
- RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
- ret = HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4);
复制代码
第一个参数 ClockType 配置说明我们要配置的是 SYSCLK, HCLK、 PCLK2、 PCLK1 四个时钟。 第二个参数 SYSCLKSource 配置选择系统时钟源为 PLL。 第三个参数 AHBCLKDivider 配置 AHB 分频系数为 1。 第四个参数 APB1CLKDivider 配置 APB1 分频系数为 1。 第五个参数 APB2CLKDivider 配置 APB2 分频系数为 1。 根据函数初始化参数值,我们可以计算出, 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。 最后我们总结一下通过调用函数 SystemClock_Config 之后的关键时钟频率值: - SYSCLK(系统时钟) =80MHz
- PLL 主时钟 =80MHz
- AHB 总线时钟(HCLK=SYSCLK/1) =80MHz
- APB1 总线时钟(PCLK1=HCLK/1) =80MHz
- APB2 总线时钟(PCLK2=HCLK/1) =80MHz
复制代码
我们来看看步骤 1 和步骤 4 中函数 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。所以函数 SystemClock_Config 中步骤 4 源码如下: - //步骤 1, 设置调压器输出电压级别 1
- HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1);
复制代码
配置好调压器输出电压级别 VOS 之后,如果需要 L4 主频要达到 80MHz,还需要配置FLASH 延迟 Latency。对于 STM32L4 系列, FLASH 延迟配置参数值是通过下表来确定的: 从上表可以看出,在 Vcore Range 1 时如果 HCLK 为 80Mhz,那么等待周期要 4WS 也就是 5 个 CPU 周期。下面我们看看我们在 SystemClock_Config 中调用函数 HAL_RCC_ClockConfig的时候,第二个入口参数设置值: ret=HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4);从上可以看出,我们设置值为 FLASH_LATENCY_4,也就是 4WS, 5 个 CPU 周期。
3. STM32L4 时钟使能和配置 上面讲解了时钟系统配置步骤。 在配置好时钟系统之后,如果我们要使用某些外设,例如 GPIO, ADC 等,我们还要使能这些外设时钟。 如果在使用外设之前没有使能外设时钟,这个外设是不可能正常运行的。 STM32 的外设时钟使能是在 RCC 相关寄存器中配置的。 RCC 相关寄存器非常多,可以通过《STM32L4XX 编程参考手册》 6.4 小节查看所有 RCC 相关寄存器的配置。 接下来讲解通过 STM32L4 的HAL 库使能外设时钟的方法。 在 STM32L4 的 HAL 库中,外设时钟使能操作都是在 RCC 相关固件库文件头文件stm32l4xx_hal_rcc.h 定义的。打开 stm32l4xx_hal_rcc.h 头文件可以看到文件中除了少数几个函数声明之外大部分都是宏定义标识符。外设时钟使能在 HAL 库中都是通过宏定义标识符来实现的。首先,来看看 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(不是AHB2ENR_GPIOAEN的位0) 寄存器的位 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();<i>//使能 GPIOA 时钟</i>
复制代码
对于其他外设,同样都是在 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 时钟
复制代码
|