STM32标准库和HAL库的对比

CMSIS文件夹:

最近看了几个开发板厂的STM32F1系列的HAL库工程代码。发现关于时钟配置单独手写了一个,对于用惯了标准库的产生一个疑问,时钟配置不都自带的文件里配置好了,非特殊甚至直接拿来用就行。为什么要单独手写了一个呢?从启动文件往下捋,发现HAL库自带的system_f1xx.c里还真有时钟配置函数,头文件也声明了。但是sym32f1xx.h没有加入…..原因未知。好吧,明白了咋回事,觉得需要先捋一捋文件的关系,HAL库文件关系不用对比了。重要是对比一下CMSIS里的文件关系。

再大概的进行一下代码接口大的对比吧。

一.外部中断:

我们看库里面,HAL库不再有类似stm32f1xx_hal_exit.c的文件。

HAL库中EXIT已经归类到GPIO类,只剩下中断回调和清理标志两个函数。

二.串口

1.HAL库进一步将协议和硬件分离。对具体MCU底层硬件相关的配置如引脚、时钟、 DMA、中断等是在HAL_UART_MspInit(UART_HandleTypeDef *huart) 函数中完成的,该函数被 HAL_UART_Init 函数所调用。与标准库不同,HAL_UART_Init 函数里__HAL_UART_ENABLE完成了串口的使能。

2. HAL_UART_Transmit函数与标准库的发送函数不一样,虽然都是死循环,但是hal里加入了长度.超时等一系列封装的代码。

三.DMA

DMA参数配置中dir方向配置参数相对标准库增加DMA_MEMORY_TO_MEMORY模式,去删掉了结构体中DMA_M2M的配置成员。

HAL库将源数据内存地址设置,目的数据内存地址设置,数据量设置,dma使能统一封装到了HAL_DMA_Start函数。HAL_UART_Transmit_DMA的函数的功能和HAL_DMA_Start函数类似,设置源数据内存地址和数据量以及DMA使能设置,最后向DMA发出TX请求。

__HAL_DMA_CLEAR_FLAG清理标志位。

四.TIM基本定时器

基本的架构思想都差不多,硬件层和用户层/协议栈分离(个人理解)。

对具体MCU底层硬件相关的配置如时钟、中断等是在HAL_TIM_Base_MspInit(TIM_HandleTypeDef* htim_base) 函数中完成的,该函数被 HAL_TIM_Base_Init函数所调用。与标准库不同,HAL库是在HAL_TIM_Base_Start_IT(&htimx);完成的使能,配置在中断模式下启动定时器。

HAL_TIMEx_MasterConfigSynchronization配置了主定时器的同步功能

sMasterConfig.MasterOutputTrigger设置主定时器的输出触发模式,输出触发信号:在主模式下向从定时器发送的同步信息(TRGO)。

TIM_MASTERSLAVEMODE_DISABLE禁用主从模式,即该定时器不会作为其他定时器的主定时器。

在STM32的HAL库中,大量使用了回调函数,提供给用户完成具体的操作,如定时器的定时中断回调函数:

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim), 使用回调函数的目的是实现库函数和应用层函数的分离。对于定时器的定时中断而言,前面的的操作,如判断中断标志,清除中断标志等,都是标准操作,所有的定时中断都是同样的处理,这些不变的处理操作就由库函数完成。但是对于发生定时后用户需要完成什么样的具体操作,库函数就无从得知了,可能是点亮一个LED灯,还可能是驱动一个蜂鸣器。这些操作需要由用户根据具体的项目需求来决定,因此库函数通过回调函数的形式由用户来决定执行什么样的操作。

HAL_TIM_PeriodElapsedCallback() 是一个回调函数,它是由 STM32 的 HAL(Hardware Abstraction Layer)库提供的,用于处理定时器的定时周期性中断事件。

当定时器的计数器达到设定的周期值(Period)并发生溢出时,就会触发定时器的定时周期性中断。当这个中断被触发时,HAL 库会自动调用 HAL_TIM_PeriodElapsedCallback() 函数,你可以在这个函数中编写你想要在定时器周期到达时执行的代码。

中断处理函数(也称为中断服务函数或 ISR)则是在特定事件(比如定时器溢出、外部信号触发等)发生时由硬件自动调用的函数。中断处理函数通常是在底层编写的,用于响应硬件中断并执行一些必要的操作。你可以自己编写中断处理函数,但在使用 HAL 库时,它会帮助你生成一些默认的中断处理函数。

