大家好,我是痞子衡,是正經搞技術的痞子。今天痞子衡給大家介紹的是SDK2.0裡事務型中斷處理函式(DriverIRQHandler)的重定向注意事項。
最近有一個 i.MXRT 客戶在使用官方 SDK 外設驅動裡的中斷處理函式時遇到了程式碼重定向失效問題,客戶用得是一個 XIP Flash 工程,想把程式中斷向量表以及相關外設的驅動函式全部重定向到 RAM 中以提高系統效能,但實測發現中斷髮生時,仍然存在 Flash 訪問行為。這本來不是個大問題,因為 SDK 在設計時已經從中斷處理函式命名上就做了明確提醒,但是很多客戶並沒有意識到,今天痞子衡就來聊聊這個話題:
一、事務型驅動函式簡介
恩智浦 SDK 軟體包裡的外設驅動(HAL級)正常來說提供的 API 都是面對外設配置(init、deinit、set_feature、get_status) 的通用功能函式。此外對於通訊介面類外設,一般還會有阻塞式(blocking)的資料傳輸功能函式。以 LPUART 外設為例,其資料傳輸有以下四個 API:
// 寫入(傳送)一個 Byte 資料(需在 FIFO 沒滿的情況下)
static inline void LPUART_WriteByte(LPUART_Type *base, uint8_t data);
// 讀取(接收)一個 Byte 資料(需在 FIFO 非空的情況下)
static inline uint8_t LPUART_ReadByte(LPUART_Type *base);
// 阻塞式寫入(傳送)多個 Byte 資料
status_t LPUART_WriteBlocking(LPUART_Type *base, const uint8_t *data, size_t length);
// 阻塞式讀取(接收)多個 Byte 資料
status_t LPUART_ReadBlocking(LPUART_Type *base, uint8_t *data, size_t length);
阻塞式資料傳輸 API 本質上就是獨佔 CPU 時間進行查詢式傳輸,API 一旦呼叫,必須等到資料收發結束才會返回,這樣會導致 CPU 利用率不高,其一般不利用外設中斷。為了結合外設中斷進行高效資料傳輸(non-blocking),SDK2.0 中額外提供瞭如下事務型相關函式(僅列出了部分):
// 建立事務型資料傳輸控制代碼
void LPUART_TransferCreateHandle(LPUART_Type *base,
lpuart_handle_t *handle,
lpuart_transfer_callback_t callback,
void *userData);
// 非阻塞式寫入(傳送)多個 Byte 資料
status_t LPUART_TransferSendNonBlocking(LPUART_Type *base, lpuart_handle_t *handle, lpuart_transfer_t *xfer)
// 非阻塞式讀取(接收)多個 Byte 資料
status_t LPUART_TransferReceiveNonBlocking(LPUART_Type *base,
lpuart_handle_t *handle,
lpuart_transfer_t *xfer,
size_t *receivedBytes);
// 事務型資料傳輸中斷處理函式
void LPUART_TransferHandleIRQ(LPUART_Type *base, void *irqHandle);
非阻塞式資料傳輸 API 顯然就是結合了外設中斷來做資料傳輸,API 呼叫後填入一些配置後會立刻返回,沒有過多消耗 CPU 時間,等外設中斷髮生時再進一步處理資料。這型別 API 常常和應用設計緊相關,所以也稱為事務型函式(transactional API)。
SDK 裡並不是所有外設驅動裡包含事務性函式,這類 API 常出現在傳輸介面類外設上。對於 i.MXRT 來說,支援此類 API 的外設有:DMA、LPUART、LPSPI、LPI2C、SAI、FLEXIO、FLEXSPI、USDHC、ENET、CAN、MIPI_DSI/CSI、SPDIF、ASRC、PDM 等。
二、事務型中斷處理函式設計
這裡繼續以 LPUART 外設來具體介紹。如下 i.MXRT1011 SDK 裡提供的 8 個 LPUART 例程中有 5 個是基於事務型驅動函式的,我們就以 interrupt_transfer 的 IAR 工程為例。
開啟這個 lpuart_interrupt_transfer 工程,找到晶片啟動檔案 startup_MIMXRT1011.s,在裡面我們能找到 PUBWEAK 型的 LPUART1_IRQHandler() 函式定義,這個是大家比較常見的中斷處理函式名,其程式碼裡面就是簡單跳轉到另一個 PUBWEAK 型 LPUART1_DriverIRQHandler 函式。
在 fsl_lpuart.c/.h 驅動裡,找不到 LPUART1_IRQHandler() 定義,但是有 LPUART1_DriverIRQHandler() 定義。這意味著 SDK 驅動設計時,將預設的 LPUART1_IRQHandler() 函式重寫的權利留給了使用者,而重新設計了 LPUART1_DriverIRQHandler() 函式來存放事務性中斷處理程式碼,從而避免因使用者自己重寫中斷處理函式時發生函式名重定義而去修改 fsl_lpuart.c 驅動檔案的麻煩。
三、重定向事務型中斷處理函式
現在我們嘗試重定向 lpuart_interrupt_transfer 工程,可以按照 《IAR下程式碼重定向的三種方法》 一文裡的方法,將 fsl_lpuart.o 和 lpuart_interrupt_transfer.o 兩個目標檔案都重定向到 RAM 中,並且在 main 里加上複製 0x60002000 處開始的 1KB 中斷向量表資料到 SRAM 中並且將 SCB->VTOR 指向對應 SRAM 的程式碼(這個過程可以參考 《Cortex-M中斷向量表重定向方法》 一文)。
上述改動完成之後,編譯工程檢視 map 檔案,我們發現所有的相關程式碼都已經被連結在了 SRAM 裡,但是 LPUART1_IRQHandler() 仍然在 Flash 裡,很顯然這種情況下中斷髮生時,仍然會有 Flash 訪問行為(暫不考慮 L1-Cache 生效的情況),這就是客戶遇到的問題。
那麼如何解決這個問題?其實 SDK 已經為你考慮到了,在 fsl_common_arm.c 檔案中定義了 InstallIRQHandler() 函式(僅在 ENABLE_RAM_VECTOR_TABLE 宏存在的情況下生效),檢視其原始碼,發現作用有兩個:一、如果 SCB->VTOR 指向得不是 SRAM,那麼將中斷向量表從 Flash 複製到 SRAM 中,並且重置 VTOR;二、根據傳入引數修改 SRAM 中的某個中斷向量值。
因此 lpuart_interrupt_transfer 例程中,如果需要徹底重定向中斷處理函式,記得在 main 函式里的 LPUART_TransferCreateHandle() 函式呼叫之後加上如下一句程式碼,其作用除了重定向中斷向量表之外,還將表裡的 LPUART1 中斷向量從 LPUART1_IRQHandler() 更換為了 LPUART1_DriverIRQHandler(),這樣程式碼重定向就徹底了。
InstallIRQHandler(LPUART1_IRQn, (uint32_t)LPUART1_DriverIRQHandler);
此時再編譯工程下載執行,發現出現 hardfault,這是怎麼回事?別急,因為 InstallIRQHandler() 函式里需要用到連結檔案 MIMXRT1011xxxxx_flexspi_nor.icf 裡定義的三個 Symbol,工程選項 Linker/Configuration file symbol 裡必須新增 __ram_vector_table__=1 設定,那些 Symbol 才會真正產生重定向作用。
define symbol m_interrupts_start = 0x60002000;
define symbol m_interrupts_ram_start = 0x20000000;
define symbol __ram_vector_table_size__ = isdefinedsymbol(__ram_vector_table__) ? 0x00000400 : 0;
define exported symbol __VECTOR_TABLE = m_interrupts_start;
define exported symbol __VECTOR_RAM = isdefinedsymbol(__ram_vector_table__) ? m_interrupts_ram_start : m_interrupts_start;
define exported symbol __RAM_VECTOR_TABLE_SIZE = __ram_vector_table_size__;
至此,SDK2.0裡事務型中斷處理函式(DriverIRQHandler)的重定向注意事項痞子衡便介紹完畢了,掌聲在哪裡~~~
歡迎訂閱
文章會同時釋出到我的 部落格園主頁、CSDN主頁、知乎主頁、微信公眾號 平臺上。
微信搜尋"痞子衡嵌入式"或者掃描下面二維碼,就可以在手機上第一時間看了哦。