没志青年
发布于 2025-07-27 / 25 阅读
0

32单片机 - 时钟系统

在芯片复位之后,所有外设的时钟都是默认关闭,对于用到的外设需要手动打开时钟,这样有利于降低功耗。

时钟树

时钟树的作用:

  • 指导正确配置系统和外设时钟

  • 指示外设运行时钟频率限制

外设时钟源选择

G4 需要配置,F4不清楚,F1不用,时钟源是固定的。

结构体:

typedef struct
{
  uint32_t PeriphClockSelection;   /*!< The Extended Clock to be configured.
                                        This parameter can be a value of @ref RCCEx_Periph_Clock_Selection */

  uint32_t Usart1ClockSelection;   /*!< Specifies USART1 clock source.
                                        This parameter can be a value of @ref RCCEx_USART1_Clock_Source */

  uint32_t Usart2ClockSelection;   /*!< Specifies USART2 clock source.
                                        This parameter can be a value of @ref RCCEx_USART2_Clock_Source */

  uint32_t Usart3ClockSelection;   /*!< Specifies USART3 clock source.
                                        This parameter can be a value of @ref RCCEx_USART3_Clock_Source */

#if defined(UART4)
  uint32_t Uart4ClockSelection;    /*!< Specifies UART4 clock source.
                                        This parameter can be a value of @ref RCCEx_UART4_Clock_Source */
#endif /* UART4 */

#if defined(UART5)
  uint32_t Uart5ClockSelection;    /*!< Specifies UART5 clock source.
                                        This parameter can be a value of @ref RCCEx_UART5_Clock_Source */

#endif /* UART5 */

  uint32_t Lpuart1ClockSelection;  /*!< Specifies LPUART1 clock source.
                                        This parameter can be a value of @ref RCCEx_LPUART1_Clock_Source */

  uint32_t I2c1ClockSelection;     /*!< Specifies I2C1 clock source.
                                        This parameter can be a value of @ref RCCEx_I2C1_Clock_Source */

  uint32_t I2c2ClockSelection;     /*!< Specifies I2C2 clock source.
                                        This parameter can be a value of @ref RCCEx_I2C2_Clock_Source */

  uint32_t I2c3ClockSelection;     /*!< Specifies I2C3 clock source.
                                        This parameter can be a value of @ref RCCEx_I2C3_Clock_Source */

#if defined(I2C4)

  uint32_t I2c4ClockSelection;     /*!< Specifies I2C4 clock source.
                                        This parameter can be a value of @ref RCCEx_I2C4_Clock_Source */
#endif /* I2C4 */

  uint32_t Lptim1ClockSelection;   /*!< Specifies LPTIM1 clock source.
                                        This parameter can be a value of @ref RCCEx_LPTIM1_Clock_Source */

  uint32_t Sai1ClockSelection;     /*!< Specifies SAI1 clock source.
                                        This parameter can be a value of @ref RCCEx_SAI1_Clock_Source */

  uint32_t I2sClockSelection;     /*!< Specifies I2S clock source.
                                        This parameter can be a value of @ref RCCEx_I2S_Clock_Source */
#if defined(FDCAN1)

  uint32_t FdcanClockSelection;     /*!< Specifies FDCAN clock source.
                                        This parameter can be a value of @ref RCCEx_FDCAN_Clock_Source */
#endif /* FDCAN1 */
#if defined(USB)

  uint32_t UsbClockSelection;      /*!< Specifies USB clock source (warning: same source for RNG).
                                        This parameter can be a value of @ref RCCEx_USB_Clock_Source */
#endif /* USB */

  uint32_t RngClockSelection;      /*!< Specifies RNG clock source (warning: same source for USB).
                                        This parameter can be a value of @ref RCCEx_RNG_Clock_Source */

  uint32_t Adc12ClockSelection;    /*!< Specifies ADC12 interface clock source.
                                        This parameter can be a value of @ref RCCEx_ADC12_Clock_Source */

#if defined(ADC345_COMMON)
  uint32_t Adc345ClockSelection;   /*!< Specifies ADC345 interface clock source.
                                        This parameter can be a value of @ref RCCEx_ADC345_Clock_Source */
#endif /* ADC345_COMMON */

#if defined(QUADSPI)
  uint32_t QspiClockSelection;     /*!< Specifies QuadSPI clock source.
                                        This parameter can be a value of @ref RCCEx_QSPI_Clock_Source */
#endif

  uint32_t RTCClockSelection;      /*!< Specifies RTC clock source.
                                        This parameter can be a value of @ref RCC_RTC_Clock_Source */
} RCC_PeriphCLKInitTypeDef;

PeriphClockSelection 用于指定要设置的外设,然后配置对应外设的时钟源就行了。

要结合时钟树,才能知道外设能设置什么时钟源。

比如:

RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};
PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_USART3;
PeriphClkInit.Usart3ClockSelection = RCC_USART3CLKSOURCE_PCLK1;
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
{
  while (1);
}

__HAL_RCC_USART3_CLK_ENABLE();  // 应该在设置时钟源后再使能时钟

对于共享的外设,使用变量标记,可防止重复设置。

(重复设置也没问题,只是会浪费少量的时间。)

// 全局变量
extern uint8_t if_already_init_adc345_clock;


// 在函数内
if (if_already_init_adc345_clock != 1)
  {
    RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};
    PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_ADC345;
    PeriphClkInit.Adc345ClockSelection = RCC_ADC345CLKSOURCE_PLL;
    if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
    {
      while (1);
    }
    __HAL_RCC_ADC345_CLK_ENABLE();
    if_already_init_adc345_clock = 1;
  }

