本文是《ALIENTEK STM32F429 FreeRTOS 开发教程》第八章学习笔记-2
第一章笔记–FreeRTOS简介与源码下载
第二章笔记–FreeRTOS在STM32F4上移植
第三章笔记-FreeRTOS系统配置
第四章笔记-FreeRTOS中断分析
第四章笔记补充-FreeRTOS临界段代码
第五章笔记-FreeRTOS任务基础
第六章笔记-FreeRTOS任务API函数的使用
第七章笔记-FreeRTOS列表和列表项
第八章笔记-1-FreeRTOS任务创建
先是创建啊一个开始任务,之后紧接调用函数vTaskStartScheduler()开启任务调度器
void vTaskStartScheduler( void )
{
BaseType_t xReturn;
/* Add the idle task at the lowest priority. */
#if( configSUPPORT_STATIC_ALLOCATION == 1 )
{
StaticTask_t *pxIdleTaskTCBBuffer = NULL;
StackType_t *pxIdleTaskStackBuffer = NULL;
uint32_t ulIdleTaskStackSize;
/* The Idle task is created using user provided RAM - obtain the
address of the RAM then create the idle task. */
vApplicationGetIdleTaskMemory( &pxIdleTaskTCBBuffer, &pxIdleTaskStackBuffer, &ulIdleTaskStackSize );
xIdleTaskHandle = xTaskCreateStatic( prvIdleTask,
"IDLE",
ulIdleTaskStackSize,
( void * ) NULL,
( tskIDLE_PRIORITY | portPRIVILEGE_BIT ),
pxIdleTaskStackBuffer,
pxIdleTaskTCBBuffer ); /*lint !e961 MISRA exception, justified as it is not a redundant explicit cast to all supported compilers. */
if( xIdleTaskHandle != NULL )
{
xReturn = pdPASS;
}
else
{
xReturn = pdFAIL;
}
}
#else
{
/* The Idle task is being created using dynamically allocated RAM. */
xReturn = xTaskCreate( prvIdleTask,
"IDLE", configMINIMAL_STACK_SIZE,
( void * ) NULL,
( tskIDLE_PRIORITY | portPRIVILEGE_BIT ),
&xIdleTaskHandle ); /*lint !e961 MISRA exception, justified as it is not a redundant explicit cast to all supported compilers. */
}
#endif /* configSUPPORT_STATIC_ALLOCATION */
#if ( configUSE_TIMERS == 1 )
{
if( xReturn == pdPASS )
{
xReturn = xTimerCreateTimerTask();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_TIMERS */
if( xReturn == pdPASS )
{
/* Interrupts are turned off here, to ensure a tick does not occur
before or during the call to xPortStartScheduler(). The stacks of
the created tasks contain a status word with interrupts switched on
so interrupts will automatically get re-enabled when the first task
starts to run. */
portDISABLE_INTERRUPTS();
#if ( configUSE_NEWLIB_REENTRANT == 1 )
{
/* Switch Newlib's _impure_ptr variable to point to the _reent
structure specific to the task that will run first. */
_impure_ptr = &( pxCurrentTCB->xNewLib_reent );
}
#endif /* configUSE_NEWLIB_REENTRANT */
xNextTaskUnblockTime = portMAX_DELAY;
xSchedulerRunning = pdTRUE;
xTickCount = ( TickType_t ) 0U;
/* If configGENERATE_RUN_TIME_STATS is defined then the following
macro must be defined to configure the timer/counter used to generate
the run time counter time base. */
portCONFIGURE_TIMER_FOR_RUN_TIME_STATS();
/* Setting up the timer tick is hardware specific and thus in the
portable interface. */
if( xPortStartScheduler() != pdFALSE )
{
/* Should not reach here as if the scheduler is running the
function will not return. */
}
else
{
/* Should only reach here if a task calls xTaskEndScheduler(). */
}
}
else
{
/* This line will only be reached if the kernel could not be started,
because there was not enough FreeRTOS heap to create the idle task
or the timer task. */
configASSERT( xReturn != errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY );
}
/* Prevent compiler warnings if INCLUDE_xTaskGetIdleTaskHandle is set to 0,
meaning xIdleTaskHandle is not used anywhere else. */
( void ) xIdleTaskHandle;
}
if( configSUPPORT_STATIC_ALLOCATION == 1 ){……}:创建空闲任务,如果使用静态内存使用函数xTaskCreateStatic()来创建空闲任务,优先级为(tskIDLE_PRIORITY | portPRIVILEGE_BIT),宏tskIDLE_PRIORITY和portPRIVILEGE_BIT都为0,即说明空闲任务的优先级最低
else { xReturn = xTaskCreate(…… #endif: 此时使用动态内存使用函数xTaskCreate()来创建任务
if(configUSE_TIMES==1){…..} #endif : 如果使用软件定时则通过函数xTimerCreateTimerTask()来创建定时器服务任务。定时器服务任务主要代码:
xReturn = xTaskCreate( prvTimerTask,
"Tmr Svc",
configTIMER_TASK_STACK_DEPTH,
NULL,
( ( UBaseType_t ) configTIMER_TASK_PRIORITY ) | portPRIVILEGE_BIT,
&xTimerTaskHandle );
if(xReturn==pdPASS)portDISABLE_INTERRUPTS(): 如果空闲任务和定时器任务创建成功关闭中断。
if (configUSE_NEWLIB_REENTRANT == 1) …… #endif: 如果配置了相关宏则使能NEWLIB
xNextTaskUnblockTime = portMAX_DELAY;xSchedulerRunning = pdTRUE;xTickCount = ( TickType_t ) 0U; :将相关全局变量进行设置,当变量xSchedulerRunning设置为pdTRUE,表示调度器开始运行
portCONFIGURE_TIMER_FOR_RUN_TIME_STATS():当宏configGENERATE_RUN_TIME_STATS为1时则需要使能时间统计功能,需要配置一个定时器/计数器,在这里不使能此宏
if(xPortStartScheduler() != pdFALSE)…else{}:调用函数xPortStartScheduler()来初始化跟调度器启动有关的硬件(滴答定时器,FPU单元和PendSV中断等等) 如果调度器启动成功,不会运行到if和else里面,因为函数没有返回值
else{configASSERT( xReturn != errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY );}: 程序运行到这里说明系统内核没有启动成功,原因是在创建空闲任务或者定时器任务的时候没有足够的内存,此时用断言函数进行报错
之前在调度器开启函数中调用了函数xPortStartScheduler()来初始化跟调度器启动有关的硬件,
BaseType_t xPortStartScheduler( void )
{
/* configMAX_SYSCALL_INTERRUPT_PRIORITY must not be set to 0.
See http://www.FreeRTOS.org/RTOS-Cortex-M3-M4.html */
configASSERT( configMAX_SYSCALL_INTERRUPT_PRIORITY );
/* This port can be used on all revisions of the Cortex-M7 core other than
the r0p1 parts. r0p1 parts should use the port from the
/source/portable/GCC/ARM_CM7/r0p1 directory. */
configASSERT( portCPUID != portCORTEX_M7_r0p1_ID );
configASSERT( portCPUID != portCORTEX_M7_r0p0_ID );
#if( configASSERT_DEFINED == 1 )
{
volatile uint32_t ulOriginalPriority;
volatile uint8_t * const pucFirstUserPriorityRegister = ( uint8_t * ) ( portNVIC_IP_REGISTERS_OFFSET_16 + portFIRST_USER_INTERRUPT_NUMBER );
volatile uint8_t ucMaxPriorityValue;
/* Determine the maximum priority from which ISR safe FreeRTOS API
functions can be called. ISR safe functions are those that end in
"FromISR". FreeRTOS maintains separate thread and ISR API functions to
ensure interrupt entry is as fast and simple as possible.
Save the interrupt priority value that is about to be clobbered. */
ulOriginalPriority = *pucFirstUserPriorityRegister;
/* Determine the number of priority bits available. First write to all
possible bits. */
*pucFirstUserPriorityRegister = portMAX_8_BIT_VALUE;
/* Read the value back to see how many bits stuck. */
ucMaxPriorityValue = *pucFirstUserPriorityRegister;
/* The kernel interrupt priority should be set to the lowest
priority. */
configASSERT( ucMaxPriorityValue == ( configKERNEL_INTERRUPT_PRIORITY & ucMaxPriorityValue ) );
/* Use the same mask on the maximum system call priority. */
ucMaxSysCallPriority = configMAX_SYSCALL_INTERRUPT_PRIORITY & ucMaxPriorityValue;
/* Calculate the maximum acceptable priority group value for the number
of bits read back. */
ulMaxPRIGROUPValue = portMAX_PRIGROUP_BITS;
while( ( ucMaxPriorityValue & portTOP_BIT_OF_BYTE ) == portTOP_BIT_OF_BYTE )
{
ulMaxPRIGROUPValue--;
ucMaxPriorityValue <<= ( uint8_t ) 0x01;
}
/* Shift the priority group value back to its position within the AIRCR
register. */
ulMaxPRIGROUPValue <<= portPRIGROUP_SHIFT;
ulMaxPRIGROUPValue &= portPRIORITY_GROUP_MASK;
/* Restore the clobbered interrupt priority register to its original
value. */
*pucFirstUserPriorityRegister = ulOriginalPriority;
}
#endif /* conifgASSERT_DEFINED */
/* Make PendSV and SysTick the lowest priority interrupts. */
portNVIC_SYSPRI2_REG |= portNVIC_PENDSV_PRI;
portNVIC_SYSPRI2_REG |= portNVIC_SYSTICK_PRI;
/* Start the timer that generates the tick ISR. Interrupts are disabled
here already. */
vPortSetupTimerInterrupt();
/* Initialise the critical nesting count ready for the first task. */
uxCriticalNesting = 0;
/* Ensure the VFP is enabled - it should be anyway. */
prvEnableVFP();
/* Lazy save always. */
*( portFPCCR ) |= portASPEN_AND_LSPEN_BITS;
/* Start the first task. */
prvStartFirstTask();
/* Should not get here! */
return 0;
}
configASSERT( configMAX_SYSCALL_INTERRUPT_PRIORITY ):宏configMAX_SYSCALL_INTERRUPT_PRIORITY不能被设置为0否则通过断言函数报错
configASSERT( portCPUID != portCORTEX_M7_r0p1_ID );configASSERT( portCPUID != portCORTEX_M7_r0p0_ID ); : 将portCPUID(一个常数表示着CPU端口ID)与Cortex-M7 r0p1和r0p0端口ID进行比较不能相等否则断言报错
portNVIC_SYSPRI2_REG |= portNVIC_PENDSV_PRI:设置PendSV的中断优先级,为最低优先级
portNVIC_SYSPRI2_REG |= portNVIC_SYSTICK_PRI:设置滴答定时器的中断优先级为最低优先级
vPortSetupTimerInterrupt():调用此函数来设置滴答定时器的定是周期,并且使能滴答定时器的中断
uxCriticalNesting = 0:初始化临界区嵌套计数器
prvEnableVFP():调用函数使能FPU
*( portFPCCR ) |= portASPEN_AND_LSPEN_BITS:设置FPCCR的bit31和bit30都为1,这样S0~S15和FPSCR寄存器在异常入口和退出时的状态自动保存和恢复。并且异常流程使用惰性压栈的特性以保证中断等待
函数xPortStartScheduler()中调用prvEnableVFP()使能FPU,函数原型:
__asm void prvEnableVFP( void )
{
PRESERVE8
/* The FPU enable bits are in the CPACR. */
ldr.w r0, =0xE000ED88
ldr r1, [r0]
/* Enable CP10 and CP11 coprocessors, then save back. */
orr r1, r1, #( 0xf << 20 )
str r1, [r0]
bx r14
nop
}
设置寄存器CPACR可以使能或者失能FPU,地址为0xE000ED88
可以看到寄存器CPACR的位0\~19和位24\~31保留,通过CP10(bit20和bit21)和CP11(bit22和bit23)用于控制FPU, 汇编代码中“orr r1,r1,#(0xf<<20)” 把4个bit位全部设置为1,全访问的开启FPU
bx r14:bx为间接跳转指令,BX即跳转到存放Rm中的地址处,此处为跳转到R14存放的地址处。R14寄存器也称链接寄存器(LR),此寄存器用于函数或子程序调用时返回地址的保存
函数xPortStartScheduler()最后调用prvStartFirstTask()用于启动第一个任务,源码为:
__asm void prvStartFirstTask( void )
{
PRESERVE8
/* Use the NVIC offset register to locate the stack. */
ldr r0, =0xE000ED08
ldr r0, [r0]
ldr r0, [r0]
/* Set the msp back to the start of the stack. */
msr msp, r0
/* Globally enable interrupts. */
cpsie i
cpsie f
dsb
isb
/* Call SVC to start the first task. */
svc 0
nop
nop
}
ldr r0, =0xE000ED08:Cortex-M处理器为了应对在运行时需要修改和重定义向量表的需求,提供了向量表重定位的特性,通过向量表偏移寄存器(VTOR)可以重定义向量表,VTORF寄存器的地址即为0XE000ED08. 这句汇编代码将0XE000ED08保存在寄存器R0中
ldr r0, [r0]:读取R0中存储的地址处的数据并将其保存在R0寄存器,也就是读取寄存器VTOR中的值,即向量表的起始地址,也就是MSP的初始值。前三局实际上就是获取MSP(主堆栈指针)的初始值
msr msp, r0: 复位MSP,R0中保存MSP的初始值,赋值给MSP相当于复位MSP
cpsie i 和 cpsie f:使能中断
dsb 和 isb:数据同步隔离和指令同步隔离
svc 0: 调用SVC指令触发SVC中断,用来启动第一个任务
在函数prvStartFirstTask()中调用SVC指令触发SVC中断,SVC中断服务函数中完成了第一个任务的启动。中断服务函数:
#define vPortSVCHandler SVC_Handler
__asm void vPortSVCHandler( void )
{
PRESERVE8
/* Get the location of the current TCB. */
ldr r3, =pxCurrentTCB
ldr r1, [r3]
ldr r0, [r1]
/* Pop the core registers. */
ldmia r0!, {r4-r11, r14}
msr psp, r0
isb
mov r0, #0
msr basepri, r0
bx r14
}
ldr r3, =pxCurrentTCB:获取pxCurrentTCB指针的存储地址,pxCurrentTCB是一个指向TCB_t的指针,这个指针永远指向正在运行的任务
ldr r1, [r3]:取R3所保存地址处的值赋给R1,即把当前任务的任务控制块存储地址放在R1
ldr r0, [r1]:取R1所保存地址处的值赋给R0,任务控制块第一个字段是任务堆栈的栈顶指针pxTopOfStack所指向的位置,即把当前程序任务堆栈栈顶指针所指向地址放在R0
ldmia r0!, {r4-r11, r14}:指令LDMIA是多加载/存储指令,把此时R0内保存地址处的值赋值给R4,把此时R0内保存地址+4处的值赋给R5,把此时R0内保存地址+8处的值赋给R6…以此类推到R14赋值结束后,把R0内保存地址+4*9赋值给R0(也就是代码中”!”的写回作用)从任务堆栈中将R4\~R11,R14这几个寄存器的值恢复
msr psp, r0:此时的R0中存放着恢复R4\~R11和R14以后的堆栈栈顶指针,此时进程栈指针PSP应该从现在的栈顶指针恢复 PSP=R0
mov r0, #0:将0放入R0中
msr basepri, r0:将R0中值放入寄存器basepri中,即将0放入,打开中断
bx r14:跳转到R14存放的地址处,此时硬件自动恢复寄存器R0\~R3,R12,LR,PC和xPSR的值,堆栈使用进程栈PSP,然后执行寄存器PC中保存的任务函数。 FreeRTOS的任务调度器从这里开始运行
函数vTaskStartScheduler()创建了一个名为“IDLE”的任务,叫做空闲任务。
空闲任务是在空闲时候运行的任务,也就是系统中其他的任务由于各种原因不能运行的时候空闲任务就在运行,所以代码中任务优先级最低。
空闲任务是FreeRTOS系统自动创建的,不需要手动创建。
任务调度器启动以后必须有一个任务运行,空闲任务的作用:
1. 满足任务调度器启动以后至少有一个任务运行而创建
2. 判断系统是否有任务删除,如果有就在空闲任务中释放被删除任务的任务堆栈和任务控制快的内存
3. 运行用户设置的空闲任务钩子函数
4. 判断是否开启低功耗tickless模式