加载中…
个人资料
最爱蓝色的天
最爱蓝色的天
  • 博客等级:
  • 博客积分:0
  • 博客访问:611
  • 关注人气:3
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
正文 字体大小:

对HAL库的补充代码

(2019-12-19 19:05:54)
标签:

it

HAL库的补充代码

前言:     

ST官方从2017年下半年开始就不再维护升级标准库,转而推广HAL库。到2019年,HAL库仍不够成熟,其原因有以下:

1. HAL库的配套指导文档,特别是中文的使用手册文档欠缺得很厉害,除了野火在2017年上半年出了一套STM32F4HAL库教程,就几乎没有任何由ST官方协助完成的教程了。

2. HAL库全称是Hardware Abstraction Layer(抽象印象层),ST初衷是减少开发人员前期在底层驱动上所消耗的时间,这样就能很快的转到应用层代码的编写上,以达到敏捷开发的要求。

HAL库的第一个不成熟就在于,MCU芯片需要的是芯片级的驱动库,不是板卡级的驱动库,这个大方向就错了。

单片机产品的一个根本性质就是随意性大,可任意配置硬件和软件,底层的驱动和上层的应用是没有被封装剥离开的;且产品一旦成熟,就很难再有大改动。

ST的意愿是好的,但脱离了实际。他们的意愿是搞这个HAL库来协助加快单片机研发速度,设备商的产品交付得就越快,这样他们的出货量就越大。ST公司这种希望把MCU的开发,变成像手机APP那样的敏捷开发,是脱离了单片机产品的实际,这是只从自己的角度看问题。 

HAL库的第二个不成熟是它的函数相对于标准库缺少得太多,现有的这些函数的接口对于做C51这类单片机出身的单片机工程师很不友好,仅用HAL库无法把底层驱动写得具有更高的自由度。

例如下面我要补充的单独设置某一通道的PWM占空比函数,在标准库中有,但是在HAL库中就没有。另外获取指定ADC通道的AD数值的函数也没有。

更让人吐槽的是,有的函数甚至返回值只有HAL_OK,而没有HAL_ERRORHAL_BUSYHAL_TIMEOUT这些状态值,那么这些返回值只有唯一状态的函数,要这样的返回值有何意义呢?

而有的函数其实是一个宏定义的计算,其返回值是0或非0(并非FALSETRUE),而这个非0就是有很多个值了,那你到底是哪一个值呢,但前的HAL库没有对这个非0进行判断,那么就会出错,搞出歧义来。

下面来看这段代码:

 

1.      

16.#define __HAL_UART_GET_IT_SOURCE(__HANDLE__, __IT__) (((((__IT__) >> 28U) == UART_CR1_REG_INDEX)? (__HANDLE__)->Instance->CR1:(((((uint32_t)(__IT__)) >> 28U) == UART_CR2_REG_INDEX)?    

17.(__HANDLE__)->Instance->CR2 (__HANDLE__)->Instance->CR3)) (((uint32_t)(__IT__)) UART_IT_MASK))    

 

这个宏定义的函数:__HAL_UART_GET_IT_SOURCE 是用于获取串口发生中断时的中断源的,由函数名就可看出来。这个很好理解。再看它的描述:@retval The new state of __IT__ (TRUE or FALSE).  它的返回值是 __IT__ 最新状态,这个状态的解释是TRUEFALSE。于是,我们就会这样去做判断:

 

1.  if(__HAL_UART_GET_FLAG(&huart5, UART_FLAG_TXE) == TRUE && __HAL_UART_GET_IT_SOURCE(&huart5, UART_IT_TXE) == TRUE)     //TXE模式的发送中断    

2.     

3.     __HAL_UART_CLEAR_FLAG(&huart5, UART_FLAG_TXE);    

4.     

  代码一编译,就有报错说没有定义TRUE,然后心里不免会冒出一句:shirt。然后几经折腾,发现可以自己定义一个TRUEFALSE,或者这一句可以改成:

