1. 程式人生 > 其它 >RISC-V MCU開發實戰 (三):移植鴻蒙OS專案

RISC-V MCU開發實戰 (三):移植鴻蒙OS專案

軟體平臺:MounRiver Studio( MRS);硬體平臺: CH32V307開發板

先去碼雲上將原始碼克隆下來:

https://gitee.com/openharmony/kernel_liteos_m

新建一個CH32V307的工程,將原始碼直接拖到工程中,就新增進來了,然後去新增標頭檔案路徑即可

原始碼中包含比較全面,我們可以選擇不需要的部分將其排除在編譯之外,

操作方法為右鍵目錄或檔案,點選Include/Exclude From Build選單項恢復編譯,同樣的方法再選一遍即可。

下面說些移植作業系統的注意事項

ARM上移植實時作業系統大家可能比較熟悉,對於RISC-V核心的MCU,可能相對比較陌生。下面結合WCH的CH32V103和CH32V307兩款晶片來詳細說下針對RISC-V平臺,移植實時作業系統的注意點。

在移植前,有必要對RISC-V的一些基本知識點有一定的瞭解,這裡對RISC-V的概況,發展,指令集,特權模式等不作詳述,僅結合WCH的RISC-V核心的MCU,簡單介紹我們移植實時作業系統有可能遇到的關鍵點做一下描述。這裡之所以選取V103和V307兩款晶片,主要其極具代表性:

首先,直觀上其外設的使用方法和我們之前熟悉的F103,F107等是相容的,這樣降低了我們使用和移植時的難度,基於WCH提供的外設庫,我們以前上層的程式碼甚至於不用修改可直接使用。其次,V103是WCH RISC-V核心家族中的V3核心,V307為V4核心,V3核心支援RV32IMAC指令集,即除支援RISC-V基本的32位整數指令集外,還支援硬體乘除法,原子指令,壓縮指令。V4在V3的基礎上增加了單精度硬體浮點,並且其效能也比V3高。

除上述之外,雖然兩者的中斷控制器(PFIC)相較於現行的PLIC均不同,均不是統一入口,而是採用中斷向量表定址的方式,但是V3的中斷向量表處存放是一條指令,而V4的向量表既可以存放指令,也可以存放中斷處理函式的地址。兩者均支援中斷巢狀和硬體壓棧,區別在於V3最大巢狀兩級,V4最大可達八級,同時V3的硬體壓棧深度兩級,V4的硬體壓棧深度為三級。這裡需要注意的是,移植實時作業系統時需要關閉硬體壓棧,在切換任務時所有暫存器,我們希望是由我們自己控制其壓棧和出棧的內容。

RISC-V暫存器如下圖所示,其中x0-x31為整形暫存器,f0-f31為浮點暫存器(V3沒有浮點暫存器)。所有帶caller的暫存器,當發生中斷時需要儲存,值得注意的是,WCH的硬體壓棧儲存的暫存器僅僅儲存整數的16個caller saved 暫存器。正常一箇中斷函式的暫存器儲存我們不用關心,編譯器會幫我們做的很好。但是當我們從一個彙編入口進中斷函式的時候這些過程就不得不由我們自己來實現。暫存器中幾個相對特殊的x0恆為0,x1是返回地址暫存器ra,函式呼叫時用來存放返回地址,x2為堆疊指標sp,x3為gp全域性指標,用來定址全域性變數。對於一個正常執行的程式,除了x0,gp兩個初始值固定的外,其餘的均會是不確定的,所有在進行上下文保護時,均需要儲存。用到硬體浮點的時候,更是要儲存32個浮點暫存器。

除了上述的暫存器,移植還要關心的是幾個csr暫存器mstatus,mepc。正常情況下大部分csr只能在機器模式下操作(WCH的v3和v4核心支援機器模式和使用者模式)。mstatus中,MIE為中斷使能,當進中斷時MPIE更新為MIE,返回時MIE更新為MPIE。MPP用於儲存進中斷之前的特權模式,如果我們設定其為MPP=0b11,那麼將一直處於機器模式,其mret返回後還是處於機器模式。mepc是機器模式下異常程式指標,其只會在發生異常是被更新(中斷也是一類異常),進異常時我們可以從另外兩個csr暫存器mcause來看引起異常原因通過mtval檢視引起異常時的值。當從異常返回時mepc的值被更新給pc。我們正是通過進中斷修改mepc來實現任務的切換的,後面會詳細說明這個過程。

實時作業系統大家應該不陌生,常見的uCOS,FreeRTOS,RT-Thread,LiteOS-M等等,其基本的思路都是一樣的,需要一個定時器用於系統時間片的實現,一箇中斷用於任務切換。想要其能夠在一個MCU上成功的跑起來,需要弄清除一下幾個事情:

(1)進中斷需要儲存哪些內容。

