(手撸HAL+CubeMX)时钟篇
- 1、对SystemInit函数的分析
- 2、使用HSI将总线时钟配置为最高频率
- 3、使用HSE将总线时钟配置为最高频率
- 4、使用Cube配置时钟树的参数
- 5、对HAL_Init函数分析
- 6、对系统定时器中断服务函数分析
有关时钟树和上电/复位的基础知识请参考“寄存器开发入门教程的第5章和第6章”
本章使用到与RCC有关的HAL库函数详解如下:
/***** RCC_Exported_Functions_Group1 *****/
1.HAL_StatusTypeDef HAL_RCC_DeInit(void);•功能:复位将时钟复位成初始状态,效果如下:①使用 HSI 作为系统时钟②关闭 HSE,PLL③关闭 CSS 和 MCO输出④AHB,APB1,APB2 分频值都是1⑤清除所有标志位,并关闭所有RCC相关中断•参数:无•返回值:HAL_OK = 0x00U,HAL_ERROR = 0x01U,HAL_BUSY = 0x02U,HAL_TIMEOUT = 0x03U:2.HAL_StatusTypeDef HAL_RCC_OscConfig(RCC_OscInitTypeDef *RCC_OscInitStruct);•功能:对时钟树的参数进行配置,包括HSI,HSE,PLL•参数:•RCC_OscInitStruct:指向RCC_OscInitTypeDef 结构体的指针•RCC_OscInitTypeDef 结构体元素,包括以下元素:•OscillatorType:选择需要配置的晶体振荡器,可选以下选项:RCC_OSCILLATORTYPE_NONE 0x00000000U:选择不配置振荡器RCC_OSCILLATORTYPE_HSE 0x00000001U:选择HSERCC_OSCILLATORTYPE_HSI 0x00000002U:选择HSIRCC_OSCILLATORTYPE_LSE 0x00000004U:选择LSERCC_OSCILLATORTYPE_LSI 0x00000008U:选择LSI•HSEState:使能/关闭外部高速晶振HSE,可选以下选项:RCC_HSE_OFF :关闭HSERCC_HSE_ON :打开HSERCC_HSE_BYPASS:使用有源晶振或者外部时钟信号时选此项•HSEPredivValue:HSE的分频值,可选以下选项:RCC_HSE_PREDIV_DIV1:1分频RCC_HSE_PREDIV_DIV2:2分频•LSEState:使能/关闭外部低速晶振LSE,可选以下选项:RCC_LSE_OFF :关闭LSERCC_LSE_ON :打开LSERCC_LSE_BYPASS:使用有源晶振或者外部时钟信号时选此项•HSIState:使能/关闭内部高速晶振HSI,可选以下选项:RCC_HSI_OFF :关闭HSIRCC_HSI_ON :打开HSI•HSICalibrationValue:对HSI晶振进行微调:可选0x00~0x1F,默认值RCC_HSICALIBRATION_DEFAULT(0x10)•LSIState:使能/关闭内部低速晶振LSI,可选以下选项:RCC_LSI_OFF :关闭LSIRCC_LSI_ON :打开LSI•RCC_PLLInitTypeDef PLL:PLL参数的结构体,包括以下元素•PLLState:使能/关闭PLL,可选以下选项:RCC_PLL_NONERCC_PLL_OFF :关闭PLLRCC_PLL_ON :打开PLL•PLLSource:PLL的时钟源,可选以下选项:RCC_PLLSOURCE_HSI_DIV2:HSI经过2分频后作为PLL的时钟源RCC_PLLSOURCE_HSE :HSE作为PLL的时钟源•PLLMUL:PLL的倍频系数,可选以下选项:RCC_PLL_MUL2~RCC_PLL_MUL16•返回值:HAL_OK = 0x00U,HAL_ERROR = 0x01U,HAL_BUSY = 0x02U,HAL_TIMEOUT = 0x03U:3.HAL_StatusTypeDef HAL_RCC_ClockConfig(RCC_ClkInitTypeDef *RCC_ClkInitStruct, uint32_t FLatency);•功能:对总线进行配置,包括SystemClock,AHB,APB1,APB2。设置Flash等待周期•参数:•RCC_ClkInitStruct:指向RCC_ClkInitTypeDef 结构体的指针•RCC_OscInitTypeDef 结构体元素,包括以下元素:•ClockType:选择需要配置总线,可选以下选项:RCC_CLOCKTYPE_SYSCLK :选择系统总线RCC_CLOCKTYPE_HCLK :选择AHB总线RCC_CLOCKTYPE_PCLK1 :选择APB1总线RCC_CLOCKTYPE_PCLK2 :选择APB2总线•SYSCLKSource:选择系统总线的时钟源,可选以下选项:RCC_SYSCLKSOURCE_HSI :选择HSI作为系统总线时钟的时钟源RCC_SYSCLKSOURCE_HSE :选择HSE作为系统总线时钟的时钟源RCC_SYSCLKSOURCE_PLLCLK:选择PLL作为系统总线时钟的时钟源•AHBCLKDivider:AHB总线的预分频系数,可选以下选项:RCC_SYSCLK_DIV1~RCC_SYSCLK_DIV512•APB1CLKDivider:APB1总线的分频系数,可选以下选项:RCC_HCLK_DIV1~RCC_HCLK_DIV16•APB2CLKDivider:APB2总线的分频系数,可选以下选项:RCC_HCLK_DIV1~RCC_HCLK_DIV16•FLatency:设置Flash等待周期•返回值:HAL_OK = 0x00U,HAL_ERROR = 0x01U,HAL_BUSY = 0x02U,HAL_TIMEOUT = 0x03U:/***** RCC_Exported_Functions_Group2 *****/
4.void HAL_RCC_MCOConfig(uint32_t RCC_MCOx, uint32_t RCC_MCOSource, uint32_t RCC_MCODiv);•功能:配置MCO引脚(PA8)输出源•参数:•RCC_MCOx:选择MCO对应的引脚(可选值为1个:RCC_MCO对应的PA8)•RCC_MCOSource:指向RCC_ClkInitTypeDef 结构体的指针RCC_MCO1SOURCE_NOCLOCK:无时钟RCC_MCO1SOURCE_SYSCLK :选择SYSCLK从MCO引脚输出RCC_MCO1SOURCE_HSI :选择HSI从MCO引脚输出RCC_MCO1SOURCE_HSE :选择HSE从MCO引脚输出•RCC_MCODiv:分频值(可选值为1个:RCC_MCODIV_1)5.uint32_t HAL_RCC_GetSysClockFreq(void);•功能:获取系统总线时钟频率•参数:无•返回值: 系统总线时钟的频率6.uint32_t HAL_RCC_GetHCLKFreq(void);•功能:获取AHB总线时钟频率•参数:无•返回值: AHB总线时钟的频率7.uint32_t HAL_RCC_GetPCLK1Freq(void);•功能:获取APB1总线时钟频率•参数:无•返回值: APB1总线时钟的频率8.uint32_t HAL_RCC_GetPCLK2Freq(void);•功能:获取APB2总线时钟频率•参数:无•返回值: APB2总线时钟的频率9.void HAL_RCC_GetOscConfig(RCC_OscInitTypeDef *RCC_OscInitStruct);•功能:获取RCC_OscInitTypeDef(时钟源配置结构体)配置详情•输入参数:•RCC_OscInitStruct:RCC_OscInitTypeDef 定义的结构体的指针10.void HAL_RCC_GetClockConfig(RCC_ClkInitTypeDef *RCC_ClkInitStruct, uint32_t *pFLatency);•功能:获取RCC_ClkInitTypeDef(总线时钟配置结构体)配置详情Flash等待周期配置详情•输入参数:•RCC_ClkInitStruct:RCC_ClkInitTypeDef 定义的结构体的指针•pFLatency:32位变量的指针(获取Flash的等待周期)11.void SystemCoreClockUpdate(void);•功能:刷新时钟树的配置参数•参数:无
1、对SystemInit函数的分析
文件源码如下:
void SystemInit (void)
{/* Reset the RCC clock configuration to the default reset state(for debug purpose) *//* Set HSION bit */RCC->CR |= 0x00000001U;/* Reset SW, HPRE, PPRE1, PPRE2, ADCPRE and MCO bits */RCC->CFGR &= 0xF8FF0000U;/* Reset HSEON, CSSON and PLLON bits */RCC->CR &= 0xFEF6FFFFU;/* Reset HSEBYP bit */RCC->CR &= 0xFFFBFFFFU;/* Reset PLLSRC, PLLXTPRE, PLLMUL and USBPRE/OTGFSPRE bits */RCC->CFGR &= 0xFF80FFFFU;/* Disable all interrupts and clear pending bits */RCC->CIR = 0x009F0000U;SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH. */
}
对源码进行分析如下:
RCC->CR |= 0x00000001U;//使能HSI内部高速晶振8MHz
RCC->CFGR &= 0xF8FF0000U;//将HSI作为系统SystemClock的时钟源//将AHB的预分频系数,APB1,APB2的预分频不分频,ADC预分频2分频
RCC->CR &= 0xFEF6FFFFU;//关闭了HSE,时钟检测器,PLL
RCC->CR &= 0xFFFBFFFFU;//关闭HSE的旁路(先关HSE,才能关旁路)。
RCC->CFGR &= 0xFF80FFFFU;//操作PLL,其实没什么卵用
RCC->CIR = 0x009F0000U;//清除一些中断的标志位
SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET;
综上:通过SysInit()函数后,选择的是HSI作为时钟树的时钟源。
我们通过代码获取一下各个总线的频率:代码如下:
#include "stm32f1xx_hal.h"uint32_t SysClockFreq;
uint32_t HCLKFreq;
uint32_t PCLK1Freq;
uint32_t PCLK2Freq;int main(void)
{HAL_Init();SystemCoreClockUpdate();//刷新一下时钟树值SysClockFreq = HAL_RCC_GetSysClockFreq();//系统时钟HCLKFreq = HAL_RCC_GetHCLKFreq();//AHB总线时钟PCLK1Freq = HAL_RCC_GetPCLK1Freq();//APB1总线时钟PCLK2Freq = HAL_RCC_GetPCLK2Freq();//APB2总线时钟
}
我们将STM32通过STLink连接到电脑,打开仿真,查看结果。0x007A1200 = 8MHz
2、使用HSI将总线时钟配置为最高频率
①RCC_Init.c文件的代码如下:
/*** 将时钟源配置配置为HSI,且AHB,ABP1和APB2的时钟频率到达最大值64MHz,32MHz,64MHz* HAL_StatusTypeDef HAL_RCC_OscConfig(RCC_OscInitTypeDef *RCC_OscInitStruct)* HAL_StatusTypeDef HAL_RCC_ClockConfig(RCC_ClkInitTypeDef *RCC_ClkInitStruct, uint32_t FLatency)* */
void HSI_RCC_Init(void)
{/* 1、配置振荡器HSI和PLL *//* 1.1、配置振荡器HSI */RCC_OscInitTypeDef CC_OscInitStruct;CC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI; //选择HSI振荡器作为时钟源CC_OscInitStruct.HSIState = RCC_HSI_ON; //开启HSI振荡器CC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT; //HSI微调/* 1.2、配置PLL */CC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL16; //PLL的倍频系数CC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI_DIV2; //选择分频后的HSICC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; //开启PLLHAL_RCC_OscConfig(&CC_OscInitStruct);/* 2、配置系统时钟 */RCC_ClkInitTypeDef RCC_ClkInitStruct;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_DIV2; //APB1的分频系数RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; //APB2的分频系数HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2); //传入配置的结构体指针//配置Flash的2个等待周期
}
②主函数main.c文件的代码如下:
#include "stm32f1xx_hal.h"
#include "STM32_RCC_Init.h"uint32_t SysClockFreq;
uint32_t HCLKFreq;
uint32_t PCLK1Freq;
uint32_t PCLK2Freq;int main(void)
{HAL_Init();HSI_RCC_Init(); //调用HSI配置函数SystemCoreClockUpdate(); //刷新一下时钟树值SysClockFreq = HAL_RCC_GetSysClockFreq();HCLKFreq = HAL_RCC_GetHCLKFreq();PCLK1Freq = HAL_RCC_GetPCLK1Freq();PCLK2Freq = HAL_RCC_GetPCLK2Freq();
}
我们将STM32通过STLink连接到电脑,打开仿真,查看结果。
3、使用HSE将总线时钟配置为最高频率
①RCC_Init.c文件的代码如下
/*** 将时钟源配置配置为HSE,且AHB,ABP1和APB2的时钟频率到达最大值72MHz,36MHz,72MHz*/
void HSE_RCC_Init(void)
{/* 1、配置振荡器HSE和PLL *//* 1.1、配置HSE*/RCC_OscInitTypeDef CC_OscInitStruct;CC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; //选择HSE振荡器作为时钟源CC_OscInitStruct.HSEState = RCC_HSE_ON; //开启HSECC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1; //HSE的预分配系数/* 1.2、配置PLL*/CC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9; //PLL的倍频系数CC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; //选择HSE作为PLL的来源CC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; //开启PLLHAL_RCC_OscConfig(&CC_OscInitStruct);/* 2、配置系统时钟 */RCC_ClkInitTypeDef RCC_ClkInitStruct;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_DIV2; //APB1的分频系数RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; //APB2的分频系数HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2); //传入配置的结构体指针//配置Flash的2个等待周期
}
②主函数main.c文件的代码如下:
#include "stm32f1xx_hal.h"
#include "STM32_RCC_Init.h"uint32_t SysClockFreq;
uint32_t HCLKFreq;
uint32_t PCLK1Freq;
uint32_t PCLK2Freq;int main(void)
{HAL_Init();HSI_RCC_Init(); //调用HSE配置函数SystemCoreClockUpdate(); //刷新一下时钟树值SysClockFreq = HAL_RCC_GetSysClockFreq();HCLKFreq = HAL_RCC_GetHCLKFreq();PCLK1Freq = HAL_RCC_GetPCLK1Freq();PCLK2Freq = HAL_RCC_GetPCLK2Freq();
}
我们将STM32通过STLink连接到电脑,打开仿真,查看结果。
4、使用Cube配置时钟树的参数
配置的步骤如下:
- 打开CubeMX,找到对应芯片,创建工程
- 对时钟树参数进行配置
- 工程名字,工程路径
对生成时钟树的代码分析:
void SystemClock_Config(void)
{RCC_OscInitTypeDef RCC_OscInitStruct = {0};RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};/** Initializes the RCC Oscillators according to the specified parameters* in the RCC_OscInitTypeDef structure.*/RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; //选择HSERCC_OscInitStruct.HSEState = RCC_HSE_ON; //使能HSERCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1; //HSE分频系数为1分频RCC_OscInitStruct.HSIState = RCC_HSI_ON; //使能HSIRCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; //使能PLLRCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; //PLL时钟源选择HSERCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9; //PLL倍频系数9if (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; //系统总线时钟源选择PLLRCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; //AHB的预分频系数RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2; //APB1的预分频系数RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; //APB2的预分频系数if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK){Error_Handler();}
}
综上:使用Cube生成的对时钟树的配置代码和手撸时钟树配置的代码毫无区别。
5、对HAL_Init函数分析
在HAL库开发中,主函数中首先就调用的是HAL_Init()函数。
①选择对HAL_Init()函数进行分析:
HAL_StatusTypeDef HAL_Init(void)
{
/* Prefetch buffer is not available on value line devices */__HAL_FLASH_PREFETCH_BUFFER_ENABLE();//使能Flash指令预取/* Set Interrupt Group Priority */HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);//NVIC优先级分组/* Use systick as time base source and configure 1ms tick (default clock after Reset is HSI) */HAL_InitTick(TICK_INT_PRIORITY);//初始化系统滴答定时器/* Init the low level hardware */HAL_MspInit();//配置底层硬件/* Return function status */return HAL_OK;
}
__HAL_FLASH_PREFETCH_BUFFER_ENABLE();//使能Flash指令预取
HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);//NVIC优先级分组,全为抢占优先级
HAL_InitTick(TICK_INT_PRIORITY);//初始化系统滴答定时器,TICK_INT_PRIORITY == 15
HAL_MspInit();//配置底层硬件
return HAL_OK;//返回一个OK
②选择对HAL_InitTick()函数进行分析:
__weak HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)
{/* Configure the SysTick to have interrupt in 1ms time basis*/if (HAL_SYSTICK_Config(SystemCoreClock / (1000U / uwTickFreq)) > 0U){return HAL_ERROR;}/* Configure the SysTick IRQ priority */if (TickPriority < (1UL << __NVIC_PRIO_BITS)){HAL_NVIC_SetPriority(SysTick_IRQn, TickPriority, 0U);uwTickPrio = TickPriority;}else{return HAL_ERROR;}/* Return function status */return HAL_OK;
}
①if (HAL_SYSTICK_Config(SystemCoreClock / (1000U / uwTickFreq)) > 0U)
{return HAL_ERROR;
}
•uint32_t HAL_SYSTICK_Config(uint32_t TicksNumb):用于配置系统定时器。详细解释如下。
•uwTickFreq:SysTick 定时器触发中断的频率。举个例子,如果你希望每秒产生 1000 次中断,那么 uwTickFreq = 1000
•SystemCoreClock:这是系统的主时钟频率,通常由 SystemClockConfig 配置,单位是 Hz。
•SystemCoreClock / (1000U / uwTickFreq) 的目的实际上是计算出正确的重载值。
•HAL_SYSTICK_Config(SystemCoreClock / (1000U / uwTickFreq)) 时,实际上传递给 HAL_SYSTICK_Config 的值就是 SysTick 定时器需要的计数器重载值。
•如果 HAL_SYSTICK_Config 返回一个大于零的值,表示配置失败,函数会返回 HAL_ERROR。②if (TickPriority < (1UL << __NVIC_PRIO_BITS))
{HAL_NVIC_SetPriority(SysTick_IRQn, TickPriority, 0U);uwTickPrio = TickPriority;
}
•配置系统定时器中断的优先级值,TickPriority:传入的参数,用来设置 SysTick 中断的优先级
③选择对HAL_SYSTICK_Config()函数进行分析:
uint32_t HAL_SYSTICK_Config(uint32_t TicksNumb)
{return SysTick_Config(TicksNumb);
}
__STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks)
{if ((ticks - 1UL) > SysTick_LOAD_RELOAD_Msk){return (1UL); /* Reload value impossible */}SysTick->LOAD = (uint32_t)(ticks - 1UL); /* set reload register */NVIC_SetPriority (SysTick_IRQn, (1UL << __NVIC_PRIO_BITS) - 1UL); /* set Priority for Systick Interrupt */SysTick->VAL = 0UL; /* Load the SysTick Counter Value */SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |SysTick_CTRL_TICKINT_Msk |SysTick_CTRL_ENABLE_Msk; /* Enable SysTick IRQ and SysTick Timer */return (0UL); /* Function successful */
}
SysTick->LOAD = (uint32_t)(ticks - 1UL);//系统定时器的重装值
NVIC_SetPriority (SysTick_IRQn, (1UL << __NVIC_PRIO_BITS) - 1UL);//设置中断优先级
SysTick->VAL = 0UL;//清除当前计数器的值
SysTick->CTRL |= SysTick_CTRL_CLKSOURCE_Msk;//滴答定时器的时钟源设置为系统总线SYSCLK
SysTick->CTRL |= SysTick_CTRL_TICKINT_Msk;//启动定时器中断
SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;//启动SysTick定时器
综上:HAL_Init()最主要的功能就是配置系统滴答定时器1ms中断。
6、对系统定时器中断服务函数分析
①中断服务函数代码如下:
void SysTick_Handler(void)
{/* USER CODE BEGIN SysTick_IRQn 0 *//* USER CODE END SysTick_IRQn 0 */HAL_IncTick();/* USER CODE BEGIN SysTick_IRQn 1 *//* USER CODE END SysTick_IRQn 1 */
}
②HAL_IncTick()函数代码如下:
__weak void HAL_IncTick(void)
{uwTick += uwTickFreq;//uwTickFreq = HAL_TICK_FREQ_1KHZ = 1U
}
所以系统滴答定时器中断服务函数里面是:每隔1ms进入中断,实现uwTick加1一次。