freertos 订阅
在嵌入式领域中,嵌入式实时操作系统正得到越来越广泛的应用。采用嵌入式实时操作系统(RTOS)可以更合理、更有效地利用CPU的资源,简化应用软件的设计,缩短系统开发时间,更好地保证系统的实时性和可靠性。 展开全文
在嵌入式领域中,嵌入式实时操作系统正得到越来越广泛的应用。采用嵌入式实时操作系统(RTOS)可以更合理、更有效地利用CPU的资源,简化应用软件的设计,缩短系统开发时间,更好地保证系统的实时性和可靠性。
信息
本    质
小型实时操作系统内核
特    点
源码公开、可移植、可裁减
外文名
FreeRTOS
功能包括
任务管理、时间管理、内存管理
FreeRTOS简介
FreeRTOS是一个迷你的实时操作系统内核。作为一个轻量级的操作系统,功能包括:任务管理、时间管理、信号量、消息队列、内存管理、记录功能、软件定时器、协程等,可基本满足较小系统的需要。由于RTOS需占用一定的系统资源(尤其是RAM资源),只有μC/OS-II、embOS、salvo、FreeRTOS等少数实时操作系统能在小RAM单片机上运行。相对μC/OS-II、embOS等商业操作系统,FreeRTOS操作系统是完全免费的操作系统,具有源码公开、可移植、可裁减、调度策略灵活的特点,可以方便地移植到各种单片机上运行,其最新版本为10.3.1版。
收起全文
精华内容
下载资源
问答
  • FreeRTOS

    2020-09-09 17:59:55
    FreeRTOS中断优先级配置

    延时


    使用DWT来实现系统延时
    STM32 DWT


    配置及中断优先级


    FreeRTOS中断优先级配置
    stm32cubemx 配置freertos中断优先级
    FreeRTOS优先级详解
    在ARM Cortex-M内核上运行RTOS
    STM32中断,及FreeRTOS中断优先级配置
    c – FreeRTOS:osDelay vs HAL_delay
    cubemx在使用freertos的时候为何推荐使用除systick以外的timebase
    FreeRtos 任务优先级和中断优先级


    功能使用


    FreeRTOS学习笔记——任务间使用队列同步数据


    注意事项


    在启动FreeRTOS之前,开启了独立看门狗IWDG,3秒报警,但是在FreeRTOS任务中没有喂狗,导致运行3秒后,程序进入HardFault
    //MX_IWDG_Init();
    FREERTOS_Init();
    FREERTOS_Start();

    1、HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);
    2、FreeRTOS强制使用SysTick作为时基,并且将其中断优先级设置为最低。STM32 的HAL库的时基须选择其他定时器
    3、建议HAL库的时基使用TIM,便于移植操作系统,也便于从IAP跳转到APP控制(如果使用SysTick,在跳转到APP前,需要关闭SysTick中断 SysTick->CTRL &= ~SysTick_CTRL_TICKINT_Msk;//关闭SysTick中断)

    void vTaskStartScheduler( void )-->BaseType_t xPortStartScheduler( void )
    /* Make PendSV and SysTick the lowest priority interrupts. */
    	portNVIC_SYSPRI2_REG |= portNVIC_PENDSV_PRI;
    	portNVIC_SYSPRI2_REG |= portNVIC_SYSTICK_PRI;
    
    /* Cortex-M specific definitions. */
    #ifdef __NVIC_PRIO_BITS
     /* __BVIC_PRIO_BITS will be specified when CMSIS is being used. */
     #define configPRIO_BITS         __NVIC_PRIO_BITS
    #else
     #define configPRIO_BITS         4
    #endif
    
    /* The lowest interrupt priority that can be used in a call to a "set priority"
    function. */
    #define configLIBRARY_LOWEST_INTERRUPT_PRIORITY   15
    
    /* The highest interrupt priority that can be used by any interrupt service
    routine that makes calls to interrupt safe FreeRTOS API functions.  DO NOT CALL
    INTERRUPT SAFE FREERTOS API FUNCTIONS FROM ANY INTERRUPT THAT HAS A HIGHER
    PRIORITY THAN THIS! (higher priorities are lower numeric values. */
    #define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5
    
    /* Interrupt priorities used by the kernel port layer itself.  These are generic
    to all Cortex-M ports, and do not rely on any particular library functions. */
    #define configKERNEL_INTERRUPT_PRIORITY 		( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
    /* !!!! configMAX_SYSCALL_INTERRUPT_PRIORITY must not be set to zero !!!!
    See http://www.FreeRTOS.org/RTOS-Cortex-M3-M4.html. */
    #define configMAX_SYSCALL_INTERRUPT_PRIORITY 	( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
    /* Definitions that map the FreeRTOS port interrupt handlers to their CMSIS
    standard names. */
    //在系统中的其他文件中不能在定义 SVC_Handler PendSV_Handler SysTick_Handler
    #define vPortSVCHandler    SVC_Handler
    #define xPortPendSVHandler PendSV_Handler
    
    /* IMPORTANT: This define is commented when used with STM32Cube firmware, when timebase is systick,
                  to prevent overwriting SysTick_Handler defined within STM32Cube HAL */
    #define xPortSysTickHandler SysTick_Handler
    
    

    FreeRTOS下载地址:

    https://download.csdn.net/download/lyrain2009/12847593


    展开全文
  • FreeRTOS系列第1篇---为什么选择FreeRTOS

    万次阅读 多人点赞 2015-11-13 15:55:38
    对比了许多RTOS,最终选择FreeRTOS,原因是多方面的: SafeRTOS便是基于FreeRTOS而来,前者是经过安全认证的RTOS,因此对于FreeRTOS的安全性也有了信心。 大量开发者使用,并保持高速增长趋势。2011、2012、2013、...

    1.为什么学习RTOS?

             作为基于ARM7、Cortex-M3硬件开发的嵌入式工程师,我一直反对使用RTOS。不仅因为不恰当的使用RTOS会给项目带来额外的稳定性风险,更重要的是我认为绝大多数基于ARM7、Cortex-M3硬件的项目,还没复杂到使用RTOS的地步,使用状态机就足够了。

             对于现代的微处理器,特别是资源相对丰富ARM7、Cortex-M3硬件来说,RTOS占用的硬件资源已经越来越可以忽略。所以在当今环境下,我们无需担心RTOS会拖累性能。相反,RTOS提供的事件驱动型设计方式,使得RTOS只是在处理实际任务时才会运行,这能够更合理的利用CPU。在实际项目中,如果程序等待一个超时事件,传统的无RTOS情况下,要么在原地一直等待而不能执行其它任务,要么使用复杂(相对RTOS提供的任务机制而言)的状态机机制。如果使用RTOS,则可以很方便的将当前任务阻塞在该事件下,然后自动去执行别的任务,这显然更方便,并且可以高效的利用CPU。处理这类事件,是我使用RTOS的最大动力,但考虑到系统的稳定性,我不得不再三权衡RTOS可能带来的一些弊端:

     

    1. 大多数RTOS代码都具有一定规模,任何代码都可能带来BUG,何况是代码具有一定规模的RTOS,因此引入RTOS的同时也可能会引入该RTOS的BUG,这些RTOS本身的BUG一旦被触发,影响可能是是灾难性的。
    2. 熟练的使用RTOS是一项技能,需要专业的知识储备和长期的经验积累。不将RTOS分析透彻,很容易为项目埋下错误。典型的,像中断优先级、任务堆栈分配、可重入等,都是更容易出错的地方。
    3. RTOS的优先级嵌套使得任务执行顺序、执行时序更难分析,甚至变成不可能。任务嵌套对所需的最大堆栈RAM大小估计也变得困难。这对于很多对安全有严格要求的场合是不可想象的。
    4. RTOS应该用于任务复杂的场合,以至于对任务调度的需求可以抵消RTOS所带来的稳定性影响,但大部分的应用并非复杂到需要RTOS。

     

             以上原因是我拒绝在实际项目中使用RTOS的理由,但是否使用RTOS跟是否学习RTOS完全是两码事。我认为任何嵌入式软件设计人员都应该至少学习一种RTOS,不仅是需要掌握RTOS背后的操作系统原理、学习RTOS的编程方式,更是为将来做准备。

             即便我认为现在的物联网有点言过其实,但我依然看好物联网的发展前景。随着物联网的发展,未来的嵌入式产品必然更为复杂、连接性更强以及需要更丰富的用户界面。当处理这些任务时,一个好的RTOS就变得不可缺少了。

             书到用时方恨少,我希望自己永远不会有这种感觉。所以从现在起,我要开始深入一个RTOS,探索它背后的原理,掌握其编程方法,避免其缺陷和陷阱,并将它安全的用在将来的项目中。

    2.为什么选用FreeRTOS?

             对比了许多RTOS,最终选择FreeRTOS,原因是多方面的:

     

    1. SafeRTOS便是基于FreeRTOS而来,前者是经过安全认证的RTOS,因此对于FreeRTOS的安全性也有了信心。
    2.  大量开发者使用,并保持高速增长趋势。2011、2012、2013、2014、2015、2017年(暂时没有2016年的数据)、2019年(暂时没有2018年的数据)的EEtimes杂志嵌入式系统市场报告显示,FreeRTOS在RTOS内核使用榜和RTOS内核计划使用榜上都名列前茅。更多的人使用可以促进发现BUG,增强稳定性。
    3. 简单。内核只有3个.c文件,全部围绕着任务调度,没有任何其它干扰,便于理解学习。而且,我根本不需要其它繁多的功能,只要任务调度就够了。
    4. 文档齐全。在FreeRTOS官方网站上,可以找到所有你需要的资料。
    5. 免费、开放源码。完全可以免费用于商业产品,开放源码更便于学习操作系统原理、从全局掌握FreeRTOS运行机理、以及对操作系统进行深度裁剪以适应自己的硬件。
    6. 2017年底,FreeRTOS作者加入亚马逊,担任首席工程师,FreeRTOS也由亚马逊管理。同时修改了用户许可证,FreeRTOS变得更加开放和自由。背靠亚马逊,相信未来FreeRTOS会更加稳定可靠。此外,以前价格不菲的《实时内核指南》和《参考手册》也免费开放下载,这使得学习更加容易。

     

             学习的资料来源主要是FreeRTOS的官方网站(www.freertos.org)和源代码。FreeRTOS的创始人RichardBarry编写了大量的移植代码和配套文档,我只不过是沿着Richard Barry铺好的路前进,所以,这没什么困难的。

             最后,感谢RichardBarry的付出,感谢Richard Barry的无私开源精神!

     

    附录1: EEtimes杂志嵌入式市场调查报告有关RTOS使用榜截图

    附录1.1  2010和2011年RTOS使用榜

     

    附录1.2  2012和2013年RTOS使用榜


    附录1.3   2013年和2014年RTOS使用榜

     

    附录1.4   2014年和2015年RTOS使用榜

    附录1.5   2017年RTOS使用榜

    附录1.6   2019年RTOS使用榜

     

     

    展开全文
  • FreeRTOS系列第6篇---FreeRTOS内核配置说明

    万次阅读 多人点赞 2015-12-01 21:14:21
    FreeRTOS内核是高度可定制的,使用配置文件FreeRTOSConfig.h进行定制。每个FreeRTOS应用都必须包含这个头文件,用户根据实际应用来裁剪定制FreeRTOS内核。这个配置文件是针对用户程序的,而非内核,因此配置文件一般...

           FreeRTOS内核是高度可定制的,使用配置文件FreeRTOSConfig.h进行定制。每个FreeRTOS应用都必须包含这个头文件,用户根据实际应用来裁剪定制FreeRTOS内核。这个配置文件是针对用户程序的,而非内核,因此配置文件一般放在应用程序目录下,不要放在RTOS内核源码目录下。

           在下载的FreeRTOS文件包中,每个演示例程都有一个FreeRTOSConfig.h文件。有些例程的配置文件是比较旧的版本,可能不会包含所有有效选项。如果没有在配置文件中指定某个选项,那么RTOS内核会使用默认值。典型的FreeRTOSConfig.h配置文件定义如下所示,随后会说明里面的每一个参数。

     

    #ifndef FREERTOS_CONFIG_H
    #define FREERTOS_CONFIG_H
     
    /*Here is a good place to include header files that are required across
    yourapplication. */
    #include "something.h"
     
    #define configUSE_PREEMPTION                    1
    #define configUSE_PORT_OPTIMISED_TASK_SELECTION 0
    #define configUSE_TICKLESS_IDLE                 0
    #define configCPU_CLOCK_HZ                      60000000
    #define configTICK_RATE_HZ                      250
    #define configMAX_PRIORITIES                    5
    #define configMINIMAL_STACK_SIZE                128
    #define configTOTAL_HEAP_SIZE                   10240
    #define configMAX_TASK_NAME_LEN                 16
    #define configUSE_16_BIT_TICKS                  0
    #define configIDLE_SHOULD_YIELD                 1
    #define configUSE_TASK_NOTIFICATIONS            1
    #define configUSE_MUTEXES                       0
    #define configUSE_RECURSIVE_MUTEXES             0
    #define configUSE_COUNTING_SEMAPHORES           0
    #define configUSE_ALTERNATIVE_API               0/* Deprecated! */
    #define configQUEUE_REGISTRY_SIZE               10
    #define configUSE_QUEUE_SETS                    0
    #define configUSE_TIME_SLICING                  0
    #define configUSE_NEWLIB_REENTRANT              0
    #define configENABLE_BACKWARD_COMPATIBILITY     0
    #define configNUM_THREAD_LOCAL_STORAGE_POINTERS 5
     
    /*Hook function related definitions. */
    #define configUSE_IDLE_HOOK                     0
    #define configUSE_TICK_HOOK                     0
    #define configCHECK_FOR_STACK_OVERFLOW          0
    #define configUSE_MALLOC_FAILED_HOOK            0
     
    /*Run time and task stats gathering related definitions. */
    #define configGENERATE_RUN_TIME_STATS           0
    #define configUSE_TRACE_FACILITY                0
    #define configUSE_STATS_FORMATTING_FUNCTIONS    0
     
    /*Co-routine related definitions. */
    #define configUSE_CO_ROUTINES                   0
    #define configMAX_CO_ROUTINE_PRIORITIES         1
     
    /*Software timer related definitions. */
    #define configUSE_TIMERS                        1
    #define configTIMER_TASK_PRIORITY               3
    #define configTIMER_QUEUE_LENGTH                10
    #define configTIMER_TASK_STACK_DEPTH            configMINIMAL_STACK_SIZE
     
    /*Interrupt nesting behaviour configuration. */
    #define configKERNEL_INTERRUPT_PRIORITY        [dependent of processor]
    #define configMAX_SYSCALL_INTERRUPT_PRIORITY   [dependent on processor and application]
    #define configMAX_API_CALL_INTERRUPT_PRIORITY  [dependent on processor and application]
     
    /*Define to trap errors during development. */
    #define configASSERT( ( x ) )     if( ( x ) == 0) vAssertCalled( __FILE__, __LINE__ )
     
    /*FreeRTOS MPU specific definitions. */
    #define configINCLUDE_APPLICATION_DEFINED_PRIVILEGED_FUNCTIONS 0
     
    /*Optional functions - most linkers will remove unused functions anyway. */
    #define INCLUDE_vTaskPrioritySet                1
    #define INCLUDE_uxTaskPriorityGet               1
    #define INCLUDE_vTaskDelete                     1
    #define INCLUDE_vTaskSuspend                    1
    #define INCLUDE_xResumeFromISR                  1
    #define INCLUDE_vTaskDelayUntil                 1
    #define INCLUDE_vTaskDelay                      1
    #define INCLUDE_xTaskGetSchedulerState          1
    #define INCLUDE_xTaskGetCurrentTaskHandle       1
    #define INCLUDE_uxTaskGetStackHighWaterMark     0
    #define INCLUDE_xTaskGetIdleTaskHandle          0
    #define INCLUDE_xTimerGetTimerDaemonTaskHandle  0
    #define INCLUDE_pcTaskGetTaskName               0
    #define INCLUDE_eTaskGetState                   0
    #define INCLUDE_xEventGroupSetBitFromISR        1
    #define INCLUDE_xTimerPendFunctionCall          0
     
    /* Aheader file that defines trace macro can be included here. */
     
    #end if/* FREERTOS_CONFIG_H*/

     

    1.configUSE_PREEMPTION

           为1时RTOS使用抢占式调度器,为0时RTOS使用协作式调度器(时间片)。

          注:在多任务管理机制上,操作系统可以分为抢占式和协作式两种。协作式操作系统是任务主动释放CPU后,切换到下一个任务。任务切换的时机完全取决于正在运行的任务。

    2.configUSE_PORT_OPTIMISED_TASK_SELECTION

           某些运行FreeRTOS的硬件有两种方法选择下一个要执行的任务:通用方法和特定于硬件的方法(以下简称“特殊方法”)。

           通用方法:

     

    • configUSE_PORT_OPTIMISED_TASK_SELECTION设置为0或者硬件不支持这种特殊方法。
    • 可以用于所有FreeRTOS支持的硬件。
    • 完全用C实现,效率略低于特殊方法。
    • 不强制要求限制最大可用优先级数目

     

           特殊方法:

     

    • 并非所有硬件都支持。
    • 必须将configUSE_PORT_OPTIMISED_TASK_SELECTION设置为1。
    • 依赖一个或多个特定架构的汇编指令(一般是类似计算前导零[CLZ]指令)。
    • 比通用方法更高效。
    • 一般强制限定最大可用优先级数目为32。

     

    3.configUSE_TICKLESS_IDLE

           设置configUSE_TICKLESS_IDLE为1使能低功耗tickless模式,为0保持系统节拍(tick)中断一直运行。

           通常情况下,FreeRTOS回调空闲任务钩子函数(需要设计者自己实现),在空闲任务钩子函数中设置微处理器进入低功耗模式来达到省电的目的。因为系统要响应系统节拍中断事件,因此使用这种方法会周期性的退出、再进入低功耗状态。如果系统节拍中断频率过快,则大部分电能和CPU时间会消耗在进入和退出低功耗状态上。

           FreeRTOS的tickless空闲模式会在空闲周期时停止周期性系统节拍中断。停止周期性系统节拍中断可以使微控制器长时间处于低功耗模式。移植层需要配置外部唤醒中断,当唤醒事件到来时,将微控制器从低功耗模式唤醒。微控制器唤醒后,会重新使能系统节拍中断。由于微控制器在进入低功耗后,系统节拍计数器是停止的,但我们又需要知道这段时间能折算成多少次系统节拍中断周期,这就需要有一个不受低功耗影响的外部时钟源,即微处理器处于低功耗模式时它也在计时的,这样在重启系统节拍中断时就可以根据这个外部计时器计算出一个调整值并写入RTOS 系统节拍计数器变量中。

    4.configUSE_IDLE_HOOK

           设置为1使用空闲钩子(Idle Hook类似于回调函数),0忽略空闲钩子。

           当RTOS调度器开始工作后,为了保证至少有一个任务在运行,空闲任务被自动创建,占用最低优先级(0优先级)。对于已经删除的RTOS任务,空闲任务可以释放分配给它们的堆栈内存。因此,在应用中应该注意,使用vTaskDelete()函数时要确保空闲任务获得一定的处理器时间。除此之外,空闲任务没有其它特殊功能,因此可以任意的剥夺空闲任务的处理器时间。

           应用程序也可能和空闲任务共享同个优先级。

           空闲任务钩子是一个函数,这个函数由用户来实现,RTOS规定了函数的名字和参数,这个函数在每个空闲任务周期都会被调用。

           要创建一个空闲钩子:

     

    1.  设置FreeRTOSConfig.h 文件中的configUSE_IDLE_HOOK 为1;
    2.  定义一个函数,函数名和参数如下所示:
    void vApplicationIdleHook(void );

     

           这个钩子函数不可以调用会引起空闲任务阻塞的API函数(例如:vTaskDelay()、带有阻塞时间的队列和信号量函数),在钩子函数内部使用协程是被允许的。

           使用空闲钩子函数设置CPU进入省电模式是很常见的。

    5.configUSE_MALLOC_FAILED_HOOK

           每当一个任务、队列、信号量被创建时,内核使用一个名为pvPortMalloc()的函数来从堆中分配内存。官方的下载包中包含5个简单内存分配策略,分别保存在源文件heap_1.c、heap_2.c、heap_3.c、heap_4.c、heap_5.c中。        仅当使用这五个简单策略之一时,宏configUSE_MALLOC_FAILED_HOOK才有意义。

           如果定义并正确配置malloc()失败钩子函数,则这个函数会在pvPortMalloc()函数返回NULL时被调用。只有FreeRTOS在响应内存分配请求时发现堆内存不足才会返回NULL。

           如果宏configUSE_MALLOC_FAILED_HOOK设置为1,那么必须定义一个malloc()失败钩子函数,如果宏configUSE_MALLOC_FAILED_HOOK设置为0,malloc()失败钩子函数不会被调用,即便已经定义了这个函数。malloc()失败钩子函数的函数名和原型必须如下所示:

    void vApplicationMallocFailedHook( void);

    6.configUSE_TICK_HOOK

           设置为1使用时间片钩子(Tick Hook),0忽略时间片钩子。

           注:时间片钩子函数(Tick Hook Function)

           时间片中断可以周期性的调用一个被称为钩子函数(回调函数)的应用程序。时间片钩子函数可以很方便的实现一个定时器功能。

           只有在FreeRTOSConfig.h中的configUSE_TICK_HOOK设置成1时才可以使用时间片钩子。一旦此值设置成1,就要定义钩子函数,函数名和参数如下所示:

    void vApplicationTickHook( void );

     

           vApplicationTickHook()函数在中断服务程序中执行,因此这个函数必须非常短小,不能大量使用堆栈,只能调用以”FromISR" 或 "FROM_ISR”结尾的API函数。

           在FreeRTOSVx.x.x\FreeRTOS\Demo\Common\Minimal文件夹下的crhook.c文件中有使用时间片钩子函数的例程。

    7.configCPU_CLOCK_HZ

           写入实际的CPU内核时钟频率,也就是CPU指令执行频率,通常称为Fcclk。配置此值是为了正确的配置系统节拍中断周期。

    8.configTICK_RATE_HZ

           RTOS 系统节拍中断的频率。即一秒中断的次数,每次中断RTOS都会进行任务调度。

    系统节拍中断用来测量时间,因此,越高的测量频率意味着可测到越高的分辨率时间。但是,高的系统节拍中断频率也意味着RTOS内核占用更多的CPU时间,因此会降低效率。RTOS演示例程都是使用系统节拍中断频率为1000HZ,这是为了测试RTOS内核,比实际使用的要高。(实际使用时不用这么高的系统节拍中断频率)

           多个任务可以共享一个优先级,RTOS调度器为相同优先级的任务分享CPU时间,在每一个RTOS 系统节拍中断到来时进行任务切换。高的系统节拍中断频率会降低分配给每一个任务的“时间片”持续时间。

    9.configMAX_PRIORITIES

           配置应用程序有效的优先级数目。任何数量的任务都可以共享一个优先级,使用协程可以单独的给与它们优先权。见configMAX_CO_ROUTINE_PRIORITIES。

           在RTOS内核中,每个有效优先级都会消耗一定量的RAM,因此这个值不要超过你的应用实际需要的优先级数目。

    注:任务优先级

           每一个任务都会被分配一个优先级,优先级值从0~ (configMAX_PRIORITIES - 1)之间。低优先级数表示低优先级任务。空闲任务的优先级为0(tskIDLE_PRIORITY),因此它是最低优先级任务。

           FreeRTOS调度器将确保处于就绪状态(Ready)或运行状态(Running)的高优先级任务比同样处于就绪状态的低优先级任务优先获取处理器时间。换句话说,处于运行状态的任务永远是高优先级任务。

           处于就绪状态的相同优先级任务使用时间片调度机制共享处理器时间。

    10.configMINIMAL_STACK_SIZE

           定义空闲任务使用的堆栈大小。通常此值不应小于对应处理器演示例程文件FreeRTOSConfig.h中定义的数值。

           就像xTaskCreate()函数的堆栈大小参数一样,堆栈大小不是以字节为单位而是以字为单位的,比如在32位架构下,栈大小为100表示栈内存占用400字节的空间。

    11.configTOTAL_HEAP_SIZE

           RTOS内核总计可用的有效的RAM大小。仅在你使用官方下载包中附带的内存分配策略时,才有可能用到此值。每当创建任务、队列、互斥量、软件定时器或信号量时,RTOS内核会为此分配RAM,这里的RAM都属于configTOTAL_HEAP_SIZE指定的内存区。后续的内存配置会详细讲到官方给出的内存分配策略。

    12.configMAX_TASK_NAME_LEN

           调用任务函数时,需要设置描述任务信息的字符串,这个宏用来定义该字符串的最大长度。这里定义的长度包括字符串结束符’\0’。

    13.configUSE_TRACE_FACILITY

           设置成1表示启动可视化跟踪调试,会激活一些附加的结构体成员和函数。

    14.configUSE_STATS_FORMATTING_FUNCTIONS (V7.5.0新增)

           设置宏configUSE_TRACE_FACILITY和configUSE_STATS_FORMATTING_FUNCTIONS为1会编译vTaskList()和vTaskGetRunTimeStats()函数。如果将这两个宏任意一个设置为0,上述两个函数不会被编译。

    15.configUSE_16_BIT_TICKS

           定义系统节拍计数器的变量类型,即定义portTickType是表示16位变量还是32位变量。

           定义configUSE_16_BIT_TICKS为1意味着portTickType代表16位无符号整形,定义configUSE_16_BIT_TICKS为0意味着portTickType代表32位无符号整形。

           使用16位类型可以大大提高8位和16位架构微处理器的性能,但这也限制了最大时钟计数为65535个’Tick’。因此,如果Tick频率为250HZ(4MS中断一次),对于任务最大延时或阻塞时间,16位计数器是262秒,而32位是17179869秒。

    16.configIDLE_SHOULD_YIELD

           这个参数控制任务在空闲优先级中的行为。仅在满足下列条件后,才会起作用。

     

    1. 使用抢占式内核调度
    2. 用户任务使用空闲优先级。

     

             通过时间片共享同一个优先级的多个任务,如果共享的优先级大于空闲优先级,并假设没有更高优先级任务,这些任务应该获得相同的处理器时间。

             但如果共享空闲优先级时,情况会稍微有些不同。当configIDLE_SHOULD_YIELD为1时,其它共享空闲优先级的用户任务就绪时,空闲任务立刻让出CPU,用户任务运行,这样确保了能最快响应用户任务。处于这种模式下也会有不良效果(取决于你的程序需要),描述如下:

     

           图中描述了四个处于空闲优先级的任务,任务A、B和C是用户任务,任务I是空闲任务。上下文切换周期性的发生在T0、T1…T6时刻。当用户任务运行时,空闲任务立刻让出CPU,但是,空闲任务已经消耗了当前时间片中的一定时间。这样的结果就是空闲任务I和用户任务A共享一个时间片。用户任务B和用户任务C因此获得了比用户任务A更多的处理器时间。

     

           可以通过下面方法避免:

     

    • 如果合适的话,将处于空闲优先级的各单独的任务放置到空闲钩子函数中;
    • 创建的用户任务优先级大于空闲优先级;
    • 设置IDLE_SHOULD_YIELD为0;

     

           设置configIDLE_SHOULD_YIELD为0将阻止空闲任务为用户任务让出CPU,直到空闲任务的时间片结束。这确保所有处在空闲优先级的任务分配到相同多的处理器时间,但是,这是以分配给空闲任务更高比例的处理器时间为代价的。

    17.configUSE_TASK_NOTIFICATIONS(V8.2.0新增)

           设置宏configUSE_TASK_NOTIFICATIONS为1(或不定义宏configUSE_TASK_NOTIFICATIONS)将会开启任务通知功能,有关的API函数也会被编译。设置宏configUSE_TASK_NOTIFICATIONS为0则关闭任务通知功能,相关API函数也不会被编译。默认这个功能是开启的。开启后,每个任务多增加8字节RAM。

           这是个很有用的特性,一大亮点。

           每个RTOS任务具有一个32位的通知值,RTOS任务通知相当于直接向任务发送一个事件,接收到通知的任务可以解除任务的阻塞状态(因等待任务通知而进入阻塞状态)。相对于以前必须分别创建队列、二进制信号量、计数信号量或事件组的情况,使用任务通知显然更灵活。更好的是,相比于使用信号量解除任务阻塞,使用任务通知可以快45%(使用GCC编译器,-o2优化级别)。

    18.configUSE_MUTEXES

           设置为1表示使用互斥量,设置成0表示忽略互斥量。读者应该了解在FreeRTOS中互斥量和二进制信号量的区别。

           关于互斥量和二进制信号量简单说:

     

    • 互斥型信号量必须是同一个任务申请,同一个任务释放,其他任务释放无效。
    • 二进制信号量,一个任务申请成功后,可以由另一个任务释放。
    • 互斥型信号量是二进制信号量的子集

     

    19.configUSE_RECURSIVE_MUTEXES

           设置成1表示使用递归互斥量,设置成0表示不使用。

    20.configUSE_COUNTING_SEMAPHORES

           设置成1表示使用计数信号量,设置成0表示不使用。

    21.configUSE_ALTERNATIVE_API

           设置成1表示使用“替代”队列函数('alternative' queue functions),设置成0不使用。替代API在queue.h头文件中有详细描述。

            注:“替代”队列函数已经被弃用,在新的设计中不要使用它!

    22.configCHECK_FOR_STACK_OVERFLOW

           每个任务维护自己的栈空间,任务创建时会自动分配任务需要的占内存,分配内存大小由创建任务函数(xTaskCreate())的一个参数指定。堆栈溢出是设备运行不稳定的最常见原因,因此FreeeRTOS提供了两个可选机制用来辅助检测和改正堆栈溢出。配置宏configCHECK_FOR_STACK_OVERFLOW为不同的常量来使用不同堆栈溢出检测机制。

           注意,这个选项仅适用于内存映射未分段的微处理器架构。并且,在RTOS检测到堆栈溢出发生之前,一些处理器可能先产生故障(fault)或异常(exception)来反映堆栈使用的恶化。如果宏configCHECK_FOR_STACK_OVERFLOW没有设置成0,用户必须提供一个栈溢出钩子函数,这个钩子函数的函数名和参数必须如下所示:

     

    void vApplicationStackOverflowHook(TaskHandle_t xTask, signed char *pcTaskName );

     

           参数xTask和pcTaskName为堆栈溢出任务的句柄和名字。请注意,如果溢出非常严重,这两个参数信息也可能是错误的!在这种情况下,可以直接检查pxCurrentTCb变量。

           推荐仅在开发或测试阶段使用栈溢出检查,因为堆栈溢出检测会增大上下文切换开销。

           任务切换出去后,该任务的上下文环境被保存到自己的堆栈空间,这时很可能堆栈的使用量达到了最大(最深)值。在这个时候,RTOS内核会检测堆栈指针是否还指向有效的堆栈空间。如果堆栈指针指向了有效堆栈空间之外的地方,堆栈溢出钩子函数会被调用。

           这个方法速度很快,但是不能检测到所有堆栈溢出情况(比如,堆栈溢出没有发生在上下文切换时)。设置configCHECK_FOR_STACK_OVERFLOW为1会使用这种方法。

           当堆栈首次创建时,在它的堆栈区中填充一些已知值(标记)。当任务切换时,RTOS内核会检测堆栈最后的16个字节,确保标记数据没有被覆盖。如果这16个字节有任何一个被改变,则调用堆栈溢出钩子函数。

           这个方法比第一种方法要慢,但也相当快了。它能有效捕捉堆栈溢出事件(即使堆栈溢出没有发生在上下文切换时),但是理论上它也不能百分百的捕捉到所有堆栈溢出(比如堆栈溢出的值和标记值相同,当然,这种情况发生的概率极小)。

           使用这个方法需要设置configCHECK_FOR_STACK_OVERFLOW为2.

    23.configQUEUE_REGISTRY_SIZE

           队列记录有两个目的,都涉及到RTOS内核的调试:

     

    1. 它允许在调试GUI中使用一个队列的文本名称来简单识别队列;
    2. 包含调试器需要的每一个记录队列和信号量定位信息;

     

           除了进行内核调试外,队列记录没有其它任何目的。

           configQUEUE_REGISTRY_SIZE定义可以记录的队列和信号量的最大数目。如果你想使用RTOS内核调试器查看队列和信号量信息,则必须先将这些队列和信号量进行注册,只有注册后的队列和信号量才可以使用RTOS内核调试器查看。查看API参考手册中的vQueueAddToRegistry() 和vQueueUnregisterQueue()函数获取更多信息。

    24.configUSE_QUEUE_SETS

           设置成1使能队列集功能(可以阻塞、挂起到多个队列和信号量),设置成0取消队列集功能。

    25.configUSE_TIME_SLICING(V7.5.0新增)

           默认情况下(宏configUSE_TIME_SLICING未定义或者宏configUSE_TIME_SLICING设置为1),FreeRTOS使用基于时间片的优先级抢占式调度器。这意味着RTOS调度器总是运行处于最高优先级的就绪任务,在每个RTOS 系统节拍中断时在相同优先级的多个任务间进行任务切换。如果宏configUSE_TIME_SLICING设置为0,RTOS调度器仍然总是运行处于最高优先级的就绪任务,但是当RTOS 系统节拍中断发生时,相同优先级的多个任务之间不再进行任务切换。

    26.configUSE_NEWLIB_REENTRANT(V7.5.0新增)

           如果宏configUSE_NEWLIB_REENTRANT设置为1,每一个创建的任务会分配一个newlib(一个嵌入式C库)reent结构。

    27.configENABLE_BACKWARD_COMPATIBILITY

           头文件FreeRTOS.h包含一系列#define宏定义,用来映射版本V8.0.0和V8.0.0之前版本的数据类型名字。这些宏可以确保RTOS内核升级到V8.0.0或以上版本时,之前的应用代码不用做任何修改。在FreeRTOSConfig.h文件中设置宏configENABLE_BACKWARD_COMPATIBILITY为0会去掉这些宏定义,并且需要用户确认升级之前的应用没有用到这些名字。

    28.configNUM_THREAD_LOCAL_STORAGE_POINTERS

           设置每个任务的线程本地存储指针数组大小。

           线程本地存储允许应用程序在任务的控制块中存储一些值,每个任务都有自己独立的储存空间,宏configNUM_THREAD_LOCAL_STORAGE_POINTERS指定每个任务线程本地存储指针数组的大小。API函数vTaskSetThreadLocalStoragePointer()用于向指针数组中写入值,API函数pvTaskGetThreadLocalStoragePointer()用于从指针数组中读取值。

           比如,许多库函数都包含一个叫做errno的全局变量。某些库函数使用errno返回库函数错误信息,应用程序检查这个全局变量来确定发生了那些错误。在单线程程序中,将errno定义成全局变量是可以的,但是在多线程应用中,每个线程(任务)必须具有自己独有的errno值,否则,一个任务可能会读取到另一个任务的errno值。

           FreeRTOS提供了一个灵活的机制,使得应用程序可以使用线程本地存储指针来读写线程本地存储。具体参见后续文章《FreeRTOS系列第12篇---FreeRTOS任务应用函数》。

    29.configGENERATE_RUN_TIME_STATS

           设置宏configGENERATE_RUN_TIME_STATS为1使能运行时间统计功能。一旦设置为1,则下面两个宏必须被定义:

     

    1. portCONFIGURE_TIMER_FOR_RUN_TIME_STATS():用户程序需要提供一个基准时钟函数,函数完成初始化基准时钟功能,这个函数要被define到宏portCONFIGURE_TIMER_FOR_RUN_TIME_STATS()上。这是因为运行时间统计需要一个比系统节拍中断频率还要高分辨率的基准定时器,否则,统计可能不精确。基准定时器中断频率要比统节拍中断快10~100倍。基准定时器中断频率越快,统计越精准,但能统计的运行时间也越短(比如,基准定时器10ms中断一次,8位无符号整形变量可以计到2.55秒,但如果是1秒中断一次,8位无符号整形变量可以统计到255秒)。
    2. portGET_RUN_TIME_COUNTER_VALUE():用户程序需要提供一个返回基准时钟当前“时间”的函数,这个函数要被define到宏portGET_RUN_TIME_COUNTER_VALUE()上。

     

           举一个例子,假如我们配置了一个定时器,每500us中断一次。在定时器中断服务例程中简单的使长整形变量ulHighFrequencyTimerTicks自增。那么上面提到两个宏定义如下(可以在FreeRTOSConfig.h中添加):

     

        extern volatile unsigned longulHighFrequencyTimerTicks;
        #define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() ( ulHighFrequencyTimerTicks = 0UL )
        #define portGET_RUN_TIME_COUNTER_VALUE() ulHighFrequencyTimerTicks

     

    30.configUSE_CO_ROUTINES

           设置成1表示使用协程,0表示不使用协程。如果使用协程,必须在工程中包含croutine.c文件。

           注:协程(Co-routines)主要用于资源发非常受限的嵌入式系统(RAM非常少),通常不会用于32位微处理器。

           在当前嵌入式硬件环境下,不建议使用协程,FreeRTOS的开发者早已经停止开发协程。

    31.configMAX_CO_ROUTINE_PRIORITIES

           应用程序协程(Co-routines)的有效优先级数目,任何数目的协程都可以共享一个优先级。使用协程可以单独的分配给任务优先级。见configMAX_PRIORITIES。

    32.configUSE_TIMERS

           设置成1使用软件定时器,为0不使用软件定时器功能。详细描述见FreeRTOS software timers 。

    33.configTIMER_TASK_PRIORITY

           设置软件定时器服务/守护进程的优先级。详细描述见FreeRTOS software timers 。

    34.configTIMER_QUEUE_LENGTH

           设置软件定时器命令队列的长度。详细描述见FreeRTOS software timers。

    35.configTIMER_TASK_STACK_DEPTH

           设置软件定时器服务/守护进程任务的堆栈深度,详细描述见FreeRTOS software timers 。

    36.configKERNEL_INTERRUPT_PRIORITY、configMAX_SYSCALL_INTERRUPT_PRIORITY和configMAX_API_CALL_INTERRUPT_PRIORITY

           这是移植和应用FreeRTOS出错最多的地方,所以需要打起精神仔细读懂。

           Cortex-M3、PIC24、dsPIC、PIC32、SuperH和RX600硬件设备需要设置宏configKERNEL_INTERRUPT_PRIORITY;PIC32、RX600和Cortex-M硬件设备需要设置宏configMAX_SYSCALL_INTERRUPT_PRIORITY。

           configMAX_SYSCALL_INTERRUPT_PRIORITY和configMAX_API_CALL_INTERRUPT_PRIORITY,这两个宏是等价的,后者是前者的新名字,用于更新的移植层代码。

           注意下面的描述中,在中断服务例程中仅可以调用以“FromISR”结尾的API函数。

     

    • 仅需要设置configKERNEL_INTERRUPT_PRIORITY的硬件设备(也就是宏configMAX_SYSCALL_INTERRUPT_PRIORITY不会用到):configKERNEL_INTERRUPT_PRIORITY用来设置RTOS内核自己的中断优先级。调用API函数的中断必须运行在这个优先级;不调用API函数的中断,可以运行在更高的优先级,所以这些中断不会被因RTOS内核活动而延时。 
    • configKERNEL_INTERRUPT_PRIORITY和configMAX_SYSCALL_INTERRUPT_PRIORITY都需要设置的硬件设备:configKERNEL_INTERRUPT_PRIORITY用来设置RTOS内核自己的中断优先级。因为RTOS内核中断不允许抢占用户使用的中断,因此这个宏一般定义为硬件最低优先级。configMAX_SYSCALL_INTERRUPT_PRIORITY用来设置可以在中断服务程序中安全调用FreeRTOS API函数的最高中断优先级。优先级小于等于这个宏所代表的优先级时,程序可以在中断服务程序中安全的调用FreeRTOS API函数;如果优先级大于这个宏所代表的优先级,表示FreeRTOS无法禁止这个中断,在这个中断服务程序中绝不可以调用任何API函数。

     

           通过设置configMAX_SYSCALL_INTERRUPT_PRIORITY的优先级级别高于configKERNEL_INTERRUPT_PRIORITY可以实现完整的中断嵌套模式。这意味着FreeRTOS内核不能完全禁止中断,即使在临界区。此外,这对于分段内核架构的微处理器是有利的。请注意,当一个新中断发生后,某些微处理器架构会(在硬件上)禁止中断,这意味着从硬件响应中断到FreeRTOS重新使能中断之间的这段短时间内,中断是不可避免的被禁止的。

           不调用API的中断可以运行在比configMAX_SYSCALL_INTERRUPT_PRIORITY高的优先级,这些级别的中断不会被FreeRTOS禁止,因此不会因为执行RTOS内核而被延时。

           例如:假如一个微控制器有8个中断优先级别:0表示最低优先级,7表示最高优先级(Cortex-M3和Cortex-M4内核优先数和优先级别正好与之相反,后续文章会专门介绍它们)。当两个配置选项分别为4和0时,下图描述了每一个优先级别可以和不可做的事件:

     

    • configMAX_SYSCALL_INTERRUPT_PRIORITY=4
    • configKERNEL_INTERRUPT_PRIORITY=0

     

     

     

           这些配置参数允许非常灵活的中断处理:

           在系统中可以像其它任务一样为中断处理任务分配优先级。这些任务通过一个相应中断唤醒。中断服务例程(ISR)内容应尽可能的精简---仅用于更新数据然后唤醒高优先级任务。ISR退出后,直接运行被唤醒的任务,因此中断处理(根据中断获取的数据来进行的相应处理)在时间上是连续的,就像ISR在完成这些工作。这样做的好处是当中断处理任务执行时,所有中断都可以处在使能状态。

           中断、中断服务例程(ISR)和中断处理任务是三码事:当中断来临时会进入中断服务例程,中断服务例程做必要的数据收集(更新),之后唤醒高优先级任务。这个高优先级任务在中断服务例程结束后立即执行,它可能是其它任务也可能是中断处理任务,如果是中断处理任务,那么就可以根据中断服务例程中收集的数据做相应处理。

           configMAX_SYSCALL_INTERRUPT_PRIORITY接口有着更深一层的意义:在优先级介于RTOS内核中断优先级(等于configKERNEL_INTERRUPT_PRIORITY)和configMAX_SYSCALL_INTERRUPT_PRIORITY之间的中断允许全嵌套中断模式并允许调用API函数。大于configMAX_SYSCALL_INTERRUPT_PRIORITY的中断优先级绝不会因为执行RTOS内核而延时。

           运行在大于configMAX_SYSCALL_INTERRUPT_PRIORITY的优先级中断是不会被RTOS内核所屏蔽的,因此也不受RTOS内核功能影响。这主要用于非常高的实时需求中。比如执行电机转向。但是,这类中断的中断服务例程中绝不可以调用FreeRTOS的API函数。

          为了使用这个方案,应用程序要必须符合以下规则:调用FreeRTOS API函数的任何中断,都必须和RTOS内核处于同一优先级(由宏configKERNEL_INTERRUPT_PRIORITY设置),或者小于等于宏configMAX_SYSCALL_INTERRUPT_PRIORITY定义的优先级。

    37.configASSERT

           断言,调试时可以检查传入的参数是否合法。FreeRTOS内核代码的关键点都会调用configASSERT( x )函数,如果参数x为0,则会抛出一个错误。这个错误很可能是传递给FreeRTOS API函数的无效参数引起的。定义configASSERT()有助于调试时发现错误,但是,定义configASSERT()也会增大应用程序代码量,增大运行时间。推荐在开发阶段使用这个断言宏。

           举一个例子,我们想把非法参数所在的文件名和代码行数打印出来,可以先定义一个函数vAssertCalled,该函数有两个参数,分别接收触发configASSERT宏的文件名和该宏所在行,然后通过显示屏或者串口输出。代码如下:

     

        #define configASSERT( ( x ) )     if( ( x ) == 0 )vAssertCalled( __FILE__, __LINE__ )

     

           这里__FILE__和__LINE__是大多数编译器预定义的宏,分别表示代码所在的文件名(字符串格式)和行数(整形)。

           这个例子虽然看起来很简单,但由于要把整形__LINE__转换成字符串再显示,在效率和实现上,都不能让人满意。我们可以使用C标准库assert的实现方法,这样函数vAssertCalled只需要接收一个字符串形式的参数(推荐仔细研读下面的代码并理解其中的技巧):

     

         #define STR(x)  VAL(x)
         #define VAL(x)  #x
         #define configASSERT(x) ((x)?(void) 0 :xAssertCalld(__FILE__ ":" STR(__LINE__) " " #x"\n"))

     

           这里稍微讲解一下,由于内置宏__LINE__是整数型的而不是字符串型,把它转化成字符串需要一个额外的处理层。宏STR和和宏VAL正是用来辅助完成这个转化。宏STR用来把整形行号替换掉__LINE__,宏VAL用来把这个整形行号字符串化。忽略宏STR和VAL中的任何一个,只能得到字符串”__LINE__”,这不是我们想要的。

           这里使用三目运算符’?:’来代替参数判断if语句,这样可以接受任何参数或表达式,代码也更紧凑,更重要的是代码优化度更高,因为如果参数恒为真,在编译阶段就可以去掉不必要的输出语句。

    38.INCLUDE Parameters

           以“INCLUDE”起始的宏允许用户不编译那些应用程序不需要的实时内核组件(函数),这可以确保在你的嵌入式系统中RTOS占用最少的ROM和RAM。

    每个宏以这样的形式出现:

           INCLUDE_FunctionName

          在这里FunctionName表示一个你可以控制是否编译的API函数。如果你想使用该函数,就将这个宏设置成1,如果不想使用,就将这个宏设置成0。比如,对于API函数vTaskDelete():

     

    #define INCLUDE_vTaskDelete    1

     

           表示希望使用vTaskDelete(),允许编译器编译该函数

     

    #define INCLUDE_vTaskDelete    0

     

           表示禁止编译器编译该函数。

    展开全文
  • FreeRTOS高级篇2---FreeRTOS任务创建分析

    万次阅读 多人点赞 2016-05-03 13:31:08
    FreeRTOS基础系列《FreeRTOS系列第10篇---FreeRTOS任务创建和删除》中介绍了任务创建API函数xTaskCreate(),我们这里先回顾一下这个函数的声明: BaseType_t xTaskCreate( TaskFunction_tp vTaskCode,

          在FreeRTOS基础系列《FreeRTOS系列第10篇---FreeRTOS任务创建和删除》中介绍了任务创建API函数xTaskCreate(),我们这里先回顾一下这个函数的声明:

     

            BaseType_t xTaskCreate(
                                TaskFunction_tp vTaskCode,
                                const char * constpcName,
                                unsigned short usStackDepth,
                                void *pvParameters,
                                UBaseType_t uxPriority,
                                TaskHandle_t *pvCreatedTask
                              );

     

          这个API函数的作用是创建新的任务并将它加入到任务就绪列表,函数参数含义为:

     

    • pvTaskCode:函数指针,指向任务函数的入口。任务永远不会返回(位于死循环内)。该参数类型TaskFunction_t定义在文件projdefs.h中,定义为:typedef void(*TaskFunction_t)( void * ),即参数为空指针类型并返回空类型。
    • pcName:任务描述。主要用于调试。字符串的最大长度(包括字符串结束字符)由宏configMAX_TASK_NAME_LEN指定,该宏位于FreeRTOSConfig.h文件中。
    • usStackDepth:指定任务堆栈大小,能够支持的堆栈变量数量(堆栈深度),而不是字节数。比如,在16位宽度的堆栈下,usStackDepth定义为100,则实际使用200字节堆栈存储空间。堆栈的宽度乘以深度必须不超过size_t类型所能表示的最大值。比如,size_t为16位,则可以表示堆栈的最大值是65535字节。这是因为堆栈在申请时是以字节为单位的,申请的字节数就是堆栈宽度乘以深度,如果这个乘积超出size_t所表示的范围,就会溢出,分配的堆栈空间也不是我们想要的。
    • pvParameters:指针,当任务创建时,作为一个参数传递给任务。
    • uxPriority:任务的优先级。具有MPU支持的系统,可以通过置位优先级参数的portPRIVILEGE_BIT位,随意的在特权(系统)模式下创建任务。比如,创建一个优先级为2的特权任务,参数uxPriority可以设置为 ( 2 | portPRIVILEGE_BIT )。
    • pvCreatedTask:用于回传一个句柄(ID),创建任务后可以使用这个句柄引用任务。

     

          虽然xTaskCreate()看上去很像函数,但其实是一个宏,真正被调用的函数是xTaskGenericCreate(),xTaskCreate()宏定义如下所示:

     

    #define xTaskCreate( pvTaskCode, pcName, usStackDepth,pvParameters, uxPriority, pxCreatedTask )    \
          xTaskGenericCreate( ( pvTaskCode ),( pcName ), ( usStackDepth ), ( pvParameters ), ( uxPriority ), ( pxCreatedTask), ( NULL ), ( NULL ), ( NULL ) )

     

          可以看到,xTaskCreate比xTaskGenericCreate少了三个参数,在宏定义中,这三个参数被设置为NULL。这三个参数用于使用静态变量的方法分配堆栈、任务TCB空间以及设置MPU相关的参数。一般情况下,这三个参数是不使用的,所以任务创建宏xTaskCreate定义的时候,将这三个参数对用户隐藏了。接下来的章节中,为了方便,我们还是称xTaskCreate()为函数,虽然它是一个宏定义。

          上面我们提到了任务TCB(任务控制块),这是一个需要重点介绍的关键点。它用于存储任务的状态信息,包括任务运行时的环境。每个任务都有自己的任务TCB。任务TCB是一个相对比较大的数据结构,这也是情理之中的,因为与任务相关的代码占到整个FreeRTOS代码量的一半左右,这些代码大都与任务TCB相关,我们先来介绍一下任务TCB数据结构的定义:

     

    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;

     

          下面我们详细的介绍这个数据结构的主要成员:

          指针pxTopOfStack必须位于结构体的第一项,指向当前堆栈的栈顶,对于向下增长的堆栈,pxTopOfStack总是指向最后一个入栈的项目。

          如果使用MPU,xMPUSettings必须位于结构体的第二项,用于MPU设置。

          接下来是状态列表项xStateListItem和事件列表项xEventListItem,我们在上一章介绍列表和列表项的文章中提到过:列表被FreeRTOS调度器使用,用于跟踪任务,处于就绪、挂起、延时的任务,都会被挂接到各自的列表中。调度器就是通过把任务TCB中的状态列表项xStateListItem和事件列表项xEventListItem挂接到不同的列表中来实现上述过程的。在task.c中,定义了一些静态列表变量,其中有就绪、阻塞、挂起列表,例如当某个任务处于就绪态时,调度器就将这个任务TCB的xStateListItem列表项挂接到就绪列表。事件列表项也与之类似,当队列满的情况下,任务因入队操作而阻塞时,就会将事件列表项挂接到队列的等待入队列表上。

          uxPriority用于保存任务的优先级,0为最低优先级。任务创建时,指定的任务优先级就被保存到该变量中。

          指针pxStack指向堆栈的起始位置,任务创建时会分配指定数目的任务堆栈,申请堆栈内存函数返回的指针就被赋给该变量。很多刚接触FreeRTOS的人会分不清指针pxTopOfStack和pxStack的区别,这里简单说一下:pxTopOfStack指向当前堆栈栈顶,随着进栈出栈,pxTopOfStack指向的位置是会变化的;pxStack指向当前堆栈的起始位置,一经分配后,堆栈起始位置就固定了,不会被改变了。那么为什么需要pxStack变量呢,这是因为随着任务的运行,堆栈可能会溢出,在堆栈向下增长的系统中,这个变量可用于检查堆栈是否溢出;如果在堆栈向上增长的系统中,要想确定堆栈是否溢出,还需要另外一个变量pxEndOfStack来辅助诊断是否堆栈溢出,后面会讲到这个变量。

          字符数组pcTaskName用于保存任务的描述或名字,在任务创建时,由参数指定。名字的长度由宏configMAX_TASK_NAME_LEN(位于FreeRTOSConfig.h中)指定,包含字符串结束标志。

          如果堆栈向上生长(portSTACK_GROWTH > 0),指针pxEndOfStack指向堆栈尾部,用于检验堆栈是否溢出。

          变量uxCriticalNesting用于保存临界区嵌套深度,初始值为0。

          接下来两个变量用于可视化追踪,仅当宏configUSE_TRACE_FACILITY(位于FreeRTOSConfig.h中)为1时有效。变量uxTCBNumber存储一个数值,在创建任务时由内核自动分配数值(通常每创建一个任务,值增加1),每个任务的uxTCBNumber值都不同,主要用于调试。变量uxTaskNumber用于存储一个特定值,与变量uxTCBNumber不同,uxTaskNumber的数值不是由内核分配的,而是通过API函数vTaskSetTaskNumber()来设置的,数值由函数参数指定。

          如果使用互斥量(configUSE_MUTEXES == 1),任务优先级被临时提高时,变量uxBasePriority用来保存任务原来的优先级。

          变量ucStaticAllocationFlags也需要说明一下,我们前面说过任务创建API函数xTaskCreate()只能使用动态内存分配的方式创建任务堆栈和任务TCB,如果要使用静态变量实现任务堆栈和任务TCB就需要使用函数xTaskGenericCreate()来实现。如果任务堆栈或任务TCB由静态数组和静态变量实现,则将该变量设置为pdTRUE(任务堆栈空间由静态数组变量实现时为0x01,任务TCB由静态变量实现时为0x02,任务堆栈和任务TCB都由静态变量实现时为0x03),如果堆栈是动态分配的,则将该变量设置为pdFALSE。

          到这里任务TCB的数据结构就讲完了,下面我们用一个例子来讲述任务创建的过程,为方便起见,假设被创建的任务叫“任务A”,任务函数为vTask_A():

     

        TaskHandle_t xHandle;
        xTaskCreate(vTask_A,”Task A”,120,NULL,1,&xHandle);

     

          这里创建了一个任务,任务优先级为1,由于硬件平台是32为架构,所以指定了120*4=480字节的任务堆栈,向任务函数vTask_A()传递的参数为空(NULL),任务句柄由变量xHandle保存。当这个语句执行后,任务A被创建并加入就绪任务列表,我们这章的主要目的,就是看看这个语句在执行过程中,发生了什么事情。

    1.创建任务堆栈和任务TCB

          调用函数prvAllocateTCBAndStack()创建任务堆栈和任务TCB。有两种方式创建任务堆栈和任务TCB,一种是使用动态内存分配方法,这样当任务删除时,任务堆栈和任务控制块空间会被释放,可用于其它任务;另一种是使用静态变量来实现,在创建任务前定义好全局或者静态堆栈数组和任务控制块变量,在调用创建任务API函数时,将这两个变量以参数的形式传递给任务创建函数xTaskGenericCreate()。如果使用默认的xTaskCreate()创建任务函数,则使用动态内存分配,因为与静态内存分配有关的参数不可见(在本文一开始我们说过xTaskCreate()其实是一个带参数的宏定义,真正被执行的函数是xTaskGenericCreate(),参考宏xTaskCreate()的定义可以知道,xTaskCreate()对外隐藏了使用静态内存分配的参数,在调用xTaskGenericCreate()时,这些参数被设置为NULL)。

          任务堆栈成功分配后,经过对齐的堆栈起始地址被保存到任务TCB的pxStack字段。如果使能堆栈溢出检查或者使用可视化追踪功能,则使用固定值tskSTACK_FILL_BYTE(0xa5)填充堆栈。

          函数prvAllocateTCBAndStack()的源码去除断言和不常用的条件编译后如下所示:

     

    static TCB_t *prvAllocateTCBAndStack( const uint16_t usStackDepth, StackType_t * const puxStackBuffer, TCB_t * const pxTaskBuffer )
    {
    TCB_t *pxNewTCB;
    StackType_t *pxStack;
     
        /* 分配堆栈空间*/
        pxStack = ( StackType_t * ) pvPortMallocAligned( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ), puxStackBuffer );
        if( pxStack != NULL )
        {
            /* 分配TCB空间 */
            pxNewTCB = ( TCB_t * ) pvPortMallocAligned( sizeof( TCB_t ), pxTaskBuffer );
     
            if( pxNewTCB != NULL )
            {
                /* 将堆栈起始位置存入TCB*/
                pxNewTCB->pxStack = pxStack;
            }
            else
            {
                /* 如果TCB分配失败,释放之前申请的堆栈空间 */
                if( puxStackBuffer == NULL )
                {
                    vPortFree( pxStack );
                }
            }
        }
        else
        {
            pxNewTCB = NULL;
        }
     
        if( pxNewTCB != NULL )
        {
            /* 如果需要,使用固定值填充堆栈 */
            #if( ( configCHECK_FOR_STACK_OVERFLOW> 1 ) || ( configUSE_TRACE_FACILITY == 1 ) || ( INCLUDE_uxTaskGetStackHighWaterMark== 1 ) )
            {
                /* 仅用于调试 */
                ( void ) memset( pxNewTCB->pxStack, ( int ) tskSTACK_FILL_BYTE, ( size_t ) usStackDepth * sizeof( StackType_t ) );
            }
            #endif
        }
     
        return pxNewTCB;
    }

     

    2.初始化任务TCB必要的字段

          调用函数prvInitialiseTCBVariables()初始化任务TCB必要的字段。在调用创建任务API函数xTaskCreate()时,参数pcName(任务描述)、uxPriority(任务优先级)都会被写入任务TCB相应的字段,TCB字段中的xStateListItem和xEventListItem列表项也会被初始化,初始化后的列表项如图2-1所示。在图2-1中,列表项xEventListItem的成员列表项值xItemValue被初始为4,这是因为我在应用中设置的最大优先级数目(configMAX_PRIORITIES)为5,而xEventListItem. xItemValue等于configMAX_PRIORITIES减去任务A的优先级(为1),即5-1=4。这一点很重要,在这里xItemValue不是直接保存任务优先级,而是保存优先级的补数,这意味着xItemValue的值越大,对应的任务优先级越小。FreeRTOS内核使用vListInsert函数(详细见高级篇第一章)将事件列表项插入到一个列表,这个函数根据xItemValue的值的大小顺序来进行插入操作。使用宏listGET_OWNER_OF_HEAD_ENTRY获得列表中的第一个列表项的xItemValue值总是最小,也就是优先级最高的任务!

     

    图2-1:初始化状态和事件列表项

          此外,TCB其它的一些字段也被初始化,比如临界区嵌套次数、运行时间计数器、任务通知值、任务通知状态等,函数prvInitialiseTCBVariables()的源码如下所示:

     

    static void prvInitialiseTCBVariables( TCB_t * const pxTCB, const char * const pcName, UBaseType_t uxPriority,   \
                                  const MemoryRegion_t * const xRegions, const uint16_t usStackDepth )
    {
    UBaseType_t x;
     
        /* 将任务描述存入TCB */
        for( x = ( UBaseType_t ) 0; x < ( UBaseType_t ) configMAX_TASK_NAME_LEN; x++ )
        {
            pxTCB->pcTaskName[ x ] = pcName[ x ];
            if( pcName[ x ] == 0x00 )
            {
                break;
            }
        }
        /* 确保字符串有结束 */
        pxTCB->pcTaskName[ configMAX_TASK_NAME_LEN - 1 ] = '\0';
     
        /* 调整优先级,宏configMAX_PRIORITIES的值在FreeRTOSConfig.h中设置 */
        if( uxPriority >= ( UBaseType_t ) configMAX_PRIORITIES )
        {
            uxPriority = ( UBaseType_t ) configMAX_PRIORITIES - ( UBaseType_t ) 1U;
        }
     
        pxTCB->uxPriority = uxPriority;
        #if ( configUSE_MUTEXES == 1 )              /*使用互斥量*/
        {  
            pxTCB->uxBasePriority = uxPriority;
            pxTCB->uxMutexesHeld = 0;
        }
        #endif /* configUSE_MUTEXES */
       
        /*初始化列表项*/
        vListInitialiseItem( &( pxTCB->xStateListItem ) );
        vListInitialiseItem( &( pxTCB->xEventListItem ) );
     
        /* 设置列表项xStateListItem的成员pvOwner指向当前任务控制块 */
        listSET_LIST_ITEM_OWNER( &( pxTCB->xStateListItem ), pxTCB );
     
        /* 设置列表项xEventListItem的成员xItemValue*/
        listSET_LIST_ITEM_VALUE( &( pxTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) uxPriority );
        /* 设置列表项xEventListItem的成员pvOwner指向当前任务控制块 */
        listSET_LIST_ITEM_OWNER( &( pxTCB->xEventListItem ), pxTCB );
     
        #if ( portCRITICAL_NESTING_IN_TCB ==1 )    /*使能临界区嵌套功能*/
        {  
            pxTCB->uxCriticalNesting = ( UBaseType_t ) 0U;
        }
        #endif /* portCRITICAL_NESTING_IN_TCB */
     
        #if ( configUSE_APPLICATION_TASK_TAG == 1 ) /*使能任务标签功能*/
        {  
            pxTCB->pxTaskTag = NULL;
        }
        #endif /* configUSE_APPLICATION_TASK_TAG */
     
        #if ( configGENERATE_RUN_TIME_STATS == 1 )  /*使能事件统计功能*/
        {
            pxTCB->ulRunTimeCounter = 0UL;
        }
        #endif /* configGENERATE_RUN_TIME_STATS */
     
        #if ( portUSING_MPU_WRAPPERS == 1 )         /*使用MPU功能*/
        {
            vPortStoreTaskMPUSettings( &( pxTCB->xMPUSettings ), xRegions, pxTCB->pxStack, usStackDepth );
        }
        #else /* portUSING_MPU_WRAPPERS */
        {
            ( void ) xRegions;
            ( void ) usStackDepth;
        }
        #endif /* portUSING_MPU_WRAPPERS */
     
        #if( configNUM_THREAD_LOCAL_STORAGE_POINTERS != 0 )/*使能线程本地存储指针*/
        {
            for( x = 0; x < ( UBaseType_t )configNUM_THREAD_LOCAL_STORAGE_POINTERS; x++ )
            {
                pxTCB->pvThreadLocalStoragePointers[ x ] = NULL;
            }
        }
        #endif
     
        #if ( configUSE_TASK_NOTIFICATIONS == 1 )   /*使能任务通知功能*/
        {
            pxTCB->ulNotifiedValue = 0;
            pxTCB->ucNotifyState = taskNOT_WAITING_NOTIFICATION;
        }
        #endif
     
        #if ( configUSE_NEWLIB_REENTRANT == 1 )     /*使用Newlib*/
        {
            _REENT_INIT_PTR( ( &( pxTCB->xNewLib_reent ) ) );
        }
        #endif
     
        #if( INCLUDE_xTaskAbortDelay == 1 )
        {
            pxTCB->ucDelayAborted = pdFALSE;
        }
        #endif
    }

     

    3.初始化任务堆栈

          调用函数pxPortInitialiseStack()初始化任务堆栈,并将最新的栈顶指针赋值给任务TCB的pxTopOfStack字段。

          调用函数pxPortInitialiseStack()后,相当于执行了一次系统节拍时钟中断:将一些重要寄存器入栈。虽然任务还没开始执行,也并没有中断发生,但看上去就像寄存器已经被入栈了,并且部分堆栈值被修改成了我们需要的已知值。对于不同的硬件架构,入栈的寄存器也不相同,所以我们看到这个函数是由移植层提供的。对于Cortex-M3架构,需要依次入栈xPSR、PC、LR、R12、R3~R0、R11~R4,假设堆栈是向下生长的,初始化后的堆栈如图3-1所示。

          在图3-1中我们看到寄存器xPSR被初始为0x01000000,其中bit24被置1,表示使用Thumb指令;寄存器PC被初始化为任务函数指针vTask_A,这样当某次任务切换后,任务A获得CPU控制权,任务函数vTask_A被出栈到PC寄存器,之后会执行任务A的代码;LR寄存器初始化为函数指针prvTaskExitError,这是由移植层提供的一个出错处理函数。当中断发生时,LR被设置成中断要返回的地址,但是每个任务都是一个死循环,正常情况下不应该退出任务函数,所以一旦从任务函数退出,说明那里出错了,这个时候会调用寄存器LR指向的函数来处理这个错误,即prvTaskExitError;根据ATPCS(ARM-Thumb过程调用标准),我们知道子函数调用通过寄存器R0~R3传递参数,在文章的最开始讲xTaskCreate()函数时,提到这个函数有一个空指针类型的参数pvParameters,当任务创建时,它作为一个参数传递给任务,所以这个参数被保存到R0中,用来向任务传递参数。

          任务TCB结构体成员pxTopOfStack表示当前堆栈的栈顶,它指向最后一个入栈的项目,所以在图中它指向R4,TCB结构体另外一个成员pxStack表示堆栈的起始位置,所以在图中它指向堆栈的最开始处。

     

    图3-1:初始化任务堆栈

    4.进入临界区

          调用taskENTER_CRITICAL()进入临界区,这是一个宏定义,最终进入临界区的代码由移植层提供。

    5.当前任务数量增加1

          在tasks.c中 ,定义了一些静态私有变量,用来跟踪任务的数量或者状态等等,其中变量uxCurrentNumberOfTasks表示当前任务的总数量,每创建一个任务,这个变量都会增加1。

    6.为第一次运行做必要的初始化

          如果这是第一个任务(uxCurrentNumberOfTasks等于1),则调用函数prvInitialiseTaskLists()初始化任务列表。FreeRTOS使用列表来跟踪任务,在tasks.c中,定义了静态类型的列表变量:

     

    PRIVILEGED_DATAstatic List_t pxReadyTasksLists[ configMAX_PRIORITIES ];/*按照优先级排序的就绪态任务*/
    PRIVILEGED_DATAstatic List_t xDelayedTaskList1;                        /*延时的任务 */
    PRIVILEGED_DATAstatic List_t xDelayedTaskList2;                        /*延时的任务 */
    PRIVILEGED_DATAstatic List_t xPendingReadyList;                        /*任务已就绪,但调度器被挂起 */
     
    #if (INCLUDE_vTaskDelete == 1 )
        PRIVILEGED_DATA static List_t xTasksWaitingTermination;             /*任务已经被删除,但内存尚未释放*/
    #endif
     
    #if (INCLUDE_vTaskSuspend == 1 )
        PRIVILEGED_DATA static List_t xSuspendedTaskList;                   /*当前挂起的任务*/
    #endif

     

          现在这些列表都要进行初始化,会调用API函数vListInitialise()初始化列表,这个函数在《FreeRTOS高级篇1---FreeRTOS列表和列表项》中讲过,每个列表的初始化方式都是相同的,以就绪态列表pxReadyTasksLists[0]为例,初始化后如图6-1所示:

     

    图6-1:初始化后的列表

          函数prvInitialiseTaskLists()的源代码如下所示:

     

    static void prvInitialiseTaskLists( void )
    {
    UBaseType_tuxPriority;
     
        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 */
     
        /* Start with pxDelayedTaskList using list1 and the pxOverflowDelayedTaskListusing list2. */
        pxDelayedTaskList = &xDelayedTaskList1;
        pxOverflowDelayedTaskList = &xDelayedTaskList2;
    }

     

    7.更新当前正在运行的任务TCB指针

          tasks.c中定义了一个任务TCB指针型变量:

          PRIVILEGED_DATA TCB_t * volatile pxCurrentTCB= NULL;

          这是一个全局变量,在tasks.c中只定义了这一个全局变量。这个变量用来指向当前正在运行的任务TCB,我们需要多了解一下这个变量。FreeRTOS的核心是确保处于优先级最高的就绪任务获得CPU运行权。在下一章讲述任务切换时会知道,任务切换就是找到优先级最高的就绪任务,而找出的这个最高优先级任务的TCB,就被赋给变量pxCurrentTCB。

          如果调度器还没有准备好(程序刚开始运行时,可能会先创建几个任务,之后才会启动调度器),并且新创建的任务优先级大于变量pxCurrentTCB指向的任务优先级,则设置pxCurrentTCB指向当前新创建的任务TCB(确保pxCurrentTCB指向优先级最高的就绪任务)。

     

    if( xSchedulerRunning == pdFALSE )
    {
        if( pxCurrentTCB->uxPriority <= uxPriority )
        {
            pxCurrentTCB = pxNewTCB;
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
    }

     

    8.将新创建的任务加入就绪列表数组

          调用prvAddTaskToReadyList(pxNewTCB)将创建的任务TCB加入到就绪列表数组中,任务的优先级确定了加入到就绪列表数组的哪个下标。比如我们新创建的任务优先级为1,则这个任务被加入到列表pxReadyTasksLists[1]中。

           prvAddTaskToReadyList()其实是一个宏,由一系列语句组成,去除其中的跟踪宏外,这个宏定义如下所示:

     

    #defineprvAddTaskToReadyList( pxTCB )                        \
        taskRECORD_READY_PRIORITY( ( pxTCB)->uxPriority );       \
        vListInsertEnd( &( pxReadyTasksLists[ (pxTCB )->uxPriority ] ), &( ( pxTCB )->xStateListItem ) );

     

          宏taskRECORD_READY_PRIORITY()用来更新变量uxTopReadyPriority,这个变量在tasks.c中定义为静态变量,记录处于就绪态的最高任务优先级。这个变量参与了FreeRTOS的最核心代码:确保处于优先级最高的就绪任务获得CPU运行权。它在这里参与如何最快的找到优先级最高的就绪任务。为了最快,不同的架构会各显神通,一些架构还有特殊指令可用,所以这个宏由移植层提供。我们会在下一章介绍任务切换时,以Cortex-M3架构为例,详细介绍如何最快的找到优先级最高的就绪任务。

          函数vListInsertEnd()将列表项插入到列表末端,在《FreeRTOS高级篇1---FreeRTOS列表和列表项》中已经提到过,这里会结合着例子再看一下这个函数。从前面我们直到,在调用函数vListInsertEnd()之前,就绪列表pxReadyTasksLists[1]和任务TCB的状态列表项xStateListItem都已经初始化好了,见图6-1和图2-1,为了方便查看,我们将这两幅图合成一副,见图8-1。

     

    图8-1:初始化后的列表和列表项

             调用vListInsertEnd(a,b)会将列表项b,插入到列表a的后面,函数执行完毕后,列表和列表项的关系如图8-2所示。

     

    图8-2:插入一个列表项后的列表

          在此基础上,假设又创建了任务B,任务A和任务B优先级相同,都为1。和任务A一样,任务B也有它自己的任务TCB,其中的状态列表项字段xStateListItem也要插入到列表pxReadyTasksLists[1]中,新的列表和列表项如图8-3所示。

     

    图8-3:相同优先级就绪列表挂接两个列表项

    9.退出临界区

          调用taskEXIT_CRITICAL()退出临界区,这是一个宏定义,最终退出临界区的代码由移植层提供。

    10.执行上下文切换

        如果上面的步骤都正确执行,并且调度器也开始工作,则判断当前任务的优先级是否大于新创建的任务优先级。如果新创建的任务优先级更高,则调用taskYIELD_IF_USING_PREEMPTION()强制进行一次上下文切换,切换后,新创建的任务将获得CPU控制权,精简后的代码如下所示。

     

     if( xReturn == pdPASS )
        {
            if( xSchedulerRunning != pdFALSE )
            {
                /* 如果新创建的任务优先级大于当前任务优先级,则新创建的任务应该被立即执行。*/
                if(pxCurrentTCB->uxPriority < uxPriority )
                {
                    taskYIELD_IF_USING_PREEMPTION();
                }
            }
        }

     

     

     

    展开全文
  • FreeRTOS高级篇1---FreeRTOS列表和列表项

    万次阅读 多人点赞 2016-04-19 14:34:13
    FreeRTOS内核调度大量使用了列表(list)这一数据结构。我们如果想一探FreeRTOS背后的运行机制,首先遇到的拦路虎就是列表。对于FreeRTOS内核来说,列表就是它最基础的部分。我们在这一章集中讲解列表和列表项的结构...
  • FreeRTOS includes libraries that are part of the FreeRTOS 202012.00 LTS release. Learn more about the FreeRTOS 202012.00 LTS libraries at https://freertos.org/lts-libraries.html. Getting started The ...
  • FreeRTOS源码20201200

    2020-12-31 14:39:46
    FreeRTOS源码程序包含FreeRTOS源码、FreeRTOS-Plus源码
  • FreeRTOS学习

    2019-01-18 11:36:16
    最好用的嵌入式操作系统FreeRTOS学习资料,系统调用讲解,源码分析。
  • freeRTOS指南

    2017-09-22 21:07:29
    是否要在系统中使用FreeRTOS,虽然我想要的也仅仅是一个实时内核,当然更重要的 是免费。之所以翻译这篇文章倒不是因为FreeRTOS有多么优秀,完全是因为这篇文章 还不算太长。而且FreeRTOS.net仿佛致力于这个内核在...
  • FreeRTOS中文版

    2020-09-30 10:56:07
    FREERTOS 实时内核实用指南,FreeRTOS中文手册,可供下载 FREERTOS 实时内核实用指南,FreeRTOS中文手册,可供下载
  • freertos插件 在使用FreeRTOS超过8年之后,我决定开始添加我希望一开始就可以使用的功能和实现。 当前功能 C ++包装器 封装了FreeRTOS功能的C ++包装器的集合,允许您在仍使用FreeRTOS的情况下用C ++编写RTOS应用...
  • FreeRTOS系统配置

    千次阅读 2017-08-23 01:43:04
    FreeRTOS
  • FreeRTOS移植

    2018-05-06 22:29:27
    FreeRTOS移植到stm32f429(编辑器是MDK5)。。。。。。。
  • trace FreeRTOS

    2018-06-22 18:51:46
    博客:https://blog.csdn.net/weixin_37058227/article/details/80776249 配置好的Trace+FreeRTOS+stm32 文件
  • [FreeRTOS]FreeRTOS使用

    2019-06-29 22:19:00
    转自:... FreeRTOS系列第1篇---为什么选择FreeRTOSFreeRTOS系列第2篇---FreeRTOS入门指南 FreeRTOS系列第3篇---FreeRTOS移植指南 FreeRTOS系列第4篇---FreeRT...
  • Freertos 7.5.0

    2021-01-01 10:48:39
    <div><p>This updates FreeRTOS on STM32 PiOS to v7.5.0. <p>New API functions: - uxTaskGetSystemState() http://www.freertos.org/uxTaskGetSystemState.html - xQueueOverwrite() ...
  • FreeRTOS.zip

    2019-12-05 21:13:10
    freertos示例代码
  • FreeRTOS.pptx

    2020-08-26 13:42:45
    最详细的freertos教程,从头到尾介绍了freertos的原理和实现,很适合新手区学习。文章还附有大厂的一些freertos实例,可以帮助我们更好的了解在实际项目中的应用。

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 11,951
精华内容 4,780
关键字:

freertos