STM32的四种库:STM32Snippets、Standard Peripheral Library、STM32Cube LL、STM32Cube HAL。
1STM32Snippets什么是STM32Snippets?STM32Snippets可翻译为“代码片段”、“裁剪”,其实他就是我们常说的“寄存器”开发STM32的底层驱动代码。比如配置ADC引脚的代码片段:
__INLINE void ConfigureGPIOforADC(void)
{
/* (1) Enable the peripheral clock of GPIOA, GPIOB and GPIOC */
/* (2) Select analog mode for PA1 */
/* (3) Select analog mode for PB1 */
/* (4) Select analog mode for PC0 */
RCC->AHBENR |= RCC_AHBENR_GPIOAEN | RCC_AHBENR_GPIOBEN | RCC_AHBENR_GPIOCEN; /* (1) */
GPIOA->MODER |= GPIO_MODER_MODER1; /* (2) */
GPIOB->MODER |= GPIO_MODER_MODER1; /* (3) */
GPIOC->MODER |= GPIO_MODER_MODER0; /* (4) */
}
STM32Snippets是 高度优化的示例代码集合 ,使用符合CMSIS的直接寄存器访问来减少代码开销,从而在各种应用程序中最大化STM32 MCUs的性能。
STM32Snippets主要针对底层开发人员,或者从51转过来,直接操作寄存器开发的人员。 是没有经过封装,可见底层寄存器的一套示例代码。每个STM32系列的100多个片段演示了如何以最小的内存占用有效地使用STM32外围设备。
STM32Snippets目前官方只提供: STM32F0和L0的示例代码包 。
提供的示例代码有点类似标准外设库风格,给大家看下F0的代码包:
同样也提供Keil MDK、 IAR EWARM的工程。
2Standard Peripheral Library
Standard Peripheral Library:简写SPL,也叫标准外设库 。
相信学习过STM32的朋友,对标准外设库都不陌生,是一组外围设备的C语言代码集合。 (因为现在ST官方主推STM32CubeMX,所以停止了对SPL的更新) 标准外设库是在寄存器的基础上进行了一次简单封装,主要是面向过程的嵌入式系统开发人员。
目前标准外设库 支持STM32F0、F1、F2、F3、F4、L1 ,不支持F7、H7、 MP1、L0、L4、L5、G0、G4等后面推出的系列。所以,不要再问: 在哪里下载L0的标准外设库了 。
3STM32Cube LL 和 HAL
LL:**** Low-Layer,底层库
HAL: Hardware Abstraction Layer,硬件抽象层库
STM32Cube HAL和LL配合STM32CubeMX工具对STM32进行开发。也是目前ST官方主推的一套开发STM32的库。HAL针对的是具有一定嵌入式基础的开发人员,HAL具有很好的移植性。LL库相对HAL,具有简单的结构,针对之前从事SPL(标准外设库),或寄存器开发的人员。
STM32Cube HAL和LL是目前官方主推,并重点维护和更新的库,也建议大家都学习一下。而且新出来的型号(如L5、G4等系列)没有标准外设库,只有STM32Cube HAL和LL库了。
4四种库对比
来自官方的对比信息,包含可移植性、优化、难易程度等。
其中: Portability: 可移植性; Optimization : 优化; Easy: 难易程度; Hardware coverage: 硬件覆盖。
定位:
支持器件:
最近新增了STM32Cube MP1,官方没有统计上。
库之间的转移:
STM32标准库和HAL库有什么不同?
网上关于标准库、HAL库的描述相信是数不胜数。可是一个对于很多刚入门的朋友还是没法很直观的去真正了解这些不同开发发方式彼此之间的区别,下面以一种非常直白的方式,用自己的理解去将这些东西表述出来。
一、配置寄存器
不少先学了51的朋友可能会知道,会有一小部分人或是教程是通过汇编语言直接操作寄存器实现功能的,这种方法到了STM32就变得不太容易行得通了。因为STM32的寄存器数量是51单片机的十数倍,如此多的寄存器根本无法全部记忆,开发时需要经常的翻查芯片的数据手册,此时直接操作寄存器就变得非常的费力了。但还是会有很小一部分人,喜欢去直接操作寄存器,因为这样更接近原理,知其然也知其所以然。
二、标准库
STM32有非常多的寄存器,而导致了开发困难,所以为此ST公司就为每款芯片都编写了一份库文件,也就是工程文件里stm32F1xx.....之类的。在这些.c .h文件中,包括一些常用量的宏定义,把一些外设也通过结构体变量封装起来,如GPIO口时钟等。所以我们只需要配置结构体变量成员就可以修改外设的配置寄存器,从而选择不同的功能。也是目前最多人使用的方式,也是学习STM32接触最多的一种开发方式。
三、HAL库
HAL库是ST公司目前主力推的开发方式,全称就是Hardware Abstraction Layer(抽象印象层)。库如其名,很抽象,一眼看上去不太容易知道其作用是什么。它的出现比标准库要晚,但其实和标准库一样,都是为了节省程序开发的时期,而且HAL库尤其的有效,如果说标准库把实现功能需要配置的寄存器集成了,那么HAL库的一些函数甚至可以做到某些特定功能的集成。也就是说,同样的功能,标准库可能要用几句话,HAL库只需用一句话就够了。并且HAL库也很好的解决了程序移植的问题,不同型号的stm32芯片它的标准库是不一样的,例如在F4上开发的程序移植到F3上是不能通用的,而使用HAL库,只要使用的是相通的外设,程序基本可以完全复制粘贴,注意是相通外设,意思也就是不能无中生有。例如F7比F3要多几个定时器,不能明明没有这个定时器却非要配置,但其实这种情况不多,绝大多数都可以直接复制粘贴。是而且使用ST公司研发的STMcube软件,可以通过图形化的配置功能,直接生成整个使用HAL库的工程文件,可以说是方便至极,但是方便的同时也造成了它执行效率的低下,在各种论坛帖子真的是被吐槽的数不胜数。
四、总结
综合上面说的,其实笔者还是强烈推荐HAL库的,理由有二:
一、 F7系列开始 ST公司就已近开始停止更新标准库,也就是F7开始包括F7已经不能用标准库了,公司对于主打HAL库的目的已经非常明显了。
二、追求更方便、追求模块化向来是世界的潮流,更方便的HAL库一定会迅速发展,低效的短板迟早会被硬件高度集成化所弥补 。
当然啦,不能只学习HAL库,底层的原理必需是要懂的,这是每个学有所成的人都公认的事实,HAL库也不是万能的,结合对底层的理解相信一定会让你的开发水准大大提高。
五、STM32 HAL库与标准库的区别
1.句柄
在STM32的标准库中,假设我们要初始化一个外设(这里以USART为例) 我们首先要初始化它们的各个寄存器。
在标准库中,这些操作都是利用固件库结构体变量+固件库Init函数实现的:
USART_InitTypeDef USART_InitStructure; USART_InitStructure.USART_BaudRate = bound;//串口波特率 USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式 USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位 USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位 USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制 USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收发模式 USART_Init(USART3, &USART_InitStructure); //初始化串口1可以看到,要初始化一个串口,需要对六个位置进行赋值,然后引用Init函数,并且USART_InitStructure并不是一个全局结构体变量,而是只在函数内部的局部变量,初始化完成之后,USART_InitStructure就失去了作用。而在HAL库中,同样是USART初始化结构体变量,我们要定义为全局变量。
UART_HandleTypeDef UART1_Handler;右键查看结构体成员:
typedef struct { 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 */ uint16_t TxXferCount; /*!< UART Tx Transfer Counter */ uint8_t *pRxBuffPtr; /*!< Pointer to UART Rx transfer Buffer */ uint16_t RxXferSize; /*!< UART Rx Transfer size */ 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 State; /*!< UART communication state */ __IO uint32_t ErrorCode; /*!< UART Error code */ }UART_HandleTypeDef;我们发现,与标准库不同的是,该成员不仅包含了之前标准库就有的六个成员(波特率,数据格式等),还包含过采样、(发送或接收的)数据缓存、数据指针、串口 DMA 相关的变量、各种标志位等等要在整个项目流程中都要设置的各个成员。该UART1_Handler就被称为串口的句柄 它被贯穿整个USART收发的流程,比如开启中断:
HAL_UART_Receive_IT(&UART1_Handler, (u8 *)aRxBuffer, RXBUFFERSIZE);比如后面要讲到的MSP与Callback回调函数:
void HAL_UART_MspInit(UART_HandleTypeDef *huart); void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);在这些函数中,只需要调用初始化时定义的句柄UART1_Handler就好。
2.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函数又可以配合句柄,达到非常强的移植性:
void HAL_UART_MspInit(UART_HandleTypeDef *huart);
3.Callback函数
类似于MSP函数,个人认为Callback函数主要帮助用户应用层的代码编写。还是以USART为例,在标准库中,串口中断了以后,我们要先在中断中判断是否是接收中断,然后读出数据,顺便清除中断标志位,然后再是对数据的处理,这样如果我们在一个中断函数中写这么多代码,就会显得很混乱:
void USART3_IRQHandler(void) //串口1中断服务程序 { u8 Res; if(USART_GetITStatus(USART3, USART_IT_RXNE) != RESET) //接收中断(接收到的数据必须是0x0d 0x0a结尾) { Res =USART_ReceiveData(USART3); //读取接收到的数据 /*数据处理区*/ } } 而在HAL库中,进入串口中断后,直接由HAL库中断函数进行托管:
void USART1_IRQHandler(void) { HAL_UART_IRQHandler(&UART1_Handler); //调用HAL库中断处理公用函数 /***************省略无关代码****************/ }
HAL_UART_IRQHandler这个函数完成了判断是哪个中断(接收?发送?或者其他?),然后读出数据,保存至缓存区,顺便清除中断标志位等等操作。比如我提前设置了,串口每接收五个字节,我就要对这五个字节进行处理。在一开始我定义了一个串口接收缓存区: