广西建设职业技术学院官方网站,营销型网站规划建设的七大要素,滨海网站建设,wordpress导出图片不显示文章目录 一、任务调度器开启函数 vTaskStartScheduler()二、内核相关硬件初始化函数 xPortStartScheduler()三、启动第一个任务 prvStartFirstTask()四、中断服务函数 xPortPendSVHandler()五、空闲任务 一、任务调度器开启函数 vTaskStartScheduler()
这个函数的功能就是开启… 文章目录 一、任务调度器开启函数 vTaskStartScheduler()二、内核相关硬件初始化函数 xPortStartScheduler()三、启动第一个任务 prvStartFirstTask()四、中断服务函数 xPortPendSVHandler()五、空闲任务 一、任务调度器开启函数 vTaskStartScheduler()
这个函数的功能就是开启任务调度器的这个函数在文件 tasks.c中有定义缩减后的函数代码如下
void vTaskStartScheduler( void )
{BaseType_t xReturn;xReturn xTaskCreate( prvIdleTask, (1)IDLE, configMINIMAL_STACK_SIZE,( void * ) NULL,( tskIDLE_PRIORITY | portPRIVILEGE_BIT ),xIdleTaskHandle );#if ( configUSE_TIMERS 1 ) //使用软件定时器使能{if( xReturn pdPASS ){xReturn xTimerCreateTimerTask(); (2)}else{mtCOVERAGE_TEST_MARKER();}}#endif /* configUSE_TIMERS */if( xReturn pdPASS ) //空闲任务和定时器任务创建成功。{portDISABLE_INTERRUPTS(); (3)#if ( configUSE_NEWLIB_REENTRANT 1 ) //使能 NEWLIB{_impure_ptr ( pxCurrentTCB-xNewLib_reent );}#endif /* configUSE_NEWLIB_REENTRANT */xNextTaskUnblockTime portMAX_DELAY;xSchedulerRunning pdTRUE; (4)xTickCount ( TickType_t ) 0U;portCONFIGURE_TIMER_FOR_RUN_TIME_STATS(); (5)if( xPortStartScheduler() ! pdFALSE ) (6){//如果调度器启动成功的话就不会运行到这里函数不会有返回值的}else{//不会运行到这里除非调用函数 xTaskEndScheduler()。}}else{//程序运行到这里只能说明一点那就是系统内核没有启动成功导致的原因是在创建//空闲任务或者定时器任务的时候没有足够的内存。configASSERT( xReturn ! errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY );}//防止编译器报错比如宏 INCLUDE_xTaskGetIdleTaskHandle 定义为 0 的话编译器就会提//示 xIdleTaskHandle 未使用。( void ) xIdleTaskHandle;
}(1)、创建空闲任务如果使用静态内存的话使用函数 xTaskCreateStatic()来创建空闲任务优先级为 tskIDLE_PRIORITY宏 tskIDLE_PRIORITY 为 0也就是说空闲任务的优先级为最低。
(2)、如果使用软件定时器的话还需要通过函数xTimerCreateTimerTask()来创建定时器服务任务。定时器服务任务的具体创建过程是在函数 xTimerCreateTimerTask()中完成的。
(3)、关闭中断在 SVC 中断服务函数 vPortSVCHandler()中会打开中断。
(4)、变量 xSchedulerRunning 设置为 pdTRUE表示调度器开始运行。
(5)、当宏 configGENERATE_RUN_TIME_STATS 为 1 的时候说明使能时间统计功能此时需要用户实现宏 portCONFIGURE_TIMER_FOR_RUN_TIME_STATS此宏用来配置一个定时器/计数器。
(6)、调用函数 xPortStartScheduler()来初始化跟调度器启动有关的硬件比如滴答定时器、FPU 单元和 PendSV 中断等等。
二、内核相关硬件初始化函数 xPortStartScheduler()
FreeRTOS 系统时钟是由滴答定时器来提供的而且任务切换也会用到 PendSV 中断这些硬件的初始化由函数 xPortStartScheduler()来完成缩减后的函数代码如下
BaseType_t xPortStartScheduler( void )
{/******************************************************************//****************此处省略一大堆的条件编译代码**********************//*****************************************************************/portNVIC_SYSPRI2_REG | portNVIC_PENDSV_PRI; (1)portNVIC_SYSPRI2_REG | portNVIC_SYSTICK_PRI; (2)vPortSetupTimerInterrupt(); (3)uxCriticalNesting 0; (4)prvStartFirstTask(); (5)//代码正常执行的话是不会到这里的return 0;
}(1)、设置 PendSV 的中断优先级为最低优先级。
(2)、设置滴答定时器的中断优先级为最低优先级。
(3)、调用函数 vPortSetupTimerInterrupt()来设置滴答定时器的定时周期并且使能滴答定时器的中断函数比较简单大家自行查阅分析。
(4)、初始化临界区嵌套计数器。
(5)、调用函数 prvStartFirstTask()开启第一个任务。
三、启动第一个任务 prvStartFirstTask()
经过上面的操作以后我们就可以启动第一个任务了函数 prvStartFirstTask()用于启动第一个任务这是一个汇编函数函数源码如下
__asm void prvStartFirstTask( void )
{PRESERVE8ldr r0, 0xE000ED08 ;R00XE000ED08 (1)ldr r0, [r0] ;取 R0 所保存的地址处的值赋给 R0 (2)ldr r0, [r0] ;获取 MSP 初始值 (3)msr msp, r0 ;复位 MSP (4)cpsie I ;使能中断(清除 PRIMASK) (5)cpsie f ;使能中断(清除 FAULTMASK) (6)dsb ;数据同步屏障 (7)isb ;指令同步屏障 (8)svc 0 ;触发 SVC 中断(异常) (9)nopnop
}(1)、将 0XE000ED08 保存在寄存器 R0 中。一般来说向量表应该是从起始地址(0X00000000)开始存储的不过有些应用可能需要在运行时修改或重定义向量表Cortex-M 处理器为此提供了一个叫做向量表重定位的特性。向量表重定位特性提供了一个名为向量表偏移寄存器(VTOR)的可编程寄存器。VTOR 寄存器的地址就是 0XE000ED08通过这个寄存器可以重新定义向量表比如在 STM32F103 的 ST 官方库中会通过函数 SystemInit()来设置 VTOR 寄存器
代码如下
SCB-VTOR FLASH_BASE | VECT_TAB_OFFSET; //VTOR0x080000000X00通过上面一行代码就将向量表开始地址重新定义到了0X08000000向量表的起始地址存储的就是 MSP 初始值。
(2)、读取 R0 中存储的地址处的数据并将其保存在 R0 寄存器也就是读取寄存器 VTOR中的值并将其保存在 R0 寄存器中。这一行代码执行完就以后 R0 的值应该为 0X08000000。
(3)、读取 R0 中存储的地址处的数据并将其保存在 R0 寄存器也就是读取地址 0X08000000处存储的数据并将其保存在 R0 寄存器中。我们知道向量表的起始地址保存的就是主栈指针MSP 的初始值这一行代码执行完以后寄存器 R0 就存储 MSP 的初始值。现在来看(1)、(2)、(3)这三步起始就是为了获取 MSP 的初始值而已
(4)、复位 MSPR0 中保存了 MSP 的初始值将其赋值给 MSP 就相当于复位 MSP。
(5)和(6)、使能中断。
(7)和(8)、数据同步和指令同步屏障。
(9)、调用 SVC 指令触发 SVC 中断SVC 也叫做请求管理调用SVC 和 PendSV 异常对于OS 的设计来说非常重要。SVC 异常由 SVC 指令触发。在 FreeRTOS 中仅仅使用 SVC 异常来启动第一 个任务后面的程序中就再也用不到 SVC 了。
四、中断服务函数 xPortPendSVHandler()
在函数 prvStartFirstTask()中通过调用 SVC 指令触发了 SVC 中断而第一个任务的启动就是在 SVC 中断服务函数中完成的SVC 中断服务函数应该为 SVC_Handler()但是 FreeRTOSConfig.h 中通过#define 的方式重新定义为了 xPortPendSVHandler()如下
#define xPortPendSVHandler PendSV_Handler函数 vPortSVCHandler()在文件 port.c 中定义这个函数也是用汇编写的函数源码如下
__asm void vPortSVCHandler( void )
{PRESERVE8ldr r3, pxCurrentTCB ;R3pxCurrentTCB 的地址 (1)ldr r1, [r3] ;取 R3 所保存的地址处的值赋给 R1 (2)ldr r0, [r1] ;取 R1 所保存的地址处的值赋给 R0 (3)ldmia r0!, {r4-r11, r14} ;出栈 R4~R11 和 R14 (4)msr psp, r0 ;进程栈指针 PSP 设置为任务的堆栈 (5)isb ;指令同步屏障mov r0, #0 ;R00 (6)msr basepri, r0 ;寄存器 basepri0开启中断 (7)orr r14, #0xd ; (8)bx r14 (9)
}(1)、获取 pxCurrentTCB 指针的存储地址pxCurrentTCB 是一个指向 TCB_t 的指针这个指针永远指向正在运行的任务。这里先获取这个指针存储的地址比如我现在的代码测试出来这个指针是存放在 0X20000044如下图所示。 (2)、取 R3 所保存的地址处的值赋给 R1。通过这一步就获取到了当前任务的任务控制块的存储地址。比如当前我的程序中这个地址就为 0X20000EE8如下图所示 (3)、取 R3 所保存的地址处的值赋给 R0我们知道任务控制块的第一个字段就是任务堆栈的栈顶指针 pxTopOfStack 所指向的位置所以读取任务控制块所在的首地址(0X20000EE8)得到 的就是栈顶指针所指向的地址当前我的程序中这个栈顶指针(pxTopOfStack)所指向的地址为0X20000E98如下图所示 可以看出(1)、(2)和(3)的目的就是获取要切换到的这个任务的任务栈顶指针因为任务所对应的寄存器值也就是现场都保存在任务的任务堆栈中所以需要获取栈顶指针来恢复这些寄存器值
(4)、R4~R11R14 这些寄存器出栈。这里使用了指令 LDMIALDMIA 指令是多加载/存储指令不过这里使用的是具有回写的多加载/存储访问指令用法如下
LDMIA Rn! , {reg list}表示从 Rn 指定的存储器位置读取多个字地址在每次读取后增加(IA),Rn 在传输完成以后写回。对于 STM32 来说地址一次增加 4 字节比如如下代码
LDR R0, 0X800
LDMIA R0!, {R2~R4}上面两行代码就是将 0X800 地址的数据赋值给寄存器 R20X804 地址的数据赋值给寄存器 R30X8008 地址的数据赋值给 R4 寄存器然后重点来了此时 R0 为 800A通过这一步我们就从任务堆栈中将 R4~R11 这几个寄存器的值给恢复了。这里有朋友就要问了R0~R3R12PC,xPSR 这些寄存器怎么没有恢复这是因为这些寄存器会在退出中断的时候 MCU 自动出栈(恢复)的而 R4~R11 需要由用户手动出栈。这个我们在分析 PendSV 中断服务函数的时候会讲到。到这步以后我们来看一下堆栈的栈顶指针指到哪 里了如下图所示 从上图可以看出恢复 R4~R11 和 R14 以后堆栈的栈顶指针应该指向地址 0X20000EB8也就是保存寄存器 R0 值的存储地址。退出中断服务函数以后进程栈指针 PSP 应该从这个地址开始恢复其他的寄存器值。
(5)、设置进程栈指针 PSPPSPR00X20000EB8如下图所示 (6)、设置寄存器 R0 为 0。
(7)、设置寄存器 BASEPRI 为 R0也就是 0打开中断
(8)、R14 寄存器的值与 0X0D 进行或运算得到的结果就是 R14 寄存器的新值。表示退出异常以后 CPU 进入线程模式并且使用进程栈
(9)、执行此行代码以后硬件自动恢复寄存器 R0~R3、R12、LR、PC 和 xPSR 的值堆栈使用进程栈 PSP然后执行寄存器 PC 中保存的任务函数。至此FreeRTOS 的任务调度器正式开始运行
五、空闲任务
在前面讲解函数 vTaskStartScheduler()说过此函数会创建一个名为“IDLE”的任务这个任务叫做空闲任务。顾名思义空闲任务就是空闲的时候运行的任务也就是系统中其他的任务由于各种原因不能运行的时候空闲任务就在运行。空闲任务是 FreeRTOS 系统自动创建的不需要用户手动创建。任务调度器启动以后就必须有一个任务运行但是空闲任务不仅仅是为了满足任务调度器启动以后至少有一个任务运行而创建的空闲任务中还会去做一些其他 的事情如下 1、判断系统是否有任务删除如果有的话就在空闲任务中释放被删除任务的任务堆栈和任务控制块的内存。
2、运行用户设置的空闲任务钩子函数。
3、判断是否开启低功耗 tickless 模式如果开启的话还需要做相应的处理空闲任务的任务优先级是最低的为 0任务函数为 prvIdleTask()。