ucos2学习心得

    ucos2是一款非常优秀的实时操作系统,源代码开放,移植性强,目前已经被移植到几十种单片机上。所谓的实时操作系统就是一个操作系统内核,主要提供进程管理、进程通信、内存管理等功能,一般不包括文件系统,TCP/IP 协议,GUI等(如果需要的话ucso2也可以加入这些功能的支持)。下面本人结合个人的学习情况,通过一些实例来帮助读者理解ucos2。本人测试的版本为ucos2.00在51单片机上的移植版,编译器Keil3,在proteus7.5中仿真通过。

     1.多任务

   所谓多任务简单的说就是多个任务并行执行,准确的说是宏观上并行,微观上顺序执行。对于单核的处理器(在嵌入式领域绝大部分处理器都是单核的),在某个片刻只能执行一个任务,而其他任务处于就绪或等待状态,CPU实际是在多个任务之间不停的切换。

关于任务的操作详情见ucos2源码文件os_task.c。

实例程序1:

//可用keil直接仿真

//可用proteus仿真观察串口输出 ,晶振11.0592M,波特9600
#include "../ucos2/includes.h"
#include
void TaskStartyya(void *yydata) reentrant;
void TaskStartyyb(void *yydata) reentrant;
void TaskStartyyc(void *yydata) reentrant;
void com_init(INT16U baud);
OS_STK TaskStartStkyya[MaxStkSize];//注意:我在ASM文件中设置?STACK空间为40H即64。
OS_STK TaskStartStkyyb[MaxStkSize];
OS_STK TaskStartStkyyc[MaxStkSize];

void main(void)
{
    OSInit();
    InitTimer0();  //务必加上
     com_init(9600);
    
    OSTaskCreate(TaskStartyya, (void *)0, &TaskStartStkyya[0],2);
    OSTaskCreate(TaskStartyyb, (void *)0, &TaskStartStkyyb[0],3);
    OSTaskCreate(TaskStartyyc, (void *)0, &TaskStartStkyyc[0],4);
    puts("There are 3 tasks");
    OSStart();
    
}

void TaskStartyya(void *yydata) reentrant
{
 
    yydata=yydata;
    printf("task1 begin\n");
    for(;;){
       OSTimeDly(OS_TICKS_PER_SEC);  //延2s
        printf("  task1 is running\n");
           
    }    
}

void TaskStartyyb(void *yydata) reentrant
{

    yydata=yydata;
       
    printf("task2 begin\n");
    for(;;){
        OSTimeDly(2*OS_TICKS_PER_SEC);  
        puts("  task2 is running");  
    }    
}

void TaskStartyyc(void *yydata) reentrant
{
    yydata=yydata;
    printf("task3 begin\n");
    for(;;){
        OSTimeDly(3*OS_TICKS_PER_SEC);   
       puts("  task3 is running");
        
    }    
}
void com_init(INT16U baud)
{
  #define fosc 11059200
  SCON = 0x50;    //串口工作方式为1,串行允许接受               
  TMOD&=0x0f;
  TMOD|=0x20;     //定时器1工作在方式2    
  if(baud<=9600) TH1=256-fosc/32/12/baud;
  else
   {PCON = 0x80;   //SMOD = 1; 波特率加倍
   TH1=256-fosc/32/12/(baud/2);
    }
    TR1  = 1;            //允许定时器1工作
 // ES   = 1; //开串口中断,必须注释掉该句puts才能输出
  EA   = 1;             //开总中断
  TI=1;//要加上此句,才能用puts输出

}

运行结果如下:

There are 3 tasks
task1 begin
task2 begin
task3 begin
  task1 is running
  task1 is running
  task2 is running
  task1 is running
  task3 is running
  task1 is running
  task2 is running
  task1 is running
  task1 is running
  task2 is running
  task3 is running
  task1 is running
  task1 is running
  task2 is running
  task1 is running

从运行结果可看出,三个函数看起来好像是同时执行。其实每个任务都会有有自己的独立堆栈空间。再来看一个例子

示例程序2:

//可用keil直接仿真
//可用proteus仿真观察串口输出 ,晶振11.0592M,波特9600
#include "../ucos2/includes.h"
#include
void TaskStartyya(void *yydata) reentrant;

void com_init(INT16U baud);
OS_STK TaskStartStkyya1[MaxStkSize];//注意:我在ASM文件中设置?STACK空间为40H即64。
OS_STK TaskStartStkyya2[MaxStkSize];//任务2的
OS_STK TaskStartStkyya3[MaxStkSize];//任务3的

