Toggle navigation
首页
问答
文章
积分商城
专家
专区
更多专区...
文档中心
返回主站
搜索
提问
会员
中心
登录
注册
裸机工程移植RT-Thread
risc-v_RISCV
OPENHW开源CORE-V-MCU移植RT-Thread
5.00
发布于 2023-01-16 18:18:17 浏览:1323
订阅该版
[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 运行结果: ![screenshot_1.png](https://oss-club.rt-thread.org/uploads/20230116/a1e7b036a56118ae5911cfd1a8137e45.png.webp) 出现上述结果,则表示示例工程运行正常,这样我们就有一个可移植的模板工程,后续移植基于该工程开展。 #### 移植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
条评论
默认排序
按发布时间排序
登录
注册新账号
关于作者
rv666
用GDB调试人生
文章
20
回答
62
被采纳
9
关注TA
发私信
相关文章
1
裸机工程移植 RT-Thread内核
2
有木有移植rt-thread(nano)到riscv 32位MCU上
3
关于rt-htread的移植问题
4
移植rtthread nano到CH32V307问题
5
如何让RT thread适配一款新的芯片?
6
EGGER Embedded Studio for ARM 环境下 stm32 移植rt-thread,有详细教程吗?
7
在移植的时候发现卡死
8
studio能否支持risc-v的工程,包括调试。
9
移植rt-nano至risc-v时,无法在main函数创建用户线程
10
risc-v移植rtthread,程序莫名跳转到异常Exception
推荐文章
1
RT-Thread应用项目汇总
2
玩转RT-Thread系列教程
3
国产MCU移植系列教程汇总,欢迎查看!
4
机器人操作系统 (ROS2) 和 RT-Thread 通信
5
五分钟玩转RT-Thread新社区
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
串口
Env
LWIP
SPI
AT
Bootloader
Hardfault
CAN总线
FinSH
ART-Pi
USB
DMA
文件系统
RT-Thread
SCons
RT-Thread Nano
线程
MQTT
STM32
RTC
FAL
rt-smart
I2C_IIC
ESP8266
UART
WIZnet_W5500
ota在线升级
PWM
cubemx
flash
freemodbus
BSP
packages_软件包
潘多拉开发板_Pandora
定时器
ADC
flashDB
GD32
socket
编译报错
中断
Debug
rt_mq_消息队列_msg_queue
SFUD
msh
keil_MDK
ulog
C++_cpp
MicroPython
本月问答贡献
踩姑娘的小蘑菇
1
个答案
2
次被采纳
用户名由3_15位
7
个答案
1
次被采纳
xusiwei1236
5
个答案
1
次被采纳
bernard
4
个答案
1
次被采纳
张世争
1
个答案
1
次被采纳
本月文章贡献
聚散无由
2
篇文章
15
次点赞
catcatbing
2
篇文章
5
次点赞
Wade
2
篇文章
2
次点赞
Ghost_Girls
1
篇文章
6
次点赞
YZRD
1
篇文章
2
次点赞
回到
顶部
发布
问题
投诉
建议
回到
底部