1.  if(__HAL_UART_GET_FLAG(&huart5, UART_FLAG_TXE) == SET && __HAL_UART_GET_IT_SOURCE(&huart5, UART_IT_TXE) == SET)     //TXE模式的发送中断    

2.         

3.          __HAL_UART_CLEAR_FLAG(&huart5, UART_FLAG_TXE);    

4.         

 

然后编译能通过了,但是程序一跑,你会发现明明有了发送中断,但是这里的判断就是进不去,于是又是一阵的懵逼,心里把shirt变成了fuck。再几经检查,发现  __HAL_UART_GET_IT_SOURCE(&huart5, UART_IT_TXE)  这一句的返回值实际是0x00000080,而并非SETSET的定义是1,实质是0x01)。

其实这还不算什么,如果串口同时有 TC 中断 和 LBD中断,你会发现用 __HAL_UART_GET_IT_SOURCE 去计算出来的值都是0x00000040,这就无法简单用if去判断是哪里在中断了,相信很多人都会爆粗口了。其实在标准库中也有类似的情况,但是标准库已经被大家熟悉了,一打听、一百度就能找到解决办法。而HAL库的使用文档少之又少,反倒是耽误了大家的时间。

我相信大多搞单片机的程序员对这个搞了三四年的HAL库都不屑,依据是看看各个论坛上有多少关于HAL库的帖子就能知道用HAL库的人有多少,特别是2018年后新出的HAL库,其相关帖子更是几乎没有。

而相对稳定得多的、但仍有一个重大缺陷的标准库却仍是绝大多数人的选择。我这里说的缺陷就是标准库对硬件IIC的支持很不好,有人用它解决了EEROM的读写,但是像更多的器件,比如SHT2x传感器,iAQ-core C型传感器(本人亲自用过的器件)用标准库来驱动硬件IIC就不能实现,而HAL库却可以(201911月最新版),这一点还是要给HAL库点赞。

(这里说个题外话:要想使用HAL库驱动硬件IIC,硬件电路上一定要对SCLSDA使用上拉电阻,否则出现死等。)

既然HAL库能解决,我相信升级的标准库也能解决,但是为何ST就不愿意去维护升级呢?

 

总结一下吧:

ST公司搞这个HAL库,初衷是减少开发人员前期在底层驱动上所消耗的时间,让大家能敏捷开发,增加产品的出货速度,相应的他们也会受益。但是我个人认为这是一个事与愿违的案例,单片机程序员该做什么关你ST公司什么事,你去试图替代别人的工作干什么呢,是想要我们这些做底层驱动的程序员都失业么?单片机的应用五花八门、千变万化地,是弄一个HAL库来抽象与封装就能包办的么?难道不知道写C代码的程序员本就是做这样琐碎的事情的吗?

早在201710月参加的在成都举办的那次ST公司的开发者大会时,我在会上就质疑和批评:为何标准库没有弄得尽善尽美,而又着急弄什么HAL库。当年(2013年时吧)标准库也是一大堆问题,如今好不容易完善了很多,又放弃它了,不愿百尺竿头,更进一步。说好的工匠精神呢?

当年,诺基亚公司不愿作出改变,死认着他们的塞班搞,结果他们被淘汰了,而如今,另一个想作出改变的,一心想进入移动端市场的微软,其 windows10 mobile却在前不久宣布被放弃了。所以,变与不变是需要智慧的,套用陈道明那句广告词:简约而不简单,取舍之间,彰显智慧

    吐槽完毕,上代码。

 

补充代码

    1个:按通道,调整PWM的占空比

 

1.  HAL_StatusTypeDef HAL_TIMx_SetCompare(TIM_HandleTypeDef *htim, uint32_t Channel, uint16_t Compare);    

2.      

3.  HAL_StatusTypeDef HAL_TIMx_SetCompare(TIM_HandleTypeDef *htim, uint32_t Channel, uint16_t Compare)    

4.     

5.    assert_param(IS_TIM_CCXN_INSTANCE(htim->Instance, Channel));    

6.          

7.    switch (Channel)    

