【STM32F407-HAL】1. RCC时钟
基本概念
-
先搞懂几个时钟信号的概念:HSI、HSE、PLLCLK、SYSCLK
- HSI 内部振荡器时钟:芯片上电后系统默认使用的时钟,由芯片内部 16MHz 的 RC 振荡器产生的频率。精度不行,所以要用外部振荡器提供系统时钟。
- HSE 外部振荡器时钟:可以由外部晶振或外部源(可能是其他单片机输出的时钟信号)提供。通常是由外部晶振提供信号源。使用时需要先读取
HSERDY(RCC_CR[17])判断 HSE 是否稳定,稳定后使能HSEON(RCC_CR[16])后使用。 - PLL 锁相环:别管为什么叫锁相环,PLL 就是一个能把 HSE 时钟从一个频率调节到另一个频率的结构,输出的时钟信号就是 PLLCLK。PLL 锁相环结构有 M、P、Q、R 几个分频器,N 是倍频器,通过这几个调节输出频率。都在寄存器
RCC_PLLCFGR中(没看到 PLLR 的配置)。 - SYSCLK 系统时钟:输入到整个系统的时钟,给各种协议提供时钟源。可以通过
SW(RCC_CFGR[1:0])设置由 HSI、HSE、PLLCLK 作为时钟源。
时钟树
时钟树最左侧都是和片外进行信号通信,输入或输出的信号源;右侧箭头最终指向系统给各个片内或外设的时钟。
时钟树只需要先了解图中红线标注的流向,也就是 HCLK到AHB总线、内核、存储器和DMA的时钟。通过时钟树可以看到,到 AHB 总线、内核、存储器和 DMA 的时钟由 SYSCLK 提供,而 SYSCLK 由 HSI、HSE 或 PLLCLK 三者其中一个提供,通常为 PLLCLK。而 PLLCLK 的源头是 HSE 经过 PLL 的各种变频变化后得到。

这样系统的时钟的来源就很清晰了,这样就可以配置系统时钟了。

