stm 社区 关于HAL库中stm32f1xx_hal_msp.c文件的认知(新手贴)

http://www.stmcu.org.cn/module/forum/thread-620752-1-3.html
关于HAL库中stm32f1xx_hal_msp.c文件的认知(新手贴)

要熟练使用STM32CUBEMX这个软件,首先就要熟悉HAL库。俺以前用的片子基本都是51核心的,都是直操寄存器的,后来用STM8的片子,用的是库,感觉用起来很方便。随着项目的不断扩充和客户要求的提高,用到了STM32F103的片子,想想自己关于STM32的认知水平还在6年前3.5版本的库,感觉应该有点落后了,于是上网找找最新版本的库,好做项目设计准备。就在这时候发现了STM32CUBEMX的这个软件以及HAL库,看了相关介绍后,瞬间觉得自己完全落伍了,学习,学习,再学习......

网上好多关于HAL库和标准库优劣的探讨,俺再这就不多说了,俺认为只要能满足项目需要就行,就好像筷子和刀叉的关系,只要能把饭吃到嘴里就行。俺为啥选择学HAL库?因为俺以前啥也不会,要学当然选最新的方向学习了。

言归正传

学习HAL库,首先是对这个库的各个文件仔细研究,每个文件的大概功能、在整个库中所起到的作用、文件与文件之间引用关系、文件与文件之间的层级关系都要做到心中有数,这样在使用库做项目的时候才不至于逻辑混乱,减少调试时间。对整个库的研究需要狠下功夫,对库中文件的作用和关系了解透彻了,也就能明白了HAL的编程思想精髓,结合STM32CUBEMX,可以随心应手操刀自己的设计。

在HAL库中有一个stm32f1xx_hal_msp.c的文件,这个文件的作用就是根据用户所提供的具体的MCU型号以及硬件配置,对HAL库进行初始化设置操作。所以这个文件是就HAL库与MCU结合的纽带(不知这样描述是否恰当)。

以下是个打比方说明方式(欢迎指正):

首先把每个片上外设看成一个功能元件,把有关所有外设的记录合起来就是一个表格(BOM表)。

好比F1系列的HAL库是对F1系列所有型号MCU功能BOM的集合。
现在要使用一个F1系列的单片机,型号就叫作A1。
在CUBEMX中,设置单片机型号为A1
那么CUBEMX就会根据A1这个关键字去配置相应的功能BOM表。并一起把相应具体的用户硬件设置写到stm32f1xx_hal_msp.c中(完成了对HAL库的初始化,注:这个初始化着重的硬件电气上的初始化,就是确定用哪个引脚,什么样高低电平、多大的频率等等)。


那么stm32f1xx_hal_msp.c中的设置是怎么调用和被调用的呢?

1.在stm32f1xx_hal_msp.c内包含了头文件stm32f1xx_hal.h。(可以认为stm32f1xx_hal.h是stm32f1xx_hal_msp.c的头文件,同样也是stm32f1xx_hal.c的头文件)
2.在stm32f1xx_hal.h中声明了HAL_MspInit(void)函数。
3.在stm32f1xx_hal_msp.c内定义了HAL_MspInit(void)函数

(也就是间接的通过stm32f1xx_hal.h文件先声明了HAL_MspInit(void)函数,再接着对其进行具体的定义,为什么要在stm32f1xx_hal.h中先声明,是因为还要在stm32f1xx_hal.c中还进行了弱定义)。

4.stm32f1xx_hal.c内弱定义了 __weak void HAL_MspInit(void)。

5.stm32f1xx_hal_msp.c中的函数定义相对stn32f1xx_hal.c中的同名函数定义具有优先权,如果在tm32f1xx_hal_msp.c没有定义某外设函数,则使用stn32f1xx_hal.c中的定义的那个函数。

6.用户可通过重新定义stm32f1xx_hal_msp.c内的函数,实现对函数的操作。

