什么是时钟(CLK)
时钟是具有周期性的脉冲信号,是单片机的脉搏,最常用的的占空比为50%的方波。
以STM32F407系列为例介绍单片的时钟系统。
时钟树
时钟源
- 晶体时钟源:成本更高,稳定性更好,精确性更高
- RC时钟源:成本更低
H–high L–low S–speed I–internal E–external
时钟简图
时钟原图
A——时钟源
B——锁相环
C——系统时钟源选择器
D——内核系统时钟和使能单元
E——定时器和外设
F——MCO时钟输出单元
STM32CubeMX 时钟树
锁相环PLL
锁相环的作用主要有两个部分:输入时钟净化和倍频。前者是利用锁相环电路的反馈机制实现,后者我们用于使芯片在更高且频率稳定的时钟下工作。
系统时钟
系统时钟 SYSCLK 为整个芯片提供了时序信号。我们已经大致知道 STM32 主控是时序电路链接起来的。对于相同的稳定运行的电路,时钟频率越高,指令的执行速度越快,单位时间能处理的功能越多。STM32 的系统时钟是可配置的,在 STM32F4 系列中,它可以为HSI、PLLCLK、HSE 中的一个,通过 CFGR 的位 SW[1:0]设置。
外设和定时器
1、 如果 APB 预分频器为 1,定时器时钟频率等于 APB 域的频率;
2、 否则,等于 APB 域的频率的两倍(×2)。
此外,AHB 总线时钟直接作为 GPIO(A\B\C\D\E\F\G\H\I)、以太网、DCMI、FSMC、AHB总线、Cortex 内核、存储器和 DMA 的 HCLK 时钟,并作为 Cortex 内核自由运行时钟 FCLK。
配置系统时钟
1. 配置HSE_VALUE
告诉HAL库外部晶振频率,stm32_hal_conf.h
#if !defined (HSE_VALUE)
#define HSE_VALUE 8000000U /*!< Value of the External oscillator in Hz */
#endif
#endif /* HSE_VALUE */
2. 调用SystemInit()函数
在启动文件中调用,system_stm32xxxx.c
void SystemInit(void)
{
/* FPU settings ------------------------------------------------------------*/
#if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2)); /* set CP10 and CP11 Full
Access */
#endif
#if defined (DATA_IN_ExtSRAM) || defined (DATA_IN_ExtSDRAM)
SystemInit_ExtMemCtl();
#endif /* DATA_IN_ExtSRAM || DATA_IN_ExtSDRAM */
/* Configure the Vector Table location -------------------------------------*/
#if defined(USER_VECT_TAB_ADDRESS)
SCB->VTOR = VECT_TAB_BASE_ADDRESS | VECT_TAB_OFFSET; /* Vector Table
Relocation in Internal SRAM */
#endif /* USER_VECT_TAB_ADDRESS */
}
3. 选择时钟源,配置PLL
通过HAL_RCC_OscConfig()函数配置
HAL_StatusTypeDef HAL_RCC_OscConfig(RCC_OscInitTypeDef *RCC_OscInitStruct)
/*结构体*/
typedef struct
{
uint32_t OscillatorType; /* 选择需要配置的振荡器 */
uint32_t HSEState; /* HSE 状态 */
uint32_t LSEState; /* LSE 状态 */
uint32_t HSIState; /* HSI 状态 */
uint32_t HSICalibrationValue; /* HSI 校准微调值,范围0x0~0x1F */
uint32_t LSIState; /* LSI 状态 */
RCC_PLLInitTypeDef PLL; /* PLL 结构体 */
}RCC_OscInitTypeDef;
/*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 */
}RCC_PLLInitTypeDef;
4. 选择系统时钟源,配置总线分频器
通过HAL_RCC_ClockConfig()函数实现
HAL_StatusTypeDef HAL_RCC_ClockConfig(RCC_ClkInitTypeDef *RCC_ClkInitStruct, uint32_t FLatency)
/*结构体*/
typedef struct
{
uint32_t ClockType; /* 要配置的时钟(SYSCLK/HCLK/PCLK1/PCLK2) */
uint32_t SYSCLKSource; /* 系统时钟源 */
uint32_t AHBCLKDivider; /* AHB 时钟预分频系数 */
uint32_t APB1CLKDivider; /* APB1 时钟预分频系数 */
uint32_t APB2CLKDivider; /* APB2 时钟预分频系数 */
}RCC_ClkInitTypeDef;
/*Flash访问等待周期*/
uint32_t FLatency
#define FLASH_LATENCY_0 FLASH_ACR_LATENCY_0WS /* FLASH 0个等待周期 */
#define FLASH_LATENCY_1 FLASH_ACR_LATENCY_1WS /* FLASH 1个等待周期 */
#define FLASH_LATENCY_2 FLASH_ACR_LATENCY_2WS /* FLASH 2个等待周期 */
...
#define FLASH_LATENCY_15 FLASH_ACR_LATENCY_15WS /* FLASH 15个等待周期 */
5. 配置扩展外设时钟
通过HAL_RCCEx_PeriphCLKConfig()函数实现
通过sys_stm32_clock_init()函数实现步骤3、4
/**
* @brief 时钟设置函数
* @param plln: 主 PLL 倍频系数(PLL 倍频), 取值范围: 64~432.
* @param pllm: 主 PLL 和音频 PLL 预分频系数(进 PLL 之前的分频), 取值范围: 2~63.
* @param pllp: 主 PLL 的 p 分频系数(PLL 之后的分频), 分频后作为系统时钟, 取值范围: 2,
4, 6, 8.(仅限这 4 个值)
* @param pllq: 主 PLL 的 q 分频系数(PLL 之后的分频), 取值范围: 2~15.
* @note
*
* Fvco: VCO 频率
* Fsys: 系统时钟频率, 也是主 PLL 的 p 分频输出时钟频率
* Fq: 主 PLL 的 q 分频输出时钟频率
* Fs: 主 PLL 输入时钟频率, 可以是 HSI, HSE 等.
* Fvco = Fs * (plln / pllm);
* Fsys = Fvco / pllp = Fs * (plln / (pllm * pllp));
* Fq = Fvco / pllq = Fs * (plln / (pllm * pllq));
*
* 外部晶振为 8M 的时候, 推荐值: plln = 336, pllm = 8, pllp = 2, pllq =
7.
* 得到:Fvco = 8 * (336 / 8) = 336Mhz
* Fsys = pll_p_ck = 336 / 2 = 168Mhz
* Fq = pll_q_ck = 336 / 7 = 48Mhz
*
* F407 默认需要配置的频率如下:
* CPU 频率(HCLK) = pll_p_ck = 168Mhz
* AHB1/2/3(rcc_hclk1/2/3) = 168Mhz
* APB1(rcc_pclk1) = pll_p_ck / 4 = 42Mhz
* APB1(rcc_pclk2) = pll_p_ck / 2 = 84Mhz
*
* @retval 错误代码: 0, 成功; 1, 错误;
*/
uint8_t sys_stm32_clock_init(uint32_t plln, uint32_t pllm, uint32_t pllp,
uint32_t pllq)
{
HAL_StatusTypeDef ret = HAL_OK;
RCC_OscInitTypeDef rcc_osc_init = {0};
RCC_ClkInitTypeDef rcc_clk_init = {0};
__HAL_RCC_PWR_CLK_ENABLE(); /* 使能 PWR 时钟 */
/* 下面这个设置用来设置调压器输出电压级别,以便在器件以最大频率工作时使性能与功耗实现平衡 */
/* 设置调压器输出电压级别,以便在器件未以最大频率工作 */
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
/* 使能 HSE,并选择 HSE 作为 PLL 时钟源,配置 PLL1,开启 USB 时钟 */
rcc_osc_init.OscillatorType = RCC_OSCILLATORTYPE_HSE;
rcc_osc_init.HSEState = RCC_HSE_ON; /* 打开 HSE */
rcc_osc_init.PLL.PLLState = RCC_PLL_ON; /* 打开 PLL */
rcc_osc_init.PLL.PLLSource = RCC_PLLSOURCE_HSE; /* PLL 时钟源选择 HSE */
rcc_osc_init.PLL.PLLN = plln;
rcc_osc_init.PLL.PLLM = pllm;
rcc_osc_init.PLL.PLLP = pllp;
rcc_osc_init.PLL.PLLQ = pllq;
ret = HAL_RCC_OscConfig(&rcc_osc_init_handle); /* 初始化 RCC */
if(ret != HAL_OK)
{
return 1; /* 时钟初始化失败,可以在这里加入自己的处理 */
}
/* 选中 PLL 作为系统时钟源并且配置 HCLK,PCLK1 和 PCLK2*/
rcc_clk_init.ClockType = ( RCC_CLOCKTYPE_SYSCLK \
| RCC_CLOCKTYPE_HCLK \
| RCC_CLOCKTYPE_PCLK1 \
| RCC_CLOCKTYPE_PCLK2);
/* 设置系统时钟时钟源为 PLL */
rcc_clk_init.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
rcc_clk_init.AHBCLKDivider = RCC_SYSCLK_DIV1; /* AHB 分频系数为 1 */
rcc_clk_init.APB1CLKDivider = RCC_HCLK_DIV4; /* APB1 分频系数为 4 */
rcc_clk_init.APB2CLKDivider = RCC_HCLK_DIV2; /* APB2 分频系数为 2 */
/* 同时设置 FLASH 延时周期为 5WS,也就是 6 个 CPU 周期 */
ret = HAL_RCC_ClockConfig(&rcc_clk_init_handle, FLASH_LATENCY_5);
if(ret != HAL_OK)
{
return 1; /* 时钟初始化失败 */
}
/* STM32F405x/407x/415x/417x Z 版本的器件支持预取功能 */
if (HAL_GetREVID() == 0x1001)
{
__HAL_FLASH_PREFETCH_BUFFER_ENABLE(); /* 使能 flash 预取 */
}
return 0;
}
外设时钟的使能和失能
使用某外设时必须先使能该外设的时钟
__HAL_RCC_GPIOA_CLK_ENABLE(); /* 使能 GPIOA 时钟
__HAL_RCC_GPIOA_CLK_DISABLE(); /* 禁止 GPIOA 时钟 */