前言:这仅是一篇学习笔记记录,无指导意义。想详细了解的人 可看优快云博主「zhzht19861011」的原创文章。
FreeROTS系统:
使用习惯:
1.一般来说,都是利用下载好的例程进行修改。其中注意的文件有heap1~5.c和配置头文件FreeROTSConfig.h,利用宏定义对系统功能进行对应的裁剪。
使用过程:
2.创建任务
①使用的时候,会用到任务名(描述),任务堆栈大小,任意指针(常用于参数传递给任务),优先级(值小级别高),任务句柄(可用该句柄引用任务)参数。
②堆栈空间太小时,会直接报错误,可通过Debug调试的API接口进行监控剩余未用堆栈大小,仅用于Debug。
③创建的内容,主要包括了TCB结构体,该数据结构由列表和列表项构成,包含了任务的信息,状态(挂起,运行,就绪),优先级(可能做了大小值处理,值小优先级高,如CortexM3)等等信息。这个过程,默认是动态生成空间的,但也有静态生成的,需调用对应static的API创建函数。一般来说,任务都是一个死循环函数。
typedef struct tskTaskControlBlock
{
volatile StackType_t *pxTopOfStack; /*当前堆栈的栈顶,必须位于结构体的第一项*/
#if ( portUSING_MPU_WRAPPERS == 1 )
xMPU_SETTINGS xMPUSettings; /*MPU设置,必须位于结构体的第二项*/
#endif
ListItem_t xStateListItem; /*任务的状态列表项,以引用的方式表示任务的状态*/
ListItem_t xEventListItem; /*事件列表项,用于将任务以引用的方式挂接到事件列表*/
UBaseType_t uxPriority; /*保存任务优先级,0表示最低优先级*/
StackType_t *pxStack; /*指向堆栈的起始位置*/
char pcTaskName[ configMAX_TASK_NAME_LEN ];/*任务名字*/
#if ( portSTACK_GROWTH > 0 )
StackType_t *pxEndOfStack; /*指向堆栈的尾部*/
#endif
#if ( portCRITICAL_NESTING_IN_TCB == 1 )
UBaseType_t uxCriticalNesting; /*保存临界区嵌套深度*/
#endif
#if ( configUSE_TRACE_FACILITY == 1 )
UBaseType_t uxTCBNumber; /*保存一个数值,每个任务都有唯一的值*/
UBaseType_t uxTaskNumber; /*存储一个特定数值*/
#endif
#if ( configUSE_MUTEXES == 1 )
UBaseType_t uxBasePriority; /*保存任务的基础优先级*/
UBaseType_t uxMutexesHeld;
#endif
#if ( configUSE_APPLICATION_TASK_TAG == 1 )
TaskHookFunction_t pxTaskTag;
#endif
#if( configNUM_THREAD_LOCAL_STORAGE_POINTERS > 0 )
void *pvThreadLocalStoragePointers[configNUM_THREAD_LOCAL_STORAGE_POINTERS ];
#endif
#if( configGENERATE_RUN_TIME_STATS == 1 )
uint32_t ulRunTimeCounter; /*记录任务在运行状态下执行的总时间*/
#endif
#if ( configUSE_NEWLIB_REENTRANT == 1 )
/* 为任务分配一个Newlibreent结构体变量。Newlib是一个C库函数,并非FreeRTOS维护,FreeRTOS也不对使用结果负责。如果用户使用Newlib,必须熟知Newlib的细节*/
struct _reent xNewLib_reent;
#endif
#if( configUSE_TASK_NOTIFICATIONS == 1 )
volatile uint32_t ulNotifiedValue; /*与任务通知相关*/
volatile uint8_t ucNotifyState;
#endif
#if( configSUPPORT_STATIC_ALLOCATION == 1 )
uint8_t ucStaticAllocationFlags; /* 如果堆栈由静态数组分配,则设置为pdTRUE,如果堆栈是动态分配的,则设置为pdFALSE*/
#endif
#if( INCLUDE_xTaskAbortDelay == 1 )
uint8_t ucDelayAborted;
#endif
} tskTCB;
typedef tskTCB TCB_t;
- 3.开始任务调度
1.任务调度之前会先创建一个空闲任务,该任务优先级最低,常用于回收任务。若是空间不足以创建空闲任务,则会执行失败,并立即返回。之后会根据任务优先级,执行当前最高优先级任务,低优先级任务进入就绪状态,同时数据进入堆栈。
2.task.c文件中定义了一个任务TCB指针变量pxCurrentTCB,指向当前运行的任务。在任务调度之前,该指针指向优先级最高的任务,即每次创建任务都会进行一次判断,只要优先级高于pxCurrentTCB指向的任务,pxCurrentTCB就指向高优先级任务。
3.调度器是FreeRTOS操作系统的核心,主要负责任务切换,即找出最高优先级的就绪任务,并使之获得CPU运行权。调度器并非自动运行的,需要人为启动它。
API函数vTaskStartScheduler()用于启动调度器,它会创建一个空闲任务、初始化一些静态变量,最主要的,它会初始化系统节拍定时器并设置好相应的中断,然后启动第一个任务。
BaseType_t xPortStartScheduler( void )
{
#if(configASSERT_DEFINED == 1 )
{
volatile uint32_tulOriginalPriority;
/* 中断优先级寄存器0:IPR0 */
volatile uint8_t * constpucFirstUserPriorityRegister = ( uint8_t * ) (portNVIC_IP_REGISTERS_OFFSET_16 +portFIRST_USER_INTERRUPT_NUMBER );
volatile uint8_tucMaxPriorityValue;
/* 这一大段代码用来确定一个最高ISR优先级,在这个ISR或者更低优先级的ISR中可以安全的调用以FromISR结尾的API函数.*/
/* 保存中断优先级值,因为下面要覆写这个寄存器(IPR0) */
ulOriginalPriority = *pucFirstUserPriorityRegister;
/* 确定有效的优先级位个数. 首先向所有位写1,然后再读出来,由于无效的优先级位读出为0,然后数一数有多少个1,就能知道有多少位优先级.*/
*pucFirstUserPriorityRegister= portMAX_8_BIT_VALUE;
ucMaxPriorityValue = *pucFirstUserPriorityRegister;
/* 冗余代码,用来防止用户不正确的设置RTOS可屏蔽中断优先级值 */
ucMaxSysCallPriority =configMAX_SYSCALL_INTERRUPT_PRIORITY &ucMaxPriorityValue;
/* 计算最大优先级组值 */
ulMaxPRIGROUPValue =portMAX_PRIGROUP_BITS;
while( (ucMaxPriorityValue &portTOP_BIT_OF_BYTE ) ==portTOP_BIT_OF_BYTE )
{
ulMaxPRIGROUPValue--;
ucMaxPriorityValue <<= ( uint8_t ) 0x01;
}
ulMaxPRIGROUPValue <<=portPRIGROUP_SHIFT;
ulMaxPRIGROUPValue &=portPRIORITY_GROUP_MASK;
/* 将IPR0寄存器的值复原*/
*pucFirstUserPriorityRegister= ulOriginalPriority;
}
#endif /*conifgASSERT_DEFINED */
/* 将PendSV和SysTick中断设置为最低优先级*/
portNVIC_SYSPRI2_REG |=portNVIC_PENDSV_PRI;
portNVIC_SYSPRI2_REG |=portNVIC_SYSTICK_PRI;
/* 启动系统节拍定时器,即SysTick定时器,初始化中断周期并使能定时器*/
vPortSetupTimerInterrupt();
/* 初始化临界区嵌套计数器 */
uxCriticalNesting = 0;
/* 启动第一个任务 */
prvStartFirstTask();
/* 永远不会到这里! */
return 0;
}
- 4.在Cortex-M3架构中,FreeRTOS为了任务启动和任务切换使用了三个异常:SVC、PendSV和SysTick。SVC(系统服务调用),有些操作系统不允许应用程序直接访问硬件,而是提供一些系统服务函数,通过SVC调用。PendSV(可挂起系统调用),用于任务切换,当有优先级高的任务就绪时,当前任务会等待高优先级任务完成后再继续当前任务。SysTick提供一个时钟片,若优先级相同,每次中断,下一个任务将会获得一个时间片的CPU时间。
5. 启动第一个任务,调用函数 prvStartFirstTask,代码如下:
__asm void prvStartFirstTask( void )
{
PRESERVE8
/* Cortext-M3硬件中,0xE000ED08地址处为VTOR(向量表偏移量)寄存器,存储向量表起始地址*/
ldr r0, =0xE000ED08
ldr r0, [r0]
/* 取出向量表中的第一项,向量表第一项存储主堆栈指针MSP的初始值*/
ldr r0, [r0]
/* 将堆栈地址存入主堆栈指针 */
msr msp, r0
/* 使能全局中断*/
cpsie i
cpsie f
dsb
isb
/* 调用SVC启动第一个任务 */
svc 0
nop
nop
}
6.SVC 0触发SVC中断,中断的内容如下,中断完成后,第一个任务即被执行。
__asm void vPortSVCHandler( void )
{
PRESERVE8
ldr r3, =pxCurrentTCB /* pxCurrentTCB指向处于最高优先级的就绪任务TCB */
ldr r1, [r3] /* 获取任务TCB地址 */
ldr r0, [r1] /* 获取任务TCB的第一个成员,即当前堆栈栈顶pxTopOfStack */
ldmia r0!, {r4-r11} /* 出栈,将寄存器r4~r11出栈 */
msr psp, r0 /* 最新的栈顶指针赋给线程堆栈指针PSP */
isb
mov r0, #0
msr basepri, r0
orrr14, #0xd /* 这里0x0d表示:返回后进入线程模式,从进程堆栈中做出栈操作,返回Thumb状态*/
bx r14
}
7.任务切换,FreeRTOS有两种方法触发任务切换:
①执行系统调用,比如普通任务可以使用taskYIELD()强制任务切换,中断服务程序中使用portYIELD_FROM_ISR()强制任务切换;
②系统节拍时钟中断
当在Cortex-M3中,都是调用PendSV切换上下文,在PendSV中断服务函数中,找到最高优先级的任务,并让CPU执行他。而这两种方法都是在使能PendSV中断,PendSV中断的产生是通过代码:portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT实现的,它向中断状态寄存器bit28位写入1,将PendSV中断设置为挂起状态,等到优先级高于PendSV的中断执行完成后,PendSV中断服务程序将被执行,进行任务切换工作。
8.PendSV中断服务函数流程
该笔记来自于对朱工的文章的学习。博客名:zhzht19861011