如何移植freertos到一块riscv 板子上。
移植FreeRTOS到RISC-V板子的指南
在这篇博客中,我们将深入探讨如何将FreeRTOS移植到RISC-V开发板上,帮助你了解整个过程的每个步骤及其挑战。
1. 前期准备
首先,需要准备开发环境,包括GCC交叉编译工具链(RISC-V GNU工具链)、RISC-V架构的开发板或者仿真器(如QEMU)以及FreeRTOS源码。你还需要准备一份开发板的手册,以便了解硬件的具体细节,包括内存映射、中断控制器和引导设置等。博主使用的是玄铁cdk,cklink,debugserver。
2. 理解FreeRTOS移植层
FreeRTOS的移植主要集中在其硬件抽象层(HAL)和启动代码部分。你需要理解port.c
和portASM.S
文件的内容。port.c
负责实现与硬件相关的功能,例如上下文切换、任务调度等,而portASM.S
则用于处理汇编级别的上下文切换代码。如果你的板子没有适配freertos(很少出现这种情况),很遗憾,你需要自己实现port.c与portASM.c。
port.c
文件
-
这个文件中包含与任务调度和上下文切换相关的代码,尤其是FreeRTOS任务切换所需的C语言逻辑。
-
需要在
port.c
中实现的功能包括:
- 上下文切换函数:例如
vPortYield()
,用于手动切换任务。(这个vPortYield,尤其重要,它是在比如任务切换中进行cpu使用权的转移) - 节拍计时器设置:确保定时器中断能够用于系统节拍。(保证你的一秒是一秒)
- 上下文切换函数:例如
通常可以找到FreeRTOS的port.c
模板代码,你可以基于此进行修改,以适配你所使用的特定RISC-V芯片。
2. portASM.S
文件
-
portASM.S
中主要是汇编代码,用于上下文保存和恢复。这对于实现任务切换至关重要,因为RISC-V有自己独特的寄存器集合和指令集。 -
主要需要实现的功能包括:
-
上下文保存:保存当前任务的所有寄存器(包括
x0
到x31
,以及状态寄存器等)。 -
上下文恢复:从新任务的栈中恢复这些寄存器,以切换到该任务执行。
-
中断入口:确保硬件中断能跳转到正确的处理例程,并正确保存/恢复状态。
通常情况下,port.c与portASM.S的内容功能都差不多,只需要依据你板子实现相应的相似的功能即可。
-
3. 配置startup代码
startup.S
文件是系统启动的关键,涉及到堆栈的初始化和中断向量表的设置。确保正确地配置了MTVEC
(机器模式中断向量寄存器),将其指向你编写的中断服务例程的入口地址。所有的中断源,包括软中断和时钟中断,都需要在这里配置以便正确触发。一定要设置好MTVEC,否则比如在Task切换的时候,会因为找不到中断处理地址找不到你的软中断函数,(除了你自己自定义的中断处理函数,这可能会让你十分疑惑)。
4. 中断和任务切换
FreeRTOS通过定时器中断(通常是SysTick
)来实现任务调度。在RISC-V中,你需要使用机器定时器(MTIME)或平台特有的定时器。确保在mie
寄存器中使能了对应的中断源,并且在mstatus
寄存器中使能全局中断。使用csrs mstatus, (1 << 3)
来设置全局中断使能位。
5. 修改freertosConfig.c
在将FreeRTOS移植到RISC-V的过程中,FreeRTOSConfig.h
文件是非常重要的配置文件,用于定制化RTOS的行为。根据你的开发板和目标应用场景,你可能需要对FreeRTOSConfig.h
进行一些定制化修改,以下是一些关键的配置选项:
1. configCPU_CLOCK_HZ
- 配置CPU频率:设置CPU的频率,这样RTOS可以根据系统时钟进行准确的计时和任务调度。
- 例如:
#define configCPU_CLOCK_HZ (80000000UL) // 设置为你的RISC-V板子的频率
2. configTICK_RATE_HZ
- 配置系统节拍频率:设置RTOS节拍频率,通常为1000Hz(1ms的时间片),但也可以根据应用需要进行调整。
#define configTICK_RATE_HZ (1000) // 每秒钟1000次节拍中断
3. configMAX_PRIORITIES
- 最大任务优先级:设定系统中可用的最大任务优先级,以适应你的应用程序中的任务调度复杂性。
#define configMAX_PRIORITIES (5) // 任务优先级的数量
4. configMINIMAL_STACK_SIZE
和 configTOTAL_HEAP_SIZE
- 最小栈大小:设定任务使用的最小栈大小,通常与任务复杂性和函数调用深度相关。
- 堆大小:设置FreeRTOS分配给任务、队列等动态资源使用的堆大小。
#define configMINIMAL_STACK_SIZE (128) // 单位:字 #define configTOTAL_HEAP_SIZE (10240) // 单位:字节
5. configUSE_PREEMPTION
- 抢占式调度:启用或禁用抢占式调度。如果你需要FreeRTOS基于优先级抢占任务,请设置为
1
。#define configUSE_PREEMPTION 1 // 启用抢占式调度
6. configUSE_TIME_SLICING
- 时间片调度:配置是否对同优先级任务进行时间片轮转。如果多个任务具有相同优先级,启用时间片可以确保公平性。
#define configUSE_TIME_SLICING 1 // 启用时间片调度
7. configKERNEL_INTERRUPT_PRIORITY
和 configMAX_SYSCALL_INTERRUPT_PRIORITY
- 中断优先级:设定内核和系统调用的中断优先级。对于RISC-V,需要确保这些优先级与硬件要求匹配,并且符合FreeRTOS对中断优先级的要求。
#define configKERNEL_INTERRUPT_PRIORITY 255 #define configMAX_SYSCALL_INTERRUPT_PRIORITY 191 // 中断优先级,较低值表示较高优先级
8. configUSE_MUTEXES
和 configUSE_COUNTING_SEMAPHORES
- 启用互斥锁和信号量:根据应用需求,决定是否使用这些同步原语。
#define configUSE_MUTEXES 1 #define configUSE_COUNTING_SEMAPHORES 1
9. configASSERT
- 调试辅助:使用
configASSERT
来在开发阶段帮助发现错误。#define configASSERT( x ) if( ( x ) == 0 ) { taskDISABLE_INTERRUPTS(); for( ;; ); }
10. configUSE_TICK_HOOK
和 configUSE_IDLE_HOOK
- 钩子函数:可以选择启用空闲任务钩子或节拍钩子函数,用于执行一些后台或周期性任务。
#define configUSE_TICK_HOOK 0 #define configUSE_IDLE_HOOK 1 // 启用空闲钩子函数
11. configUSE_PORT_OPTIMISED_TASK_SELECTION
- 任务选择优化:对于RISC-V这样有较多寄存器的处理器,可以启用优化的任务选择算法。
#define configUSE_PORT_OPTIMISED_TASK_SELECTION 1
记得特别关注栈与堆的设置,因为一些板子,他的栈与堆的容量特别小,导致虽然你可以创建task,但是在真正启动的时候,会因为一些堆栈的溢出,甚至说freertos在ram中的写入时出先问题,但是这些问题的报错都是memalloc error,会让你十分疑惑。
6. 调试与验证
在编译之前,你需要依据你板子的内存情况调整link文件,因为如果你的板子是特殊型号的,你的内存可能非常小或者说特殊,你需要参考别人的link文件来调整存储的排布。移植完成后,首先是编译,可能是freertos的本身的bug,我在编译的时候,需要加上一个-fno-strict-aliasing来放宽别名的限制,否则又会爆出一些奇怪的错误。最后,你需要使用调试工具(如OpenOCD)对整个移植过程进行调试。首先,检查系统是否可以进入main()
函数并启动第一个任务。其次,验证定时器中断和任务切换是否正常工作。如果系统无法正确启动,可能需要查看中断是否被使能,以及上下文切换代码是否存在错误。
7. 结束语
将FreeRTOS移植到RISC-V板子上需要对处理器架构、启动代码以及RTOS的内部机制有深入的理解。虽然这个过程充满挑战,但完成后,你将对系统的底层硬件有更加深刻的认识,并且能够为你的应用程序构建一个高度可定制的实时操作系统环境。
希望这篇指南对你有所帮助,祝你移植顺利!