|
在PROTEUS中使用ARM處理器及uC/OS-II移植理解 Rein Lee 一.嵌入式系統(tǒng)概述 通過本次嵌入式系統(tǒng)課程的學習,我了解了嵌入式系統(tǒng)的概念。所謂嵌入式系統(tǒng),是指用于執(zhí)行獨立功能的專用計算機系統(tǒng),它由包括微處理器、定時器、微控制器、存儲器、傳感器等一系列微電子芯片與器件,和嵌入在存儲器中的微型操作系統(tǒng)、控制應(yīng)用軟件組成,共同實時諸如實時控制、監(jiān)視、管理、移動計算、數(shù)據(jù)處理等各種自動化處理任務(wù)。嵌入式系統(tǒng)以應(yīng)用為中心,以微電子技術(shù)、控制技術(shù)和通訊技術(shù)為基礎(chǔ),強調(diào)硬件軟件的協(xié)同性與整合性,軟件與硬件可裁減,以滿足系統(tǒng)對功能、成本、體積和功耗等要求。 1.1 嵌入式系統(tǒng)的硬件特征 嵌入式系統(tǒng)的硬件必須根據(jù)具體的應(yīng)用任務(wù),以功耗、成本、體積、可靠性、處理能力等為指標來選擇。嵌入式系統(tǒng)的核心是系統(tǒng)軟件和應(yīng)用軟件。由于存儲空間有限,因而要求軟件代碼緊湊、可靠,大多對實時性有嚴格的要求。 早期的嵌入式系統(tǒng)設(shè)計方法,通常是采用“硬件優(yōu)先”原則。在粗略估計軟件任務(wù)需求的情況下,首先進行硬件設(shè)計與實現(xiàn)。然后在此硬件平臺上,再進行軟件設(shè)計。因為很難充分利用硬件軟件資源,取得最佳性能的效果。同時,一旦在測試時發(fā)現(xiàn)問題,需求對設(shè)計進行修改時,整個設(shè)計流程將重新進行,對成本和設(shè)計周期的影響很大。這種傳統(tǒng)的設(shè)計方法只能改善硬件/軟件各自的性能,在有限的設(shè)計空間不可能對系統(tǒng)做出較好的性能綜合優(yōu)化,在很大程度上依賴于設(shè)計者的經(jīng)驗和反復實驗。 隨著電子系統(tǒng)功能的日益強大和微型化,系統(tǒng)設(shè)計涉及的問題越來越多,難度也越來越大。硬件和軟件也不再是截然分開的兩個概念。因而出現(xiàn)了軟硬件協(xié)同的設(shè)計方法。在系統(tǒng)目標要求下,協(xié)同設(shè)計軟硬件體系結(jié)構(gòu),以最大限度地挖掘系統(tǒng)軟硬件能力,得到高性能低代價的優(yōu)化設(shè)計方案。 1.2 嵌入式操作系統(tǒng) 目前流行的嵌入式操作系統(tǒng)可以分為兩類:一類是從運行在個人電腦上的操作系統(tǒng)向下移植到嵌入式系統(tǒng)中,形成的嵌入式系統(tǒng),如微軟公司的Windows CE,SUN公司的Java操作系統(tǒng),嵌入式Linux等。 另一類是實時操作系統(tǒng),如WindRiver公司的VxWorks,ISI的pSOS,ATI的Nucleus,和免費公開源代碼的uC/OS-II等。 二.在Proteus中使用ARM處理器 由于Proteus中只支持LPC系列的ARM處理器,在這里只是簡單的列舉出LPC2124的一些特性: LPC2124是基于一個支持實時仿真和跟蹤的16/32位ARM7TDMI-S CPU的微處理器,并帶有256k的嵌入的高速Flash存儲器和16k的片那靜態(tài)RAM。128位寬度的存儲器接口和獨特的加速結(jié)構(gòu)使得32位代碼能夠在最大的時鐘速率下運行。對代碼規(guī)模有嚴格控制的應(yīng)用可使用16位Thumb模式,將使得代碼規(guī)模降低超過30%,而性能的損失卻很小。 LPC2124片那Boot裝載程序?qū)崿F(xiàn)在系統(tǒng)編程(ISP)和在應(yīng)用編程(IAP)。1ms可以編程512字節(jié)。整片擦除只需要400ms。此外還有4路A/D轉(zhuǎn)換器,轉(zhuǎn)換時間低于2.24us;2個32位定時器,6路PWM輸出、RTC、看門狗和多個串行接口。LPC系列微處理器的抗干擾能力強,在很多應(yīng)用中得到了使用。 三.軟件分析 1.LPC的Memory Map、Remap和LPC2124的Bootblock程序 Memory Map是把芯片中、芯片外的Flash、RAM、外設(shè)、BootBlock等進行統(tǒng)一編址,用地址來表示對象。LPC系列ARM處理器的這個地址是出廠時,由廠家規(guī)定的,用戶只能訪問,而不能進行更改。 Remap和Boot,個人理解如下: 在Reset信號周期內(nèi),LPC2124運行芯片內(nèi)部自帶的Bootblock程序,復位信號過后才是運行用戶的程序。 LPC系列ARM處理器的Bootblock被固化在最高的Flash塊中,運行時是被映射到0x7FFFE000~0x7FFFFFFF區(qū)域,這個程序是廠家寫入的,它由任何復位硬件激活,在任何復位后都會先執(zhí)行Boot裝載程序。之所以要把BootBlock程序放在Flash塊的頂端,是因為各芯片的Flash大小不一致,廠家為了BootBlock在芯片中的位置固定,在編址的2G靠前的位置虛擬劃分一個區(qū)域作為BootBlock區(qū)域。這就是Remap。 BootBlock的工作如下: 1. 判斷P0.14是否為低,如果為低,進入ISP模式。 2. 若P0.14不為低,要判斷Boot(1:0)這兩個腳,如果為11,要設(shè)置MEMAP=1,即運行內(nèi)部Flash。否則設(shè)MEMAP=3,運行外部Flash。 3. 如果是運行外部Flash,那需要把外部Flash的起始地址重新映射到0x00000000,以便復位信號過后就開始運行用戶程序。 個人理解BootBlock相當于PC中的BIOS,由廠家固化,上電后首先完成映射,即把它的地址映射到0x00000000處,當初始化完成后,就運行內(nèi)部Flash或者外部Flash的程序,通常要以向量表開頭,這個過程就是Remap,也就是把內(nèi)部ROM或者外部Flash的地址映射到0x00000000處。 2.啟動代碼分析 運行完BootBlock程序之后就是要運行用戶自己編寫的啟動代碼了。啟動代碼中最重要的是異常向量表。這個表包括復位、未定義指令、軟中斷、預取指中止、數(shù)據(jù)中止、IRQ中斷和FIQ中斷。 Reset LDR PC, ResetAddr LDR PC, UndefinedAddr LDR PC, SWI_Addr LDR PC, PrefetchAddr LDR PC, DataAbortAddr DCD 0xb9205f80 ;插入用戶代碼有效簽名 LDR PC, [PC, #-0xff0] ;從VIC處取得IRQ入口地址 LDR PC, FIQ_Addr ResetAddr DCD ResetInit UndefinedAddr DCD Undefined SWI_Addr DCD SoftwareInterrupt PrefetchAddr DCD PrefetchAbort DataAbortAddr DCD DataAbort Nouse DCD 0 IRQ_Addr DCD 0 FIQ_Addr DCD FIQ_Handler Undefined B Undefined ;未定義指令 PrefetchAbort B PrefetchAbort ;取指令中止 ;取數(shù)據(jù)中止 DataAbort B DataAbort ;快速中斷 未定義指令、取指令中止、快速中斷使得PC不跳轉(zhuǎn),進入死循環(huán),等待看門狗復位用戶程序。對于ARM9,有MMU單元,應(yīng)該在PrefetchAbort和DataAbort處進行處理,然后返回到發(fā)生異常的指令處重新執(zhí)行一次。 FIQ_Handler STMFD SP!, {R0-R3, LR} ;ADS編譯器會自動保存R4-R11 IMPORT FIQ_Exception ;也可以在startup.s的開頭引入 BL FIQ_Exception ;調(diào)用C語言中的中斷服務(wù)程序 LDMFD SP!, {R0-R3, LR} ;出棧 SUBS PC, LR, #4 如果在CPSR中沒有禁止中斷,那么每執(zhí)行一條指令,處理器會檢查是否有FIQ或者IRQ中斷發(fā)生。若有中斷發(fā)生,當且僅當PC指更新后,即處理器對下一條指令進行取指發(fā)生后,處理器才會進入到中斷的服務(wù)程序中。因此保存在LR中的指是PC-4,此處的PC是更新后的PC,由于ARM處理器是三級流水線的結(jié)構(gòu),因此返回地址是LR-4,返回到的地址是將要執(zhí)行,但是被中斷了指令。 LDR只能實現(xiàn)當前PC 4KB范圍內(nèi)的跳轉(zhuǎn),而在LDR的不遠處用DCD定義一個字,而在這個字里存放最終的異常服務(wù)程序的入口地址,可以實現(xiàn)在4GB范圍內(nèi)的全范圍跳轉(zhuǎn)。要求整個向量表的累加和為0。 __user_initial_stackheap函數(shù)是對用戶的堆棧進行初始化,對它的調(diào)用是在__main中完成的,對編程人員不可見。__main是ADS編譯系統(tǒng)提供的一個庫函數(shù),使用__main標號引導系統(tǒng)時,必須將應(yīng)用程序的入口定義為main()。__main完成代碼和數(shù)據(jù)的復制,并且把ZI數(shù)據(jù)區(qū)清零。這一步當代碼和數(shù)據(jù)區(qū)在存儲和運行時處于不同的存儲器位置時有意義。__main保證了系統(tǒng)在進入用戶的應(yīng)用程序前自動完成了系統(tǒng)調(diào)用。但是如果所有的初始化過程都已經(jīng)被用戶代碼顯式地被完成,如堆棧初始化、加載映象、執(zhí)行映象、RW、ZI數(shù)據(jù)的復制等,那么用戶應(yīng)用程序的入口函數(shù)可以任意定義any_name(),完成初始化后,直接B any_name即可。個人建議使用B __main的方式,防止疏忽出現(xiàn)不必要的錯誤,比如出現(xiàn)程序中的全局變量沒有被正確的初始化等錯誤。 3.IRQ.S分析 通過定義宏: $IRQ_Label HANDLER $IRQ_Exception_Function來實現(xiàn)LPC 2124的多個中斷源共用一段異常處理代碼的目的。 SUB LR, LR, #4 ; 計算返回地址 STMFD SP!, {R0-R3, R12, LR} ; 保存任務(wù)環(huán)境 進入IRQ中斷,首先計算中斷的返回地址,將LR減4,然后將R0-R3,R12,LR壓入堆棧。由于進入了IRQ中斷,因此堆棧指針是SP_IRQ,和用戶代碼運行的SP_SYS不是同一個寄存器。至于只保存R0-R3,R12,是因為程序在正常運行過程中,R4-R11中裝載的是局部變量,在中斷跳轉(zhuǎn)是ADS編譯器會自動將R4-R11入棧保護。 進行了堆棧保護后,要保存進入中斷之前的CPSR寄存器,以保證完成中斷處理后,使處理器狀態(tài)和沒有發(fā)生中斷一樣。 MRS R3, SPSR ; 保存狀態(tài) STMFD SP, {R3, SP, LR}^ ; 保存用戶狀態(tài)的R3,SP,LR,注意不能回寫 若STM的寄存器列表中不包含PC,^的作用是保存用戶模式下的寄存器。此操作的目的是用于中斷嵌套。通過定義IRQ模式的堆棧深度,可以控制中斷嵌套的深度。分析整段代碼,一次中斷響應(yīng)需要占用8個32bit的堆棧,定義堆棧深度為n*8,可以允許n級中斷嵌套。 此處^操作符不允許回寫,經(jīng)過AXD仿真驗證,回寫操作會寫入sp_usr而不是sp_irq,此操作屬于不安全的操作,并且編譯之后,ADS編譯器會給出響應(yīng)的警告。 MSR CPSR_c, #(NoInt | SYS32Mode) ; 切換到系統(tǒng)模式 CMP R1, #1 LDREQ SP, =StackUsr BL $IRQ_Exception_Function ; 調(diào)用c語言的中斷處理程序 堆棧操作完成之后,切換到系統(tǒng)模式下運行C語言中定義的中斷復位函數(shù),才能達到中斷嵌套的目的。 當中斷服務(wù)函數(shù)執(zhí)行完畢之后,退出中斷。在uC/OS-II的環(huán)境下考慮,因為IRQ中斷發(fā)生之后,會改變?nèi)蝿?wù)就緒列表,因此需要在中斷退出時確定是否需要進行任務(wù)切換。這點在下面的OS_cpu_a.s中會涉及到。 4.OS_cpu_a.s分析 移植uC/OS-II,需要改寫os_cpu.h,OS_cpu_a.s,OS_cpu_a.c,includes.h幾個文件。在LPC系列的平臺,任務(wù)級的任務(wù)切換和中斷級的任務(wù)切換是通過軟中斷(SWI,Software Interrupt)來完成的。 OS_CPU_A.S中包括SoftwareInterrupt、OSIntCtxSw、OSIntCtxSw_1和__OSStartHighRdy四個函數(shù)。SoftwareInterrupt定義了軟件中斷處理方法,OSIntCtxSw對應(yīng)于中斷級任務(wù)切換和任務(wù)級任務(wù)切換。而__OSStartHighRdy是uC/OS-II啟動時調(diào)用的,調(diào)用新建的優(yōu)先級最高的任務(wù)。這四個函數(shù)的編寫需要很好的了解ARM7的內(nèi)核結(jié)構(gòu)和完全了解uC/OS-II內(nèi)核運行的原理。 在SoftwareInterrupt主要完成寄存器R0-R3,R12,LR在SVC模式(Supervisor)的堆棧中的保存、取得軟件中斷號并調(diào)用相應(yīng)的處理過程。在OS_CPU.H中可以找到關(guān)于幾個函數(shù)的軟件中斷定義。 __swi(0x00) void OS_TASK_SW(void); /*任務(wù)級任務(wù)切換函數(shù)*/ __swi(0x01) void _OSStartHighRdy(void); /*運行優(yōu)先級最高的任務(wù)*/ __swi(0x02) void OS_ENTER_CRITICAL(void) /*關(guān)中斷*/ __swi(0x03) void OS_EXIT_CRITICAL(void); /*開中斷*/ 由于SWI中斷是由指令本身引起的,因此發(fā)生中斷時,PC值并沒有更新,所以計算中斷返回地址,并不需要將LR減4,LR指向的就是發(fā)生中斷指令的下一條指令。SWI中斷下堆棧的操作如下指令所示: LDR SP, StackSvc ; 重新設(shè)置堆棧指針 STMFD SP!, {R0-R3, R12, LR} ; 寄存器入棧 …… ; 軟件中斷處理 LDMFD SP!, {R0-R3, R12, PC}^ ; 寄存器出棧,返回 OS_TASK_SW是任務(wù)級任務(wù)切換函數(shù),引起0x00號軟中斷,并最終會跳轉(zhuǎn)到OSIntCtxSw執(zhí)行,OSIntCtxSw也是中斷級任務(wù)切換的函數(shù)。 對OSIntCtxSw的理解,實際上是要理解各個任務(wù)的堆棧設(shè)置。一個任務(wù)正常運行,是運行在user模式下,根據(jù)ARM7的體系結(jié)構(gòu),user模式的所有寄存器和system模式下的所有寄存器使用同一個物理地址。其他模式R13,R14不同物理地址,FIQ模式R8-R14不同物理地址。其他模式相比user/sys模式,多一個SPSR寄存器,用于保存發(fā)生中斷時的CPSR的備份。因此,當發(fā)生任務(wù)切換,需要保存當前任務(wù)的所有寄存器,即R0-R14。 OSIntCtxSw LDR R2, [SP, #20] ;獲取PC (LR_SVC) LDR R12, [SP, #16] ;獲取R12 MRS R0, CPSR ;保存當前SVC模式到R0 MSR CPSR_c, #(NoInt | SYS32Mode) ;切換到系統(tǒng)模式 MOV R1, LR ;保存LR_sys STMFD SP!, {R1-R2} ;保存LR_sys, PC (LR_SVC) STMFD SP!, {R4-R12} ;保存R4-R12 MSR CPSR_c, R0 ;從R0中恢復到SVC模式 LDMFD SP!, {R4-R7} ;獲取R0-R3,SVC堆棧 ADD SP, SP, #8 ;出棧R12,PC,計算SP_SVC MSR CPSR_c, #(NoInt | SYS32Mode) ; 切換到系統(tǒng)模式 STMFD SP!, {R4-R7} ;保存R0-R3 需要注意,執(zhí)行MSR CPSR_c, #(NoInt | SYS32Mode)之前的SP是指SP_SVC,而模式切換之后,SP對應(yīng)于SP_user。 注意ADS采用的是滿遞減堆棧。 此段代碼的作用是在系統(tǒng)模式下,依次入棧PC,(即LR_SVC),LR(LR_SYS)R12,R11,……R4。然后切換回SVC模式下出棧R0-R3,在切換回SYSTEM模式下入棧R3,R2,R1,R0。 當以上入棧工作完成之后,再保存OSEnterSum和CPSR即可完成當前任務(wù)的上下文環(huán)境的保存。 LDR R1, =OsEnterSum ;獲取OsEnterSum LDR R2, [R1] STMFD SP!, {R2, R3} ;保存CPSR,OsEnterSum 從上述程序分析可以看出,一個任務(wù)的上下文環(huán)境入棧如下圖所示:
LDR R1, =OSTCBCur LDR R1, [R1] STR SP, [R1] 至此,任務(wù)的上下文保存工作完成,下面需要進行任務(wù)切換。第一步是把就緒列表中最高優(yōu)先級OSPrioHighRdy賦值給OSPrioCur,然后把最高優(yōu)先級任務(wù)對應(yīng)的TCB OSTCBHighRdy賦值給當前任務(wù)TCB OSTCBCur。之后運行到OSIntCtxSw_1,進行新任務(wù)的數(shù)據(jù)出棧,切換到新任務(wù)的運行。 這里值得一提的是以下兩條語句: ADD SP, R4, #68 LDR LR, [SP, #-8] 之所以對于SP進行加68的操作,是因為此處的SP處理器在系統(tǒng)模式下的堆棧指針,也就是uC/OS-II的用戶程序運行的任務(wù)堆棧指針。即將運行的任務(wù)的數(shù)據(jù)在這兩條語句之后會通過設(shè)置SVC模式下的堆棧出棧,然后就將PC設(shè)置到新任務(wù)的入口處開始運行任務(wù)代碼了。這樣,我們沒有機會再修改即將運行的任務(wù)的堆棧指針了。若在恢復任務(wù)之前,不修改SP_SYS,會導致有數(shù)據(jù)壓棧,有數(shù)據(jù)出棧,但是堆棧指針卻一直向棧底方向運動,沒有往棧頂方向運動的情況出現(xiàn),任務(wù)長期運行,被切換,那么任務(wù)的堆棧會溢出,出現(xiàn)了內(nèi)存泄漏。那么之所以是68,是因為任務(wù)的上下文切換是17個寄存器的內(nèi)容,每個寄存器是32bit,所以是68=17*4。 那么LDR LR, [SP, #-8]的原因又是什么呢?為什么在沒有恢復待運行任務(wù)的數(shù)據(jù)之前,要先恢復系統(tǒng)模式下的LR寄存器呢?原因同上,當新任務(wù)運行后,程序再沒有機會修改系統(tǒng)模式下的LR寄存器了,這樣會導致任務(wù)跑飛。OSIntCtxSw_1的最后一句:LDMFD SP!, {R0-R12, LR, PC }^,恢復了新任務(wù)。當時仔細調(diào)試程序會發(fā)現(xiàn),MSR SPSR_cxsf, R5,恢復處理器模式是在把堆棧的數(shù)據(jù)恢復到PC之后才會改變到系統(tǒng)模式下,因此可以得出結(jié)論,這里出棧的倒數(shù)第二個數(shù)據(jù),就是寄存器列表中的LR,是SVC模式下的LR,并不是系統(tǒng)模式下的LR。所以,需要在任務(wù)數(shù)據(jù)出棧之前先恢復LR_SYS。 至此,關(guān)于uC/OS-II的移植部分的分析全部完畢。至于OS_CPU_A.C中的一個函數(shù)是初始化任務(wù)堆棧。值得注意的地方是,ADS使用的是滿遞減堆棧。因此需要在OS_CPU.H中把OS_STK_GROWTH設(shè)置為1。 四.uC/OS-II的使用 通過全部閱讀uC/OS-II的代碼,個人總結(jié)了一下uC/OS-II的核心算法。主要是通過對OSUnMapTbl這張表的讀取,才能確定優(yōu)先級最高的就緒任務(wù)。這張表的編碼規(guī)則是:對應(yīng)偏移量的數(shù)值中,出現(xiàn)第一個二進制”1”的位置。由于算出優(yōu)先級最高的任務(wù)是通過查表完成的,因此任務(wù)個數(shù)的多少對于任務(wù)的調(diào)度沒有影響。同樣的道理,對于事件的管理,也是一樣通過OSUnMapTbl來完成的。只要理解了這張表,也就理解了OSRdyGrp、OSRdyTbl[]和OSEventGrp、OSEventTbl[]的使用。唯一的一點不同在于,新建任務(wù)的狀態(tài)是:OS_STAT_RDY,任務(wù)處于就緒態(tài)。而新建事件之后,比如郵箱:新建一個郵箱調(diào)用OSMboxCreate完成,返回申請成功的郵箱地址,然后任務(wù)需要等待郵件的接受,調(diào)用OSMboxPend,通過從對應(yīng)的OSRdyTbl中刪除該任務(wù),達到掛起任務(wù)的目的。 另外,關(guān)于如何新建任務(wù),如何使用郵箱、信號量等,關(guān)于uC/OS-II的書籍上都會介紹,這里就不再贅述了。 唯一一點需要注意的是,在開始多任務(wù)之前,也就是調(diào)用OSStart之前,不允許打開時鐘節(jié)拍中斷,防止操作系統(tǒng)崩潰。時鐘節(jié)拍中斷的允許,是在TargetInit中完成的。 五.關(guān)于Proteus仿真、總結(jié) 此次仿真,以LPC2124為處理器,完成了對串口、SPI口、I2C接口和AD接口轉(zhuǎn)換的編程。LCD采用LM4229,T6963C控制器。個人感覺程序比較簡單,在這里就不多加敘述了。 在實際仿真的時候,有一個問題無法解決。 由于LCM接口占用了P0.4到P0.15,因此PWM的輸出我選擇了P0.21,PWM5,我采用單邊輸出,匹配后復位TC的做法。若P0.21接示波器,uC/OS-II就無法正常啟動了。我打開了串口,然后跟蹤調(diào)試,發(fā)現(xiàn),當程序調(diào)用GUI函數(shù)時,就是運行緩慢,P0.4到P0.11緩慢讀取數(shù)據(jù),導致串口輸出及其緩慢。開始我以為是LCD驅(qū)動程序關(guān)于PINSEL引腳的使用錯誤,但是,轉(zhuǎn)換數(shù)據(jù)接口到P1口,P1.24-P1.31,占用JTAG口,使用GPIO功能,上述問題依然。當P0.21設(shè)置為PWM,但不接示波器,卻一切正常。個人認為是PROTEUS本身仿真問題,并不是程序的問題。 通過通讀、調(diào)試uC/OS-II代碼和移植代碼,可以更加深刻地理解嵌入式操作系統(tǒng)的工作原理和ARM處理器內(nèi)核結(jié)構(gòu)。不過,uC/OS-II作為比較簡單的操作系統(tǒng),還是存在一些不足。比如缺乏文件系統(tǒng)和GUI。相比于uclinux,還是顯得不足更多。未來的路還很長,期待linux的更加深入的學習和使用! |
|
|