STM32——STM32F103时钟解析(正点原子资料+HAL库代码分析)

前言

上次写系统时钟解析的时候说出一篇103的时钟解析,我就整理HAL库开发的正点的资料,给小白梳理,我也是小白,不做权威使用。

时钟树详解

在这里插入图片描述
在 STM32 中,有五个时钟源,为 HSI、HSE、LSI、LSE、PLL。从时钟频率来分可以分为高速时钟源和低速时钟源,在这 5 个中 HIS,HSE 以及 PLL 是高速时钟,LSI 和 LSE是低速时钟。从来源可分为外部时钟源和内部时钟源,外部时钟源就是从外部通过接晶振的方式获取时钟源,其中 HSE 和 LSE 是外部时钟源,其他的是内部时钟源。下面我们看看 STM32 的5 个时钟源,我们讲解顺序是按图中红圈标示的顺序:

①、HSI 是高速内部时钟,RC 振荡器,频率为 8MHz。
②、HSE 是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为 4MHz~16MHz。
③、LSI 是低速内部时钟,RC 振荡器,频率为 40kHz。独立看门狗的时钟源只能是 LSI,同时 LSI 还可以作为 RTC 的时钟源。
④、LSE 是低速外部时钟,接频率为 32.768kHz 的石英晶体。这个主要是 RTC 的时钟源。
⑤、PLL 为锁相环倍频输出,其时钟输入源可选择为 HSI/2、HSE 或者 HSE/2。倍频可选择为 2~16 倍,但是其输出频率最大不得超过 72MHz。

上面我们简要概括了 STM32 的时钟源,那么这 5 个时钟源是怎么给各个外设以及系统提供时钟的呢?这里我们将一一讲解。我们还是从图的下方讲解起吧,因为下方比较简单。图中我们用 A~E 标示我们要讲解的地方。

A. MCO 是 STM32 的一个时钟输出 IO(PA8),它可以选择一个时钟信号输出,可以选择为 PLL 输出的 2 分频、HSI、HSE、或者系统时钟。这个时钟可以用来给外部其他系统提供时钟源。
B. 这里是 RTC 时钟源,从图上可以看出,RTC 的时钟源可以选择 LSI,LSE,以及HSE 的 128 分频。
C. 从图中可以看出 C 处 USB 的时钟是来自 PLL 时钟源。STM32 中有一个全速功能的 USB 模块,其串行接口引擎需要一个频率为 48MHz 的时钟源。该时钟源只能从 PLL 输出端获取,可以选择为 1.5 分频或者 1 分频,也就是,当需要使用 USB模块时,PLL 必须使能,并且时钟频率配置为 48MHz 或 72MHz。
D.处就是 STM32 的系统时钟 SYSCLK,它是供 STM32 中绝大部分部件工作的时钟源。系统时钟可选择为 PLL 输出、HSI 或者 HSE。系统时钟最大频率为 72MHz,当然你也可以超频,不过一般情况为了系统稳定性是没有必要冒风险去超频的。
E. 这里的 E 处是指其他所有外设了。从时钟图上可以看出,其他所有外设的时钟最终来源都是 SYSCLK。SYSCLK 通过 AHB 分频器分频后送给各模块使用。这些模块包括:
①、AHB 总线、内核、内存和 DMA 使用的 HCLK 时钟。
②、通过 8 分频后送给 Cortex 的系统定时器时钟,也就是 systick 了。
③、直接送给 Cortex 的空闲运行时钟 FCLK。
④、送给 APB1 分频器。APB1 分频器输出一路供 APB1 外设使用(PCLK1,最大频率 36MHz),另一路送给定时器(Timer)2、3、4 倍频器使用。
⑤、送给 APB2 分频器。APB2 分频器分频输出一路供 APB2 外设使用(PCLK2,最大频率 72MHz),另一路送给定时器(Timer)1 倍频器使用。

