选择HAL库还是标准库

选择HAL库还是标准库呢?HAL库是趋势,标准库不再升级了,转HAL库是大势所趋。HAL库有优点,也有自身的不足,建议初学者还是从标准库入手。

标准库是单片机开发的基本库,它把"用寄存器实现的功能"写成一个函数或定义成宏,供用户使用。

HAL库把"有关系的功能"封装成一个函数,供用户调用,其目的是想隔绝用户直接干预寄存器操作,想法很好,但实际是行不通的,大部分是好的,另外提供了两个用户接口函数MspInit和Callback。

1、MspInit函数

HAL库写了很多个Init函数供用户调用,前提是必须要写好MspInit函数。同种功能硬件资源共用一个MspInit函数,所以所以这个MspInit函数必然就有许多分支,降低了程序的执行效率。下面举例说明:

//函数功能:使能TIMx时钟,设置中断优先级,使能TIMx中断

void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim)

{

if(htim->Instance == TIM1)//初始化TIM1

{

__HAL_RCC_TIM1_CLK_ENABLE(); //使能TIM1时钟

HAL_NVIC_SetPriority(TIM1_BRK_UP_TRG_COM_IRQn, 0, 0); //设置中断优先级

HAL_NVIC_EnableIRQ(TIM1_BRK_UP_TRG_COM_IRQn);

//使能TIM1刹车事件,更新事件,触发事件,COM事件中断

}

if(htim->Instance == TIM3)//初始化TIM3

{

__HAL_RCC_TIM3_CLK_ENABLE(); //使能TIM3时钟

HAL_NVIC_SetPriority(TIM3_IRQn, 0, 0); //设置中断优先级

HAL_NVIC_EnableIRQ(TIM3_IRQn); //使能TIM3中断

}

}

用户通过增删MspInit函数中的内容,好像也很容易。在初始化时,设备用到的时钟,引脚复用,中断优先级管理等都需要写在这里。

2、Callback函数

HAL库写了很多个IRQHandler函数供用户调用,前提是必须要写好Callback函数。同种功能硬件资源共用一个Callback函数,所以所以这个Callback函数必然就有许多分支,降低了程序的执行效率。下面举例说明:

//函数功能:在定时器更新中断时,HAL_TIM_IRQHandler()会调用函数,处理用户程序

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)

{

if(htim->Instance==TIM1)//处理定时器1

{

TIM1_LED_Toggle();

}

if(htim->Instance==TIM3)//处理定时器3

{

TIM3_LED_Toggle();

}

}

//函数功能:TIM1中断服务程序

void TIM1_BRK_UP_TRG_COM_IRQHandler(void)

{

TIM_HandleTypeDef TIM1_HandleStructure;

TIM1_HandleStructure.Instance=TIM1;

HAL_TIM_IRQHandler(&TIM1_HandleStructure);

}

//函数功能:TIM3中断服务程序

void TIM3_IRQHandler(void)

{

TIM_HandleTypeDef TIM3_HandleStructure;

TIM3_HandleStructure.Instance=TIM3;

HAL_TIM_IRQHandler(&TIM3_HandleStructure);

}

通过上面的举例,好像Callback函数也不像想象中的那么难写。但我们发现一个问题,这个IRQHandler函数传递是一个结构指针,所以必须有一个结构变量与之对应,所以在中断服务程序中申请一个局部结构变量,因为申请为全局变量确实有点浪费内存。官方给的例子基本都是全局变量,容易把人带入歧途。

3、HAL库的缺点

HAL库中的某些功能性函数,有可能不能满足需求,需要自己动手写,也是常有的事情。例如串口的函数库,有些函数脱离实际,如:

1)、串口接收函数,必须先知道接收多少个数据,才可以调用。

HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)

HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)

2)、串口发送函数,需要把UART_HandleTypeDef结构类型的变量定义为全局变量,才可以使用。这么设计,很消耗内存。

HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)

HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)

