对HAL库的补充代码
前言:
ST官方从2017年下半年开始就不再维护升级标准库,转而推广HAL库。到2019年,HAL库仍不够成熟,其原因有以下:
1.
HAL库的配套指导文档,特别是中文的使用手册文档欠缺得很厉害,除了野火在2017年上半年出了一套STM32F4的HAL库教程,就几乎没有任何由ST官方协助完成的教程了。
2. HAL库全称是Hardware
Abstraction
Layer(抽象印象层),ST初衷是减少开发人员前期在底层驱动上所消耗的时间,这样就能很快的转到应用层代码的编写上,以达到敏捷开发的要求。
而HAL库的第一个不成熟就在于,MCU芯片需要的是芯片级的驱动库,不是板卡级的驱动库,这个大方向就错了。
单片机产品的一个根本性质就是随意性大,可任意配置硬件和软件,底层的驱动和上层的应用是没有被封装剥离开的;且产品一旦成熟,就很难再有大改动。
ST的意愿是好的,但脱离了实际。他们的意愿是搞这个HAL库来协助加快单片机研发速度,设备商的产品交付得就越快,这样他们的出货量就越大。ST公司这种希望把MCU的开发,变成像手机APP那样的敏捷开发,是脱离了单片机产品的实际,这是只从自己的角度看问题。
HAL库的第二个不成熟是它的函数相对于标准库缺少得太多,现有的这些函数的接口对于做C51这类单片机出身的单片机工程师很不友好,仅用HAL库无法把底层驱动写得具有更高的自由度。
例如下面我要补充的单独设置某一通道的PWM占空比函数,在标准库中有,但是在HAL库中就没有。另外获取指定ADC通道的AD数值的函数也没有。
更让人吐槽的是,有的函数甚至返回值只有HAL_OK,而没有HAL_ERROR,HAL_BUSY,HAL_TIMEOUT这些状态值,那么这些返回值只有唯一状态的函数,要这样的返回值有何意义呢?
而有的函数其实是一个宏定义的计算,其返回值是0或非0(并非FALSE或TRUE),而这个非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__ 最新状态,这个状态的解释是TRUE或FALSE。于是,我们就会这样去做判断:
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。然后几经折腾,发现可以自己定义一个TRUE和FALSE,或者这一句可以改成:
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,而并非SET(SET的定义是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库却可以(2019年11月最新版),这一点还是要给HAL库点赞。
(这里说个题外话:要想使用HAL库驱动硬件IIC,硬件电路上一定要对SCL、SDA使用上拉电阻,否则出现死等。)
既然HAL库能解决,我相信升级的标准库也能解决,但是为何ST就不愿意去维护升级呢?
总结一下吧:
ST公司搞这个HAL库,初衷是减少开发人员前期在底层驱动上所消耗的时间,让大家能敏捷开发,增加产品的出货速度,相应的他们也会受益。但是我个人认为这是一个事与愿违的案例,单片机程序员该做什么关你ST公司什么事,你去试图替代别人的工作干什么呢,是想要我们这些做底层驱动的程序员都失业么?单片机的应用五花八门、千变万化地,是弄一个HAL库来抽象与封装就能包办的么?难道不知道写C代码的程序员本就是做这样琐碎的事情的吗?
早在2017年10月参加的在成都举办的那次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_Channel,ADC的通道,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);
加载中,请稍候......