LL驱动概述
低层(LL)驱动器旨在提供快速轻量级的专家导向层,它比硬件更接近硬件;
与HAL相反,LLAPI不适用于优化访问不是关键功能的外设设备,或者需要大量软件配置和/或复杂的高级堆栈(如USB)的外设;
LL驱动函数库既可以在不使用HAL驱动库下使用(独立模式),也可以和HAL驱动库一起使用(混合模式);
LL是底层驱动库,这些库完全反应硬件功能,并且LL不实现任何处理,不需要任何额外的存储器资源来保存它们的状态、计数器或数据指针,而是通过改变任何相关的外设寄存器内容来执行所有的操作;
LL库文件结构:
用于核心总线控制和外设时钟激活/停用的源文件:
- stm32f0xx_ll_bus.h
库核心配置文件(初始化函数包含在c文件中,所有其他的API包含在h文件中):
- stm32f0xx_ll_ppp.h/.c
CortexM相关寄存器操作API(包括Systick、低功耗模式):
- stm32xx_ll_cortex.h
通用API(获取设备唯一ID/电子签名、时基和延时管理系统、系统时钟配置):
- stm32xx_ll_xx_ll_utils.h/.c
系统相关操作(SYSCFG、DBGMCU、FLASH):
- stm32xx_ll_xx_ll_system.h
定义assert_param宏的模板文件,在启用运行时检查时使用:
- stm32_assert_template.h
仅当LL驱动程序以独立模式使用(不调用HALAPI)时,才需要此文件;
应将其复制到应用程序文件夹并重命名为stm32_assert.h;
可以从LL驱动文件夹中看到其搭建库文件需要包含的内容;
由于LL库是直接驱动底层寄存器来操作,因此和CMSIS驱动紧密联系,在使用LL库时需要将相应器件的CMSIS驱动文件包含进去:
#include "stm32yyxx.h"
其和CMSIS文件的关系如下图所示:
LL API和命名规则:
外设初始化功能:
LL驱动程序提供三组初始化函数,它们在stm32f0xx_ll_ppp.c文件中定义:
- LL_PPP_Init用于根据数据结构中指定的参数初始化外设主要功能;
- LL_PPP_StructInit使用复位值来重设数据结构;
- LL_PPP_DeInit解除外设初始化功能(外设寄存器恢复为默认值);
这些LL初始化函数和相关资源的定义由编译器宏来决定:USE_FULL_LL_DRIVER或者在调用LL驱动库前在头文件添加此开关;
除此之外还有可选的额外附加功能:
- LL_PPP{_CATEGORY}_Init外设初始化功能;
- LL_PPP{_CATEGORY}_StructInit重设外设功能;
- LL_PPP_CommonInit初始化同一外设的不同实例之间共享的公共功能;
- LL_PPP_CommonStructInit重设同一外设的不同实例之间共享的公共功能;
- LL_PPP_ClockInit初始化(同步)外设时钟模式;
- LL_PPP_ClockStructInit重设外设时钟寄存器值;
运行时间检测:
和HAL驱动库一样,LL初始化函数通过检查LL库函数中的输入值来实现运行时故障检测;在独立模式下使用LL库,在运行时检查需要执行以下操作:
- 将stm32_assert_template.h复制到应用程序文件夹并将其重命名为stm32_assert.h;此文件定义启用运行时检查时使用的assert_param宏;
- 在应用程序主头文件中包含stm32_assert.h文件;
- 在工具链编译器预处理器中或在stm32_assert.h驱动程序之前处理的任何通用头文件中添加USE_FULL_ASSERT编译开关;
该检查不适用LL库的内联函数(inline function);
外设寄存器配置:
除了外设初始化函数之外,LL库还提供了一组直接用于原子寄存器(保证寄存器的读写在某个时间点发生,在该点之前执行将获取寄存器的旧值,在该点之后执行将获取寄存器的新值,后面的读取不能返回旧值)的内联函数(为了解决函数调用时参数传递造成的时间开销和形参占用的内存空间,采用预编译宏来定义简单的函数,但是宏定义的本质是字符串替换因此在定义函数很容易造成调用结果的错误,而内联函数就是为了解决和替换宏而产生的,其本质是一个具有时间和内存优化的简单函数,不能在内联函数中包含循环、条件、选择等复杂的结构);
__STATIC_INLINE return_type LL_PPP_Function (PPPx_TypeDef *PPPx, args)
根据操作内容定义功能名称:
- 特定中断、DMA请求和状态标志管理:
在中断和状态寄存器中有以下Flags:Set/Get/Clear/Enable/Disable
BITNAME是指产品系列参考手册中外设寄存器的名称;
- 外设时钟的使能/失能管理:
在外设时钟中有以下Flags:Enable/Disable/Reset
'x'对应于组索引并且指的是给定总线上的修改寄存器的索引,类似于多个寄存器之间的编号;
'bus'对应于总线名称(例如APB1);
- 外设的使能/失能管理:
对于外设而言有以下Flags:Enable/disable
- 外设配置管理:
存在以下配置Flags:Set/Get
- 外设寄存器管理:
对寄存器进行读写操作的Flags:Write/Read
Propriety是一个用于标识DMA传输方向或数据寄存器类型的变量;
HAL与LL的混合模式:
对于相同的外设实例,它们不能一起使用,如果对特定的实例使用LL API,则仍然可以将HALAPI应用于其他实例,低层LL库可能会覆盖某些在HAL句柄的内容;
独立模式下使用LL驱动库:
可以在不调用HAL库的情况下使用LL库,只需要将应用程序中包含stm32f0xx_ll_ppp.h既可以完成独立使用LL库的操作;通过参考手册推荐的编程模型调用推荐的相同循序调用给定的LLAPI;LL库应使用和HAL库相同的处理方式,始终使用系统文件、启动文件和CMSIS;
当包含BSP驱动文件的时候,与BSP功能驱动程序相关联的HAL库应包含在目录下,即便它们未被应用程序使用;
混合模式下使用LL和HAL库:
低层API和HAL库混合使用,可以实现基于寄存器的直接操作;
- 应避免同时使用LL和HAL初始化同一个外设,如果是这种情况需要更新HAL PPP句柄结构PPP_HandleTypeDef下的一个或多个私有字段;
- 对于不改变句柄字段(包括初始化结构)的操作和进程,HAL驱动程序API和LL服务可以一起用于同一个外设实例;
- LL驱动库可以使用不基于任何句柄对象(RCC、common HAL、flash、GPIO)的HAL驱动API;
- 当没有使用HAL Init/DeInit并且被LL的宏取代时,InitMsp()将不被调用,这个需要在用户程序中自己实现;
- 如果未使用进程API而是通过LL API来执行相应的功能,则不会调用回调函数,并且应由用户自己来执行处理和错误管理;
- 当LL API用于进程操作时,无法调用HAL API的IRQ处理程序,而是要由用户自己来实现IRQ程序,每个LL程序中都要实现读取和清除相关中断标志所需的宏;