最近研究了一段时间的rt-thread,相对来说还是算比较有追求的设计。从风格上讲,与Linux类似,很多设计思想也是借鉴Linux,比如设备驱动;从设计上来说,精简,高效易扩展,做了很多不错的中间件。
当然最主要的工作还是集中于IMX8QXP,A核就不多说了,都是Linux的传统内容,开发也都搞定了。对其内部的M核比较感兴趣,就想着尝试移植一下rt-thread,一来挑战一下,二来检验一下学习成果。虽然之前在STM32上移植过rt-thread,不过太过easy,感觉没什么意思。
移植前,首先看一下IMX8内部的架构图
其中有两个M4,左下角的称之为SCU,用于资源及clock管理,不对外开放。上面的一个M4才是我们所能用的。
无论A核还是M4,都必须通过SCU来进行资源及clock管理。SCU的SDK不开放,只提供相关API。
内部的M4默认运行的系统为FreeRTOS,个人对其极不喜欢,也是我移植rt-thread的一个原因,O(∩_∩)O哈哈~
首先看一下FreeRTOS的SDK代码结构:
原始SDK中提供了很多裸机example以及包含FreeRtos的demo,移植思路就从这些demo开始。
首先我们需要将rt-thread的源码拷贝进rtos下并为其新建目录rt-thread,同时还需要修改编译配置文件,加入编译过程。
将hello也拷贝一份,相关FreeRTOS重命名为rt-thread。
原始SDK使用的Cmake编译,入口为相关的裸机demo以及rtos demo,我们选择rtos_examples下的hello为例,移植后只要能见到“hello world”,世界从此就光明了。当然见到光明之前,必定先经历一片漆黑。
hello下的armgcc文件夹即为编译配置,对应Cmakelists.txt,在其中加入rt-thread相关源码以及修改hello相关文件。
编译问题不多说,遇到问题修复问题即可。基本都是文件路径以及先后问题,没什么难度。
下面看一下入口main函数,原始代码如下:
/*!
* @brief Application entry point.
*/
int main(void)
{
/* Init board hardware. */
sc_ipc_t ipc;
ipc = BOARD_InitRpc();
BOARD_InitPins(ipc);
BOARD_BootClockRUN();
BOARD_InitDebugConsole();
BOARD_InitMemory();
if (xTaskCreate(hello_task, "Hello_task", configMINIMAL_STACK_SIZE + 100, NULL, hello_task_PRIORITY, NULL) !=
pdPASS)
{
PRINTF("Task creation failed!.\r\n");
while (1)
;
}
vTaskStartScheduler();
for (;;)
;
}
可见BOARD_XX开头的5个函数已经将硬件环境准备好了,后面直接创建task即可。基于此,我们的适配工作好像很简单。貌似只需要初始化systick即可。由于FreeRTOS systick初始化行为在其源码中,rt-thread需要在外部进行。因此我们得自行实现systick初始化,有两个选择,第一SDK中已有API可调用,第二借用FreeRTOS初始化代码。有systick中断,必定有中断处理好函数,rt-thread已经有标准的实现参考,通常我们放到board.c文件
void SysTick_Handler(void)
{
/* enter interrupt */
rt_interrupt_enter();
rt_tick_increase();
/* leave interrupt */
rt_interrupt_leave();
}
建议移植时,分小步进行,先确认systick是否运行OK,然后再继续。可在Systick_handler中打印log检测,同时屏蔽rtos API。
一般来说systick基本不会有什么问题。
接着往下,有了systick就有任务切换,代码位于:rtos/rt-thread/kernel/libcpu/arm/cortex-m4/context_gcc.S,也不需要变动。
rt-thread初始化过程中有一个重要的板级初始化过程rt_hw_board_init
//for rt-thread
void rt_hw_board_init()
{
main_scu_init();
/* Call components board initial (use INIT_BOARD_EXPORT()) */
#ifdef RT_USING_COMPONENTS_INIT
rt_components_board_init();
#endif
#if defined(RT_USING_USER_MAIN) && defined(RT_USING_HEAP)
rt_system_heap_init(heap, sizeof(heap));
#endif
}
前面提到的5个硬件初始化函数BOARD_XX就是在这里进行
void main_scu_init(void)
{
/* Init board hardware. */
sc_ipc_t ipc;
ipc = BOARD_InitRpc();
BOARD_InitPins(ipc);
BOARD_BootClockRUN();
BOARD_InitDebugConsole();
BOARD_InitMemory();
PRINTF("Hello world. main_scu_init\r\n");
}
在rt-thread初始化流程之中,有一个组件的初始化过程rt_components_board_init,其相关符号位于一些特殊的段中,因此需要修改链接,加入一些段
有了板级初始化,有了systick,调度不用变,加入了特有段,至此应该可以正常跑起来,至少能进入main,哪怕没有调度,至少main能够运行。
关于main入口函数,armgcc与gnugcc入口不同,注意我使用的gnu gcc,入口为entry,在CFLAGS中指定即可-eentry
rt-rthread代码中也有注释说明
需要注意的几个地方:
1. 链接加入特定的段
2. gnu gcc指定入口函数
3. Cmake加入新的或改动的源文件并注意编译顺序
4. Systick中断以及PendSV中断的优先级
5. main task的栈不可过小
6. 精简rt-thread编译配置,尽量最小化
7. 注意宏定义FSL_RTOS_FREE_RTOS,原始SDK中有很多此宏定义,参与编译的 其包含的内容 需要修改为rt-thread头文件
8. 注意rt-thread配置的栈大小:
STACK_SIZE = DEFINED(__stack_size__) ? __stack_size__ : 0x0400;