开启外设时钟

在标准库中,每个总线上都有打开时钟的函数:

void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState);

比如,打开 GPIOA 的时钟:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

RCC_APB2Periph_GPIOA 在

在 HAL 中,使用宏打开时钟:

__HAL_RCC_TIM2_CLK_ENABLE();

这种宏在 _hal_rcc.h 文件中,比如:

#define __HAL_RCC_TIM2_CLK_ENABLE()            do { \
                                                 __IO uint32_t tmpreg; \
                                                 SET_BIT(RCC->APB1ENR1, RCC_APB1ENR1_TIM2EN); \
                                                 /* Delay after an RCC peripheral clock enabling */ \
                                                 tmpreg = READ_BIT(RCC->APB1ENR1, RCC_APB1ENR1_TIM2EN); \
                                                 UNUSED(tmpreg); \
                                               } while(0)

系统时钟设置

F0 标准库

/*
 * 使用外部晶振
 */
void Rcc_Init(void)
{
  RCC_ClocksTypeDef clock_init_struct;
  ErrorStatus       ret;

  RCC_ClockSecuritySystemCmd(ENABLE);
                  
  RCC_HSEConfig(RCC_HSE_ON);
  ret = RCC_WaitForHSEStartUp();  

  if(ret == SUCCESS)  
  {
    FLASH_PrefetchBufferCmd(ENABLE);          
    RCC_HCLKConfig(RCC_SYSCLK_Div1);                                    
    RCC_PCLKConfig(RCC_HCLK_Div1);
    RCC_PLLConfig(RCC_PLLSource_HSE,RCC_PLLMul_6);   
    RCC_PLLCmd(ENABLE);
                    
    while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET)      
    {
    }
    RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
    RCC_GetClocksFreq(&clock_init_struct);     
    while(RCC_GetSYSCLKSource() != 0x08)                  
    {
    }
  } 
}

F1 标准库

F4 标准库

F1 HAL 库

F4 HAL 库

标准库修改主频:

时钟检查

获取系统时钟频率

// 标准库
SystemCoreClockUpdate();   // 在修改系统时钟频率后务必先更新一下值
printf("System Clock (HCLK) = %lu Hz\r\n", SystemCoreClock);

// HAL
printf("SystemCoreClock: %d Hz\n", SystemCoreClock);
printf("SYSCLK  = %lu Hz\r\n", HAL_RCC_GetSysClockFreq());
printf("HCLK    = %lu Hz\r\n", HAL_RCC_GetHCLKFreq());
printf("PCLK1   = %lu Hz\r\n", HAL_RCC_GetPCLK1Freq());
printf("PCLK2   = %lu Hz\r\n", HAL_RCC_GetPCLK2Freq());

检查某个外设时钟是否开启

// 标准库,使用位判断
if (RCC->APB2ENR & RCC_APB2ENR_IOPAEN)
{
    // GPIOA 时钟已经开启
}

// HAL,使用函数判断
if (__HAL_RCC_USART2_IS_CLK_ENABLED())
{
    // USART2 时钟已开启
}

Systick 嘀嗒定时器

F1 和 F4 都是默认 8 分频的?,查看当前的时钟源:

if (SysTick->CTRL & SysTick_CTRL_CLKSOURCE_Msk) {
    printf("SysTick时钟源 = HCLK(无分频)\n");
} else {
    printf("SysTick时钟源 = HCLK/8\n");
}

设置时钟源:

// 标准库手动操作寄存器
SysTick->CTRL |= SysTick_CTRL_CLKSOURCE_Msk;     // HCLK
SysTick->CTRL &= ~SysTick_CTRL_CLKSOURCE_Msk;    // HCLK / 8

// 标准库
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK);            // HCLK
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);       // HCLK / 8

// HAL
HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);        // HCLK
HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK_DIV8);   // HCLK / 8

适合没有 RTOS 的裸机程序。

虽然网上也有在 RTOS 下使用的延时函数,但是我从来没有成功过。

F0 系列标准库延时函数:

#ifndef _SYSTICK_DELAY_H
#define _SYSTICK_DELAY_H

#include "stm32f0xx.h"

void delay_init(void);
void delay_ms(unsigned short nms);
void delay_us(unsigned int nus);

#endif
#include "delay.h"

#define SysClk 48      // 主频

void delay_init(void)
{
    SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);   
}	

void delay_ms(unsigned short nms)
{	
	unsigned int temp;	
    
	SysTick->LOAD=(unsigned int)(nms*1000*SysClk/8);
    SysTick->VAL=0x00;  
    SysTick->CTRL=0x01;  
  
    do
	{
		temp=SysTick->CTRL;
	}
	while(!((temp)>>4)&0x00000001);
	SysTick->CTRL=0x00;
	SysTick->VAL =0X00; 
	     	    
} 

void delay_us(unsigned int nus)
{		
	unsigned int temp;	
    
    SysTick->LOAD=(unsigned int)(nus*SysClk/8);
    SysTick->VAL=0x00;  
    SysTick->CTRL=0x01;  
  
    do
  	{
		temp=SysTick->CTRL;
	}
	while(!((temp)>>4)&0x00000001);
	SysTick->CTRL=0x00;
	SysTick->VAL =0X00;      
}

F1 系列HAL库:

DWT 定时器

DWT 时钟为 CPU 时钟 HCLK,因此低功耗模式下会停止。

适合有 RTOS 的程序,因为 Systick 被占用。

F1