其中需要理解的是 APB1 和 APB2 的区别,APB1 上面连接的是低速外设,包括电源接口、备份接口、CAN、USB、I2C1、I2C2、UART2、UART3 等等,APB2 上面连接的是高速外设包 括 UART1、SPI1、Timer1、ADC1、ADC2、所有普通 IO 口(PA~PE)、第二功能 IO 口等。居宁老师的《稀里糊涂玩 STM32》资料里面教大家的记忆方法是 2>1, APB2 下面所挂的外设的时钟要比 APB1 的高。

在以上的时钟输出中,有很多是带使能控制的,例如 AHB 总线时钟、内核时钟、各种 APB1 外设、APB2 外设等等。当需要使用某模块时,记得一定要先使能对应的时钟。

系统时钟配置

在系统启动之后,程序会先执行 HAL 库定义的 SystemInit 函数,进行系统一些初始化配置。 SystemInit 程序中主要做了以下三个方面的工作:
1) 复位 RCC 时钟配置为默认复位值(默认开始了 HIS)
2) 外部存储器配置
3) 中断向量表地址配置

HAL 库的 SystemInit 函数并没有像标准库的 SystemInit 函数一样进行时钟的初始化配置。HAL 库的 SystemInit 函数除了打开 HSI 之外,没有任何时钟相关配置,所以使用 HAL 库我们必须编 写自己的时钟配置函数。首先我们打开工程模板看看我们在工程 SYSTEM 分组下面定义的 sys.c中包含,也可以使用Cubemx自动生成配置代码,具体可以参考STM32Cubemx的总结手册。

//时钟系统配置函数 
//PLL:选择的倍频数,RCC_PLL_MUL2~RCC_PLL_MUL16 
//返回值:0,成功;1,失败 
void Stm32_Clock_Init(u32 PLL) 
{ 
HAL_StatusTypeDef ret = HAL_OK; 
RCC_OscInitTypeDef RCC_OscInitStructure; 
RCC_ClkInitTypeDef RCC_ClkInitStructure; 
RCC_OscInitStructure.OscillatorType=RCC_OSCILLATORTYPE_HSE; //时钟源为 HSE 
RCC_OscInitStructure.HSEState=RCC_HSE_ON; //打开 HSE 
RCC_OscInitStructure.HSEPredivValue=RCC_HSE_PREDIV_DIV1; //HSE 预分频 
RCC_OscInitStructure.PLL.PLLState=RCC_PLL_ON;
//打开 PLL 
RCC_OscInitStructure.PLL.PLLSource=RCC_PLLSOURCE_HSE; 
//PLL 时钟源选择 HSE 
RCC_OscInitStructure.PLL.PLLMUL=PLL; 
//主 PLL 倍频因子 
ret=HAL_RCC_OscConfig(&RCC_OscInitStructure);//初始化 
if(ret!=HAL_OK) while(1); 
//选中 PLL 作为系统时钟源并且配置 HCLK,PCLK1 和 PCLK2 
RCC_ClkInitStructure.ClockType=(RCC_CLOCKTYPE_SYSCLK| 
RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_PCLK1| 
RCC_CLOCKTYPE_PCLK2); 
//设置系统时钟时钟源为 PLL 
RCC_ClkInitStructure.SYSCLKSource=RCC_SYSCLKSOURCE_PLLCLK; 
RCC_ClkInitStructure.AHBCLKDivider=RCC_SYSCLK_DIV1; 
//AHB 分频系数为 1 
RCC_ClkInitStructure.APB1CLKDivider=RCC_HCLK_DIV2; 
//APB1 分频系数为 2 
RCC_ClkInitStructure.APB2CLKDivider=RCC_HCLK_DIV1; 
//APB2 分频系数为 1 
ret=HAL_RCC_ClockConfig(&RCC_ClkInitStructure,FLASH_LATENCY_2); 
//同时设置 FLASH 延时周期为 2WS,也就是 3 个 CPU 周期。 
if(ret!=HAL_OK) while(1); 
}

在sys.c文件中,有一个Stm32_Clock_Init()函数,这个函数里面是时钟的配置。除了配置 PLL 相关参数确定 SYSCLK 值之外,还配置了 AHB,APB1 和 APB2 的分频系数,也就是确定了HCLK,PCLK1 和 PCLK2 的时钟值。

