比较常见的启动流程有两种,一种是在main函数中完成硬件和RTOS系统的初始化,并且创建所需的任务,最后只需要开启调度器即可。还有一种是在main函数中将硬件和RTOS初始化,然后创建一个启动任务,在启动任务中完成其余任务的创建。
很明显UCOS属于后者,下面我们来分析一下UCOS是如何启动的。
主函数
/**
* @brief The application entry point.
* @retval int
*/
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 *)0,
(OS_PRIO)APP_TASK_START_PRIO,
(CPU_STK *)&AppTaskStartStk[0],
(CPU_STK_SIZE)APP_TASK_START_STK_SIZE / 10,
(CPU_STK_SIZE)APP_TASK_START_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);
OSStart(&err);
}
简单说一下,可以看到首先创建一个变量,类型是OS_ERR,如果找到定义的话,可以发现,OS_ERR是一个枚举值,里面包括各种错误的类型可以方便检查错误。
紧接着就是OSIinit()函数,用于初始化uC/OS-III系统,这里面初始化了空闲任务和时钟节拍任务,同时给一些变量赋初值。下面是使用OSTaskCreate函数
创建启动任务,然后调用OSStart启动调度器。
void AppTaskStart(void *p_arg)
{
CPU_INT32U cpu_clk_freq;
CPU_INT32U cnts;
OS_ERR err;
(void)p_arg;
CPU_Init();
BSP_Init(); /* Initialize BSP functions */
cpu_clk_freq = BSP_CPU_ClkFreq(); /* Determine SysTick reference freq. */
cnts = cpu_clk_freq / (CPU_INT32U)OSCfg_TickRate_Hz; /* Determine nbr SysTick increments */
OS_CPU_SysTickInit(cnts); /* Init uC/OS periodic time src (SysTick). */
Mem_Init(); /* Initialize Memory Management 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
while (DEF_TRUE)
{ /* Task body, always written as an infinite loop. */
HAL_GPIO_TogglePin(GPIOF, GPIO_PIN_9);
OSTimeDly(200, OS_OPT_TIME_DLY, &err);
}
}
CPU_Init()是对CPU的初始化,里面包含一些时间戳的初始化、最大关中断时间的测量初始化、CPU名字的初始化。
BSP_Init()是板级初始化,对硬件外设的初始化
紧接着下面三段是对滴答定时器的初始化,通常是每1ms中断一次,为操作系统提供时基。可以修改
截至到上面这些,操作系统启动就没有问题了。
OSInit函数
void OSInit (OS_ERR *p_err)
{
CPU_STK *p_stk;
CPU_STK_SIZE size;
#ifdef OS_SAFETY_CRITICAL
if (p_err == (OS_ERR *)0) {
OS_SAFETY_CRITICAL_EXCEPTION();
return;
}
#endif
OSInitHook(); /* Call port specific initialization code */
OSIntNestingCtr = (OS_NESTING_CTR)0; /* Clear the interrupt nesting counter */
OSRunning = OS_STATE_OS_STOPPED; /* Indicate that multitasking not started */
OSSchedLockNestingCtr = (OS_NESTING_CTR)0; /* Clear the scheduling lock counter */
OSTCBCurPtr = (OS_TCB *)0; /* Initialize OS_TCB pointers to a known state */
OSTCBHighRdyPtr = (OS_TCB *)0;
OSPrioCur = (OS_PRIO)0; /* Initialize priority variables to a known state */
OSPrioHighRdy = (OS_PRIO)0;
OSPrioSaved = (OS_PRIO)0;
#if OS_CFG_SCHED_LOCK_TIME_MEAS_EN > 0u
OSSchedLockTimeBegin = (CPU_TS)0;
OSSchedLockTimeMax = (CPU_TS)0;
OSSchedLockTimeMaxCur = (CPU_TS)0;
#endif
#ifdef OS_SAFETY_CRITICAL_IEC61508
OSSafetyCriticalStartFlag = DEF_FALSE;
#endif
#if OS_CFG_SCHED_ROUND_ROBIN_EN > 0u
OSSchedRoundRobinEn = DEF_FALSE;
OSSchedRoundRobinDfltTimeQuanta = OSCfg_TickRate_Hz / 10u;
#endif
if (OSCfg_ISRStkSize > (CPU_STK_SIZE)0) {
p_stk = OSCfg_ISRStkBasePtr; /* Clear exception stack for stack checking. */
if (p_stk != (CPU_STK *)0) {
size = OSCfg_ISRStkSize;
while (size > (CPU_STK_SIZE)0) {
size--;
*p_stk = (CPU_STK)0;
p_stk++;
}
}
}
#if OS_CFG_APP_HOOKS_EN > 0u
OS_AppTaskCreateHookPtr = (OS_APP_HOOK_TCB )0; /* Clear application hook pointers */
OS_AppTaskDelHookPtr = (OS_APP_HOOK_TCB )0;
OS_AppTaskReturnHookPtr = (OS_APP_HOOK_TCB )0;
OS_AppIdleTaskHookPtr = (OS_APP_HOOK_VOID)0;
OS_AppStatTaskHookPtr = (OS_APP_HOOK_VOID)0;
OS_AppTaskSwHookPtr = (OS_APP_HOOK_VOID)0;
OS_AppTimeTickHookPtr = (OS_APP_HOOK_VOID)0;
#endif
#if OS_CFG_TASK_REG_TBL_SIZE > 0u
OSTaskRegNextAvailID = (OS_REG_ID)0;
#endif
OS_PrioInit(); /* Initialize the priority bitmap table */
OS_RdyListInit(); /* Initialize the Ready List */
#if OS_CFG_FLAG_EN > 0u /* Initialize the Event Flag module */
OS_FlagInit(p_err);
if (*p_err != OS_ERR_NONE) {
return;
}
#endif
#if OS_CFG_MEM_EN > 0u /* Initialize the Memory Manager module */
OS_MemInit(p_err);
if (*p_err != OS_ERR_NONE) {
return;
}
#endif
#if (OS_MSG_EN) > 0u /* Initialize the free list of OS_MSGs */
OS_MsgPoolInit(p_err);
if (*p_err != OS_ERR_NONE) {
return;
}
#endif
#if OS_CFG_MUTEX_EN > 0u /* Initialize the Mutex Manager module */
OS_MutexInit(p_err);
if (*p_err != OS_ERR_NONE) {
return;
}
#endif
#if OS_CFG_Q_EN > 0u
OS_QInit(p_err); /* Initialize the Message Queue Manager module */
if (*p_err != OS_ERR_NONE) {
return;
}
#endif
#if OS_CFG_SEM_EN > 0u /* Initialize the Semaphore Manager module */
OS_SemInit(p_err);
if (*p_err != OS_ERR_NONE) {
return;
}
#endif
#if defined(OS_CFG_TLS_TBL_SIZE) && (OS_CFG_TLS_TBL_SIZE > 0u)
OS_TLS_Init(p_err); /* Initialize Task Local Storage, before creating tasks */
if (*p_err != OS_ERR_NONE) {
return;
}
#endif
OS_TaskInit(p_err); /* Initialize the task manager */
if (*p_err != OS_ERR_NONE) {
return;
}
#if OS_CFG_ISR_POST_DEFERRED_EN > 0u
OS_IntQTaskInit(p_err); /* Initialize the Interrupt Queue Handler Task */
if (*p_err != OS_ERR_NONE) {
return;
}
#endif
OS_IdleTaskInit(p_err); /* Initialize the Idle Task */
if (*p_err != OS_ERR_NONE) {
return;
}
OS_TickTaskInit(p_err); /* Initialize the Tick Task */
if (*p_err != OS_ERR_NONE) {
return;
}
#if OS_CFG_STAT_TASK_EN > 0u /* Initialize the Statistic Task */
OS_StatTaskInit(p_err);
if (*p_err != OS_ERR_NONE) {
return;
}
#endif
#if OS_CFG_TMR_EN > 0u /* Initialize the Timer Manager module */
OS_TmrInit(p_err);
if (*p_err != OS_ERR_NONE) {
return;
}
#endif
#if OS_CFG_DBG_EN > 0u
OS_Dbg_Init();
#endif
OSCfg_Init();
}
很多初始化面都有这一段话,应该是调试作用,个人猜测。
这里是对钩子函数的初始化,还有一些变量赋初值。
OSIntNestingCtr代表中断嵌套数
OSRunning代表OS的运行状态
OSSchedLockNestingCtr表示调度器上锁计数
OSTCBCurPtr指向当前运行的任务控制块
OSTCBHighRdyPtr指向优先级最高的就绪任务的任务控制块
OSPrioCur当前运行任务的优先级
OSPrioHighRdy表示就绪任务的最高优先级
OSPrioSaved用于保存任务的优先级
这一段话用于清除µC/OS-III’sinterrupt stack。
这一段话用于清除钩子函数指针,默认关闭所有动作的钩子函数。
OS_CFG_TASK_REG_TBL_SIZE用于定义任务寄存器的数组的大小,UCOS分配给任务一个寄存器,用于存储数据,根本上是一个无符号32位整型数组。
OSTaskRegNextAvailID用于存储下一个可用的任务寄存器号,每获取一次,加一。
OS_PrioInit()初始化优先级列表,这里要注意,每一位代表一个优先级,64个优先级数组大小只需要为2。
OS_RdyListInit()初始化就绪列表
其余都是初始化各部分功能,相似程度非常高,大多都是将数量归零。但是好包括空闲任务和时钟节拍任务,这两个比较特殊,是系统自带的两个任务。
空闲任务创建
/*$PAGE*/
/*
************************************************************************************************************************
* INITIALIZE THE IDLE TASK
*
* Description: This function initializes the idle task
*
* Arguments : p_err is a pointer to a variable that will contain an error code returned by this function.
*
* Returns : none
*
* Note(s) : 1) This function is INTERNAL to uC/OS-III and your application MUST NOT call it.
************************************************************************************************************************
*/
void OS_IdleTaskInit (OS_ERR *p_err)
{
#ifdef OS_SAFETY_CRITICAL
if (p_err == (OS_ERR *)0) {
OS_SAFETY_CRITICAL_EXCEPTION();
return;
}
#endif
OSIdleTaskCtr = (OS_IDLE_CTR)0;
/* ---------------- CREATE THE IDLE TASK ---------------- */
OSTaskCreate((OS_TCB *)&OSIdleTaskTCB,
(CPU_CHAR *)((void *)"uC/OS-III Idle Task"),
(OS_TASK_PTR)OS_IdleTask,
(void *)0,
(OS_PRIO )(OS_CFG_PRIO_MAX - 1u),
(CPU_STK *)OSCfg_IdleTaskStkBasePtr,
(CPU_STK_SIZE)OSCfg_IdleTaskStkLimit,
(CPU_STK_SIZE)OSCfg_IdleTaskStkSize,
(OS_MSG_QTY )0u,
(OS_TICK )0u,
(void *)0,
(OS_OPT )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR | OS_OPT_TASK_NO_TLS),
(OS_ERR *)p_err);
}
初始化空闲任务,空闲任务的作用比较大,CPU在某一时刻没有任务去执行时,便会执行空闲任务,实际上就是去累加一个变量,所以说任务优先级最低。首先对累加变量进行清零,接着创建任务。
时钟节拍任务
void OS_TickTaskInit (OS_ERR *p_err)
{
#ifdef OS_SAFETY_CRITICAL
if (p_err == (OS_ERR *)0) {
OS_SAFETY_CRITICAL_EXCEPTION();
return;
}
#endif
OSTickCtr = (OS_TICK)0u; /* Clear the tick counter */
OSTickListDly.TCB_Ptr = (OS_TCB *)0;
OSTickListTimeout.TCB_Ptr = (OS_TCB *)0;
#if OS_CFG_DBG_EN > 0u
OSTickListDly.NbrEntries = (OS_OBJ_QTY)0;
OSTickListDly.NbrUpdated = (OS_OBJ_QTY)0;
OSTickListTimeout.NbrEntries = (OS_OBJ_QTY)0;
OSTickListTimeout.NbrUpdated = (OS_OBJ_QTY)0;
#endif
/* ---------------- CREATE THE TICK TASK ----------- */
if (OSCfg_TickTaskStkBasePtr == (CPU_STK *)0) {
*p_err = OS_ERR_TICK_STK_INVALID;
return;
}
if (OSCfg_TickTaskStkSize < OSCfg_StkSizeMin) {
*p_err = OS_ERR_TICK_STK_SIZE_INVALID;
return;
}
if (OSCfg_TickTaskPrio >= (OS_CFG_PRIO_MAX - 1u)) { /* Only one task at the 'Idle Task' priority */
*p_err = OS_ERR_TICK_PRIO_INVALID;
return;
}
OSTaskCreate((OS_TCB *)&OSTickTaskTCB,
(CPU_CHAR *)((void *)"uC/OS-III Tick Task"),
(OS_TASK_PTR )OS_TickTask,
(void *)0,
(OS_PRIO )OSCfg_TickTaskPrio,
(CPU_STK *)OSCfg_TickTaskStkBasePtr,
(CPU_STK_SIZE)OSCfg_TickTaskStkLimit,
(CPU_STK_SIZE)OSCfg_TickTaskStkSize,
(OS_MSG_QTY )0u,
(OS_TICK )0u,
(void *)0,
(OS_OPT )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR | OS_OPT_TASK_NO_TLS),
(OS_ERR *)p_err);
}
时钟节拍任务相对来说比较复杂,
OSTickCtr表示累加变量,初始化时清零。
这两句话和延时有关,实际上UCOS上面的每个延时任务都会添加到这个列表里面,超时的也有一个列表。在每个时钟周期,都会维护这两个列表。
TCB_Ptr是一个指针,指向列表。
这一段用于记录列表中任务的数量,DEBUG时才会用到。
这段话很简单。就是创建任务。