void main(void)
{
    unsigned char a1=1;
     unsigned char b1=2;
     unsigned char c1=3;
    
    OSInit();
    com_init(9600);
    InitTimer0();
    
    OSTaskCreate(TaskStartyya, (void *)&a1, &TaskStartStkyya1[0],1);
    OSTaskCreate(TaskStartyya, (void *)&b1, &TaskStartStkyya2[0],2);
    OSTaskCreate(TaskStartyya, (void *)&c1, &TaskStartStkyya3[0],3);    
    OSStart();    
}

void TaskStartyya(void *yydata) reentrant
{
    unsigned char a=*(unsigned char *)yydata;
    unsigned int x=1;
    printf("Task%c begin\n",a+48);
    for(;;){    
        OSTimeDly(OS_TICKS_PER_SEC);
        x=x*a;
        printf("  task%c:x=%d\n",a+48,x);      
    }    
}
void com_init(INT16U baud)
{
  #define fosc 11059200
  SCON = 0x50;    //串口工作方式为1,串行允许接受               
  TMOD&=0x0f;
  TMOD|=0x20;     //定时器1工作在方式2    
  if(baud<=9600) TH1=256-fosc/32/12/baud;
  else
   {PCON = 0x80;   //SMOD = 1; 波特率加倍
   TH1=256-fosc/32/12/(baud/2);
    }
    TR1  = 1;            //允许定时器1工作
 // ES   = 1; //开串口中断,必须注释掉该句puts才能输出
  EA   = 1;             //开总中断
  TI=1;//要加上此句,才能用puts输出
}



Task1 begin
Task2 begin
Task3 begin
  task1:x=1
  task2:x=2
  task3:x=3
  task1:x=1
  task2:x=4
  task3:x=9
  task1:x=1
  task2:x=8
  task3:x=27
  task1:x=1
  task2:x=16
  task3:x=81

这个程序中三个任务都调用的是一个函数,由于每个任务的堆栈空间是独立的,因此我们看到临时变量x在不同的任务中互不影响。

2 信号量

     信号量是一种很常用的进程间通信机制,在多任务操作系统内核中普遍使用,如linux操作系统。信号量可分为二值信号量和计数式信号量。
每一个信号量都有一个计数值,它表示某种资源的可用数目。二值信号量的值只能是0和1;计数式信号量的取
值范围则由所使用的嵌入式操作系统内核决定。内核根据信号量的值,跟踪那些等待信号量的任务。

 关于信号量的操作详情见ucos2源码文件os_sem.c。
     对信号量的操作一般有初始化、等待和释放三种,下面简要介绍一下这三种操作过程。
① 初始化信号量:信号量初始化时要给信号量赋初值,并清空等待信号量的任务表。
② 等待信号量:需要获取信号量的任务执行等待操作。如果该信号量值大于0,则信号量值减1,
任务得以继续运行;如果信号量值为0,等待信号量的任务就被列入等待该信号量的任务表。
③ 释放信号量:已经获取信号量的任务在使用完某种资源后释放其信号量。如果没有任务等待
该信号量,信号量值仅仅是简单的加1;如果有任务正在等待该信号量,那么就会有一个任务进
入就绪态,信号量的值也就不加1。至于哪个任务进入就绪态,要看内核是如何调度的。

//数值越小,优先级越高
//******************************************************************
//通过信号量轮流两个任务交替执行,可用用keil仿真
//可用proteus仿真观察串口输出 ,晶振11.0592M,波特9600
//由于任务A的优先级比B高,所以只采用1个信号量,当然也可以采用2个信号量
#include "../UCOS2/includes.h"
#include
#include
#define   TASK_STK_SIZE   128 //* 128 word
//* mytest task stack define
OS_STK  xdata   AnPrintfStk[TASK_STK_SIZE];
OS_STK  xdata   BnPrintfStk[TASK_STK_SIZE];
//******************************************************************
//* OS_EVENT
OS_EVENT xdata * AnPrintfWait;     //定义
OS_EVENT xdata * BnPrintfWait;

