韓雨泓,陳良勇
(1.四川大學(xué) 計算機(jī)學(xué)院,四川 成都 610000;2.原成都軍區(qū)聯(lián)勤部后勤信息中心,四川 成都 610000)
在實際的生產(chǎn)工作中,從經(jīng)濟(jì)和實用性方面考慮,選擇嵌入式系統(tǒng)MCU控制器時常常會使用到一些性能相對較弱,功能配置也不太強(qiáng)大的單片機(jī),這類單片機(jī)有時候僅僅夠用,無法從根本上滿足多元化的需求。如果需要更多的功能就需要選擇更高端的單片機(jī),同時也意味著成本的增加。本文在項目過程中選用的MCU即為這種類型。在產(chǎn)品的整個生命周期中,需要對應(yīng)用程序進(jìn)行及時的升級換代,這就用到了IAP功能[1-2],而該款MCU不具備硬件級的中斷向量重定位功能,因此只能另辟蹊徑,以軟件的方式解決該問題。
在傳統(tǒng)的單片機(jī)應(yīng)用開發(fā)中,程序員將寫好的代碼通過IDE編譯鏈接后生成可執(zhí)行的二進(jìn)制文件下載到單片機(jī)Flash中即可[3],功能的修改或者錯誤的修正需要程序員在完成代碼修改后,再次通過專用調(diào)試器下載到單片機(jī)中才能實現(xiàn)。在簡單小批量產(chǎn)品功能的實現(xiàn)中,這已經(jīng)可以滿足要求。如果是已經(jīng)量產(chǎn)的產(chǎn)品,要想再修改功能幾乎不可能。但是面對越來越復(fù)雜的應(yīng)用環(huán)境,越來越多的應(yīng)用要求,一次性調(diào)試好代碼并下載運行的情況已經(jīng)變得越來越難,而且隨著代碼量的增多,隱藏的BUG也越不容易被發(fā)現(xiàn),需要在后期的應(yīng)用中不斷完善。
IAP(In Application Programming)功能即是為了應(yīng)對這種情況應(yīng)運而生的。IAP是一段完整的程序,它與應(yīng)用程序(APP,即具體實現(xiàn)功能的單片機(jī)代碼)是相互獨立和分開的兩部分,它的功能是在產(chǎn)品全生命周期中隨時根據(jù)需要對應(yīng)用程序部分代碼進(jìn)行擦除和燒寫,目的是為了在產(chǎn)品發(fā)布后可以方便地通過預(yù)留的通信接口對產(chǎn)品中的功能進(jìn)行更新升級[4]。
IAP和APP是同時存在于單片機(jī)上的兩個完整的代碼段。IAP實現(xiàn)對APP段代碼的擦除和重新燒寫,APP則負(fù)責(zé)實現(xiàn)具體的功能任務(wù),當(dāng)需要時跳轉(zhuǎn)至IAP進(jìn)行程序更新。它們分別燒寫在單片機(jī)Flash的兩個區(qū)域。對于本文所使用的華芯微特SWM240D8U7,它擁有64 KB的片上Flash(0x0——0xFFFF),就可以劃分16 KB給IAP(0x0——0x3FFF),余下48 KB留給APP(0x4000——0xFFFF)。需要特別注意的是IAP代碼和APP代碼編譯后的大小不能超過所分配的空間大小。
本文使用的SWM240單片機(jī)在上電后固定地從Flash地址0處開始執(zhí)行代碼[5],因本文將IAP程序放于此處,因此首先運行IAP程序,在IAP程序中通過檢查預(yù)先設(shè)置的標(biāo)志信息來確定APP代碼段是否正常,若符合要求就跳轉(zhuǎn)到APP段開始執(zhí)行,否則進(jìn)入等待升級燒寫狀態(tài)。
ARM的Cortex M0單片機(jī)將代碼的最初幾十個字作為中斷向量入口,該區(qū)域存放的是對應(yīng)的中斷處理程序代碼所在的開始位置。中斷發(fā)生時由硬件將PC指針拉到對應(yīng)的中斷入口處,取得地址后跳轉(zhuǎn)到中斷處理程序,完成完整的中斷處理過程[6]。在IAP和APP共存的理想情況下,如果在APP中發(fā)生中斷,中斷指針應(yīng)該指向APP入口處的中斷向量;同樣,如果在IAP中發(fā)生中斷,中斷指針就應(yīng)該指向IAP入口處的中斷向量??蓪嶋H情況是,Cortex M0系列沒有像其他M3/M4/M0+系列核心所具備的中斷矢量表重定位寄存器[7],其中斷發(fā)生時會始終會指向從0x0開始的某入口向量處,即本文存放IAP程序的向量位置。那么當(dāng)APP中發(fā)生中斷時,取得的是IAP中的處理程序地址如圖1所示。
圖1 IAP啟動流程及中斷流程
由于每次中斷發(fā)生時指針都指向從IAP開始的入口地址[8],因此可以考慮將IAP的所有可屏蔽中斷(或者只是用到的中斷)程序都寫成一個跳轉(zhuǎn)程序,根據(jù)當(dāng)前所處的位置來取得不同的跳轉(zhuǎn)地址,這在單片機(jī)的啟動文件中可以通過匯編語言來實現(xiàn)。跳轉(zhuǎn)地址的取得可以有多種方法。這里通過將向量地址拷貝到RAM指定位置來實現(xiàn)。在IAP進(jìn)程時內(nèi)存存儲的是IAP中斷向量地址,當(dāng)跳轉(zhuǎn)到APP時,在APP中將該內(nèi)存地址存儲內(nèi)容改寫為APP的中斷向量地址。同樣當(dāng)再次跳轉(zhuǎn)回IAP時也會執(zhí)行同樣的操作。這樣無論是在哪個運行空間,雖然中斷都是跳回IAP向量位置,但是獲取的中斷地址卻是與運行空間對應(yīng),如圖2所示。
圖2 修改后的中斷處理流程
首先要實現(xiàn)的是將原IAP入口處的中斷函數(shù)改寫為跳轉(zhuǎn)函數(shù)。在ARM的啟動文件中定義了與硬件相對應(yīng)的中斷函數(shù)名稱,如圖3所示。通過修改啟動文件中的函數(shù)內(nèi)容,使用LDR和BX兩條指令來實現(xiàn)取值和跳轉(zhuǎn)。
圖3 啟動文件定義的中斷函數(shù)名
用戶如果在自己的代碼空間中實現(xiàn)了該函數(shù),并且在啟動文件中EXPORT了該函數(shù),中斷后就會跳轉(zhuǎn)到用戶函數(shù)中??梢孕薷臑樽远x的跳轉(zhuǎn)指令。以GPIOA0外部中斷函數(shù)為例,圖4顯示的是未修改前在啟動文件中的代碼段,它的作用是當(dāng)發(fā)生該中斷時,使程序跳轉(zhuǎn)到GPIOA0_Handler函數(shù)去執(zhí)行(函數(shù)名其實就是一個地址)。圖5顯示的是修改以后的代碼段,它的作用是當(dāng)發(fā)生中斷時,使程序跳轉(zhuǎn)到GPIOA0_ISR_FUN_ADD所指的地址去執(zhí)行。這里__GPIOA0_ISR_FUN_ADD是強(qiáng)行指定的RAM地址,該地址的內(nèi)容根據(jù)是在IAP還是在APP有所不同,如圖6所示。
圖4 修改前的中斷函數(shù)
圖5 修改后的中斷函數(shù)
圖6 指定存儲中斷地址的內(nèi)存位置
其次,在完成跳轉(zhuǎn)函數(shù)后,需要分別在IAP和APP代碼段將自己空間的中斷函數(shù)地址填入對應(yīng)的RAM位置。做法是在代碼中定義全局變量,并將變量指定在RAM的固定位置,實現(xiàn)代碼如下:
uint32_t VectorTable[4] __attribute__((at(0x20000000)));
此處只使用了4個中斷并將該數(shù)組固定在RAM起始位置。最后,需要分別在IAP和APP代碼初始化階段,將自己運行空間的處理函數(shù)地址填入對應(yīng)的數(shù)組中即可。實現(xiàn)代碼如下:
VectorTable[0] = (uint32_t)GPIOA0_Handler;
VectorTable[1] = (uint32_t)TIMR0_Handler;
VectorTable[2] = (uint32_t)GPIOC0_Handler;
VectorTable[3] = (uint32_t)TIMR1_Handler;
需要說明的是,在編寫代碼時,由于IAP和APP是兩個獨立的工程,分別都有自己的啟動文件,而單品機(jī)發(fā)生中斷時起作用的是IAP中的啟動文件,因此只需要將IAP中的啟動文件進(jìn)行修改即可,APP中的不做改動。另外,在IAP和APP初始化過程中,文中所述獲取中斷處理函數(shù)地址的方法,APP中還可以通過直接拷貝對應(yīng)中斷向量表中的內(nèi)容來得到,但是在IAP中必須按照上面的方法,如果拷貝中斷向量表的內(nèi)容,得到的就是啟動代碼中XXX_Handler PROC的地址,這將導(dǎo)致程序進(jìn)入死循環(huán)。
經(jīng)過上面的設(shè)置后,分別將IAP和APP燒入對應(yīng)的Flash空間,就可以實現(xiàn)兩個空間的中斷。如果用戶有多個運行空間,也可以按照此方法來進(jìn)行處理。
按照本文所述,在某國產(chǎn)SWM240D8U7單片機(jī)上分別建立了基于CAN通信的IAP和APP程序。程序上電后首先按照硬件默認(rèn)設(shè)置從0x0處運行代碼,該處代碼完成MCU的啟動,中斷向量的定位,APP應(yīng)用標(biāo)志的檢測(檢查APP代碼段標(biāo)志是否正常),如果一切正常則跳轉(zhuǎn)到應(yīng)用程序段執(zhí)行,否則等待接收從CAN通信接口傳送過來的升級數(shù)據(jù)包。經(jīng)測試文章所述方法完全滿足要求,在IAP和APP之間相互跳轉(zhuǎn)的效果和基于硬件設(shè)置的單片機(jī)效果一致,達(dá)到了設(shè)計初衷。