ucosii和ucosiii(OS-II操作系统实验)
这一章的目的是让学生了解嵌入式和行业中操作系统中的应用,在以前学习的例程中大多都不带操作系统,也就是裸奔,本章将带领大家进入RTOS的世界,关于RTOS类操作系统有很多,本教程选取的是非常有名的uC/OS-II操作系统。在使用uC/OS-II之前我们要先完成uC/OS-II在我们开发平台上的移植操作。
uC/OS-II简介uC/OS-II由Micrium公司提供,是一个可移植、可固化的、可裁剪的、占先式多任务实时内核,它适用于多种微处理器,微控制器和数字处理芯片(已经移植到超过100种以上的微处理器应用中)。同时,该系统源代码开放、整洁、一致,注释详尽,适合系统开发。 uC/OS-II已经通过联邦航空局(FAA)商用航行器认证,符合航空无线电技术委员会(RTCA)DO-178B标准。
我们常见的嵌入式操作一同可以大致分为四层,分别是功能层、软件层、中间层和硬件层。其中我们使用的uC/OS-II就位于中间层,具体如下图29.1所示:
图29.1 嵌入式操作系统分层
严格地说uC/OS-II只是一个实时操作系统内核,它仅仅包含了任务调度,任务管理,时间管理,内存管理和任务间的通信和同步等基本功能。没有提供输入输出管理,文件系统,网络等额外的服务。但由于uC/OS-II良好的可扩展性和源码开放,这些非必须的功能完全可以由用户自己根据需要分别实现。
uC/OS-II移植移植准备首先我们先要准备一个一直所需的基本工程,本章所讲的移植步骤实在LED工程基础上实现的。其次我们需要获取uC/OS-II的源码,源码我们可以直接从Micrium官网下载,需要我们首先登录该网站,第一次登陆需要进行注册。下载地址:
Micrium uC-Eval-STM32F107 - Weston Embedded Solutions (weston-embedded.com)
登陆官网之后,我们首先找到下载地址如下图29.2所示:
图29.2 下载地址
然后我们点击EXMAPLE,并选择STM系列的芯片相关例子,具体操作如下图29.3所示:
图29.3 芯片相关示例选择
选择ST相关芯片之后然后点击在搜索栏输入我们所需要的使用的芯片类型,由于我们实在STM32F103开发板上移植,所以我们选择同系列的STM32F10B系列实例即可。具体操作如下图29.4所示:
图29.4 uC/OS-II版本选择
下载完之后会显示一个如下图29.5所示的可执行文件,点击打开之后我们就就可以将对应的源码文件解压到指定的目录之下:
图29.5 源码软件
解压过程下图29.6所示,我们需要先点击Browse选择解压路径,然后点击Unzip解压即可。
图29.6 源码解压
解压完成之后,我们可以打开对应的路径,我们所需要用到的内容如下图29.6所示:
29.6 源码路径
至此,我们的源码就以获取完毕,准备工作已经完成。接下来我们就可以开始移植操作了。
移植步骤首先我们需要在模板工程里新建一个uCosII文件夹,然后再文件夹里新建三个子文件夹Config、Core和Port。其中Config用来存放我们对uCosII操作系统的配置文件,Core用来存放uCosII的源码,Port用来存放和CPU的接口文件。具体操作如下图29.7所示:
图29.7 创建工程存放文件夹
接下来我们就是向指定文件夹移植对应的文件即可,首先我们需要向Config文件夹里移植如下图29.8所示两个文件,其中 includes.h 里面都是一些头文件,os_cfg.h 文件主要是用来 配置和裁剪UCOSII 的。
图29.8 配置文件
这两个文件我们可以从源码里获取,具体路径:
Micrium\Software\EvalBoards\ST\STM3210E-EVAL\RVMDK\OS-Probe
然后向Core文件夹内移植uCosII的源码,具体内容如下图29.9所示:
图29.9 内核源码文件
内核源码我们可以从源码里获取,具体路径:
Micrium\Software\uCOS-II\Source
然后我们移植CPU接口文件,具体内容如下图29.10所示:
图29.10 接口文件
这几个文件在源码中的具体路径如下:
Micrium\Software\uCOS-II\Ports\ARM-Cortex-M3\Generic\RealView
至此所有的文件就已经移植完成,接下来我们就需要对工程尽型配置了。
工程配置首先在工程目录中创建一下三个分组,如下图29.11所示:
图29.11 工程目录创建
然后分别向三个目录中添加对应的文件夹里的c文件和.a文件,注意在添加内核文件时不要将ucos_ii.c文件添加到工程里。文件添加完成后的效果如下图29.12所示:
图29.12 文件添加效果图
添加完文件之后需要将头文件的路径一并给添加到工程里,具体如下图29.12所示:
图29.12 头文件路径配置
此时直接编译会提示找不到app_cfg.h文件,因为我们没有将这个文件添加到自己的目录中,所以直接将#include <app_cfg.h>替换为#include “includes.h”,然后修改includs.h文件将无关的头文件给屏蔽掉。具体操作如下图29.13所示:
图29.13 屏蔽app_cfg.h文件修改includes.h文件
屏蔽之后还会提示一些钩子函数未定义,此时我们需要在配置文件里将钩子函数给关闭,找到os_cfg.h文件的第30行,将1改为0即可。具体操作如下图
图29.14 关闭钩子函数
修改完之后在编译一次,会提示另外一个函数未定义,如下图所示29.15所示:
图29.15 错误提示
我们找到os_cpu_c.c文件,然后将361行的OS_CPU_SysTickClkFreq()函数直接修改为单片机的时钟频率也就是72000000。具体操作如下图29.16所示:
图29.16 修改时钟频率
此时我们在编译就不会再出错了,但是我们还需要进行以下操作让uCosII系统跑起来。我们将uCosII系统运行所依赖的函数放到系统定时器的中断函数里运行,如图29.17所示,然后将STM32启动文件里出现pendSV_Handler的地方全部修改为OS_CPU_PendSVHandler,因为上了操作系统之后,上下文切换的中断会由uCosII来执行。具体内容如下图29.18所示:
图29.17 函数调用
图29.18 启动文件修改
uC/OS-II操作系统使用基础任务创建和删除实验任务基础多任务操作系统最主要的就是对任务的管理,包括任务的创建、挂起、删除和调度等,因此对于UCOSII操作系统中任务管理的理解就显得尤为重要。这一节我们就讲解UCOSII中的任务管理。
在使用UCOSIII 的时候我们要按照一定的顺序初始化并打开UCOSII,我们可以按照下面的顺序
- 最先肯定是要调用CPU_Init()初始化UCOSII
- 创建任务,一般我们在 main()函数中只创建一个 start_task 任务,其他任务都在start_task 任务中创建,在调用 OSTaskCreate() 函数创建任务的时候一定要调用 OS_CRITICAL_ENTER()函数进入临界区,任务创建完以后调用 OS_CRITICAL_EXIT()函数退出临界区
- 最后调用 OSStart()函数开启 UCOSII
任务的状态:
UCOSII支持的是单核 CPU,不支持多核CPU,这样在某一时刻只有一个任务会获得CPU使用权进入运行态,其他的任务就会进入其他状态,UCOSII中的任务有多个状态,如下表 29.1所示。
表29.1 任务状态
任务状态 |
描述 |
休眠态 |
休眠态就是任务只是以任务函数的方式存在,只是存储区中的一段代码,并 未用OSTaskCreate()函数创建这个任务,不受 UCOSII管理的。 |
就绪态 |
任务在就绪表中已经登记,等待获取 CPU使用权。 |
运行态 |
正在运行的任务就处于运行态。 |
等待态 |
正在运行的任务需要等待某一个事件,比如信号量、消息、事件标志组等, 就会暂时让出 CPU使用权,进入等待事件状态。 |
中断服务态 |
一个正在执行的任务被中断打断,CPU 转而执行中断服务程序,这时这个任 务就会被挂起,进入中断服务态。 |
在uCosII中任务的5种状态转换关系如下图29.19所示;
图29.19 任务状态切换
任务相关API的使用:
创建任务函数:
在UCOSIII 中我们通过函数 OSTaskCreate()来创建任务,OSTaskCreate()函数原型如下(在 os_task.c 中有定义)。
INT8U OSTaskCreate (void (*task)(void *p_arg), void *p_arg, OS_STK *ptos, INT8U prio) |
函数需要四个参数,对应每个功能如下:
task:要执行的任务的指针,也就是函数名字
p_arg:任务开始时传递给任务的参数
ptos:分配给任务的栈空间
rio:人物的优先级
删除任务函数:
OSTaskDel()函数用来删除任务,当一个任务不需要运行的话,我们就可以将其删除掉,删除任务不是说删除任务代码,而是UCOSII不再管理这个任务,在有些应用中我们只需要某个 任务只运行一次,运行完成后就将其删除掉,比如外设初始化任务,OSTaskDel()函数原型如下:
INT8U OSTaskDel (INT8U prio) |
参数含义:
prio:删除任务的优先级
代码实现我们在主函数直接创建两个LED灯的任务分别控制LED1和LED2,并且在LED1任务运行5次之后删除LED2的任务,具体代码如下:
#include "main.h" #include "delay.h" #include "led.h" #include "key.h" #include "usart.h" #include "includes.h" //START 任务 //设置任务优先级 #define START_TASK_PRIO 10 //开始任务的优先级设置为最低 //设置任务堆栈大小 #define START_STK_SIZE 64 //创建任务堆栈空间 OS_STK START_TASK_STK[START_STK_SIZE]; //任务函数接口 void start_task(void *pdata); //LED1任务 //设置任务优先级 #define LED1_TASK_PRIO 7 //设置任务堆栈大小 #define LED1_STK_SIZE 64 //创建任务堆栈空间 OS_STK LED1_TASK_STK[LED1_STK_SIZE]; //任务函数接口 void led1_task(void *pdata); //LED2任务 //设置任务优先级 #define LED2_TASK_PRIO 8 //设置任务堆栈大小 #define LED2_STK_SIZE 64 //创建任务堆栈空间 OS_STK LED2_TASK_STK[LED2_STK_SIZE]; //任务函数接口 void led2_task(void *pdata); int main(void) { NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); SysTick_Config(72000); Led_Config(); Beep_Config(); RGB_Config(); Relay_Config(); Key_Config(); USART1_Config(115200); OSInit(); OSTaskCreate(start_task,(void *)0,(OS_STK *)&START_TASK_STK[START_STK_SIZE-1],START_TASK_PRIO );//创建起始任务 OSStart(); while(1) { } } //开始任务 void start_task(void *pdata) { OS_CPU_SR cpu_sr=0; pdata = pdata; OSStatInit(); //初始化统计任务.这里会延时1秒钟左右 OS_ENTER_CRITICAL(); //进入临界区(无法被中断打断) OSTaskCreate(led1_task,(void *)0,(OS_STK*)&LED1_TASK_STK[LED1_STK_SIZE-1],LED1_TASK_PRIO); OSTaskCreate(led2_task,(void *)0,(OS_STK*)&LED2_TASK_STK[LED2_STK_SIZE-1],LED2_TASK_PRIO); OSTaskSuspend(START_TASK_PRIO); //挂起起始任务. OS_EXIT_CRITICAL(); //退出临界区(可以被中断打断) } //LED1任务 void led1_task(void *pdata) { uint8_t count=0; while(1) { count ; LED1_Toggle(); if(count==5) { printf("LED1任务删除LED2任务\r\n"); OSTaskDel(LED2_TASK_PRIO); } printf("LED1任务运行%d次\r\n",count); OSTimeDly(1000); } } //LED2任务 void led2_task(void *pdata) { uint8_t count=0; while(1) { count ; LED2_Toggle(); printf("LED2任务运行%d次\r\n",count); OSTimeDly(500); } } |
将代码烧录到开发板,然后打开串口助手,我们可以看到串口助手会打印出来每个任务运行的次数,而且LED灯1和LED灯2都开始闪烁,当LED1任务运行到第五次时,会打印“LED1任务删除LED2任务”,此时LED2不再闪烁,且串口不再是输出LED2任务运行次数,只有LED1任务运行,具体现象如下图29.20所示:
图29.20 任务从创建删除实验现象
,免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。文章投诉邮箱:anhduc.ph@yahoo.com