嵌入式系统应用-第三章 RT-Thead(RTOS)操作系统移植

第三章 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 组件:

Nano组件
它能支持架构:ARM:Cortex M0/ M3/ M4/ M7 等、RISC-V 及其他。

涵盖的功能有:线程管理、线程间同步与通信、时钟管理、中断管理、内存管理。

Rt-thread的资源都可以在其官方网站上面找到:

内核实验手册:链接

编程手册:链接

API函数手册:链接

3.3 Nano 安装

3.3.1 在线安装

  1. 打开KEIL 软件,点击工具栏的 Pack Installer 图标
    在这里插入图片描述
  2. 点击右侧的 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 的启动流程如下:

  1. 全局关中断,初始化与系统相关的硬件。 打印系统版本信息,初始化系统内核对象(如定时器、调度器)。 初始化用户 main
  2. 线程(同时会初始化线程栈),在 main 线程中对各类模块依次进行初始化。 初始化软件定时器线程、初始化空闲线程。
  3. 启动调度器,系统切换到第一个线程开始运行(如 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 中,函数中做了许多系统启动必要的工作,其中包含:

  1. 配置系统时钟。
  2. 实现 OS 节拍。(其中步骤 1 和 2 为 3.1.5 版本中#error TODO 1 的部分: #error"TODO 1: OS Tick Configuration.") 初始化外设:如 GPIO/UART 等等,若需要请在此处调用。
  3. 初始化系统内存堆,实现动态堆内存管理。 板级自动初始化,使用 INIT_BOARD_EXPORT() 自动初始化的函数会在此处被初始化。
  4. 其他必要的初始化,如 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 的起始地址与结束地址,该数组大小可手动更改,如下所示
在这里插入图片描述

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
好的,这是一个关于线程优先级和动态创建静态创建线程的问题,需要结合操作系统的知识进行回答。下面是我的回答: 首先,利用RT-Thread设计三个线程,其中线程thread1动态创建,而线程thread2和Thread Flag3是静态创建。代码如下: ```c #include <rtthread.h> #include <rtdevice.h> /* thread1 entry */ static void thread1_entry(void* parameter) { while(1) { rt_thread_mdelay(1000); // 延时1s } } /* thread2 entry */ static void thread2_entry(void* parameter) { while(1) { rt_kprintf("thread2 is running!\n"); rt_thread_mdelay(500); // 延时0.5s } } /* thread flag3 entry */ static void thread_flag3_entry(void* parameter) { rt_uint32_t count = 0; rt_uint8_t flag = 0; while(1) { if(count < 5) { flag = 1; } else if(count >= 5 && count < 10) { flag = 0; } else { count = 0; flag = 1; } rt_thread_delay(10); // 延时10个OSTicks rt_hw_interrupt_set_flag(RT_THREAD_FLAG_GET(), flag); // 设置线程标志 count++; } } int main(void) { /* 创建thread1 */ rt_thread_t thread1 = rt_thread_create("thread1", thread1_entry, RT_NULL, 512, 25, 5); if(thread1 != RT_NULL) { rt_thread_startup(thread1); } /* 创建thread2 */ rt_thread_t thread2 = rt_thread_create("thread2", thread2_entry, RT_NULL, 512, 24, 5); if(thread2 != RT_NULL) { rt_thread_startup(thread2); } /* 创建thread flag3 */ rt_thread_t thread_flag3 = rt_thread_create("thread_flag3", thread_flag3_entry, RT_NULL, 512, 26, 5); if(thread_flag3 != RT_NULL) { rt_thread_startup(thread_flag3); } return 0; } ``` 其中,线程thread1的优先级为25,线程thread2的优先级为24,Thread Flag3的优先级高于thread1。 其次,优先级较高的Thread2抢占低优先级的thread1,执行完毕后自动被系统脱离。优先级较低的thread1被设计成死循环,循环中有让出CPU使用权的动作,使用了delay函数。该线程在Thread2退出运行之后开始运行,每隔一段时间运行一次,并一直循环运行下去。Thread Flag3实现一个周期为10个OSTicks的方波,要求在thread1和thread2中添加代码,能够在虚拟逻辑分析仪中观察到对应的状态。 在thread1和thread2中添加代码,能够在虚拟逻辑分析仪中观察到对应的状态,代码如下: ```c /* thread1 entry */ static void thread1_entry(void* parameter) { while(1) { rt_thread_mdelay(1000); // 延时1s rt_hw_interrupt_set_flag(RT_THREAD_FLAG_GET(), 1); // 设置线程标志 } } /* thread2 entry */ static void thread2_entry(void* parameter) { while(1) { rt_kprintf("thread2 is running!\n"); rt_thread_mdelay(500); // 延时0.5s rt_hw_interrupt_set_flag(RT_THREAD_FLAG_GET(), 0); // 设置线程标志 } } ``` 这样,在虚拟逻辑分析仪中就可以观察到对应的状态了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

HHONGQI123

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值