前期準(zhǔn)備
開發(fā)環(huán)境:ubuntu18.04
驗(yàn)證示例工程
本次實(shí)驗(yàn)驗(yàn)證的平臺是PLCT提供的QEMU,在Linux下的QEMU可以使用上述的筆者編譯好的,也可以使用自己嘗試編譯PLCT提供的源碼。
OPENHW提供了基于FreeRTOS的示例工程,由于使用的是PLCT提供的QEMU,所以IDE中自帶的工程并不能直接使用,為了避免不必要的麻煩,本文采用PLCT提供的示例工程。
下載好示例工程,進(jìn)入到app目錄,執(zhí)行以下命令配置編譯工程:
source ../env/core-v-mcu.sh
make RISCV=xxx //xxx為工具鏈的路徑
編譯完成后將生成的cli_test可執(zhí)行文件拷到qemu的安裝目錄,運(yùn)行下述命令驗(yàn)證工程;
./qemu-system-riscv32 -M core_v_mcu -bios none -kernel cli_test -nographic -monitor none -serial stdio
運(yùn)行結(jié)果:
出現(xiàn)上述結(jié)果,則表示示例工程運(yùn)行正常,這樣我們就有一個(gè)可移植的模板工程,后續(xù)移植基于該工程開展。
移植RT-Thread
CV32E40P內(nèi)核是一個(gè)RISC-V架構(gòu)的內(nèi)核,移植RTOS不可避免的會涉及到匯編部分的修改,所以在移植前期學(xué)習(xí)一下RISC-V架構(gòu)與匯編會產(chǎn)生事半功倍的效果,同樣在一之前需要熟悉CV32E40P的內(nèi)核資源。
cv32e40p繼承自pulp開源的RI5CY內(nèi)核,而RI5CY內(nèi)核的對接代碼在RT-Thread的倉庫libcpu/riscv/rv32m1已經(jīng)實(shí)現(xiàn),所以以RI5CY中的代碼為基礎(chǔ),移植cv32e40p的對接代碼。
成功移植RT-Thread有如下幾個(gè)關(guān)鍵的階段:
節(jié)拍定時(shí)器正常工作
線程可以正常創(chuàng)建,調(diào)度(這步最難,需要前面好多工作來保證)
shell可以正常使用(從這開始就順利多了)
RTOS的最小時(shí)間單位是系統(tǒng)節(jié)拍,系統(tǒng)正常工作需要節(jié)拍定時(shí)器的支持,因而以節(jié)拍定時(shí)器的移植為切入點(diǎn)展開移植。
core-v-mcu.c中放置的是系統(tǒng)初始化,中斷處理相關(guān)代碼。其中system_init函數(shù)完成了時(shí)鐘初始化,串口,I2C等外設(shè)的初始化,以及中斷函數(shù)的綁定。在文件的開頭可以找到如下一個(gè)指針數(shù)組:
void (*isr_table[32])(uint32_t);
該數(shù)組用于綁定中斷入口函數(shù)。
for (int i = 0 ; i < 32 ; i ++){
isr_table[i] = undefined_handler;
handler_count[i] = 0;
}
isr_table[0x7] = timer_irq_handler;
isr_table[0xb] = (void(*)(uint32_t))fc_soc_event_handlzer1;
在system_init函數(shù)中我們可以找到上述代碼,瀏覽該文件我們可以知道,timer_irq_handler即系統(tǒng)定時(shí)器的中斷入口函數(shù),
在原有FreeRTOS工程中該函數(shù)內(nèi)容如下:
void timer_irq_handler(uint32_t mcause)
{
#warning requires critical section if interrupt nesting is used.
if (xTaskIncrementTick() != 0) {
vTaskSwitchContext();
}
}
該函數(shù)實(shí)現(xiàn)系統(tǒng)節(jié)拍的產(chǎn)生,所以將這里的內(nèi)容修改為RT-Thread的節(jié)拍產(chǎn)生的方式,修改如下:
void timer_irq_handler(uint32_t mcause)
{
#warning requires critical section if interrupt nesting is used.
rt_interrupt_enter();
rt_tick_increase();
rt_interrupt_leaves();
}
涉及到中斷,我們就得考慮系統(tǒng)的中斷的實(shí)現(xiàn)方式,中斷一般會分為向量中斷與非向量中斷。RISC-V常在啟動后文件中進(jìn)行中斷模式的配置。從crt0.S文件可以找到如下代碼:
/* set vector table address /
la a0, __vector_start
or a0, a0, 1 / enable vectored mode (hardcoded anyway for CV32E40P) */
csrw mtvec, a0
RISC-V規(guī)范定義中斷、異常的入口地址,以及模式使用mtvec寄存器配置,上述代碼將系統(tǒng)的中斷模式配置為了向量模式,向量模式下,每個(gè)函數(shù)均包含一個(gè)獨(dú)立的入口函數(shù)用來處理中斷。所以我們切換至寫中斷向量表的文件vector.S,我們可以很明顯的看到一段代碼,從名字就可以看出下面是一張向量表 :
vector_table:
j freertos_risc_v_trap_handler // irq0
j freertos_risc_v_trap_handler
j freertos_risc_v_trap_handler
j freertos_risc_v_trap_handler // irq3
j freertos_risc_v_trap_handler
j freertos_risc_v_trap_handler
j freertos_risc_v_trap_handler
j freertos_risc_v_trap_handler //ctxt_handler // irq 7 mtime or timer
j freertos_risc_v_trap_handler
j freertos_risc_v_trap_handler
j h7// freertos_risc_v_trap_handler
j freertos_risc_v_trap_handler // irq 11 Machine (event Fifo)
j freertos_risc_v_trap_handler
j freertos_risc_v_trap_handler
j freertos_risc_v_trap_handler
j freertos_risc_v_trap_handler
j freertos_risc_v_trap_handler // IRQ16
j freertos_risc_v_trap_handler // IRQ17
j freertos_risc_v_trap_handler // IRQ18
j freertos_risc_v_trap_handler // IRQ19
j freertos_risc_v_trap_handler // IRQ20
j freertos_risc_v_trap_handler // IRQ21
j freertos_risc_v_trap_handler // IRQ22
j freertos_risc_v_trap_handler // IRQ23
j freertos_risc_v_trap_handler // IRQ24
j freertos_risc_v_trap_handler // IRQ25
j freertos_risc_v_trap_handler // IRQ26
j freertos_risc_v_trap_handler // IRQ27
j freertos_risc_v_trap_handler // IRQ28
j freertos_risc_v_trap_handler // IRQ29
j freertos_risc_v_trap_handler // IRQ30
j freertos_risc_v_trap_handler // IRQ30
PS:上述向量表看的我懵了好久啊,相信對于剛接觸底層不久的小伙伴也會有同樣的感受吧,向量表不是一張多姿多彩的表嗎,怎么感覺都一樣啊???不急,我們在看一下core-v-mcu.c這個(gè)文件,又會發(fā)現(xiàn)一段非常顯眼的代碼:
void vSystemIrqHandler(uint32_t mcause)
{
uint32_t val = 0;
// extern void (*isr_table[32])(uint32_t);
isr_table[mcause & 0x1f](mcause & 0x1f);
}
結(jié)合上文,思考一下大致可以明白這個(gè)函數(shù)的作用了:分發(fā)中斷。即所有的異常與中斷觸發(fā)后均會執(zhí)行這個(gè)函數(shù),那我們?nèi)炙阉饕幌逻@個(gè)函數(shù),找一下是哪里調(diào)用了.全局搜索可以找到如下代碼:
CPPFLAGS += -DportasmHANDLE_INTERRUPT=vSystemIrqHandler
繼續(xù)搜索DportasmHANDLE_INTERRUPT可從portASM.S找到如下代碼;
load_x sp, xISRStackTop /* Switch to ISR stack before function call. */
jal portasmHANDLE_INTERRUPT
j processed_source
瀏覽代碼可知,系統(tǒng)觸發(fā)中斷或異常后最終均會執(zhí)行vSystemIrqHandler函數(shù),該函數(shù)是在freertos_risc_v_trap_handler函數(shù)中被調(diào)用,至此我們大致可以明白整個(gè)過程了,當(dāng)中斷或者異常出發(fā)后,會查詢向量表,執(zhí)行freertos_risc_v_trap_handler函數(shù),該函數(shù)會調(diào)用vSystemIrqHandler,經(jīng)其分發(fā)后最終執(zhí)行到系統(tǒng)初始化時(shí)綁定的中斷入口函數(shù)。
在RT-Thread 中由interrupt_gcc.S中的函數(shù)實(shí)現(xiàn)vSystemIrqHandler函數(shù)的調(diào)用,所以我們修改中斷向量表的內(nèi)容如下:
vector_table:
j IRQ_Handler // irq0
j IRQ_Handler
j IRQ_Handler
j IRQ_Handler // irq3
j IRQ_Handler
j IRQ_Handler
j IRQ_Handler
j IRQ_Handler //ctxt_handler // irq 7 mtime or timer
j IRQ_Handler
j IRQ_Handler
j IRQ_Handler // IRQ_Handler
j IRQ_Handler // irq 11 Machine (event Fifo)
j IRQ_Handler
j IRQ_Handler
j IRQ_Handler
j IRQ_Handler
j IRQ_Handler // IRQ16
j IRQ_Handler // IRQ17
j IRQ_Handler // IRQ18
j IRQ_Handler // IRQ19
j IRQ_Handler // IRQ20
j IRQ_Handler // IRQ21
j IRQ_Handler // IRQ22
j IRQ_Handler // IRQ23
j IRQ_Handler // IRQ24
j IRQ_Handler // IRQ25
j IRQ_Handler // IRQ26
j IRQ_Handler // IRQ27
j IRQ_Handler // IRQ28
j IRQ_Handler // IRQ29
j IRQ_Handler // IzRQ30
j IRQ_Handler // IRQ30
瀏覽IRQ_Handler可知,觸發(fā)中斷或異常均會執(zhí)行該代碼,該函數(shù)的主要的功能就是實(shí)現(xiàn)了軟件保存上下文。根據(jù)RISC-V規(guī)范可知,RISC-V架構(gòu)定義不支持硬件壓棧,所以需要軟件實(shí)現(xiàn)這部分,這樣做的出發(fā)點(diǎn)大概是為了簡化RISC-V架構(gòu)的內(nèi)核的設(shè)計(jì)吧?。?!
libcpu/riscv/rv32m1中的IRQ_Handler函數(shù)存在如下內(nèi)容
/* switch to interrupt stack */
la sp, __stack // 移植時(shí)需修改
/* interrupt handle */
call rt_interrupt_enter
csrr a0, mcause
csrr a1, mepc
mv a2, sp
call SystemIrqHandler // 移植時(shí)需修改
call rt_interrupt_leave
這部分的作用是,加載中斷棧的棧頂?shù)刂放c執(zhí)行保存上文之后的工作,這里是調(diào)用SystemIrqHandler函數(shù)進(jìn)行中斷分發(fā),修改如下:
/* switch to interrupt stack */
la sp, __freertos_irq_stack_top //棧頂?shù)刂?位于鏈接腳本中
/* interrupt handle */
call rt_interrupt_enter
csrr a0, mcause
csrr a1, mepc
mv a2, sp
call vSystemIrqHandler // 調(diào)用vSystemIrqHandler函數(shù)
call rt_interrupt_leave
在board.c的rt_hw_board_init函數(shù)中,添加如下代碼:
vPortSetupTimerInterrupt();//初始化定時(shí)器
volatile uint32_t mtvec = 0;
__asm volatile( "csrr %0, mtvec" : "=r"( mtvec ) );//聲明僅有一張向量表
__asm volatile( "csrs mie, %0" :: "r"(0x880) );//使能定時(shí)器中斷與外部中斷
至此,基本的移植工作已經(jīng)完成,可以采用靜態(tài)創(chuàng)建任務(wù)的方式,實(shí)現(xiàn)多任務(wù)的創(chuàng)建與調(diào)度。
動態(tài)內(nèi)存
在board.c添加下述代碼:
#if defined(RT_USING_USER_MAIN) && defined(RT_USING_HEAP)
#define RT_HEAP_SIZE (64*1024)
static rt_uint8_t rt_heap[RT_HEAP_SIZE];
void *rt_heap_begin_get(void)
{
return rt_heap;
}
void *rt_heap_end_get(void)
{
return rt_heap + RT_HEAP_SIZE;
}
#endif
在rt_hw_board_init添加下述代碼:
#if defined(RT_USING_USER_MAIN) && defined(RT_USING_HEAP)
rt_system_heap_init(rt_heap_begin_get(), rt_heap_end_get());
#endif
完成上述工作便可使用RT-Thread的動態(tài)內(nèi)存相關(guān)接口,同樣可以種動態(tài)創(chuàng)建線程的函數(shù)。
shell
在鏈接腳本的.text段添加如下內(nèi)容
/* section information for finsh shell */
. = ALIGN(4);
__fsymtab_start = .;
KEEP(*(FSymTab))
__fsymtab_end = .;
. = ALIGN(4);
__vsymtab_start = .;
KEEP(*(VSymTab))
__vsymtab_end = .;
. = ALIGN(4);
實(shí)現(xiàn)以下函數(shù):
char rt_hw_console_getchar(void)
{
return udma_uart_getchar(0);
}
void rt_hw_console_output(const char str)
{
writeraw(0, strlen(str), (uint8_t )str);
}
注:writeraw函數(shù)來自udma_uart_writeraw,去掉了其中涉及FreeRTOS的API.
完成上述部分便可以使用RT-Thread的shell。
自動初始化
在鏈接腳本的.text段添加如下內(nèi)容:
/* section information for initial. */
. = ALIGN(4);
__rt_init_start = .;
KEEP(*(SORT(.rti_fn*)))
__rt_init_end = .;
. = ALIGN(4);
添加上述代碼后便可使用RT-Thread的自動初始化接口。
結(jié)果驗(yàn)證
在生成的目標(biāo)文件的目錄下,輸入運(yùn)行命令,示例命令:
/home/wangshun/bin/qemu-riscv/bin/qemu-system-riscv32 -M core_v_mcu -bios none -kernel rtthread.elf -nographic -monitor none -serial stdio
使用時(shí),/home/wangshun/bin/qemu-riscv/bin/修改為用戶的qemu的路徑。
運(yùn)行結(jié)果如下:
| /
RT - Thread Operating System
/ | 5.0.0 build Dec 16 2022 11:25:07
2006 - 2022 Copyright by RT-Thread team
Hello RT-Thread
msh >help
RT-Thread shell commands:
finshToCLI - Switch to CLI: CLI component of Core-V-MCU
pin - pin [option]
clear - clear the terminal screen
version - show RT-Thread version information
list - list objects
help - RT-Thread shell help.
ps - List threads in the system.
free - Show the memory usage in the system.
msh >ps
thread pri status sp stack size max used left tick error
tshell 20 running 0x00000160 0x00001000 22% 0x00000009 OK
tidle0 31 ready 0x000000f0 0x00000100 98% 0x1c05e62d OK
timer 4 suspend 0x000000e0 0x00000200 43% 0x00000008 OK
msh >
至此,移植RT-Thread至OPENHW開源的基于CV32E40P內(nèi)核的CORE-V-MCU便移植完成。
CORE-V-MCU bsp
core-v-mcu的bsp已經(jīng)合并至RT-Thread的主倉庫,并配有詳細(xì)的使用說明,鏈接。
小結(jié)
在為RISC-V移植RTOS時(shí),筆者認(rèn)為要具備RISC-V架構(gòu)規(guī)范與編程規(guī)范的基本的了解,磨刀不誤砍柴工嘛,雖然在寫本文時(shí)洋洋灑灑寫的很自在,但是在移植的過程中每遇到一個(gè)坑就會卡好久,不過也不要妄自菲薄,經(jīng)過這么一個(gè)過程就好很多了,此時(shí)不禁讓人感慨“兩岸猿聲啼不住,輕舟已過萬重山”。
-
定時(shí)器
+關(guān)注
關(guān)注
23文章
3297瀏覽量
117506 -
MCU控制
+關(guān)注
關(guān)注
0文章
48瀏覽量
6945 -
RT-Thread
+關(guān)注
關(guān)注
32文章
1380瀏覽量
41630 -
Ubuntu系統(tǒng)
+關(guān)注
關(guān)注
0文章
91瀏覽量
4263 -
qemu
+關(guān)注
關(guān)注
0文章
57瀏覽量
5649
發(fā)布評論請先 登錄
i.MX RT1170:VGLite移植RT-Thread Nano過程講解(上)

OPENHW開源CORE-V-MCU移植RT-Thread的流程
移植RT-Thread的原理與方法
【國產(chǎn)MCU移植】HC32F460基于Keil MDK 移植 RT-Thread Nano

【國產(chǎn)MCU系列】在 HK32F030 上移植 RT-Thread Nano

【國產(chǎn)MCU移植】移植RT-Thread到國產(chǎn)芯片HC32F460PETB

【國產(chǎn)MCU移植】移植RT-Thread到國產(chǎn)芯片F(xiàn)M33LC026

基于 Keil MDK 移植 RT-Thread Nano

RT-Thread操作系統(tǒng)的FreeRTOS兼容層
OPENHW開源CORE-V-MCU移植RT-Thread
機(jī)智云設(shè)備移植RT-Thread

評論