寄存器
- RCC_CR 寄存器的 CSSON 位:CSS 开了,HSE 崩了会自动切 HSI;CSS 关掉,HSE 坏了不会自动切换,单片机直接死机。TIM1 和 TIM8 作为外部输出 PWM 的时候因为用的也是 HSE,如果 HSE 坏了,CSS 保护机制会立刻断掉 PWM 输出功能。这么一看,如果要用 HSE,CSSON 位还是默认开启吧。
HAL 代码分析
-
HAL 库中 RCC 主要关系两个初始化函数,
HAL_RCC_OscConfig和HAL_RCC_ClockConfigHAL_RCC_OscConfig:主要是对 HSE、HSI 和 PLLCLK 进行配置。写HSEON(RCC_CR[16])配置 HSE;写HSION(RCC_CR[0])配置 HSI;写RCC_PLLCFGR寄存器的PLLSource、PLLM、PLLN、PLLP、PLLQ配置 PLLCLK 时钟。总的来说,主要就是配置RCC_CR和RCC_PLLCFGR寄存器。HAL_RCC_ClockConfig:配置 SYSCLK 系统时钟源、APB 总线和 AHB 总线。总得来说,主要就是操作RCC_PLLCFGR寄存器
-
对于各个 APBx、AHBx 外设时钟的使能和失能,HAL 库中没有使用函数来定义,都是写成宏的形式。实际上都是操作
RCC_AHBxRSTR、RCC_APBxRSTR、RCC_AHBxENR、RCC_APBxENR寄存器,操作的方式大同小异,就是直接给对应位写入目标值。
初始化系统时钟 HAL_RCC_OscConfig
__weak HAL_StatusTypeDef HAL_RCC_OscConfig(const RCC_OscInitTypeDef *RCC_OscInitStruct)
{
uint32_t tickstart;
uint32_t pll_config;
/* Check Null pointer */
if (RCC_OscInitStruct == NULL)
{
return HAL_ERROR;
}
/* Check the parameters */
assert_param(IS_RCC_OSCILLATORTYPE(RCC_OscInitStruct->OscillatorType));
/*------------------------------- HSE Configuration ------------------------*/
if (((RCC_OscInitStruct->OscillatorType) & RCC_OSCILLATORTYPE_HSE) == RCC_OSCILLATORTYPE_HSE)
{
/* Check the parameters */
assert_param(IS_RCC_HSE(RCC_OscInitStruct->HSEState));
/* When the HSE is used as system clock or clock source for PLL in these cases HSE will not disabled */
// 这里配置HSE作为PLLCLK作为SYSCLK之前,好像现检测是不是已经用HSE作为输入源了
// 这样检测难道是为了防止重复配置?还是另有企图?
if ((__HAL_RCC_GET_SYSCLK_SOURCE() == RCC_CFGR_SWS_HSE) || \
((__HAL_RCC_GET_SYSCLK_SOURCE() == RCC_CFGR_SWS_PLL) && ((RCC->PLLCFGR & RCC_PLLCFGR_PLLSRC) == RCC_PLLCFGR_PLLSRC_HSE)))
{
if ((__HAL_RCC_GET_FLAG(RCC_FLAG_HSERDY) != RESET) && (RCC_OscInitStruct->HSEState == RCC_HSE_OFF))
{
return HAL_ERROR;
}
}
else
{
/* Set the new HSE configuration ---------------------------------------*/
// 这里是直接给HSE配置状态使能了
__HAL_RCC_HSE_CONFIG(RCC_OscInitStruct->HSEState);
/* Check the HSE State */
// 检查HSE的状态,如果调用这个函数是为了使能HSE,那就等等看HSE是否真的使能了
if ((RCC_OscInitStruct->HSEState) != RCC_HSE_OFF)
{
/* Get Start Tick */
tickstart = HAL_GetTick();
/* Wait till HSE is ready */
// 这是一个超时检测,很多好像都会搞个超时的检测
while (__HAL_RCC_GET_FLAG(RCC_FLAG_HSERDY) == RESET)
{
if ((HAL_GetTick() - tickstart) > HSE_TIMEOUT_VALUE)
{
return HAL_TIMEOUT;
}
}
}
else
{
/* Get Start Tick */
// 这边也是同理,如果要关HSE,也是要等到HSE状态稳定
tickstart = HAL_GetTick();
/* Wait till HSE is bypassed or disabled */
while (__HAL_RCC_GET_FLAG(RCC_FLAG_HSERDY) != RESET)
{
if ((HAL_GetTick() - tickstart) > HSE_TIMEOUT_VALUE)
{
return HAL_TIMEOUT;
}
}
}
}
}
/* ...
...
中间HSI、LSI、LSE的配置省略,都没用到,不管了。
...
... */
/*-------------------------------- PLL Configuration -----------------------*/
/* Check the parameters */
assert_param(IS_RCC_PLL(RCC_OscInitStruct->PLL.PLLState));
if ((RCC_OscInitStruct->PLL.PLLState) != RCC_PLL_NONE)
{
/* Check if the PLL is used as system clock or not */
// SYSCLK如果不是PLLCLK
if (__HAL_RCC_GET_SYSCLK_SOURCE() != RCC_CFGR_SWS_PLL)
{
// 判断要把PLL启动的时候,做下面的操作,如果要启动就把PLLCFGR配置一下
if ((RCC_OscInitStruct->PLL.PLLState) == RCC_PLL_ON)
{
/* Check the parameters */
assert_param(IS_RCC_PLLSOURCE(RCC_OscInitStruct->PLL.PLLSource));
assert_param(IS_RCC_PLLM_VALUE(RCC_OscInitStruct->PLL.PLLM));
assert_param(IS_RCC_PLLN_VALUE(RCC_OscInitStruct->PLL.PLLN));
assert_param(IS_RCC_PLLP_VALUE(RCC_OscInitStruct->PLL.PLLP));
assert_param(IS_RCC_PLLQ_VALUE(RCC_OscInitStruct->PLL.PLLQ));
/* Disable the main PLL. */
// 要先关掉PLL才能配置
__HAL_RCC_PLL_DISABLE();
/* Get Start Tick */
tickstart = HAL_GetTick();
/* Wait till PLL is disabled */
while (__HAL_RCC_GET_FLAG(RCC_FLAG_PLLRDY) != RESET)
{
if ((HAL_GetTick() - tickstart) > PLL_TIMEOUT_VALUE)
{
return HAL_TIMEOUT;
}
}
/* Configure the main PLL clock source, multiplication and division factors. */
// 这里的操作全部都是“与”操作,这些变量基本都是啥0x00080000之类的值,与到一起最后统一一起写入寄存器。
WRITE_REG(RCC->PLLCFGR, (RCC_OscInitStruct->PLL.PLLSource | \
RCC_OscInitStruct->PLL.PLLM | \
(RCC_OscInitStruct->PLL.PLLN << RCC_PLLCFGR_PLLN_Pos) | \
(((RCC_OscInitStruct->PLL.PLLP >> 1U) - 1U) << RCC_PLLCFGR_PLLP_Pos) | \
(RCC_OscInitStruct->PLL.PLLQ << RCC_PLLCFGR_PLLQ_Pos)));
/* Enable the main PLL. */
__HAL_RCC_PLL_ENABLE();
/* Get Start Tick */
tickstart = HAL_GetTick();
/* Wait till PLL is ready */
while (__HAL_RCC_GET_FLAG(RCC_FLAG_PLLRDY) == RESET)
{
if ((HAL_GetTick() - tickstart) > PLL_TIMEOUT_VALUE)
{
return HAL_TIMEOUT;
}
}
}
else
{
// 这里处理如果是为了关闭PLL,那就只要关闭就好了。
/* Disable the main PLL. */
__HAL_RCC_PLL_DISABLE();
/* Get Start Tick */
tickstart = HAL_GetTick();
/* Wait till PLL is disabled */
while (__HAL_RCC_GET_FLAG(RCC_FLAG_PLLRDY) != RESET)
{
if ((HAL_GetTick() - tickstart) > PLL_TIMEOUT_VALUE)
{
return HAL_TIMEOUT;
}
}
}
}
else
{
/* Check if there is a request to disable the PLL used as System clock source */
// 这里的状态,是已经把PLLCLK作为SYSCLK了
// 已经是的情况下,如果要关闭PLL,就报错,因为关不掉了。。
// 其实没看懂,。这里不知道干嘛
if ((RCC_OscInitStruct->PLL.PLLState) == RCC_PLL_OFF)
{
return HAL_ERROR;
}
else
{
/* Do not return HAL_ERROR if request repeats the current configuration */
pll_config = RCC->PLLCFGR;
#if defined (RCC_PLLCFGR_PLLR)
if (((RCC_OscInitStruct->PLL.PLLState) == RCC_PLL_OFF) ||
(READ_BIT(pll_config, RCC_PLLCFGR_PLLSRC) != RCC_OscInitStruct->PLL.PLLSource) ||
(READ_BIT(pll_config, RCC_PLLCFGR_PLLM) != (RCC_OscInitStruct->PLL.PLLM) << RCC_PLLCFGR_PLLM_Pos) ||
(READ_BIT(pll_config, RCC_PLLCFGR_PLLN) != (RCC_OscInitStruct->PLL.PLLN) << RCC_PLLCFGR_PLLN_Pos) ||
(READ_BIT(pll_config, RCC_PLLCFGR_PLLP) != (((RCC_OscInitStruct->PLL.PLLP >> 1U) - 1U)) << RCC_PLLCFGR_PLLP_Pos) ||
(READ_BIT(pll_config, RCC_PLLCFGR_PLLQ) != (RCC_OscInitStruct->PLL.PLLQ << RCC_PLLCFGR_PLLQ_Pos)) ||
(READ_BIT(pll_config, RCC_PLLCFGR_PLLR) != (RCC_OscInitStruct->PLL.PLLR << RCC_PLLCFGR_PLLR_Pos)))
#else
if (((RCC_OscInitStruct->PLL.PLLState) == RCC_PLL_OFF) ||
(READ_BIT(pll_config, RCC_PLLCFGR_PLLSRC) != RCC_OscInitStruct->PLL.PLLSource) ||
(READ_BIT(pll_config, RCC_PLLCFGR_PLLM) != (RCC_OscInitStruct->PLL.PLLM) << RCC_PLLCFGR_PLLM_Pos) ||
(READ_BIT(pll_config, RCC_PLLCFGR_PLLN) != (RCC_OscInitStruct->PLL.PLLN) << RCC_PLLCFGR_PLLN_Pos) ||
(READ_BIT(pll_config, RCC_PLLCFGR_PLLP) != (((RCC_OscInitStruct->PLL.PLLP >> 1U) - 1U)) << RCC_PLLCFGR_PLLP_Pos) ||
(READ_BIT(pll_config, RCC_PLLCFGR_PLLQ) != (RCC_OscInitStruct->PLL.PLLQ << RCC_PLLCFGR_PLLQ_Pos)))
#endif /* RCC_PLLCFGR_PLLR */
{
return HAL_ERROR;
}
}
}
}
return HAL_OK;
}
初始化外设时钟 HAL_RCC_ClockConfig
这个函数传入了一个 FLatency 参数,表示 CPU 时钟周期与 Flash 访问时间之比,也就是访问 FLASH 时,CPU 要等待多少个时钟周期,CPU 速度越快,需要等待的时钟周期越多。因为 FLASH 速度比较慢,如果突然修改了系统时钟频率,会导致 FLASH 访问错误,改完时钟之后 FLASH 就无法操作了。所以要先改 FLASH 的访问频率。改的也就是 FLASH_ACR 寄存器的低 3 位。至于为什么代码里面设置了要等待 5 个周期,AI 是这么给出的解释:
| SYSCLK 主频 | 最小 Flash Latency | 宏定义 |
|---|---|---|
| 0 < f ≤ 30MHz | 0 wait | FLASH_LATENCY_0 |
| 30 < f ≤ 60MHz | 1 wait | FLASH_LATENCY_1 |
| 60 < f ≤ 90MHz | 2 wait | FLASH_LATENCY_2 |
| 90 < f ≤ 120MHz | 3 wait | FLASH_LATENCY_3 |
| 120 < f ≤ 150MHz | 4 wait | FLASH_LATENCY_4 |
| 150 < f ≤ 168MHz | 5 wait | FLASH_LATENCY_5 |
看了一下,这个函数主要初始化了 SYSCLK 的源、HCLK、PCLK1、PCLK2,相当于给外设 AHB、APB1、AP2 初始化好。
/**
* @brief Initializes the CPU, AHB and APB busses clocks according to the specified
* parameters in the RCC_ClkInitStruct.
* @param RCC_ClkInitStruct pointer to an RCC_OscInitTypeDef structure that
* contains the configuration information for the RCC peripheral.
* @param FLatency FLASH Latency, this parameter depend on device selected
*
* @note The SystemCoreClock CMSIS variable is used to store System Clock Frequency
* and updated by HAL_RCC_GetHCLKFreq() function called within this function
*
* @note The HSI is used (enabled by hardware) as system clock source after
* startup from Reset, wake-up from STOP and STANDBY mode, or in case
* of failure of the HSE used directly or indirectly as system clock
* (if the Clock Security System CSS is enabled).
*
* @note 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.
*
* @note Depending on the device voltage range, the software has to set correctly
* HPRE[3:0] bits to ensure that HCLK not exceed the maximum allowed frequency
* (for more details refer to section above "Initialization/de-initialization functions")
* @retval None
*/
HAL_StatusTypeDef HAL_RCC_ClockConfig(const RCC_ClkInitTypeDef *RCC_ClkInitStruct, uint32_t FLatency)
{
uint32_t tickstart;
/* Check Null pointer */
if (RCC_ClkInitStruct == NULL)
{
return HAL_ERROR;
}
/* Check the parameters */
assert_param(IS_RCC_CLOCKTYPE(RCC_ClkInitStruct->ClockType));
assert_param(IS_FLASH_LATENCY(FLatency));
/* To correctly read data from FLASH memory, the number of wait states (LATENCY)
must be correctly programmed according to the frequency of the CPU clock
(HCLK) and the supply voltage of the device. */
/* Increasing the number of wait states because of higher CPU frequency */
// 如果要设置的频率超过了FLASH的频率,就要先给FLASH调整频率,免得FLASH访问出问题
if (FLatency > __HAL_FLASH_GET_LATENCY())
{
/* Program the new number of wait states to the LATENCY bits in the FLASH_ACR register */
__HAL_FLASH_SET_LATENCY(FLatency);
/* Check that the new number of wait states is taken into account to access the Flash
memory by reading the FLASH_ACR register */
if (__HAL_FLASH_GET_LATENCY() != FLatency)
{
return HAL_ERROR;
}
}
/*-------------------------- HCLK Configuration --------------------------*/
if (((RCC_ClkInitStruct->ClockType) & RCC_CLOCKTYPE_HCLK) == RCC_CLOCKTYPE_HCLK)
{
/* Set the highest APBx dividers in order to ensure that we do not go through
a non-spec phase whatever we decrease or increase HCLK. */
// 这里在修改HCLK之前,先调低了APB1和APB2的时钟频率,这是因为修改HCLK是要修改AHB的分频系数
// ,AHB分频系数会影响APB的分频系数。因此如果突然调高了AHB,导致APB频率也被动拉高爆了就完了。
// 完事以后,配置HCLK就是配置一下CFGR寄存器的HPRE位。
if (((RCC_ClkInitStruct->ClockType) & RCC_CLOCKTYPE_PCLK1) == RCC_CLOCKTYPE_PCLK1)
{
MODIFY_REG(RCC->CFGR, RCC_CFGR_PPRE1, RCC_HCLK_DIV16);
}
if (((RCC_ClkInitStruct->ClockType) & RCC_CLOCKTYPE_PCLK2) == RCC_CLOCKTYPE_PCLK2)
{
MODIFY_REG(RCC->CFGR, RCC_CFGR_PPRE2, (RCC_HCLK_DIV16 << 3));
}
assert_param(IS_RCC_HCLK(RCC_ClkInitStruct->AHBCLKDivider));
MODIFY_REG(RCC->CFGR, RCC_CFGR_HPRE, RCC_ClkInitStruct->AHBCLKDivider);
}
/*------------------------- SYSCLK Configuration ---------------------------*/
if (((RCC_ClkInitStruct->ClockType) & RCC_CLOCKTYPE_SYSCLK) == RCC_CLOCKTYPE_SYSCLK)
{
assert_param(IS_RCC_SYSCLKSOURCE(RCC_ClkInitStruct->SYSCLKSource));
/* HSE is selected as System Clock Source */
if (RCC_ClkInitStruct->SYSCLKSource == RCC_SYSCLKSOURCE_HSE)
{
/* Check the HSE ready flag */
if (__HAL_RCC_GET_FLAG(RCC_FLAG_HSERDY) == RESET)
{
return HAL_ERROR;
}
}
/* PLL is selected as System Clock Source */
else if ((RCC_ClkInitStruct->SYSCLKSource == RCC_SYSCLKSOURCE_PLLCLK) ||
(RCC_ClkInitStruct->SYSCLKSource == RCC_SYSCLKSOURCE_PLLRCLK))
{
/* Check the PLL ready flag */
if (__HAL_RCC_GET_FLAG(RCC_FLAG_PLLRDY) == RESET)
{
return HAL_ERROR;
}
}
/* HSI is selected as System Clock Source */
else
{
/* Check the HSI ready flag */
if (__HAL_RCC_GET_FLAG(RCC_FLAG_HSIRDY) == RESET)
{
return HAL_ERROR;
}
}
// 实际配置的就这条代码,上面和下面的代码都是确保健壮性的冗余检查代码,确保代码执行不会出错。
// __HAL_RCC_SYSCLK_CONFIG是一个宏定义,将给定的SYSCLKSource赋值给CFGR的SW位。
__HAL_RCC_SYSCLK_CONFIG(RCC_ClkInitStruct->SYSCLKSource);
/* Get Start Tick */
tickstart = HAL_GetTick();
while (__HAL_RCC_GET_SYSCLK_SOURCE() != (RCC_ClkInitStruct->SYSCLKSource << RCC_CFGR_SWS_Pos))
{
if ((HAL_GetTick() - tickstart) > CLOCKSWITCH_TIMEOUT_VALUE)
{
return HAL_TIMEOUT;
}
}
}
/* Decreasing the number of wait states because of lower CPU frequency */
// 设置完成以后,看看FLASH频率是不是太高了,如果太高了就调到合适的频率。
if (FLatency < __HAL_FLASH_GET_LATENCY())
{
/* Program the new number of wait states to the LATENCY bits in the FLASH_ACR register */
__HAL_FLASH_SET_LATENCY(FLatency);
/* Check that the new number of wait states is taken into account to access the Flash
memory by reading the FLASH_ACR register */
if (__HAL_FLASH_GET_LATENCY() != FLatency)
{
return HAL_ERROR;
}
}
/*-------------------------- PCLK1 Configuration ---------------------------*/
if (((RCC_ClkInitStruct->ClockType) & RCC_CLOCKTYPE_PCLK1) == RCC_CLOCKTYPE_PCLK1)
{
assert_param(IS_RCC_PCLK(RCC_ClkInitStruct->APB1CLKDivider));
MODIFY_REG(RCC->CFGR, RCC_CFGR_PPRE1, RCC_ClkInitStruct->APB1CLKDivider);
}
/*-------------------------- PCLK2 Configuration ---------------------------*/
if (((RCC_ClkInitStruct->ClockType) & RCC_CLOCKTYPE_PCLK2) == RCC_CLOCKTYPE_PCLK2)
{
assert_param(IS_RCC_PCLK(RCC_ClkInitStruct->APB2CLKDivider));
MODIFY_REG(RCC->CFGR, RCC_CFGR_PPRE2, ((RCC_ClkInitStruct->APB2CLKDivider) << 3U));
}
/* Update the SystemCoreClock global variable */
SystemCoreClock = HAL_RCC_GetSysClockFreq() >> AHBPrescTable[(RCC->CFGR & RCC_CFGR_HPRE) >> RCC_CFGR_HPRE_Pos];
/* Configure the source of time base considering new system clocks settings */
HAL_InitTick(uwTickPrio);
return HAL_OK;
}
手搓驱动代码思路
- 先给外部输入进来的时钟做好配置,如给 PLL 配置时钟源是 HSE 还是 HSI,然后配置好 PLL 倍频分频系数,得到 PLLCLK。
- 给输入到内部各个时钟的时钟信号做好配置,如配置输入系统时钟是 HSE、HSI 还是 PLLCLK,AHB、APB1、APB2 总线的分频系数的调整。
评论