韓雨泓,陳良勇
(1.四川大學 計算機學院,四川 成都 610000;2.原成都軍區聯勤部后勤信息中心,四川 成都 610000)
在實際的生產工作中,從經濟和實用性方面考慮,選擇嵌入式系統MCU控制器時常常會使用到一些性能相對較弱,功能配置也不太強大的單片機,這類單片機有時候僅僅夠用,無法從根本上滿足多元化的需求。如果需要更多的功能就需要選擇更高端的單片機,同時也意味著成本的增加。本文在項目過程中選用的MCU即為這種類型。在產品的整個生命周期中,需要對應用程序進行及時的升級換代,這就用到了IAP功能[1-2],而該款MCU不具備硬件級的中斷向量重定位功能,因此只能另辟蹊徑,以軟件的方式解決該問題。
在傳統的單片機應用開發中,程序員將寫好的代碼通過IDE編譯鏈接后生成可執行的二進制文件下載到單片機Flash中即可[3],功能的修改或者錯誤的修正需要程序員在完成代碼修改后,再次通過專用調試器下載到單片機中才能實現。在簡單小批量產品功能的實現中,這已經可以滿足要求。如果是已經量產的產品,要想再修改功能幾乎不可能。但是面對越來越復雜的應用環境,越來越多的應用要求,一次性調試好代碼并下載運行的情況已經變得越來越難,而且隨著代碼量的增多,隱藏的BUG也越不容易被發現,需要在后期的應用中不斷完善。
IAP(In Application Programming)功能即是為了應對這種情況應運而生的。IAP是一段完整的程序,它與應用程序(APP,即具體實現功能的單片機代碼)是相互獨立和分開的兩部分,它的功能是在產品全生命周期中隨時根據需要對應用程序部分代碼進行擦除和燒寫,目的是為了在產品發布后可以方便地通過預留的通信接口對產品中的功能進行更新升級[4]。
IAP和APP是同時存在于單片機上的兩個完整的代碼段。IAP實現對APP段代碼的擦除和重新燒寫,APP則負責實現具體的功能任務,當需要時跳轉至IAP進行程序更新。它們分別燒寫在單片機Flash的兩個區域。對于本文所使用的華芯微特SWM240D8U7,它擁有64 KB的片上Flash(0x0——0xFFFF),就可以劃分16 KB給IAP(0x0——0x3FFF),余下48 KB留給APP(0x4000——0xFFFF)。需要特別注意的是IAP代碼和APP代碼編譯后的大小不能超過所分配的空間大小。
本文使用的SWM240單片機在上電后固定地從Flash地址0處開始執行代碼[5],因本文將IAP程序放于此處,因此首先運行IAP程序,在IAP程序中通過檢查預先設置的標志信息來確定APP代碼段是否正常,若符合要求就跳轉到APP段開始執行,否則進入等待升級燒寫狀態。
ARM的Cortex M0單片機將代碼的最初幾十個字作為中斷向量入口,該區域存放的是對應的中斷處理程序代碼所在的開始位置。中斷發生時由硬件將PC指針拉到對應的中斷入口處,取得地址后跳轉到中斷處理程序,完成完整的中斷處理過程[6]。在IAP和APP共存的理想情況下,如果在APP中發生中斷,中斷指針應該指向APP入口處的中斷向量;同樣,如果在IAP中發生中斷,中斷指針就應該指向IAP入口處的中斷向量。可實際情況是,Cortex M0系列沒有像其他M3/M4/M0+系列核心所具備的中斷矢量表重定位寄存器[7],其中斷發生時會始終會指向從0x0開始的某入口向量處,即本文存放IAP程序的向量位置。那么當APP中發生中斷時,取得的是IAP中的處理程序地址如圖1所示。

圖1 IAP啟動流程及中斷流程
由于每次中斷發生時指針都指向從IAP開始的入口地址[8],因此可以考慮將IAP的所有可屏蔽中斷(或者只是用到的中斷)程序都寫成一個跳轉程序,根據當前所處的位置來取得不同的跳轉地址,這在單片機的啟動文件中可以通過匯編語言來實現。跳轉地址的取得可以有多種方法。這里通過將向量地址拷貝到RAM指定位置來實現。在IAP進程時內存存儲的是IAP中斷向量地址,當跳轉到APP時,在APP中將該內存地址存儲內容改寫為APP的中斷向量地址。同樣當再次跳轉回IAP時也會執行同樣的操作。這樣無論是在哪個運行空間,雖然中斷都是跳回IAP向量位置,但是獲取的中斷地址卻是與運行空間對應,如圖2所示。

圖2 修改后的中斷處理流程
首先要實現的是將原IAP入口處的中斷函數改寫為跳轉函數。在ARM的啟動文件中定義了與硬件相對應的中斷函數名稱,如圖3所示。通過修改啟動文件中的函數內容,使用LDR和BX兩條指令來實現取值和跳轉。

圖3 啟動文件定義的中斷函數名
用戶如果在自己的代碼空間中實現了該函數,并且在啟動文件中EXPORT了該函數,中斷后就會跳轉到用戶函數中。可以修改為自定義的跳轉指令。以GPIOA0外部中斷函數為例,圖4顯示的是未修改前在啟動文件中的代碼段,它的作用是當發生該中斷時,使程序跳轉到GPIOA0_Handler函數去執行(函數名其實就是一個地址)。圖5顯示的是修改以后的代碼段,它的作用是當發生中斷時,使程序跳轉到GPIOA0_ISR_FUN_ADD所指的地址去執行。這里__GPIOA0_ISR_FUN_ADD是強行指定的RAM地址,該地址的內容根據是在IAP還是在APP有所不同,如圖6所示。

圖4 修改前的中斷函數

圖5 修改后的中斷函數

圖6 指定存儲中斷地址的內存位置
其次,在完成跳轉函數后,需要分別在IAP和APP代碼段將自己空間的中斷函數地址填入對應的RAM位置。做法是在代碼中定義全局變量,并將變量指定在RAM的固定位置,實現代碼如下:
uint32_t VectorTable[4] __attribute__((at(0x20000000)));
此處只使用了4個中斷并將該數組固定在RAM起始位置。最后,需要分別在IAP和APP代碼初始化階段,將自己運行空間的處理函數地址填入對應的數組中即可。實現代碼如下:
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是兩個獨立的工程,分別都有自己的啟動文件,而單品機發生中斷時起作用的是IAP中的啟動文件,因此只需要將IAP中的啟動文件進行修改即可,APP中的不做改動。另外,在IAP和APP初始化過程中,文中所述獲取中斷處理函數地址的方法,APP中還可以通過直接拷貝對應中斷向量表中的內容來得到,但是在IAP中必須按照上面的方法,如果拷貝中斷向量表的內容,得到的就是啟動代碼中XXX_Handler PROC的地址,這將導致程序進入死循環。
經過上面的設置后,分別將IAP和APP燒入對應的Flash空間,就可以實現兩個空間的中斷。如果用戶有多個運行空間,也可以按照此方法來進行處理。
按照本文所述,在某國產SWM240D8U7單片機上分別建立了基于CAN通信的IAP和APP程序。程序上電后首先按照硬件默認設置從0x0處運行代碼,該處代碼完成MCU的啟動,中斷向量的定位,APP應用標志的檢測(檢查APP代碼段標志是否正常),如果一切正常則跳轉到應用程序段執行,否則等待接收從CAN通信接口傳送過來的升級數據包。經測試文章所述方法完全滿足要求,在IAP和APP之間相互跳轉的效果和基于硬件設置的單片機效果一致,達到了設計初衷。