
为开发者提供了非常方便的开发库:有标准外设库(SPL库)、HAL 库(Hardware Abstraction Layer,硬件抽象层库)、LL 库(Low-Layer,底层库)三种。前者是ST的老库已经停更了,后两者是ST现在主推的开发库。 相比标准外设库,STM32Cube HAL库表现出更高的抽象整合水平,HAL API集中关注各外设的公共函数功能,这样便于定义一套通用的用户友好的API函数接口,从而可以轻松实现从一个STM32产品移植到另一个不同的STM32系列产品。HAL库是ST未来主推的库,ST新出的芯片已经没有STD库了,比如F7系列。目前,HAL库已经支持STM32全线产品。 通过文字描述可以知道HAL库的几个点: 1.最大可移植性。 2.提供了一整套一致的中间件组件,如RTOS,USB,TCP / IP和图形等。 3.通用的用户友好的API函数接口。 4.ST新出的芯片已经没有标准库。 5.HAL库已经支持STM32全线产品。 网友认为,“HAL我觉得是极好的,就是SPI接收时速度实在太慢,不用DMA的话,吃不消。“ 通常新手在入门STM32的时候,首先都要先选择一种要用的开发方式,不同的开发方式会导致你编程的架构是完全不一样的。一般大多数都会选用标准库和HAL库,而极少部分人会通过直接配置寄存器进行开发。网上关于标准库、HAL库的描述相信是数不胜数。可是一个对于很多刚入门的朋友还是没法很直观的去真正了解这些不同开发方式彼此之间的区别,所以笔者想以一种非常直白的方式,用自己的理解去将这些东西表述出来,如果有描述的不对的地方或者是不同意见的也可以大家提出。 01、直接配置寄存器 不少先学了51的朋友可能会知道,会有一小部分人或是教程是通过汇编语言直接操作寄存器实现功能的,这种方法到了STM32就变得不太容易行得通了,因为STM32的寄存器数量是51单片机的十数倍,如此多的寄存器根本无法全部记忆,开发时需要经常的翻查芯片的数据手册,此时直接操作寄存器就变得非常的费力了。但还是会有很小一部分人,喜欢去直接操作寄存器,因为这样更接近原理,知其然也知其所以然。 02、标准库 上面也提到了,STM32有非常多的寄存器,而导致了开发困难,所以为此ST公司就为每款芯片都编写了一份库文件,也就是工程文件里stm32F1xx…之类的。在这些 .c .h文件中,包括一些常用量的宏定义,把一些外设也通过结构体变量封装起来,如GPIO口时钟等。所以我们只需要配置结构体变量成员就可以修改外设的配置寄存器,从而选择不同的功能。也是目前最多人使用的方式,也是学习STM32接触最多的一种开发方式,我也就不多阐述了。 03、HAL库 HAL库是ST公司目前主力推的开发方式,全称就是Hardware Abstraction Layer(抽象印象层)。库如其名,很抽象,一眼看上去不太容易知道他的作用是什么。它的出现比标准库要晚,但其实和标准库一样,都是为了节省程序开发的时期,而且HAL库尤其的有效,如果说标准库把实现功能需要配置的寄存器集成了,那么HAL库的一些函数甚至可以做到某些特定功能的集成。也就是说,同样的功能,标准库可能要用几句话,HAL库只需用一句话就够了。并且HAL库也很好的解决了程序移植的问题,不同型号的stm32芯片它的标准库是不一样的,例如在F4上开发的程序移植到F3上是不能通用的,而使用HAL库,只要使用的是相通的外设,程序基本可以完全复制粘贴,注意是相通外设,意思也就是不能无中生有,例如F7比F3要多几个定时器,不能明明没有这个定时器却非要配置,但其实这种情况不多,绝大多数都可以直接复制粘贴。是而且使用ST公司研发的STMcube软件,可以通过图形化的配置功能,直接生成整个使用HAL库的工程文件,可以说是方便至极,但是方便的同时也造成了它执行效率的低下,在各种论坛帖子真的是被吐槽的数不胜数。 HAL库和标准固件库区别 STM32的开发中,我们可以操作寄存器: GPIOF->BSRR=0x00000001;//这里是针对STM32F1系列 这种方法当然可以,但是这种方法的劣势是你需要去掌握每个寄存器的用法,你才能正确使用STM32,而对于STM32这种级别的MCU,数百个寄存器记起来又是谈何容易。于是ST(意法半导体)推出了官方标准固件库,标准固件库将这些寄存器底层操作都封装起来,提供一整套接口(API)供开发者调用,大多数场合下,你不需要去知道操作的是哪个寄存器,你只需要知道调用哪些函数即可。 比如上面的控制 BRR 寄存器实现电平控制,官方库封装了一个函数:
标准固件库自推出以来受到广大工程师推崇,现在很多工程师和公司还在使用标准库函数开发。不过,ST官方已经不再更新STM32标准固件库,而是力推新的固件库:HAL库。 比如上面的控制BSRRL 寄存器实现电平控制,官方 HAL 库封装了一个函数:
标准固件库和HAL库一样都是固件库函数,由ST官方硬件抽象层而设计的软件函数包,由程序、数据结构和宏组成,包括了STM32所有外设的性能特征。 这些固件库为开发者底层硬件提供了中间API,通过使用固件库,无需掌握底层细节,开发者就可以轻松应用每一个外设。 HAL 库和标准库本质上是一样的,都是提供底层硬件操作 API,而且在使用上也是大同小异。有过标准库基础的同学对 HAL 库的使用也很容易入手。ST 官方之所以这几年大力推广 HAL 库,是因为 HAL 的结构更加容易整合 STM32Cube,而 STM32CubeMX 是 ST 这几年极力推荐的程序生成开发工具。所以这几年新出的 STM32 芯片,ST 直接只提供 HAL 库。 在ST的官方声明中,HAL库是大势所趋。ST最新开发的芯片中,只有HAL库没有标准库。标准库和HAL库虽然都是对外设进行操作的函数,但由于标准库官方已经停止更新,而且标准库在STM32创建工程和初始化时,不能由CubMX软件代码生成使用,也就是说CubMX软件在生产代码时,工程项目和初始化代码就自动生成,这个工程项目和初始化代码里面使用的库都是基于HAL库的。STM32CubeMX是一个图形化的工具,也是配置和初始化C代码生成器 ,与STM32CubeMX配合使用的是HAL库(硬件抽象层软件库)。 基本配置 工程创建 通过内核芯片的选择,创建相应的工程文件。 ![]() 配置时钟系统我们首要思考的是:我们需要怎样的时钟系统,而不是如何配置时钟系统。 ![]() ![]() 点击图中“GENERATE CODE”生成Keil工程文件 ![]() GPIO使用 开发环境搭建好以后,可以依开始STM32的开发,下面是使用GPIO的例程,让LED灯每隔400ms闪烁一次 打开STM32CubeMX新建工程,选择STM32F103ZET6芯片。选择外部高速晶振(HSE)。 ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() 在main函数里添加如下代码 ![]() 再点击Build按钮,然后等一会,Build Optput信息框会输出没有错误没有警告。 代码烧写。 现在开始烧写程序,烧写程序有两种,一种是使用ST-LINK工具烧写,一种是直接用与上位机通讯的串口1烧写。烧写工具使用mcuisp。 图片,软件可自行网上搜索,下载配置如下。 ![]() HAL库固件库安装与用户手册 1.首先设置让Cube可以自动联网下载相关固件库选择updater Settings ![]() 设置如下 ![]() 版本是向下兼容的,可以直接选择最新版。但如果觉得最新版太大,可以阅读下面的Main Changes.能够支持你目前的芯片就好。 ![]() 文件会被下载到如下位置,建议更改此目录,不要选在C盘!!! ![]() 查找帮助手册 3.寻找用户帮助手册进入固件所在文件夹,里面包含很多内容。 ![]() 比如说 官方提供的开发板程序 ![]() ![]() ![]() STM32 HAL库与标准库的区别 ——浅谈句柄、MSP函数、Callback函数 01 句柄 句柄(handle),有多种意义,其中第一种是指程序设计,第二种是指Windows编程。现在大部分都是指程序设计/程序开发这类。 · 第一种解释:句柄是一种特殊的智能指针。当一个应用程序要引用其他系统(如数据库、操作系统)所管理的内存块或对象时,就要使用句柄。 · 第二种解释:整个Windows编程的基础。一个句柄是指使用的一个唯一的整数值,即一个4字节(64位程序中为8字节)长的数值,来标识应用程序中的不同对象和同类中的不同的实例,诸如,一个窗口,按钮,图标,滚动条,输出设备,控件或者文件等。应用程序能够通过句柄访问相应的对象的信息,但是句柄不是指针,程序不能利用句柄来直接阅读文件中的信息。如果句柄不在I/O文件中,它是毫无用处的。句柄是Windows用来标志应用程序中建立的或是使用的唯一整数,Windows大量使用了句柄来标识对象。 STM32的标准库中,句柄是一种特殊的指针,通常指向结构体! 在STM32的标准库中,假设我们要初始化一个外设(这里以USART为例),我们首先要初始化他们的各个寄存器。在标准库中,这些操作都是利用固件库结构体变量+固件库Init函数实现的: (向右滑动哦)
· 1、对六个位置进行赋值, · 2、然后引用Init函数, USART_InitStructure并不是一个全局结构体变量,而是只在函数内部的局部变量,初始化完成之后,USART_InitStructure就失去了作用。 而在HAL库中,同样是USART初始化结构体变量,我们要定义为全局变量。
· 1、包含了之前标准库就有的六个成员(波特率,数据格式等), · 2、还包含过采样、(发送或接收的)数据缓存、数据指针、串口 DMA 相关的变量、各种标志位等等要在整个项目流程中都要设置的各个成员。该UART1_Handler就被称为串口的句柄,它被贯穿整个USART收发的流程,比如开启中断:
02 MSP函数 MSP: MCU Specific Package 单片机的具体方案 MSP是指和MCU相关的初始化,引用一下正点原子的解释,个人觉得说的很明白:我们要初始化一个串口,首先要设置和 MCU 无关的东西,例如波特率,奇偶校验,停止位等,这些参数设置和 MCU 没有任何关系,可以使用 STM32F1,也可以是 STM32F2/F3/F4/F7上的串口。而一个串口设备它需要一个 MCU 来承载,例如用 STM32F4 来做承载,PA9 做为发送,PA10 做为接收,MSP 就是要初始化 STM32F4 的 PA9,PA10,配置这两个引脚。所以 HAL驱动方式的初始化流程就是: HAL_USART_Init()—>HAL_USART_MspInit() ,先初始化与 MCU无关的串口协议,再初始化与 MCU 相关的串口引脚。 在 STM32 的 HAL 驱动中HAL_PPP_MspInit()作为回调,HAL_PPP_Init()函数所调用。当我们需要移植程序到 STM32F1平台的时候,我们只需要修改 HAL_PPP_MspInit 函数内容而不需要修改 HAL_PPP_Init 入口参数内容。 在HAL库中,几乎每初始化一个外设就需要设置该外设与单片机之间的联系,比如IO口,是否复用等等,可见,HAL库相对于标准库多了MSP函数之后,移植性非常强,但与此同时却增加了代码量和代码的嵌套层级。可以说各有利弊。 同样,MSP函数又可以配合句柄,达到非常强的移植性:
03 Callback函数 类似于MSP函数,个人认为Callback函数主要帮助用户应用层的代码编写。 还是以USART为例,在标准库中,串口中断了以后,我们要先在中断中判断是否是接收中断,然后读出数据,顺便清除中断标志位,然后再是对数据的处理,这样如果我们在一个中断函数中写这么多代码,就会显得很混乱:
综上,就是HAL库的三个与标准库不同的地方之个人见解。个人觉得从这三个小点就可以看出HAL库的可移植性之强大,并且用户可以完全不去理会底层各个寄存器的操作,代码也更有逻辑性。但与此带来的是复杂的代码量,极慢的编译速度,略微低下的效率。看怎么取舍了。 04 STM32 HAL库结构 说到STM32的HAL库,就不得不提STM32CubeMX,其作为一个可视化的配置工具,对于开发者来说,确实大大节省了开发时间。STM32CubeMX就是以HAL库为基础的,且目前仅支持HAL库及LL库!首先看一下,官方给出的HAL库的包含结构: ![]()
· 4.2 stm32f4xx_hal.h:stm32f4xx_hal.c/h 主要实现HAL库的初始化、系统滴答相关函数、及CPU的调试模式配置 · 4.3 stm32f4xx_hal_conf.h:该文件是一个用户级别的配置文件,用来实现对HAL库的裁剪,其位于用户文件目录,不要放在库目录中。 接下来对于HAL库的源码文件进行一下说明,HAL库文件名均以stm32f4xx_hal开头,后面加上_外设或者模块名(如:stm32f4xx_hal_adc.c): · 4.4 库文件 stm32f4xx_hal_ppp.c/.h // 主要的外设或者模块的驱动源文件,包含了该外设的通用API stm32f4xx_hal_ppp_ex.c/.h // 外围设备或模块驱动程序的扩展文件。这组文件中包含特定型号或者系列的芯片的特殊API。以及如果该特定的芯片内部有不同的实现方式,则该文件中的特殊API将覆盖_ppp中的通用API stm32f4xx_hal.c/.h // 此文件用于HAL初始化,并且包含DBGMCU、重映射和基于systick的时间延迟等相关的API · 4.5 其他库文件 用户级别文件: stm32f4xx_hal_msp_template.c // 只有.c没有.h。它包含用户应用程序中使用的外设的MSP初始化和反初始化(主程序和回调函数)。使用者复制到自己目录下使用模板。 stm32f4xx_hal_conf_template.h // 用户级别的库配置文件模板。使用者复制到自己目录下使用 system_stm32f4xx.c // 此文件主要包含SystemInit()函数,该函数在刚复位及跳到main之前的启动过程中被调用。 它不在启动时配置系统时钟(与标准库相反)。 时钟的配置在用户文件中使用HAL API来完成。 startup_stm32f4xx.s // 芯片启动文件,主要包含堆栈定义,终端向量表等stm32f4xx_it.c/.h // 中断处理函数的相关实现 · 4.6 main.c/.h // 根据HAL库的命名规则,其API可以分为以下三大类: · 初始化/反初始化函数:HAL_PPP_Init(), HAL_PPP_DeInit() · IO 操作函数:HAL_PPP_Read(), HAL_PPP_Write(),HAL_PPP_Transmit(), HAL_PPP_Receive() · 控制函数:HAL_PPP_Set (), HAL_PPP_Get (). · 状态和错误: ** HAL_PPP_GetState (), HAL_PPP_GetError (). 注意:目前LL库是和HAL库捆绑发布的,所以在HAL库源码中,还有一些名为 stm32f2xx_ll_ppp的源码文件,这些文件就是新增的LL库文件。 使用CubeMX生产项目时,可以选择LL库 HAL库最大的特点就是对底层进行了抽象。在此结构下,用户代码的处理主要分为三部分: · 处理外设句柄(实现用户功能) · 处理MSP · 处理各种回调函数 相关知识如下: · (1) 外设句柄定义 用户代码的第一大部分:对于外设句柄的处理。HAL库在结构上,对每个外设抽象成了一个称为ppp_HandleTypeDef的结构体,其中ppp就是每个外设的名字。*所有的函数都是工作在ppp_HandleTypeDef指针之下。 1. 多实例支持:每个外设/模块实例都有自己的句柄。因此,实例资源是独立的 2. 外围进程相互通信:该句柄用于管理进程例程之间的共享数据资源。下面,以ADC为例
当然,对于那些共享型外设或者说系统外设来说,他们不需要进行以上这样的抽象,这些部分与原来的标准外设库函数基本一样。例如以下外设: - GPIO - SYSTICK - NVIC - RCC - FLASH 以GPIO为例,对于HAL_GPIO_Init() 函数,其只需要GPIO 地址以及其初始化参数即可。 · (2) 三种编程方式 HAL库对所有的函数模型也进行了统一。在HAL库中,支持三种编程模式:轮询模式、中断模式、DMA模式(如果外设支持)。其分别对应如下三种类型的函数(以ADC为例):
__HAL_PPP_ENABLE_IT(HANDLE, INTERRUPT): 使能一个指定的外设中断 __HAL_PPP_DISABLE_IT(HANDLE, INTERRUPT):失能一个指定的外设中断 __HAL_PPP_GET_IT (HANDLE, __ INTERRUPT __):获得一个指定的外设中断状态 __HAL_PPP_CLEAR_IT (HANDLE, __ INTERRUPT __):清除一个指定的外设的中断状态 __HAL_PPP_GET_FLAG (HANDLE, FLAG):获取一个指定的外设的标志状态 __HAL_PPP_CLEAR_FLAG (HANDLE, FLAG):清除一个指定的外设的标志状态 __HAL_PPP_ENABLE(HANDLE) :使能外设__HAL_PPP_DISABLE(HANDLE) :失能外设 __HAL_PPP_XXXX (HANDLE, PARAM) :指定外设的宏定义 _HAL_PPP_GET IT_SOURCE (HANDLE, __ INTERRUPT __):检查中断源 · (3)三大回调函数 在HAL库的源码中,到处可见一些以__weak开头的函数,而且这些函数,有些已经被实现了,比如:
1. 外设系统级初始化/解除初始化回调函数(用户代码的第二大部分:对于MSP的处理): HAL_PPP_MspInit()和 HAL_PPP_MspDeInit** 例如:__weak void HAL_SPI_MspInit(SPI_HandleTypeDef *hspi)。在HAL_PPP_Init() 函数中被调用,用来初始化底层相关的设备(GPIOs, clock, DMA, interrupt) 2.处理完成回调函数:HAL_PPP_ProcessCpltCallback*(Process指具体某种处理,如UART的Tx),例如:__weak void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi)。当外设或者DMA工作完成后时,触发中断,该回调函数会在外设中断处理函数或者DMA的中断处理函数中被调用 3.错误处理回调函数:HAL_PPP_ErrorCallback例如:__weak void HAL_SPI_ErrorCallback(SPI_HandleTypeDef *hspi)**。当外设或者DMA出现错误时,触发终端,该回调函数会在外设中断处理函数或者DMA的中断处理函数中被调用 绝大多数用户代码均在以上三大回调函数中实现。 HAL库结构中,在每次初始化前(尤其是在多次调用初始化前),先调用对应的反初始化(DeInit)函数是非常有必要的。 某些外设多次初始化时不调用返回会导致初始化失败。完成回调函数有多种,例如串口的完成回调函数有HAL_UART_TxCpltCallback 和 HAL_UART_TxHalfCpltCallback等(用户代码的第三大部分:对于上面第二点和第三点的各种回调函数的处理)在实际使用中,发现HAL仍有不少问题,例如在使用USB时,其库配置存在问题 05 HAL库移植使用 基本步骤 1.复制stm32f2xx_hal_msp_template.c,参照该模板,依次实现用到的外设的HAL_PPP_MspInit()和 HAL_PPP_MspDeInit。 2.复制stm32f2xx_hal_conf_template.h,用户可以在此文件中自由裁剪,配置HAL库。 3.在使用HAL库时,必须先调用函数:HAL_StatusTypeDef HAL_Init(void)(该函数在stm32f2xx_hal.c中定义,也就意味着第一点中,必须首先实现HAL_MspInit(void)和HAL_MspDeInit(void)) 4.HAL库与STD库不同,HAL库使用RCC中的函数来配置系统时钟,用户需要单独写时钟配置函数(STD库默认在system_stm32f2xx.c中) 5.关于中断,HAL提供了中断处理函数,只需要调用HAL提供的中断处理函数。用户自己的代码,不建议先写到中断中,而应该写到HAL提供的回调函数中。 6.对于每一个外设,HAL都提供了回调函数,回调函数用来实现用户自己的代码。整个调用结构由HAL库自己完成。例如:Uart中,HAL提供了void HAL_UART_IRQHandler(UART_HandleTypeDef *huart);函数,用户只需要触发中断后,用户只需要调用该函数即可,同时,自己的代码写在对应的回调函数中即可!如下:
基本结构 综上所述,使用HAL库编写程序(针对某个外设)的基本结构(以串口为例)如下: 1.配置外设句柄 例如,建立UartConfig.c,在其中定义串口句柄 UART_HandleTypeDef huart;,接着使用初始化句柄(HAL_StatusTypeDef HAL_UART_Init(UART_HandleTypeDef huart)) 2.编写Msp 例如,建立UartMsp.c,在其中实现void HAL_UART_MspInit(UART_HandleTypeDef huart) 和 void HAL_UART_MspDeInit(UART_HandleTypeDef* huart) 3.实现对应的回调函数 例如,建立UartCallBack.c,在其中实现上文所说明的三大回调函数中的完成回调函数和错误回调函数 |
感谢支持!