Toggle navigation
首页
问答
文章
积分商城
专家
专区
更多专区...
文档中心
返回主站
搜索
提问
会员
中心
登录
注册
裸机工程移植RT-Thread
risc-v_RISCV
OPENHW开源CORE-V-MCU移植RT-Thread
5.00
发布于 2023-01-16 18:18:17 浏览:957
订阅该版
[tocm] #### 项目背景 [OpenHW Group](https://www.openhwgroup.org/) 是一个以协作方式开发开源硬件和相关软件的非营利组织,致力于开发、验证和提供开源处理器内核。OpenHW Group的开源项目致力于开发和验证基于免费和开放的[RISC-V](http://riscv.org/)指令集架构 (ISA) 系列内核,称为 CORE-V系列。[CV32E40P](https://www.openhwgroup.org/resources/blog/open-source-hardware-the-new-reality-cv32e40p-project/) 开源处理器 IP 内核,这是 OpenHW CORE-V 系列中第一个经过全面验证的内核。本文的对象就是基于CV32E40P实现的开源CORE-V-MCU移植[RT-Thread](https://www.rt-thread.org/)。 CORE-V-MCU目前是以软核的形式在FPGA上进行了实现,中科院 PLCT 实验室针对CV32E40P内核的CORE-V-MCU适配了QEMU,本文最终的验证在QEMU上进行。 #### 相关链接 [CORE-V-MCU IDE](https://github.com/openhwgroup/core-v-sdk):OPENHW组织针对CORE-V-MCU系列,基于eclipse开发的集成环境,IDE内置示例工程。 [PLCT QEMU](https://github.com/plctlab/plct-qemu/tree/plct-corev-upstream-sync-dma):中科院PLCT实验室针对CV32E40P内核的CORE-V-MCU适配的QEMU。 [Linux环境下的QEMU]([Release qemu-linux · Yaochenger/openhw- · GitHub](https://github.com/Yaochenger/openhw-/releases/tag/qemu-linux)):Linux环境下编译完成的QEMU。 [工具链](https://github.com/Yaochenger/openhw-/tree/master/toolchain):工具链。 [RT-Thread源码](https://github.com/RT-Thread/rt-thread/archive/refs/heads/master.zip):RT-Thread源码。 [RT-Thread-nano源码](https://www.rt-thread.org/download.html):RT-Thread nano源码。 #### 前期准备 开发环境:ubuntu18.04 #### 验证示例工程 本次实验验证的平台是PLCT提供的QEMU,在Linux下的QEMU可以使用上述的笔者编译好的,也可以使用自己尝试编译PLCT提供的源码。 OPENHW提供了基于FreeRTOS的示例工程,由于使用的是PLCT提供的QEMU,所以IDE中自带的工程并不能直接使用,为了避免不必要的麻烦,本文采用PLCT提供的[示例工程](https://github.com/Yaochenger/openhw-/tree/master/%E5%8F%AF%E6%89%A7%E8%A1%8C%E6%96%87%E4%BB%B6/PLCT)。 下载好示例工程,进入到app目录,执行以下命令配置编译工程: > source ../env/core-v-mcu.sh > > make RISCV=xxx //xxx为工具链的路径 编译完成后将生成的**cli_test**可执行文件拷到qemu的安装目录,运行下述命令验证工程; > ./qemu-system-riscv32 -M core_v_mcu -bios none -kernel cli_test -nographic -monitor none -serial stdio 运行结果:  出现上述结果,则表示示例工程运行正常,这样我们就有一个可移植的模板工程,后续移植基于该工程开展。 #### 移植RT-Thread CV32E40P内核是一个RISC-V架构的内核,移植RTOS不可避免的会涉及到汇编部分的修改,所以在移植前期学习一下RISC-V架构与汇编会产生事半功倍的效果,同样在一之前需要熟悉[CV32E40P的内核资源](https://docs.openhwgroup.org/projects/core-v-mcu/doc-src/overview.html)。 cv32e40p继承自pulp开源的RI5CY内核,而RI5CY内核的对接代码在RT-Thread的仓库[libcpu/riscv/rv32m1](https://github.com/Yaochenger/rt-thread/tree/master/libcpu/risc-v)已经实现,所以以RI5CY中的代码为基础,移植cv32e40p的对接代码。 成功移植RT-Thread有如下几个关键的阶段: - 节拍定时器正常工作 - 线程可以正常创建,调度(这步最难,需要前面好多工作来保证) - shell可以正常使用(从这开始就顺利多了) RTOS的最小时间单位是系统节拍,系统正常工作需要节拍定时器的支持,因而以节拍定时器的移植为切入点展开移植。 **core-v-mcu.c**中放置的是系统初始化,中断处理相关代码。其中**system_init**函数完成了时钟初始化,串口,I2C等外设的初始化,以及中断函数的绑定。在文件的开头可以找到如下一个指针数组: ````C void (*isr_table[32])(uint32_t); ```` 该数组用于绑定中断入口函数。 ```C 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**函数中我们可以找到上述代码,浏览该文件我们可以知道,**timer_irq_handler**即系统定时器的中断入口函数, 在原有FreeRTOS工程中该函数内容如下: ```c void timer_irq_handler(uint32_t mcause) { #warning requires critical section if interrupt nesting is used. if (xTaskIncrementTick() != 0) { vTaskSwitchContext(); } } ``` 该函数实现系统节拍的产生,所以将这里的内容修改为RT-Thread的节拍产生的方式,修改如下: ```c 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(); } ``` 涉及到中断,我们就得考虑系统的中断的实现方式,中断一般会分为向量中断与非向量中断。RISC-V常在启动后文件中进行中断模式的配置。从**crt0.S**文件可以找到如下代码: ```assembly /* set vector table address */ la a0, __vector_start or a0, a0, 1 /* enable vectored mode (hardcoded anyway for CV32E40P) */ csrw mtvec, a0 ``` RISC-V规范定义中断、异常的入口地址,以及模式使用**mtvec**寄存器配置,上述代码将系统的中断模式配置为了向量模式,向量模式下,每个函数均包含一个独立的入口函数用来处理中断。所以我们切换至写中断向量表的文件**vector.S**,我们可以很明显的看到一段代码,从名字就可以看出下面是一张向量表 : ```assembly 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**这个文件,又会发现一段非常显眼的代码: ```c void vSystemIrqHandler(uint32_t mcause) { uint32_t val = 0; // extern void (*isr_table[32])(uint32_t); isr_table[mcause & 0x1f](mcause & 0x1f); } ``` 结合上文,思考一下大致可以明白这个函数的作用了:分发中断。即所有的异常与中断触发后均会执行这个函数,那我们全局搜索一下这个函数,找一下是哪里调用了.全局搜索可以找到如下代码: ```c CPPFLAGS += -DportasmHANDLE_INTERRUPT=vSystemIrqHandler ``` 继续搜索**DportasmHANDLE_INTERRUPT**可从**portASM.S**找到如下代码; ```assembly load_x sp, xISRStackTop /* Switch to ISR stack before function call. */ jal portasmHANDLE_INTERRUPT j processed_source ``` 浏览代码可知,系统触发中断或异常后最终均会执行**vSystemIrqHandler**函数,该函数是在**freertos_risc_v_trap_handler**函数中被调用,至此我们大致可以明白整个过程了,当中断或者异常出发后,会查询向量表,执行**freertos_risc_v_trap_handler**函数,该函数会调用**vSystemIrqHandler**,经其分发后最终执行到系统初始化时绑定的中断入口函数。 在RT-Thread 中由interrupt_gcc.S中的函数实现**vSystemIrqHandler**函数的调用,所以我们修改中断向量表的内容如下: ```C 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**可知,触发中断或异常均会执行该代码,该函数的主要的功能就是实现了软件保存上下文。根据RISC-V规范可知,RISC-V架构定义不支持硬件压栈,所以需要软件实现这部分,这样做的出发点大概是为了简化RISC-V架构的内核的设计吧!!! libcpu/riscv/rv32m1中的**IRQ_Handler**函数存在如下内容 ```c /* switch to interrupt stack */ la sp, __stack // 移植时需修改 /* interrupt handle */ call rt_interrupt_enter csrr a0, mcause csrr a1, mepc mv a2, sp call SystemIrqHandler // 移植时需修改 call rt_interrupt_leave ``` 这部分的作用是,加载中断栈的栈顶地址与执行保存上文之后的工作,这里是调用**SystemIrqHandler**函数进行中断分发,修改如下: ```c /* switch to interrupt stack */ la sp, __freertos_irq_stack_top //栈顶地址 位于链接脚本中 /* interrupt handle */ call rt_interrupt_enter csrr a0, mcause csrr a1, mepc mv a2, sp call vSystemIrqHandler // 调用vSystemIrqHandler函数 call rt_interrupt_leave ``` 在**board.c**的**rt_hw_board_init**函数中,添加如下代码: ``` vPortSetupTimerInterrupt();//初始化定时器 volatile uint32_t mtvec = 0; __asm volatile( "csrr %0, mtvec" : "=r"( mtvec ) );//声明仅有一张向量表 __asm volatile( "csrs mie, %0" :: "r"(0x880) );//使能定时器中断与外部中断 ``` 至此,基本的移植工作已经完成,可以采用静态创建任务的方式,实现多任务的创建与调度。 #### 动态内存 在**board.c**添加下述代码: ```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**添加下述代码: ```c #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的动态内存相关接口,同样可以种动态创建线程的函数。 #### shell 在链接脚本的**.text**段添加如下内容 ```makefile /* section information for finsh shell */ . = ALIGN(4); __fsymtab_start = .; KEEP(*(FSymTab)) __fsymtab_end = .; . = ALIGN(4); __vsymtab_start = .; KEEP(*(VSymTab)) __vsymtab_end = .; . = ALIGN(4); ``` 实现以下函数: ```c char rt_hw_console_getchar(void) { return udma_uart_getchar(0); } ``` ```c void rt_hw_console_output(const char *str) { writeraw(0, strlen(str), (uint8_t*)str); } ``` 注:**writeraw**函数来自**udma_uart_writeraw**,去掉了其中涉及FreeRTOS的API. 完成上述部分便可以使用RT-Thread的shell。 #### 自动初始化 在链接脚本的**.text**段添加如下内容: ``` /* section information for initial. */ . = ALIGN(4); __rt_init_start = .; KEEP(*(SORT(.rti_fn*))) __rt_init_end = .; . = ALIGN(4); ``` 添加上述代码后便可使用RT-Thread的自动初始化接口。 #### 结果验证 在生成的目标文件的目录下,输入运行命令,示例命令: ```shell /home/wangshun/bin/qemu-riscv/bin/qemu-system-riscv32 -M core_v_mcu -bios none -kernel rtthread.elf -nographic -monitor none -serial stdio ``` 使用时,`/home/wangshun/bin/qemu-riscv/bin/`修改为用户的qemu的路径。 **运行结果如下**: ```shell \ | / - 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内核的CORE-V-MCU便移植完成。 #### CORE-V-MCU bsp core-v-mcu的bsp已经合并至RT-Thread的主仓库,并配有详细的使用说明,[链接](https://github.com/Yaochenger/rt-thread/tree/master/bsp/core-v-mcu/core-v-cv32e40p)。 #### 小结 在为RISC-V移植RTOS时,笔者认为要具备RISC-V架构规范与编程规范的基本的了解,磨刀不误砍柴工嘛,虽然在写本文时洋洋洒洒写的很自在,但是在移植的过程中每遇到一个坑就会卡好久,不过也不要妄自菲薄,经过这么一个过程就好很多了,此时不禁让人感慨“两岸猿声啼不住,轻舟已过万重山”。
7
条评论
默认排序
按发布时间排序
登录
注册新账号
关于作者
药RV
调网络不抓包,调I2C等时序不上逻辑分析仪,就像电工不用万用表!多用整理的好的文字,比截图更省流量,还能在整理过程中思考。
文章
12
回答
10
被采纳
0
关注TA
发私信
相关文章
1
裸机工程移植 RT-Thread内核
2
有木有移植rt-thread(nano)到riscv 32位MCU上
3
关于rt-htread的移植问题
4
移植rtthread nano到CH32V307问题
5
studio能否支持risc-v的工程,包括调试。
6
移植rt-nano至risc-v时,无法在main函数创建用户线程
7
risc-v移植rtthread,程序莫名跳转到异常Exception
8
GD32VF103出现to free a bad data block:错误
9
rtt os riscv Store address misaligned异常
10
仅实现机器模式的芯片是否可以移植RT-THREAD系统
推荐文章
1
RT-Thread应用项目汇总
2
玩转RT-Thread系列教程
3
机器人操作系统 (ROS2) 和 RT-Thread 通信
4
五分钟玩转RT-Thread新社区
5
国产MCU移植系列教程汇总,欢迎查看!
6
【技术三千问】之《玩转ART-Pi》,看这篇就够了!干货汇总
7
关于STM32H7开发板上使用SDIO接口驱动SD卡挂载文件系统的问题总结
8
STM32的“GPU”——DMA2D实例详解
9
RT-Thread隐藏的宝藏之completion
10
【ART-PI】RT-Thread 开启RTC 与 Alarm组件
热门标签
RT-Thread Studio
串口
LWIP
Env
AT
SPI
Bootloader
FinSH
ART-Pi
CAN总线
Hardfault
USB
文件系统
RT-Thread
DMA
SCons
线程
MQTT
RT-Thread Nano
STM32
RTC
rt-smart
ESP8266
flash
ota在线升级
WIZnet_W5500
FAL
I2C
packages_软件包
UART
cubemx
freemodbus
潘多拉开发板_Pandora
定时器
BSP
PWM
ADC
socket
中断
rt_mq_消息队列_msg_queue
keil_MDK
SDIO
Debug
AB32VG1
MicroPython
C++_cpp
编译报错
msh
ulog
QEMU
本月问答贡献
出出啊
1500
个答案
338
次被采纳
小小李sunny
1390
个答案
276
次被采纳
张世争
715
个答案
157
次被采纳
crystal266
522
个答案
153
次被采纳
whj467467222
1216
个答案
146
次被采纳
本月文章贡献
出出啊
1
篇文章
12
次点赞
小小李sunny
1
篇文章
1
次点赞
张世争
2
篇文章
2
次点赞
crystal266
2
篇文章
5
次点赞
whj467467222
2
篇文章
1
次点赞
回到
顶部
发布
问题
投诉
建议
回到
底部