2024年2月17日

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();
  }
}
  • 6
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值