很多同学都问过这个问题,移植RTOS到一个开发板上,难么?需要学习哪些知识? 从我学习国内常见的RTOS,以及一些构建系统的经验上看。真正要做移植的工作,需要的知识范围还是非常广泛的。
1. 理解这个RTOS的系统源码目录组成,源码层级结构(需要知道厂家SDK放哪里,系统层的Driver驱动,板级配置目录,工程模板目录)
2. 理解构建系统(需要知道对应的RTOS所使用的构建系统相关配置,清楚板级的宏定义开关在哪里设置,RTOS配置,以及c文件和h文件如何添加)
3. 理解RTOS的启动流程(万一移植后编译成功,但是无法运行系统时,要清楚如何调试,找到问题点)
4. 理解所移植的MCU的系统时钟配置,外设配置等等内容
5. 理解RTOS的系统调度和内存管理(万一无法运行系统,不清楚这些就不能调试)
6. 熟悉调试工具(不管是什么IDE,什么link,都需要熟悉至少一种自己常用的)
个人觉得,移植RTOS其实是一个非常严谨的工作,需要的知识除了上面列举的这些,当然越多越好。有兴趣移植的话,建议先把调试工具,RTOS的系统调度、内存管理和启动流程先给熟悉了,否则一旦碰到问题就不知道自己错在哪里。
首先我对RTOS的基础知识还是有一些的,其次也比较熟悉OneOS的目录结构和系统结构,对构建系统也稍微有一些了解(非常不喜欢Scons,但是没办法,OneOS没有别的构建方式)。 由于长期没有玩OneOS,对OneOS的启动流程有点生疏了,为了快速梳理一遍启动的相关流程和细节,我找到了AliOS-Things的DeveloperKit开发板。这是一块stm32l496的开发板,目前OneOS的支持也挺好的,串口、SPI屏幕,GPIO等外设都支持得很好了。代码量相对来说也是比较少的,所以选用这一块开发板作为熟悉启动流程的板子。点亮图片如下:
一、安装编译工具链: 下载gcc-arm-none-eabi工具链(arm官网有,自己喜欢哪个版本就下哪个),并安装。
二、安装OpenOCD(需要配置好PATH环境变量)和VSCode(随便装装就行,插件只需要Cortex-Debu),网上已经有教程了,不再累赘。
三、下载源码和编译 到gitee上克隆OneOS源码,然后打开OneOS源码目录(git clone下来的),切换到v2.3.0版本,并打开根目录下projects目录,按教程生成stm32l496-ali-developerkit模板的工程,并编译好。
我用的是VSCode,要调试嵌入式设备,仅仅需要安装Cortex-Debug这个插件就可以开始调试了(编译工具链和OpenOCD需要提前安装和配置好)。按下图的顺序创建launch.json调试配置文件。
1. 创建launch.json文件:
2. 选择Cortex Debug调试器
3. 在打开的launch.json文件编辑中,将文件内容替换成以下内容。
{
// 使用 IntelliSense 了解相关属性。
// 悬停以查看现有属性的描述。
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Cortex Debug",
"cwd": "${workspaceFolder}/build", // 调试时的工作目录应和编译目录一致,否则在调试中会定位不到源码
"executable": "${workspaceFolder}/out/oneos.elf", // 修改成编译生成的elf文件路径
"request": "launch",
"type": "cortex-debug",
"runToEntryPoint": "Reset_Handler", // 入口点改成stm32的复位入口函数
"servertype": "openocd", // GDB Server修改成openocd
"configFiles": [
"interface/stlink.cfg", // 使用板载stlink
"target/stm32l4x.cfg" // 调试目标为stm32l4x
]
}
]
}
点击绿色的小三角开始调试,30秒左右就正式进入调试状态了如下图。
在这里,我们先不着急执行程序,往下拉到110行,会发现程序入口点被修改成了entry,如下图:
注意:很多RTOS都会修改启动文件,替换程序入口点。因为需要在用户应用调用之前,先初始化RTOS的相关内容。对于用户来说,这些初始化的东西在大部分的时候是不需要关注的(写应用时重点关注应用逻辑,外设初始化、操作系统初始化这些是移植时就要完善好的)。假如是一款全新的芯片(源码中找不到类似或者已有的芯片支持和启动文件的)要移植进来,需要关注入口点,否则就算编译通过了,也没办法正常启动系统。
当我们跟着entry函数,会发现它实际上调用的是_k_startup函数,如下图:
然后在_k_startup函数中,有不同功能的函数,大致如下图:
对于移植工作来说,最容易让人迷惑的,也就是_k_core_auto_init这个函数的内容,函数实现如下图:
是不是完全看不懂它做了什么,其实这就是有名的Init Call机制。因为RTOS运行前,需要做相当多的准备工作,而根据用户的组件设定的不同(例如使用了不同的组件),调用的内容也会有所区别。所以对于这些变化的准备工作,如果都写在一个函数里,会很乱,也很难看。所以不少RTOS都借鉴了Linux的Init Call机制。 通过一段区分了不同初始化级别的指针,按顺序取出指针并执行对应的初始化函数。
Init Call 机制的简单理解
其实我们并不需要过于在意Init Call机制是如何实现的,我们只需要知道,它是保存在Flash中的一段指针,通过这些指针可以有顺序的对初始化函数进行调用即可。以下是在Map文件中搜索.init_call找到的对应内容。在文件中我们不难看出, 当前的工程里一共分了1、2、3、4、5、7个启动等级(别问我为啥没有6,以为map文件里没有),并保存了对应等级需要执行的函数指针。
1. 现在我们继续跟着启动流程走,点击单步执行,进入 Init Call 机制 指定的第一个函数(cotex_m_set_vector)中,大部分情况下可以不用管它,应该是处理中断向量表指针之类的内容。
2. 单步跳出这个函数后,接下来进入 Init Call 机制 指定的第二个函数(os_hw_board_init),这个函数调用了**板级的外设初始化函数**,这个在我们移植的时候需要注意把板级外设初始化的函数更名成下图的名称。同时在图中也可以看到Init Call 机制实现的重要一环,OS_PREV_INIT(函数名称, 启动等级)。正是通过一行,编译器才会将这个函数指针存入Init Call 机制在flash中指定的固定指针段里。
1. 单步跳出这个函数后,接下来进入 Init Call 机制 指定的第三个函数(driver_stm32_usart_early_driver_init),这个函数给系统的前期输出指派了对应的串口设备(oneos_config.h文件中定义的OS_CONSOLE_DEVICE_NAME串口名称一致的设备)。若发现没有串口输出信息,可以先检查是否正确初始化了对应串口,以及是否正确指派了串口设备。
接下来的启动过程,就不再去分析了(太菜,后面的也不知道怎么解释)。我们移植前期需要关注的启动内容,大概就是这些了。基本上完成一个移植工作,串口正常工作,系统调度正常运行,这两个工作是优先保障的。因为串口Debug也是一个常用的技巧,大部分时候串口输出可以帮助调试。而完成了串口和线程调度,移植的初步阶段就完成了。RTOS的Shell交互,也是非常有用的一个工具。以下是进入OneOS的启动信息和shell截图。可以通过在shell中查看线程信息,线程堆信息等待内容。甚至可以在shell中开启外设,修改外设状态等(需要编写shell命令进行支持)。
本次分享就暂时告一段路,移植经验的分享会接着做构建系统部分的。
审核编辑 黄昊宇
全部0条评论
快来发表一下你的评论吧 !