_STM32关于CPU超频的参考_HAL

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)。

后面超频时,利用的就是下面代码。

cpp 复制代码
void 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();
  }
}

二、超频

根据前面所言,运行时直接修改所用的时钟会发生错误,所以需要切换时钟源后再修改。下面两个函数用于切换时钟源

cpp 复制代码
void 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;
}
相关推荐
国科安芯1 小时前
航天器多路并联大功率电源系统设计与ASP4644均流特性分析
单片机·嵌入式硬件·fpga开发·安全性测试
茯苓gao3 小时前
嵌入式开发笔记:CANopen相关移位运算与通信协议术语详解
网络·嵌入式硬件·学习·信息与通信
梁朝辉6 小时前
STM32H750VBT6中ADCINP与INN什么区别
stm32·单片机·嵌入式硬件
省四收割者7 小时前
一文详解信号完整性(1)
python·嵌入式硬件·数学建模·信息与通信·信号处理·智能硬件
崇山峻岭之间8 小时前
单片机无感FOC驱动之ADC
单片机·嵌入式硬件
Ligocious8 小时前
1.点亮一颗小小的LED
单片机·嵌入式硬件
KaifuZeng12 小时前
通信与接口协议面试八、CAN通信
单片机·嵌入式硬件·面试·通信与接口协议
智源单片机设计13 小时前
基于单片机的直流电机双闭环调速系统设计
单片机·嵌入式硬件
kebidaixu14 小时前
两轮车换电 BMS 的模拟前端中颖 SH367306 AFE 芯片详解
stm32
kebidaixu14 小时前
两轮车换电 BMS 的模拟前端中颖 SH367306 AFE 芯片调试实录:四个关键问题与解决方案
stm32