typedef struct 
{ 
uint32_t OscillatorType; //需要选择配置的振荡器类型 
uint32_t HSEState; //HSE 状态 
uint32_t HSEPredivValue; // Prediv1 值 
uint32_t LSEState; //LSE 状态 
uint32_t HSIState; //HIS 状态
uint32_t HSICalibrationValue; //HIS 校准值 
uint32_t LSIState; 
//LSI 状态 
RCC_PLLInitTypeDef PLL; //PLL 配置 
}RCC_OscInitTypeDef; 

typedef struct 
{ 
uint32_t PLLState; //PLL 状态 
uint32_t PLLSource; //PLL 时钟源 
uint32_t PLLMUL; //PLL VCO 输入时钟的乘法因子 
}RCC_PLLInitTypeDef;

在这个函数中初始化一个结构体,关于RCC_OscInitTypeDef外部晶振的结构体,里面包含选择配置振荡器的类型。在这个结构体中有关于PLL的配置的成员变量,RCC_PLLInitTypeDef 结构体,里面又包含了PLL状态、 PLL时钟源、PLL输入时钟的乘法因子,这三个成员变量主要是设置PLL的分频倍频参数。

RCC_OscInitStructure.OscillatorType=RCC_OSCILLATORTYPE_HSE; //时钟源为 HSE 
RCC_OscInitStructure.HSEState=RCC_HSE_ON; //打开 HSE 
RCC_OscInitStructure.HSEPredivValue=RCC_HSE_PREDIV_DIV1; //HSE 预分频 
RCC_OscInitStructure.PLL.PLLState=RCC_PLL_ON;
//打开 PLL 
RCC_OscInitStructure.PLL.PLLSource=RCC_PLLSOURCE_HSE; 
//PLL 时钟源选择 HSE 
RCC_OscInitStructure.PLL.PLLMUL=PLL; 
//主 PLL 倍频因子 
ret=HAL_RCC_OscConfig(&RCC_OscInitStructure);//初始化 

在时钟配置函数中开启了 HSE 时钟源,同时选择 PLL 时钟源为 HSE,然后把Stm32_Clock_Init 的唯一的入口参数直接设置作为 PLL 的倍频因子。设置好 PLL 时钟源参数之后,也就是确定了 PLL 的时钟频率,接下来我们就需要设置系统时钟,以及 AHB,APB1 和 APB2 相关参数。

HAL_StatusTypeDef HAL_RCC_ClockConfig(RCC_ClkInitTypeDef *RCC_ClkInitStruct, 
uint32_t FLatency); 

该函数有两个入口参数,第一个入口参数 RCC_ClkInitStruct 是结构体RCC_ClkInitTypeDef 指针类型,用来设置 SYSCLK 时钟源以及 AHB,APB1 和 APB2 的分频系数。第二个入口参数 FLatency 用来设置 FLASH 延迟。

//选中 PLL 作为系统时钟源并且配置 HCLK,PCLK1 和 PCLK2 
RCC_ClkInitStructure.ClockType=(RCC_CLOCKTYPE_SYSCLK| 
RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_PCLK1| 
RCC_CLOCKTYPE_PCLK2);
//设置系统时钟时钟源为 PLL 
RCC_ClkInitStructure.SYSCLKSource=RCC_SYSCLKSOURCE_PLLCLK; 
RCC_ClkInitStructure.AHBCLKDivider=RCC_SYSCLK_DIV1; //AHB 分频系数为 1 
RCC_ClkInitStructure.APB1CLKDivider=RCC_HCLK_DIV2; //APB1 分频系数为 2 
RCC_ClkInitStructure.APB2CLKDivider=RCC_HCLK_DIV1; //APB2 分频系数为 1 
ret=HAL_RCC_ClockConfig(&RCC_ClkInitStructure,FLASH_LATENCY_2); 
//同时设置 FLASH 延时周期为 2WS,也就是 3 个 CPU 周期。 