從之前的描述中,應該知道,對於risc-v核心來說其進中斷壓棧的是caller saved的暫存器。從下圖一可以看出,進Systick中斷函式,先進行暫存器儲存,退出中斷時進行暫存器恢復,如果開啟硬體浮點,同時還會對浮點暫存器進行儲存和恢復。這個過程是編譯器幫我們實現,有一點需要注意的是我們移植的程式碼裡面進中斷後獲取了中斷的堆疊“csrrw sp,mscratch,sp”,返回時恢復了執行緒的堆疊指標“csrrw sp,mscratch,sp”中斷堆疊指標初始值是在任務開始時存入mscratch暫存器的,如果採用C形式中斷函式,中斷堆疊的獲取會在壓棧操作之後,中斷壓入的堆疊是當前執行任務的任務堆疊區域,如果想要中斷函式壓棧時壓入的自己的堆疊區域,可以使用匯編入口,進中斷後先修改sp,然後壓棧,再呼叫中斷處理函式,如圖二所示。

圖1

圖2

(2)任務棧需要儲存哪些內容。

前文說過對於一個正常執行的程式,切換任務前,除了x0恆0,x3 gp指標外,其餘的暫存器均需要儲存,每個RTOS中都會定義一個上下文儲存相關的結構體,這裡我們以華為鴻蒙LiteOS_M為例,看一下這個結構體:

圖3

在建立任務的時候均會為一個任務分配一個id和堆疊大小並對這個堆疊做初始化:

圖4

圖5

任務建立好了後會關聯一個根據任務id關聯一個任務控制塊taskCB,總的任務個數是在標頭檔案中配置的(target_config.h)總的任務塊的初始化也是在LOS_KernelInit被初始化。

圖6

從上面可以看出來,task--->taskCB--->sp指標--->memory這樣的路線,而這片memory開始位置用於上下文儲存。

這樣的方式在其他RTOS中也可以看到,例如RT-Thread中用於上下文儲存的結構體rt_hw_stack_frame,和taskCB類似的結構體rt_thread等。

圖7

圖8

(3)如何開啟任務排程。

前面看了每個任務上下文儲存位置,注意到堆疊初始化的時候把任務的入口地址給了context->epc。同時LiteOS_M原始碼中定義了一個LosTask型別的全域性變數g_losTask,其內部只有兩個任務控制塊指標,一個指向當前執行的任務,一個指向新任務,即要切換至的任務。

圖9

當做好一系列初始化後,LiteOS會呼叫HalStartSchedule來初始化系統節拍定時器,並註冊系統定時器的中斷處理函式,然後開始轉向執行第一個任務,如下圖所示:

圖10

其中OsSchedStart函式從任務列表中獲取第一個任務,並賦值給g_losTask裡面的runTask和newTask。然後呼叫HalStartToRun轉向執行runTask所指示的任務。HalStartToRun是一段彙編程式碼,下面就具體看其如何切換至runTask,具體如下圖的註釋:

圖11

這樣mret之後就轉向去執行第一個任務,並且不會再有返回,因為每個任務本身會是個迴圈,這裡也就能理解其原始碼註釋 never return的含義。

圖12

其他作業系統中也有類似的操作,例如RT-Thread中有個rt_hw_context_switch_to函式,其也是彙編程式碼實現,它是一個帶引數的函式,其傳入的引數為(&to_thread->sp),如下圖:

圖13

從名字就可以看出,傳遞的引數為啟動執行的第一個執行緒的控制塊的堆疊指標sp的值,後面賦值mepc,mstatus,其他暫存器等等都是和LiteOS_M一致的。

(4)如何進行任務切換。

瞭解瞭如何切換至第一個任務,那麼如何實現不同任務之間的切換呢,在這之前我們應該都有了解,RTOS是根據任務的優先順序和時間片進行輪轉的,每個任務執行一段時間,然後切換至下一個任務執行。每次切換前我們需要把當前任務的執行狀態進行儲存,然後切換至新任務,對其執行狀態進行恢復,如此迴圈反覆,實現任務排程。時間片實現使用的是核心的SysTick定時器,LiteOS_M是在los_timer.c中實現的,這個只需要根據實際硬體的進行初始化就行。其他作業系統也是類似,像RT-Thread原始碼中我們根據硬體完成board.c。對於任務切換,我們利用核心的軟中斷,只要使能該中斷,並且當需要切換任務時,把中斷控制器的對應的pendset位置1,即可觸發該中斷進行任務切換。下圖是liteOS_M切換過程:

圖14

圖15

其他作業系統也是大同小異,具體的區別僅僅是在切換新任務時,新任務如何獲取的問題,上圖可以看到LiteOS_M是通過g_losTask來管理,RT-Thread中定義了from_thread,to_thread,顧名思義從一個執行緒切換至另外一個執行緒。

弄清楚以上的問題,對於某一個RTOS的基本移植來說應該就比較明瞭。

最後移植好的鴻蒙os,RT-Thread等實時作業系統的程式碼均已在MRS上線,可以直接建立,開發相關應用