如果你不按照HAL库的套路干,就需要对某些函数和宏定义进行重构。如果我们不了解底层,就只能按照它的意思干。

还是举例说明

#define _HAL_UART_SendByte(__INSTANCE__, __DATA__) (__INSTANCE__)->DR = ( (__DATA__) & (uint16_t)0x01FF );
//将(__DATA__)写入串口发送缓冲区

#define _HAL_UART_ReceiveByte(__INSTANCE__) ( (uint16_t)( (__INSTANCE__)->DR & (uint16_t)0x01FF) )
//读串口发送缓冲区

#define _HAL_UART_GET_FLAG(__INSTANCE__, __FLAG__) ( ( (__INSTANCE__)->SR & (__FLAG__) ) == (__FLAG__) )
//读串口中断标志位
//(__FLAG__)=UART_IT_RXNE,读"串口接收寄存器为非空时产生的中断标志位"
//(__FLAG__)=UART_IT_PE,读"串口奇偶校验错误产生的中断标志位"
//(__FLAG__)=UART_IT_ERR,读"帧错误、噪音错误和溢出错误时产生的中断标志位"
//(__FLAG__)=UART_IT_TXE时,读"串口发送寄存器为空产生的中断标志位"
//(__FLAG__)=UART_IT_TC时,读"发送完成产生的中断标志位"

#define _HAL_UART_CLEAR_FLAG(__INSTANCE__, __FLAG__) ( (__INSTANCE__)->SR = ~(__FLAG__) )
//清除串口中断标志位
//(__FLAG__)=UART_IT_RXNE,清除"串口接收寄存器为非空时产生的中断标志位"
//(__FLAG__)=UART_IT_PE,清除"串口奇偶校验错误产生的中断标志位"
//(__FLAG__)=UART_IT_ERR,清除"帧错误、噪音错误和溢出错误时产生的中断标志位"
//(__FLAG__)=UART_IT_TXE时,清除"串口发送寄存器为空产生的中断标志位"
//(__FLAG__)=UART_IT_TC时,清除"发送完成产生的中断标志位"

#define _HAL_UART_ENABLE_IT(__INSTANCE__, __INTERRUPT__)   ((((__INTERRUPT__) >> 28U) == UART_CR1_REG_INDEX)? ((__INSTANCE__)->CR1 |= ((__INTERRUPT__) & UART_IT_MASK)): \
                                                           (((__INTERRUPT__) >> 28U) == UART_CR2_REG_INDEX)? ((__INSTANCE__)->CR2 |= ((__INTERRUPT__) & UART_IT_MASK)): \
                                                           ((__INSTANCE__)->CR3 |= ((__INTERRUPT__) & UART_IT_MASK)))
//设置串口中断使能位
//(__INTERRUPT__)=UART_IT_RXNE,设置"串口接收寄存器为非空"时,使其产生中断
//(__INTERRUPT__)=UART_IT_PE,设置"串口奇偶校验错误"时,使其产生中断
//(__INTERRUPT__)=UART_IT_ERR,设置"帧错误、噪音错误和溢出错误"时,使其产生中断
//(__INTERRUPT__)=UART_IT_TXE时,设置"串口发送寄存器为空"时,使其产生中断
//(__INTERRUPT__)=UART_IT_TC时,设置"串口发送寄存器发送完成"时,使其产生中断

#define _HAL_UART_DISABLE_IT(__INSTANCE__, __INTERRUPT__)  ((((__INTERRUPT__) >> 28U) == UART_CR1_REG_INDEX)? ((__INSTANCE__)->CR1 &= ~((__INTERRUPT__) & UART_IT_MASK)): \
                                                           (((__INTERRUPT__) >> 28U) == UART_CR2_REG_INDEX)? ((__INSTANCE__)->CR2 &= ~((__INTERRUPT__) & UART_IT_MASK)): \
                                                           ((__INSTANCE__)->CR3 &= ~ ((__INTERRUPT__) & UART_IT_MASK)))