第一个参数 ClockType 配置说明我们要配置的是 SYSCLK,HCLK,PCLK1 和 PCLK2 四个时钟。
第二个参数 SYSCLKSource 配置选择系统时钟源为 PLL。
第三个参数 AHBCLKDivider 配置 AHB 分频系数为 1。
第四个参数 APB1CLKDivider 配置 APB1 分频系数为 2。
第五个参数 APB2CLKDivider 配置 APB2 分频系数为 1。
根据我们在主函数中调用 Stm32_Clock_Init(RCC_PLL_MUL9)时候设置的入口参数值,我 们可以计算出,PLL 时钟为 PLLCLK=HSE9 =8MHz9=72MHz,同时我们选择系统时钟源为 PLL , 所 以 系 统 时 钟 SYSCLK=72MHz 。 AHB 分频系数为 1 ,故其频率为 HCLK=SYSCLK/1=72MHz。APB1 分频系数为 2,故其频率为 PCLK1=HCLK/2=36MHz。APB2 分频系数为 1,故其频率为 PCLK2=HCLK/1=72/1=72MHz。最后我们总结一下通过调用函数 Stm32_Clock_Init(RCC_PLL_MUL9)之后的关键时钟频率值:
SYSCLK(系统时钟) =72MHz
PLL 主时钟 =72MHz
AHB 总线时钟(HCLK=SYSCLK/1) =72MHz
APB1 总线时钟(PCLK1=HCLK/2) =36MHz
APB2 总线时钟(PCLK2=HCLK/1) =72MHz

系统时钟使能配置

在 STM32F1 的 HAL 库中,外设时钟使能操作都是在 RCC 相关固件库文件头文件 stm32f1xx_hal_rcc.h 定义的。大家打开stm32f1xx_hal_rcc.h 头文件可以看到文件中除了少数几 个函数声明之外大部分都是宏定义标识符。外设时钟使能在 HAL 库中都是通过宏定义标识符来实现的。我们的宏是__HAL_RCC_GPIOA_CLK_ENABLE(),宏定义里面有SET_BIT()函数,SET_BIT(RCC->APB2ENR, RCC_APB2ENR_IOPAEN); 意思很清楚哈,里面是寄存器配置APB2ENR,后面是配置的位,是一个宏

#define RCC_APB2ENR_IOPAEN ((uint32_t)0x00000001) 

作用是设置寄存器 RCC->APB2ENR 寄存器的位 2 为 1。我们可以从 STM32F1 的中文参考手册中搜索 APB2ENR 寄存器定义,位 2 的作用是用来使用 GPIOA 时钟。而配置描述如下:
IOPAEN:IO 端口 A 时钟使能
由软件置 1 和清零
0:禁止 IO 端口 A 时钟
1:使能 IO 端口 A 时钟
那么我们对各个时钟的使能就可以用以下方式表述:

__HAL_RCC_GPIOA_CLK_ENABLE();//使能 GPIOA 时钟 
__HAL_RCC_DMA1_CLK_ENABLE();//使能 DMA1 时钟 
__HAL_RCC_USART2_CLK_ENABLE();//使能串口 2 时钟 
__HAL_RCC_TIM1_CLK_ENABLE();//使能 TIM1 时钟

我们使用外设的时候需要使能外设时钟,如果我们不需要使用某个外设,同样我们可以禁止某个外设时钟。禁止外设时钟使用方法和使能外设时钟非常类似,同样是头文件中定义的宏定义标识符。我们同样以 GPIOA 为例,宏定义标识符为:

#define __HAL_RCC_GPIOA_CLK_DISABLE() \ 
(RCC->APB2ENR &= ~(RCC_APB2ENR_IOPAEN)) 

禁止外设时钟的设置为:

