痞子衡嵌入式:瑞薩RA系列FSP韌體庫分析之外設驅動

痞子衡發表於2024-10-20

  大家好,我是痞子衡,是正經搞技術的痞子。今天痞子衡給大家介紹的是瑞薩RA系列FSP韌體庫裡的外設驅動

  上一篇文章 《瑞薩RA8系列高效能MCU開發初體驗》,痞子衡帶大家快速體驗了一下瑞薩 MCU 開發三大件(開發環境e² studio、軟體包FSP、評估板EK),其中軟體包 FSP 為何不叫更通用的 SDK,痞子衡特地留了伏筆,今天就讓我們分析一下這個 FSP 到底是什麼來頭?(本篇主要分析其中外設驅動部分)

一、韌體包架構對比

  我們嘗試對比意法半導體、恩智浦以及瑞薩三家的韌體包來看看它們的架構差異。

1.1 ST STM32Cube MCU Packages

  首先來看在韌體包生態上建立得比較早的意法半導體,它家韌體包全稱 STM32Cube MCU Packages,從下往上一共四層(MCU硬體、BSP&HAL驅動、Middleware、App),另外 CMSIS 地位與 Milddeware 平齊,說明意法認為 CMSIS 是相對通用的中間層程式碼。

  其中我們主要關注 BSP 和 HAL 驅動,BSP 即板級器件(比如 Codec、各種感測器等)相關的驅動,HAL 則是 MCU 片內外設驅動,在意法架構裡 BSP 和 HAL 是相同的層級,但其實我們知道 BSP 功能也要基於 HAL 驅動來具體實現。

  關於這裡的 HAL 驅動,有必要多展開一些,最早期的時候意法半導體主推得是標準庫(Standard Peripheral Libraries,簡稱 SPL),目前已經不再維護更新,現在主推 HAL 庫(Hardware Abstraction Layer)和 LL 庫(Low-Layer),所以架構圖裡 HAL 實際上是統指 HAL 庫和 LL 庫,三者關係簡單理解就是 SPL = HAL + LL。

底層庫檔案:xxxMCU_ll_xxxPeripheral.c/h,提供的 API 主要是對於片內外設暫存器的單一設定操作,API 命名為 LL_PERIPHERAL_xxxAction()
           原型示例:ErrorStatus LL_USART_Init(USART_TypeDef *USARTx, LL_USART_InitTypeDef *USART_InitStruct)
抽象層檔案:xxxMCU_hal_xxxPeripheral.c/h,提供的 API 主要是對於片內外設具體功能的綜合操作,API 命名為 HAL_PERIPHERAL_xxxFunc()
           原型示例:HAL_StatusTypeDef HAL_USART_Init(USART_HandleTypeDef *husart)
標準庫檔案:xxxMCU_xxxPeripheral.c/h,提供的 API 同時包含上述 LL 和 HAL 功能(但是實現豐富度稍低),API 命名為 PERIPHERAL_xxxFunc/Action()
           原型示例:void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct)

1.2 NXP MCUXpresso-SDK

  再來看痞子衡東家恩智浦半導體,韌體包全稱 MCUXpresso-SDK,從下往上一共五層(MCU硬體、CMSIS、HAL驅動、Middleware&BSP、App),這樣的分層方式其實是 ARM 公司比較推薦的,與意法見解不同的是,這裡 CMSIS 緊靠 MCU 硬體層,顯然恩智浦認為 CMSIS 也是底層基礎程式碼。

  恩智浦架構裡 BSP 和 HAL 不在同一層,清晰地表明瞭 BSP 是在 HAL 基礎之上的程式碼。恩智浦的 HAL 驅動比較像意法半導體的早期標準庫 SPL,但是 API 功能豐富度遠超 SPL。

抽象層檔案:fsl_xxxPeripheral.c/h,提供的 API 同時包含片內外設暫存器的單一設定操作以及外設具體功能的綜合操作,API 命名為 PERIPHERAL_xxxFunc/Action()
           原型示例:status_t LPUART_Init(LPUART_Type *base, const lpuart_config_t *config, uint32_t srcClock_Hz);

1.3 Renesas RA FSP

  最後來看瑞薩家的 FSP,沒有表現出明顯的層次結構,但是能看出瑞薩架構裡 BSP 和 HAL 不在同一層,且 BSP 在 HAL 之下。這裡的 BSP 也包含了 CMSIS,顯然瑞薩認為 BSP 既包含了 MCU 核心相關基礎硬體也包含板級器件硬體驅動。

  瑞薩 HAL 驅動設計得比較有意思,不同於意法以及恩智浦,它對於外設功能抽象更為看重(也可以理解為更物件導向),為此額外建立了一個 r_xxxModule_api.h 檔案,裡面定義了 API 原型,原型重點強調外設的通用功能行為,而忽略具體外設的操作細節和差異,這個我們下一節會細聊。

抽象層檔案:r_xxxModule_api.h,定義統一的外設模組驅動 API 原型結構體,適用於同類功能的不同外設情況(比如 UART 功能既可能是 SCI_USART 也可能是 SCI_UART 或者其它)
           r_xxxPeripheral.c/h,提供的 API 主要包含片內外設具體功能的綜合操作,API 命名為 R_PERIPHERAL_xxxFunc()
           原型示例:fsp_err_t R_SCI_B_UART_Open (uart_ctrl_t * const p_api_ctrl, uart_cfg_t const * const p_cfg)

二、FSP裡的外設驅動結構

  在上篇文章示例工程 lpm_ek_ra8m1_ep 裡,我們發現有如下 ra 資料夾,這就是 FSP 包相關的原始檔,我們結合具體原始檔來分析:

