【STM32F407-HAL】1. RCC时钟
基本概念
- HSE 振荡器时钟:可以由外部晶振或外部源(可能是其他单片机输出的时钟信号)提供。通常是由外部晶振提供信号源,使用时需要先读取
HSERDY(RCC_CR[17])判断 HSE 是否稳定,稳定后使能HSEON(RCC_CR[16])后使用。 - HSI 系统内部时钟:开机上电后系统首先使用的时钟,SYSCLK 默认初始使用的时钟。
- PLLCLK 时钟:由 HSE 时钟经过 PLL 机构进行分频、倍频之后得到的时钟。
- SYSCLK 系统时钟:可以设置成 HSI、HSE 以及 PLLCLK 作为输入。
时钟树
时钟树左侧都是和片外进行信号通信的输入输出的时钟源信号,右侧箭头最终指向系统给各个片内设备的时钟。
先关注系统时钟,也就是 HCLK到AHB总线、内核、存储器和DMA的时钟。通过时钟树可以看到,到 AHB 总线、内核、存储器和 DMA 的时钟由 SYSCLK 提供,而 SYSCLK 由 HSI、HSE 或 PLLCLK 三者其中一个提供,通常为 PLLCLK。而 PLLCLK 的源头是 HSE 经过 PLL 的各种变频变化后得到。

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

HAL 代码分析
HAL 库中 RCC 的初始化主要使用两个函数,把时钟源和分频倍频配置好,再把各个总线的时钟配置好就完成了。话虽这么多,但是 RCC 里面涉及到的寄存器真的太多了,寄存器里面每一个位的配置又都不太相同,需要清楚了解各个位置的功能,全部都弄明白其实也挺不容易的。
有几个概念需要落实:
- HSI 和 HSE 时钟,是通过内部或者外部晶振提供的时钟频率信号,是一个具体的信号(一对一的关系,没得选,所以 STM32F407 中直接就是具体指外部晶振信号了)。
- RCC_CR 寄存器的 CSSON 位,CSS 开了,HSE 崩了会自动切 HSI;CSS 关掉,HSE 坏了不会自动切换,单片机直接死机、跑飞、锁死。TIM1 和 TIM8 作为外部输出 PWM 的时候因为用的也是 HSE,如果 HSE 坏了,CSS 保护机制会立刻断掉 PWM 输出功能。这么一看,如果要用 HSE,CSSON 位还是默认开启吧。
- PLL 就是用来配置分频、倍频的东西。RCC_CR 寄存器里面 4 个和 PLL 相关,2 个 PLL,2 个 PLLI2S,都是使能和检测状态。其他 PLL 有关的配置就都在 RCC_PLLCFGR 寄存器中。
- RCC_CFGR 寄存器就是做一些时钟配置,选择什么时钟作为内部时钟输入,什么时钟作为输出。
- SYSCLK 先是一个抽象的概念,在选择 SYSCLK 是根据什么输入源以后就变得具体,但一开始只是一个抽象的概念。但是一般在使用的时候,都会将 SYSCLK 选择为经过了 PLL 分频和倍频的 HSE ,也就是 PLLCLK。
初始化系统时钟
这个部分就是给系统时钟 SYSCLK 进行配置,包括 HSE、PLL 的配置。
__weak HAL_StatusTypeDef HAL_RCC_OscConfig(const RCC_OscInitTypeDef *RCC_OscInitStruct)
/**
* @brief Initializes the RCC Oscillators according to the specified parameters in the
* RCC_OscInitTypeDef.
* @param RCC_OscInitStruct pointer to an RCC_OscInitTypeDef structure that
* contains the configuration information for the RCC Oscillators.
* @note The PLL is not disabled when used as system clock.
* @note Transitions LSE Bypass to LSE On and LSE On to LSE Bypass are not
* supported by this API. User should request a transition to LSE Off
* first and then LSE On or LSE Bypass.
* @note Transition HSE Bypass to HSE On and HSE On to HSE Bypass are not
* supported by this API. User should request a transition to HSE Off
* first and then HSE On or HSE Bypass.
* @retval HAL status
*/
__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 总线的分频系数的调整。
评论