MCU: STM32F407VET6
**官方最高稳定频率:**168MHz
工具:STM32CubeMX
本篇仅仅只是提供超频(默认指的是主频)的简单方法,并未涉及STM32超频极限等问题。原理很简单,通过设置锁相环的倍频系数达到不同的频率,从而实现超频。需要注意的是,运行时切换频率不能直接修改,因为此时用的HSE时钟是经由锁相环产生的,直接修改锁相环会出现问题。解决办法是,切换为HSI并关闭锁相环,然后重新配置锁相环,最后再重新切换HSE。
最后切记,超频有风险!!
一、时钟配置
使用STM32CubeMX可以很方便地配置时钟树,配置时钟树时需要先知道开发板所用的外部晶振频率。从下图可知晓,使用的外部晶振为12MHz
然后经由STM32CubeMX自动配置时钟树,从这里可以看到锁相环里能看到M、N、P三个参数,其中N是倍频系数,最高可达432。这里我们目标是配置为168MHz,即官方标称频率
使用STM32CubeMX生成代码后,我们可以在Core/Src目录下找到main.c中的SystemClock_Config函数。从代码中可以轻易看到,PLLM、PLLN、PLLP就是前面看到的M、N、P三个参数。同时由于外部晶振频率为12MHz,自动配置过程中,M和P分频系数分别为6和2恰好可以把12MHz分频为1MHz,使得倍频系数即为主频频率(MHz)。
后面超频时,利用的就是下面代码。
cppvoid SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; /** Configure the main internal regulator output voltage */ __HAL_RCC_PWR_CLK_ENABLE(); __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1); /** Initializes the RCC Oscillators according to the specified parameters * in the RCC_OscInitTypeDef structure. */ RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState = RCC_HSE_ON; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; RCC_OscInitStruct.PLL.PLLM = 6; RCC_OscInitStruct.PLL.PLLN = 168; RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2; RCC_OscInitStruct.PLL.PLLQ = 4; if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { Error_Handler(); } /** Initializes the CPU, AHB and APB buses clocks */ 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_DIV4; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2; if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK) { Error_Handler(); } }
二、超频
根据前面所言,运行时直接修改所用的时钟会发生错误,所以需要切换时钟源后再修改。下面两个函数用于切换时钟源
cppvoid SystemClock_SwitchToHSI(void) { // 将系统时钟切换到 HSI __HAL_RCC_SYSCLK_CONFIG(RCC_SYSCLKSOURCE_HSI); // 等待时钟切换完成 while (__HAL_RCC_GET_SYSCLK_SOURCE() != RCC_SYSCLKSOURCE_STATUS_HSI) {} } void SystemClock_SwitchToPLL(void) { // 将系统时钟切换到 PLL __HAL_RCC_SYSCLK_CONFIG(RCC_SYSCLKSOURCE_PLLCLK); // 等待时钟切换完成 while (__HAL_RCC_GET_SYSCLK_SOURCE() != RCC_SYSCLKSOURCE_STATUS_PLLCLK) {} }
接下来,在系统时钟配置函数的基础上,修改代码。在配置PLL前,先禁用全局中断,防止时钟中断等影响配置过程,然后切换时钟源为HSI并禁用锁相环
cpp// 禁用全局中断 __disable_irq(); // 切换到 HSI SystemClock_SwitchToHSI(); // 禁用 PLL __HAL_RCC_PLL_DISABLE();
然后是配置PLL过程,此时参考系统时钟初始化代码,其中plln是传入的形参变量(锁相环倍频系数)。需要注意的是主频频率提高后,Flash的等待周期要相应延长,原为FLASH_LATENCY_5,这里简单判断了一下,如果大于168MHz,就延长为FLASH_LATENCY_7
cpp// 配置 PLL RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState = RCC_HSE_ON; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; RCC_OscInitStruct.PLL.PLLM = 6; // HSE 分频系数 RCC_OscInitStruct.PLL.PLLN = plln; // PLL 倍频系数 RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2; // PLLP 分频系数 RCC_OscInitStruct.PLL.PLLQ = 4; // PLLQ 分频系数 if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { Error_Handler(); } // 配置系统时钟 RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; 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_DIV4; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2; // 设置 Flash 等待周期 uint32_t flash_latency = (plln <= 168) ? FLASH_LATENCY_5 : FLASH_LATENCY_7; if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, flash_latency) != HAL_OK) { Error_Handler(); }
最后是切换为PLL,并重新启用全局中断
cpp// 切换到 PLL SystemClock_SwitchToPLL(); // 重新启用全局中断 __enable_irq();
完整代码如下,再次提醒,此代码是以外部晶振为12MHz的前提下有STM32CubeMX生成的,使用时需根据自身情况修改
cpp/** * @brief 设置系统时钟频率 * @param plln 锁相环倍频 * @details 基于外部晶振为12MHz的配置 */ void SystemClock_SetFrequency(uint32_t plln) { // 禁用全局中断 __disable_irq(); // 切换到 HSI SystemClock_SwitchToHSI(); // 禁用 PLL __HAL_RCC_PLL_DISABLE(); // 配置 PLL RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState = RCC_HSE_ON; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; RCC_OscInitStruct.PLL.PLLM = 6; // HSE 分频系数 RCC_OscInitStruct.PLL.PLLN = plln; // PLL 倍频系数 RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2; // PLLP 分频系数 RCC_OscInitStruct.PLL.PLLQ = 4; // PLLQ 分频系数 if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { Error_Handler(); } // 配置系统时钟 RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; 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_DIV4; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2; // 设置 Flash 等待周期 uint32_t flash_latency = (plln <= 168) ? FLASH_LATENCY_5 : FLASH_LATENCY_7; if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, flash_latency) != HAL_OK) { Error_Handler(); } // 切换到 PLL SystemClock_SwitchToPLL(); // 重新启用全局中断 __enable_irq(); }
此时可另写两个函数,其一为默认配置,以便能从超频状态返回标准状态;其二为超频状态,建议不要设太高,推荐200~216MHz。提高主频后,也会提高其他时钟线,进而会对外设产生影响。此时需要注意一些如USB、以太网等对频率要求很高的外设,需要重新设置参数。
cpp/** * @brief 超频 * @param 无 * @retval 无 * @details 通过超频可以提升至 200MHz~216MHz,这里默认使用216MHz */ void SystemClock_Overclock() { SystemClock_SetFrequency(216); }
cpp/** * @brief 默认系统时钟初始化 * @details 最高稳定频率为168MHz */ void SystemClock_DefaultConfig() { SystemClock_SetFrequency(168); }
HAL中,对定时器的频率设置是依赖于全局变量SystemCoreClock,由HAL_RCC_ClockConfig来更新,使用HAL_RCC_GetPCLK1Freq等函数可以实现自动纠正频率,下面代码可以作为参考
cpp#include "stm32f4xx_hal.h" /** * @brief 设置定时器频率 * @param htim: 定时器句柄(如 &htim2) * @param target_freq: 目标频率(单位:Hz) * @retval HAL_StatusTypeDef: 成功返回 HAL_OK,失败返回 HAL_ERROR */ HAL_StatusTypeDef Timer_SetFrequency(TIM_HandleTypeDef *htim, uint32_t target_freq) { uint32_t timer_clock_freq; // 定时器时钟源频率 uint32_t psc_value; // 预分频器值 // 获取 APB 总线时钟频率 if (htim->Instance == TIM2 || htim->Instance == TIM3 || htim->Instance == TIM4 || htim->Instance == TIM5 || htim->Instance == TIM9 || htim->Instance == TIM10 || htim->Instance == TIM11) { // APB1 定时器 timer_clock_freq = HAL_RCC_GetPCLK1Freq(); if (RCC->CFGR & RCC_CFGR_PPRE1_2) // 检查 APB1 预分频器 { timer_clock_freq *= 2; // 如果预分频器不为 1,时钟频率乘以 2 } } else { // APB2 定时器 timer_clock_freq = HAL_RCC_GetPCLK2Freq(); if (RCC->CFGR & RCC_CFGR_PPRE2_2) // 检查 APB2 预分频器 { timer_clock_freq *= 2; // 如果预分频器不为 1,时钟频率乘以 2 } } // 检查目标频率是否有效 if (target_freq == 0 || target_freq > timer_clock_freq) { return HAL_ERROR; // 目标频率无效 } // 计算预分频器值 psc_value = (timer_clock_freq / target_freq) - 1; // 检查预分频器值是否超出范围 if (psc_value > 0xFFFF) { return HAL_ERROR; // 预分频器值超出 16 位范围 } // 设置定时器预分频器 __HAL_TIM_SET_PRESCALER(htim, psc_value); return HAL_OK; }
其参考的是STM32CubeMX生成的"系统"定时器(这里使用的是TIM7)的初始化代码
cpp/*用于配置供HAL使用基础时钟,频率为1KHz*/ HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority) { RCC_ClkInitTypeDef clkconfig; uint32_t uwTimclock, uwAPB1Prescaler = 0U; uint32_t uwPrescalerValue = 0U; uint32_t pFLatency; HAL_StatusTypeDef status; /* Enable TIM7 clock */ __HAL_RCC_TIM7_CLK_ENABLE(); /* Get clock configuration */ HAL_RCC_GetClockConfig(&clkconfig, &pFLatency); /* Get APB1 prescaler */ uwAPB1Prescaler = clkconfig.APB1CLKDivider; /* Compute TIM7 clock */ if (uwAPB1Prescaler == RCC_HCLK_DIV1) { uwTimclock = HAL_RCC_GetPCLK1Freq(); } else { uwTimclock = 2UL * HAL_RCC_GetPCLK1Freq(); } /* Compute the prescaler value to have TIM7 counter clock equal to 1MHz */ uwPrescalerValue = (uint32_t) ((uwTimclock / 1000000U) - 1U); /* Initialize TIM7 */ htim7.Instance = TIM7; /* Initialize TIMx peripheral as follow: + Period = [(TIM7CLK/1000) - 1]. to have a (1/1000) s time base. + Prescaler = (uwTimclock/1000000 - 1) to have a 1MHz counter clock. + ClockDivision = 0 + Counter direction = Up */ htim7.Init.Period = (1000000U / 1000U) - 1U; htim7.Init.Prescaler = uwPrescalerValue; htim7.Init.ClockDivision = 0; htim7.Init.CounterMode = TIM_COUNTERMODE_UP; htim7.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; // 考虑到LVGL初始化会死循环 status = HAL_TIM_Base_Init(&htim7); if (status == HAL_OK) { /* Start the TIM time Base generation in interrupt mode */ status = HAL_TIM_Base_Start_IT(&htim7); if (status == HAL_OK) { /* Enable the TIM7 global Interrupt */ HAL_NVIC_EnableIRQ(TIM7_IRQn); /* Configure the SysTick IRQ priority */ if (TickPriority < (1UL << __NVIC_PRIO_BITS)) { /* Configure the TIM IRQ priority */ HAL_NVIC_SetPriority(TIM7_IRQn, TickPriority, 0U); uwTickPrio = TickPriority; } else { status = HAL_ERROR; } } } /* Return function status */ return status; }