任务的最基本功能是任务管理,任务管理中最基本操作是任务的创建和删除。对于任务的创建和删除,由于篇幅有点长,分两篇分别讲解。在FreeRTOS中任务的创建函数如下:
函数 | 描述 |
---|---|
xTaskCreate() | 使用动态方法创建一个任务 |
xTaskCreateStatic() | 使用静态方法创建一个任务 |
xTaskCreateRestricted() | 创建一个使用MPU进行限制的任务,使用动态内存分配,要求对应的MCU有MPU功能(内存保护单元) |
其它内部调用函数:
函数 | 描述 |
---|---|
prvInitialiseNewTask() | 初始化任务 |
pxPortInitialiseStack() | 初始化堆栈 |
prvAddNewTaskToReadyList() | 若第一次创建任务,将调函数prvInitialiseTaskLists(),初始化各个系统列表,最后调用函数prvAddTaskToReadyList(),将新创建的任务挂接到任务就绪表中 |
prvInitialiseTaskLists() | 初始化任务列表 |
prvAddTaskToReadyList() | 将新创建的任务挂接到任务就绪表中 |
该函数是使用动态方法创建一个任务,每个任务需要RAM来保存任务的状态信息,在FreeRTOS中称为任务控制块。该函数会自动给任务申请任务控制块空间、申请任务的堆栈空间,不需要用户自己定义堆栈和任务控制块储存空间。另外也会将任务添加到任务就绪表中,使创建的任务处于就绪态,供任务调度器调用。使用此函数,必须将configSUPPORT_DYNAMIC_ALLOCATION设置为1,此宏定义在FreeRTOSConfig.h文件中。
函数原型如下:
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,
const char * const pcName,
const uint16_t usStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * const pxCreatedTask )
函数参数说明:
返回值:
函数代码如下:
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,
const char * const pcName,
const uint16_t usStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * const pxCreatedTask )
{
TCB_t *pxNewTCB;
BaseType_t xReturn;
/*
* portSTACK_GROWTH > 0时,表示堆栈向上增长
* portSTACK_GROWTH <=0时,表示堆栈向下增长
* portSTACK_GROWTH 表示栈的生长方向,对于ARM CM4是向下增长的
* portSTACK_GROWTH在portmacro.h中定义,默认为 -1
*/
#if( portSTACK_GROWTH > 0 )
{
/* 堆栈向上增长,为防止堆栈增长到任务控制块,则先为任务控制块申请内存 */
pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );
/* 任务控制块申请内存成功 */
if( pxNewTCB != NULL )
{
/*
* 堆栈申请内存,堆栈的实际内存空间等于(usStackDepth * sizeof( StackType_t ) )
* 其中,StackType_t 为uint32_t类型,即堆栈实际字节数为:usStackDepth * 4
* 直接让任务控制块中堆栈指针指向申请的堆栈入口地址
*/
pxNewTCB->pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) );
/* 如果堆栈内存申请失败 */
if( pxNewTCB->pxStack == NULL )
{
vPortFree( pxNewTCB ); /* 堆栈内存申请失败,释放掉刚刚申请的任务控制块内存 */
pxNewTCB = NULL; /* pxNewTCB 指针指向空地址,因为释放掉了刚刚申请的任务控制块内存 */
}
}
}
#else /* portSTACK_GROWTH */
{
StackType_t *pxStack;
/*
* 堆栈申请内存,堆栈的实际内存空间等于(usStackDepth * sizeof( StackType_t ) )
* 其中,StackType_t 为uint32_t类型,即堆栈实际字节数为:usStackDepth * 4
*/
pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) );
/* 堆栈申请成功 */
if( pxStack != NULL )
{
/* 任务控制块申请内存 */
pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );
/* 任务控制块申请成功 */
if( pxNewTCB != NULL )
{
/* 任务控制块中堆栈指针指向刚刚申请的堆栈入口地址 */
pxNewTCB->pxStack = pxStack;
}
else
{
/* 任务控制块申请失败,则释放掉刚刚申请的堆栈空间 */
vPortFree( pxStack );
}
}
else /* 堆栈申请失败 */
{
pxNewTCB = NULL; /* pxNewTCB 指针指向空地址 */
}
}
#endif /* portSTACK_GROWTH */
/* 如果任务控制块申请内存成功 */
if( pxNewTCB != NULL )
{
#if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )
{
/* 标记任务是用动态的方法申请堆栈和任务控制块。为后面任务删除,自动收回控制块和堆栈内存时提供标记
* 若使用动态方式创建任务,任务控制块和堆栈空间会自动释放,静态方式时需要用户自己释放内存空间
*/
pxNewTCB->ucStaticallyAllocated = tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB;
}
#endif /* configSUPPORT_STATIC_ALLOCATION */
/* 初始化任务 */
prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL );
/* 将新任务插入到就绪列表中 */
prvAddNewTaskToReadyList( pxNewTCB );
xReturn = pdPASS;
}
else /* 任务控制块内存申请失败 */
{
/* 返回堆内存申请失败,返回错误码 */
xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
}
return xReturn;
}
说明:
任务创建函数调用函数prvInitialiseNewTask()来初始化任务,函数原型如下:
static void prvInitialiseNewTask( TaskFunction_t pxTaskCode,
const char * const pcName,
const uint32_t ulStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * const pxCreatedTask,
TCB_t * pxNewTCB,
const MemoryRegion_t * const xRegions )
函数参数说明:
函数返回值:无
函数代码如下:
static void prvInitialiseNewTask(TaskFunction_t pxTaskCode,
const char * const pcName,
const uint32_t ulStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * const pxCreatedTask,
TCB_t * pxNewTCB,
const MemoryRegion_t * const xRegions )
{
StackType_t *pxTopOfStack;
UBaseType_t x;
/* 如果开启了 MPU, 判断任务是否运行在特权模式 */
#if( portUSING_MPU_WRAPPERS == 1 )
BaseType_t xRunPrivileged;
if( ( uxPriority & portPRIVILEGE_BIT ) != 0U )
{
/* 优先级特权模式掩码置位,任务运行在特权模式 */
xRunPrivileged = pdTRUE;
}
else
{
xRunPrivileged = pdFALSE;
}
uxPriority &= ~portPRIVILEGE_BIT;
#endif /* portUSING_MPU_WRAPPERS == 1 */
/* 如果使能了堆栈溢出检测功能或者可视化跟踪调试功能 */
#if( ( configCHECK_FOR_STACK_OVERFLOW > 1 ) || ( configUSE_TRACE_FACILITY == 1 ) || ( INCLUDE_uxTaskGetStackHighWaterMark == 1 ) )
{
/* 将申请的堆栈用tskSTACK_FILL_BYTE填充 ,其中tskSTACK_FILL_BYTE默认为 0xa5U */
( void ) memset( pxNewTCB->pxStack, ( int ) tskSTACK_FILL_BYTE, ( size_t ) ulStackDepth * sizeof( StackType_t ) );
}
#endif /* ( ( configCHECK_FOR_STACK_OVERFLOW > 1 ) || ( ( configUSE_TRACE_FACILITY == 1 ) || ( INCLUDE_uxTaskGetStackHighWaterMark == 1 ) ) ) */
/* 如果堆栈向下增长 */
#if( portSTACK_GROWTH < 0 )
{
/*
* 向下增长,计算堆栈栈顶,栈顶在内存的高位
* pxNewTCB->pxStack固定为申请的堆栈起始位置,即对于向下增长的堆栈需要计算堆栈栈顶
* pxTopOfStack随着堆栈的分配不断变化。
* pxNewTCB->pxStack为uint32_t类型的指针,若指针加1,移动4个地址位
*/
pxTopOfStack = pxNewTCB->pxStack + ( ulStackDepth - ( uint32_t ) 1 );
/*
* 8字节对齐
* 为了后续兼容浮点(64位)运行,向下8字节对齐
* 假如pxTopOfStack是36,能被4整除,但不能整除8,进行向下8字节对齐就是32,那么就会空出4个字节不使用
*/
pxTopOfStack = ( StackType_t * ) ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) );
/* (断言) 检查申请的堆栈是否符合8字节对齐 */
configASSERT( ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack & ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) == 0UL ) );
}
#else /* portSTACK_GROWTH */
{
/* 向上增长的堆栈,计算堆栈栈顶,栈顶在内存的低位 */
pxTopOfStack = pxNewTCB->pxStack;
/* (断言) 检查申请的堆栈是否符合8字节对齐 */
configASSERT( ( ( ( portPOINTER_SIZE_TYPE ) pxNewTCB->pxStack & ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) == 0UL ) );
/* 对于堆栈向上增长,设置上边界,用于检验堆栈是否溢出 */
pxNewTCB->pxEndOfStack = pxNewTCB->pxStack + ( ulStackDepth - ( uint32_t ) 1 );
}
#endif /* portSTACK_GROWTH */
/* 将任务名字保存到任务控制块中,方便调试 */
for( x = ( UBaseType_t ) 0; x < ( UBaseType_t ) configMAX_TASK_NAME_LEN; x++ )
{
pxNewTCB->pcTaskName[ x ] = pcName[ x ];
/* 如果字符为0x00,跳出for()循环 */
if( pcName[ x ] == 0x00 )
{
break;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
/* 确保TCB中任务名字的最后一个字节为'\0',可以看出任务名字超出范围,将会无用 */
pxNewTCB->pcTaskName[ configMAX_TASK_NAME_LEN - 1 ] = '\0';
/* 如果任务优先级大于或等于设置的最大优先级,限制任务优先级 */
if( uxPriority >= ( UBaseType_t ) configMAX_PRIORITIES )
{
uxPriority = ( UBaseType_t ) configMAX_PRIORITIES - ( UBaseType_t ) 1U;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
pxNewTCB->uxPriority = uxPriority; /* 任务优先级保存到任务控制块中 */
/* 如果使用任务互斥量信号 */
#if ( configUSE_MUTEXES == 1 )
{
pxNewTCB->uxBasePriority = uxPriority;
pxNewTCB->uxMutexesHeld = 0;
}
#endif /* configUSE_MUTEXES */
vListInitialiseItem( &( pxNewTCB->xStateListItem ) ); /* 初始化任务状态列表项 */
vListInitialiseItem( &( pxNewTCB->xEventListItem ) ); /* 初始化任务事件列表项 */
/*
* 将任务状态列表项的 pvOwner 指向所属的TCB
* 如任务切换到运行态,系统从就绪列表取出这一项,获得TCB(ListItem->pvOwner),切换到运行状态
*/
listSET_LIST_ITEM_OWNER( &( pxNewTCB->xStateListItem ), pxNewTCB );
/*
* 给任务事件列表项中成员xItemValue赋项值,等于configMAX_PRIORITIES - uxPriority。
* 该值用于在对应事件列表中排序,按照升序排序,写入优先级的 “补数”,因此优先级越高越靠前
* 即保证优先级高的任务,在列表中越靠前。
*/
listSET_LIST_ITEM_VALUE( &( pxNewTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) uxPriority );
/* 将事件列表项的 pvOwner 指向所属的TCB */
listSET_LIST_ITEM_OWNER( &( pxNewTCB->xEventListItem ), pxNewTCB );
/* 初始化嵌套值为0 */
#if ( portCRITICAL_NESTING_IN_TCB == 1 )
{
pxNewTCB->uxCriticalNesting = ( UBaseType_t ) 0U;
}
#endif /* portCRITICAL_NESTING_IN_TCB */
/* 如果使能任务标签功能 */
#if ( configUSE_APPLICATION_TASK_TAG == 1 )
{
pxNewTCB->pxTaskTag = NULL;
}
#endif /* configUSE_APPLICATION_TASK_TAG */
/* 如果使能事件统计功能 */
#if ( configGENERATE_RUN_TIME_STATS == 1 )
{
pxNewTCB->ulRunTimeCounter = 0UL;
}
#endif /* configGENERATE_RUN_TIME_STATS */
/* 如果使用MPU功能 */
#if ( portUSING_MPU_WRAPPERS == 1 )
{
/* 设置 MPU,任务内存访问权限设置 */
vPortStoreTaskMPUSettings( &( pxNewTCB->xMPUSettings ), xRegions, pxNewTCB->pxStack, ulStackDepth );
}
#else
{
/* 避免编译报 warning 没有使用变量 */
( void ) xRegions;
}
#endif
/* 初始化任务局部数据指针 */
#if( configNUM_THREAD_LOCAL_STORAGE_POINTERS != 0 )
{
for( x = 0; x < ( UBaseType_t ) configNUM_THREAD_LOCAL_STORAGE_POINTERS; x++ )
{
pxNewTCB->pvThreadLocalStoragePointers[ x ] = NULL;
}
}
#endif
/* 如果使用了任务通知功能,初始化任务消息通知变量 */
#if ( configUSE_TASK_NOTIFICATIONS == 1 )
{
pxNewTCB->ulNotifiedValue = 0;
pxNewTCB->ucNotifyState = taskNOT_WAITING_NOTIFICATION;
}
#endif
/* 如果使用Newlib */
#if ( configUSE_NEWLIB_REENTRANT == 1 )
{
_REENT_INIT_PTR( ( &( pxNewTCB->xNewLib_reent ) ) );
}
#endif
#if( INCLUDE_xTaskAbortDelay == 1 )
{
pxNewTCB->ucDelayAborted = pdFALSE;
}
#endif
/*
* 初始化栈空间
* 执行一次该函数,相当于被调度器中断切换其它任务,而原函数入栈做了现场保护
* 当任务再次被调度器取出后,可以直接执行出栈恢复现场,运行任务
*/
#if( portUSING_MPU_WRAPPERS == 1 )
{
pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters, xRunPrivileged );
}
#else /* portUSING_MPU_WRAPPERS */
{
pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters );
}
#endif /* portUSING_MPU_WRAPPERS */
if( ( void * ) pxCreatedTask != NULL )
{
/* 让任务句柄指向任务控制块,可用于修改优先级,通知或者删除任务等 */
*pxCreatedTask = ( TaskHandle_t ) pxNewTCB;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
调用函数pxPortInitialiseStack()初始化任务堆栈,并将最新的栈顶指针赋值给任务TCB的pxTopOfStack。调用一次该函数,相当于执行了一次系统节拍中断,将一些重要的寄存器入栈。当任务需要运行时, 可以直接执行出栈恢复现场,运行任务。对于不同的硬件架构,其入栈的寄存器不同。
函数的原型如下:
StackType_t *pxPortInitialiseStack( StackType_t *pxTopOfStack,
TaskFunction_t pxCode,
void *pvParameters )
函数参数说明:
返回值: 当前堆栈的栈顶地址
函数代码如下:
StackType_t *pxPortInitialiseStack( StackType_t *pxTopOfStack, TaskFunction_t pxCode, void *pvParameters )
{
pxTopOfStack--; /* 栈顶地址减1,相当于向下移动4个地址 */
*pxTopOfStack = portINITIAL_XPSR; /* xPSR = 0x01000000,其中bit24被置1,表示使用Thumb指令*/
pxTopOfStack--;
*pxTopOfStack = ( ( StackType_t ) pxCode ) & portSTART_ADDRESS_MASK; /* 任务函数指针 */
pxTopOfStack--;
*pxTopOfStack = ( StackType_t ) prvTaskExitError; /* LR为函数指针prvTaskExitError,由移植层提供的一个出错处理函数*/
pxTopOfStack -= 5; /* R12, R3, R2 and R1. */
*pxTopOfStack = ( StackType_t ) pvParameters; /* R0,此寄存器为传递参数 */
pxTopOfStack -= 8; /* R11, R10, R9, R8, R7, R6, R5 and R4. */
return pxTopOfStack;
}
初始化后任务的堆栈如下:
该函数是将任务添加到任务就绪表中,供任务调度器调用。函数原型如下:
/********************************************************
参数:pxNewTCB:新建任务的TCB指针
返回:无
*********************************************************/
static void prvAddNewTaskToReadyList(TCB_t *pxNewTCB )
函数代码如下:
static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB )
{
/* 开启临界区保护, 关闭中断(平台相关,移植层实现)*/
taskENTER_CRITICAL();
{
uxCurrentNumberOfTasks++; /* 当前总任务数加1 */
if( pxCurrentTCB == NULL )
{
pxCurrentTCB = pxNewTCB; /* 如当前没有运行态任务,设置新任务为运行态 */
/* 如果当前任务为新创建的第一个任务 */
if( uxCurrentNumberOfTasks == ( UBaseType_t ) 1 )
{
prvInitialiseTaskLists(); /* 初始化各个列表 */
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
/* 如果调度器没有运行 */
if( xSchedulerRunning == pdFALSE )
{
/* 新任务优先级比当前运行的任务优先级更高 */
if( pxCurrentTCB->uxPriority <= pxNewTCB->uxPriority )
{
pxCurrentTCB = pxNewTCB; /* 设置新任务为当前运行任务 */
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
uxTaskNumber++; /* 记录创建任务数*/
/* 如果使用调试追踪功能 */
#if ( configUSE_TRACE_FACILITY == 1 )
{
pxNewTCB->uxTCBNumber = uxTaskNumber;
}
#endif /* configUSE_TRACE_FACILITY */
traceTASK_CREATE( pxNewTCB );
/* 将任务插入就绪列表中 */
prvAddTaskToReadyList( pxNewTCB );
/* 使用宏定义实现,标记任务就绪表中相关位 */
portSETUP_TCB( pxNewTCB );
}
taskEXIT_CRITICAL(); /* 退出边界,恢复中断 */
/* 如果调度器已经启动 */
if( xSchedulerRunning != pdFALSE )
{
/* 新任务优先级比正在运行的任务高 */
if( pxCurrentTCB->uxPriority < pxNewTCB->uxPriority )
{
taskYIELD_IF_USING_PREEMPTION(); /* 系统执行任务切换 */
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
创建第一个任务时执行该函数,主要功能是初始化所有的内核列表
函数代码如下:
static void prvInitialiseTaskLists( void )
{
UBaseType_t uxPriority;
/* 初始化所有的就绪列表 */
for( uxPriority = ( UBaseType_t ) 0U; uxPriority < ( UBaseType_t ) configMAX_PRIORITIES; uxPriority++ )
{
vListInitialise( &( pxReadyTasksLists[ uxPriority ] ) );
}
/* 初始化延时列表 */
vListInitialise( &xDelayedTaskList1 );
vListInitialise( &xDelayedTaskList2 );
/*
* 在调度器被挂起时,如果一个任务从非就绪状态变为就绪状态,它不直接加到就绪链表中,而是加到该列表中。
* 等调度器重新启动时再检查这个链表,把里面的任务加到就绪链表中
* /
vListInitialise( &xPendingReadyList );
/* 初始化任务等待删除列表 */
#if ( INCLUDE_vTaskDelete == 1 )
{
vListInitialise( &xTasksWaitingTermination );
}
#endif /* INCLUDE_vTaskDelete */
#if ( INCLUDE_vTaskSuspend == 1 )
{
vListInitialise( &xSuspendedTaskList ); /* 初始化任务挂起列表 */
}
#endif /* INCLUDE_vTaskSuspend */
/* 延时列表相关操作 */
pxDelayedTaskList = &xDelayedTaskList1;
pxOverflowDelayedTaskList = &xDelayedTaskList2;
}
该函数是宏定义,主要功能是标记任务就绪表中相关位,uxTopReadyPriority是32位的数据,每一位表示一个优先级是否存在任务函数,如:第8位置1,表示优先级为8的就绪表中存在任务。同时,该函数也将任务的状态列表项插入到对应的任务就绪表末尾项中,如下:
#define prvAddTaskToReadyList( pxTCB ) \
traceMOVED_TASK_TO_READY_STATE( pxTCB ); \
taskRECORD_READY_PRIORITY( ( pxTCB )->uxPriority );\
vListInsertEnd( &( pxReadyTasksLists[ ( pxTCB )->uxPriority ] ), &( ( pxTCB )->xStateListItem ) );\
tracePOST_MOVED_TASK_TO_READY_STATE( pxTCB )
其中
/* 记任务就绪表中相关位 */
#define taskRECORD_READY_PRIORITY( uxPriority ) portRECORD_READY_PRIORITY( uxPriority, uxTopReadyPriority )
#define portRECORD_READY_PRIORITY( uxPriority, uxReadyPriorities ) ( uxReadyPriorities ) |= ( 1UL << ( uxPriority ) )
使用静态方式创建任务时需要使将宏 configSUPPORT_STATIC_ALLOCATION设置为 1,即使用静态内存。
静态创建任务函数原型如下:
TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode,
const char * const pcName,
const uint32_t ulStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
StackType_t * const puxStackBuffer,
StaticTask_t * const pxTaskBuffer )
动态创建任务函数原型如下:
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,
const char * const pcName,
const uint16_t usStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * const pxCreatedTask )
说明:
使用静态方式创建任务时,需要给空闲任务和定时器的任务堆栈和任务控制块分配内存,使用vApplicationGetIdleTaskMemory()和 ApplicationGetTimerTaskMemory()进行实现,如下:
#define configMINIMAL_STACK_SIZE ((unsigned short)130) /* 空闲任务使用的堆栈大小 */
#define configTIMER_TASK_STACK_DEPTH (configMINIMAL_STACK_SIZE*2) /* 软件定时器任务堆栈大小 */
/* 空闲任务 */
static StackType_t IdleTaskStack[configMINIMAL_STACK_SIZE];
static StaticTask_t IdleTaskTCB;
/* 定时器任务 */
static StackType_t TimerTaskStack[configTIMER_TASK_STACK_DEPTH];
static StaticTask_t TimerTaskTCB;
/* 空闲任务所需内存 */
void vApplicationGetIdleTaskMemory( StaticTask_t **ppxIdleTaskTCBBuffer,
StackType_t **ppxIdleTaskStackBuffer,
uint32_t *pulIdleTaskStackSize )
{
*ppxIdleTaskTCBBuffer=&IdleTaskTCB;
*ppxIdleTaskStackBuffer=IdleTaskStack;
*pulIdleTaskStackSize=configMINIMAL_STACK_SIZE;
}
/* 定时器任务所需内存 */
void vApplicationGetTimerTaskMemory( StaticTask_t **ppxTimerTaskTCBBuffer,
StackType_t **ppxTimerTaskStackBuffer,
uint32_t *pulTimerTaskStackSize )
{
*ppxTimerTaskTCBBuffer=&TimerTaskTCB;
*ppxTimerTaskStackBuffer=TimerTaskStack;
*pulTimerTaskStackSize=configMINIMAL_STACK_SIZE;
}
【1】: 正点原子:《STM32F407 FreeRTOS开发手册V1.1》
【2】: 野火:《FreeRTOS 内核实现与应用开发实战指南》
【3】: 《Cortex M3权威指南(中文)》