第三章 Rt-thread 操作系统移植
3.1 RTOS的介绍
实时操作系统(Real Time Operating System,简称RTOS)是指当外界事件或数据产生时,能够接受并以足够快的速度予以处理,其处理的结果又能在规定的时间之内来控制生产过程或对处理系统做出快速响应,调度一切可利用的资源完成实时任务,并控制所有实时任务协调一致运行的操作系统。提供及时响应和高可靠性是其主要特点。
常见的RTOS有以下几种:Ucos、RT-Thread、thread-x、FreeRTOS. 一般国外的产品中用的比较多是FreeRTOS,国内用比较多是Rt-thread。
笔者这里开发以RT-THREAD作为代表介绍操作系统方面的操作。
3.2 Rt-Thread 介绍
Rt-thread 常见的版本有:Nano版本、标准版本和Smart版本。不同版本中的内核代码是一样,只是多了一些外设资源。由于F4的资源有限和作为入门介绍,本章这里以Nano版本作为介绍。
RT-Thread Nano 是一个极简版的硬实时内核,它是由 C 语言开发,采用面向对象的编程思维,具有良好的代码风格,是一款可裁剪的、抢占式实时多任务的 RTOS。其内存资源占用极小,功能包括任务处理、软件定时器、信号量、邮箱和实时调度等相对完整的实时操作系统特性。适用于家电、消费电子、医疗设备、工控等领域大量使用的 32 位 ARM 入门级 MCU 的场合。
下图是 RT-Thread Nano 的软件框图,包含支持的 CPU 架构与内核源码,还有可拆卸的 FinSH 组件:
它能支持架构:ARM:Cortex M0/ M3/ M4/ M7 等、RISC-V 及其他。
涵盖的功能有:线程管理、线程间同步与通信、时钟管理、中断管理、内存管理。
Rt-thread的资源都可以在其官方网站上面找到:
内核实验手册:链接
编程手册:链接
API函数手册:链接
3.3 Nano 安装
3.3.1 在线安装
- 打开KEIL 软件,点击工具栏的 Pack Installer 图标
- 点击右侧的 Pack,展开 Generic,可以找到 RealThread::RT-Thread,点击 Action 栏对应的 Install ,就可以在线安装 Nano Pack 了。
3.3.2 离线安装
有一些电脑如果没有联网,也不用担心。也可以这里采用KEIL集成开发平台来开发相关应用,可以通过加载KEIL的Pack来安装。
首先下载Pack包,资源包已经准备好;
其次双击pack包,安装到默认路径;
3.3.3 KEIL 加载Nano
1 打开已经准备好的可以运行的裸机程序,将 RT-Thread 添加到工程。如下图,点击 Manage Run-Time Environment。
2 在 Manage Rum-Time Environment 里 “Software Component” 栏找到 RTOS,Variant 栏选择 RT-Thread,然后勾选 kernel,点击 “OK” 就添加 RT-Thread 内核到工程了。
3 . 现在可以在 Project 看到 RT-Thread RTOS 已经添加进来了,展开 RTOS,可以看到添加到工程的文件:
3.4 Nano移植
3.4.1 Nano 介绍
RT-Thread 启动代码统一入口为 rtthread_startup() ,芯片启动文件在完成必要工作(如初始化时钟、配置中断向量表、初始化堆栈等)后,最终会在程序跳转时,跳转至 RT-Thread 的启动入口中。RT-Thread 的启动流程如下:
- 全局关中断,初始化与系统相关的硬件。 打印系统版本信息,初始化系统内核对象(如定时器、调度器)。 初始化用户 main
- 线程(同时会初始化线程栈),在 main 线程中对各类模块依次进行初始化。 初始化软件定时器线程、初始化空闲线程。
- 启动调度器,系统切换到第一个线程开始运行(如 main 线程),并打开全局中断。
系统文件架构如图所示:
启动文件由芯片厂商提供,位于芯片固件库中。每款芯片都有相对应的启动文件,在不同开发环境下启动文件也不相同。当系统加入 RT-Thread 之后,会将 RT-Thread 的启动放在调用 main() 函数之前,如下图所示:
RT-Thread 启动
startup.s:主要完成初始化时钟、配置中断向量表;完成全局 / 静态变量的初始化工作;初始化堆栈;库函数的初始化;程序的跳转等内容。
程序跳转:芯片在 KEIL MDK 与 IAR 下的启动文件不用做修改,会自动转到 RT-Thread 系统启动函数 rtthread_startup() 。GCC 下的启动文件需要修改,让其跳转到 RT-Thread 提供的 entry() 函数,其中 entry() 函数调用了 RT-Thread 系统启动函数 rtthread_startup()。
3.4.2 板级移植 board.c
由于配置文件,Rt-thread已经完成了cpu其他文件。所以对于我们应用而言,主要是完成板级移植。
板级移植主要是针对 rt_hw_board_init() 函数内容的实现,该函数在板级配置文件 board.c 中,函数中做了许多系统启动必要的工作,其中包含:
- 配置系统时钟。
- 实现 OS 节拍。(其中步骤 1 和 2 为 3.1.5 版本中#error TODO 1 的部分: #error"TODO 1: OS Tick Configuration.") 初始化外设:如 GPIO/UART 等等,若需要请在此处调用。
- 初始化系统内存堆,实现动态堆内存管理。 板级自动初始化,使用 INIT_BOARD_EXPORT() 自动初始化的函数会在此处被初始化。
- 其他必要的初始化,如 MMU 配置(需要时请自行在 rt_hw_board_init 函数中调用应用函数实现)。
#include "stm32f4xx.h"
/* board.c */
void rt_hw_board_init(void)
{
/* 第一部分:系统初始化、系统时钟配置等 */
SystemInit();; // 配置系统时钟
SystemCoreClockUpdate(); // 更新系统时钟频率 SystemCoreClock
/* 第二部分:配置 OS Tick 的频率,实现 OS 节拍(并在中断服务例程中实现 OS Tick 递增) */
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK);
SysTick_Config(SystemCoreClock / RT_TICK_PER_SECOND);
/* 第三部分:初始化硬件外设,若有需要,则放在此处调用 */
/* 第四部分:系统动态内存堆初始化 */
#if defined(RT_USING_USER_MAIN) && defined(RT_USING_HEAP)
rt_system_heap_init(rt_heap_begin_get(), rt_heap_end_get());
#endif
/* 第五部分:使用 INIT_BOARD_EXPORT() 进行的初始化 */
#ifdef RT_USING_COMPONENTS_INIT
rt_components_board_init();
#endif
/* 第六部分:其他初始化 */
}
3.4.3 中断与异常处理
/* timer 定时器中断服务函数调用 rt_os_tick_callback function,cortex-m 架构使用 SysTick_Handler() */
void rt_os_tick_callback(void)
{
rt_interrupt_enter(); /* 进入中断时必须调用 */
rt_tick_increase(); /* RT-Thread 系统时钟计数 */
rt_interrupt_leave(); /* 退出中断时必须调用 */
}
/* cortex-m 架构使用 SysTick_Handler() */
void SysTick_Handler()
{
rt_os_tick_callback();
}
3.4.4 屏蔽it文件的中断
找到stm32f4xx_it.c的文件,屏蔽以下文件
//void HardFault_Handler(void)
//{
// /* Go to infinite loop when Hard Fault exception occurs */
// while (1)
// {
// }
//}
//void PendSV_Handler(void)
//{
//}
///**
// * @brief This function handles SysTick Handler.
// * @param None
// * @retval None
// */
//void SysTick_Handler(void)
//{
//}
3.5 内存堆初始化
系统内存堆的初始化在 board.c 中的 rt_hw_board_init() 函数中完成,内存堆功能是否使用取决于宏 RT_USING_HEAP 是否开启,RT-Thread Nano 默认不开启内存堆功能,这样可以保持一个较小的体积,不用为内存堆开辟空间。
开启系统 heap 将可以使用动态内存功能,如使用 rt_malloc、rt_free 以及各种系统动态创建对象的 API。若需要使用系统内存堆功能,则打开 RT_USING_HEAP 宏定义即可,此时内存堆初始化函数 rt_system_heap_init() 将被调用,如下所示:
初始化内存堆需要堆的起始地址与结束地址这两个参数,系统中默认使用数组作为 heap,并获取了 heap 的起始地址与结束地址,该数组大小可手动更改,如下所示