//设置串口中断不使能
//(__INTERRUPT__)=UART_IT_RXNE,设置"串口接收寄存器为非空"时,不使其产生中断
//(__INTERRUPT__)=UART_IT_PE,设置"串口奇偶校验错误"时,不使其产生中断
//(__INTERRUPT__)=UART_IT_ERR,设置"帧错误、噪音错误和溢出错误"时,不使其产生中断
//(__INTERRUPT__)=UART_IT_TXE时,设置"串口发送寄存器为空"时,不使其产生中断
//(__INTERRUPT__)=UART_IT_TC时,设置"串口发送寄存器发送完成"时,不使其产生中断

//重定义fputc函数
//函数功能:发送ch的值给USART2串口

int fputc(int ch, FILE *f)

{

_HAL_UART_SendByte(USART2, (unsigned char) ch);

while( _HAL_UART_GET_FLAG(USART2,USART_SR_TC)!= SET);

//等待发送完成标志位被置1

return ch;

}

//函数功能:串口2发送一个字节

void USART2_SendByte( unsigned char ch )

{

_HAL_UART_SendByte(USART2, (unsigned char) ch);

while( _HAL_UART_GET_FLAG(USART2,USART_SR_TC)!= SET);

//等待发送完成标志位被置1

}

void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
	GPIO_InitTypeDef  GPIO_InitStructureure;
	if(huart->Instance==USART2)
	{
    __HAL_RCC_USART2_CLK_ENABLE();//使能USART2外设时钟
    __HAL_RCC_GPIOA_CLK_ENABLE(); //使能GPIOA时钟

//串口引脚映射开始/
    GPIO_InitStructureure.Pin = GPIO_PIN_0;     //选择第0脚,PA0是为USART2_TX
    GPIO_InitStructureure.Mode = GPIO_MODE_AF_PP;            //复用功能推挽模式
    GPIO_InitStructureure.Pull = GPIO_PULLUP;                //引脚上拉被激活
    GPIO_InitStructureure.Speed = GPIO_SPEED_FREQ_VERY_HIGH; //引脚速度为最高速
    GPIO_InitStructureure.Alternate = GPIO_AF9_USART2;       //将引脚复用为USART2
    HAL_GPIO_Init(GPIOA, &GPIO_InitStructureure);
	  //根据GPIO_InitStructureure结构变量指定的参数初始化GPIOA的外设寄存器
	  //将PA0初始化为USART2_TX

    GPIO_InitStructureure.Pin = GPIO_PIN_1;     //选择第1脚,PA1是USART2_RX
    GPIO_InitStructureure.Mode = GPIO_MODE_AF_PP;            //复用功能推挽模式
    GPIO_InitStructureure.Pull = GPIO_PULLUP;                //引脚上拉被激活
    GPIO_InitStructureure.Speed = GPIO_SPEED_FREQ_VERY_HIGH; //引脚速度为最高速
    GPIO_InitStructureure.Alternate = GPIO_AF9_USART2;       //将引脚复用为USART2
    HAL_GPIO_Init(GPIOA, &GPIO_InitStructureure);
	  //根据GPIO_InitStructureure结构变量指定的参数初始化GPIOA的外设寄存器
	  //将PA1初始化为USART2_RX
//串口引脚映射结束/

    HAL_NVIC_SetPriority(USART2_IRQn, 0x01, 0);
	  //设置串口2中断优先级为0x01,0无意义
	}
}