7.stm32f1xx_hal_msp.c中的函数通过stm32f1xx_hal.h头文件引用。(也就是stm32f1xx_hal_msp.c中具体定义的函数都先在stm32f1xx_hal.h中被预先声明了)。

通过上面可以了解到,用户对MCU与外设配置写在stm32f1xx_hal_msp.c内,作为回调函数被HAL库中的其他文件使用。

 

下面是根据网上大师们和大咖们的教导结合自己的认知写的总结,仅供参考。

英文翻译,不知道那个对,俺也不敢说:

MSP MCU Specific Package MCU 特征 包、MCU 具体实例化 包

MSP MCU Support package MCU 支持 包

作用和目的:

根据具体的MCU型号,对片上外设进行初始化设置。

这样做的目的是因为从以下几个方面考虑的:

1、所有的同一类型的外设被抽象成通用的封装,即同类型的外设在不同的MCU上的特征属性和API接口都是一样和相同的。

2、相同内核但不同型号的MCU(比如STM32F103C8T6和 STM32F103ZET6都是M3内核)的引脚数量、顺序、资源、内存空间以及片上外设种类数量存在不同。

3、为了使HAL库对具有相同内核但资源不同的MCU有较强兼容性,特增加了此文件,让用户根据每款MCU在硬件上具体区别,初始化和配置外设的IO引脚、外设的工作时钟以及外设的中断与MCU内核寄存器的对应关系。

4、此文件是对MCU硬件上的初始化设置(一些协议、数据格式等等上层的内容一般不涉及),把具体的硬件配置抽象,形成符合HAL库要求的、具有统一格式的和属性种类的结构体。此文件由用户进行编程初始化和配置。该文件内的初始化过程强调的是外设最底层硬件上的初始化。

举例:如果存在某个外设PPP

那么HAL_PPP_MSP_Init() 函数是对外设PPP硬件上的具体设置。

HAL_PPP_MSP_Init()这个函数又进一步被PPP_Init()外设初始化函数调用。

HAL_PPP_MSP_Init()是做为一个回调函数被用户配置,HAL库回调使用,从而使HAL库在整体架构上做到统一和兼容。

就是说MSP的作用是把某个外设的接口资源给具体化了,比如对于串口外设,就是指定串口具体的接口引脚状态(包含引脚的位置、电气属性等等)以及外设与CPU的接口(外设与CPU的接口就是特殊功能寄存器的映射地址,也就是告诉CPU要操作哪个外设只要操作相应地址的寄存器就可以了)。

很久之前就听说st出了一个新版本的库,用于代替原来的标准库,非常好奇,但是一直没有机会去体验。这次借着做毕设的机会,尝试着切换到新库。

官网介绍说,hal(hardware abstract layer)是一层硬件的抽象,看到这里,我非常激动,看来st终于意识到原来标准库的问题了,原来的标准库非常依赖于具体硬件细节,很难体现出使用库的优势,而且很难移植。同时我也非常好奇,st到底是如何把不同系列mcu的操作给封装起来的,是不是足够抽象,方便移植。

话不多说,直接上官网下下来再说。

在这里插入图片描述

上图就是hal库的全部内容,其中STM32F1xx_HAL_Driver中属于hal库的内容。

拿到库第一步需要做的就是配置一个简单的hello world,我在配置的时候,出现了非常多的问题。最开始非常自信,只从文件夹里挑选自认为有用的文件加入到工程中,结果出现了各种问题,里面各种库的依赖关系比较复杂,如果不是很熟悉整个架构的话,还是老老实实拷贝整个文件夹吧。

配置之前,首先要了解一下整个库的框架,官方给的框架图如下:

在这里插入图片描述

个人认为这幅图和我理解的有些许出入,故重新画了一张:

在这里插入图片描述