区别在于:

触发条件:HAL_TIM_PeriodElapsedCallback() 在定时器周期性中断事件发生时被调用,而中断处理函数在硬件中断事件发生时被调用。

编写位置:你需要自己编写 HAL_TIM_PeriodElapsedCallback() 函数的内容,而中断处理函数可以是由 HAL 库生成的默认函数,或者你自己编写的底层中断处理函数。

用途:HAL_TIM_PeriodElapsedCallback() 通常用于周期性任务,而中断处理函数通常用于响应硬件事件,比如外部触发或定时器溢出。

综合来说,HAL_TIM_PeriodElapsedCallback() 更适合处理周期性任务,而中断处理函数更适合处理硬件事件的响应。

四.TIM高级定时器和通用定时器

除了前面在基本定时器介绍的,大体上也相差不大。

拿输入捕获功能-测量脉冲宽度举例:

HAL库里设置了定时器的时钟源。

CLKSource.ClockSource = TIM_CLOCKSOURCE_INTERNAL;

HAL_TIM_ConfigClockSource(& TIM_TimeBase, &CLKSource);

STM32手册里可以看到,定时器的基本框架里有很多的时钟源。

高级控制定时器有四个时钟源可选:

• 内部时钟源 CK_INT

• 外部时钟模式 1:外部输入引脚 TIx(x=1,2,3,4)

• 外部时钟模式 2:外部触发输入 ETR

• 内部触发输入 (ITRx)

使用HAL_TIM_ConfigClockSource()分配 ,实际上就是哪个时钟源驱动时基单元。

这里选择的是内部时钟源 CK_INT。

其实不设置也可以,因为复位值默认的就是CK_INT。HAL_TIM_ConfigClockSource中可以看到什么配置工作也没做。

HAL_TIM_IC_ConfigChannel配置输入触发参数和通道选择。

最后。

/* 启动定时器通道输入捕获并开启中断 */

HAL_TIM_IC_Start_IT(&TIM_TimeBase, TIM_CHANNEL_1);

五.I2C

这里主要看了一下硬件I2C,模拟I2c的区别不大,就不再祥述了。

我使用的开发板上通常都是接一个EEPROM的器件。

主要区别在于HAL库将协议进一步进行了封装,用户甚至不用再花很多时间去协议上设计时序等操作。

HAL中通常使用了HAL_I2C_Mem_Write或HAL_I2C_Master_Transmit来完成对i2c从设备器件的数据发送,用HAL_I2C_Mem_Read或HAL_I2C_Master_Receive来完成对i2c从设备器件的数据接收。这些函数内部已经完成了起始,地址+R/W的写入,数据的发送,停止等这些工作。

        HAL_I2C_Master_Transmit 函数用于向一个 I2C 设备发送数据。它只负责将一个数据块发送到指定的从设备地址,而不管这些数据是否会被存储在一个特定的内存地址中。        

        HAL_I2C_Master_Transmit 适用于简单的数据块传输,比如发送一个命令序列或者一组数据给一个没有内部地址空间的设备。

HAL_I2C_Mem_Write 函数则用于向一个具有内部地址空间的 I2C 设备写入数据。这个函数允许你指定数据将被写入设备的哪个内部地址,这对于那些具有多个寄存器或内存区域的设备是非常有用的。HAL_I2C_Mem_Write 更加灵活,因为它允许你指定数据的目的地址,而不仅仅是设备地址。这使得你可以直接访问设备内部的寄存器或内存区域。

HAL_I2C_Mem_Write 比 HAL_I2C_Master_Transmit 多了两个参数 MemAddress 和 MemAddSize,用于指定内部地址和地址位宽大小。

在EEPROM的一个写循环中可以写多个字节,但一次写入的字节数不能超过EEPROM页的大小,AT24C02每页有8个字节.

HAL的例程

关于硬件I2C的标准库据说是有Bug,非常不稳定,而且读取数据也有乱码,它只在特定的一小段时间内读外设的ACK信号,一旦错过了ack信号,后面所有的shu时序都乱了。

新版的hal库基本解决这种问题,可以放心使用。

六.ADC

单通道-中断方式采集:

