1、聊一聊
今天跟大家推荐一首非常暖心的歌曲<What Are Words>,一首歌曲的灵魂并不是歌曲本身,而是歌曲后面的故事,"或许喜欢这首歌,却心疼这个故事"。
最近有很多小伙伴问到一些HAL库的问题,也有一些小伙伴还在是否使用HAL库前徘徊,相信这个文章会带你正确抉择!
2、话题引出
小白,stm32标准库好久没更新了,现在入坑HAL库,用得我好难受呀!
额~,HAL库?stm32新的库吗?像安卓那种HAL层?
。。。,你不会还没玩过HAL库吧?
我们都是一些老项目了,之前用的标准库就一直用标准库咯,也没有新的外设需求。HAL库名字看上去有点高大上呀!
高大上倒看不出来,坑倒是找出来蛮多!
算了,跟你说也是白说,我去问问bug菌。
bug菌,你了解HAL库吗 ?
我知道你要问啥,最近很多人也这么问我,我这里仔细跟你讲下!
1
各个库分析
为什么很多小伙伴对于库的选择会如此的困难,终究是对库的认识还不够,当你对一个事物没有充分理解的时候,往往都会缺乏对一些标准的判断力,所以bug菌首先跟大家聊聊各个库都干了啥。
首先大家可以看一下ST官方文档对于这几个库的设计和规划,如下路径的pdf,同时bug把其中重要的几个图贴上:
(https://www.st.com/content/ccc/resource/sales_and_marketing/presentation/product_presentation/37/55/ff/bc/a8/71/4f/c5/stm32_embedded_software_offering.pdf/files/stm32_embedded_software_offering.pdf/jcr:content/translations/en.stm32_embedded_software_offering.pdf)
各种库的性能比较
各种库的抽象位置
1
寄存器
寄存器编程对于从51等等芯片过渡过来的小伙伴并不陌生,不管你是什么库,最终操作的还是寄存器,所以对于标准库、HAL库、LL库都是在寄存器上的编程,所以可以直接在各种库中直接操作寄存器。
大家都可以在库文件中找到像stm32f4xx.h这样的寄存器头文件(这里均以F4系列为例),该头文件中大概有如下几个方面的内容:
寄存器地址:
寄存器结构 :
寄存器的位定义:
操作位的宏等等:
对于 “ 寄存器敲击大师 ”而言,基本上该头文件就可以搞定所有外设需求,相比其他库,直接操作寄存器来得更为直接、高效,省略了繁琐的调用和封装过程,但可移植性相对会降低一些。
同时对于芯片的熟练程度也是提出了要求,对于一款相对外设比较丰富的芯片需要花一定的时间阅读文档的等等,对开发人员有一定的门槛。
2
标准库
标准库全名叫标准外设库(Standard Peripheral Library),其实标准库所做的事情就是对寄存器进行了封装,形成了一套API函数供用户使用,仅此而已。
比如上图串口外设的API接口,其第一个参数即为串口的外设寄存器基地址,所以这些函数都是围绕寄存器来进行封装的,说白了就是寄存器的访问封装。
相比直接操作寄存器,相关寄存器功能更加明确、易懂。
3
LL库
LL库是与HAL库打包发布的一部分库文件,其也叫底层库(Low-Layer),它也是对寄存器进行了封装,与标准库其实差别并不是很大。
上图是LL库中的UART的相关实现,似乎函数声明部分该库中提供的API并没有标准库那么多,其实LL库中大部分API都是以static inline这样的内联函数形式进行定义的。
内联函数的优势在于不会像函数调用需要跳转到定义处等额外处理,而有点像宏定义一样直接展开并copy代码到执行位置,相对效率会提高很多,当然如果代码特别大,运行时间长,也就没有内联的必要了,所以其内联函数内部代码都相对比较短小。
4
HAL库
终于到了备受关注的HAL库,HAL全名叫硬件抽象层(Hardware Abstraction layer),说实在的bug菌刚听到HAL库的时候,瞬间就把该库与windows和安卓的硬件抽象层放到同一水平线上,瞬间就把ST给拔高了,然而当我打开HAL库细细品味的时候,竟无语凝噎。
不过不管怎样,既然推出了新东西就有其存在的理由吧,比如更进一步的让开发者更少的关注底层硬件,加快产品的功能逻辑开发,降低MCU开发的门槛等等。那么下面我们一起看一下HAL库大体做了一些什么工作?
在前面的标准库中提到过,所有的API接口都是围绕着外设寄存器基地址,而对于HAL其围绕的是Handle,即一个外设句柄。(如下图所示)
那我们来看看这个Handle结构体到底有一些什么内容 ? (截图有点大,直接copy代码)
/**
* @brief UART handle Structure definition
*/
typedef struct __UART_HandleTypeDef
{
USART_TypeDef *Instance; /*!< UART registers base address */
UART_InitTypeDef Init; /*!< UART communication parameters */
uint8_t *pTxBuffPtr; /*!< Pointer to UART Tx transfer Buffer */
uint16_t TxXferSize; /*!< UART Tx Transfer size */
__IO uint16_t TxXferCount; /*!< UART Tx Transfer Counter */
uint8_t *pRxBuffPtr; /*!< Pointer to UART Rx transfer Buffer */
uint16_t RxXferSize; /*!< UART Rx Transfer size */
__IO uint16_t RxXferCount; /*!< UART Rx Transfer Counter */
DMA_HandleTypeDef *hdmatx; /*!< UART Tx DMA Handle parameters */
DMA_HandleTypeDef *hdmarx; /*!< UART Rx DMA Handle parameters */
HAL_LockTypeDef Lock; /*!< Locking object */
__IO HAL_UART_StateTypeDef gState; /*!< UART state information related to global Handle management
and also related to Tx operations.
This parameter can be a value of @ref HAL_UART_StateTypeDef */
__IO HAL_UART_StateTypeDef RxState; /*!< UART state information related to Rx operations.
This parameter can be a value of @ref HAL_UART_StateTypeDef */
__IO uint32_t ErrorCode; /*!< UART Error code */
#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
void (* TxHalfCpltCallback)(struct __UART_HandleTypeDef *huart); /*!< UART Tx Half Complete Callback */
void (* TxCpltCallback)(struct __UART_HandleTypeDef *huart); /*!< UART Tx Complete Callback */
void (* RxHalfCpltCallback)(struct __UART_HandleTypeDef *huart); /*!< UART Rx Half Complete Callback */
void (* RxCpltCallback)(struct __UART_HandleTypeDef *huart); /*!< UART Rx Complete Callback */
void (* ErrorCallback)(struct __UART_HandleTypeDef *huart); /*!< UART Error Callback */
void (* AbortCpltCallback)(struct __UART_HandleTypeDef *huart); /*!< UART Abort Complete Callback */
void (* AbortTransmitCpltCallback)(struct __UART_HandleTypeDef *huart); /*!< UART Abort Transmit Complete Callback */
void (* AbortReceiveCpltCallback)(struct __UART_HandleTypeDef *huart); /*!< UART Abort Receive Complete Callback */
void (* WakeupCallback)(struct __UART_HandleTypeDef *huart); /*!< UART Wakeup Callback */
void (* MspInitCallback)(struct __UART_HandleTypeDef *huart); /*!< UART Msp Init callback */
void (* MspDeInitCallback)(struct __UART_HandleTypeDef *huart); /*!< UART Msp DeInit callback */
#endif /* USE_HAL_UART_REGISTER_CALLBACKS */
} UART_HandleTypeDef;
从UART_HandleTypedef结构体里面的内容来看,其中不仅仅包括了UART_Typedef寄存器基地址数据结构,同时还包含通信过程的数据结构,比如缓存区指针及大小等,以及各种初始化和中断回调函数等等用户型数据。
那么HAL库所实现的API不再是简单的像标准库那样封装寄存器了,而是实现了一个外设对象的数据结构封装,这样对于所有的处理只需要传入这个handle对象句柄即可对该对象进行所有操作。(可模仿学习!)
大家使用过HAL库都知道每个handle句柄结构体中都会存在MSP两个回调函数指针,如下图所示 :
这算是HAL库设计不错的地方,MSP(MCU Specific Package)表示单片机的具体方案,是MCU相关的初始化处理。
在早期使用标准库学习stm32的过程中,很多小伙伴都会有这样的体会,经常性的忘记初始化引脚、初始化时钟、处理中断优先级等等错误,那么现在HAL库通过这两个回调函数在初始化过程中用来处理与当前外设功能相关性并不大的硬件配置,这样一方面起到了提醒的目的,另一方面也增强了可移植性。
当然HAL库缺点也非常明显,较多的函数嵌套以及结构体索引,会导致占用更多的程序空间,并且效率上大打折扣。
对于函数指针的大量使用,虽然可以带来代码的更好的扩展和复用,但是其对于MCU的RAM占用也是一笔资源。
然而HAL库性能上的损失,从而让更多的项目趋向于使用高性能MCU。
2
到底如何选择
ST并没有强制要求一定要使用哪一种库来进行开发,所以对于库的选择还是要看个人需求,通过上面的这些描述,大家应该对这些库有了一个清晰的认识,下面谈谈bug菌的几点看法 :
1、寄存器库算是所有库的根基,对于技术研发非常规范的公司会直接去封装寄存库,根据自己本公司的编码规范从而实现自身的一套API,这样一方面兼容已开发算法等应用库,同时有利于代码风格统一。
2、对于一些已使用标准库的老项目,如果技术到位的话,可以直接根据HAL或者LL库,进行版本上的升级和优化,从而降低产品开发风险和成本。
3、对于一些新项目开发者已经非常熟悉标准库,却不想使用HAL库,但苦于标准库不再更新,可以选择LL库来进行开发。
4、对于性能要求不高,且追求开发时效性,可以使用HAL与CubeMX工具结合开发,HAL 的结构更加容易整合 STM32Cube,而 STM32CubeMX 是 ST 这几年极力推荐的程序生成开发工具,bug再多应该也会得以修复,记得关注官方勘误表!
行吧,库的选择这块bug菌就谈这么多,大家可以多尝试着使用每种库,只有真正使用过了,才知道哪一种是真正适合自己的,当然在使用库的过程中你也会学到很多。
bug菌,明白了!
赶紧把每个库玩起来,别忘了三连哦!!
3、结束语
本文主要跟大家介绍了每种库的特点和选择库的一些建议,大家也可以在下面留言处留下你的观点!
好了,这里是公众号:“最后一个bug”,一个为大家打造的技术知识提升基地,如果你喜欢交流可以添加下方bug菌微信,我拉你加入公众号技术交流群。
推荐好文 点击蓝色字体即可跳转