You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

STM32L486运行时切换时钟频率如何避免影响外设?

运行时切换STM32L486系统时钟:常见性与外设问题解决方案

嘿,这个需求太常见了——但凡做电池供电的低功耗设备,比如便携传感器、智能手环这类,几乎都会用到这种“平时低功耗摸鱼,需要干活时拉满性能”的操作。STM32L4系列本身就为这种场景做了专门的时钟设计,所以完全不用担心可行性,重点是搞定外设失效的问题。

一、先给你吃颗定心丸:运行时切换系统时钟是常规操作

STM32的动态时钟切换是官方支持的核心特性之一,很多官方低功耗Demo里都有现成的实现。本质上就是利用时钟树的灵活性,在不同场景下切换系统时钟源/频率,平衡功耗和性能,属于低功耗设备的标准操作流程。

二、外设失效的核心原因&解决思路

你说的USART失效,本质是外设的工作参数是基于当前外设时钟计算的——比如USART的波特率依赖于APB总线时钟,当系统时钟切换后,APB总线的分频比或者时钟源变了,外设的实际工作时钟就变了,原来的配置(比如波特率寄存器BRR的值)自然就不匹配了。

解决思路也很直接:在时钟切换前后,对依赖系统时钟的外设做“先停后启+重新配置”的操作

三、具体操作步骤(以HAL库为例)

1. 时钟切换前:安全关停外设,保存配置

  • 对于USART这类有数据传输的外设,先确保当前传输完成:比如调用HAL_UART_GetState()检查是否处于HAL_UART_STATE_READY状态,或者等待DMA传输完成(如果用了DMA的话)。
  • 禁用外设时钟和相关中断:比如__HAL_RCC_USART1_CLK_DISABLE()HAL_NVIC_DisableIRQ(USART1_IRQn),避免切换时钟时出现异常中断。
  • 保存外设的关键配置:把USART的波特率、数据位、停止位这些参数存在全局变量里,方便后续恢复:
    typedef struct {
      uint32_t baudrate;
      uint32_t word_length;
      uint32_t stop_bits;
      uint32_t parity;
      uint32_t mode;
      uint32_t hw_flow_ctrl;
    } UART_SavedConfig;
    UART_SavedConfig uart1_config;
    
    // 保存当前配置
    uart1_config.baudrate = huart1.Init.BaudRate;
    uart1_config.word_length = huart1.Init.WordLength;
    // ... 其他参数同理
    

2. 执行系统时钟切换

STM32L4的时钟切换要遵循“先切到临时时钟源,再配置目标时钟,最后切换系统时钟”的流程,避免切换过程中系统崩溃。

从80MHz PLL切换到1MHz MSI:

void SwitchToLowPowerClock(void) {
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  // 1. 先切换到HSI临时时钟,保证切换过程稳定
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.HSIDiv = RCC_HSI_DIV1;
  RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) {
    Error_Handler();
  }

  // 2. 配置系统时钟为HSI
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_SYSCLK;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK) {
    Error_Handler();
  }

  // 3. 关闭PLL
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_PLL;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_OFF;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) {
    Error_Handler();
  }

  // 4. 配置MSI到1MHz
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_MSI;
  RCC_OscInitStruct.MSIState = RCC_MSI_ON;
  RCC_OscInitStruct.MSIClockRange = RCC_MSIRANGE_0; // 对应1MHz
  RCC_OscInitStruct.MSICalibrationValue = RCC_MSICALIBRATION_DEFAULT;
  RCC_OscInitStruct.MSIDiv = RCC_MSI_DIV1;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) {
    Error_Handler();
  }

  // 5. 切换系统时钟到MSI
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_MSI;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK) {
    Error_Handler();
  }
}

从1MHz MSI切回80MHz PLL:

void SwitchToFullSpeedClock(void) {
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  // 1. 先切换到HSI临时时钟
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.HSIDiv = RCC_HSI_DIV1;
  RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) {
    Error_Handler();
  }

  // 2. 配置系统时钟为HSI
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_SYSCLK;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK) {
    Error_Handler();
  }

  // 3. 配置PLL到80MHz(MSI 4MHz为源,PLLM=1, PLLN=20, PLLR=2)
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_PLL;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_MSI;
  RCC_OscInitStruct.PLL.PLLM = 1;
  RCC_OscInitStruct.PLL.PLLN = 20;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV7;
  RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;
  RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) {
    Error_Handler();
  }

  // 4. 切换系统时钟到PLL,配置FLASH延迟(80MHz需要FLASH_LATENCY_4)
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4) != HAL_OK) {
    Error_Handler();
  }
}

3. 时钟切换后:重新初始化外设

切换完成后,把之前保存的配置重新加载给外设,恢复正常工作:

void RestoreUARTConfig(UART_HandleTypeDef *huart, UART_SavedConfig *config) {
  huart->Init.BaudRate = config->baudrate;
  huart->Init.WordLength = config->word_length;
  huart->Init.StopBits = config->stop_bits;
  huart->Init.Parity = config->parity;
  huart->Init.Mode = config->mode;
  huart->Init.HwFlowCtl = config->hw_flow_ctrl;
  huart->Init.OverSampling = UART_OVERSAMPLING_16;

  // 重新初始化USART
  if (HAL_UART_Init(huart) != HAL_OK) {
    Error_Handler();
  }

  // 重新开启外设时钟和中断
  __HAL_RCC_USART1_CLK_ENABLE();
  HAL_NVIC_EnableIRQ(USART1_IRQn);
}

四、一些额外的注意事项

  • FLASH延迟配置:不同的系统时钟频率需要对应的FLASH读延迟,比如80MHz需要FLASH_LATENCY_4,1MHz只需要FLASH_LATENCY_0,切换时一定要同步修改,否则会导致系统崩溃。
  • 定时器等外设的处理:如果用了定时器,切换时钟后也要重新计算自动重装载值(ARR)和预分频器(PSC),确保定时周期正确。
  • 低功耗模式的配合:切换到低频率时钟后,可以进入STOP/STOP2模式进一步降低功耗,唤醒后再切回全频率,这时候要注意唤醒后的时钟初始化顺序。
  • 避免频繁切换:虽然支持动态切换,但过于频繁的时钟切换会增加功耗开销,尽量根据业务场景规划切换时机(比如定时唤醒处理数据,处理完立刻切回低功耗)。

内容的提问来源于stack exchange,提问作者Guillaume Petitjean

火山引擎 最新活动