大家好,我是痞子衡,是正經(jīng)搞技術(shù)的痞子。今天痞子衡給大家介紹的是SDK2.0里事務(wù)型中斷處理函數(shù)(DriverIRQHandler)的重定向注意事項(xiàng)。
最近有一個(gè) i.MXRT 客戶在使用官方 SDK 外設(shè)驅(qū)動(dòng)里的中斷處理函數(shù)時(shí)遇到了代碼重定向失效問(wèn)題,客戶用得是一個(gè) XIP Flash 工程,想把程序中斷向量表以及相關(guān)外設(shè)的驅(qū)動(dòng)函數(shù)全部重定向到 RAM 中以提高系統(tǒng)性能,但實(shí)測(cè)發(fā)現(xiàn)中斷發(fā)生時(shí),仍然存在 Flash 訪問(wèn)行為。這本來(lái)不是個(gè)大問(wèn)題,因?yàn)?SDK 在設(shè)計(jì)時(shí)已經(jīng)從中斷處理函數(shù)命名上就做了明確提醒,但是很多客戶并沒(méi)有意識(shí)到,今天痞子衡就來(lái)聊聊這個(gè)話題:
一、事務(wù)型驅(qū)動(dòng)函數(shù)簡(jiǎn)介
恩智浦 SDK 軟件包里的外設(shè)驅(qū)動(dòng)(HAL級(jí))正常來(lái)說(shuō)提供的 API 都是面對(duì)外設(shè)配置(init、deinit、set_feature、get_status) 的通用功能函數(shù)。此外對(duì)于通信接口類外設(shè),一般還會(huì)有阻塞式(blocking)的數(shù)據(jù)傳輸功能函數(shù)。以 LPUART 外設(shè)為例,其數(shù)據(jù)傳輸有以下四個(gè) API:
// 寫(xiě)入(發(fā)送)一個(gè) Byte 數(shù)據(jù)(需在 FIFO 沒(méi)滿的情況下)
static inline void LPUART_WriteByte(LPUART_Type *base, uint8_t data);
// 讀取(接收)一個(gè) Byte 數(shù)據(jù)(需在 FIFO 非空的情況下)
static inline uint8_t LPUART_ReadByte(LPUART_Type *base);
// 阻塞式寫(xiě)入(發(fā)送)多個(gè) Byte 數(shù)據(jù)
status_t LPUART_WriteBlocking(LPUART_Type *base, const uint8_t *data, size_t length);
// 阻塞式讀取(接收)多個(gè) Byte 數(shù)據(jù)
status_t LPUART_ReadBlocking(LPUART_Type *base, uint8_t *data, size_t length);
阻塞式數(shù)據(jù)傳輸 API 本質(zhì)上就是獨(dú)占 CPU 時(shí)間進(jìn)行查詢式傳輸,API 一旦調(diào)用,必須等到數(shù)據(jù)收發(fā)結(jié)束才會(huì)返回,這樣會(huì)導(dǎo)致 CPU 利用率不高,其一般不利用外設(shè)中斷。為了結(jié)合外設(shè)中斷進(jìn)行高效數(shù)據(jù)傳輸(non-blocking),SDK2.0 中額外提供了如下事務(wù)型相關(guān)函數(shù)(僅列出了部分):
// 創(chuàng)建事務(wù)型數(shù)據(jù)傳輸句柄
void LPUART_TransferCreateHandle(LPUART_Type *base,
lpuart_handle_t *handle,
lpuart_transfer_callback_t callback,
void *userData);
// 非阻塞式寫(xiě)入(發(fā)送)多個(gè) Byte 數(shù)據(jù)
status_t LPUART_TransferSendNonBlocking(LPUART_Type *base, lpuart_handle_t *handle, lpuart_transfer_t *xfer)
// 非阻塞式讀取(接收)多個(gè) Byte 數(shù)據(jù)
status_t LPUART_TransferReceiveNonBlocking(LPUART_Type *base,
lpuart_handle_t *handle,
lpuart_transfer_t *xfer,
size_t *receivedBytes);
// 事務(wù)型數(shù)據(jù)傳輸中斷處理函數(shù)
void LPUART_TransferHandleIRQ(LPUART_Type *base, void *irqHandle);
非阻塞式數(shù)據(jù)傳輸 API 顯然就是結(jié)合了外設(shè)中斷來(lái)做數(shù)據(jù)傳輸,API 調(diào)用后填入一些配置后會(huì)立刻返回,沒(méi)有過(guò)多消耗 CPU 時(shí)間,等外設(shè)中斷發(fā)生時(shí)再進(jìn)一步處理數(shù)據(jù)。這類型 API 常常和應(yīng)用設(shè)計(jì)緊相關(guān),所以也稱為事務(wù)型函數(shù)(transactional API)。
SDK 里并不是所有外設(shè)驅(qū)動(dòng)里包含事務(wù)性函數(shù),這類 API 常出現(xiàn)在傳輸接口類外設(shè)上。對(duì)于 i.MXRT 來(lái)說(shuō),支持此類 API 的外設(shè)有:DMA、LPUART、LPSPI、LPI2C、SAI、FLEXIO、FLEXSPI、USDHC、ENET、CAN、MIPI_DSI/CSI、SPDIF、ASRC、PDM 等。
二、事務(wù)型中斷處理函數(shù)設(shè)計(jì)
這里繼續(xù)以 LPUART 外設(shè)來(lái)具體介紹。如下 i.MXRT1011 SDK 里提供的 8 個(gè) LPUART 例程中有 5 個(gè)是基于事務(wù)型驅(qū)動(dòng)函數(shù)的,我們就以 interrupt_transfer 的 IAR 工程為例。
打開(kāi)這個(gè) lpuart_interrupt_transfer 工程,找到芯片啟動(dòng)文件 startup_MIMXRT1011.s,在里面我們能找到 PUBWEAK 型的 LPUART1_IRQHandler() 函數(shù)定義,這個(gè)是大家比較常見(jiàn)的中斷處理函數(shù)名,其代碼里面就是簡(jiǎn)單跳轉(zhuǎn)到另一個(gè) PUBWEAK 型 LPUART1_DriverIRQHandler 函數(shù)。
在 fsl_lpuart.c/.h 驅(qū)動(dòng)里,找不到 LPUART1_IRQHandler() 定義,但是有 LPUART1_DriverIRQHandler() 定義。這意味著 SDK 驅(qū)動(dòng)設(shè)計(jì)時(shí),將默認(rèn)的 LPUART1_IRQHandler() 函數(shù)重寫(xiě)的權(quán)利留給了用戶,而重新設(shè)計(jì)了 LPUART1_DriverIRQHandler() 函數(shù)來(lái)存放事務(wù)性中斷處理代碼,從而避免因用戶自己重寫(xiě)中斷處理函數(shù)時(shí)發(fā)生函數(shù)名重定義而去修改 fsl_lpuart.c 驅(qū)動(dòng)文件的麻煩。
三、重定向事務(wù)型中斷處理函數(shù)
現(xiàn)在我們嘗試重定向 lpuart_interrupt_transfer 工程,可以按照 《IAR下代碼重定向的三種方法》 一文里的方法,將 fsl_lpuart.o 和 lpuart_interrupt_transfer.o 兩個(gè)目標(biāo)文件都重定向到 RAM 中,并且在 main 里加上拷貝 0x60002000 處開(kāi)始的 1KB 中斷向量表數(shù)據(jù)到 SRAM 中并且將 SCB->VTOR 指向?qū)?yīng) SRAM 的代碼(這個(gè)過(guò)程可以參考 《Cortex-M中斷向量表重定向方法》 一文)。
上述改動(dòng)完成之后,編譯工程查看 map 文件,我們發(fā)現(xiàn)所有的相關(guān)代碼都已經(jīng)被鏈接在了 SRAM 里,但是 LPUART1_IRQHandler() 仍然在 Flash 里,很顯然這種情況下中斷發(fā)生時(shí),仍然會(huì)有 Flash 訪問(wèn)行為(暫不考慮 L1-Cache 生效的情況),這就是客戶遇到的問(wèn)題。
那么如何解決這個(gè)問(wèn)題?其實(shí) SDK 已經(jīng)為你考慮到了,在 fsl_common_arm.c 文件中定義了 InstallIRQHandler() 函數(shù)(僅在 ENABLE_RAM_VECTOR_TABLE 宏存在的情況下生效),查看其源碼,發(fā)現(xiàn)作用有兩個(gè):一、如果 SCB->VTOR 指向得不是 SRAM,那么將中斷向量表從 Flash 拷貝到 SRAM 中,并且重置 VTOR;二、根據(jù)傳入?yún)?shù)修改 SRAM 中的某個(gè)中斷向量值。
因此 lpuart_interrupt_transfer 例程中,如果需要徹底重定向中斷處理函數(shù),記得在 main 函數(shù)里的 LPUART_TransferCreateHandle() 函數(shù)調(diào)用之后加上如下一句代碼,其作用除了重定向中斷向量表之外,還將表里的 LPUART1 中斷向量從 LPUART1_IRQHandler() 更換為了 LPUART1_DriverIRQHandler(),這樣代碼重定向就徹底了。
InstallIRQHandler(LPUART1_IRQn, (uint32_t)LPUART1_DriverIRQHandler);
此時(shí)再編譯工程下載運(yùn)行,發(fā)現(xiàn)出現(xiàn) hardfault,這是怎么回事?別急,因?yàn)?InstallIRQHandler() 函數(shù)里需要用到鏈接文件 MIMXRT1011xxxxx_flexspi_nor.icf 里定義的三個(gè) Symbol,工程選項(xiàng) Linker/Configuration file symbol 里必須添加 __ram_vector_table__=1 設(shè)置,那些 Symbol 才會(huì)真正產(chǎn)生重定向作用。
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里事務(wù)型中斷處理函數(shù)(DriverIRQHandler)的重定向注意事項(xiàng)痞子衡便介紹完畢了,掌聲在哪里~~~