8.       

9.      case TIM_CHANNEL_1:    

10.       

11.      htim->Instance->CCR1 Compare;    

12.    

13.      break   

14.       

15.    

16.    case TIM_CHANNEL_2:    

17.       

18.      htim->Instance->CCR2 Compare;    

19.    

20.      break   

21.       

22.    

23.    case TIM_CHANNEL_3:    

24.       

25.      htim->Instance->CCR3 Compare;    

26.    

27.      break   

28.       

29.        

30.    case TIM_CHANNEL_4:    

31.       

32.      htim->Instance->CCR4 Compare;    

33.    

34.      break   

35.       

36.        

37.    default   

38.      break   

39.          

40.     

41.      

42.  return HAL_OK;    

43.  

函数的声明是放在文件stm32f1xx_hal_tim_ex.h中的,

函数的定义是放在文件stm32f1xx_hal_tim_ex.c中的。

入口参数:TIM_HandleTypeDef *htim,  定时器,HAL库自带,其具体描述请见HAL库中stm32f1xx_hal_tim.h的描述。

uint32_t Channel, PWM通道,HAL库自带的宏定义中有,其具体描述请见HAL库中stm32f1xx_hal_tim.h的描述。

uint16_t Compare,占空比值,存入定时器的寄存器ccr1的值

返回值:  HAL_OK;  

具体用法:

   

1.  TIM_HandleTypeDef htim3;    

2.      

3.  HAL_TIMx_SetCompare(&htim3,TIM_CHANNEL_4,200);    

 

这个函数本人写得简单,并没有对占空比的范围做判断,返回值也只有 HAL_OK,有完善的小伙伴可自行修改。

2个:按通道,获取ADC的采样值

1.  uint16_t Get_AdcValue(ADC_HandleTypeDef* hadc,ADC_ChannelConfTypeDef* sConfig, uint32_t ADC_Channel);    

2.      

3.  uint16_t Get_AdcValue(ADC_HandleTypeDef* hadc,ADC_ChannelConfTypeDef* sConfig, uint32_t ADC_Channel)    

4.     

5.      uint16_t val;    

6.      

7.      sConfig->Channel ADC_Channel;    

8.          

9.      if(HAL_ADC_ConfigChannel(hadc,sConfig) != HAL_OK)    

10.       

11.        Error_Handler();    

12.       

13.        

14.    HAL_ADC_Start(hadc);    

15.    HAL_ADC_PollForConversion(hadc, 10);    //等待转换完成,第二个参数表示超时时间,单位ms            

16.    if(HAL_IS_BIT_SET(HAL_ADC_GetState(hadc), HAL_ADC_STATE_REG_EOC))    

17.       

18.        val=HAL_ADC_GetValue(hadc);    

19.       

20.    

21.    HAL_ADC_Stop(&hadc1);    

22.        

23.    return val;    

24. 

 

 

函数的声明可以放在stm32f1xx_hal_adc_ex.h中,也可以放在自己建立的接口层的头文件中,函数自然是放在对应的C文件中。

入口参数:TIM_HandleTypeDef *adc,  模数转换器,HAL库自带,其具体描述请见HAL库中stm32f1xx_hal_adc.h的描述。

ADC_ChannelConfTypeDef* sConfig, 指定要配置为ADC常规组的通道,HAL库自带,其具体定义方式和描述请见HAL库中stm32f1xx_hal_adc.h的描述。

uint32_t ADC_ChannelADC的通道,HAL自带,其取值和具体描述请见HAL库中stm32f1xx_hal_adc.h的描述。

返回值:  ADC采样值 

具体用法:

   

1.  ADC_HandleTypeDef hadc1;    

2.  ADC_ChannelConfTypeDef sConfigADC1 {0};    

3.      

4.  INT16U adcVal=0;  //ADC采样值    

5.      

6.  adcVal  Get_AdcValue(&hadc1, &sConfigADC1, ADC_CHANNEL_10);    

 

    3个:串口中断,仿标准库那种对串口中断的处理,以串口5举例