//******************************************************************
//-------------------------------------------------------------------
void  AnPrintf(void *dataptr);   //* Startup Task          
void  BnPrintf(void *dataptr);   //* 8 Identical Tasks
void  Serial_initialize(void);
//******************************************************************
void main (void)
{                           
    OSInit(); //* Initialize uC/OS-II, The Real-Time Kernel
    AnPrintfWait = OSSemCreate(0);    //初始化信号量的值为0
    BnPrintfWait = OSSemCreate(1);
    
 //创建任务,四个参数依次任务代码的指针、任务的参数的指针、分配给任务的堆栈的栈顶指针、优先级
  //数值越小,优先级越高。此程序中A的优先级高  
    OSTaskCreate( AnPrintf,(void *)0,(void *)&AnPrintfStk[0],0x01);
    //原程序堆栈设置为AnPrintfStk[TASK_STK_SIZE-1]
    OSTaskCreate( BnPrintf,(void *)0,(void *)&BnPrintfStk[0],0x02);  
    InitTimer0();
    Serial_initialize();
    puts("semaphore");
    OSStart();           
}
//由于ucos2的抢占式特点,所以只需采用1个信号量
void  AnPrintf(void *dataptr)
{   
    INT8U err;
    dataptr = dataptr;
    while(1)
    {
       OSSemPend(AnPrintfWait,10,&err); //信号量为零等待,非零-1
        puts("THIS IS TASK 1");
      //OSSemPost(BnPrintfWait);
    }
}

//******************************************************************
void  BnPrintf(void *dataptr)
{
    
    //INT8U err;
    dataptr = dataptr;
    while(1)
    {
     //OSSemPend(BnPrintfWait,10,&err);
     puts("this is task 2");   
     OSSemPost(AnPrintfWait);  //信号量+1
    }
   
}
void Serial_initialize()
{
    SCON  = 0x50;  //* SCON: mode 1, 8-bit UART, enable rcvr */
    TMOD |= 0x20;  //* TMOD: timer 1, mode 2, 8-bit reload */
    TH1   = 0xfd;  //* TH1:  reload value for 9600 baud @ 11.0592MHz */
    TR1   = 1;     //* TR1:  timer 1 run */
    TI    = 1;     //* TI:   set TI to send first char of UART */
    //如果使用库函数的puts或printf,务必加上此句
}

输出结果:

semaphore
this is task 2
THIS IS TASK 1
this is task 2
THIS IS TASK 1
this is task 2
THIS IS TASK 1
this is task 2
THIS IS TASK 1
this is task 2
THIS IS TASK 1
this is task 2
通过信号量可控制两个任务的执行顺序。如果不采用信号的话,两个任务就会发生竞争,由于任务1优先级比任务2高,此时输出结果为:

emaphore
this is task 2
THIS IS TASK 1
THIS IS TASK 1
THIS IS TASK 1
THIS IS TASK 1
THIS IS TASK 1

3 邮箱

