uCOS-II学习环境的建立可以参考这个帖子:
http://www.armjishu.com/bbs/viewtopic.php?id=1629&tagid=31 &flag=1578
1、 相关背景知识
信号量为操作系统处理临界区问题和进程间同步提供了一种有效的机制。信号量本身不具备数据传输的功能,它只是资源的外部标识,通过该标识可以判断该资源是否可用。信号量在此过程中负责数据操作的互斥、同步等功能。信号量的行为常用P操作和V操作来表示,简称PV操作。任务对资源进行P操作,即意味着申请占有一个资源;任务对资源进行V操作,即意味着释放占有的资源。如果初始资源数为1,那么该资源就是互斥资源,即一次只允许一个任务使用。
2、 uCOS-II中任务之间的通信和同步
在uCOS-II中学习信号量就不得不提uCOS-II中任务之间的通信与同步机制。在uCOS-II中一个任务可以通过事件控制块(ECB)来向另外的任务发信号,所有的信号都被看做为事件。这里,事件可以是信号量、邮箱或者消息队列等。也就是说uCOS-II对信号量、邮箱或者消息队列等事件定义了一个相同的数据结构——ECB。uCOS-II 通过uCOS-II_II.H 中定义的OS_EVENT 数据结构来维护一个事件控制块的所有信息,下面是其详细代码描述:
typedef struct { void *OSEventPtr; /* 指向消息或者消息队列的指针 */ INT8U OSEventTbl[OS_EVENT_TBL_SIZE]; /* 等待任务列表 */ INT16U OSEventCnt; /* 计数器(当事件是信号量时) */ INT8U OSEventType; /* 事件类型 */ INT8U OSEventGrp; /* 等待任务所在的组 */ } OS_EVENT;
其中OSEventType设定具体事件类型,在uCOS-II中可以设定为信号量(OS_EVENT_SEM)、邮箱(OS_EVENT_TYPE_MBOX)或消息队列(OS_EVENT_TYPE_Q)。信号量主要涉及的域为OSEventCnt(信号量值),OSEventGrp,OSEventTbl。
3、uCOS-II中的信号量
uCOS-II 中的信号量由两部分组成:一个是信号量的计数值,它是一个16 位的无符号整数(0 到65,535 之间);另一个是由等待该信号量的任务组成的等待任务表。
在使用一个信号量之前,首先要建立该信号量,对信号量的初始计数值赋值。该初始值为0 到65,535 之间的一个数。如果信号量是用来表示一个或者多个事件的发生,那么该信号量的初始值应设为0。如果信号量是用于对共享资源的访问,那么该信号量的初始值应设为1。最后,如果该信号量是用来表示允许任务访问n 个相同的资源,那么该初始值显然应该是n,并把该信号量作为一个可计数的信号量使用。
uCOS-II 提供了5 个对信号量进行操作的函数。它们是:OSSemCreate(),OSSemPend(),OSSemPost(),OSSemAccept()和OSSemQuery()函数。
OS_EVENT *OSSemCreate (INT16U cnt)
创建一个信号量,创建工作必须在任务启动之前完成。该函数的功能主要是获取一个事件控制块ECB,然后初始化,参数为信号量值。如果创建成功函数则返回一个指向该事件控制块的指针。这里需要注意的是,在uCOS-II中,信号量一旦建立就不能删除了。
void OSSemPend (OS_EVENT *pevent, INT16U timeout, INT8U *err)
等待一个信号量,本函数主要应用于下面三种场合:
1、任务试图获得共享资源的使用权;
2、任务需要与其他任务或中断同步;
3、任务需要等待特定事件发生
该函数的第一个参数是指向等待信号量对应事件控制块的指针;第二个参数是等待的时间,如果为0则表示一直等待;第三个参数用来保存出错信息。在调用OSSemPend()时一定要检查err的值,然后再进行后面的操作。err的值一共有OS_ERR_PEND_ISR、OS_ERR_PEVENT_NULL(没有创建该信号量)、OS_ERR_EVENT_TYPE(类型不匹配)、OS_NO_ERR(成功)和OS_TIMEOUT(超时)5个值。
INT8U OSSemPost (OS_EVENT *pevent)
发送一个信号量,该函数的第一个参数是指向需要发送信号量对应事件控制块的指针。
INT16U OSSemAccept (OS_EVENT *pevent)
无等待的请求一个信号量,如果任务请求的信号量暂时无效,则该任务简单返回。中断服务子程序要请求信号量时必须使用该函数。
INT8U OSSemQuery (OS_EVENT *pevent, OS_SEM_DATA *pdata)
查询一个信号量的当前状态,函数的第一个参数为指向需要查询信号量对应事件控制块的指针;第二个参数为指向用于记录信号量信息的数据结构OS_SEM_DATA的指针。该数据结构是事件控制块的简化版,里面只包含事件控制块中跟信号量相关的域,即只包含OSEventCnt、OSEventGrp和OSEventTbl三项。
3、 uCOS-II信号量实例
该实例主要模拟串口驱动的缓冲区实现,由于串行设备存在外设处理速度和CPU速度不匹配的问题,所以需要缓冲区。在实例中每个串行端口有两个环状队列缓冲区,同时有两个信号量。一个用来指示接收字节(RxSem),另一个用来指示发送字节(TxSem)。每个环状队列包括下面四个要素:
存储数据(INT8U数组);
1)、包含环状缓冲区字节数的计数器;
2)、环状缓冲区中指向将被放置的下一字节的指针;
3)、环状缓冲区中指向被取出的下一字节的指针
两个环形队列缓冲区一个为数据接收缓冲区RxBuf,另一个为数据发送缓冲区TxBuf。
struct SerialBuf{ //环形缓冲区 INT8U *base; INT16U cnt; INT8U *in; INT8U *out; }; struct SerialPort{ struct SerialBuf TxBuf; //数据发送缓冲区 struct SerialBuf RxBuf; //数据接收缓冲区 OS_EVENT *TxSem; OS_EVENT *RxSem; };
对两个缓冲区进行操作的函数主要有四个:SerialGetTxChar()、SerialPutRxChar()、SerialPutChar(INT8U c)和SerialGetChar()。其中函数SerialPutChar(INT8U c)和SerialGetChar()是应用程序接口,SerialPutChar()把数据放到TxBuf中,SerialGetChar()从RxBuf中获取数据。函数SerialPutRxChar()用来将接收的字节放到RxBuf中,如果接收缓冲区已满,则该字节被丢弃。当字节插入到缓冲区后,SerialPutRxChar()发送数据接收信号量,将数据己到的消息传达给所有等待的任务。函数SerialGetTxChar()从TxBuf中接收数据然后发送出去。
INT8U SerialPutChar(INT8U c) { INT8U err; OSSemPend(com.TxSem,0,&err); if(err == OS_NO_ERR) { OS_ENTER_CRITICAL(); *(com.TxBuf.in)++ = c; com.TxBuf.cnt++; if(com.TxBuf.in >= com.TxBuf.base + BUFSIZE) com.TxBuf.in = com.TxBuf.base; OS_EXIT_CRITICAL(); return OK; } else return FAIL; } INT8U SerialGetChar() { INT8U c; INT8U err; c = 0; OSSemPend(com.RxSem,0,&err); if(err == OS_NO_ERR) { OS_ENTER_CRITICAL(); c = *(com.RxBuf.out)++; com.RxBuf.cnt--; if(com.RxBuf.out >= com.RxBuf.base + BUFSIZE) com.RxBuf.out = com.RxBuf.base; OS_EXIT_CRITICAL(); } return c; } INT8U SerialGetTxChar() { INT8U c; c = 0; OSSemQuery(com.TxSem,&SemData); if(SemData.OSCnt < BUFSIZE) { c = *(com.TxBuf.out)++; com.TxBuf.cnt++; if(com.TxBuf.out >= com.TxBuf.base + BUFSIZE) com.TxBuf.out = com.TxBuf.base; OSSemPost(com.TxSem); } return c; } INT8U SerialPutRxChar(INT8U c) { OSSemQuery(com.RxSem,&SemData); if(SemData.OSCnt < BUFSIZE) { *(com.RxBuf.in)++ = c; com.RxBuf.cnt--; if(com.RxBuf.in >= com.RxBuf.base + BUFSIZE) com.RxBuf.in = com.RxBuf.base; OSSemPost(com.RxSem); return OK; } else { return FAIL; } }
下面是完整代码:
点击此处下载serial.h (文件大小:0K)
点击此处下载serical.c (文件大小:1K)
4、 测试实例
测试程序创建了四个任务,TaskStart、TaskCom、TaskGet和TaskPut。任务TaskSatart设置中断向量、开启时钟节拍、设置显示界面和创建TaskCom、TaskGet和TaskPut三个任务。这里需要注意的是用户必须在多任务系统启动以后再开启时钟节拍器,也就是在调用OSStart()之后。换句话说,在调用OSStart()后做的第一件事就是初始化定时器中断。任务TaskCom模拟数据的传输,就是把数据从TxBuf中取出然后放入RxBuf中。任务TaskGet从RxBuf中读取数据然后显示到屏幕上。任务TaskPut响应键盘中断,从键盘接收数据然后放到TxBuf中。为了对比,任务TaskPut除了把数据放到TxBuf中同时还将其显示到屏幕上。
下面是测试结果:
test.jpg
下面是测试代码:
点击此处下载test.c (文件大小:6K)