1.  void UART5_IRQHandler(void   

2.     

3.     uint8_t UART5Res;    

4.      

5.      HAL_UART_IRQHandler(&huart5);    

6.      

7.      if__HAL_UART_GET_FLAG(&huart5, UART_FLAG_ORE) != RESET && __HAL_UART_GET_IT_SOURCE(&huart5, UART_IT_ERR) != RESET //注意!不能使用 ( __HAL_UART_GET_FLAG(&huart5, UART_FLAG_ORE) == SET)来判断,也不能使用__HAL_UART_GET_IT_SOURCE(&huart5, UART_IT_ERR) == SET 来判断,只能是!= RESET    

8.          

9.          __HAL_UART_CLEAR_FLAG(&huart5, UART_FLAG_ORE); //清除溢出中断    

10.        UART5Res (uint8_t)(huart5.Instance->DR (uint16_t)0x01FF);    //读取出来扔掉     

11.             //User code begin    

12.             //自定义代码    

13.             //User code end    

14.       

15.    

16.    if(__HAL_UART_GET_FLAG(&huart5, UART_FLAG_RXNE) != RESET && __HAL_UART_GET_IT_SOURCE(&huart5, UART_IT_RXNE) != RESET)  //接收中断    

17.       

18.        __HAL_UART_CLEAR_FLAG(&huart5,UART_FLAG_RXNE);    

19.    

20.        UART5Res (uint8_t)(huart5.Instance->DR (uint16_t)0x01FF);    

21.            

22.             //User code begin    

23.             //自定义代码    

24.             //User code end    

25.       

26.        

27.    if(__HAL_UART_GET_FLAG(&huart5, UART_FLAG_TXE) != RESET && __HAL_UART_GET_IT_SOURCE(&huart5, UART_IT_TXE) != RESET)     //TXE模式的发送中断    

28.       

29.        __HAL_UART_CLEAR_FLAG(&huart5, UART_FLAG_TXE);    

30.             //User code begin    

31.             //自定义代码    

32.             //User code end    

33.       

34.    else if__HAL_UART_GET_FLAG(&huart5, UART_FLAG_TC) != RESET && __HAL_UART_GET_IT_SOURCE(&huart5, UART_IT_TC) != RESET    //判断是否是TC发送中断    

35.       

36.        __HAL_UART_CLEAR_FLAG(&huart5, UART_FLAG_TC);    

37.             //User code begin    

38.             //自定义代码    

39.             //User code end    

40.       

41.   

 

  函数void UART5_IRQHandler(void)是HAL库自带的,直接调用就OK了。这个函数是改写,不是新增的,它仿照的是正点原子和野火库标准库教程中的那种用法,学过的人一看就明白。

 这里有有一个特别、特别、特别要注意的地方:不能使用  __HAL_UART_GET_IT_SOURCE(&huart5, UART_IT_RXNE) == SET 来判断,只能是: != RESET

   用SET和RESET这个梗,是连正点原子和野火的标准库教程中都没有提到的,过去标准库的USART_GetITStatus(UART5, USART_IT_RXNE) != RESET,也是用!= RESET 来判断,现在HAL库也只能这样 __HAL_UART_GET_IT_SOURCE(&huart5, UART_IT_TXE) != RESET 来判断。

    因为 __HAL_UART_GET_IT_SOURCE 和 __HAL_UART_GET_FLAG  的返回值是 0 或 非0(0x00000080,0x00000040 之类的),用SET去判断会出错(SET在内存中实际的值是0x01)。

Get_AdcValue(ADC_HandleTypeDef* hadc,ADC_ChannelConfTypeDef* sConfig, uint32_t ADC_Channel);  

 

Get_AdcValue(ADC_HandleTypeDef* hadc,ADC_ChannelConfTypeDef* sConfig, uint32_t ADC_Channel);  

0

阅读 收藏 喜欢 打印举报/Report
  

新浪BLOG意见反馈留言板 欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 产品答疑

新浪公司 版权所有