UCOSIII 任务创建和删除
这篇总结只是概述任务的创建和删除,至于系统的原理、前后台系统的区别、系统移植、系统初始化配置等以后会用专门的文章来总结。
系统初始化大概流程
- 各个外设的初始化
- 系统初始化,其实就是调用一个系统初始化函数就好 OSInit()
- 进入临街代码保护 OS_CRITICAL_ENTER()
- 创建任务 OSTaskCreate()
- 退出临界代码保护 OS_CRITICAL_EXIT()
- 启动系统 OSStart()
看起来好像挺简单的,但是其中有些地方还是要注意的。比如在创建任务的时候一般都不希望被中断打断,所以就需要对这部分代码进行保护,让这部分代码能够连续执行,所以就需要调用临界代码保护的函数对代码临界保护。这个是参考原子的代码,这两个函数在UCOS作者的书中特别说明用户是不能调用的,而且UCOS给用户的API接口函数中也没有这两个函数,但是在UCOS源码中这两个函数也是当普通函数用的。
这几个步骤中,创建任务这步是比较关键的,函数原型和入口参数解释如下;
函数原型;
void OSTaskCreate (OS_TCB *p_tcb,
CPU_CHAR *p_name,
OS_TASK_PTR p_task,
void *p_arg,
OS_PRIO prio,
CPU_STK *p_stk_base,
CPU_STK_SIZE stk_limit,
CPU_STK_SIZE stk_size,
OS_MSG_QTY q_size,
OS_TICK time_quanta,
void *p_ext,
OS_OPT opt,
OS_ERR *p_err)
参数解释;
OS_TCB *p_tcb, | 这个是任务控制块,里面记载着本任务的各种信息,有点像其他语言里面的句柄吧。 |
CPU_CHAR *p_name, | 可以给任务起一个名字,用上位机调试的时候可以看到,但是如果不用上位机调式的话好像没啥用 |
OS_TASK_PTR p_task, | 这是任务函数的函数名,本任务要做的事情都放在这个任务函数里 |
void *p_arg, | 可以给任务函数传递入口参数 |
OS_PRIO prio, | 设定本任务的优先级,这里用的是原子的demo,原子的基本工程里0-2,最后的两个优先级已经使用了,所以我们不能使用这几个优先级,如果开启了时间片轮转调度的话不同的任务可以使用相同的优先级,但是我还是喜欢每个任务单独占用一个优先级。优先级的数字越低优先级越高,第5级优先级比第6级优先级高。 |
CPU_STK *p_stk_base, | 任务堆栈基地址,任务切换的时候要把当前状态备份到这个堆栈里,就像中断的压栈PUSH。等到恢复这个任务的时候再把堆栈里的状态数据恢复到相关寄存器,相当于中断里的出栈POP。 |
CPU_STK_SIZE stk_limit, | 这个堆栈也是有大小的,为了防止堆栈溢出,这里对堆栈进行限位检测,一般为10%吧,但是系统检测到堆栈到达限位时不知道系统会怎么处理 |
CPU_STK_SIZE stk_size, | 这个就是堆栈的总大小了,其实就是堆栈数组的长度。但是这个大小和CPU_STK_SIZE的数据类型有关,如果是8位的话堆栈长度10就是10个字节,如果是32位的话堆栈长度10就是40个字节,依次类推 |
OS_MSG_QTY q_size, | 如果使能了内部消息队列功能的话,这个参数就是用来确定可用消息长度的,如果设置为0则代表禁止该任务接收消息队列 |
OS_TICK time_quanta, | UCOSIII支持同一个优先级下有多个任务,这个参数就是确定本任务所占用的时间片的个数,如果设置为0则代表一个时间片,每个时间片的长度由系统决定 |
void *p_ext, | 任务扩展TCB用的,其实我没用过 |
OS_OPT opt, | 这个选项有5个可选项;如果想多选的话用与连接就好 OS_OPT_TASK_NONE 表示没有任何选项 OS_OPT_TASK_STK_CHK 指定是否允许检测任务的堆栈,当然是允许啊 OS_OPT_TASK_STK_CLR 指定是否需要清零该任务的堆栈,在新建任务的时候清空下堆栈比较好 OS_OPT_TASK_SAVE_FP 指定是否保存浮点运算寄存器,有浮点运算的CPU才会用到,最起码M3是没有的 OS_OPT_TASK_NO_TLS 这个选项在工程里面有,但是UCOS 的书上却没有解释 |
OS_ERR *p_err | 这个参数返回一个错误码,不同的错误码代表不同的错误类型,UCOS作者强烈建议检查这个错误码,但是事实上因为程序空间不够或者其他原因(懒),很多时候是不检测的。 |
关于任务函数,每个任务的函数都是一个无限循环的函数,至于什么原因在UCOS原理章节解释吧,反正按照这个格式写就对了,举个栗子;
void uartTask_1_Func()
{
OS_ERR myErr;
while(1)//死循环
{
OSTimeDlyHMSM(0,0,1,0,OS_OPT_TIME_HMSM_STRICT,&myErr);//延时1秒
printf("Im uart task 1 \r\n");//串口输出信息
}
}
这个函数的功能是每隔1秒就用串口向电脑发送一些信息,函数内部是一个死循环。这里有个要注意的地方,在死循环的内部一定要调用可以引起任务切换的函数,不然程序会一直运行这个任务而不能切换到其他任务运行。能引起任务切换的函数有以下几个;
OSFlagPend()
OSMutexPend();
OSPendMulti()
OSQPend()
OSSemPend()
OSTimeDly()
OSTimeDlyHMSM()
OSTaskQPend()
OSTaskSemPend()
OSTaskSuspend()
OSTaskDel()
................暂时想到这几个,应该还有其他的函数可以引起任务切换。
下面来个完整的栗子吧,在main函数里创建一个任务star task,这个任务只是用来创建led task、uart task 1 和uart task 2这三个任务,在创建完这三个任务后star task就把自己给删除掉,然后系统就循环运行led task、uart task 1 和uart task 2这三个任务了。其中led task这个任务每隔100毫秒翻转一个LED灯,指示系统正在运行;uart task 1这个任务每隔1秒向串口输出一串信息,uart task 2这个任务每隔1.5秒向串口输出一串信息,在电脑的串口助手可以看到uart task 1 和uart task 2这两个任务的运行情况。虽然我用的是原子的代码,但是用的却是STM32的最小系统板,这也是为什么用串口输出来查看任务的原因,在学习UCOS部分估计都会用最小系统来测试代码,至于代码里出现的正点原子相关的信息,因为用的是正点原子的工程,所以有些信息保留了下来,当是尊重版权吧。主要代码如下;
Main.c文件
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "includes.h"
#include "task.h"
/************************************************
ALIENTEK战舰STM32开发板UCOS实验
技术支持:www.openedv.com
淘宝店铺:http://eboard.taobao.com
关注微信公众平台微信号:"正点原子",免费获取STM32资料。
广州市星翼电子科技有限公司
作者:正点原子 @ALIENTEK
************************************************/
//UCOSIII中以下优先级用户程序不能使用,ALIENTEK
//将这些优先级分配给了UCOSIII的5个系统内部任务
//优先级0:中断服务服务管理任务 OS_IntQTask()
//优先级1:时钟节拍任务 OS_TickTask()
//优先级2:定时任务 OS_TmrTask()
//优先级OS_CFG_PRIO_MAX-2:统计任务 OS_StatTask()
//优先级OS_CFG_PRIO_MAX-1:空闲任务 OS_IdleTask()
//技术支持:www.openedv.com
//淘宝店铺:http://eboard.taobao.com
//广州市星翼电子科技有限公司
//作者:正点原子 @ALIENTEK
//*****************************************************************
//开始任务
OS_TCB starTaskTCB; //任务控制块
#define STAR_TASK_PRIO 3 //任务优先级
#define STAR_TASK_STK_SIZE 128 //任务堆栈总大小
CPU_STK STAR_TASK_STK[ STAR_TASK_STK_SIZE ]; //任务堆栈数组
int main()
{
OS_ERR myErr; //os的错误码
CPU_SR_ALLOC(); //使用临街保护就得添加这玩意,不然会报错
delay_init(); //时钟初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//中断分组配置
uart_init(115200); //串口初始化
LED_Init(); //LED初始化
OSInit(&myErr); //os初始化
//创建任务的时候进入临街保护比较好,但是这个函数非用户的API函数,用户使用是否合适以后再讨论
OS_CRITICAL_ENTER();
//创建开始任务,在开始任务里创建其他任务,为每个函数入口参数都强制转换类型吧,以防万一
OSTaskCreate ( (OS_TCB *)&starTaskTCB, //任务控制块,
(CPU_CHAR *)"star task", //任务名字
(OS_TASK_PTR )starTaskFunc, //任务函数
(void *)0, //任务函数入口参数
(OS_PRIO )STAR_TASK_PRIO, //任务优先级
(CPU_STK *)&STAR_TASK_STK[0], //堆栈数组基地址
(CPU_STK_SIZE ) STAR_TASK_STK_SIZE/10, //堆栈溢出限位
(CPU_STK_SIZE ) STAR_TASK_STK_SIZE, //堆栈大小
(OS_MSG_QTY ) 0, //本任务消息长度
(OS_TICK ) 0, //时间片个数
(void *) 0, //任务块补充参数
(OS_OPT ) OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR, //选项
(OS_ERR *)&myErr); //错误码
OS_CRITICAL_EXIT(); //任务创建完了就该退出临界保护了
OSStart(&myErr); //任务创建完成后就该运行了
while(1); //运行系统后就一直在系统里面跑了,应该运行不到这里
}
Task.c文件
#include "task.h"
#include "sys.h"
#include "led.h"
#include "includes.h" //想要使用UCOS系统,得包含这个头文件
//LED秒闪任务,提示系统正在运行
OS_TCB ledTaskTCB;
#define LED_TASK_PRIO 4
#define LED_TASK_STK_SIZE 128
CPU_STK LED_TASK_STK[LED_TASK_STK_SIZE];
//串口输出任务1
OS_TCB uartTask_1_TCB;
#define UART_TASK_1_PRIO 5
#define UART_TASK_1_STK_SIZE 128
CPU_STK UART_TASK_1_STK[ UART_TASK_1_STK_SIZE ];
//串口输出任务2
OS_TCB uartTask_2_TCB;
#define UART_TASK_2_PRIO 6
#define UART_TASK_2_STK_SIZE 128
CPU_STK UART_TASK_2_STK[ UART_TASK_2_STK_SIZE ];
void starTaskFunc()
{
OS_ERR myErr;
OSTaskCreate ( (OS_TCB *)&ledTaskTCB,
(CPU_CHAR *)"led task",
(OS_TASK_PTR )ledTaskFunc,
(void *)0,
(OS_PRIO )LED_TASK_PRIO,
(CPU_STK *)&LED_TASK_STK[0],
(CPU_STK_SIZE ) LED_TASK_STK_SIZE/10,
(CPU_STK_SIZE ) LED_TASK_STK_SIZE,
(OS_MSG_QTY ) 0,
(OS_TICK ) 0,
(void *) 0,
(OS_OPT ) OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR,
(OS_ERR *)&myErr);
OSTaskCreate ( (OS_TCB *)&uartTask_1_TCB,
(CPU_CHAR *)"uart task 1 ",
(OS_TASK_PTR )uartTask_1_Func,
(void *)0,
(OS_PRIO )UART_TASK_1_PRIO,
(CPU_STK *)&UART_TASK_1_STK[0],
(CPU_STK_SIZE ) UART_TASK_1_STK_SIZE/10,
(CPU_STK_SIZE ) UART_TASK_1_STK_SIZE,
(OS_MSG_QTY ) 0,
(OS_TICK ) 0,
(void *) 0,
(OS_OPT ) OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR,
(OS_ERR *)&myErr);
OSTaskCreate ( (OS_TCB *)&uartTask_2_TCB,
(CPU_CHAR *)"uart task 2 ",
(OS_TASK_PTR )uartTask_2_Func,
(void *)0,
(OS_PRIO )UART_TASK_2_PRIO,
(CPU_STK *)&UART_TASK_2_STK[0],
(CPU_STK_SIZE ) UART_TASK_2_STK_SIZE/10,
(CPU_STK_SIZE ) UART_TASK_2_STK_SIZE,
(OS_MSG_QTY ) 0,
(OS_TICK ) 0,
(void *) 0,
(OS_OPT ) OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR,
(OS_ERR *)&myErr);
//这个任务只是用来创建其他任务的,其他任务创建完成了这个任务也就没用了,删除掉
OSTaskDel((OS_TCB*)0,&myErr);
}
//LED任务的任务函数
//LED每隔500ms翻转一次,提示系统正在运行
void ledTaskFunc()
{
OS_ERR myErr;
while(1) //注意,每个任务都是一个死循环
{
OSTimeDlyHMSM ( (CPU_INT16U ) 0, //时
(CPU_INT16U ) 0, //分
(CPU_INT16U ) 0, //秒
(CPU_INT32U ) 100, //毫秒
(OS_OPT ) OS_OPT_TIME_HMSM_STRICT, //选项
(OS_ERR *)&myErr); //错误码
LED = !LED; //LED状态翻转
}
}
//串口任务1的任务函数
//功能;每隔1秒向电脑串口助手发送一次数据
void uartTask_1_Func()
{
OS_ERR myErr;
while(1)
{
OSTimeDlyHMSM(0,0,1,0,OS_OPT_TIME_HMSM_STRICT,&myErr);//延时1秒
printf("Im uart task 1 \r\n");
}
}
//串口任务2的任务函数
//功能;每隔1.5秒向电脑串口助手发送一次数据
void uartTask_2_Func()
{
OS_ERR myErr;
while(1)
{
OSTimeDlyHMSM(0,0,1,500,OS_OPT_TIME_HMSM_STRICT,&myErr);//延时1.5秒
printf("Im uart task 2 \r\n");
}
}
Task.h文件
#ifndef __TASK_H
#define __TASK_H
void starTaskFunc();
void ledTaskFunc();
void uartTask_1_Func();
void uartTask_2_Func();
#endif