//函数功能:
//PA0是为USART2_TX,PA1是USART2_RX
//中断优先级为0x01
//波特率为115200,数字为8位,停止位为1位,无奇偶校验,允许发送和接收数据,只允许接收中断,并使能串口
void USART2_Init(uint32_t baudrate)
{
	UART_HandleTypeDef UART_HandleStructureure;

  UART_HandleStructureure.Instance          = USART2;              //接口为USART2
  UART_HandleStructureure.Init.BaudRate     = baudrate;            //波特率为115200bps
  UART_HandleStructureure.Init.WordLength   = UART_WORDLENGTH_8B;  //串口字长度为8
  UART_HandleStructureure.Init.StopBits     = UART_STOPBITS_1;     //串口停止位为1位
  UART_HandleStructureure.Init.Parity       = UART_PARITY_NONE;    //串口无需奇偶校验
  UART_HandleStructureure.Init.HwFlowCtl    = UART_HWCONTROL_NONE; //串口无硬件流程控制
  UART_HandleStructureure.Init.Mode         = UART_MODE_TX_RX;     //串口工作模式为发送和接收模式
	UART_HandleStructureure.AdvancedInit.AdvFeatureInit=UART_ADVFEATURE_NO_INIT;//不使用自动波特率
//	UART_HandleStructureure.AdvancedInit.AdvFeatureInit=UART_ADVFEATURE_AUTOBAUDRATE_INIT;//使用自动波特率配置
//	UART_HandleStructureure.AdvancedInit.AutoBaudRateEnable=UART_ADVFEATURE_AUTOBAUDRATE_ENABLE;//自动波特率使能
//	UART_HandleStructureure.AdvancedInit.AutoBaudRateMode=UART_ADVFEATURE_AUTOBAUDRATE_ONSTARTBIT;//自动波特率模式
	HAL_UART_Init(&UART_HandleStructureure);
	//根据UART_HandleStructureure型结构初始化USART2

	__HAL_UART_ENABLE_IT(&UART_HandleStructureure, UART_IT_RXNE);
	开启串口接收中断
	//串口接收数据时,使能"接收数据寄存器不为空"则产生中断(位RXNE=1)
	//Enable the UART Data Register not empty Interrupt

	__HAL_UART_DISABLE_IT(&UART_HandleStructureure, UART_IT_TXE);
	//串口发送数据时,不使能"串口发送数据寄存器为空"产生中断(位TXE=0)
	//Disable the UART Transmit Complete Interrupt

	__HAL_UART_DISABLE_IT(&UART_HandleStructureure,UART_IT_TC);
	//串口发送数据时,不使能"串口发送完成"产生中断(位TC=1)

  HAL_NVIC_EnableIRQ(USART2_IRQn);
	//使能串口2中断
	//USART2_IRQn表示中断源为串口2
}

放弃了HAL_UART_Receive_IT()之后,HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)也就废了。直接用自己的,不用Callback函数,如下:

//函数功能:串口2中断服务程序

void USART2_IRQHandler(void)

{

uint8_t RX_temp;

(void)RX_temp;//防止RX_temp不使用而产生警告

if( _HAL_UART_GET_FLAG(USART2,USART_SR_RXNE) )

{//在串口状态寄存器中,发现RXNE=1,且串口控制寄存器1允许接收数据

RX_temp =_HAL_UART_ReceiveByte(USART2);//读串口数据

_HAL_UART_CLEAR_FLAG(USART2,USART_SR_RXNE);

if(RX_temp=='1' && USART2_RX_Time_Count==0) USART2_RX_Time_Count = 1;

//如果接收到帧头为变频器地址为0x1,则启动USART2接收时间计数器

if(USART2_RX_Time_Count > 0)

{

USART2_RX_Time_Count = 1;//设置USART2接收时间计数器为1;

USART2_RX_Buffer[USART2_RX_Buffer_Load_Index] = RX_temp;//保存接收到的新数据

USART2_RX_Buffer_Load_Index++;

if(USART2_RX_Buffer_Load_Index>=USART2_RX_Buffer_Size) USART2_RX_Buffer_Load_Index=1;//防止USART2_RX_Buffer[]溢出

}

//软件先读"串口状态寄存器(USART_SR)",然后再读"串口数据寄存器USART_DR",就可以将ORE位(Overrun错误标志)清零;
//软件先读"串口状态寄存器(USART_SR)",然后再读"串口数据寄存器USART_DR",就可以将NE位(噪声错误标志)清零;
//软件先读"串口状态寄存器(USART_SR)",然后再读"串口数据寄存器USART_DR",就可以将FE位(帧错误标志)清零;
//软件先读"串口状态寄存器(USART_SR)",然后再读"串口数据寄存器USART_DR",就可以将PE位(奇偶校验值错误)清零;
//软件读"串口数据寄存器USART_DR",就可以将RXNE位清零

}

}

