HAL库main函数的分析与学习
概要
通过学习采用CubeMX生成的工程中main函数的写法,来学习HAL库编程的思想和方法。通过今天的学习,希望自己能够在不使用CubeMX的情况下,采用类似固件库的方式写HAL库简单代码,控制LED。
内容
1.打开"main.c"后,首先遇到的是“#Include main.h”
看看 main.h做了哪些工作:
1.1 #include “stm32f4xx_hal.h”——将hal库包括进来
而stm32f4xx_hal.h中又包含了stm32f4xx_hal_conf.h。
#include “stm32f4xx_hal_conf.h”
这个头文件在昨天学习过了。
1.2 对Error_Handler()函数做了声明
void Error_Handler(void);该函数位于main()中。
1.3重新定义了工程中用到的引脚
因为我在CubeMX中对GPIO引脚,根据功能进行了重命名,所以系统在工程中也进行了重新定义。如果我不重命名,这部分就没有。
1.4 没了
2.#include “gpio.h”
gpio.h中首先也包含了main.h,然后对gpio初始化函数进行声明。基本上就2句话:
#include “main.h”
void MX_GPIO_Init(void);
没了
3.void SystemClock_Config(void);配置系统时钟
SystemClock_Config()函数就在main.c中定义的,下面好好研究一下它的实现过程:
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};//初始化时钟源的结构体,定义在hal_rcc.h中,结构如下:
/*typedef struct
{
uint32_t OscillatorType; 可以是:RCC_OSCILLATORTYPE_NONE、RCC_OSCILLATORTYPE_LSI、RCC_OSCILLATORTYPE_HSI、RCC_OSCILLATORTYPE_LSE、RCC_OSCILLATORTYPE_HSE。
uint32_t HSEState; 可以是RCC_HSE_OFF、RCC_HSE_ON、RCC_HSE_BYPASS
uint32_t LSEState; 可以是RCC_LSE_OFF、RCC_LSE_ON、RCC_LSE_BYPASS
uint32_t HSIState; 可以是RCC_HSI_OFF、RCC_HSI_ON、RCC_HSICALIBRATION_DEFAULT
uint32_t HSICalibrationValue; 不用关注,值在0~31之间
uint32_t LSIState;可以是RCC_LSI_OFF、RCC_LSI_ON
RCC_PLLInitTypeDef PLL; 这是个PLL初始化结构体
}RCC_OscInitTypeDef;
PLL初始化结构体。该结构体位于hal_rcc_ex.h中:
typedef struct
{
uint32_t PLLState; The new state of the PLL.
This parameter can be a value of @ref RCC_PLL_Config
基本上每个参数后面都会说明该参数的值可以取哪些,比如上面的PLLState,让我们参考RCC_PLL_Config的值,只要ctrl+F去查找RCC_PLL_Config,看看可以取哪些值就好了。这里PLLState可以取:RCC_PLL_NONE、RCC_PLL_OFF、RCC_PLL_ON(这些值的定义又回到hal_rcc.h中)
uint32_t PLLSource; 可以取RCC_PLLSOURCE_HSE、RCC_PLLSOURCE_HSI
uint32_t PLLM; 分频系数M,在0-63之间
uint32_t PLLN; 倍频系数N,在50-432之间
uint32_t PLLP; 分频系数P,只能取2、4、6、8
uint32_t PLLQ; 分频系数Q,在2-15之间
}RCC_PLLInitTypeDef;
PLLCLK频率=输入时钟源频率*N/(M*P*Q)。
*/
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; //初始化时钟的结构体,定义在hal_rcc.h中,结构如下:
/*
typedef struct
{
uint32_t ClockType; 时钟类型,可以选择RCC_CLOCKTYPE_SYSCLK、RCC_CLOCKTYPE_HCLK、RCC_CLOCKTYPE_PCLK1、RCC_CLOCKTYPE_PCLK2
uint32_t SYSCLKSource; 选择SYSCLK时钟的来源,选项有:RCC_SYSCLKSOURCE_HSI、RCC_SYSCLKSOURCE_HSE、RCC_SYSCLKSOURCE_PLLCLK
uint32_t AHBCLKDivider; 设置AHB时钟是由SYSCLK经过几分频得到的,可选的分频数值有1、2、4、8...512
uint32_t APB1CLKDivider; 设置APB1时钟是由SYSCLK经过几分频得到的,可选的分频数值有1、2、4、8、16
uint32_t APB2CLKDivider; 设置APB2时钟是由SYSCLK经过几分频得到的,可选的分频数值有1、2、4、8、16
}RCC_ClkInitTypeDef;
*/
/** Configure the main internal regulator output voltage */
__HAL_RCC_PWR_CLK_ENABLE();
/*宏定义如下:
#define __HAL_RCC_PWR_CLK_ENABLE() do { \
__IO uint32_t tmpreg = 0x00U; \
SET_BIT(RCC->APB1ENR, RCC_APB1ENR_PWREN);\
Delay after an RCC peripheral clock enabling \
tmpreg = READ_BIT(RCC->APB1ENR, RCC_APB1ENR_PWREN);\
UNUSED(tmpreg); \
} while(0U)
这个宏将RCC_APB1ENR寄存器的PWREN位置1,使能了电源接口时钟。
STM32单片机内部有线性稳压器,输出电压1.2V,在画最小系统时,在2个VCAP引脚上连接的2.2uf的电容就是用来对线性稳压器的输出进行滤波的。
*/
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
//将电源控制寄存器PWR_CR的VOS位(调压器输出电压级别选择)置为1,此位用来控制内部主调压器的输出电压
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_LSI|RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.LSIState = RCC_LSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLM = 4;
RCC_OscInitStruct.PLL.PLLN = 168;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
RCC_OscInitStruct.PLL.PLLQ = 4;
//以上语句的功能是:启用HSE、LSI、PLL,将PLL的时钟源设为HSE,设置分频、倍频因子,通过下面的HAL_RCC_OscConfig()函数完成时钟的设置。
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/*HAL_RCC_OscConfig()函数有300+行,基本分成了5个模块,前四个分别对4个时钟源进行处理,最后一个对PLL进行设置。下面分析HSE部分程序
if(((RCC_OscInitStruct->OscillatorType) & RCC_OSCILLATORTYPE_HSE) == RCC_OSCILLATORTYPE_HSE)
{
* Check the parameters *
assert_param(IS_RCC_HSE(RCC_OscInitStruct->HSEState));
* 检查SYSCLK的时钟源是不是HSE。如果系统时钟直接使用HSE或者采用以HSE为时钟源的锁相环,并且HSE已经就绪,如果程序员将HSE状态设置为OFF,则返回HAL_ERROR,也就是说你不能在HSE还在使用着的情况下关闭它 *
if((__HAL_RCC_GET_SYSCLK_SOURCE() == RCC_CFGR_SWS_HSE) ||\
((__HAL_RCC_GET_SYSCLK_SOURCE() == RCC_CFGR_SWS_PLL) && ((RCC->PLLCFGR & RCC_PLLCFGR_PLLSRC) == RCC_PLLCFGR_PLLSRC_HSE)))
{
if((__HAL_RCC_GET_FLAG(RCC_FLAG_HSERDY) != RESET) && (RCC_OscInitStruct->HSEState == RCC_HSE_OFF))
*__HAL_RCC_GET_FLAG()宏是一个比较复杂点的宏,它的定义如下:
#define __HAL_RCC_GET_FLAG(__FLAG__) (((((((__FLAG__) >> 5U) == 1U)? RCC->CR :((((__FLAG__) >> 5U) == 2U) ? RCC->BDCR :((((__FLAG__) >> 5U) == 3U)? RCC->CSR :RCC->CIR))) & (1U << ((__FLAG__) & RCC_FLAG_MASK)))!= 0U)? 1U : 0U)
而我们程序中,RCC_FLAG_HSERDY=0x31,每个RCC_FLAG中的低5位决定了这个FLAG在寄存器中的位置,而剩下的高位,决定了位在哪个寄存器中,去掉低5位后如果为1,则在CR寄存器;如果为2,则在BDCR寄存器;如果为3,则在CSR寄存器。
{
return HAL_ERROR;
}
}
else //如果HSE没有被使用,那么可以重新设置它
{
* Set the new HSE configuration ---------------------------------------*
__HAL_RCC_HSE_CONFIG(RCC_OscInitStruct->HSEState);可以设置三种状态:ON、OFF、BYPASS
* Check the HSE State *
if((RCC_OscInitStruct->HSEState) != RCC_HSE_OFF)//如果你是要将HSE设为ON
{
* Get Start Tick *
tickstart = HAL_GetTick();获取将HSE设为ON的开始时刻(用tick计数值表示)
* Wait till HSE is ready *
while(__HAL_RCC_GET_FLAG(RCC_FLAG_HSERDY) == RESET)在定时器未超时的情况下,等待HSE准备好,如果在时限之内HSE都没准备好,那么返回HAL_TIMEOUT。
{
if((HAL_GetTick() - tickstart ) > HSE_TIMEOUT_VALUE)
{
return HAL_TIMEOUT;
}
}
}
else//如果你是要将HSE设置为BYPASS或者OFF,则同样在时限范围内等待HSEOFF或者BYPASS.
{
* Get Start Tick *
tickstart = HAL_GetTick();
* Wait till HSE is bypassed or disabled *
while(__HAL_RCC_GET_FLAG(RCC_FLAG_HSERDY) != RESET)
{
if((HAL_GetTick() - tickstart ) > HSE_TIMEOUT_VALUE)
{
return HAL_TIMEOUT;
}
}
}
}
}
*/
//接下来对RCC_ClkInitStruct结构体设置系统时钟SYSCLK的时钟来源、HCLK、PCLK1、PCLK2的分频系数,最后使用HAL_RCC_ClockConfig()函数将这些参数写入RCC的各个寄存器。HAL_RCC_ClockConfig()函数中有一个参数FLASH_LATENCY_5,为什么有这么一个参数?查阅参考手册,可以看到不同的电源电压和主频的情况下,需要设置不同的FLASH访问等待时间,在3.3V供电、168MHz频率下,访问FLASH就需要等待6个CPU时钟周期,也就是这里要设置为FLASH_LATENCY_5。同时还可以看到,当使用168M频率时,PWR_CR的VOS位就需要设置为1.
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
{
Error_Handler();
}
}