__HAL_RCC_DMA1_CLK_DISABLE();//禁止 DMA1 时钟 
__HAL_RCC_USART2_CLK_DISABLE();//禁止串口 2 时钟 
__HAL_RCC_TIM1_CLK_DISABLE();//禁止 TIM1 时钟
  • 15
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是使用HAL库实现STM32F103单片机定时器触发ADC转换的代码实现。 首先需要在CubeMX中配置好ADC和定时器的参数,然后在main.c文件中编写以下代码: ```c #include "stm32f1xx_hal.h" ADC_HandleTypeDef hadc1; TIM_HandleTypeDef htim2; void SystemClock_Config(void); static void MX_GPIO_Init(void); static void MX_ADC1_Init(void); static void MX_TIM2_Init(void); int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_ADC1_Init(); MX_TIM2_Init(); HAL_TIM_Base_Start(&htim2); //启动定时器 HAL_ADC_Start(&hadc1); //启动ADC uint32_t adc_value; //定义变量存储ADC转换结果 while (1) { HAL_ADC_PollForConversion(&hadc1, 100); //等待ADC转换完成 if (HAL_ADC_GetState(&hadc1) == HAL_ADC_STATE_EOC_REG) //判断ADC转换完成状态 { adc_value = HAL_ADC_GetValue(&hadc1); //获取ADC转换结果 //处理ADC转换结果 } } } void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; __HAL_RCC_PWR_CLK_ENABLE(); __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1); RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI; RCC_OscInitStruct.HSIState = RCC_HSI_ON; RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE; if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { Error_Handler(); } RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI; 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(); } } static void MX_ADC1_Init(void) { ADC_ChannelConfTypeDef sConfig = {0}; hadc1.Instance = ADC1; hadc1.Init.ScanConvMode = DISABLE; hadc1.Init.ContinuousConvMode = ENABLE; hadc1.Init.DiscontinuousConvMode = DISABLE; hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIGCONV_T2_CC2; //设置ADC触发源为定时器2的CC2输出 hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT; hadc1.Init.NbrOfConversion = 1; if (HAL_ADC_Init(&hadc1) != HAL_OK) { Error_Handler(); } sConfig.Channel = ADC_CHANNEL_0; //设置采样通道为ADC1的通道0 sConfig.Rank = ADC_REGULAR_RANK_1; sConfig.SamplingTime = ADC_SAMPLETIME_28CYCLES_5; if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) { Error_Handler(); } } static void MX_TIM2_Init(void) { TIM_ClockConfigTypeDef sClockSourceConfig = {0}; TIM_MasterConfigTypeDef sMasterConfig = {0}; htim2.Instance = TIM2; htim2.Init.Prescaler = 35999; //设置定时器分频系数,定时器时钟频率为72MHz/(35999+1)=2kHz htim2.Init.CounterMode = TIM_COUNTERMODE_UP; htim2.Init.Period = 999; //设置定时器计数周期为1000个时钟周期,即500ms产生一次触发信号 htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; if (HAL_TIM_Base_Init(&htim2) != HAL_OK) { Error_Handler(); } sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL; if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK) { Error_Handler(); } sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE; sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK) { Error_Handler(); } } static void MX_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; __HAL_RCC_GPIOA_CLK_ENABLE(); GPIO_InitStruct.Pin = GPIO_PIN_0; GPIO_InitStruct.Mode = GPIO_MODE_ANALOG; GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); } ``` 在以上代码中,HAL_ADC_Init()函数用于初始化ADC,HAL_ADC_ConfigChannel()函数用于配置ADC的采样通道,HAL_TIM_Base_Init()函数用于初始化定时器,HAL_TIM_Base_Start()函数用于启动定时器,HAL_ADC_Start()函数用于启动ADC,HAL_ADC_PollForConversion()函数用于等待ADC转换完成,HAL_ADC_GetValue()函数用于获取ADC转换结果。 需要注意的是,在MX_ADC1_Init()函数中,使用了ADC_EXTERNALTRIGCONV_T2_CC2宏定义来设置ADC触发源为定时器2的CC2输出,这个宏定义在stm32f1xx_hal_adc.h头文件中定义。而在MX_TIM2_Init()函数中,使用了TIM_TRGO_UPDATE宏定义来设置定时器触发输出为更新事件触发。 以上就是使用HAL库实现STM32F103单片机定时器触发ADC转换的代码实现。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值