最新項目中需要使用 STM32L476 的片子。在選擇片子時,資源的多少成爲了一個比較重要的考量。在斟酌一番之後,我決定採用 LL 庫來實現本次的功能。下面就以 STM32L476 爲例來介紹一下 LL 庫(low-layer drivers)。下面是ST 中文官網上一篇《關於ST庫函數的代碼性能對比》的文章中對比了各種庫的性能的圖示:
關於 ST 各種庫的介紹,可以參見博文《STM32 之一 HAL庫、標準外設庫、LL庫(STM32 Embedded Software)》
文檔
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 庫的一大特點就是巧妙的運用 C 語言的靜態、內聯函數來直接操作寄存器。你會在 LL 庫 .h 文件中發現大量的類似的靜態內聯函數。也正是因此,在 LL 庫中,只有少數函數接口是放在 .c 文件中的。
所有的外設接口都是一個模板,下面以 ADC 爲例來簡單說明一下。每個 .h 文件的內容從前到後,大都可以分爲四大部分:
-
代表各寄存器位或者參數的常量值(#define),例如 ADC 通道的定義:
#define LL_ADC_CHANNEL_0 (ADC_CHANNEL_0_NUMBER | ADC_CHANNEL_0_SMP | ADC_CHANNEL_0_BITFIELD ) /*!< ADC external channel (channel connected to GPIO pin) ADCx_IN0 */
-
寄存器讀寫宏函數以及一些基本運算宏函數。其中,寄存器讀寫宏函數是所有內聯函數的基礎。
/** * @brief Write a value in ADC register * @param __INSTANCE__ ADC Instance * @param __REG__ Register to be written * @param __VALUE__ Value to be written in the register * @retval None */ #define LL_ADC_WriteReg(__INSTANCE__, __REG__, __VALUE__) WRITE_REG(__INSTANCE__->__REG__, (__VALUE__)) /** * @brief Read a value in ADC register * @param __INSTANCE__ ADC Instance * @param __REG__ Register to be read * @retval Register value */ #define LL_ADC_ReadReg(__INSTANCE__, __REG__) READ_REG(__INSTANCE__->__REG__)
-
函數參數結構體以及靜態內聯函數。LL 絕大多數函數即可都是靜態內聯函數。使用 LL 庫,我們絕大多數都是使用這些內聯函數即可。例如:
typedef struct { uint32_t Resolution; /*!< Set ADC resolution. This parameter can be a value of @ref ADC_LL_EC_RESOLUTION This feature can be modified afterwards using unitary function @ref LL_ADC_SetResolution(). */ uint32_t DataAlignment; /*!< Set ADC conversion data alignment. This parameter can be a value of @ref ADC_LL_EC_DATA_ALIGN This feature can be modified afterwards using unitary function @ref LL_ADC_SetDataAlignment(). */ uint32_t LowPowerMode; /*!< Set ADC low power mode. This parameter can be a value of @ref ADC_LL_EC_LP_MODE This feature can be modified afterwards using unitary function @ref LL_ADC_SetLowPowerMode(). */ } LL_ADC_InitTypeDef; /** * @brief Set ADC resolution. * Refer to reference manual for alignments formats * dependencies to ADC resolutions. * @note On this STM32 serie, setting of this feature is conditioned to * ADC state: * ADC must be disabled or enabled without conversion on going * on either groups regular or injected. * @rmtoll CFGR RES LL_ADC_SetResolution * @param ADCx ADC instance * @param Resolution This parameter can be one of the following values: * @arg @ref LL_ADC_RESOLUTION_12B * @arg @ref LL_ADC_RESOLUTION_10B * @arg @ref LL_ADC_RESOLUTION_8B * @arg @ref LL_ADC_RESOLUTION_6B * @retval None */ __STATIC_INLINE void LL_ADC_SetResolution(ADC_TypeDef *ADCx, uint32_t Resolution) { MODIFY_REG(ADCx->CFGR, ADC_CFGR_RES, Resolution); } /** * @brief Get ADC resolution. * Refer to reference manual for alignments formats * dependencies to ADC resolutions. * @rmtoll CFGR RES LL_ADC_GetResolution * @param ADCx ADC instance * @retval Returned value can be one of the following values: * @arg @ref LL_ADC_RESOLUTION_12B * @arg @ref LL_ADC_RESOLUTION_10B * @arg @ref LL_ADC_RESOLUTION_8B * @arg @ref LL_ADC_RESOLUTION_6B */ __STATIC_INLINE uint32_t LL_ADC_GetResolution(ADC_TypeDef *ADCx) { return (uint32_t)(READ_BIT(ADCx->CFGR, ADC_CFGR_RES)); }
-
配合 .c 的函數。這部分主要是一些函數體較長不適合作爲內聯函數的函數接口。這部分接口是封裝的一些常用的操作。例如,我們在操作 RTC 時的流程:
- 將 RTC_ISR 寄存器中的 INIT 位置 1 以進入初始化模式。在此模式下,日曆計數器將停 止工作並且其值可更新。
- 輪詢 RTC_ISR 寄存器中的 INITF 位。當 INITF 置 1 時進入初始化階段模式。大約需要 2 個 RTCCLK 時鐘週期(由於時鐘同步)。
- 要爲日曆計數器生成 1 Hz 時鐘,應首先編程 RTC_PRER 寄存器中的同步預分頻係數, 然後編程異步預分頻係數。即使只需要更改這兩個字段中之一,也必須對 RTC_PRER 寄存器執行兩次單獨的寫訪問。
- 在影子寄存器(RTC_TR 和 RTC_DR)中加載初始時間和日期值,然後通過 RTC_CR 寄存器中的 FMT 位配置時間格式(12 或 24 小時制)。
- 通過清零 INIT 位退出初始化模式。隨後,自動加載實際日曆計數器值,在 4 個 RTCCLK 時鐘週期後重新開始計數。
我們可以使用
stm32l4xx_ll_rtc.h
中的各內聯函數實現以上流程來字節實現一個寫時間或者日期的接口。同時,stm32l4xx_ll_rtc.c
ST也爲我們提供封裝好的接口ErrorStatus LL_RTC_DATE_Init(RTC_TypeDef *RTCx, uint32_t RTC_Format, LL_RTC_DateTypeDef *RTC_DateStruct)
和ErrorStatus LL_RTC_TIME_Init(RTC_TypeDef *RTCx, uint32_t RTC_Format, LL_RTC_TimeTypeDef *RTC_TimeStruct)
。至於如何取捨就看自己了!
但是需要注意:這部分接口僅僅封裝了很少的一部分! 大多數我們還是需要自己根據寄存器操作流程來封裝自己的接口!例如,讀取 RTC 時間或者日期,需要先檢查並等待 RTC_ISR 寄存器中的 RSF 位置 1 時纔可以讀取(影子寄存器模式下)。
文件結構
LL 庫圍繞 .h / .c 文件構建,每個受支持的外圍設備一個獨立的文件,外加上五個與某些系統和 Cortex 相關功能的頭文件。具體如下:
除了以上這些文件以外,LL 庫 和 HAL 庫共享一部分文件。這部分文件位於 CMSIS 中。主要是對於 MCU 中寄存器的封裝定義,如下圖所示:
紅色框中的文件是與 HAL 庫共享的。上圖中的其他幾個文件原則上來說數據用戶層文件,不屬於庫文件。下圖顯示了 LL 庫文件的包含關係:
通常來說,其只會包含 CMSIS 中的兩個文件。
移植使用
LL 庫的使用既可以獨立使用也可以和 HAL 庫混合使用。 但是統一外設不能即使用 HAL 庫,又使用 LL 庫。例如,不可使用 HAL 庫初始化外設,然後使用 LL 庫讀寫外設。配套文檔中有專門的兩個章節介紹:
手動移植
總體來說,LL 庫的手動移植與標準外設庫基本一致,就時鐘源配置有些區別!先來看看移植之後的最終結果圖:
下面我們一步一步來介紹一下如何手動移植。
第一步:複製庫文件
首先我們需要從 ST 給的對應的芯片軟件包裏提取出需要的庫文件(如果你不在意項目中一堆無用的文件的話,可以不提取),具體需要的庫文件如下:
當然我們只需要複製一些必須的文件,杜絕出現一堆無用的文件,全部庫文件如下:
第二步:複製用戶層文件
根據 ST 驅動開發架構,我們的用戶層文件中必須包含幾個特定的文件。由於這幾個文件需要根據用戶的功能而變化,因此他們並不屬於庫文件,而屬於用戶層文件。具體如下:
我們只需要從任意例子中查到這幾個文件就可以,後面我們需要更加自己的需要修改裏面的內容,首要的是刪除裏面的不需要的內容。複製到自己的用戶層目錄中後,我一般會根據我的程序架構,調整裏面的內容,刪除不需要的東西。基本我會根據我的程序架構,將這幾個文件的內容全部重整。
其中需要注意的是 stm32_assert.h
這個文件。這個文件是庫文件源碼目錄中的 stm32_assert_template.h
複製到用戶目錄中並更名的!這是 LL 庫唯一一個需要用戶處理的文件。
第三步:修改配置
LL 庫相比於 HAL 庫 和 標準外設庫,一個特點就是沒有了庫的配置文件。但是 CMSIS 中要求的配置以及 MCU 需要的配置還是必須的!
這裏有個配置技巧,ST的文件中也都有說明,就是將配置項配置到自己的編譯工具鏈中,從而避免修改各個配置文件導致移植時的麻煩。以 MDK-ARM 爲例,我們可以把配置項如下配置:
其他 IDE 的配置基本類似,這裏就不一一說明了。下面我們具體來說明一下 LL 庫的使用需要進行哪些配置。
斷言的配置
在 LL 庫中,默認使用了斷言(assert),斷言的定義位於文件 stm32_assert.h
中。如果要使用默認的斷言,則該文件要求,在自己的編譯工具鏈中定義宏值 USE_FULL_ASSERT
。注意,如果與 HAL 庫混合使用,可能會導致重定義(HAL 庫中也存在默認斷言的定義)。使用時自己注意一下!
/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef STM32_ASSERT_H
#define STM32_ASSERT_H
#ifdef __cplusplus
extern "C" {
#endif
/* Exported types ------------------------------------------------------------*/
/* Exported constants --------------------------------------------------------*/
/* Includes ------------------------------------------------------------------*/
/* Exported macro ------------------------------------------------------------*/
#ifdef USE_FULL_ASSERT
/**
* @brief The assert_param macro is used for function's parameters check.
* @param expr: If expr is false, it calls assert_failed function
* which reports the name of the source file and the source
* line number of the call that failed.
* If expr is true, it returns no value.
* @retval None
*/
#define assert_param(expr) ((expr) ? (void)0U : assert_failed((char *)__FILE__, __LINE__))
/* Exported functions ------------------------------------------------------- */
void assert_failed(char *file, uint32_t line);
#else
#define assert_param(expr) ((void)0U)
#endif /* USE_FULL_ASSERT */
#ifdef __cplusplus
}
#endif
#endif /* STM32_ASSERT_H */
配置使用的芯片類型
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 庫。但是根據我之前的測試,其生成的代碼比較簡單,多半還需要自己再進行完善!具體如下:
直接生成沒啥意思,生成的 LL 庫的代碼,仍然需要大量自己修改!
編程事項
通過 LL 庫的基本架構,我們不難發現,LL 就是通過內聯函數的形式封裝了一下對於寄存器的讀寫。用戶必須要自己掌握各外設的操作流程。例如,在 HAL 庫或者 SPL 庫中,外設的初始化都有專門的接口,用戶甚至不需要關心外設寄存器中,哪個配置項在前哪個在後。但是到了 LL 庫中,用戶必須知道外設配置項的先後操作順序。
舉個例子來具體說明一下。RTC 時間或者日期的設置流程爲:
- 將 RTC_ISR 寄存器中的 INIT 位置 1 以進入初始化模式。在此模式下,日曆計數器將停 止工作並且其值可更新。
- 輪詢 RTC_ISR 寄存器中的 INITF 位。當 INITF 置 1 時進入初始化階段模式。大約需要 2 個 RTCCLK 時鐘週期(由於時鐘同步)。
- 要爲日曆計數器生成 1 Hz 時鐘,應首先編程 RTC_PRER 寄存器中的同步預分頻係數, 然後編程異步預分頻係數。即使只需要更改這兩個字段中之一,也必須對 RTC_PRER 寄存器執行兩次單獨的寫訪問。
- 在影子寄存器(RTC_TR 和 RTC_DR)中加載初始時間和日期值,然後通過 RTC_CR 寄存器中的 FMT 位配置時間格式(12 或 24 小時制)。
- 通過清零 INIT 位退出初始化模式。隨後,自動加載實際日曆計數器值,在 4 個 RTCCLK 時鐘週期後重新開始計數。
在 HAL 庫中,寫 RTC 時間函數接口 HAL_StatusTypeDef HAL_RTC_SetTime(RTC_HandleTypeDef *hrtc, RTC_TimeTypeDef *sTime, uint32_t Format);
的實現中已經完整封裝好了以上流程,我們直接調用即可!而使用 LL 庫我們不得不自己實現以上流程(使用 stm32l4xx_ll_rtc.h
中的內聯函數來實現):
/** 設置時間
*
* @param[in] t 要設置的參數指針
* @retval 是否成功
**/
bool STM32RTC_SetTime(RTC_TIME *t)
{
if(!RTC_IS_BCD(t->ss) || RTC_BCD2DEC(t->ss) > RTC_ss_MAX
|| !RTC_IS_BCD(t->mm) || RTC_BCD2DEC(t->mm) > RTC_mm_MAX
|| !RTC_IS_BCD(t->hh) || RTC_BCD2DEC(t->hh) > RTC_hh_MAX)
{
return false;
}
LL_RTC_DisableWriteProtection(RTC);
LL_RTC_EnableInitMode(RTC);
/* 輪詢 RTC_ISR 寄存器中的 INITF 位。當 INITF 置 1 時進入初始化階段模式。大約需要 2 個 RTCCLK 時鐘週期(由於時鐘同步)。 */
while (LL_RTC_IsActiveFlag_INIT(RTC) != 1);
LL_RTC_TIME_Config(RTC, LL_RTC_TIME_FORMAT_AM_OR_24, t->hh, t->mm, t->ss);
LL_RTC_DisableInitMode(RTC);
LL_RTC_EnableWriteProtection(RTC);
return true;
}
當然,對於某些外設的常用操作,ST 也封裝了一些常用的操作接口。這些接口就是位於對應的 .c 文件中(區別於.h文件中的各內聯函數)的各接口,但是這部分接口很少,大多數還是需要我們自己來封裝需要的接口。例如以上流程,ST 就提供了一個封裝好的函數 LL_RTC_TIME_Init(RTC_TypeDef *RTCx, uint32_t RTC_Format, LL_RTC_TimeTypeDef *RTC_TimeStruct);
來直接設置時間。其他外設基本也都還是這樣。
參考
- Description of STM32L4L4+ HAL and low-layer drivers.pdf