选择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库的程序管理方法,可以值得借鉴,除了那些不切合实际的库函数外。经得起实践检验的方法就是最好的方法,不必死板硬套。

相关推荐
计算机小手41 分钟前
推荐一个 GitHub 开源项目信息卡片生成工具,支持Docker快速部署和API调用
经验分享·docker·github·开源软件
就叫飞六吧1 小时前
普中stm32大Dap烧录流程
stm32
聪明的笨猪猪2 小时前
Java Spring “Bean” 面试清单(含超通俗生活案例与深度理解)
java·经验分享·笔记·面试
A9better2 小时前
嵌入式开发学习日志38——stm32之看门狗
stm32·嵌入式硬件·学习
小莞尔3 小时前
【51单片机】【protues仿真】基于51单片机智能路灯控制系统
c语言·stm32·单片机·嵌入式硬件·51单片机
辰哥单片机设计12 小时前
TT直流减速电机(STM32)
stm32
A9better12 小时前
嵌入式开发学习日志36——stm32之USART串口通信前述
stm32·单片机·嵌入式硬件·学习
思诺学长13 小时前
BMS(电池管理系统)的主要功能和架构简述
单片机·嵌入式硬件
czhaii13 小时前
全局不关总中断的 RTOS / CosyOS-II for STCAI MCU
单片机
qq_4017004113 小时前
STM32低功耗Tickless模式
stm32·单片机