STM32F4X UCOSIII任务
什么是任务
在日常生活中,我们要完成一个问题时,一般都会把一个大问题分解成若干个小问题,当每个小问题依次被解决时,大问题也随之被解决。同样地对于MCU来说也是如此,通常在没有RTOS的情况下,一般我们都会把所有的业务逻辑都放到main函数里面进行解决,但是随着业务逻辑的不断增多,就会发现把所有的业务逻辑都放到main函数里面就会显得特别臃肿,而且实时性也不好。RTOS可以把一个很复杂的业务逻辑分成一个个的任务,每个任务都可以根据自己的事件片进行调度,从而使程序更加简洁,提高实时性。
UCOS任务
任务控制块(OS_TCB)
在UCOSIII中,每个任务都会有自己的任务控制块,所谓的任务控制块就类似于人的身份证,是这个任务的唯一标识,后续对任务的操作都会基于任务控制块进行操作。
struct os_tcb {
CPU_STK *StkPtr; /* Pointer to current top of stack */
void *ExtPtr; /* Pointer to user definable data for TCB extension */
#if ((OS_CFG_DBG_EN > 0u) || (OS_CFG_STAT_TASK_STK_CHK_EN > 0u))
CPU_STK *StkLimitPtr; /* Pointer used to set stack 'watermark' limit */
#endif
OS_TCB *NextPtr; /* Pointer to next TCB in the TCB list */
OS_TCB *PrevPtr; /* Pointer to previous TCB in the TCB list */
OS_TCB *TickNextPtr;
OS_TCB *TickPrevPtr;
OS_TICK_LIST *TickListPtr; /* Pointer to tick list if task is in a tick list */
#if OS_CFG_DBG_EN > 0u
CPU_CHAR *NamePtr; /* Pointer to task name */
#endif
#if ((OS_CFG_DBG_EN > 0u) || (OS_CFG_STAT_TASK_STK_CHK_EN > 0u))
CPU_STK *StkBasePtr; /* Pointer to base address of stack */
#endif
#if defined(OS_CFG_TLS_TBL_SIZE) && (OS_CFG_TLS_TBL_SIZE > 0u)
OS_TLS TLS_Tbl[OS_CFG_TLS_TBL_SIZE];
#endif
#if OS_CFG_DBG_EN > 0u
OS_TASK_PTR TaskEntryAddr; /* Pointer to task entry point address */
void *TaskEntryArg; /* Argument passed to task when it was created */
#endif
OS_PEND_DATA *PendDataTblPtr; /* Pointer to list containing objects pended on */
OS_STATE PendOn; /* Indicates what task is pending on */
OS_STATUS PendStatus; /* Pend status */
OS_STATE TaskState; /* See OS_TASK_STATE_xxx */
OS_PRIO Prio; /* Task priority (0 == highest) */
#if ((OS_CFG_DBG_EN > 0u) || (OS_CFG_STAT_TASK_STK_CHK_EN > 0u))
CPU_STK_SIZE StkSize; /* Size of task stack (in number of stack elements) */
#endif
OS_OPT Opt; /* Task options as passed by OSTaskCreate() */
OS_OBJ_QTY PendDataTblEntries; /* Size of array of objects to pend on */
CPU_TS TS; /* Timestamp */
#if (defined(TRACE_CFG_EN) && (TRACE_CFG_EN > 0u))
CPU_INT08U SemID; /* Unique ID for third-party debuggers and tracers. */
#endif
OS_SEM_CTR SemCtr; /* Task specific semaphore counter */
/* DELAY / TIMEOUT */
OS_TICK TickRemain; /* Number of ticks remaining (updated at by OS_TickTask() */
OS_TICK TickCtrPrev; /* Used by OSTimeDly??() in PERIODIC mode */
#if OS_CFG_SCHED_ROUND_ROBIN_EN > 0u
OS_TICK TimeQuanta;
OS_TICK TimeQuantaCtr;
#endif
#if OS_MSG_EN > 0u
void *MsgPtr; /* Message received */
OS_MSG_SIZE MsgSize;
#endif
#if OS_CFG_TASK_Q_EN > 0u
OS_MSG_Q MsgQ; /* Message queue associated with task */
#if OS_CFG_TASK_PROFILE_EN > 0u
CPU_TS MsgQPendTime; /* Time it took for signal to be received */
CPU_TS MsgQPendTimeMax; /* Max amount of time it took for signal to be received */
#endif
#endif
#if OS_CFG_TASK_REG_TBL_SIZE > 0u
OS_REG RegTbl[OS_CFG_TASK_REG_TBL_SIZE]; /* Task specific registers */
#endif
#if OS_CFG_FLAG_EN > 0u
OS_FLAGS FlagsPend; /* Event flag(s) to wait on */
OS_FLAGS FlagsRdy; /* Event flags that made task ready to run */
OS_OPT FlagsOpt; /* Options (See OS_OPT_FLAG_xxx) */
#endif
#if OS_CFG_TASK_SUSPEND_EN > 0u
OS_NESTING_CTR SuspendCtr; /* Nesting counter for OSTaskSuspend() */
#endif
#if OS_CFG_TASK_PROFILE_EN > 0u
OS_CPU_USAGE CPUUsage; /* CPU Usage of task (0.00-100.00%) */
OS_CPU_USAGE CPUUsageMax; /* CPU Usage of task (0.00-100.00%) - Peak */
OS_CTX_SW_CTR CtxSwCtr; /* Number of time the task was switched in */
CPU_TS CyclesDelta; /* value of OS_TS_GET() - .CyclesStart */
CPU_TS CyclesStart; /* Snapshot of cycle counter at start of task resumption */
OS_CYCLES CyclesTotal; /* Total number of # of cycles the task has been running */
OS_CYCLES CyclesTotalPrev; /* Snapshot of previous # of cycles */
CPU_TS SemPendTime; /* Time it took for signal to be received */
CPU_TS SemPendTimeMax; /* Max amount of time it took for signal to be received */
#endif
#if OS_CFG_STAT_TASK_STK_CHK_EN > 0u
CPU_STK_SIZE StkUsed; /* Number of stack elements used from the stack */
CPU_STK_SIZE StkFree; /* Number of stack elements free on the stack */
#endif
#ifdef CPU_CFG_INT_DIS_MEAS_EN
CPU_TS IntDisTimeMax; /* Maximum interrupt disable time */
#endif
#if OS_CFG_SCHED_LOCK_TIME_MEAS_EN > 0u
CPU_TS SchedLockTimeMax; /* Maximum scheduler lock time */
#endif
#if OS_CFG_DBG_EN > 0u
OS_TCB *DbgPrevPtr;
OS_TCB *DbgNextPtr;
CPU_CHAR *DbgNamePtr;
#endif
#if (defined(TRACE_CFG_EN) && (TRACE_CFG_EN > 0u))
CPU_INT08U TaskID; /* Unique ID for third-party debuggers and tracers. */
#endif
};
可以看到任务控制块结构体里面有很多成员变量,这些变量都是记录了每个任务的状态,当然这些变量我们只需要做个简单的理解即可,因为在任务的运行中我们都不需要直接去访问这些变量,UCOSIII会通过API进行访问。
UCOSIII任务的状态
任务状态图
任务就像日常生活中的人一样,也有自己的运行状态,任务大概可以分成以下几种状态。
- (1):创建任务–>就绪态:任务被创建后会被放到任务的就绪列表中,此时还没有被调度。
- (2):就绪态–>运行态:发生任务切换时,任务就绪列表中优先级最高的任务会被调度,此时就进入运行态。
- (3):运行态–>就绪态:如果此时有一个更高优先级的任务被创建或者恢复后,发生任务调度,此时更高优先级的任务会被调度,原先运行的任务会重新回到任务就绪列表,变成就绪态。
- (4):运行态–>阻塞态:任务在等待某些资源(信号量、互斥锁、消息等),此时任务会进行阻塞状态。
- (5):阻塞态–>就绪态:如果任务被恢复后(获取到资源),会重新把任务放到就绪列表中,等待调度。
- (6)(7)(8):阻塞态、就绪态、运行态–>删除态:调用OSTaskDel函数将任务删除,被删除的任务不能再次使用,也不会继续参与任务的调度
- (9):删除态–>就绪态:调用OSTaskCreate函数创建任务,任务重新进入就绪态。
UCOSIII任务状态
从上图可以大概知道RTOS中的任务状态,下面就来具体了解一下UCOSIII的任务状态。
- (就绪)OS_TASK_STATE_RDY:任务被创建后被放到就绪列表,等待调度
- (延时)OS_TASK_STATE_DLY:调用OSTimeDly,任务会进入延时调度状态。
- (阻塞)OS_TASK_STATE_PEND:当任务等待信号量,互斥锁,消息等资源时,会进入阻塞状态
- (阻塞超时)OS_TASK_STATE_PEND_TIMEOUT:任务在等待信号量,互斥锁,消息等资源时,可以设置超时阻塞时间,超过时间任务会退出阻塞态,进行就绪态
- (挂起)OS_TASK_STATE_SUSPENDED:任务调用OSTaskSuspend挂起自己或者其他任务,被挂起的任务将不参与调度,直到调用OSTaskResume恢复任务。
- (延时+挂起)OS_TASK_STATE_DLY_SUSPENDED:任务延时一段时间,当延时还没结束就被挂起时,效果叠加。只有当延时结束并且挂起被恢复时,任务才会进行调度。
- (阻塞+挂起)OS_TASK_STATE_PEND_SUSPENDED:任务阻塞等待资源进入阻塞态时,被其他任务挂起时,效果叠加。只有当获取到资源退出阻塞态并且挂起被恢复时,任务才会进行调度。
- (超时等待 + 挂起)OS_TASK_STATE_PEND_TIMEOUT_SUSPENDED:任务在指定时间内等待事件或信号的产生,但是任务已经被其他任务挂起。
- (删除)OS_TASK_STATE_DEL:调用OSTaskDel删除任务,被删除任务不能继续使用
任务优先级
UCOSIII的任务在创建的时候可以设置任务的优先级,任务调度器通常会选择优先级调度优先级高的任务先运行。在UCOSIII中,最大支持的优先级是0-255,数值越低,代表优先级越高。UCOSIII可以通过配置文件设置优先级的数量。
任务调度
抢占式调度
UCOSIII是一个可抢占,支持优先级的内核,在创建任务的时候给每个任务都分配了一个优先级,可抢占的意思是如果在系统出现一个高优先级任务,那么这个高优先级的任务会立刻进行调度。当系统发生一些可以触发任务调度的事件时,UCOSIII会选择任务就绪列表中优先级最高的任务进行调度。同样地当中断结束后也会进行任务调度,此时UCOSIII也是会选择任务就绪列表中优先级最高的任务进行调度。下图简单描述了高优先级调度的过程。
- 低优先级任务正在运行,此时发生了一次中断
- 系统进入中断
- 开始运行中断服务函数
- 中断服务函数运行完成,跳出中断
- UCOSIII接管系统
- 由于有高优先级任务,UCOSIII选择运行高优先级任务,低优先级任务不运行
- 开始运行高优先级任务
- 高优先级任务运行完成
- UCOSIII接管系统
- 选择低优先级任务运行
- 开始运行低优先级任务
时间片轮转调度
当多个任务有相同的优先级时,uC/OS-III 允许每个任务运行规定的时间片。当任务没有用完分配给它的时间片时,它可以自愿地放弃CPU。
假设有2个优先级相同的就绪态线程A与B,A 线程的时间片设置为10,B线程的时间片设置为 5,那么当系统中不存在比A优先级高的就绪态线程时,系统会在 A、B线程间来回切换执行,并且每次对A线程执行10个节拍的时长,对B线程执行5个节拍的时长。
任务调度点
当系统发生以下事件时,会触发任务的调度
- 任务被标记或发送消息给另一个任务
- 任务调用 OSTimeDly()或 OSTimeDlyHMSM()
- 任务所等待的事件发生或超时
- 任务取消挂起
- 新任务被创建
- 任务被删除
- 内核对象被删除
- 任务改变自身的优先级或其它任务的优先级
- 当任务通过调用 OSTaskSuspend()停止自身
- 任务调用 OSTaskResume()恢复其它停止了的任务
- 退出中断服务程序
- 通过调用 OSSchedUnlock()调度器被解锁
- 调用 OSSchedRoundRobinYield()任务放弃了分配给它的时间片
- 用户调用 OSSched()
任务栈
UCOSIII中每个任务都有自己的栈空间,任务栈的作用是用来进行任务调度时保存当前任务的状态,以便后续恢复任务。UCOSIII中的任务栈初始化函数是OSTaskStkInit
CPU_STK *OSTaskStkInit (OS_TASK_PTR p_task,
void *p_arg,
CPU_STK *p_stk_base,
CPU_STK *p_stk_limit,
CPU_STK_SIZE stk_size,
OS_OPT opt)
{
CPU_STK *p_stk;
(void)opt; /* Prevent compiler warning */
p_stk = &p_stk_base[stk_size]; /* Load stack pointer */
/* Align the stack to 8-bytes. */
p_stk = (CPU_STK *)((CPU_STK)(p_stk) & 0xFFFFFFF8);
/* Registers stacked as if auto-saved on exception */
*--p_stk = (CPU_STK)0x01000000u; /* xPSR */
*--p_stk = (CPU_STK)p_task; /* 任务处理函数 */
*--p_stk = (CPU_STK)OS_TaskReturn; /* 任务返回函数,通常任务都是一个死循环,没有返回 */
*--p_stk = (CPU_STK)0x12121212u; /* R12 */
*--p_stk = (CPU_STK)0x03030303u; /* R3 */
*--p_stk = (CPU_STK)0x02020202u; /* R2 */
*--p_stk = (CPU_STK)p_stk_limit; /* R1 任务的栈大小 */
*--p_stk = (CPU_STK)p_arg; /* R0 : 任务参数 */
/* Remaining registers saved on process stack */
*--p_stk = (CPU_STK)0x11111111u; /* R11 */
*--p_stk = (CPU_STK)0x10101010u; /* R10 */
*--p_stk = (CPU_STK)0x09090909u; /* R9 */
*--p_stk = (CPU_STK)0x08080808u; /* R8 */
*--p_stk = (CPU_STK)0x07070707u; /* R7 */
*--p_stk = (CPU_STK)0x06060606u; /* R6 */
*--p_stk = (CPU_STK)0x05050505u; /* R5 */
*--p_stk = (CPU_STK)0x04040404u; /* R4 */
return (p_stk);
}
由于ARM的栈是满减栈,所以任务栈的生成方向如下所示
上面的任务栈初始化代码可以分成两部分,一部分是初始化R0-R3、R12、R14和PC,分别保存了任务的参数,任务的入口函数、任务的返回函数。R0-R3、R12、R14、PSR和PC会在发生异常时,自动压入栈里面,并且返回时自动弹栈,不用人为保存。第二部分是初始化R4-R11,因为任务刚创建,还没开始运行,所以初始化的值都是固定值。
UCOSIII任务常用函数
任务创建函数OSTaskCreate
/*
* p_tcb:任务控制块
* p_name:任务名
* p_task:任务入口函数
* p_arg:任务参数
* prio:任务优先级
* p_stk_base:任务栈地址
* stk_limit:栈深度的限制位置
* stk_size;任务栈大小
* q_size:设置可以发送到任务的最大消息数
* time_quanta:在任务之间循环时的时间片的时间量(以滴答为单位)。指定0则使 用默认值
* p_ext:向用户提供的内存位置的指针,用作 TCB 扩展
* opt:用户可选的任务特定选项
* p_err:返回的错误码
*/
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)
{
任务删除函数OSTaskDel
/*
* p_tcb:任务控制块
* p_err:返回的错误码
*/
void OSTaskDel (OS_TCB *p_tcb,
OS_ERR *p_err)
任务挂起函数OSTaskSuspend
/*
* p_tcb:任务控制块
* p_err:返回的错误码
*/
void OSTaskSuspend (OS_TCB *p_tcb,
OS_ERR *p_err)
任务恢复函数OSTaskResume
/*
* p_tcb:任务控制块
* p_err:返回的错误码
*/
void OSTaskResume (OS_TCB *p_tcb,
OS_ERR *p_err)
任务延时函数
/*
* dly:延时的时间片
* opt:用户可选的特定选项
* p_err:返回的错误码
*/
void OSTimeDly (OS_TICK dly,
OS_OPT opt,
OS_ERR *p_err)
/*
* hours:小时
* minutes:分
* seconds:秒
* milli:毫秒
* opt:用户可选的特定选项
* p_err:返回的错误码
*/
void OSTimeDlyHMSM (CPU_INT16U hours,
CPU_INT16U minutes,
CPU_INT16U seconds,
CPU_INT32U milli,
OS_OPT opt,
OS_ERR *p_err)
UCOSIII任务创建例程
/*
*********************************************************************************************************
* EXAMPLE CODE
*
* (c) Copyright 2013; Micrium, Inc.; Weston, FL
*
* All rights reserved. Protected by international copyright laws.
* Knowledge of the source code may not be used to write a similar
* product. This file may only be used in accordance with a license
* and should not be redistributed in any way.
*********************************************************************************************************
*/
/*
*********************************************************************************************************
*
* EXAMPLE CODE
*
* IAR Development Kits
* on the
*
* STM32F429II-SK KICKSTART KIT
*
* Filename : app.c
* Version : V1.00
* Programmer(s) : YS
*********************************************************************************************************
*/
/*
*********************************************************************************************************
* INCLUDE FILES
*********************************************************************************************************
*/
#include <includes.h>
/*
*********************************************************************************************************
* LOCAL DEFINES
*********************************************************************************************************
*/
/*
*********************************************************************************************************
* LOCAL GLOBAL VARIABLES
*********************************************************************************************************
*/
/* ----------------- APPLICATION GLOBALS -------------- */
static OS_TCB AppTaskStartTCB;
static CPU_STK AppTaskStartStk[APP_CFG_TASK_START_STK_SIZE];
#define APPTASK1NAME "App Task1"
#define APP_TASK1_PRIO 3
#define APP_TASK1_STK_SIZE 1024
static OS_TCB AppTask1TCB;
static void AppTask1 (void *p_arg);
static CPU_STK AppTask1Stk[APP_TASK1_STK_SIZE];
#define APPTASK2NAME "App Task2"
#define APP_TASK2_PRIO 4
#define APP_TASK2_STK_SIZE 1024
static OS_TCB AppTask2TCB;
static void AppTask2 (void *p_arg);
static CPU_STK AppTask2Stk[APP_TASK2_STK_SIZE];
/*
*********************************************************************************************************
* FUNCTION PROTOTYPES
*********************************************************************************************************
*/
static void AppTaskStart (void *p_arg);
/*
*********************************************************************************************************
* main()
*
* Description : This is the standard entry point for C code. It is assumed that your code will call
* main() once you have performed all necessary initialization.
*
* Arguments : none
*
* Returns : none
*********************************************************************************************************
*/
int main(void)
{
OS_ERR err;
OSInit(&err); /* Init uC/OS-III. */
OSTaskCreate((OS_TCB *)&AppTaskStartTCB, /* Create the start task */
(CPU_CHAR *)"App Task Start",
(OS_TASK_PTR )AppTaskStart,
(void *)0u,
(OS_PRIO )APP_CFG_TASK_START_PRIO,
(CPU_STK *)&AppTaskStartStk[0u],
(CPU_STK_SIZE )AppTaskStartStk[APP_CFG_TASK_START_STK_SIZE / 10u],
(CPU_STK_SIZE )APP_CFG_TASK_START_STK_SIZE,
(OS_MSG_QTY )0u,
(OS_TICK )0u,
(void *)0u,
(OS_OPT )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),
(OS_ERR *)&err);
OSStart(&err); /* Start multitasking (i.e. give control to uC/OS-III). */
}
/*
*********************************************************************************************************
* STARTUP TASK
*
* Description : This is an example of a startup task. As mentioned in the book's text, you MUST
* initialize the ticker only once multitasking has started.
*
* Arguments : p_arg is the argument passed to 'AppTaskStart()' by 'OSTaskCreate()'.
*
* Returns : none
*
* Notes : 1) The first line of code is used to prevent a compiler warning because 'p_arg' is not
* used. The compiler should not generate any code for this statement.
*********************************************************************************************************
*/
static void AppTaskStart (void *p_arg)
{
CPU_INT32U cpu_clk_freq;
CPU_INT32U cnts;
OS_ERR err;
(void)p_arg;
BSP_Init();
CPU_Init(); /* Initialize the uC/CPU services */
cpu_clk_freq = BSP_CPU_ClkFreq(); /* Determine SysTick reference freq. */
cnts = cpu_clk_freq /* Determine nbr SysTick increments */
/ (CPU_INT32U)OSCfg_TickRate_Hz;
OS_CPU_SysTickInit(cnts); /* Init uC/OS periodic time src (SysTick). */
Mem_Init(); /* Initialize memory managment module */
Math_Init(); /* Initialize mathematical module */
#if OS_CFG_STAT_TASK_EN > 0u
OSStatTaskCPUUsageInit(&err); /* Compute CPU capacity with no task running */
#endif
#ifdef CPU_CFG_INT_DIS_MEAS_EN
CPU_IntDisMeasMaxCurReset();
#endif
#if (APP_CFG_SERIAL_EN == DEF_ENABLED)
App_SerialInit(); /* Initialize Serial communication for application ... */
#endif
OSTaskCreate((OS_TCB *)&AppTask1TCB, // 线程TCB
(CPU_CHAR *)APPTASK1NAME, // 线程名字
(OS_TASK_PTR ) AppTask1, // 线程入口函数
(void *) "TASK1", // 线程参数
(OS_PRIO ) APP_TASK1_PRIO, // 线程优先级
(CPU_STK *)&AppTask1Stk[0], // 线程栈起始地址
(CPU_STK_SIZE) APP_TASK1_STK_SIZE / 10, // 栈深度的限制位置
(CPU_STK_SIZE) APP_TASK1_STK_SIZE, // 栈大小
(OS_MSG_QTY ) 5u, // 最大的消息个数
(OS_TICK ) 0u, // 时间片
(void *) 0, // 向用户提供的内存位置的指针
(OS_OPT )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR), // 线程特定选项
(OS_ERR *)&err); // 错误标志
if(OS_ERR_NONE == err)
printf("%s Create Success\r\n",APPTASK1NAME);
else
printf("%s Create Error\r\n",APPTASK1NAME);
OSTaskCreate((OS_TCB *)&AppTask2TCB, // 线程TCB
(CPU_CHAR *)APPTASK2NAME, // 线程名字
(OS_TASK_PTR ) AppTask2, // 线程入口函数
(void *) "TASK2", // 线程参数
(OS_PRIO ) APP_TASK2_PRIO, // 线程优先级
(CPU_STK *)&AppTask2Stk[0], // 线程栈起始地址
(CPU_STK_SIZE) APP_TASK2_STK_SIZE / 10, // 栈深度的限制位置
(CPU_STK_SIZE) APP_TASK2_STK_SIZE, // 栈大小
(OS_MSG_QTY ) 5u, // 最大的消息个数
(OS_TICK ) 0u, // 时间片
(void *) 0, // 向用户提供的内存位置的指针
(OS_OPT )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR), // 线程特定选项
(OS_ERR *)&err); // 错误标志
if(OS_ERR_NONE == err)
printf("%s Create Success\r\n",APPTASK2NAME);
else
printf("%s Create Error\r\n",APPTASK2NAME);
OSTaskDel ( & AppTaskStartTCB, & err );
}
static void AppTask1 (void *p_arg)
{
OS_ERR err;
while(DEF_TRUE)
{
printf("Task1 Runing Task1 Arg %s\r\n",p_arg);
OSTimeDly ( 1000, OS_OPT_TIME_DLY, & err ); // 1s运行一次
}
}
static void AppTask2 (void *p_arg)
{
OS_ERR err;
while(DEF_TRUE)
{
printf("Task1 Runing Task2 Arg %s\r\n",p_arg);
OSTimeDly ( 1000, OS_OPT_TIME_DLY, & err ); // 1s运行一次
}
}