5、学习HAL库的方法:

1)、要了解了HAL库对程序的管理方法,这个只能靠自己去看库中的函数了。

2)、熟悉结构变量中各个成员的意义。

3)、熟悉HAL库的函数。

4)、多测试,多看高手的程序。如果刚入门,可能会被半桶水的人带偏了。例如:官方的程序,他们的思路可能不符合实际使用,但是用来测试还是可以的,我们需要把它修改实际需要的那种代码。

不建议初学者,直接学习HAL库,还是从标准库学习比较好。只要你有兴趣,学习都是一样的。

有时没办法,不能按照HAL库思路干,还是需要涉及到寄存器操作。

6、学习总结

标准库转HAL库,HAL里面的初始化函数,需要重点掌握,虽然申请的结构变量占很多空间,但是初始化完后,就可以释放了,但串口有点特殊,最好自己写。

1)、因为内存容量有限,不建议将初始化结构参数申请为全局变量。由于没有将结构参数设为全局变量,有些函数或宏需要重写,以满足实际需求。如:

读中断标志位

清除中断标志位

使能中断

不使能能中断

串口发送

串口接收

等相关的函数需要重构。

2)、带MspInit的函数,建议写在需要初始化的函数中。HAL采用同一管理,这个MspInit的函数只有一个,所以很庞大,而且还要做判断,效率极低。

3)、带CallBack的函数只有一个,由于没有食使用全局结构变量,所以不能用。而申请为全局结构变量,又很浪费内存。

4)、为了方便移植,中断不再使用同一个CallBack函数了。

7、HAL库有自己的优点:

1)、把所有的硬件驱动都写好了,我们要从中扣出有价值的函数,使编程更加灵活。

2)、初始化函数只有一个。

不管是用什么库,里面的结构参数知道怎么赋值,干啥用的,基本就上路了。

这是我个人的体会,觉得这么做,可以提高程序的执行效率,其次是节省内存,更主要的是功能模块化,易读,易移植,易交流。程序短小。

程序短小精悍,是每个程序员的追求。

芯片内存不是问题,HAL库的程序管理方法,可以值得借鉴,除了那些不切合实际的库函数外。经得起实践检验的方法就是最好的方法,不必死板硬套。

相关推荐
新晓·故知4 分钟前
<基于递归实现线索二叉树的构造及遍历算法探讨>
数据结构·经验分享·笔记·算法·链表
哲伦贼稳妥15 分钟前
一天认识一个硬件之机房地板
运维·网络·经验分享·其他
gavin_gxh1 小时前
项目管理-信息系统管理
经验分享·其他
芯橦1 小时前
【瑞昱RTL8763E】音频
单片机·嵌入式硬件·mcu·物联网·音视频·visual studio code·智能手表
夜间去看海5 小时前
基于单片机的智能浇花系统
单片机·嵌入式硬件·智能浇花
蜡笔小新星6 小时前
Python Kivy库学习路线
开发语言·网络·经验分享·python·学习
VirtuousLiu6 小时前
LM74912-Q1用作电源开关
单片机·嵌入式硬件·ti·电源设计·lm74912·电源开关
打地基的小白6 小时前
软件I2C-基于江科大源码进行的原理解析和改造升级
stm32·单片机·嵌入式硬件·通信模式·i2c
Echo_cy_6 小时前
STM32 DMA+AD多通道
stm32·单片机·嵌入式硬件
朴人6 小时前
【从零开始实现stm32无刷电机FOC】【实践】【7.2/7 完整代码编写】
stm32·单片机·嵌入式硬件·foc