2.1 標頭檔案與啟動檔案

  首先在系統檔案(標頭檔案與啟動檔案)命名上,三家小有差異,不過差異最大的是型號標頭檔案裡的外設暫存器定義,這和後面的 HAL 驅動裡程式碼實現息息相關。

檔案型別 意法半導體 恩智浦半導體 瑞薩電子
系列標頭檔案 stm32xxxx.h fsl_device_registers.h renesas.h
型號標頭檔案 xxxMcu.h xxxMCU.h xxxMCU.h
啟動檔案 startup_xxxMcu.s startup_xxxMcu.s startup.c
初始化檔案 system_xxxMcu.c/h system_xxxMcu.c/h system.c/h

  在標頭檔案裡的外設暫存器原型定義上,意法和恩智浦是一致的,每個暫存器均用一個 uint32_t 型別儲存,而瑞薩則用聯合體(union)來儲存每個暫存器,這樣不僅能整體訪問該暫存器,還能按 bit field 訪問暫存器中的具體功能位。

  除此以外,三家均為外設暫存器的單/多 bit 功能位做了 mask 和 pos 定義便於程式碼做相關位操作。而為了便於對多 bit 功能位區域的賦值,恩智浦和意法還有額外定義(以達到瑞薩用 union 定義外設暫存器原型的效果)。

xxxPERIPHERAL_xxxREGISTER_xxxFunc_Msk/MASK
xxxPERIPHERAL_xxxREGISTER_xxxFunc_Pos/SHIFT
// 恩智浦額外定義瞭如下宏用於賦值多 bit 功能位區域
xxxPERIPHERAL_xxxREGISTER_xxxFunc()
// 意法則直接用多個宏來輔助置位多 bit 功能位區域的每一位
xxxPERIPHERAL_xxxREGISTER_xxxFunc
xxxPERIPHERAL_xxxREGISTER_xxxFunc_0
xxxPERIPHERAL_xxxREGISTER_xxxFunc_1
...

2.2 HAL驅動檔案

  關於 HAL 驅動本身程式碼結構部分,我們主要分析三家 API 第一個形參定義即可知主要差別,其中恩智浦和意法 LL 庫均是指向外設原型結構體的指標,而意法 HAL 庫和瑞薩則是指向自定義外設控制塊的指標,前者偏底層,後者偏應用層。

引數 意法半導體 恩智浦半導體 瑞薩電子
第一個 LL庫:PERIPHERAL_TypeDef *
HAL庫:PERIPHERAL_HandleTypeDef *
PERIPHERAL_Type * module_ctrl_t * const

  前面痞子衡說了瑞薩多了一個 r_xxxModule_api.h 檔案,我們就以 SCI 外設為例,其對應 r_uart_api.h 檔案,該檔案裡定義瞭如下標準 API 動作集,這些動作不太像一般的外設驅動函式名(比如 init, deinit 等),更像是應用層動作。

/** Shared Interface definition for UART */
typedef struct st_uart_api
{
    fsp_err_t (* open)(uart_ctrl_t * const p_ctrl, uart_cfg_t const * const p_cfg);
    fsp_err_t (* read)(uart_ctrl_t * const p_ctrl, uint8_t * const p_dest, uint32_t const bytes);
    fsp_err_t (* write)(uart_ctrl_t * const p_ctrl, uint8_t const * const p_src, uint32_t const bytes);
    fsp_err_t (* baudSet)(uart_ctrl_t * const p_ctrl, void const * const p_baudrate_info);
    fsp_err_t (* infoGet)(uart_ctrl_t * const p_ctrl, uart_info_t * const p_info);
    fsp_err_t (* communicationAbort)(uart_ctrl_t * const p_ctrl, uart_dir_t communication_to_abort);
    fsp_err_t (* callbackSet)(uart_ctrl_t * const p_ctrl, void (* p_callback)(uart_callback_args_t *),
                              void const * const p_context, uart_callback_args_t * const p_callback_memory);
    fsp_err_t (* close)(uart_ctrl_t * const p_ctrl);
    fsp_err_t (* readStop)(uart_ctrl_t * const p_ctrl, uint32_t * remaining_bytes);
} uart_api_t;

  而在 r_sci_b_uart.c 檔案裡,將基於 SCI 外設實現的 UART 驅動函式對 uart_api_t 做了例項化,這樣上層應用可以僅呼叫 uart_api_t 裡的介面實現具體功能,而不必在意這些介面具體由哪個型別的外設來實現的。這樣設計的好處是便於程式碼跨外設(跨MCU),移植起來方便,缺點是限制了 API 豐富度,難以展現外設間的差異化特性。

/* UART on SCI HAL API mapping for UART interface */
const uart_api_t g_uart_on_sci_b =
{
    .open               = R_SCI_B_UART_Open,
    .close              = R_SCI_B_UART_Close,
    .write              = R_SCI_B_UART_Write,
    .read               = R_SCI_B_UART_Read,
    .infoGet            = R_SCI_B_UART_InfoGet,
    .baudSet            = R_SCI_B_UART_BaudSet,
    .communicationAbort = R_SCI_B_UART_Abort,
    .callbackSet        = R_SCI_B_UART_CallbackSet,
    .readStop           = R_SCI_B_UART_ReadStop,
};

  至此,瑞薩RA系列FSP韌體庫裡的外設驅動痞子衡便介紹完畢了,掌聲在哪裡~~~

歡迎訂閱

文章會同時釋出到我的 部落格園主頁CSDN主頁知乎主頁微信公眾號 平臺上。

微信搜尋"痞子衡嵌入式"或者掃描下面二維碼,就可以在手機上第一時間看了哦。

相關文章