最新项目中需要使用 STM32L476 的片子。在选择片子时,资源的多少成为了一个比较重要的考量。在斟酌一番之后,我决定采用 LL 库来实现本次的功能。下面就以 STM32L476 为例来介绍一下 LL 库。
文档
LL 库一直是与 Cube HAL 库捆绑发布的。我们可以自己从 ST 官网下载对应的 Cube 包 STM32CubeL4 ,也可以直接在 CubeMX 中下载。对应的文档也是和 HAL 库在同一个文档中。名为 UM1884:Description of STM32L4/L4+ HAL and low-layer drivers,这里就不演示如何下载了。本次我们只需要关系文档中的 LL 库相关的章节即可。
简介
LL库旨在提供快速轻巧的面向专家的层,其比 HAL 库更接近硬件。 与 HAL 相反,LL API 不是提供给优化访问不是关键功能的外围设备或需要繁重的软件配置和/或复杂的上层协议栈(例如 FSMC,USB 或 SDMMC)。
在设计上,LL 库的 API 旨在用于独立模式或与 HAL 库结合使用。不过它们不能与 HAL库同时用于相同的外设实例。如果您将 LL api 用于特定的外设实例,那么您仍然可以将 HAL api 用于其他外设实例。注意,LL api可能会覆盖一些寄存器,这些寄存器的内容被映射到 HAL 句柄中。
文件结构
LL库围绕 .h / .c 文件构建,每个受支持的外围设备一个独立的文件,外加上五个与某些系统和 Cortex 相关功能的头文件。具体如下:
除了以上这些文件以外,LL 库 和 HAL 库共享一部分文件。这部分文件位于 CMSIS 中。主要是对于 MCU 中寄存器的封装定义,如下图所示:
红色框中的文件是与 HAL 库共享的。上图中的其他几个文件原则上来说数据用户层文件,不属于库文件。下图显示了 LL 库文件的包含关系:
通常来说,其只会包含 CMSIS 中的两个文件。
移植使用
手动移植
总体来说,LL 库的手动移植与标准外设库基本一致,就时钟源配置有些区别!先来看看移植之后的最终结果图:
第一步:复制库文件
首先我们需要从 ST 给的对应的芯片软件包里提取出需要的库文件(如果你不在意项目中一堆无用的文件的话,可以不提取),具体需要的库文件如下:
当然我们只需要复制一些必须的文件,杜绝出现一堆无用的文件,全部库文件如下:
第二步:复制用户层文件
根据 ST 驱动开发架构,我们的用户层文件中必须包含几个特定的文件。由于这几个文件需要根据用户的功能而变化,因此他们并不属于库文件,而属于用户层文件。具体如下:
我们只需要从任意例子中查到这几个文件就可以,后面我们需要更加自己的需要修改里面的内容,首要的是删除里面的不需要的内容。复制到自己的用户层目录中后,我一般会根据我的程序架构,调整里面的内容,删除不需要的东西。基本我会根据我的程序架构,将这几个文件的内容全部重整。
第三步:修改配置
LL 库相比于 HAL 库 和 标准外设库,一个特点就是没有了库的配置文件。但是 CMSIS 中要求的配置以及MCU需要的配置还是必须的!
这里有个配置技巧,ST的文件中也都有说明,就是将配置项配置到自己的编译工具链中,从而避免修改各个配置文件导致移植时的麻烦。以 MDK-ARM 为例,我们可以把配置项如下配置:
其他 IDE 的配置基本类似,这里就不一一说明了。下面我们具体来说明一下 LL 库的使用需要进行哪些配置。
配置使用的芯片类型
LL 库本身支持多个系列的 MCU,我们必须要配置使用的芯片型号。这是 CMSIS 中的 stm32l4xx.h
文件中要求的。如下是未配置之前的原文件部分内容:
/** @addtogroup Library_configuration_section
* @{
*/
/**
* @brief STM32 Family
*/
#if !defined (STM32L4)
#define STM32L4
#endif /* STM32L4 */
/* Uncomment the line below according to the target STM32L4 device used in your
application
*/
#if !defined (STM32L412xx) && !defined (STM32L422xx) && \
!defined (STM32L431xx) && !defined (STM32L432xx) && !defined (STM32L433xx) && !defined (STM32L442xx) && !defined (STM32L443xx) && \
!defined (STM32L451xx) && !defined (STM32L452xx) && !defined (STM32L462xx) && \
!defined (STM32L471xx) && !defined (STM32L475xx) && !defined (STM32L476xx) && !defined (STM32L485xx) && !defined (STM32L486xx) && \
!defined (STM32L496xx) && !defined (STM32L4A6xx) && \
!defined (STM32L4P5xx) && !defined (STM32L4Q5xx) && \
!defined (STM32L4R5xx) && !defined (STM32L4R7xx) && !defined (STM32L4R9xx) && !defined (STM32L4S5xx) && !defined (STM32L4S7xx) && !defined (STM32L4S9xx)
/* #define STM32L412xx */ /*!< STM32L412xx Devices */
/* #define STM32L422xx */ /*!< STM32L422xx Devices */
/* #define STM32L431xx */ /*!< STM32L431xx Devices */
/* #define STM32L432xx */ /*!< STM32L432xx Devices */
/* #define STM32L433xx */ /*!< STM32L433xx Devices */
/* #define STM32L442xx */ /*!< STM32L442xx Devices */
/* #define STM32L443xx */ /*!< STM32L443xx Devices */
/* #define STM32L451xx */ /*!< STM32L451xx Devices */
/* #define STM32L452xx */ /*!< STM32L452xx Devices */
/* #define STM32L462xx */ /*!< STM32L462xx Devices */
/* #define STM32L471xx */ /*!< STM32L471xx Devices */
/* #define STM32L475xx */ /*!< STM32L475xx Devices */
/* #define STM32L476xx */ /*!< STM32L476xx Devices */
/* #define STM32L485xx */ /*!< STM32L485xx Devices */
/* #define STM32L486xx */ /*!< STM32L486xx Devices */
/* #define STM32L496xx */ /*!< STM32L496xx Devices */
/* #define STM32L4A6xx */ /*!< STM32L4A6xx Devices */
/* #define STM32L4P5xx */ /*!< STM32L4Q5xx Devices */
/* #define STM32L4R5xx */ /*!< STM32L4R5xx Devices */
/* #define STM32L4R7xx */ /*!< STM32L4R7xx Devices */
/* #define STM32L4R9xx */ /*!< STM32L4R9xx Devices */
/* #define STM32L4S5xx */ /*!< STM32L4S5xx Devices */
/* #define STM32L4S7xx */ /*!< STM32L4S7xx Devices */
/* #define STM32L4S9xx */ /*!< STM32L4S9xx Devices */
#endif
/* Tip: To avoid modifying this file each time you need to switch between these
devices, you can define the device in your toolchain compiler preprocessor.
*/
通常,我们是把需要的芯片类型定义到自己的编译工具链中,而不是去修改该文件!
配置时钟源
STM32 系列的 MCU 都有一个很丰富的时钟源配置功能,满足用户的各种需求。时钟的配置是用户层文件 system_stm32l4xx.c
必须的。该文件中有如下内容:
#if !defined (HSE_VALUE)
#define HSE_VALUE 8000000U /*!< Value of the External oscillator in Hz */
#endif /* HSE_VALUE */
#if !defined (MSI_VALUE)
#define MSI_VALUE 4000000U /*!< Value of the Internal oscillator in Hz*/
#endif /* MSI_VALUE */
#if !defined (HSI_VALUE)
#define HSI_VALUE 16000000U /*!< Value of the Internal oscillator in Hz*/
#endif /* HSI_VALUE */
通常,我们是把使用的时钟源类型及频率定义到自己的编译工具链中,而不是去修改该文件!
选择了时钟源之后,我们必须要对 MCU 进行配置,以启动切换到选择的时钟源。通常在 main
函数的一开始就必须要先配置时钟源。在 ST 给的例子中的 main
函数中,就有配置的函数
/**
* @brief System Clock Configuration
* The system Clock is configured as follows :
* System Clock source = PLL (MSI)
* SYSCLK(Hz) = 80000000
* HCLK(Hz) = 80000000
* AHB Prescaler = 1
* APB1 Prescaler = 1
* APB2 Prescaler = 1
* MSI Frequency(Hz) = 4000000
* PLL_M = 1
* PLL_N = 40
* PLL_R = 2
* Flash Latency(WS) = 4
* @param None
* @retval None
*/
void SystemClock_Config(void)
{
/* MSI configuration and activation */
LL_FLASH_SetLatency(LL_FLASH_LATENCY_4);
LL_RCC_MSI_Enable();
while(LL_RCC_MSI_IsReady() != 1)
{
};
/* Main PLL configuration and activation */
LL_RCC_PLL_ConfigDomain_SYS(LL_RCC_PLLSOURCE_MSI, LL_RCC_PLLM_DIV_1, 40, LL_RCC_PLLR_DIV_2);
LL_RCC_PLL_Enable();
LL_RCC_PLL_EnableDomain_SYS();
while(LL_RCC_PLL_IsReady() != 1)
{
};
/* Sysclk activation on the main PLL */
LL_RCC_SetAHBPrescaler(LL_RCC_SYSCLK_DIV_1);
LL_RCC_SetSysClkSource(LL_RCC_SYS_CLKSOURCE_PLL);
while(LL_RCC_GetSysClkSource() != LL_RCC_SYS_CLKSOURCE_STATUS_PLL)
{
};
/* Set APB1 & APB2 prescaler*/
LL_RCC_SetAPB1Prescaler(LL_RCC_APB1_DIV_1);
LL_RCC_SetAPB2Prescaler(LL_RCC_APB2_DIV_1);
/* Set systick to 1ms in using frequency set to 80MHz */
/* This frequency can be calculated through LL RCC macro */
/* ex: __LL_RCC_CALC_PLLCLK_FREQ(__LL_RCC_CALC_MSI_FREQ(LL_RCC_MSIRANGESEL_RUN, LL_RCC_MSIRANGE_6),
LL_RCC_PLLM_DIV_1, 40, LL_RCC_PLLR_DIV_2)*/
LL_Init1msTick(80000000);
/* Update CMSIS variable (which can be updated also through SystemCoreClockUpdate function) */
LL_SetSystemCoreClock(80000000);
}
我们必须要根据自己的需要修改该函数!!!
注意,在函数的最后,必须要调用一下函数 void SystemCoreClockUpdate(void)
,否则,必须要手动修改 system_stm32l4xx.c
文件中的如下这些全局变量:
/** @addtogroup STM32L4xx_System_Private_Variables
* @{
*/
/* The SystemCoreClock variable is updated in three ways:
1) by calling CMSIS function SystemCoreClockUpdate()
2) by calling HAL API function HAL_RCC_GetHCLKFreq()
3) each time HAL_RCC_ClockConfig() is called to configure the system clock frequency
Note: If you use this function to configure the system clock; then there
is no need to call the 2 first functions listed above, since SystemCoreClock
variable is updated automatically.
*/
uint32_t SystemCoreClock = 4000000U;
const uint8_t AHBPrescTable[16] = {0U, 0U, 0U, 0U, 0U, 0U, 0U, 0U, 1U, 2U, 3U, 4U, 6U, 7U, 8U, 9U};
const uint8_t APBPrescTable[8] = {0U, 0U, 0U, 0U, 1U, 2U, 3U, 4U};
const uint32_t MSIRangeTable[12] = {100000U, 200000U, 400000U, 800000U, 1000000U, 2000000U, \
4000000U, 8000000U, 16000000U, 24000000U, 32000000U, 48000000U};
/**
* @}
*/
配置中断向量偏移
在使用了在线升级(IAP)时,通常我们的程序需要分为两部分,即在实际功能程序前必须有个额外的程序来处理升级。我们的实际功能程序就必须要有偏移。这个配置项也是用户层文件 system_stm32l4xx.c
必须的。该文件中有如下内容:
/************************* Miscellaneous Configuration ************************/
/*!< Uncomment the following line if you need to relocate your vector Table in
Internal SRAM. */
/* #define VECT_TAB_SRAM */
#define VECT_TAB_OFFSET 0x00 /*!< Vector Table base offset field.
This value must be a multiple of 0x200. */
/******************************************************************************/
这个项不能配置到编译工具链中,只能修改本文件!
配置 LL 库
最后,如果要完整使用 LL 库,LL 库要求必须要定义全局宏值 USE_FULL_LL_DRIVER
。
第四步:功能实现
至此,使用LL库的全部文件已经整理完成,接下来就是根据自己的功能修改代码即可!
CubeMX
现在 CubeMX 生成代码时,可以直接选择 LL 库。但是根据我之前的测试,其生成的代码比较简单,多半还需要自己再进行完善!具体如下:
参考
- Description of STM32L4L4+ HAL and low-layer drivers.pdf