大家好,我是痞子衡,是正經(jīng)搞技術的痞子。今天痞子衡給大家介紹的是瑞薩RA系列FSP固件庫里的外設驅(qū)動。
上一篇文章痞子衡帶大家快速體驗了一下瑞薩 MCU 開發(fā)三大件(開發(fā)環(huán)境e2 studio、軟件包FSP、評估板EK),其中軟件包 FSP 為何不叫更通用的 SDK,痞子衡特地留了伏筆,今天就讓我們分析一下這個 FSP 到底是什么來頭?(本篇主要分析其中外設驅(qū)動部分)
一、固件包架構對比
我們嘗試對比意法半導體、恩智浦以及瑞薩三家的固件包來看看它們的架構差異。
1.1 ST STM32Cube MCU Packages
首先來看在固件包生態(tài)上建立得比較早的意法半導體,它家固件包全稱 STM32Cube MCU Packages,從下往上一共四層(MCU硬件、BSP&HAL驅(qū)動、Middleware、App),另外 CMSIS 地位與 Milddeware 平齊,說明意法認為 CMSIS 是相對通用的中間層代碼。
其中我們主要關注 BSP 和 HAL 驅(qū)動,BSP 即板級器件(比如 Codec、各種傳感器等)相關的驅(qū)動,HAL 則是 MCU 片內(nèi)外設驅(qū)動,在意法架構里 BSP 和 HAL 是相同的層級,但其實我們知道 BSP 功能也要基于 HAL 驅(qū)動來具體實現(xiàn)。
關于這里的 HAL 驅(qū)動,有必要多展開一些,最早期的時候意法半導體主推得是標準庫(Standard Peripheral Libraries,簡稱 SPL),目前已經(jīng)不再維護更新,現(xiàn)在主推 HAL 庫(Hardware Abstraction Layer)和 LL 庫(Low-Layer),所以架構圖里 HAL 實際上是統(tǒng)指 HAL 庫和 LL 庫,三者關系簡單理解就是 SPL = HAL + LL。
底層庫文件:xxxMCU_ll_xxxPeripheral.c/h,提供的 API 主要是對于片內(nèi)外設寄存器的單一設置操作,API 命名為 LL_PERIPHERAL_xxxAction()
???????????原型示例:ErrorStatus LL_USART_Init(USART_TypeDef *USARTx, LL_USART_InitTypeDef *USART_InitStruct)
抽象層文件:xxxMCU_hal_xxxPeripheral.c/h,提供的 API 主要是對于片內(nèi)外設具體功能的綜合操作,API 命名為 HAL_PERIPHERAL_xxxFunc()
???????????原型示例:HAL_StatusTypeDef HAL_USART_Init(USART_HandleTypeDef *husart)
標準庫文件:xxxMCU_xxxPeripheral.c/h,提供的 API 同時包含上述 LL 和 HAL 功能(但是實現(xiàn)豐富度稍低),API 命名為 PERIPHERAL_xxxFunc/Action()
???????????原型示例:void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct)
1.2 NXP MCUXpresso-SDK
再來看痞子衡東家恩智浦半導體,固件包全稱 MCUXpresso-SDK,從下往上一共五層(MCU硬件、CMSIS、HAL驅(qū)動、Middleware&BSP、App),這樣的分層方式其實是 ARM 公司比較推薦的,與意法見解不同的是,這里 CMSIS 緊靠 MCU 硬件層,顯然恩智浦認為 CMSIS 也是底層基礎代碼。
恩智浦架構里 BSP 和 HAL 不在同一層,清晰地表明了 BSP 是在 HAL 基礎之上的代碼。恩智浦的 HAL 驅(qū)動比較像意法半導體的早期標準庫 SPL,但是 API 功能豐富度遠超 SPL。
抽象層文件:fsl_xxxPeripheral.c/h,提供的 API 同時包含片內(nèi)外設寄存器的單一設置操作以及外設具體功能的綜合操作,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,沒有表現(xiàn)出明顯的層次結構,但是能看出瑞薩架構里 BSP 和 HAL 不在同一層,且 BSP 在 HAL 之下。這里的 BSP 也包含了 CMSIS,顯然瑞薩認為 BSP 既包含了 MCU 內(nèi)核相關基礎硬件也包含板級器件硬件驅(qū)動。
瑞薩 HAL 驅(qū)動設計得比較有意思,不同于意法以及恩智浦,它對于外設功能抽象更為看重(也可以理解為更面向?qū)ο螅?,為此額外創(chuàng)建了一個 r_xxxModule_api.h 文件,里面定義了 API 原型,原型重點強調(diào)外設的通用功能行為,而忽略具體外設的操作細節(jié)和差異,這個我們下一節(jié)會細聊。
抽象層文件:r_xxxModule_api.h,定義統(tǒng)一的外設模塊驅(qū)動 API 原型結構體,適用于同類功能的不同外設情況(比如 UART 功能既可能是 SCI_USART 也可能是 SCI_UART 或者其它)
???????????r_xxxPeripheral.c/h,提供的?API?主要包含片內(nèi)外設具體功能的綜合操作,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里的外設驅(qū)動結構
在上篇文章示例工程 lpm_ek_ra8m1_ep 里,我們發(fā)現(xiàn)有如下 ra 文件夾,這就是 FSP 包相關的源文件,我們結合具體源文件來分析:
2.1 頭文件與啟動文件
首先在系統(tǒng)文件(頭文件與啟動文件)命名上,三家小有差異,不過差異最大的是型號頭文件里的外設寄存器定義,這和后面的 HAL 驅(qū)動里代碼實現(xiàn)息息相關。
文件類型 | 意法半導體 | 恩智浦半導體 | 瑞薩電子 |
---|---|---|---|
系列頭文件 | 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 類型存儲,而瑞薩則用聯(lián)合體(union)來存儲每個寄存器,這樣不僅能整體訪問該寄存器,還能按 bit field 訪問寄存器中的具體功能位。
除此以外,三家均為外設寄存器的單/多 bit 功能位做了 mask 和 pos 定義便于代碼做相關位操作。而為了便于對多 bit 功能位區(qū)域的賦值,恩智浦和意法還有額外定義(以達到瑞薩用 union 定義外設寄存器原型的效果)。
xxxPERIPHERAL_xxxREGISTER_xxxFunc_Msk/MASK
xxxPERIPHERAL_xxxREGISTER_xxxFunc_Pos/SHIFT
// 恩智浦額外定義了如下宏用于賦值多 bit 功能位區(qū)域
xxxPERIPHERAL_xxxREGISTER_xxxFunc()
// 意法則直接用多個宏來輔助置位多 bit 功能位區(qū)域的每一位
xxxPERIPHERAL_xxxREGISTER_xxxFunc
xxxPERIPHERAL_xxxREGISTER_xxxFunc_0
xxxPERIPHERAL_xxxREGISTER_xxxFunc_1
...
2.2 HAL驅(qū)動文件
關于 HAL 驅(qū)動本身代碼結構部分,我們主要分析三家 API 第一個形參定義即可知主要差別,其中恩智浦和意法 LL 庫均是指向外設原型結構體的指針,而意法 HAL 庫和瑞薩則是指向自定義外設控制塊的指針,前者偏底層,后者偏應用層。
。
參數(shù) | 意法半導體 | 恩智浦半導體 | 瑞薩電子 |
---|---|---|---|
第一個 | LL庫:PERIPHERAL_TypeDef * HAL庫:PERIPHERAL_HandleTypeDef * |
PERIPHERAL_Type * | module_ctrl_t * const |
前面痞子衡說了瑞薩多了一個 r_xxxModule_api.h 文件,我們就以 SCI 外設為例,其對應 r_uart_api.h 文件,該文件里定義了如下標準 API 動作集,這些動作不太像一般的外設驅(qū)動函數(shù)名(比如 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 外設實現(xiàn)的 UART 驅(qū)動函數(shù)對 uart_api_t 做了實例化,這樣上層應用可以僅調(diào)用 uart_api_t 里的接口實現(xiàn)具體功能,而不必在意這些接口具體由哪個類型的外設來實現(xiàn)的。這樣設計的好處是便于代碼跨外設(跨MCU),移植起來方便,缺點是限制了 API 豐富度,難以展現(xiàn)外設間的差異化特性。
/*?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固件庫里的外設驅(qū)動痞子衡便介紹完畢了,掌聲在哪里~~~