1.标准库配置时钟用常用的RCC_APB2PeriphClockCmd和ADC函数库中的分频设置函数来完成,HAL库中封装为一个统一的HAL_RCCEx_PeriphCLKConfig来完成。

常见外设(如 UART、SPI、I2C 等):它们通常依赖于 APB1 或 APB2 的分频,通过初始化函数即可设置其工作时钟的预分频,如 HAL_UART_Init() 中的波特率配置(在这个函数里进行分频)。

特殊外设(如 ADC、RTC):使用 HAL_RCCEx_PeriphCLKConfig,因为它们的时钟源可能来自不同的时钟树分支,需要单独的配置函数来灵活选择时钟源和分频(在这个函数里进行分频)。

ADC 的时钟确实可以来自 APB2 总线,但它并不完全依赖于 APB2 的时钟,因为 STM32 提供了一些灵活的配置选项,允许在 APB2 时钟的基础上通过分频或从不同的时钟源选择适合的频率,这样的设计可以满足 ADC 的特殊需求。这就需要用到HAL_RCCEx_PeriphCLKConfig。

2.我们在HAL库中看见相对于标准库,配置结构中又新增了DiscontinuousConvMode禁止不连续采样模式和NbrOfDiscConversion不连续采样通道数。查阅网上的资料获悉这不是很简单的配置,大有用处。实际上这两个参数是为了配置出更多的模式。

        ①都为DISABLE时,单次转换,触发一次,转换一个序列(转换所有被选通道),转换结束后停止。

        ②ContinuousConvMode为ENABLE时触发一次,转换一个序列(转换所有被选通道),转换结束后自动重新开始。

        ③ContinuousConvMode为DISABLE,DiscontinuousConvMode为ENABLE,触发一次转换一个通道,再触发,再转换。在所选通道上循环。

3.标准库里在ADC结构体里就有关于模式的配置,是ADC_Mode_Independent独立模式还是规则同步模式等模式。HAL库中均单独拿出来在HAL_ADCEx_MultiModeConfigChannel中来配置。

4.与标准库的ADC_RegularChannelConfig函数不同,HAL库使用了HAL_ADC_ConfigChannel传递了一个配置结构体的参数来完成对通道,序列位置,采样时间的配置。

5.其它的不同之处就是之前提及的,HAL用callback回调来设计中断服务程序的框架。

多通道-DMA方式采集

基本上和单通道的方式相似,ScanConvMode设置为ENABLE。

依次调用HAL_ADC_ConfigChannel配置通道,序列位置,采样时间。

HAL_ADC_Start_DMA(&ADC_Handle, (uint32_t*)&ADC_ConvertedValue, 5);启动DMA开始读取。

双重ADC同步规则模式采集

与单ADC采集类似,需要配置ADC2,基本上的配置也都相似。

不过这里还需要调用HAL_ADCEx_MultiModeConfigChannel配置同步规则模式。

还有一个地方值得注意,标准库里调用了ADC_ExternalTrigConvCmd(ADC2, ENABLE); 使能ADCx_2的外部触发转换,在HAL库里没看到相似的函数调用,哪去了? 

仔细看初始化配置后的代码,有一句HAL_ADC_Start(&hadcx2);,那会不会是在这个函数里配置了,还真是这样,进入这个函数,有这么一句

进入ADC_NONMULTIMODE_OR_MULTIMODEMASTER看看都做什么判断了

基本意思就是如果是启动的主ADC,则启动并开始转换;如果启动是从ADC(也就是咱们的ADC2),并且不是独立模式,则使能该ADC的外部触发。从这可以看见HAL库对代码的封装设计。

作者水平有限,如有书写错误之处感谢纠正!

持续更新中。。。。

参考:

1.STM32 HAL 库中的 HAL_I2C_Master_Transmit 与 HAL_I2C_Mem_Write 区别详解

2.关于stm32的iic为什么不稳定的讨论

3.HAL_RCCEx_PeriphCLKConfig函数配置ADC时钟

4.STM32 ContinuousConvMode和DiscontinuousConvMode的组合方式

5.[STM32F103]使用ADC的注入组和规则组接收四个模块的输入

6.STM32 ADC规则组和注入组配置方法

7.ARM STM32F中ADC中 规则通道 注入通道 序列寄存器(SQR JSQR)的使用方法

作者:Will·Jason

物联沃分享整理
物联沃-IOTWORD物联网 » STM32标准库和HAL库的对比

发表回复