邮箱是µC/OS-II中另一种通讯机制,其实就是一个任务或者中断服务子程序向另一个任务发送一个消息。消息的数据类型为空指针,即万能类型,各种数据类型都转化成该类型才能发送,接收后再转化成响应类型。为了在µC/OS-II中使用邮箱,必须将OS_CFG.H中的OS_MBOX_EN常数置为1。 关于邮箱的操作详情见ucos2源码文件os_mbox.c。
创建:调用OSMboxCreate()函数来完成(并且要指定指针的初始值。一般情况下,这个初始值是NULL,但也可以初始化一个邮
箱,使其在最开始就包含一条消息。
发送:OSMboxPost()

阻塞式接收:OSMboxPend()

非阻塞式接收OSMboxAccept()

OSMboxAccept()

一点情况下,邮箱也可当做信号量使用

实例程序3

//可用keil直接仿真
//可用proteus仿真观察串口输出 ,晶振11.0592M,波特9600
#include "../ucos2/includes.h"
#include
void TaskA(void *yydata) reentrant;
void TaskB(void *yydata) reentrant;

OS_STK TaskStartStkA[MaxStkSize];//注意:我在ASM文件中设置?STACK空间为40H即64。
OS_STK TaskStartStkB[MaxStkSize];
OS_EVENT *myMbox;

static INT8U TaskAMsg='A',TaskBMsg='B';

void com_init(INT16U baud);
void main(void)
{
    OSInit();
    InitTimer0();  //务必加上
     com_init(9600);
    
    OSTaskCreate(TaskA, (void *)0, &TaskStartStkA[0],2);
    OSTaskCreate(TaskB, (void *)0, &TaskStartStkB[0],3);
    puts("There are 2 tasks");
    myMbox = OSMboxCreate((void *)TaskAMsg);
    OSStart();
       
}

void TaskA(void xdata *i)reentrant
{
  INT8U myErr;
   i=i;
   printf("TaskA begin\n");
   for(;;)
 {
   // if(OSMboxPend(myMbox,0,&myErr)== (void *)TaskAMsg)
    if((INT8U)OSMboxPend(myMbox,0,&myErr)== TaskAMsg)//该写法与上面等效
     {
      printf(" TaskA receive mail\n");
      OSTimeDly(5);
      OSMboxPost(myMbox,(void *)TaskBMsg);
      }
  }
}

void TaskB(void xdata *i)reentrant
{
  INT8U myErr;
  i=i;
  printf("TaskB begin\n");
  for (;;)
  {
    //if(OSMboxPend(myMbox,0,&myErr) == (void *)TaskBMsg)
    if((INT8U)OSMboxPend(myMbox,0,&myErr) == TaskBMsg)
    {
    printf(" TaskB receive mail\n");
    OSTimeDly(5);
    OSMboxPost(myMbox,(void *)TaskAMsg);
    }
  }
}
void com_init(INT16U baud)
{
  #define fosc 11059200
  SCON = 0x50;    //串口工作方式为1,串行允许接受               
  TMOD&=0x0f;
  TMOD|=0x20;     //定时器1工作在方式2    
  if(baud<=9600) TH1=256-fosc/32/12/baud;
  else
   {PCON = 0x80;   //SMOD = 1; 波特率加倍
   TH1=256-fosc/32/12/(baud/2);
    }
    TR1  = 1;            //允许定时器1工作
 // ES   = 1; //必须注释掉该句puts才能输出
  EA   = 1;             //开总中断
  TI=1;//要加上此句,才能用puts输出
}

输出结果:

There are 2 tasks
TaskA begin
 TaskA receive mail
TaskB begin
 TaskB receive mail
 TaskA receive mail
 TaskB receive mail
 TaskA receive mail
 TaskB receive mail
 TaskA receive mail
 TaskB receive mail
 TaskA receive mail
 TaskB receive mail
 TaskA receive mail
 TaskB receive mail
 TaskA receive mail
 TaskB receive mail
 TaskA receive mail
 TaskB receive mail
 TaskA receive mail

运行结果类似于采用信号量的结果。

4 消息队列

消息队列是µC/OS-II中另一种通讯机制,和邮箱类似,不同点在于,邮箱只能传递一个变量,而消息队列可传递一个数组,可往这个数组里多次发送数据,自然也可以多次读取。

关于队列的操作详情见ucos2源码文件os_q.c。

消息队列创建函数:     
OS_EVENT  *OSQCreate (void **start, INT16U size)
LIFO方式发送消息函数:
 INT8U  OSQPostFront (OS_EVENT *pevent, void *msg)//后进先出
FIFO方式发送消息函数:
 INT8U  OSQPost (OS_EVENT *pevent, void *msg)//先进先出

OSTaskCreate()函数创建TaskStart任务,
在TaskStart任务4次调用ucos任务创建函数OSTaskCreate()创建4个任务:TaskLed,TaskLed1,TaskDisp,TaskKey 。同时创建消息队列
Str_Q =  OSQCreate(&MsgGrp[0],N_MESSAGES);。
然后调用OSTaskSuspend()函数将TaskStart任务挂起,因为在4个任务创建后,TaskStart任务该做的事情已经完毕,挂起任务。然后4个任务在开始执行执行;

TaskLed:     LED0循环闪烁,反转间隔为200ms
TaskDisp:      每隔50ms请求消息队列,并显示得到的消息
TaskKey:     每隔20ms扫描按键值 。

TaskKey进行按键扫描,这里在任务TaskDisp和TaskKey之间有一个任务挂起OSTaskSuspend和恢复OSTaskResume操作,这个操作的目的是为了让按键扫描之后发送消息到消息队列完成之后,TaskDisp任务才开始请求消息邮箱,也就是消息发送完成了才开始请求,这样方便查看队列顺序。


*/
#include "../ucos2/includes.h"
#include
//设置任务堆栈大小
#define u8 unsigned char
sbit LED0=P3^4;
sbit LED1=P3^5;
sbit KEY1=P3^3;
sbit KEY2=P3^2;
#define LED_STK_SIZE     64     //堆栈空间不能太小
#define KEY_STK_SIZE     100
#define LCD_STK_SIZE     100  //64

//设置任务优先级
#define LED_TASK_Prio       4
#define KEY_TASK_Prio       2
#define LCD_TASK_Prio       3

#define N_MESSAGES  10

//任务堆栈
OS_STK  TASK_LED_STK[LED_STK_SIZE];
OS_STK  TASK_KEY_STK[KEY_STK_SIZE];
OS_STK  TASK_LCD_STK[LCD_STK_SIZE];
 

//任务申明
void TaskLed(void *ppdata) reentrant;
void TaskKey(void *ppdata) reentrant;
void TaskDisp(void *ppdata) reentrant;
 
OS_EVENT *Str_Q;
void * MsgGrp[N_MESSAGES];
u8 err;
 
 void com_init(INT16U baud)
{
  #define fosc 11059200
  SCON = 0x50;    //串口工作方式为1,串行允许接受               
  TMOD&=0x0f;
  TMOD|=0x20;     //定时器1工作在方式2    
  if(baud<=9600) TH1=256-fosc/32/12/baud;
  else
   {PCON = 0x80;   //SMOD = 1; 波特率加倍
   TH1=256-fosc/32/12/(baud/2);
    }
    TR1  = 1;            //允许定时器1工作
 // ES   = 1; //必须注释掉该句puts才能输出
  EA   = 1;             //开总中断
  TI=1;//要加上此句,才能用puts输出
}
//按键按下检测
bit ButtonDown(bit state)
{  static bit flag=0;  //标志位
    if(state==0)
    {
      if(flag==0){flag=1;return 1;}
     }
   else flag=0;// 按键弹起flag清零
   return 0;
}
u8 KEY_Scan(void)
{
  if(ButtonDown(KEY1))return 1;
  if(ButtonDown(KEY2))return 2;
  return 0;


}
 
 
 void main(void)
 {
     
    
    OSInit();
    InitTimer0();  //务必加上
     com_init(9600);
        
    OSTaskCreate(TaskLed, (void * )0, (OS_STK *)&TASK_LED_STK[0], LED_TASK_Prio);
      OSTaskCreate(TaskKey, (void * )0, (OS_STK *)&TASK_KEY_STK[0], KEY_TASK_Prio);
      OSTaskCreate(TaskDisp, (void * )0, (OS_STK *)&TASK_LCD_STK[0], LCD_TASK_Prio);
    puts("a example of queue");
    Str_Q = OSQCreate(&MsgGrp[0],N_MESSAGES);
    OSStart();
    //return 0;

 }    
 
//任务1
//控制DS0的亮灭.
void TaskLed(void *ppdata)reentrant
{    
    ppdata=ppdata;
    while(1)
    {    
        LED0=!LED0;
    //    OSTimeDlyHMSM(0,0,0,200);
    OSTimeDly(10);    
    }
}

//任务3
//按键检测  
 void TaskKey(void *ppdata)reentrant
 {
  u8  key=0;
   u8 array[]="12345";
    while(1){
       key=KEY_Scan();

      if(key!=0)
        {    
         putchar(' ');
         if(key==1)
          {    
                 OSQPostFront(Str_Q,array+0); //以LIFO(后进先出)方式发送消息
            OSQPostFront(Str_Q,array+1);
            OSQPostFront(Str_Q,array+2);
            OSQPostFront(Str_Q,array+3);
            OSQPostFront(Str_Q,array+4);
           }
         if(key==2)
            {        
            OSQPost(Str_Q,array+0);     //以FIFO(先进先出)方式发送消息
             OSQPost(Str_Q,array+1);
            OSQPost(Str_Q,array+2);
            OSQPost(Str_Q,array+3);
            OSQPost(Str_Q,array+4);
            
            }    
         OSTaskResume(LCD_TASK_Prio); //任务恢复
      }
     // OSTimeDlyHMSM(0,0,0,20);
     OSTimeDly(5);        
    }
 }


 void TaskDisp(void *ppdata)reentrant
 {     u8 *result,i=0;
    
        puts("UcosII Message Queue Test");
     puts("Press KEY0:  LIFO");
     puts("Press KEY1:  FIFO");
     while(1)
     {    
         if(i==0)    
          {
           OSTaskSuspend(LCD_TASK_Prio);//任务挂起,等待消息发送完毕恢复任务开始接受消息    
           // puts("hello");
           OSTimeDly(5);//任务回复时从此处开始运行    
          
          }
         result= (u8 *)OSQPend(Str_Q,0,&err) ;//请求消息队列
         putchar(*result);
         if(++i==5) i=0;
          // OSTimeDlyHMSM(0,0,0,50);    
        OSTimeDly(1);
     }
 }


现象:

如果为KEY0按下,那么以LIFO(后进先出)方式向消息队列发送消息1-5,发送之后,可以看到串口显示收到的消息顺序为5,4,3,2,1,为什么?因为是后进先出嘛,自然最后发送的5最先收到了.
如果KEY1按下,那么以FIFO(先进先出)方式向消息队列发送消息1-5,发送之后,可以看到串口显示收到的消息顺序为1,2,3,4,5,因为是先进先出嘛,自然最先发送的1最先收到.


你可能感兴趣的:(学习心得)