ucosii_ucosiii - CSDN
精华内容
参与话题
  • 从零开始学习UCOSII操作系统1--UCOSII的基础知识

    万次阅读 多人点赞 2018-03-31 08:34:57
    从零开始学习UCOSII操作系统1--UCOSII的基础知识前言:首先比较主流的操作系统有UCOSII、FREERTOS、LINUX等,UCOSII的资料相对比其余的两个操作系统的资料是多很多的。更重要的原因是自己本身还没有能力深入的研究...

    从零开始学习UCOSII操作系统1--UCOSII的基础知识


    前言:

    首先比较主流的操作系统有UCOSII、FREERTOS、LINUX等,UCOSII的资料相对比其余的两个操作系统的资料是多很多的。

    更重要的原因是自己本身还没有能力深入的研究Linux操作系统。本次学习UCOSII主要是学习内核的设计原理。

    此次专栏涉及到的API的使用是非常小的,仅仅作为本人学习的记录。后期也会对比UCOSIII说出实现的更多功能的代码。


    参考书籍:《嵌入式实时操作系统μCOS-II原理及应用》、《嵌入式实时操作系统uCOS-II 邵贝贝(第二版)》

    学习代码的出处:http://bbs.elecfans.com/jishu_345856_1_1.html


    1、操作系统的作用

    操作系统是计算机硬件系统与应用程序之间的接口,应用程序设计人员只是以操作系统层为基础使用计算机系统,所以程序设计人员

    看到和使用的只是一些由计算机操作系统所提供的函数API,至于操作系统的这些底层函数是怎么实现的,作为一个应用开发人员是不需要

    管的。


    2、嵌入式系统的特点:

    (1)专用性强:嵌入式系统通常是面向某个特定的应用的,所以嵌入式系统的硬件是特定用户群来设计的

    (2)可以裁剪:Linux系统有很多网络协议栈之类的,有些做个数码相框的,没有必要的话,那么直接把那段源码去掉也是可以的

    (3)实时性与可靠性:所谓的实时性不是指,你代码写的东西必须要马上执行,而是你写的代码可以

    在合适的时间中执行,因为操作系统有很多机制,比如资源的强占,优先级低的任务是不可能实现

    只要申请资源就马上得到的。



    3、裸机和操作系统最大的区别:

    裸机是在一个main函数中不断的执行,除了有中断才会跳转到别的相应的代码中执行的。不然运行的指针就会按照

    地址的不断的加4来执行。顺序的执行结构,就是你可以通过代码的找到相应的执行流程。


    操作系统之后,main函数,当通过函数堆栈切换的跳转到第一个任务之后,通过优先级的判断

    优先级高的任务可以得到优先的执行,就好像有多个main函数在一起执行的假象(并行)



    4、任务的C语言表示:

    任务是不需要返回的无类型函数。

    void  task1(void * param)
    {
    	for( ; ;)
            {
                                 //用户编写的代码
            }
    }

    当然有人就觉得奇怪了,为什么你在一个函数里面添加一个死循环,那么函数不会就在这个任务中不断的执行吗?

    当然这需要在里面添加中断的代码的,以便于它能跳转到其他的任务中执行,切换任务的时候,判断优先级进行任务之间的切换的工作。



    5、嵌入式实时操作系统的基本功能:

    (1)内存管理:

    内存管理主要是动态内存的管理,当应用程序需要使用内存的时候,可以利用操作系统所提供

    内存分配函数来获得足够的内存空间。

    动态内存就是指malloc、free函数的分配,就是从堆里面拿内存,因为我们本来嵌入式系统的内存分配就比较

    少,所以这里很关键。


    (2)多任务管理:

    裸机就是一个单任务的前后台的程序,就是一个任务加上中断的机器的实现方法。

    程序设计人员就可以按照多线程来设计自己的程序,程序的耦合性和单元测试方面就会比较的容易。

    关键就是程序的可复用性会更加的好。


    (3)外围资源管理:

    除了本身自己必须需要的东西,内存和CPU,还有很多输入型设备和输出型设备需要管理。

    由于资源是有限的,因此操作系统必须对这些资源进行合理的调度和管理,才能保证每个要使用

    资源的任务在运行时可以获得足够的资源。


    6、UCOS的代码结构:

    这个是UCOS的代码结构,首先我们认识一个东西,需要从它的整体分析一下,因为有一些东西

    我们是在UCOS中不需要深入理解的。

















    展开全文
  • STM32学习笔记一一UCOSII(1)

    千次阅读 2020-07-04 11:39:38
    UCOSII 是一个可以基于 ROM 运行的、可裁减的、抢占式、实时多任务内核,具有高度可移植性,特别适合于微处理器和控制器,是和很多商业操作系统性能相当的实时操作系统(RTOS)。 1.1 UCOSII 体系结构图 UCOSII 的...

    前言:

    为了方便查看博客,特意申请了一个公众号,附上二维码,有兴趣的朋友可以关注,和我一起讨论学习,一起享受技术,一起成长。

    在这里插入图片描述


    1. 简介

    UCOSII 是一个可以基于 ROM 运行的、可裁减的、抢占式、实时多任务内核,具有高度可移植性,特别适合于微处理器和控制器,是和很多商业操作系统性能相当的实时操作系统(RTOS)。

    1.1 UCOSII 体系结构图

    这里写图片描述

    UCOSII 的移植,我们只需要修改: os_cpu.h、 os_cpu_a.asm 和 os_cpu.c等三个文件。

    os_cpu.h: 进行数据类型的定义,以及处理器相关代码和几个函数原型;

    os_cpu_a.asm:是移植过程中需要汇编完成的一些函数,主要就是任务切换函数;

    os_cpu.c:定义一些用户 HOOK 函数。

    定时器的作用:为 UCOSII 提供系统时钟节拍,实现任务切换和任务延时等功能。这
    个时钟节拍由 OS_TICKS_PER_SEC(在 os_cfg.h 中定义)设置,一般我们设置UCOSII 的系统时钟节拍为 1ms~100ms,具体根据你所用处理器和使用需要来设置。本章,利用 STM32的 SYSTICK 定时器来提供 UCOSII 时钟节拍。

    1.2 任务

    任务:其实就是一个死循环函数,该函数实现一定的功能,一个工程可以有很多这样的任务(最多 255 个), UCOSII 对这些任务进行调度管理, 让这些任务可以并发工作(注意不是同时工作,并发只是各任务轮流占用 CPU,而不是同时占用,任何时候还是只有 1个任务能够占用 CPU), 这就是 UCOSII 最基本的功能。

    Ucos 任务的一般格式为:

    void MyTask (void *pdata)
    {
    任务准备工作…
    While(1)//死循环
    { 任务 MyTask 实体代码;
    OSTimeDlyHMSM(x,x,x,x);//调用任务延时函数,释放 cpu 控制权,
    }
     }
    

    1.3 任务优先级

    ucos 中,每个任务都有唯一的一个优先级。优先级是任务的唯一标识。在 UCOSII中,使用 CPU 的时候,优先级高(数值小)的任务比优先级低的任务具有优先使用权,即任务就绪表中总是优先级最高的任务获得 CPU 使用权,只有高优先级的任务让出 CPU 使用权(比如延时)时,低优先级的任务才能获得 CPU 使用权。 UCOSII 不支持多个任务优先级相同,也就是每个任务的优先级必须不一样。

    1.4 任务堆栈

    存储器中的连续存储空间。为了满足任务切换和响应中断时保存 CPU 寄存器中的内容以及任务调用其他函数时的需要,每个任务都有自己的堆栈。在创建任务的时候,任务堆栈是任务创建的一个重要入口参数。

    1.5 任务控制块 OS_TCB

    用来记录任务堆栈指针,任务当前状态以及任务优先级等任务属性。UCOSII 的任何任务都是通过任务控制块(TCB)的东西来控制的,一旦任务创建了,任务控制块 OS_TCB 就会被赋值。每个任务管理块有 3 个最重要的参数:

    1.任务函数指针;

    2.任务堆栈指针;

    3.任务优先级;

    任务控制块就是任务在系统里面的身份证。

    1.6 任务就绪表

    用来记录系统中所有处于就绪状态的任务。它是一个位图,系统中每个任务都在这个图中占据一个进制位,该位置的状态(1 或者 0)就表示任务是否处于就绪状态。

    1.7 任务调度

    一是在任务就绪表中查找优先级最高的就绪任务,二是实现任务的切换。比如说,当一个任务释放 cpu 控制权后,进行一次任务调度,这个时候任务调度器首先要去任务就绪表查询优先级最高的就绪任务,查到之后,进行一次任务切换,转而去执行下一个任务。

    1.8 状态切换

    UCOSII 的每个任务都是一个死循环。每个任务都处在以下 5 种状态之一的状态下,这 5种状态是**:睡眠状态、 就绪状态、 运行状态、 等待状态(等待某一事件发生)和中断服务状态。**

    睡眠状态: 任务在没有被配备任务控制块或被剥夺了任务控制块时的状态。

    就绪状态: 系统为任务配备了任务控制块且在任务就绪表中进行了就绪登记,任务已经准备好了,但由于该任务的优先级比正在运行的任务的优先级低, 还暂时不能运行,这时任务的状态叫做就绪状态。

    运行状态: 该任务获得 CPU 使用权,并正在运行中,此时的任务状态叫做运行状态。

    等待状态: 正在运行的任务,需要等待一段时间或需要等待一个事件发生再运行时,该任务就会把 CPU 的使用权让给别的任务而使任务进入等待状态。

    中断服务状态: 一个正在运行的任务一旦响应中断申请就会中止运行而去执行中断服务程序,这时任务的状态叫做中断服务状态。

    5种状态之间的转换如下图:

    这里写图片描述

    2.UCOS相关函数

    2.1 建立任务函数

    如果想让 UCOSII 管理用户的任务,必须先建立任务。 UCOSII 提供了 2 个建立任务的函数: OSTaskCreat 和 OSTaskCreatExt,一般用 OSTaskCreat 函数来创建任务。

    该函数原型为:

    OSTaskCreate(void(*task)(void*pd),void*pdata,OS_STK*ptos,INTU prio)

    task:是指向任务代码的指针;

    pdata:是任务开始执行时,传递给任务的参数的指针;

    ptos:是分配给任务的堆栈的栈顶指针;

    prio :是分配给任务的优先级。

    每个任务都有自己的堆栈,堆栈必须申明为 OS_STK 类型,并且由连续的内存空间组
    成。可以静态分配堆栈空间,也可以动态分配堆栈空间。

    2.2 任务删除函数

    任务删除,其实就是把任务置于睡眠状态。 UCOSII提供的任务删除函数原型为:

    INT8U OSTaskDel(INT8U prio)

    参数 prio :要删除的任务的优先级,可见该函数是通过任务优先级来实现任务删除的。

    特别注意: 任务不能随便删除,必须在确保被删除任务的资源被释放的前提下才能删除!

    2.3 请求任务删除函数

    前面提到,必须确保被删除任务的资源被释放的前提下才能将其删除,所以通过向被删除任务发送删除请求,来实现任务释放自身占用资源后再删除。

    UCOSII 提供的请求删除任务函数原型为:

    INT8U OSTaskDelReq(INT8U prio)

    通过优先级来确定被请求删除任务。

    2.4 任务挂起函数

    任务挂起和任务删除有点类似,但是又有区别,任务挂起只是将被挂起任务的就绪标志删除,并做任务挂起记录,并没有将任务控制块任务控制块链表里面删除, 也不需要释
    放其资源, 而任务删除则必须先释放被删除任务的资源,并将被删除任务的任务控制块也给删了。被挂起的任务,在恢复(解挂)后可以继续运行。

    UCOSII 提供的任务挂起函数原型为:

    INT8U OSTaskSuspend(INT8U prio)

    2.5 任务恢复函数

    有任务挂起函数,就有任务恢复函数,通过该函数将被挂起的任务恢复,让调度器能
    够重新调度该函数。

    UCOSII 提供的任务恢复函数原型为:

    INT8U OSTaskResume(INT8U prio)

    3. 移植 UCOSII

    3.1 移植 UCOUS

    3.2 编写任务函数并设置其堆栈大小和优先级等参数

    编写任务函数,以便 UCOSII 调用。

    设置函数堆栈大小,这个需要根据函数的需求来设置,如果任务函数的局部变量多,嵌套层数多,那么相应的堆栈就得大一些,如果堆栈设置小了,很可能出现的结果就是 CPU进入 HardFault,遇到这种情况,就必须把堆栈设置大一点了。另外,有些地方还需要注意堆栈字节对齐的问题,如果任务运行出现莫名其妙的错误(比如用到 sprintf 出错),请考虑是不是字节对齐的问题。

    设置任务优先级, 这个需要根据任务的重要性和实时性设置,高优先级的任务有优先使用 CPU 的权利。

    3.3 初始化 UCOSII,并在 UCOSII 中创建任务

    调用 OSInit,初始化 UCOSII,通过调用 OSTaskCreate 函数创建我们的任务。

    3.4 启动 UCOSII

    调用 OSStart,启动 UCOSII。

    4. 软件配置

    4.1 UCOSII 源码说明

    这里写图片描述

    UCOSII-CORE:是UCOSII 的核心源码,不需要做任何变动。

    UCOSII-PORT :移植 UCOSII 要修改的 3 个代码,这个在移植的时候完成。

    UCOSII-CONFIG : UCOSII 的配置部分,主要由用户根据自己的需要对 UCOSII进行裁剪或其他设置。

    4.2 UCOSII 简易测试

    #include "led.h"
    #include "delay.h"
    #include "sys.h"
    #include "includes.h" 	 
    
    /UCOSII任务设置///
    //START 任务
    //设置任务优先级
    #define START_TASK_PRIO      			10 //开始任务的优先级设置为最低
    //设置任务堆栈大小
    #define START_STK_SIZE  				64
    //任务堆栈	
    OS_STK START_TASK_STK[START_STK_SIZE];
    //任务函数
    void start_task(void *pdata);	
     			   
    //LED0任务
    //设置任务优先级
    #define LED0_TASK_PRIO       			7 
    //设置任务堆栈大小
    #define LED0_STK_SIZE  		    		64
    //任务堆栈	
    OS_STK LED0_TASK_STK[LED0_STK_SIZE];
    //任务函数
    void led0_task(void *pdata);
    
    
    //LED1任务
    //设置任务优先级
    #define LED1_TASK_PRIO       			6 
    //设置任务堆栈大小
    #define LED1_STK_SIZE  					64
    //任务堆栈
    OS_STK LED1_TASK_STK[LED1_STK_SIZE];
    //任务函数
    void led1_task(void *pdata);
    
    
     int main(void)
     {	
    	delay_init();	    	 //延时函数初始化	
    	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);// 设置中断优先级分组2
    	LED_Init();		  	//初始化与LED连接的硬件接口
    	OSInit();   
     	OSTaskCreate(start_task,(void *)0,(OS_STK *)&START_TASK_STK[START_STK_SIZE-1],START_TASK_PRIO );//创建起始任务
    	OSStart();	
     }
    
    	  
    //开始任务
    void start_task(void *pdata)
    {
        OS_CPU_SR cpu_sr=0;
    	pdata = pdata; 
      	OS_ENTER_CRITICAL();			//进入临界区(无法被中断打断)    
     	OSTaskCreate(led0_task,(void *)0,(OS_STK*)&LED0_TASK_STK[LED0_STK_SIZE-1],LED0_TASK_PRIO);						   
     	OSTaskCreate(led1_task,(void *)0,(OS_STK*)&LED1_TASK_STK[LED1_STK_SIZE-1],LED1_TASK_PRIO);	 				   
    	OSTaskSuspend(START_TASK_PRIO);	//挂起起始任务.
    	OS_EXIT_CRITICAL();				//退出临界区(可以被中断打断)
    }
    
    //LED0任务
    void led0_task(void *pdata)
    {	 	
    	while(1)
    	{
    		LED0=0;
    		delay_ms(80);
    		LED0=1;
    		delay_ms(920);
    	};
    }
    
    //LED1任务
    void led1_task(void *pdata)
    {	  
    	while(1)
    	{
    		LED1=0;
    		delay_ms(300);
    		LED1=1;
    		delay_ms(300);
    	};
    }
    

    参考:

    1. 原子库函数手册
    2. 第3章 内核结构
    展开全文
  • 从零开始学习UCOSII操作系统15--总结篇

    万次阅读 多人点赞 2018-04-06 11:17:12
    从零开始学习UCOSII操作系统15--总结篇 前言:在大学的时候,我们班级上面都有很多人觉得学习UCOSII(包括UCOSIII)是没什么厉害的,因为很多人都喜欢去学习Linux操作系统,但是,但是,真实的对整个UCOSII操作...
    
    

    从零开始学习UCOSII操作系统15--总结篇

    前言:在大学的时候,我们班级上面都有很多人觉得学习UCOSII(包括UCOSIII)是没什么厉害的,因为很多人都喜欢去学习Linux操作系统,但是,但是,真实的对整个UCOSII操作系统进行学习,我可以保证,如果你是基于源码级别的阅读的话,绝对是不简单的。仅仅是调用几个API的话,是永远用不好UCOSII的操作系统的。还有你真正学通了UCOSII操作系统的话,那么你对Linux操作系统的内核也不会有太大的难度。

    参考:嵌入式实时操作系统UCOSII原理与应用

    1、UCOSII操作系统是怎么管理任务的?

    用图是最能表现的:
    (1)任务控制块是来管理任务的。
    (2)其中UCOSII把所有的任务都是通过双向链表来连接到一起的,为什么?我难道不能使用数组来分配空间吗?

    关键原因:在整个操作系统中,因为我们不知道用户到底需要多少个任务,所以使用链表的话,在编译后才确认的话,数组的方式优秀很多。

    我个人认为这张图其实是有一些错误的:
    比如里面写了指向任务的指针,我觉得是应该放置在任务堆栈里面的,因为创建任务的时候,是将任务的函数名,传递给任务堆栈,然后任务堆栈再传到CPU的SR寄存器中,实现任务切换的。

    所谓的指向任务的指针:其实就是任务的函数的函数名。

    2、UCOSII操作系统是怎么运行的?

    (1)睡眠态:首先我们的任务是按需分配的,你想要多少个任务的话,那么我们就可以创建多少个任务。刚刚创建的任务是处于睡眠的状态的。

    (2)就绪状态:如果系统为任务配备了任务控制块并且在任务的就绪表中进行了就绪登记的话,则任务就具备了运行的充分条件,这时候任务的状态就叫做就绪态。

    (3)运行状态:处于就绪状态的任务如果经过UCOSII的位图机制,判断为处于最高优先级的任务的话,那么它就可以获得CPU的使用权,这时候就是运行状态。

    (4)中断服务状态:这个真的就没有什么好说的了,连前后台系统都会存在的状态,触发到中断的条件,就会进入中断态,而且不管你是否处于运行态还是怎么样?

    以上的4个状态是我认为UCOSII操作系统中必不可少的状态的。

    (5)等待状态:这个状态的话,是可以通过裁剪UCOSII内核去掉的,其实这个状态就是为了满足任务之间的通讯和任务与中断服务子程序通信产生的一种状态。

    3、UCOSII操作系统任务之间是通过什么进行通信的?

    总所周知,UCOSII的任务是一个特殊的函数,没有类型,没有返回值。
    里面是一个死循环。

    那么为什么它能够跳出来执行别的任务?
    它之所以能够跳出来跟别的任务进行通信的话,在下面的用户需要添加的代码中一定是有一个任务切换的函数调用的。实质上是CPU的SR寄存器中的任务堆栈的切换的过程。

    那么它怎么跟别的任务进行通信?
    常考题:Linux的进程间通信的方式:
    信号量(互斥型信号量)、消息队列、共享内存、消息邮箱、事件标志组

    其中UCOSII任务之间进行通信的方式除了共享内存,其余都存在。

    其中有好几个是类型的:
    比如信号量和互斥型信号量。使用这个是可以占用资源,或者同步任务的运行。

    比如消息邮箱和消息队列,消息队列也称为多个消息邮箱,都是用来在任务之间传递数据的。

    事件标记组:首先请求事件标记组的时候,通过一个整型数的某几个位,如果那几个位都已经置位或者都是为空的话,那么请求事件标记组的任务可以得到运行。

    为什么需要互斥型信号量?
    因为会产生任务优先级的反转问题?

    什么是优先级反转?高优先级任务被低优先级任务剥脱CPU的使用权。
    (1)假设现在有一个低的优先级任务占有CPU的内核,同时他占有了一些资源。
    (2)此时,来了一个高优先级的任务,它想要得到这个资源,但是这个资源已经被低优先级的任务占有了。
    (3)在后来来了一个中等优先级的任务,它因为优先级比低优先级的任务高,但是它的优先级没有高的优先级高,所以它强占CPU的使用权,继续执行。
    (4)这时候,形成的情况就是中等优先级的任务比高优先级的任务更加早的执行,形成优先级反转的问题。

    解决方案1:
    如果低优先级任务占有的那个资源后,后面有更高的优先级任务到来的话,那么就把自己的优先级提高到那个想要占有优先级的任务。

    解决方案2:
    使用互斥型信号量。因为互斥型信号量,只有你自己请求,自己释放,不能通过别的任务释放,如果你请求一个已经请求过的互斥性信号量的话,那么直接跳过执行。

    void task1(void * pada)
    {
        for(;;)
        {
            //用户需要添加的代码
        }
    }
    

    4、UCOSII操作系统的内存管理

    参考:https://www.cnblogs.com/apollius/archive/2013/03/26/2981429.html
    UCOSII中动态内存管理的C语言实现:

    嵌入式编程比较关心的就是内存大小,在有限的内存中实现动态和静态的代码分配是有学问的,对于像malloc这类在运行的时候从堆中请求内存的函数,诺调用次数太多可能会造成内存的快速消息。

    将所有已经分配好的空间计算好,利用全局变量分配到静态的代码空间中。
    实际使用时调用自己编写好的内存管理函数从这块静态空间中申请内存。

    内存的总体大小是受控的,所有涉及到动态内存的地方实际上都已经被预先分配好,在编译时候写在静态代码区。

    INT8U Buff8U [40][32];
    INT16U Buff16U[40][32];

    每个内存管理块中记录了对应存储的数据地址(EntryAddr),当前可用的数组的地址(FreeAddr),当内存进行分配时候,这一位会进行偏移操作,指向还没有被分配的存储数组。而这些管理块也需要一个总的数组来存储其位置,否则这些指针也是无法初始化的指针。并且新建一个指针(*MMUFREE)指向当前还未被分配,可以用的管理块数组中位置。

    MMUType     MMUPool[MMU_BUFF_MAX];  //Store all the MMUs
    MMUType    *MMUFree;                //Pointer to the available MMUType in MMUPool
    
    MMUType    *MMUBuff8;            //Controller of Buff8U
    MMUType    *MMUBuff16;           //Controller of Buff16U
    

    可以看出核心思想是,所有需要实际存储的指针都由数组来保存,所有指针最终指向一个实际的变量,诺要使用这种内存管理,第一步要做的就是初始化MMUPOOL初始化内存池。调用MEMInit()之后将内部所有的成员形成一个链表,由MMUFree指向最开头的一个。

    这样一来就可以为内存管理块分配一个实际的对象了,调用MemCreate()可以分配一个实际的对象给内存管理块,并且将内存管理与实际的内存存储去联系在一起。

    展开全文
  • 一步一步教你使用uCOS-II

    万次阅读 多人点赞 2016-05-17 11:59:01
    第一篇 UCOS介绍 第一篇 UCOS介绍   这个大家都知道。呵呵。考虑到咱们学习的完整性还是在这里唠叨一下。让大家再熟悉一下。高手们忍耐一下吧! uC/OS II(Micro Control Operation System Two)...

    第一篇 UCOS介绍

    第一篇 UCOS介绍

     

    这个大家都知道。呵呵。考虑到咱们学习的完整性还是在这里唠叨一下。让大家再熟悉一下。高手们忍耐一下吧! uC/OS II(Micro Control Operation System Two)是一个可以基于ROM运行的、可裁减的、抢占式、实时多任务内核,具有高度可移植性,特别适合于微处理器和控制器,是和很多商业操作系统性能相当的实时操作系统(RTOS)。为了提供最好的移植性能,uC/OS II最大程度上使用ANSI C语言进行开发,并且已经移植到近40多种处理器体系上,涵盖了从8位到64位各种CPU(包括DSP)。   

    uC/OS II可以简单的视为一个多任务调度器,在这个任务调度器之上完善并添加了和多任务操作系统相关的系统服务,如信号量、邮箱等。其主要特点有公开源代码,代码结构清晰、明了,注释详尽,组织有条理,可移植性好,可裁剪,可固化。内核属于抢占式,最多可以管理60个任务。

    μC/OS-II 的前身是μC/OS,最早出自于1992 年美国嵌入式系统专家Jean J.Labrosse 在《嵌入式系统编程》杂志的5 月和6 月刊上刊登的文章连载,并把μC/OS 的源码发布在该杂志的B B S 上。   

    μC/OS 和μC/OS-II 是专门为计算机的嵌入式应用设计的, 绝大部分代码是用C语言编写的。CPU 硬件相关部分是用汇编语言编写的、总量约200行的汇编语言部分被压缩到最低限度,为的是便于移植到任何一种其它的CPU 上。用户只要有标准的ANSI 的C交叉编译器,有汇编器、连接器等软件工具,就可以将μC/OS-II嵌人到开发的产品中。μC/OS-II 具有执行效率高、占用空间小、实时性能优良和可扩展性强等特点, 最小内核可编译至 2KB 。μC/OS-II 已经移植到了几乎所有知名的CPU 上。   

    严格地说uC/OS-II只是一个实时操作系统内核,它仅仅包含了任务调度,任务管理,时间管理,内存管理和任务间的通信和同步等基本功能。没有提供输入输出管理,文件系统,网络等额外的服务。但由于uC/OS-II良好的可扩展性和源码开放,这些非必须的功能完全可以由用户自己根据需要分别实现。

    uC/OS-II目标是实现一个基于优先级调度的抢占式的实时内核,并在这个内核之上提供最基本的系统服务,如信号量,邮箱,消息队列,内存管理,中断管理等。

    uC/OS-II以源代码的形式发布,但并不意味着它是开源软件。你可以将其用于教学和私下研究(peaceful research);但是如果你将其用于商业用途,那么你必须通过Micrium获得商用许可。

    虽然uCOS-II在商业上使用时需要的得到授权并且费用也是一笔不小的数字,但是他的开源毕竟带领我们走入了内核的世界。在此我代表嵌入式工程师向Mr Jean J.Labrosse 致谢。


    任务管理

    uC/OS-II 中最多可以支持64 个任务,分别对应优先级0~63,其中0 为最高优先级。63为最低级,系统保留了4个最高优先级的任务和4个最低优先级的任务,所有用户可以使用的任务数有56个。   

    uC/OS-II提供了任务管理的各种函数调用,包括创建任务,删除任务,改变任务的优先级,任务挂起和恢复等。   

    系统初始化时会自动产生两个任务:一个是空闲任务,它的优先级最低,该任务仅给一个整形变量做累加运算;另一个是系统任务,它的优先级为次低,该任务负责统计当前cpu的利用率。

    在系统初始化完毕后启动任务时必须创建一份用户任务,也就是说必须有一个应用程序(用户任务,使用应用程序对于我们经常使用Windows用户容易接受一些。呵呵),否则系统会崩溃。当然还有一些其他的要求,咱们后续再说,下面简要概述一下任务管理相关的函数

    1:建立任务OSTaskCreat()/OSTaskCreatExt()
    如果想让UCOS管理用户的任务,必须先建立任务。可以通过将任务的地址和其他参数传递到以下两个函数之一来建立任务。当调用OSTaskCreat()时,需要四个参数:
    OSTaskCreate(void(*task)(void*pd),void*pdata,OS_STK*ptos,INTU prio)
    Task:是指向任务代码的指针,pdata:是任务开始执行是,传递给任务的参数的指针,ptos:是分配给任务的堆栈的栈顶指针,prio是分配给任务的优先级。
    也可以用OSTaskCreatExt(),不过该函数需要9个参数,前四个参数与OSTaskCreat()一样,例如:
    INT8U OSTaskCreateExt(void(*task)(void *pd),void *pdata,OS_STK *ptos, INT8U prio, INT16U   id, OS_STK  *pbos, OS_STK  *pbos, OS_STK  *pbos, INT16U  opt)
    id参数为要建立的任务创建一个特殊的标识符。pbos是指向任务的堆栈栈底的指针,用于堆栈的检验。stk  _size用于指定堆栈成员数目的容量。pext是指向用户附加的数据域的指针,用来扩展任务的OS_TCB。opt用于设定OSTaskCreateExt()的选项,指定是否允许堆栈检验,是否将堆栈清零,任务是否要进行浮点操作等等。
    2:任务堆栈OS_STK()
    每个任务都有自己的堆栈,堆栈必须申明为OS_STK类型,并且由连续的内存空间组成。可以静态分配堆栈空间,也可以动态分配堆栈空间。
    3:堆栈检验OSTaskStkChk()
    有时确定任务实际需要的堆栈空间的大小是很有必要的,因为这样就可以避免为任务分配过多的堆栈空间,从而减少应用程序代码所需的RAM空间。
    4:删除任务OSTaskDel()
    有时需要删除任务,删除任务,是说任务返回并处于休眠态,并不是说任务的代码被删除了,只是任务的代码不再被UCOS调用。删除任务前应保证所删任务并非空闲任务。
    5:请求删除任务OSTaskDelReq()
    有时,任务会占用一些内存缓冲或信号量一类的资源。这时,假如另一个任务试图删除该任务,这些被占用的资源就会因为没有被释放而丢失。在这种情况下,需想办法拥有这些资源的任务在使用完资源后先释放资源,再删除自己。
    6:改变任务的优先级OSTaskChangePrio()
    在建立任务时,会分配给任务一个优先级。在程序运行期间,可以通过调用该函数改变任务的优先级。也就是说,UCOS允许动态的改变任务的优先级。
    7:挂起任务OSTaskSuspend()
    任务挂起是一个附加功能,也就是说,如果任务在被挂起的同时也在等待延迟时间到,那么,需要对任务做取消挂起的操作,并且等待延迟时间到,任务才能转让就绪状态。任务可以挂起自己或者其他任务。
    8:恢复任务OSTaskResume()
    挂起的任务只有通过该函数才能被恢复。
    9:获得任务的信息OSTaskQuery()
    通过调用该函数,来获得自身或其他应用任务的信息


    时间管理

    uC/OS-II的时间管理是通过定时中断来实现的,该定时中断一般为10毫秒或100毫秒发生一次(这个时间片段是OS的作者推荐的,大家可以参考邵贝贝翻译的《嵌入式实时操作系统ucos-II》这本书),时间频率取决于用户对硬件系统的定时器编程来实现。中断发生的时间间隔是固定不变的,该中断也成为一个时钟节拍。这里隐含的意思就是你选择的芯片如果想使用UCOS系统,前提条件一定要有一个Timer。   
    uC/OS-II要求用户在定时中断的服务程序中,调用系统提供的与时钟节拍相关的系统函数,例如中断级的任务切换函数,系统时间函数。
    uCOS时间管理的相关函数
    1:任务延迟函数OSTimeDly()
    Ucos提供一个可以被任务调用而将任务延时一段特定时间的功能函数,即OSTimeDly().任务调用OSTimeDly()后,一旦规定的时间期满或者有其他的任务通过调用OSTimeDlyResume()取消了延时,他就会进入就绪状态。只有当该任务在所有就绪态任务中具有最高的优先级,它才会立即运行。
    2:按时,分,秒延时函数OSRimeDLyHMSM()
    与OSTimeDly()一样,调用OSRimeDlyHMSM()函数也会是UCOS进行一次任务调度,并且执行下一个优先级最高的就绪任务。当OSTimeDlyHMSM()后,一旦规定的时间期满,或者有OSTimeDlyResume(),它就会马上处于就绪态。同样,只有当该任务在所有就绪态任务中具有最高的优先级,他才开始运行。
    3:恢复延时的任务OSTimeDlyResume()
    延时的任务可以不等待延时的期满,而是通过其他任务取消延时而使自己处于就绪态,可以通过该函数来实现,实际上,OSTimeDlyResume()也可以唤醒正在等待的事件。
    4:系统时间OSTimeGet()和OSTimeSet()

    内存管理

    在ANSI C中是使用malloc和free两个函数来动态分配和释放内存。例如在Linux系统中就是这样。但在嵌入式实时系统中,多次这样的操作会导致内存碎片,因为嵌入式系统尤其是uCOS是实地址模式,这种模式在分配任务堆栈时需要整块连续的空间,否则任务无法正确运行。且由于内存管理算法的原因,malloc和free的执行时间也是不确定。这点是实时内核最大的矛盾。

    基于以上的原因uC/OS-II中把连续的大块内存按分区管理。每个分区中包含整数个大小相同的内存块,但不同分区之间的内存快大小可以不同。用户需要动态分配内存时,系统选择一个适当的分区,按块来分配内存。释放内存时将该块放回它以前所属的分区,这样能有效解决碎片问题,同时执行时间也是固定的。

    同时uCOS-II根据以上的处理封装了适合于自己的动态内存分配函数OSMemGet()和OSMemPut(),但是使用这两个函数动态分配内存前需要先创建内存空间,也就是第二段咱们介绍的内存分块。呵呵,不罗嗦了,具体的关于内存管理的函数如下:

    内存控制块的数据结构
    Typedef
    struct

    {void   *osmemaddr    ;指向内存分区起始地址的指针。
    Void   *osmemfreelist  ;指向下一个空余内存控制块或者下一个空余内存块的指针,
    Int32u  osmemblksize  ;
    内存分区中内存块的大小,是建立内存分区时定义的。
    Int32u osmemnblks     ;
    内存分区中总的内存块数量,也是建立该内存分区时定义的。
    Int32u  osmemnfree    ;
    内存分区块中当前获得的空余块数量。
    }os_mem;
    1;建立一个内存分区,OSMemCreate()
    2:分配一个内存块,OSMemGet()
    应用程序通过调用该函数,从已经建立的内存分区中申请一个内存块。该函数唯一的参数是指向特定内存分区的指针。
    3:释放一个内存块,OSMemPut()
    当应用程序不再使用一个内存块时,必须及时的把它释放,并放回到相应的内存分区中,这个操作就是通过调用该函数实现的。
    4:查询一个内存分区的状态,OSQMemQuery()


    任务间通信与同步

    对一个多任务的操作系统来说,任务间的通信和同步是必不可少的。uC/OS-II中提供了4种同步对象,分别是信号量,邮箱,消息队列和事件。所有这些同步对象都有创建,等待,发送,查询的接口用于实现进程间的通信和同步。
    对于这4种同步对象将在后面一一讨论。

    任务调度

    uC/OS-II 采用的是可剥夺型实时多任务内核。可剥夺型的实时内核在任何时候都运行就绪了的最高优先级的任务。
    uC/os-II的任务调度是完全基于任务优先级的抢占式调度,也就是最高优先级的任务一旦处于就绪状态,则立即抢占正在运行的低优先级任务的处理器资源。为了简化系统设计,uC/OS-II规定所有任务的优先级不同,因为任务的优先级也同时唯一标志了该任务本身。
    UCOS的任务调度在一下情况下发生:
    1) 高优先级的任务因为需要某种临界资源,主动请求挂起,让出处理器,此时将调度就绪状态的低优先级任务获得执行,这种调度也称为任务级的上下文切换。
    2) 高优先级的任务因为时钟节拍到来,在时钟中断的处理程序中,内核发现高优先级任务获得了执行条件(如休眠的时钟到时),则在中断态直接切换到高优先级任务执行。这种调度也称为中断级的上下文切换。
    这两种调度方式在uC/OS-II的执行过程中非常普遍,一般来说前者发生在系统服务中,后者发生在时钟中断的服务程序中。
    调度工作的内容可以分为两部分:最高优先级任务的寻找和任务切换。其最高优先级任务的寻找是通过建立就绪任务表来实现的。u C / O S 中的每一个任务都有独立的堆栈空间,并有一个称为任务控制块TCB(Task Control Block)的数据结构,其中第一个成员变量就是保存的任务堆栈指针。任务调度模块首先用变量OSTCBHighRdy 记录当前最高级就绪任务的TCB 地址,然后调用OS_TASK_SW()函数来进行任务切换。


    第二章 搭建UCOS-II 2.52版的调试平台

    在这一章中我们主要讨论UCOSII的源码调试环境,为了给大家一个共同的学习平台,我搜集整理了一写资料,就是以X86为平台,使用BC31(这个堪称骨灰级的编译器)来调试UCOSII源码。当然你也可以用BC45或更高版本的编译器,具体方法大同小异,我在此就不再啰嗦。

    本章节的主要内容包括四点:

    1、下载并安装BC31编译器

    2、下载并安装UCOS-II2.52版本源代码

    3、使用BC31编译UCOS-II源码

    4、让OS的第一个任务RUN起来

    接下来会在每个帖子中讨论一点。耐心等待哦!



    下载并安装BC31编译器

    我在这里提供给大家这个骨灰级的编译器BC31.需要的可以下载。见附件(骨灰级编译器BC31)由于这个软件的比较大,分成两个压缩包。下班了,先到这里,回家再传附件!


    让自己的第一个任务Run起来

    前面已经给大家介绍了如何在PC机上调试UCOS,方法和需要的软件都介绍给大家了,相信有兴趣的朋友已经安装调试了,下面咱们就让自己的第一个任务在PC上Run起来。

    OK,下面我就分步介绍建立自己的第一个任务

    第一步:CopyC:\SOFTWARE\uCOS-II目录下的EX1_x86L文件夹。作为我们的工程模板

    第二步:修改工程模板的名字为:HelloEEWorld

    第三部:按照咱们前面的《使用 BC31 工具编译 UCOS‐II 的源码过程 》修改配置文件;

    第四步:修改Test.c文件,建立自己的第一个任务

    具体的内容我就不再帖子上写了。大家可以参考附件HelloEEWorld.rar里面的Test.c文件。然后编译



    关于UCOS任务的理解

    UCOS的运行是基于任务运行的,为了能够好的使用UCOS我们先要对UCOS的任务的概念做一个理解

    在学习UCOS任务前我们先对我们以前使用的模式做一个回顾--前后台模式。

    这种系统可称为前后台系统或超循环系统(Super-Loops)。应用程序是一个无限的循环,循环中调用相应的函数完成相应的操作,这部分可以看成后台行为(background)。中断服务程序处理异步事件,这部分可以看成前台行 foreground。后台也可以叫做任务级。前台也叫中断级。时间相关性很强的关键操作(Critical operation)一定是靠中断服务来保证的。因为中断服务提供的信息一直要等到后台程序走到该处理这个信息这一步时才能得到处理,这种系统在处理信息的及时性上,比实际可以做到的要差。这个指标称作任务级响应时间。最坏情况下的任务级响应时间取决于整个循环的执行时间。因为循环的执行时间不是常数,程序经过某一特定部分的准确时间也是不能确定的。进而,如果程序修改了,循环的时序也会受到影响。

    这种系统是在我们上学时和做小项目时经常用到,很多工程师称这种方式为“裸奔”。哈哈!我大学毕业后的钱三年写的项目都是在裸奔。

    UCOS-II是基于任务运行的。一个任务,也称作一个线程,是一个简单的程序,该程序可以认为 CPU 完全只属该程序自己。实时应用程序的设计过程,包括如何把问题分割成多个任务,每个任务都是整个应用的某一部分,每个任务被赋予一定的优先级,有它自己的一套 CPU 寄存器和自己的栈空间(如下图所示)。

    可以这么理解,UCOS-II的每一个任务都有一个CPU,任务在运行时占用CPU的全部资源,同时拥有自己的一套寄存器,当任务执行完毕后(时间片到),他把自己的CPU寄存器所有内容保存到自己的堆栈中,同时把CPU让给别的任务,那么得到CPU使用权的任务把自己的CPU寄存器从自己的堆栈中放到真正的CPU寄存器中开始运行,就这样周而复始。

    大家一定不要把任务的运行当成是函数的调用,这完全是两回事。这个我们到后面的任务调度时在细说。每个任务都是一个无限的循环。每个任务都处在以下 5种状态之一的状态下,这5种状态是休眠态, 就绪态、 运行态、 挂起态(等待某一事件发生)和被中断态(参见下图)   休眠态相当于该任务驻留在内存中,但并不被多任务内核所调度。就绪意味着该任务已经准备好, 可以运行了, 但由于该任务的优先级比正在运行的任务的优先级低, 还暂时不能运行。运行态的任务是指该任务掌握了 CPU 的控制权,正在运行中。挂起状态也可以叫做等待事件态WAITING,指该任务在等待,等待某一事件的发生, (例如等待某外设的 I/O 操作,等待某共享资源由暂不能使用变成能使用状态, 等待定时脉冲的到来或等待超时信号的到来以结束目前的等待,等等) 。最后,发生中断时,CPU提供相应的中断服务,原来正在运行的任务暂不能运行,就进入了被中断状态。如下图表示μC/OS-Ⅱ中一些函数提供的服务,这些函数使任务从一种状态变到另一种状态。


    简单的我们可以把每一次任务的切换当成一次中断,这个中断不同于我们在使用前后台模式时的中断,那个中断是硬件中断,中断时需要保存的CPU寄存器是由硬件实现的,而在UCOS中的任务切换是软中断,CPU保存了必要的寄存器后在切换时系统会在保存任务使用的寄存器。


    补充知识-可剥夺型内核和不可剥夺型内核

    不可剥夺型内核

    不可剥夺型内核要求每个任务自我放弃CPU 的所有权。 不可剥夺型调度法也称作合作型多任务,各个任务彼此合作共享一个 CPU。异步事件还是由中断服务来处理。中断服务可以使一个高优先级的任务由挂起状态变为就绪状态。 但中断服务以后控制权还是回到原来被中断了的那个任务,直到该任务主动放弃 CPU 的使用权时,那个高优先级的任务才能获得 CPU的使用权。

    不可剥夺型内核允许每个任务运行,直到该任务自愿放弃 CPU的控制权。中断可以打入运行着的任务。 中断服务完成以后将 CPU 控制权还给被中断了的任务。任务级响应时间要大大好于前后系统,但仍是不可知的,商业软件几乎没有不可剥夺型内核。

    不可剥夺型内核的工作过程见下图:

    可剥夺型内核

     当系统响应时间很重要时,要使用可剥夺型内核。因此,μC/OS-Ⅱ以及绝大多数商业上销售的实时内核都是可剥夺型内核。 最高优先级的任务一旦就绪, 总能得到CPU 的控制权。当一个运行着的任务使一个比它优先级高的任务进入了就绪态, 当前任务的CPU使用权就被剥夺了,或者说被挂起了,那个高优先级的任务立刻得到了 CPU的控制权。如果是中断服务子程序使一个高优先级的任务进入就绪态,中断完成时,中断了的任务被挂起,优先级高的那个任务开始运行。使用可剥夺型内核,最高优先级的任务什么时候可以执行,可以得到 CPU的控制权是可知的。使用可剥夺型内核使得任务级响应时间得以最优化。

    可剥夺型内核的工作过程是这样的:



    UCOS-II 任务调度

    任务调度是内核的主要职责之一,就是要决定该轮到哪个任务运行了。多数实时内核是基于优先级调度法的,UCOS也不例外。每个任务根据其重要程度的不同被赋予一定的优先级。基于优先级的调度法指,CPU总是让处在就绪态的优先级最高的任务先运行。然而,究竟何时让高优先级任务掌握CPU 的使用权,有两种不同的情况,这要看用的是什么类型的内核,是不可剥夺型的还是可剥夺型内核。

    上一次咱们已经介绍了可剥夺型内核和不可剥夺型内核的工作过程了。在此不再赘述!

    当多任务内核决定运行另外的任务时,它保存正在运行任务的当前状态,即CPU寄存器中的全部内容。这些内容保存在任务的当前状况保存区,也就是任务自己的栈区之中,上一次讨论的内容中有这个图示。入栈工作完成以后,就是把下一个将要运行的任务的当前状况从该任务的栈中重新装入 CPU 的寄存器,并开始下一个任务的运行。这个过程叫做任务切换。任务切换过程增加了应用程序的额外负荷。CPU的内部寄存器越多,额外负荷就越重。做任务切换所需要的时间取决于CPU有多少寄存器要入栈。实时内核的性能不应该以每秒钟能做多少次任务切换来评价。而是要看OS总的关中断时间。总的关中断时间越短说明这个内核的实时性越好。这个问题在前面一个坛友的问题中我做了详细的描述,有兴趣的朋友可以在UCOS这个版块找找这个帖子。

    任务调度的算法有很多种。一种是基于优先级的。一种是基于时间片的。这两种算法在邵贝贝教授翻译的《UCOS-II内核详解》这本书中有详细解释。我就不再重复。如果坛子里有朋友对此有什么不明白。可以在这里留言。咱们再讨论。



    UCOS-II的文件结构

    前面我们对UCOS的基础知识做了了解,其中有些地方由于邵贝贝翻译的树上讲解的很少我就没有班门弄斧,大家可以结合那本书来看。有问题或不明白的在这里讨论,欢迎大家剔除问题。

    这次我们主要了解UCOS-II的文件结构。等对UCOS文件结构了解以后,我们就逐一的去讲解其各章的重点和难点,达到在短时间内学会使用UCOS。

    我们利用这张图片把UCOS的内部做一个解剖,我们可以清楚的看到UCOS内核的结构及层次,在这个图的最下面是我们使用的硬件,就是我们的移植平台,比如STM32F103XX系列的最小系统版、51最小系统版。呵呵,我本人觉得把UCOS移植到51上的意义不大。只是学习可以,使用我就不建议了!从图中我们可以知道,要想移植UCOS你的硬件平台必须具备一个定时器,也就是上图中的TIMER。这个TIMER是用来给UCOS提供时钟节拍的,相当于我们人的心跳。如果没有这个TIMER,统统就无法运行。

    再往上就是软件了,软件的第一层是我们移植的重点,这三个文件内主要包括一些与处理器相关的代码,在后面我们我们再讲解移植过程的时候会详细的讨论到这三个文件。

    在往上左侧就是系统内核源码的各个文件。有兴趣的坛友可以参考邵贝贝教授翻译的书进行深入学习,由于我在这里的主要任务是告诉大家如何使用UCOS,故不再过多的讲解源码部分,只是告诉大家如何使用即可。当然,如果你在研究过程中遇到问题可以拿出来和大家共同讨论,右侧是系统的配置文件,相对比较简单,主要涉及到一些功能的裁剪。

    最上层是我们的应用软件,相当于我们在电脑上使用的Office软件等,当然这里是你自己的任务代码。

     


    UCOS的任务及状态

    任务的资源主要包括以下几部分,ECB控制块、任务堆栈、任务代码及与CPU共用的寄存器和CPU的使用权



    第4章_uCOS-II及其任务.ppt

    这是一个讲解任务的详细资料,提供给大家参考

    嵌入式实时操作系统uCOS-II_邵贝贝.pdf

    http://ishare.iask.sina.com.cn/f/11711644.html

    关于UCOS信号量

    一:信号量的理解:

       (1)信号量可以分为两种:一种是二值信号量(0和1),一种是N值信号量(计数式信号量)。

        二值信号量的意思是可以有多少任务同时享用这个信号量。比如二值信号,就是只有1个任务可以使用。当有一个任务使用该信号量的时候,那么其他需要使用该信号量的任务就必须等待,直到该任务释放该信号量。这种信号量可以看作一把钥匙。

        对于N值信号量(计数式信号量),就是说可以同时有N-1个任务同时使用该信号量。对于二值信号量,N=1。

      (2)建立信号量的工作必须在任务级代码中或者多任务启动之前完成。

    二:任务如何得到信号量的问题:

       想得到信号量的任务,必须执行等待操作(pend)。在信号量的建立的时候,我们首先确定了该信号量可以被共享的资源数(N),并将其赋值给pevent->OSEventCnt。如果信号量有效(非0),即pevent->OSEventCnt>0,则信号量减1,任务得以继续运行。如果信号量无效,即pevent->OSEventCnt==0,则等待信号量的任务就被列入等待信号量的任务表中。许多内核允许定义等待超时,当等待时间超过了设定值,该信号量还是无效,则等待该信号量的任务进入就绪态,准备运行,并返回出错代码(等待超时错误)。

    三:任务对信号量的释放问题:

        任务执行发信号(post)操作来释放信号量。如果没有任务等待信号量,那么信号量的值仅是简单的加1(则信号量大于0,有效);如果有任务等待该信号量,那么就会有另一个任务进入就绪态,信号量的值就不加1。

    之后,这个释放的信号量给那个等待中的任务,要看内核如何调度的。收到信号量的任务可能是如下两者之一:

        ◆等待任务中,优先级最高的;(uc/os-ii仅支持这种方式)。

        ◆最早开始等待信号量的任务(如果是按先进先出FIFO原则)。

    四:信号量的有效与无效的问题:

        信号量有效:信号量的计算器非0(.OSEventCnt!=0)。信号量有效表示任务对资源可用。

        信号量无效:信号量的计算器为0。信号量无效表示任务对目前资源不可用,需要等待其他另一个任务(或者中断服务子程序)发出该信号量(OSSemPost)。

    五:关于信号量的三个重要函数:

    ◆OSSemCreate() 创建一个信号量  (注:由任务或启动代码操作)

        创建工作必须在任务级代码中或者多任务启动之前完成。功能只要是先获取一个事件控制块ECB,写入一些参数。其中调用了OS_EeventWaitListInt()函数,对事件控制块的等待任务列表进行初始化。完成初始化工作后,返回一个该信号量的句柄(Handle)。


    ◆OSSemPend() 等待一个信号量                  (注:只能由任务操作)

       本函数应用于任务试图获得共享资源的使用权、任务需要与其他任务或中断同步及任务需要等待特定事件发生的场合。

       如果任务Task_A调用OSSemPend(),且信号量的值有效(非0),那么OSSemPend()递减信号量计数器(.OSEventCnt),并返回该值。换句话说,Task_A获取到共享资源的使用权了,之后就执行该资源。

       如果如果任务Task_A调用OSSemPend(),信号量无效(为0),那么OSSemPend()调用OS_EventTaskWait()函数,把Task_A放入等待列表中。(等待到什么时候呢?要看OSSemPost()(或者等待超时情况),由它释放信号量并检查任务执行权,见下资料)

    ◆OSSemPost() 发出(释放)一个信号量          (注:由任务或中断操作)

        本函数其中调用OS_EventTaskRdy()函数,把优先级最高的任务Task_A(在这假如是Task_A,另外假设当前调用OSSemPost()的任务是Task_B)从等待任务列表中去除,并使它进入就绪态。然后调用OSSched()进行任务调度。如果Task_A是当前就绪态中优先级最高的任务,则内核执行Task_A;否则,OSSched()直接返回,Task_B继续执行。



    UCOS另类信号量--互斥信号量

    在UCOS的信号量使用过程中,我们经常会用的是二值信号量,而在二值信号两种用的醉的情况就是互斥信号量。互斥信号是本身是一种二进制信号,具有超出uCOS-II提供的一般信号机制的特性。由于其特殊性,UCOS的作者将其独立成章,单独对待。组织了一套对于互斥信号量管理的单独函数。互斥信号量具有以下特点: 1) 降解优先级反转。 2) 实现对资源的独占式访问(二值信号量)。

    在应用程序中使用互斥信号是为了减少优先级翻转问题,当一个高优先级的任务需要的资源被一个低优先级的任务使用时,就会发生优先级翻转问题。为了减少优先级翻转问题,内核可以提高的优先级任务的优先级,先于高优先级的任务运行,释放占用的资源。
    为了实现互斥,实时内核需要具有支持在同一优先级具有多个任务的能力。不幸的是,UC/OS-II不允许在相同的优先级有多个任务,必须只有一个任务。但是我们有另外的方法解决这个问题。可以把需要资源的高优先级任务上面的一个任务使用Mutex保留,允许提高的优先级任务的优先级。
    举一个mutexes信号工作的例子,如l下面的程序所示。
    其中有三个任务可以使用共同的资源,为了访问这个资源,每个任务必须在互斥信号 ResourceMutex上等待(pend),任务#1有最高优先级10,任务#2优先级为15,任务#3优先级为20,一个没有使用的正好在最高优先级之上的优先级#9用来作为优先级继承优先级。如main()所示,代码中(1)进行uC/OS-II初始化,并通过调用OSMutexCreate()代码中(2)创建了一个互斥信号。需要注意的是,OSMutexCreate()函数使用PIP最为参数。然后创建三个任务代码中(3),启动uC/OS-II 代码中(4).
    假设任务运行了一段时间,在某个时间点,任务#3最先访问了共同的资源,并得到了互斥信号,任务#3运行了一段时间后被任务#1抢占。任务#1需要使用这个资源,并通过调用OSMutexPend()企图获得互斥信号,这种情况下,OSMutexPend()会发现一个高优先级的任务需要这个资源,就会把任务#3的优先级提高到9,同时强迫进行上下文切换退回到任务#3执行。 任务#3可以继续执行然后释放占用的共同资源。任务#3通过调用OSMutexPost()释放占用的mutex信号,OSMutexPost()会发现mutex被一个优先级提升的低优先级的任务占有,就会把任务#3的优先级返回到20。把资源释放给任务#1使用,执行上下文切换到任务#1 
    -----------------------------------------------------------------
    OS_EVENT *ResourceMutex;
    OS_STK TaskPrio10Stk[1000];
    OS_STK TaskPrio15Stk[1000];
    OS_STK TaskPrio20Stk[1000];
    void main (void)
    {
    INT8U err;
    OSInit(); /* (1) */
    /* ---------- 应用程序初始化 ---------- */
    OSMutexCreate(9, &err); /* (2) */
    OSTaskCreate(TaskPrio10, (void *)0, &TaskPrio10Stk[999], 10); /* (3) */
    OSTaskCreate(TaskPrio15, (void *)0, &TaskPrio15Stk[999], 15);
    OSTaskCreate(TaskPrio20, (void *)0, &TaskPrio20Stk[999], 20);
    /* ---------- Application Initialization ---------- */
    OSStart(); /* (4) */
    }
    void TaskPrio10 (void *pdata)
    {
    INT8U err;
    pdata = pdata;
    while (1) {
    /* --------- 应用程序代码 ---------- */
    OSMutexPend(ResourceMutex, 0, &err);
    /* ------- 访问贡献资源 ------ */
    OSMutexPost(ResourceMutex);
    /* --------- 应用程序代码 ---------- */
    }
    }
    void TaskPrio15 (void *pdata)
    {
    INT8U err;
    pdata = pdata;
    while (1) {
    /* ---------应用程序代码 ---------- */
    OSMutexPend(ResourceMutex, 0, &err);
    /* ------- 访问共享资源 ------ */
    OSMutexPost(ResourceMutex);
    /* --------- 应用程序代码 ---------- */
    }
    }
    void TaskPrio20 (void *pdata)
    {
    INT8U err;
    pdata = pdata;
    while (1) {
    /* ---------应用程序代码---------- */
    OSMutexPend(ResourceMutex, 0, &err);
    /* -------访问共享资源------ */
    OSMutexPost(ResourceMutex);
    /* ---------应用程序代码---------- */
    }
    }
    上面代码为互斥信号使用示例
    uC/OS-II'互斥信号包含三个元素,一个flag表示当前mutex是否能够获得(0或1);一个priority表示使用这个mutex的任务,以防一个高优先级的任务需要访问mutex;还包括一个等待这个mutex的任务列表。
    为了启动uC/OS-II’s mutex服务,应该在OS_CFG.H中设置OS_MUTEX_EN=1。在使用一个互斥信号之前应该首先创建它,创建一个mutex信号通过调用OSMutexCreate()完成,mutex的初始值总是设置为1,表示资源可以获得。
    uC/OS-II提供了六种访问互斥信号量的操作 OSMutexCreate(), OSMutexDel(),OSMutexPend(), OSMutexPost(), OSMutexAccept() and OSMutexQuery(). 
    展示了任务和互斥信号量的关系。一个互斥信号量只能被任务访问。在以后的资料中使用钥匙符号表示互斥信号。钥匙符号表明互斥信号用来访问共享资源。没有钥匙就无法访问。只有得到钥匙的任务才有资格访问共享资源




    UCOS互斥信号量操作函数分析

    //建立并初始化一个互斥型信号量(优先级继承优先级(PIP)、出错代码指针)

    OS_EVENT  *OSMutexCreate (INT8U prio, INT8U *err)

    {

    #if OS_CRITICAL_METHOD == 3          /* Allocate storage for CPU status register */

        OS_CPU_SR  cpu_sr;

    #endif    

        OS_EVENT  *pevent; 



        if (OSIntNesting > 0) {                 /* See if called from ISR ...               */

            *err = OS_ERR_CREATE_ISR;       /* can''t CREATE mutex from an ISR    */

            return ((OS_EVENT *)0);

        }//不能从ISR中建立,不允许在ISR中调用此函数

    #if OS_ARG_CHK_EN > 0

        if (prio >= OS_LOWEST_PRIO) {              /* Validate PIP         */

            *err = OS_PRIO_INVALID;

            return ((OS_EVENT *)0);

        }//不合理的PIP

    #endif

        OS_ENTER_CRITICAL();

    if (OSTCBPrioTbl[prio] != (OS_TCB *)0) {    /* Mutex priority must nalready exist*/ 



    //确认PIP没有被任何任务占用。OSTCBPrioTbl[ ]中的一个指向NULL的空指针指示//PIP有效

                  

            OS_EXIT_CRITICAL();          /* Task already exist at priority ...       */

            *err = OS_PRIO_EXIST;            /* ... inheritance priority      */

                  //如果优先级存在 ,则出错。

            return ((OS_EVENT *)0);                            

        }

        OSTCBPrioTbl[prio] = (OS_TCB *)1;    /* Reserve the table entry       */

           //置非空指针,将这个优先级保留下来。

        pevent             = OSEventFreeList;   /* Get next free event control block       */

           //从空余ECB中得到一块空的ECB。

        if (pevent == (OS_EVENT *)0) {         /* See if an ECB was available    */

                  //看ECB是否可用

            OSTCBPrioTbl[prio] = (OS_TCB *)0;        /* No, Release the table entry */

                  //如果不可用,释放此优先级表入口

            OS_EXIT_CRITICAL();

            *err = OS_ERR_PEVENT_NULL;        /* No more event control blocks */

            return (pevent);

        }

        OSEventFreeList = (OS_EVENT *)OSEventFreeList->OSEventPtr;   /* Adjust the free list     

           //如果可用,重新调整事件控制块的表头

        OS_EXIT_CRITICAL();

        pevent->OSEventType = OS_EVENT_TYPE_MUTEX;   //将其标记为互斥型信号量

        pevent->OSEventCnt  = (prio << 8) | OS_MUTEX_AVAILABLE;/* Resource is available                    */              // (#define  OS_MUTEX_AVAILABLE      0x00FF)

           //mutex为有效值,同时将PIP保存起来。值得注意的是,事件计数器.OSEventCnt

        //在此处的用法不同,高八位用于保存PIP的值,低侂位在资源无任务占用

        //时的值为0xff,有任务占用时为占用mutex任务的优先级。这个避免了增加额

        //外的空间,节约对RAM的占用量

        pevent->OSEventPtr  = (void *)0;          /* No task owning the mutex       */

           //消息正在初始化,所以没有等待这个mutex的任务

        OS_EventWaitListInit(pevent);//初始化事件等待列表

        *err                = OS_NO_ERR;

        return (pevent);

    }

    PIP是该函数的参数,指定优先级继承优先级。当发生优先级反转时,将占用该mutex的任务的优先级太高到PIP。



    UCOS互斥信号量操作函数分析

    等待(申请)一个互斥信号量:OSMutexPend()

    关键代码剖析:

    void  OSMutexPend (OS_EVENT *pevent, INT16U timeout, INT8U *err)

    {

       //不得在中断中调用该函数

        if (OSIntNesting > 0) {                                

            *err = OS_ERR_PEND_ISR;                            

            return;

    }

    OS_ENTER_CRITICAL();    

    //信号量可用                                              

    if ((INT8U)(pevent->OSEventCnt & OS_MUTEX_KEEP_LOWER_8) 

                                   == OS_MUTEX_AVAILABLE) {

            pevent->OSEventCnt &= OS_MUTEX_KEEP_UPPER_8;      

            //将计数器低8为置成占用该mutex的任务(当前任务)的优先级。

            pevent->OSEventCnt |= OSTCBCur->OSTCBPrio;

            //在mutex中保存占用信号量的任务:修改该mutex的OSEventPtr ,使其指向当前任务

            pevent->OSEventPtr  = (void *)OSTCBCur;            

            OS_EXIT_CRITICAL();

            //信号量可用,正常返回。

            *err  = OS_NO_ERR;

            return;

    }

    //信号量不可用:即已被占用

    //从该信号量中获得PIP

    pip   = (INT8U)(pevent->OSEventCnt >> 8); 

    //从该信号量中获得占用该信号量的任务的优先级。                    

    mprio = (INT8U)(pevent->OSEventCnt & OS_MUTEX_KEEP_LOWER_8);  

    //从信号量中获得占用该信号量的任务

    ptcb  = (OS_TCB *)(pevent->OSEventPtr);  

    /*

         如果原先占用该mutex的优先级比提出申请该mutex的任务的优先级低

        (mprio > OSTCBCur->OSTCBPrio),则提升原任务的优先级至PIP

    */

        if (ptcb->OSTCBPrio != pip && mprio > OSTCBCur->OSTCBPrio) {  

            if ((OSRdyTbl[ptcb->OSTCBY] & ptcb->OSTCBBitX) != 0x00) {     

                if ((OSRdyTbl[ptcb->OSTCBY] &= ~ptcb->OSTCBBitX) == 0x00) {

                    OSRdyGrp &= ~ptcb->OSTCBBitY;

                }

                //若原任务已就绪,则将其从就绪表中删除,并置就绪标志rdy

                rdy = TRUE;

            } else {

                rdy = FALSE;                                          

            }

            //修改优先级,及相关参数

            ptcb->OSTCBPrio  = pip;                     

            ptcb->OSTCBY    = ptcb->OSTCBPrio >> 3;

            ptcb->OSTCBBitY  = OSMapTbl[ptcb->OSTCBY];

            ptcb->OSTCBX     = ptcb->OSTCBPrio & 0x07;

            ptcb->OSTCBBitX   = OSMapTbl[ptcb->OSTCBX];

            //如果原任务是就绪的,则继续让新的优先级就绪

            if (rdy == TRUE) {                                

                OSRdyGrp               |= ptcb->OSTCBBitY;    

                OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;

            }

            OSTCBPrioTbl[pip]   = (OS_TCB *)ptcb;

    }

    //让提出申请的任务先等待(从就绪表中删除,如mutex的等待队列)………

        OSTCBCur->OSTCBStat |= OS_STAT_MUTEX;            

        OSTCBCur->OSTCBDly   = timeout;                   

        OS_EventTaskWait(pevent);  

    OS_EXIT_CRITICAL();

    //执行任务切换(如果原来低优先级的任务优先级被抬高了,则该任务将被执行)

        OS_Sched();                                        

    OS_ENTER_CRITICAL();

    //提出申请的任务被唤醒继续执行

    if (OSTCBCur->OSTCBStat & OS_STAT_MUTEX) {

        //1)由于等待超时被定时器唤醒        

            OS_EventTO(pevent);

            OS_EXIT_CRITICAL();

            *err = OS_TIMEOUT;                            

            return;

    }

    /*

          2)原先占用mutex的任务执行完成释放了mutex

             并唤醒了等待该mutex的最高优先级的任务

    */

        OSTCBCur->OSTCBEventPtr = (OS_EVENT *)0;

        OS_EXIT_CRITICAL();

        *err = OS_NO_ERR;

    }

    释放一个互斥信号量

    释放一个互斥信号量:OSMutexPost()

    关键代码剖析:

    INT8U  OSMutexPost (OS_EVENT *pevent)

    {

       //不得在中断中调用该函数

        if (OSIntNesting > 0) {                           

            return (OS_ERR_POST_ISR);                    

        }

    OS_ENTER_CRITICAL();

    //从该信号量中获得PIP

    pip  = (INT8U)(pevent->OSEventCnt >> 8);          

    /*

        从该信号量中获得占用该信号量的任务的优先级。

        在OSEventCnt中的低8位保存占用mutex的任务的原始优先级,

        不随优先级的提高而改变。

    */

    prio = (INT8U)(pevent->OSEventCnt & OS_MUTEX_KEEP_LOWER_8);  

    /*

        确认释放mutex的任务确实是占用mutex的任务自身。

        占用/申请mutex的任务的优先级可能是pip(被提高),也可能是原先任务的优先级。

    */

        if (OSTCBCur->OSTCBPrio != pip && 

            OSTCBCur->OSTCBPrio != prio) {               

            OS_EXIT_CRITICAL();

            //若释放mutex的任务非占用/申请的任务,则返回错误信息。

            return (OS_ERR_NOT_MUTEX_OWNER);

    }

       //若当前释放mutex的任务的优先级为pip,则需将该任务的优先级降到原来水平

    if (OSTCBCur->OSTCBPrio == pip) {

       //首先将pip从就绪表删除

            if ((OSRdyTbl[OSTCBCur->OSTCBY] &= ~OSTCBCur->OSTCBBitX) == 0) {

                OSRdyGrp &= ~OSTCBCur->OSTCBBitY;

            }

            //将任务优先级修改为原始优先级,并修改相关参数

            OSTCBCur->OSTCBPrio   = prio;

            OSTCBCur->OSTCBY     = prio >> 3;

            OSTCBCur->OSTCBBitY   = OSMapTbl[OSTCBCur->OSTCBY];

            OSTCBCur->OSTCBX      = prio & 0x07;

            OSTCBCur->OSTCBBitX    = OSMapTbl[OSTCBCur->OSTCBX];

            //将修改优先级后的任务重新如就绪表

            OSRdyGrp           |= OSTCBCur->OSTCBBitY;

            OSRdyTbl[OSTCBCur->OSTCBY] |= OSTCBCur->OSTCBBitX;

            OSTCBPrioTbl[prio]    = (OS_TCB *)OSTCBCur;

        }

    OSTCBPrioTbl[pip] = (OS_TCB *)1;                  

    //若mutex的等待列表不空,唤醒等待列表中最高优先级的任务,并将mutex分配给它

    if (pevent->OSEventGrp != 0x00) {           

        /*

            唤醒等待列表中最高优先级的任务(从mutex等待列表中删除,使其入就绪表),

            清除等待任务的OS_STAT_MUTEX标志,并返回其优先级prio

        */

            prio  = OS_EventTaskRdy(pevent, (void *)0, OS_STAT_MUTEX);

            pevent->OSEventCnt &= OS_MUTEX_KEEP_UPPER_8;  

            //将mutex分配给新任务:置OSEventCnt为prio

            pevent->OSEventCnt |= prio;

            //在mutex中保存占用信号量的任务

            pevent->OSEventPtr  = OSTCBPrioTbl[prio];     

            OS_EXIT_CRITICAL();

            //任务切换(如果唤醒的任务优先级比当前任务高,则使唤醒的任务得到运行)

            OS_Sched();                                   

            return (OS_NO_ERR);

    }

    //mutex的等待列表为空,即该mutex可用:置mutex可用标志及占用任务指针。

        pevent->OSEventCnt |= OS_MUTEX_AVAILABLE;        

        pevent->OSEventPtr  = (void *)0;

        OS_EXIT_CRITICAL();

        return (OS_NO_ERR);

    }


    UCOS事件标志组管理

    今天我们就看看事件标志组的使用和管理吧

    事件标志组(event flag)包含两部分:

    typedef struct

    {

    INT8U OSFlagType;

    void *OSFlagWaitList;

    OS_FLAGS OSFlagFlags;

    }OS_FLAG_GRP;

    1 组中各事件状态的标志位

    2 等待这些标志位或清除的任务列表 (这里是双向链表) 用于删除标志时检查是否有等待该

    标志的任务链表包含3个数据结构: OS_FLAG_GRP, OS_TCB,OS_FLAG-NODE 用来记录任务在等待哪些标志位及等待方式(与/或),当一个任务开始等待某些标志位时建立一个OS_FLAG-NODE,当这些等待的事件标志位发生后,删除数据结构。

    当某个任务需要与多个任务同步时,须要使用事件标志组。
    ⒈弄清楚OS_FLAG_GRP、OS_FLAG_NODE和OS_TCB之间的关系。
    当一个任务开始等待某些事件标志位时,就回建立一个事件标志节点OS_FLAG_NODE数据结构,并且将任务所要等待的事件标志位写入OS_FLAG_NODE的分量.OSFlagNodeFlags。然后将该数据结构分量.OSFlagNodeFLagGrp指向事件标志组OS_FLAG_GRP,将.OSFlagNodeTCB指向该任务的控制块OS_TCB,建立起任务与事件标志组之间的联系,说明该任务是等待该事件标志组中某些事件标志位的任务。当有多个任务都需要等待某个事件标志组中某些事件标志位时,这些任务分别建立自己的事件标志节点。并且将这些事件标志节点通过分量.OSFlagNodeNext和.OSFlagNodePrev连接成链。
    ⒉任务可以等待事件标志组中某些位置位1,也可以等待事件标志组中某些位清0,而置1(或清0)又可以分为所有事件都发生的“与”型和任何一个事件发生的“或”型。这样便有了4种不同的类型存放在.OSFlagNodeWaitType(OS_FLAG_NODE)中。
    ⒊事件标志组和信号量我觉得是有不同的。
    信号量建立以后,假设初始值为N,前N个任务调用OSSemPend()函数都会得到信号量。之后如果第N+1个任务调用OSSemPend()函数申请信号量,该任务将会被置为等待事件发生的状态(睡眠态)。只到前N个任务中有任务运行完了所要运行的程序,调用OSSenmPost()函数,释放了所占用了信号量,第N+1个任务。(这里假设该任务是所有等待信号量任务中优先级最高的任务)才会获得信号量,被从睡眠态转入就绪态。
    而事件标志组是事件标志组建立之后,某个任务需要事件标志组中某些事件标志位(置位或者清0)才能继续运行,于是任务调用OSFlagPend()函数,而此时若这些标志位满足要求,任务返回,继续执行。否则,任务将被挂起。而当有另外一个任务调用OSFlagPost()函数将前一个任务所需要的标志位(置位或清0)使之满足要求,前一个被挂起的任务将被置为就绪态。因此几个任务可以同时得到所需要的事件标志进入就绪态。注意:只要任务所需要的标志位满足要求,任务便进入就绪态。与信号量不同,信号量中的任务需要是在等待该信号量中优先级最高的任务才能得到信号量进入就绪态。事件标志组可以一个任务与多个任务同步,而信号量只能是一个任务与另一个任务同步。

    等讲完邮箱和消息队列后然后对UCOS的内部通信机制做一个总结,然后就该讲关于移植的过程了
    考虑了好多方法。最后还是在有问必答的帖子上有个坛友要51下移植好的UCOS源码+在Proteus下仿真。我觉得这道是一个好的办法
    所以我决定这几天抽出点时间移植一个UCOS到STC51单片机上。使用Keil4.0+Proteus仿真。作为大家共同学习移植的平台。
    基本外设如下:
    输出设备:液晶屏(主要为以后讲解UCGUI做准备)+串口
    输入设备:4*4键盘
    其它:DS18B20温度采集+LED跑马灯
    大家有好的主意可以提出来,咱们争取做一个完善的开发板。用于学习UCOS+UCGUI



    uC/OS-II中的消息邮箱

    消息邮箱是uC/OS-II中的另一种通信机制,可以使一个任务或者中断服务子程序向另一个任务发送一个指针型的变量。通常该指针指向一个包含了“消息”的特定数据结构。
    各个任务之间把自己的数据封装完毕后可以以邮箱的形式发送给其它的任务使用,从而达到任务间通信的目的。
    应用程序可以使用多少个邮箱,其最大数目是由OS_CFG.H文件中的配置常数OS_MAX_EVENTS设定。
    下面以一个实例来向大家讲解一下邮箱的使用过程。
    具体的邮箱使用过程如下:
    第一步:在程序开始先定义一个邮箱,是指针形式的。定义函数如下:
    OS_EVENT *MsgBox;
    第二步:在主程序中建立邮箱:
    MsgBox = OSMboxCreate((void *)0); // 创建邮箱并初始化为空 
    第三步:在接受函数中定义接受时需用到的两个变量,一个为指针形式
    INT8U   err;
    INT32U *msg;
    在其循环函数中接受邮箱信息:
    msg = OSMboxPend(MsgBox, 0, &err); // 接收Task1发来的消息
    Messgae = msg[0]; // 接收消息到指定的变量
    第四步:在发送函数中也需要先定义两个变量
    INT8U err;
    volatile  INT32U MsgData = 1000;
    也是在其循环函数中发送邮箱信息:
    err = OSMboxPost(MsgBox, (void*)&MsgData); // 向TaskPWM发送消息
    注意:在使用时一定要注意数据类型的定义,一定要统一起来。
    下面分别对油箱相关的函数做一个简要的描述。
    任务或者中断服务子程序都可以调用函数OSMboxPost(),OSMboxPostOpt() ,而只有任务可以调用OSMboxDel() OSMboxPend(), OSMboxQuery()。

    void *OSMboxAccept (OS_EVENT *pevent)  无等待的从邮箱中得到一则消息。

    OS_EVENT *OSMboxCreate (void *msg)  建立一个邮箱。
    msg:用来初始化建立的消息邮箱,如果该指针不为空,则建立的消息邮箱将含有消息。
    返回值:指向分配给所建立的消息邮箱的事件控制块的指针。如果没有可用的事件控制块,则返回空指针。

    OS_EVENT *OSMboxDel (OS_EVENT *pevent, INT8U opt, INT8U *err)
    删除一个邮箱。当将OS_CFG.H文件中的OS_MBOX_DEL_EN设为1时,该函数才会被编译。使用该函数时要注意,多个任务可能试图操作已经删除的邮箱。在删除邮箱之前,必须首先删除可能操作该邮箱的所有任务。
    pevent:指向邮箱的指针。该指针是在邮箱建立时返回给用户应用程序的指针。
    opt:该先项定义邮箱的删除条件,可以选择只能在已经没有任何在等待该邮箱的消息时,才能删除邮箱(OS_DEL_NO_PEND);或者不管有没有任务在等待邮箱的消息,立即删除邮箱(OS_DEL_ALWAYS),在这种情况 下,所有等待邮箱消息的任务都会立即进入就绪态。
    err:指向出错代码的指针。返回的出错代码可以是以下几种情况之一。
    OS_NO_ERR 调用成功,邮箱已经被删除。
    OS_ERR_DEL_ISR 试图在中断服务子程序中删除邮箱。
    OS_ERR_INVALID_OPT 无效的opt参数,用户没有将opt定义为上述两种情况之一。
    OS_ERR_EVENT_TYPE pevent不是指向邮箱的指针。
    OS_ERR_PEVENT_NULL 已经没有OS_EVENT数据结构可以使用。
    返回值:返回NULL表示邮箱已被删除;返回pevent表示邮箱没有删作。

    void *OSMboxPend (OS_EVENT *pevent, INT16U timeout, INT8U *err)  等待邮箱中的消息。
    pevent:指向即将接收消息的消息邮箱的指针。
    timeout:允许一个任务在经过了指定数目的时钟节拍后还没有得到需要的消息时恢复运行。如果该值为0表示任务将持续等待消息。
    err:指向包含错误码的变量的指针。该函数返回的错误码可能为下述几种情况
    OS_NO_ERR 消息被正确地接收。
    OS_TIMEOUT 消息没有在指定的等待时间内送到。
    OS_ERR_EVENT_TYPE pevent不是指向消息邮箱的指针。
    OS_ERR_PEND_ISR 从中断调用该函数。
    OS_ERR_PEVENT_NULL pevent是空指针。
    返回值:该函数返回接收的消息并将*err置为OS_NO_ERR.

    INT8U OSMboxPost (OS_EVENT *pevent, void *msg)  向邮箱发送一则消息。
    pevent:指向即将接收消息的消息邮箱的指针。
    msg:即将实际发送给任务的的消息。消息是一个以指针表示的苛种数据类型的变量,在不同的程序中消息的使用也可能不同。不允许传递一个空指针,国灰这意味着消息邮箱为空。
    返回值:该函数的返回值为下述之一:
    OS_NO_ERR 消息成功地放到消息邮箱中。
    OS_MBOX_FULL 消息邮箱已经包含了其他消息,已满。
    OS_ERR_EVENT_TYPE pevent不是指向消息邮箱的指针。
    OS_ERR_PEVENT_NULL pevent是空指针。
    OS_ERR_POST_NULL_PTR 用户试图发出空指针。根据规则,在这里不支持空指针。

    INT8U OSMboxPostOpt (OS_EVENT *pevent, void *msg, INT8U opt)  向邮箱发送一则消息。该函数可以向等待邮箱的所有任务发送消息(广播)。
    pevent:指向即将接收消息的消息邮箱的指针。
    msg:即将实际发送给任务的消息。消息是一个以指针表示的某种数据类型的变量,在不同的程序中消息的使用也可能不同。不允许传递一个空指针,因为这意味着消息邮箱为空。
    opt:定义消息只发给等待邮箱消息的任务中优先级最高的任务(将opt置为OS_POST_OPT_NONE),或者让所有等待 邮箱消息的任务都得到消息(将opt置为OS_POST_OPT_BROADCAST)。
    返回值:
    err 指向包含错误码的变量指针,返回的错误码可能为下述几种之一:
    OS_NO_ERR 消息成功地放到消息邮箱中。
    OS_MBOX_FULL 消息邮箱已经包含了其他消息,已满。
    OS_ERR_EVENT_TYPE pevent不是指向消息邮箱的指针。
    OS_ERR_PEVENT_NULL pevent是空指针。
    OS_ERR_POST_NULL_PTR 用户试图发出空指针。根据规则,在这里不支持空指针。 

    INT8U OSMboxQuery (OS_EVENT *pevent, OS_MBOX_DATA *p_mbox_data)  查询一个邮箱的状态。
    pevent:指向即将接收消息的消息邮箱的指针。
    pdata:指向OS_MBOX_DATA数据结构的指针,该数据结构包含下述成员。
    void *OSMsg; /*消息邮箱中消息的复制*/

    INT8U OSEventTbl[OS_EVENT_TBL_SIZE]; /*消息邮箱等待队列的复制*/
    INT8U OSEventGrp
    返回值:该函数返回值为下述之一:
    OS_NO_ERR 调用成功
    OS_ERR_EVENT_NULL pevent是空指针
    OS_ERR_EVENT_TYPE pevent不是指向消息邮箱的指针

    邮箱使用之数据存放

    邮箱一般用来传递数据,不管何种类型都可以传递。在传递时,先把数据数据用void *进行类型变化,化为void *这种万用类型,而在接收邮箱的数据时,再还原成本身的数据类型。比如以下的两个例子:

    一是传递指向一个数组的指针。发端采用如下方式:
    OSMboxPost(ComSendMbox, (void *)ComBufRec);         
    其中的ComBufRec就是数组名,也表示指向该数组的指针,将原来的字符型数组名(或指针)变成(void *)类型,而接收端,利用
    send_ptr = (uint8 *)OSMboxPend(ComSendMbox, 0, &err);      
    将(void *)类型还原为指针。

    二是传递一个字符型变量。发端采用如下方式:
    OSMboxPost(KeyMbox, (void *)key_value);             
    其中的key_value就是字符型变量,将原来的字符型变量变成(void *)类型,而接收端,利用
    key = (INT8U)OSMboxPend(KeyMbox, 0, &err);    
    将(void *)类型还原为字符型变量。


    邮箱使用之0值的传递

    在邮箱的传递中,如果把一个0值放入邮箱,经过void *类型变化后,变成了void *0,而判断邮箱中是否有数据正是通过判断邮箱中指向Message的指针是否为0来判断,这样虽然放入了一个0变量,但邮箱中却无法判断这个0值,认为邮箱中还是空。
    这种情况下最好时避免0值的使用,如果非使用不可。建议在0值前加一个辅助变量。这样可以避免邮箱接收数据后认为是空的情况的发生



    展开全文
  • uCOS-II 基础入门教程(一)

    千次阅读 2019-01-01 17:13:52
    最近一起分享下uCOS-II学习教程,包括uCOS-II一些专有名词,任务,优先级等。 Start: uCOS-II概念 μC/OS-II由Micrium公司提供,是一个可移植、可固化的、可裁剪的、占先式多任务实时内核,它适用于多种微处理器...
  • 浅谈uCOS-II的任务(下)

    千次阅读 2018-11-20 22:13:22
    文章目录任务就绪表及任务调度任务的就绪表结构对任务就绪表的操作登记注销最高优先级就绪任务的查找任务的调度任务的创建OSTaskCreate()...OSTaskResume()其他任务管理函数任务优先级别修改任务的删除查询任务信息uCOS...
  • 内核(Kernel) 多任务系统中,内核负责管理各个任务,或者说为每个任务分配CPU时间,并且负责任务之间的通讯。内核提供的基本服务是任务切换。之所以使用实时内核可以大大简化应用系统的设计,是因为实时内核允许...
  • uCOS-II总体介绍

    千次阅读 2016-07-11 19:11:47
    第一篇 UCOS介绍 第一篇 UCOS介绍   这个大家都知道。呵呵。考虑到咱们学习的完整性还是在这里唠叨一下。让大家再熟悉一下。高手们忍耐一下吧! uC/OS II(Micro Control Operation System Two)...
  • uCOS-II源码下载及源码目录结构

    万次阅读 2018-04-19 14:56:12
    其次是就是uCOS-IIuCOS-III。uCOS-II用于商业途径是需要付费的,但是对于学习研究却是免费的。它们同是嵌入式实时操作系统,很多东西都是共通的,熟悉其一,其它都非难题了。   RTOS的学习,我选择的是uCOS-II...
  • FreeRTOS 和uCOS II的简单比较

    万次阅读 2018-06-12 12:26:49
    freeRTOS比uCOS II优胜的地方:1。内核ROM和耗费RAM都比uCOS 小,特别是RAM。 这在单片机里面是稀缺资源,uCOS至少要5K以上, 而freeOS用2~3K也可以跑的很好。2。freeRTOS 可以用协程(Co-routine),减少RAM消耗...
  • uCOS-II在PC上的移植(使用VS2015)

    千次阅读 2015-12-25 11:24:04
    uCOS-II移植到PC项目文件(使用VS2015,包含uCOS-II代码).zip uCOS-II移植到PC源码(uCOS-II版本v2.91).zip 方法1.新建一个空项目。2.添加.c文件。 uCOS-II里有3个:ucos_ii.c、os_cpu_c.c、os_trace.c 自定义1个...
  • 2、像ucos-ii这类收费的操作系统又是怎么收费的呢? 3、现在市场上应用于商业的实时性操作系统和非实时性操作系统主要有哪些呢? 这关乎到我未来要学什么操作系统,希望大虾们能帮我解答,感激不尽~~!!! 4、还有...
  • windows下搭建的ucos开发环境(两种)

    千次阅读 2012-07-16 17:57:02
    第一种 1.将ucos_ii的源代码解压到c盘根目录下形成SOFTWARE文件夹(邵贝贝书内的光盘获取SOFTWARE) 2.安装bc45(borland c++...3.建立一个工作文件夹比如叫c:\oswork\ucos,然后将解压目录C:\SOFTWARE\uCOS-II
  • STM32---ucosii和ucosiii

    千次阅读 2018-11-23 17:11:39
    几个 UCOSII 相关的概念需要大家了解一下。任务优先级,任务堆栈,任务控制块,任务就绪表和任务调度器。 任务优先级,这个概念比较好理解, ucos 中,每个任务都有唯一的一个优先级。优先级是任务的唯一标识。在 ...
  • uCOSII 编译环境搭建

    千次阅读 2009-08-09 13:45:00
    uCOSII 编译环境搭建uCos编译环境搭建真没有想象的那么简单,并不是安装好IDE后直接用就行。查阅了好多网上的指南,并经过一晚上的努力,才弄好。为了便于以后参考,并且也方便像我一样的新手学习uCos,便写了下面...
  • 推荐ucos-II 3本参考书 经典

    千次阅读 2011-10-22 16:40:09
    在这里给大家推荐三本学习ucos的必看书籍 1.(比较难买)嵌入式实时操作系统uc/os-II教程 西安电子科技大学出版 -----这本书对UCOS的源代码分析的非常清楚 比作者原著  在某种程度上要好,这本书对关键的代码...
  • keil4中头文件路径设置的方法汇总

    万次阅读 2015-11-07 11:00:54
    keil4中头文件路径设置的方法汇总
  • ucos_ii定时器详解

    万次阅读 2015-09-19 17:24:23
    UCOSII从V2.83版本以后,加入了软件定时器,这使得UCOSII的功能更加完善,在其上的应用程序开发与移植也更加方便。在实时操作系统中一个好的软件定时器实现要求有较高的精度、较小的处理器开销,且占用较少的存储器...
  • ucos_ii VC++ 6.0开发环境建立

    千次阅读 2013-11-20 13:46:33
    1. 现在文件下建立目录,如下:...ucos_ii/v2.80: platform/win32: app: includes: 2. 建立VC++ 6.0工程 3. 导入代码到VC++ 6.0工程中 4. 修改VC++ 6.0配置 导入环境变量,头文件所在路径:
  • uCOS-II在STM32F407上移植

    千次阅读 2014-12-07 17:38:34
    一、复制uCOS-II源码 1、在工程目录的uCOS-II下建立Ports和Source这两个文件夹 2、 I:\UCOS\Micrium\Software\uCOS-II\Ports\ARM-Cortex-M4\Generic\IAR下的所有文件复制到Ports目录下 3、 I:\UCOS\Micrium\...
1 2 3 4 5 ... 20
收藏数 7,501
精华内容 3,000
关键字:

ucosii