有几点区别:

  • cmsis我放在了驱动层的最底层,因为cmsis库中包含的内容都是和具体cpu内核相关的东西,还有一些地址定义,这些都是非常底层的东西了,而且hal层确实是依赖于cmsis。
  • hal底层我增加了一层msp,类似于bsp,全称是mcu support package,这一层相当于hal的驱动层,与硬件相关的部分比如最终的时钟配置,gpio配置等等提取出来,交给用户配置。

了解了架构,下面我们就来配置一个简单的工程吧。

  1. 首先拷贝整个Driver目录到工程中。
  2. 新建user文件夹,新建main.c文件。
    找到stm32f1xx_hal_conf_template.h,stm32f1xx_hal_msp_template.c,去掉"_template"放入user文件夹。
    找到stm32f1xx_it.c和stm32f1xx_it.h放入user文件夹。
  3. 新建工程
    添加源文件:

在这里插入图片描述

Paste_Image.png

配置工程:

  • 勾选Use MicroLib,因为hal使用了c标准库。
  • 添加全局宏定义:USE_HAL_DRIVER,STM32F103xB。关于芯片选择,有如下表格:


    在这里插入图片描述

  • 勾选c99支持,因为hal采用的是c99标准编写,不勾选的话,会出现类似于uint32_t等类型不存在的编译错误。
  • 添加包含目录,如下图:
  • 在这里插入图片描述

    4.编写代码:

    配置stm32f1xx_hal_conf.h:
    这里面有许多用于配置的宏,比如用于精准延时的晶振频率,还有各个外设模块的开关等等。

    main.c

    #include "stm32f1xx_hal.h"
    int main()
    {
        HAL_Init();
        
        __HAL_RCC_GPIOC_CLK_ENABLE();
        
        GPIO_InitTypeDef gpio_initstruct;
        gpio_initstruct.Mode=GPIO_MODE_OUTPUT_PP;
        gpio_initstruct.Speed=GPIO_SPEED_FREQ_HIGH;
        gpio_initstruct.Pull=GPIO_NOPULL;
        gpio_initstruct.Pin=GPIO_PIN_13;
        HAL_GPIO_Init(GPIOC,&gpio_initstruct);
    
        while(1)
        {
            HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,0);
            HAL_Delay(150);
            HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,1);
            HAL_Delay(150);
            HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,0);
            HAL_Delay(150);
            HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,1);
            HAL_Delay(1000);
        }
        return 0;
    }
    

    stm32f1xx_hal_msp.c

    #include "stm32f1xx_hal.h"
    void SystemClock_Config(void);
    void HAL_MspInit(void)
    {
        SystemClock_Config();
    }
    void SystemClock_Config(void)
    {
      RCC_ClkInitTypeDef clkinitstruct = {0};
      RCC_OscInitTypeDef oscinitstruct = {0};
      
      /* Configure PLL ------------------------------------------------------*/
      /* PLL configuration: PLLCLK = (HSI / 2) * PLLMUL = (8 / 2) * 16 = 64 MHz */
      /* PREDIV1 configuration: PREDIV1CLK = PLLCLK / HSEPredivValue = 64 / 1 = 64 MHz */
      /* Enable HSI and activate PLL with HSi_DIV2 as source */
      oscinitstruct.OscillatorType  = RCC_OSCILLATORTYPE_HSE;//RCC_OSCILLATORTYPE_HSI;
      oscinitstruct.HSEState        = RCC_HSE_ON;//RCC_HSE_OFF;
      oscinitstruct.LSEState        = RCC_LSE_OFF;
      oscinitstruct.HSIState        = RCC_HSI_OFF;//RCC_HSI_ON;
      oscinitstruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
      oscinitstruct.HSEPredivValue    = RCC_HSE_PREDIV_DIV1;
      oscinitstruct.PLL.PLLState    = RCC_PLL_ON;
      oscinitstruct.PLL.PLLSource   = RCC_PLLSOURCE_HSE;//RCC_PLLSOURCE_HSI_DIV2;
      oscinitstruct.PLL.PLLMUL      = RCC_PLL_MUL9;//RCC_PLL_MUL16;
      if (HAL_RCC_OscConfig(&oscinitstruct)!= HAL_OK)
      {
        /* Initialization Error */
        while(1); 
      }
    
      /* Select PLL as system clock source and configure the HCLK, PCLK1 and PCLK2 
         clocks dividers */
      clkinitstruct.ClockType = (RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2);
      clkinitstruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
      clkinitstruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
      clkinitstruct.APB2CLKDivider = RCC_HCLK_DIV1;
      clkinitstruct.APB1CLKDivider = RCC_HCLK_DIV2;  
      if (HAL_RCC_ClockConfig(&clkinitstruct, FLASH_LATENCY_2)!= HAL_OK)
      {
        /* Initialization Error */
        while(1); 
      }
    }
    

    整个编程步骤就是,hal库初始化->开外设时钟->外设初始化->用户程序,然后在msp.c文件里实现其他平台相关的杂七杂八的操作,需要调用的时候会自动调用,我这里只实现了一个点亮led的功能,故只实现了HAL_MspInit()函数。如果我们要使用uart、adc等其他更复杂的外设,我们需要在msp.c文件里重写HAL_UART_MspInit()、HAL_ADC_MspInit()等函数,当我们调用HAL_PPP_Init()时,他们都会自动被调用。

    说到这里,我要说一下这里其实使用了一个c语言的技巧,实现了类似于c++的重载功能。比如我们来看UART的源文件:

    /**
      * @brief  USART MSP Init.
      * @param  husart: Pointer to a USART_HandleTypeDef structure that contains
      *                 the configuration information for the specified USART module.
      * @retval None
      */
     __weak void HAL_USART_MspInit(USART_HandleTypeDef *husart)
    {
      /* Prevent unused argument(s) compilation warning */
      UNUSED(husart);
      /* NOTE: This function should not be modified, when the callback is needed,
               the HAL_USART_MspInit can be implemented in the user file
       */ 
    }
    

    函数被__weak修饰了,意思就是,如果别处没定义,这个函数就是他,如果别处重定义了,就用新的函数,这样就实现了重载。这有一个很大的好处,就是实现oo思想中的差异化编程,hal实现所有硬件通用的功能,而把不通用的部分通过可重载的函数开放给用户修改。

    体现oo的还有个地方,每个函数中,都会接收到一个handle指针,这其实和this指针非常类似,每个函数都不用知道自己到底是在操作某一个具体的对象,只需要根据handle的指向来操作就可以了。

    回到上面的重载。在hal库中有一点比较大的改变是,中断都是通过回调函数来开放给用户的,具体使用方式也是重载相关回调函数,不像标准库是直接在stm32fxxx_it.c里填写相关中断处理函数。这样做的好处是,hal帮我们处理了一些中断来临时的杂务,只把我们感兴趣的事件开放给用户。

    但是个人觉得这个改变需要再彻底一点,因为这并没有解决代码耦合性的痛点,每一次我们需要写中断函数的时候,总是要去改底层代码,而如果st给我们实现一个注册回调的接口,那么上层和下层之前就完全分离了,应用层各个模块之间也不会产生耦合。

    总结:
    总体而言,hal相比于标准库,层次架构更加清晰了,对平台更加抽象,但是还远远不够,依然非常依赖于具体的硬件,如果能实现Qt的那种抽象就完美了。用户使用的时候,只用包含hal.h而不用去管是hal_f1还是hal_f2或是什么其他系列的头文件,所有系列的代码打包在一起,通过条件编译来实现真正的跨平台,而如果需要使用某款mcu的特色功能时,就再包含一个hal_f1extend.h。如果这些st都实现了,那么单片机编程将会变得和应用编程一样简单方便!

    作者:logic_wei
    链接:https://www.jianshu.com/p/c6809c2bcb4f
    来源:简书
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值