1.UCOSII原理
UCOSII 是一个可以基于ROM 运行的、可裁减的、抢占式、实时多任务内核,具有高度可
移植性,特别适合于微处理器和控制器,是和很多商业操作系统性能相当的实时操作系统
(RTOS)。
UCOSII 是专门为计算机的嵌入式应用设计的, 绝大部分代码是用C 语言编写的。CPU 硬
件相关部分是用汇编语言编写的、总量约200 行的汇编语言部分被压缩到最低限度,为的是便
于移植到任何一种其它的CPU 上。用户只要有标准的ANSI 的C 交叉编译器,有汇编器、连
接器等软件工具,就可以将UCOSII 嵌人到开发的产品中。UCOSII 具有执行效率高、占用空间
小、实时性能优良和可扩展性强等特点, 最小内核可编译至 2KB 。UCOSII 已经移植到了几
乎所有知名的CPU 上。
UCOSII体系结构如图所示
UCOSII在版本2.80后,支持任务数提高到255个。
任务,其实就是一个死循环函数,该函数实现一定的功能,一个工程可以有很多这样的任务(最多255个),UCOSII对这些任务进行调度管理,让这些任务可以并发工作(并发的意思是:各任务轮流占用CPU,而不是同时占用,任何时候还是只有1个任务能够占用CPU),这就是UCOSII最基本的功能。Ucos任务的一般格式为:
void MyTask(void *pdata){
//任务准备工作
while(1){
//任务MyTask实际代码
...
OSTimeDlyHMSM();//调用任务延时函数,释放cpu控制权
}
}
下面有几个概念需要掌握
1.任务优先级
每个任务都有一个唯一的优先级,换言之,每个任务的优先级都不一样。在UCOSII中,优先级高的任务比优先级低的任务有CPU优先使用权,只有高优先级的任务让出使用权时(用的多的是函数OSTimeDlyHMSM();//调用任务延时函数,释放cpu控制权),低优先级的任务才可以使用CPU。
2.任务堆栈
任务堆栈是存储器中的一块连续区域,每个任务都有自己的堆栈,主要是为了满足任务切换和响应中断时保存CPU寄存器中的内容。
3.任务控制块
任务控制块OS_TCB,用来记录任务堆栈指针,任务当前状态以及任务优先级等任务属性。UCOSII的任何任务都是通过任务控制块(TCB)来控制,一旦任务创建,任务控制块OS_TCB就会被赋值。每个任务管理块有3个最重要的参数:任务函数指针;任务堆栈指针;任务优先级。任务控制块就是任务在系统里面的身份证(UCOSII通过优先级识别任务)。
4 .任务就绪表
任务就绪表,用来记录系统中所有处于就绪状态的任务。它是一个位图,系统中每个任务都在这个位图中占据一个进制位,该位置的状态(1或者0)就表示任务是否处于就绪状态。
5.任务调度
任务调度的作用一是在任务就绪表中查找优先级最高的就绪任务,二是实现任务的切换。比如说,当一个任务释放cpu控制权后,进行一次任务调度,这个时候任务调度器首先要去任务就绪表查询优先级最高的就绪任务,查到之后,进行一次任务切换,转而去执行下一个任务。
下面是几个关于任务的函数:
1)任务建立函数
一般使用OSTaskCreate
定义为
INT8U OSTaskCreate (void (*task)(void *p_arg),
void *p_arg,
OS_STK *ptos,
INT8U prio)
解释如下
该函数包括4个参数:task:是指向任务代码的指针;p_arg:是任务开始执行时,传递给任务参数的指针;ptos:是分配给任务的堆栈的栈顶指针;prio是分配给任务的优先级。
/*
*********************************************************************************************************
* CREATE A TASK
*
* Description: This function is used to have uC/OS-II manage the execution of a task. Tasks can either
* be created prior to the start of multitasking or by a running task. A task cannot be
* created by an ISR.
*
* Arguments : task is a pointer to the task's code
*
* p_arg is a pointer to an optional data area which can be used to pass parameters to
* the task when the task first executes. Where the task is concerned it thinks
* it was invoked and passed the argument 'p_arg' as follows:
*
* void Task (void *p_arg)
* {
* for (;;) {
* Task code;
* }
* }
*
* ptos is a pointer to the task's top of stack. If the configuration constant
* OS_STK_GROWTH is set to 1, the stack is assumed to grow downward (i.e. from high
* memory to low memory). 'pstk' will thus point to the highest (valid) memory
* location of the stack. If OS_STK_GROWTH is set to 0, 'pstk' will point to the
* lowest memory location of the stack and the stack will grow with increasing
* memory locations.
*
* prio is the task's priority. A unique priority MUST be assigned to each task and the
* lower the number, the higher the priority.
*
* Returns : OS_ERR_NONE if the function was successful.
* OS_PRIO_EXIT if the task priority already exist
* (each task MUST have a unique priority).
* OS_ERR_PRIO_INVALID if the priority you specify is higher that the maximum allowed
* (i.e. >= OS_LOWEST_PRIO)
* OS_ERR_TASK_CREATE_ISR if you tried to create a task from an ISR.
*********************************************************************************************************
*/
2)任务删除函数
任务删除,是把任务置于睡眠状态。一般使用OSTaskDel
原型为
INT8U OSTaskDel(INT8U prio);
1
其中参数prio就是要删除任务的优先级
特别注意:任务不能随便删除,必须在确保被删除任务的资源被释放的前提下才能删除!
3)请求任务删除函数
向被删除任务发送删除请求,实现任务释放自身占用的资源并删除。函数为OSTaskDelReq,原型如下
INT8U OSTaskDelReq(INT8U prio);
1
其中参数prio就是要删除任务的优先级
4)优先级更改函数
函数为OSTaskChangePrio,其原型如下
INT8U OSTaskChangePrio(INT8U oldprio,INT8U newprio);
1
其中参数oldprio是要任务的之前的优先级,newprio是更改之后的优先级。
5)任务挂起函数
任务挂起函数把任务的就绪标志删除,做好任务挂起记录,被挂起的任务,在解挂之后可以继续运行。任务观其函数为OsTaskSuspend
定义为
INT8U OSTaskSuspend (INT8U prio)
{
BOOLEAN self;
OS_TCB *ptcb;
INT8U y;
#if OS_CRITICAL_METHOD == 3u /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr = 0u;
#endif
#if OS_ARG_CHK_EN > 0u
if (prio == OS_TASK_IDLE_PRIO) { /* Not allowed to suspend idle task */
return (OS_ERR_TASK_SUSPEND_IDLE);
}
if (prio >= OS_LOWEST_PRIO) { /* Task priority valid ? */
if (prio != OS_PRIO_SELF) {
return (OS_ERR_PRIO_INVALID);
}
}
#endif
OS_ENTER_CRITICAL();
if (prio == OS_PRIO_SELF) { /* See if suspend SELF */
prio = OSTCBCur->OSTCBPrio;
self = OS_TRUE;
} else if (prio == OSTCBCur->OSTCBPrio) { /* See if suspending self */
self = OS_TRUE;
} else {
self = OS_FALSE; /* No suspending another task */
}
ptcb = OSTCBPrioTbl[prio];
if (ptcb == (OS_TCB *)0) { /* Task to suspend must exist */
OS_EXIT_CRITICAL();
return (OS_ERR_TASK_SUSPEND_PRIO);
}
if (ptcb == OS_TCB_RESERVED) { /* See if assigned to Mutex */
OS_EXIT_CRITICAL();
return (OS_ERR_TASK_NOT_EXIST);
}
y = ptcb->OSTCBY;
OSRdyTbl[y] &= (OS_PRIO)~ptcb->OSTCBBitX; /* Make task not ready */
if (OSRdyTbl[y] == 0u) {
OSRdyGrp &= (OS_PRIO)~ptcb->OSTCBBitY;
}
ptcb->OSTCBStat |= OS_STAT_SUSPEND; /* Status of task is 'SUSPENDED' */
OS_EXIT_CRITICAL();
if (self == OS_TRUE) { /* Context switch only if SELF */
OS_Sched(); /* Find new highest priority task */
}
return (OS_ERR_NONE);
}
解释如下
/*
*********************************************************************************************************
* SUSPEND A TASK
*
* Description: This function is called to suspend a task. The task can be the calling task if the
* priority passed to OSTaskSuspend() is the priority of the calling task or OS_PRIO_SELF.
*
* Arguments : prio is the priority of the task to suspend. If you specify OS_PRIO_SELF, the
* calling task will suspend itself and rescheduling will occur.
*
* Returns : OS_ERR_NONE if the requested task is suspended
* OS_ERR_TASK_SUSPEND_IDLE if you attempted to suspend the idle task which is not allowed.
* OS_ERR_PRIO_INVALID if the priority you specify is higher that the maximum allowed
* (i.e. >= OS_LOWEST_PRIO) or, you have not specified OS_PRIO_SELF.
* OS_ERR_TASK_SUSPEND_PRIO if the task to suspend does not exist
* OS_ERR_TASK_NOT_EXITS if the task is assigned to a Mutex PIP
*
* Note : You should use this function with great care. If you suspend a task that is waiting for
* an event (i.e. a message, a semaphore, a queue ...) you will prevent this task from
* running when the event arrives.
*********************************************************************************************************
*/
6)任务恢复函数
有任务挂起函数,就有任务恢复函数,通过该函数将被挂起的任务恢复,让调度器能够重新调度该函数。函数为OSTaskResume
原型为
INT8U OSTaskResume(INT8U prio);
1
UCOSII信号量和邮箱
任务间的同步依赖于任务间的通信。在UCOSII 中,是使用信号量、邮箱(消息邮箱)和消息队列这些被称作事件的中间环节来实现任务之间的通信。使用事件控制块来描述具体的信息,事件控制块结构体
typedef struct{
INT8U OSEventType;//事件的类型
INT16U OSEventCnt;//信号量计数器
void *OSEventPtr; //消息或消息队列的指针
INT8U OSEventGrp; //等待事件的任务组
INT8U OSEventTbl[OS_EVENT_TBL_SIZE];//任务等待表
#if OS_EVENT_NAME_EN > 0u
INT8U *OSEventName; //事件名
#endif
}OS_EVENT;
信号量
信号量是一类事件,它是为了给共享资源设立一个标志,该标志表示该共享资源的占用情况。这样,当一个任务在访问共享资源之前,就可以先对这个标志进行查询,从而在了解资源被占用的情况之后,再来决定自己的行为。
信号量可以分为两种:一种是二值型信号量(也称为互斥信号量),另外一种是N 值信号量。
邮箱
在多任务操作系统中,常常需要在任务与任务之间通过传递“消息”的方式来进行通信。为此需要在内存中创建一个存储空间作为该消息的缓冲区,称为消息缓冲区,这样在任务间传递数据(消息)的最简单办法就是传递消息缓冲区的指针。把用来传递消息缓冲区指针的数据结构叫做邮箱(消息邮箱)。
在UCOSII中,通过事件控制块的OSEventPrt来传递消息缓冲区指针,同时使事件控制块的成员OSEventType为常数OS_EVENT_TYPE_MBOX,则该事件控制块就叫做消息邮箱。
下面看一下关于信号量的函数
1)创建信号量
函数为OSSemCreate
OS_EVENT *OSSemCreate (INT16U cnt);
1
该函数返回值为已创建的信号量的指针,而参数cnt则是信号量计数器(OSEventCnt)的初始值。
2)请求信号量函数
函数为OSSemPend
void OSSemPend ( OS_EVENT *pevent, INT16U timeout, INT8U *err);
1
其中,参数pevent是被请求信号量的指针,timeout为等待时限,err为错误信息。
为防止任务因得不到信号量而处于长期的等待状态,函数OSSemPend允许用参数timeout设置一个等待时间的限制,当任务等待的时间超过timeout时可以结束等待状态而 进入就绪状态。如果参数timeout被设置为0,则表明任务的等待时间为无限长。
3)发送信号量函数
任务获得信号量,并在访问共享资源结束以后要释放信号量,释放信号量也叫做发送信号量,发送信号通过OSSemPost函数实现 。OSSemPost 函数在对信号量的计数器操作之前,首先要检查是否还有等待该信号量的任务。如果没有,就把信号量计数器OSEventCnt加一;如果有,则调用调度器OS_Sched( )去运行等待任务中优先级别最高的任务。函数OSSemPost的原型为:
INT8U OSSemPost(OS_EVENT *pevent);
1
其中,pevent为信号量指针,该函数在调用成功后,返回值为OS_ON_ERR,否则会根据具体错误返回OS_ERR_EVENT_TYPE、OS_SEM_OVF。
4)删除信号量
应用程序如果不需要某个信号量,可以调用函数OSSemDel来删除该信号量,该函数的原型为:
OS_EVENT *OSSemDel (OS_EVENT *pevent,INT8U opt, INT8U *err);
1
其中,pevent为要删除的信号量指针,opt为删除条件选项,err为错误信息。
下面是关于邮箱的函数
1)创建邮箱
创建邮箱通过函数OSMboxCreate实现,该函数原型为:
OS_EVENT *OSMboxCreate (void *msg);
1
函数中的参数msg为消息的指针,函数的返回值为消息邮箱的指针。
调用函数OSMboxCreate需先定义msg的初始值。在一般的情况下,这个初始值为NULL;但也可以事先定义一个邮箱,然后把这个邮箱的指针作为参数传递到函数OSMboxCreate 中,使之一开始就指向一个邮箱。
2) 向邮箱发送消息函数
任务可以通过调用函数OSMboxPost向消息邮箱发送消息,这个函数的原型为:
INT8U OSMboxPost (OS_EVENT *pevent,void *msg);
1
其中pevent为消息邮箱的指针,msg为消息指针。
3) 请求邮箱函数
当一个任务请求邮箱时需要调用函数OSMboxPend,这个函数的主要作用就是查看邮箱指针OSEventPtr是否为NULL,如果不是NULL就把邮箱中的消息指针返回给调用函数的任务,同时用OS_NO_ERR通过函数的参数err通知任务获取消息成功;如果邮箱指针OSEventPtr是NULL,则使任务进入等待状态,并引发一次任务调度。
函数OSMboxPend的原型为:
void *OSMboxPend (OS_EVENT *pevent, INT16U timeout, INT8U *err);
1
其中pevent为请求邮箱指针,timeout为等待时限,err为错误信息。
4) 查询邮箱状态函数
任务可以通过调用函数OSMboxQuery查询邮箱的当前状态。该函数原型为:
INT8U OSMboxQuery(OS_EVENT *pevent,OS_MBOX_DATA *pdata);
1
其中pevent为消息邮箱指针,pdata为存放邮箱信息的结构。
5) 删除邮箱函数
在邮箱不再使用的时候,我们可以通过调用函数OSMboxDel来删除一个邮箱,该函数原型为:
OS_EVENT *OSMboxDel(OS_EVENT *pevent,INT8U opt,INT8U *err);
1
其中pevent为消息邮箱指针,opt为删除选项,err为错误信息。
2.UCOSII实验代码
下面是一个STM32mini板实例:
主要功能:程序初始时,LED0灯闪烁,表示任务1在运行;串口显示表示任务2在运行。
通过信号量和邮箱,实现KEY0控制LED0任务(任务1)的挂起,KEY1实现串口显示任务(任务2)的删除,并且实现LED1灯的亮灭;WK_UP实现LED0任务(任务1)的恢复。
实验结果:只有窗口显示部分
main.c文件
#include "led.h"
#include "delay.h"
#include "sys.h"
#include "usart.h"
#include "includes.h"
#include "key.h"
#include "usart.h"
//START 任务
//设置任务优先级
#define START_TASK_PRIO 10 ///开始任务的优先级为最低
//设置任务堆栈大小
#define START_STK_SIZE 128
//任务任务堆栈
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);
//key传递函数
//设置任务优先级
#define KEY_TASK_PRIO 5
//设置任务堆栈大小
#define KEY_STK_SIZE 64
//任务堆栈
OS_STK KEY_TASK_STK[KEY_STK_SIZE];
//任务函数
void key_task(void *pdata);
//按键扫描任务
//设置任务优先级
#define SCAN_TASK_PRIO 4
//设置任务堆栈大小
#define SCAN_STK_SIZE 64
//任务堆栈
OS_STK SCAN_TASK_STK[SCAN_STK_SIZE];
//任务函数
void scan_task(void *pdata);
//串口显示任务
#define FLOAT_TASK_PRIO 8
//设置任务堆栈大小
#define FLOAT_STK_SIZE 128
//任务堆栈
//如果任务中使用printf来打印浮点数据的话一点要8字节对齐
__align(8) OS_STK FLOAT_TASK_STK[FLOAT_STK_SIZE];
//任务函数
void float_task(void *pdata);
OS_EVENT * msg_key; //按键邮箱时间块指针
OS_EVENT * sem_led0; //LED0信号量指针
OS_EVENT * sem_led1; //LED1信号量指针
OS_EVENT * sem_print;//WK_UP信号量指针
int main(void)
{
delay_init(); //延时初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //中断分组配置
uart_init(115200); //串口波特率设置
LED_Init(); //LED初始化
KEY_Init();
OSInit(); //UCOS初始化
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;
msg_key=OSMboxCreate((void *)0);//创建消息邮箱
sem_led0=OSSemCreate(0);
sem_led1=OSSemCreate(0);//创建信号量
sem_print=OSSemCreate(0);
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);
OSTaskCreate(key_task,(void *)0,(OS_STK*)&KEY_TASK_STK[KEY_STK_SIZE-1],KEY_TASK_PRIO);
OSTaskCreate(scan_task,(void *)0,(OS_STK*)&SCAN_TASK_STK[SCAN_STK_SIZE-1],SCAN_TASK_PRIO);
OSTaskCreate(float_task,(void *)0,(OS_STK*)&FLOAT_TASK_STK[FLOAT_STK_SIZE],FLOAT_TASK_PRIO);
OSTaskSuspend(START_TASK_PRIO); //挂起起始任务.
OS_EXIT_CRITICAL(); //退出临界区(可以被中断打断)
}
//LED0任务
void led0_task(void *pdata)
{
//u8 err;
while(1)
{
// OSSemPend(sem_led0,0,&err);
LED0=0;
delay_ms(500);
LED0=1;
delay_ms(500);
};
}
//LED1任务
void led1_task(void *pdata)
{
u8 err;
while(1)
{
OSSemPend(sem_led1,0,&err);
LED1=0;
delay_ms(500);
LED1=1;
delay_ms(500);
}
}
//按键扫描任务
void key_task(void *pdata)
{
int key=0;
u8 err;
while(1)
{
key=(int)OSMboxPend(msg_key,10,&err);
switch(key)
{
case KEY0_PRES://发送信号量0
OSSemPost(sem_led0);
OSTaskSuspend(LED0_TASK_PRIO);//led0任务挂起
break;
case KEY1_PRES://发送信号量1
OSSemPost(sem_led1);
OSTaskDel(FLOAT_TASK_PRIO);//删除串口显示任务
break;
case WKUP_PRES:
//OSSemPost(sem_print);
// OSSemPost(sem_led1);
OSTaskResume(LED0_TASK_PRIO);//led1任务挂起后恢复
break;
}
}
}
//串口显示任务
void float_task(void *pdata)
{
//u8 err;
OS_CPU_SR cpu_sr=0;
while(1)
{
// OSSemPend(sem_print,0,&err);
OS_ENTER_CRITICAL(); //进入临界区(关闭中断)
printf("串口显示程序正在运行:rnn"); //串口打印结果
printf("这是任务2:rnn"); //串口打印结果
OS_EXIT_CRITICAL(); //退出临界区(开中断)
delay_ms(500);
}
}
//按键扫描任务
void scan_task(void *pdata)
{
u8 key;
while(1)
{
key=KEY_Scan(0);
if(key)OSMboxPost(msg_key,(void*)key);//发送消息
delay_ms(10);
}
}
key.c文件
#include "key.h"
#include "delay.h"
//按键初始化函数
//PA0.15和PC5 设置成输入
void KEY_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOC,ENABLE);//使能PORTA,PORTC时钟
GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);//关闭jtag,使能SWD,可以用SWD模式调试
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15;//PA15
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //设置成上拉输入
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA15
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;//PC5
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //设置成上拉输入
GPIO_Init(GPIOC, &GPIO_InitStructure);//初始化GPIOC5
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;//PA0
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //PA0设置成输入,默认下拉
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.0
}
//按键处理函数
//返回按键值
//mode:0,不支持连续按;1,支持连续按;
//返回值:
//0,没有任何按键按下
//KEY0_PRES,KEY0按下
//KEY1_PRES,KEY1按下
//WKUP_PRES,WK_UP按下
//注意此函数有响应优先级,KEY0>KEY1>WK_UP!!
u8 KEY_Scan(u8 mode)
{
static u8 key_up=1;//按键按松开标志
if(mode)key_up=1; //支持连按
if(key_up&&(KEY0==0||KEY1==0||WK_UP==1))
{
delay_ms(10);//去抖动
key_up=0;
if(KEY0==0)return KEY0_PRES;
else if(KEY1==0)return KEY1_PRES;
else if(WK_UP==1)return WKUP_PRES;
}else if(KEY0==1&&KEY1==1&&WK_UP==0)key_up=1;
return 0;// 无按键按下
}
led.c文件
#include "led.h"
//初始化PA8和PD2为输出口.并使能这两个口的时钟
//LED IO初始化
void LED_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOD, ENABLE); //使能PA,PD端口时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; //LED0-->PA.8 端口配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度为50MHz
GPIO_Init(GPIOA, &GPIO_InitStructure); //根据设定参数初始化GPIOA.8
GPIO_SetBits(GPIOA,GPIO_Pin_8); //PA.8 输出高
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; //LED1-->PD.2 端口配置, 推挽输出
GPIO_Init(GPIOD, &GPIO_InitStructure); //推挽输出 ,IO口速度为50MHz
GPIO_SetBits(GPIOD,GPIO_Pin_2); //PD.2 输出高
}
工程目录
1.UCOSII原理
UCOSII 是一个可以基于ROM 运行的、可裁减的、抢占式、实时多任务内核,具有高度可
移植性,特别适合于微处理器和控制器,是和很多商业操作系统性能相当的实时操作系统
(RTOS)。
UCOSII 是专门为计算机的嵌入式应用设计的, 绝大部分代码是用C 语言编写的。CPU 硬
件相关部分是用汇编语言编写的、总量约200 行的汇编语言部分被压缩到最低限度,为的是便
于移植到任何一种其它的CPU 上。用户只要有标准的ANSI 的C 交叉编译器,有汇编器、连
接器等软件工具,就可以将UCOSII 嵌人到开发的产品中。UCOSII 具有执行效率高、占用空间
小、实时性能优良和可扩展性强等特点, 最小内核可编译至 2KB 。UCOSII 已经移植到了几
乎所有知名的CPU 上。
UCOSII体系结构如图所示
UCOSII在版本2.80后,支持任务数提高到255个。
任务,其实就是一个死循环函数,该函数实现一定的功能,一个工程可以有很多这样的任务(最多255个),UCOSII对这些任务进行调度管理,让这些任务可以并发工作(并发的意思是:各任务轮流占用CPU,而不是同时占用,任何时候还是只有1个任务能够占用CPU),这就是UCOSII最基本的功能。Ucos任务的一般格式为:
void MyTask(void *pdata){
//任务准备工作
while(1){
//任务MyTask实际代码
...
OSTimeDlyHMSM();//调用任务延时函数,释放cpu控制权
}
}
下面有几个概念需要掌握
1.任务优先级
每个任务都有一个唯一的优先级,换言之,每个任务的优先级都不一样。在UCOSII中,优先级高的任务比优先级低的任务有CPU优先使用权,只有高优先级的任务让出使用权时(用的多的是函数OSTimeDlyHMSM();//调用任务延时函数,释放cpu控制权),低优先级的任务才可以使用CPU。
2.任务堆栈
任务堆栈是存储器中的一块连续区域,每个任务都有自己的堆栈,主要是为了满足任务切换和响应中断时保存CPU寄存器中的内容。
3.任务控制块
任务控制块OS_TCB,用来记录任务堆栈指针,任务当前状态以及任务优先级等任务属性。UCOSII的任何任务都是通过任务控制块(TCB)来控制,一旦任务创建,任务控制块OS_TCB就会被赋值。每个任务管理块有3个最重要的参数:任务函数指针;任务堆栈指针;任务优先级。任务控制块就是任务在系统里面的身份证(UCOSII通过优先级识别任务)。
4 .任务就绪表
任务就绪表,用来记录系统中所有处于就绪状态的任务。它是一个位图,系统中每个任务都在这个位图中占据一个进制位,该位置的状态(1或者0)就表示任务是否处于就绪状态。
5.任务调度
任务调度的作用一是在任务就绪表中查找优先级最高的就绪任务,二是实现任务的切换。比如说,当一个任务释放cpu控制权后,进行一次任务调度,这个时候任务调度器首先要去任务就绪表查询优先级最高的就绪任务,查到之后,进行一次任务切换,转而去执行下一个任务。
下面是几个关于任务的函数:
1)任务建立函数
一般使用OSTaskCreate
定义为
INT8U OSTaskCreate (void (*task)(void *p_arg),
void *p_arg,
OS_STK *ptos,
INT8U prio)
解释如下
该函数包括4个参数:task:是指向任务代码的指针;p_arg:是任务开始执行时,传递给任务参数的指针;ptos:是分配给任务的堆栈的栈顶指针;prio是分配给任务的优先级。
/*
*********************************************************************************************************
* CREATE A TASK
*
* Description: This function is used to have uC/OS-II manage the execution of a task. Tasks can either
* be created prior to the start of multitasking or by a running task. A task cannot be
* created by an ISR.
*
* Arguments : task is a pointer to the task's code
*
* p_arg is a pointer to an optional data area which can be used to pass parameters to
* the task when the task first executes. Where the task is concerned it thinks
* it was invoked and passed the argument 'p_arg' as follows:
*
* void Task (void *p_arg)
* {
* for (;;) {
* Task code;
* }
* }
*
* ptos is a pointer to the task's top of stack. If the configuration constant
* OS_STK_GROWTH is set to 1, the stack is assumed to grow downward (i.e. from high
* memory to low memory). 'pstk' will thus point to the highest (valid) memory
* location of the stack. If OS_STK_GROWTH is set to 0, 'pstk' will point to the
* lowest memory location of the stack and the stack will grow with increasing
* memory locations.
*
* prio is the task's priority. A unique priority MUST be assigned to each task and the
* lower the number, the higher the priority.
*
* Returns : OS_ERR_NONE if the function was successful.
* OS_PRIO_EXIT if the task priority already exist
* (each task MUST have a unique priority).
* OS_ERR_PRIO_INVALID if the priority you specify is higher that the maximum allowed
* (i.e. >= OS_LOWEST_PRIO)
* OS_ERR_TASK_CREATE_ISR if you tried to create a task from an ISR.
*********************************************************************************************************
*/
2)任务删除函数
任务删除,是把任务置于睡眠状态。一般使用OSTaskDel
原型为
INT8U OSTaskDel(INT8U prio);
1
其中参数prio就是要删除任务的优先级
特别注意:任务不能随便删除,必须在确保被删除任务的资源被释放的前提下才能删除!
3)请求任务删除函数
向被删除任务发送删除请求,实现任务释放自身占用的资源并删除。函数为OSTaskDelReq,原型如下
INT8U OSTaskDelReq(INT8U prio);
1
其中参数prio就是要删除任务的优先级
4)优先级更改函数
函数为OSTaskChangePrio,其原型如下
INT8U OSTaskChangePrio(INT8U oldprio,INT8U newprio);
1
其中参数oldprio是要任务的之前的优先级,newprio是更改之后的优先级。
5)任务挂起函数
任务挂起函数把任务的就绪标志删除,做好任务挂起记录,被挂起的任务,在解挂之后可以继续运行。任务观其函数为OsTaskSuspend
定义为
INT8U OSTaskSuspend (INT8U prio)
{
BOOLEAN self;
OS_TCB *ptcb;
INT8U y;
#if OS_CRITICAL_METHOD == 3u /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr = 0u;
#endif
#if OS_ARG_CHK_EN > 0u
if (prio == OS_TASK_IDLE_PRIO) { /* Not allowed to suspend idle task */
return (OS_ERR_TASK_SUSPEND_IDLE);
}
if (prio >= OS_LOWEST_PRIO) { /* Task priority valid ? */
if (prio != OS_PRIO_SELF) {
return (OS_ERR_PRIO_INVALID);
}
}
#endif
OS_ENTER_CRITICAL();
if (prio == OS_PRIO_SELF) { /* See if suspend SELF */
prio = OSTCBCur->OSTCBPrio;
self = OS_TRUE;
} else if (prio == OSTCBCur->OSTCBPrio) { /* See if suspending self */
self = OS_TRUE;
} else {
self = OS_FALSE; /* No suspending another task */
}
ptcb = OSTCBPrioTbl[prio];
if (ptcb == (OS_TCB *)0) { /* Task to suspend must exist */
OS_EXIT_CRITICAL();
return (OS_ERR_TASK_SUSPEND_PRIO);
}
if (ptcb == OS_TCB_RESERVED) { /* See if assigned to Mutex */
OS_EXIT_CRITICAL();
return (OS_ERR_TASK_NOT_EXIST);
}
y = ptcb->OSTCBY;
OSRdyTbl[y] &= (OS_PRIO)~ptcb->OSTCBBitX; /* Make task not ready */
if (OSRdyTbl[y] == 0u) {
OSRdyGrp &= (OS_PRIO)~ptcb->OSTCBBitY;
}
ptcb->OSTCBStat |= OS_STAT_SUSPEND; /* Status of task is 'SUSPENDED' */
OS_EXIT_CRITICAL();
if (self == OS_TRUE) { /* Context switch only if SELF */
OS_Sched(); /* Find new highest priority task */
}
return (OS_ERR_NONE);
}
解释如下
/*
*********************************************************************************************************
* SUSPEND A TASK
*
* Description: This function is called to suspend a task. The task can be the calling task if the
* priority passed to OSTaskSuspend() is the priority of the calling task or OS_PRIO_SELF.
*
* Arguments : prio is the priority of the task to suspend. If you specify OS_PRIO_SELF, the
* calling task will suspend itself and rescheduling will occur.
*
* Returns : OS_ERR_NONE if the requested task is suspended
* OS_ERR_TASK_SUSPEND_IDLE if you attempted to suspend the idle task which is not allowed.
* OS_ERR_PRIO_INVALID if the priority you specify is higher that the maximum allowed
* (i.e. >= OS_LOWEST_PRIO) or, you have not specified OS_PRIO_SELF.
* OS_ERR_TASK_SUSPEND_PRIO if the task to suspend does not exist
* OS_ERR_TASK_NOT_EXITS if the task is assigned to a Mutex PIP
*
* Note : You should use this function with great care. If you suspend a task that is waiting for
* an event (i.e. a message, a semaphore, a queue ...) you will prevent this task from
* running when the event arrives.
*********************************************************************************************************
*/
6)任务恢复函数
有任务挂起函数,就有任务恢复函数,通过该函数将被挂起的任务恢复,让调度器能够重新调度该函数。函数为OSTaskResume
原型为
INT8U OSTaskResume(INT8U prio);
1
UCOSII信号量和邮箱
任务间的同步依赖于任务间的通信。在UCOSII 中,是使用信号量、邮箱(消息邮箱)和消息队列这些被称作事件的中间环节来实现任务之间的通信。使用事件控制块来描述具体的信息,事件控制块结构体
typedef struct{
INT8U OSEventType;//事件的类型
INT16U OSEventCnt;//信号量计数器
void *OSEventPtr; //消息或消息队列的指针
INT8U OSEventGrp; //等待事件的任务组
INT8U OSEventTbl[OS_EVENT_TBL_SIZE];//任务等待表
#if OS_EVENT_NAME_EN > 0u
INT8U *OSEventName; //事件名
#endif
}OS_EVENT;
信号量
信号量是一类事件,它是为了给共享资源设立一个标志,该标志表示该共享资源的占用情况。这样,当一个任务在访问共享资源之前,就可以先对这个标志进行查询,从而在了解资源被占用的情况之后,再来决定自己的行为。
信号量可以分为两种:一种是二值型信号量(也称为互斥信号量),另外一种是N 值信号量。
邮箱
在多任务操作系统中,常常需要在任务与任务之间通过传递“消息”的方式来进行通信。为此需要在内存中创建一个存储空间作为该消息的缓冲区,称为消息缓冲区,这样在任务间传递数据(消息)的最简单办法就是传递消息缓冲区的指针。把用来传递消息缓冲区指针的数据结构叫做邮箱(消息邮箱)。
在UCOSII中,通过事件控制块的OSEventPrt来传递消息缓冲区指针,同时使事件控制块的成员OSEventType为常数OS_EVENT_TYPE_MBOX,则该事件控制块就叫做消息邮箱。
下面看一下关于信号量的函数
1)创建信号量
函数为OSSemCreate
OS_EVENT *OSSemCreate (INT16U cnt);
1
该函数返回值为已创建的信号量的指针,而参数cnt则是信号量计数器(OSEventCnt)的初始值。
2)请求信号量函数
函数为OSSemPend
void OSSemPend ( OS_EVENT *pevent, INT16U timeout, INT8U *err);
1
其中,参数pevent是被请求信号量的指针,timeout为等待时限,err为错误信息。
为防止任务因得不到信号量而处于长期的等待状态,函数OSSemPend允许用参数timeout设置一个等待时间的限制,当任务等待的时间超过timeout时可以结束等待状态而 进入就绪状态。如果参数timeout被设置为0,则表明任务的等待时间为无限长。
3)发送信号量函数
任务获得信号量,并在访问共享资源结束以后要释放信号量,释放信号量也叫做发送信号量,发送信号通过OSSemPost函数实现 。OSSemPost 函数在对信号量的计数器操作之前,首先要检查是否还有等待该信号量的任务。如果没有,就把信号量计数器OSEventCnt加一;如果有,则调用调度器OS_Sched( )去运行等待任务中优先级别最高的任务。函数OSSemPost的原型为:
INT8U OSSemPost(OS_EVENT *pevent);
1
其中,pevent为信号量指针,该函数在调用成功后,返回值为OS_ON_ERR,否则会根据具体错误返回OS_ERR_EVENT_TYPE、OS_SEM_OVF。
4)删除信号量
应用程序如果不需要某个信号量,可以调用函数OSSemDel来删除该信号量,该函数的原型为:
OS_EVENT *OSSemDel (OS_EVENT *pevent,INT8U opt, INT8U *err);
1
其中,pevent为要删除的信号量指针,opt为删除条件选项,err为错误信息。
下面是关于邮箱的函数
1)创建邮箱
创建邮箱通过函数OSMboxCreate实现,该函数原型为:
OS_EVENT *OSMboxCreate (void *msg);
1
函数中的参数msg为消息的指针,函数的返回值为消息邮箱的指针。
调用函数OSMboxCreate需先定义msg的初始值。在一般的情况下,这个初始值为NULL;但也可以事先定义一个邮箱,然后把这个邮箱的指针作为参数传递到函数OSMboxCreate 中,使之一开始就指向一个邮箱。
2) 向邮箱发送消息函数
任务可以通过调用函数OSMboxPost向消息邮箱发送消息,这个函数的原型为:
INT8U OSMboxPost (OS_EVENT *pevent,void *msg);
1
其中pevent为消息邮箱的指针,msg为消息指针。
3) 请求邮箱函数
当一个任务请求邮箱时需要调用函数OSMboxPend,这个函数的主要作用就是查看邮箱指针OSEventPtr是否为NULL,如果不是NULL就把邮箱中的消息指针返回给调用函数的任务,同时用OS_NO_ERR通过函数的参数err通知任务获取消息成功;如果邮箱指针OSEventPtr是NULL,则使任务进入等待状态,并引发一次任务调度。
函数OSMboxPend的原型为:
void *OSMboxPend (OS_EVENT *pevent, INT16U timeout, INT8U *err);
1
其中pevent为请求邮箱指针,timeout为等待时限,err为错误信息。
4) 查询邮箱状态函数
任务可以通过调用函数OSMboxQuery查询邮箱的当前状态。该函数原型为:
INT8U OSMboxQuery(OS_EVENT *pevent,OS_MBOX_DATA *pdata);
1
其中pevent为消息邮箱指针,pdata为存放邮箱信息的结构。
5) 删除邮箱函数
在邮箱不再使用的时候,我们可以通过调用函数OSMboxDel来删除一个邮箱,该函数原型为:
OS_EVENT *OSMboxDel(OS_EVENT *pevent,INT8U opt,INT8U *err);
1
其中pevent为消息邮箱指针,opt为删除选项,err为错误信息。
2.UCOSII实验代码
下面是一个STM32mini板实例:
主要功能:程序初始时,LED0灯闪烁,表示任务1在运行;串口显示表示任务2在运行。
通过信号量和邮箱,实现KEY0控制LED0任务(任务1)的挂起,KEY1实现串口显示任务(任务2)的删除,并且实现LED1灯的亮灭;WK_UP实现LED0任务(任务1)的恢复。
实验结果:只有窗口显示部分
main.c文件
#include "led.h"
#include "delay.h"
#include "sys.h"
#include "usart.h"
#include "includes.h"
#include "key.h"
#include "usart.h"
//START 任务
//设置任务优先级
#define START_TASK_PRIO 10 ///开始任务的优先级为最低
//设置任务堆栈大小
#define START_STK_SIZE 128
//任务任务堆栈
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);
//key传递函数
//设置任务优先级
#define KEY_TASK_PRIO 5
//设置任务堆栈大小
#define KEY_STK_SIZE 64
//任务堆栈
OS_STK KEY_TASK_STK[KEY_STK_SIZE];
//任务函数
void key_task(void *pdata);
//按键扫描任务
//设置任务优先级
#define SCAN_TASK_PRIO 4
//设置任务堆栈大小
#define SCAN_STK_SIZE 64
//任务堆栈
OS_STK SCAN_TASK_STK[SCAN_STK_SIZE];
//任务函数
void scan_task(void *pdata);
//串口显示任务
#define FLOAT_TASK_PRIO 8
//设置任务堆栈大小
#define FLOAT_STK_SIZE 128
//任务堆栈
//如果任务中使用printf来打印浮点数据的话一点要8字节对齐
__align(8) OS_STK FLOAT_TASK_STK[FLOAT_STK_SIZE];
//任务函数
void float_task(void *pdata);
OS_EVENT * msg_key; //按键邮箱时间块指针
OS_EVENT * sem_led0; //LED0信号量指针
OS_EVENT * sem_led1; //LED1信号量指针
OS_EVENT * sem_print;//WK_UP信号量指针
int main(void)
{
delay_init(); //延时初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //中断分组配置
uart_init(115200); //串口波特率设置
LED_Init(); //LED初始化
KEY_Init();
OSInit(); //UCOS初始化
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;
msg_key=OSMboxCreate((void *)0);//创建消息邮箱
sem_led0=OSSemCreate(0);
sem_led1=OSSemCreate(0);//创建信号量
sem_print=OSSemCreate(0);
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);
OSTaskCreate(key_task,(void *)0,(OS_STK*)&KEY_TASK_STK[KEY_STK_SIZE-1],KEY_TASK_PRIO);
OSTaskCreate(scan_task,(void *)0,(OS_STK*)&SCAN_TASK_STK[SCAN_STK_SIZE-1],SCAN_TASK_PRIO);
OSTaskCreate(float_task,(void *)0,(OS_STK*)&FLOAT_TASK_STK[FLOAT_STK_SIZE],FLOAT_TASK_PRIO);
OSTaskSuspend(START_TASK_PRIO); //挂起起始任务.
OS_EXIT_CRITICAL(); //退出临界区(可以被中断打断)
}
//LED0任务
void led0_task(void *pdata)
{
//u8 err;
while(1)
{
// OSSemPend(sem_led0,0,&err);
LED0=0;
delay_ms(500);
LED0=1;
delay_ms(500);
};
}
//LED1任务
void led1_task(void *pdata)
{
u8 err;
while(1)
{
OSSemPend(sem_led1,0,&err);
LED1=0;
delay_ms(500);
LED1=1;
delay_ms(500);
}
}
//按键扫描任务
void key_task(void *pdata)
{
int key=0;
u8 err;
while(1)
{
key=(int)OSMboxPend(msg_key,10,&err);
switch(key)
{
case KEY0_PRES://发送信号量0
OSSemPost(sem_led0);
OSTaskSuspend(LED0_TASK_PRIO);//led0任务挂起
break;
case KEY1_PRES://发送信号量1
OSSemPost(sem_led1);
OSTaskDel(FLOAT_TASK_PRIO);//删除串口显示任务
break;
case WKUP_PRES:
//OSSemPost(sem_print);
// OSSemPost(sem_led1);
OSTaskResume(LED0_TASK_PRIO);//led1任务挂起后恢复
break;
}
}
}
//串口显示任务
void float_task(void *pdata)
{
//u8 err;
OS_CPU_SR cpu_sr=0;
while(1)
{
// OSSemPend(sem_print,0,&err);
OS_ENTER_CRITICAL(); //进入临界区(关闭中断)
printf("串口显示程序正在运行:rnn"); //串口打印结果
printf("这是任务2:rnn"); //串口打印结果
OS_EXIT_CRITICAL(); //退出临界区(开中断)
delay_ms(500);
}
}
//按键扫描任务
void scan_task(void *pdata)
{
u8 key;
while(1)
{
key=KEY_Scan(0);
if(key)OSMboxPost(msg_key,(void*)key);//发送消息
delay_ms(10);
}
}
key.c文件
#include "key.h"
#include "delay.h"
//按键初始化函数
//PA0.15和PC5 设置成输入
void KEY_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOC,ENABLE);//使能PORTA,PORTC时钟
GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);//关闭jtag,使能SWD,可以用SWD模式调试
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15;//PA15
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //设置成上拉输入
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA15
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;//PC5
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //设置成上拉输入
GPIO_Init(GPIOC, &GPIO_InitStructure);//初始化GPIOC5
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;//PA0
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //PA0设置成输入,默认下拉
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.0
}
//按键处理函数
//返回按键值
//mode:0,不支持连续按;1,支持连续按;
//返回值:
//0,没有任何按键按下
//KEY0_PRES,KEY0按下
//KEY1_PRES,KEY1按下
//WKUP_PRES,WK_UP按下
//注意此函数有响应优先级,KEY0>KEY1>WK_UP!!
u8 KEY_Scan(u8 mode)
{
static u8 key_up=1;//按键按松开标志
if(mode)key_up=1; //支持连按
if(key_up&&(KEY0==0||KEY1==0||WK_UP==1))
{
delay_ms(10);//去抖动
key_up=0;
if(KEY0==0)return KEY0_PRES;
else if(KEY1==0)return KEY1_PRES;
else if(WK_UP==1)return WKUP_PRES;
}else if(KEY0==1&&KEY1==1&&WK_UP==0)key_up=1;
return 0;// 无按键按下
}
led.c文件
#include "led.h"
//初始化PA8和PD2为输出口.并使能这两个口的时钟
//LED IO初始化
void LED_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOD, ENABLE); //使能PA,PD端口时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; //LED0-->PA.8 端口配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度为50MHz
GPIO_Init(GPIOA, &GPIO_InitStructure); //根据设定参数初始化GPIOA.8
GPIO_SetBits(GPIOA,GPIO_Pin_8); //PA.8 输出高
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; //LED1-->PD.2 端口配置, 推挽输出
GPIO_Init(GPIOD, &GPIO_InitStructure); //推挽输出 ,IO口速度为50MHz
GPIO_SetBits(GPIOD,GPIO_Pin_2); //PD.2 输出高
}
工程目录
举报