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

时钟树的作用:
指导正确配置系统和外设时钟
指示外设运行时钟频率限制
外设时钟源选择
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