ESP32 FreeRTOS-任务内核控制(6)

提示:好记性不如烂笔头。本博客作为学习笔记,有错误的地方希望指正

ESP32-FreeRTOS序列:

ESP32 FreeRTOS-任务的创建与删除 (1)
ESP32 FreeRTOS-任务输入参数(2)
ESP32 FreeRTOS-任务优先级(3)
ESP32 FreeRTOS-调试任务实用工具(4)
ESP32 FreeRTOS-任务控制(5)
ESP32 FreeRTOS-任务内核控制(6)

前言:

  参考资料:FreeRTOS API参考
  这一篇主要介绍一些关于系统级别的API,主要对于任务的管理,其中包括任务上下文切换、任务临界保护,任务的调度、任务的挂起、

一、taskYIELD()

API原型:

/**
 * @cond !DOC_EXCLUDE_HEADER_SECTION
 *任务。 h
 * @endcond
 *
 * 用于强制切换上下文的宏。
 *
 * @cond !DOC_SINGLE_GROUP
 * defgroup taskYIELD taskYIELD
 * @endcond
 * ingroup SchedulerControl
 */
#define taskYIELD() portYIELD()

  taskYIELD()是用来请求切换到另一个任务的上下文。然而,如果没有其他任务的优先级高于或等于调用taskYIELD()的任务,那么RTOS调度器将简单地选择调用taskYIELD()的任务来再次运行。
  如果configUSE_PREEMPTION设置为1,那么RTOS调度器将始终运行能够运行的最高优先级的任务,所以调用taskYIELD()将永远不会导致切换到更高优先级的任务。
  而这里的话就涉及到FreeRTOS的核心任务调度,FreeRTOS的任务调度主要分为三种方式。可以参考这篇文章介绍的挺详细的:freertos中任务的调度方式

  • 抢占式调度(Pre-emptive):CPU总是运行多个任务中优先级别最高的那个任务,高优先级的任务有剥夺低优先级任务运行的权利。
  • 合作式调度(co-operative):只有在Running状态任务进入Blocked状态,或者Running状态任务通过调用taskYIELD()显式让步(手动请求重新调度)时才会发生任务切换。 任务永远不会被抢占,因此不能使用时间切片。
  • 时间片调度(time slice):只有同优先级任务才会使用时间片调度,任务的优先级一样,一个任务运行一段时间,然后让出CPU使用权,给另外一个任务使用,值的注意的是一个任务运行进入阻塞态的时候,虽然时间片还没有用完依然会通过时间片切换到下一个任务,下次在调用该任务的时候,不会继续使用上次留下来的时间片。

二、taskDISABLE_INTERRUPTS()

API原型:

/**
 * @cond !DOC_EXCLUDE_HEADER_SECTION
 *任务。 h
 * @endcond
 *
 * 禁用所有可屏蔽的中断的宏程序。
 *
 * @cond !DOC_SINGLE_GROUP
 * defgroup taskDISABLE_INTERRUPTS taskDISABLE_INTERRUPTS
 *@endcond
 *ingroup SchedulerControl
 */
#define taskDISABLE_INTERRUPTS() portDISABLE_INTERRUPTS()

  如果使用的端口支持 configMAX_SYSCALL_INTERRUPT_PRIORITY (或 configMAX_API_CALL_INTERRUPT_PRIORITY) 常数,那么 taskDISABLE_INTERRUPTS 将禁用所有中断,或者屏蔽(禁用)中断,直至 configMAX_SYSCALL_INTERRUPT_PRIORITY 设置。检查使用中的端口对taskDISABLE_INTERRUPTS的实现。
  如果使用的端口不支持 configMAX_SYSCALL_INTERRUPT_PRIORITY 常数,那么 taskDISABLE_INTERRUPTS() 将全局禁用所有可屏蔽中断。
  通常情况下,这个宏不会被直接调用,应该用taskENTER_CRITICAL()和taskEXIT_CRITICAL()来代替它。

三、taskENABLE_INTERRUPTS()

API原型:

/**
 * @cond !DOC_EXCLUDE_HEADER_SECTION
 *任务。 h
 * @endcond
 *
 *启用微控制器中断的宏程序。
 *
 * @cond !DOC_SINGLE_GROUP
 * defgroup taskENABLE_INTERRUPTS taskENABLE_INTERRUPTS
 * @endcond
 *ingroup SchedulerControl
 */
#define taskENABLE_INTERRUPTS() portENABLE_INTERRUPTS()

  启用微控制器中断的宏。
  通常这个宏不会被直接调用,应该用taskENTER_CRITICAL()和taskEXIT_CRITICAL()来代替它。

四、taskENTER_CRITICAL()、taskEXIT_CRITICAL()

API原型:

void taskENTER_CRITICAL( void );
void taskEXIT_CRITICAL( void );

  关键部分通过调用taskENTER_CRITICAL()进入,随后通过调用taskEXIT_CRITICAL()退出。
  taskENTER_CRITICAL()和taskEXIT_CRITICAL()宏提供了一个基本的临界区实现,其工作方式是简单地禁用中断,可以是全局的,也可以是特定中断优先级的。参见vTaskSuspendAll()RTOS API函数,以了解在不禁用中断的情况下创建一个关键部分的信息。
  如果正在使用的FreeRTOS端口没有使用configMAX_SYSCALL_INTERRUPT_PRIORITY内核配置常数(也称为configMAX_API_CALL_INTERRUPT_PRIORITY),那么调用taskENTER_CRITICAL()将使中断全局禁用。如果正在使用的FreeRTOS端口确实使用了configMAX_SYSCALL_INTERRUPT_PRIORITY内核配置常数,那么调用taskENTER_CRITICAL()将使中断在configMAX_SYSCALL_INTERRUPT_PRIORITY设置的中断优先级及以下的中断被禁用,而所有更优先的中断被启用。
  抢占式上下文切换只发生在中断内部,所以当中断被禁用时不会发生。因此,调用taskENTER_CRITICAL()的任务被保证保持在运行状态,直到退出临界段,除非该任务明确地尝试阻塞或屈服(它不应该从临界段内做)。
  对 taskENTER_CRITICAL() 和 taskEXIT_CRITICAL() 的调用被设计为嵌套。因此,只有在对taskEXIT_CRITICAL()的每一次调用都被执行时,临界区才会被退出。
  关键部分必须保持非常短,否则会对中断响应时间产生不利影响。每个对taskENTER_CRITICAL()的调用都必须与对taskEXIT_CRITICAL()的调用紧密结合。
  FreeRTOS的API函数不能从临界区中调用。
  taskENTER_CRITICAL()和taskEXIT_CRITICAL()不能从中断服务例程(ISR)中调用–参见taskENTER_CRITICAL_FROM_ISR()和taskEXIT_CRITICAL_FROM_ISR()中的中断安全等效。
使用示例

/* A function that makes use of a critical section. */
void vDemoFunction( void )
{
    
    
    /* Enter the critical section.  In this example, this function is itself called
    from within a critical section, so entering this critical section will result
    in a nesting depth of 2. */
    taskENTER_CRITICAL();

    /* Perform the action that is being protected by the critical section here. */

    /* Exit the critical section.  In this example, this function is itself called
    from a critical section, so this call to taskEXIT_CRITICAL() will decrement the
    nesting count by one, but not result in interrupts becoming enabled. */
    taskEXIT_CRITICAL();
}

/* A task that calls vDemoFunction() from within a critical section. */
void vTask1( void * pvParameters )
{
    
    
    for( ;; )
    {
    
    
        /* Perform some functionality here. */

        /* Call taskENTER_CRITICAL() to create a critical section. */
        taskENTER_CRITICAL();

        /* Execute the code that requires the critical section here. */
        
        /* Calls to taskENTER_CRITICAL() can be nested so it is safe to call a
        function that includes its own calls to taskENTER_CRITICAL() and
        taskEXIT_CRITICAL(). */
        vDemoFunction();

        /* The operation that required the critical section is complete so exit the
        critical section.  After this call to taskEXIT_CRITICAL(), the nesting depth
        will be zero, so interrupts will have been re-enabled. */
        taskEXIT_CRITICAL();
    }
}

五、taskENTER_CRITICAL_FROM_ISR()、taskEXIT_CRITICAL_FROM_ISR()

API原型:

UBaseType_t taskENTER_CRITICAL_FROM_ISR( void );
void taskEXIT_CRITICAL_FROM_ISR( UBaseType_t uxSavedInterruptStatus );

  taskENTER_CRITICAL()和taskEXIT_CRITICAL()的版本,可以在中断服务例程(ISR)中使用。
  在ISR中,通过调用taskENTER_CRITICAL_FROM_ISR()进入临界区,随后通过调用taskEXIT_CRITICAL_FROM_ISR()退出。
  taskENTER_CRITICAL_FROM_ISR()和taskEXIT_CRITICAL_FROM_ISR()宏提供了一个基本的临界区实现,其工作原理是简单地禁用中断,可以是全局的,也可以是特定中断优先级的。
  如果正在使用的FreeRTOS端口支持中断嵌套,那么调用taskENTER_CRITICAL_FROM_ISR()将禁用由configMAX_SYSCALL_INTERRUPT_PRIORITY(或configMAX_API_CALL_INTERRUPT_PRIORITY)内核配置常数设置的中断优先级及以下的中断,并保留所有其他中断优先级。如果正在使用的FreeRTOS端口不支持中断嵌套,那么taskENTER_CRITICAL_FROM_ISR()和taskEXIT_CRITICAL_FROM_ISR()将没有作用。
  对taskENTER_CRITICAL_FROM_ISR()和taskEXIT_CRITICAL_FROM_ISR()的调用被设计为嵌套,但如何使用这些宏的语义与taskENTER_CRITICAL()和taskEXIT_CRITICAL()的对应物不同。
  关键部分必须保持非常短,否则它们将对高优先级中断的响应时间产生不利影响,否则就会嵌套。每个对taskENTER_CRITICAL_FROM_ISR()的调用都必须与对taskEXIT_CRITICAL_FROM_ISR()的调用紧密配对。
  FreeRTOS的API函数不能从临界区中调用。
参数:

  • uxSavedInterruptStatus: taskEXIT_CRITICAL_FROM_ISR()将uxSavedInterruptStatus作为其唯一参数。用作uxSavedInterruptStatus参数的值必须是调用taskENTER_CRITICAL_FROM_ISR()时返回的值。
    taskENTER_CRITICAL_FROM_ISR()不接受任何参数。

返回:
  taskENTER_CRITICAL_FROM_ISR()返回调用宏之前的中断屏蔽状态。taskENTER_CRITICAL_FROM_ISR()返回的值必须作为匹配调用taskEXIT_CRITICAL_FROM_ISR()时的uxSavedInterruptStatus参数。taskEXIT_CRITICAL_FROM_ISR()并不返回一个值。
使用示例:

/* A function called from an ISR. */
void vDemoFunction( void )
{
    
    
UBaseType_t uxSavedInterruptStatus;

    /* Enter the critical section.  In this example, this function is itself called from
    within a critical section, so entering this critical section will result in a nesting
    depth of 2. Save the value returned by taskENTER_CRITICAL_FROM_ISR() into a local
    stack variable so it can be passed into taskEXIT_CRITICAL_FROM_ISR(). */
    uxSavedInterruptStatus = taskENTER_CRITICAL_FROM_ISR();

    /* Perform the action that is being protected by the critical section here. */

    /* Exit the critical section.  In this example, this function is itself called from a
    critical section, so interrupts will have already been disabled before a value was
    stored in uxSavedInterruptStatus, and therefore passing uxSavedInterruptStatus into
    taskEXIT_CRITICAL_FROM_ISR() will not result in interrupts being re-enabled. */
    taskEXIT_CRITICAL_FROM_ISR( uxSavedInterruptStatus );
}

/* A task that calls vDemoFunction() from within an interrupt service routine. */
void vDemoISR( void )
{
    
    
UBaseType_t uxSavedInterruptStatus;

    /* Call taskENTER_CRITICAL_FROM_ISR() to create a critical section, saving the
    returned value into a local stack variable. */
    uxSavedInterruptStatus = taskENTER_CRITICAL_FROM_ISR();

    /* Execute the code that requires the critical section here. */

    /* Calls to taskENTER_CRITICAL_FROM_ISR() can be nested so it is safe to call a
    function that includes its own calls to taskENTER_CRITICAL_FROM_ISR() and
    taskEXIT_CRITICAL_FROM_ISR(). */
    vDemoFunction();

    /* The operation that required the critical section is complete so exit the
    critical section.  Assuming interrupts were enabled on entry to this ISR, the value
    saved in uxSavedInterruptStatus will result in interrupts being re-enabled.*/
    taskEXIT_CRITICAL_FROM_ISR( uxSavedInterruptStatus );
}

六、vTaskStartScheduler()

API原型:

void vTaskStartScheduler( void );

  启动RTOS调度器。调用后RTOS内核可以控制哪些任务被执行以及何时执行。
  当RTOS调度器被启动时,空闲任务和可选的定时器守护任务会被自动创建。
  vTaskStartScheduler()只会在没有足够的RTOS堆可用来创建空闲或定时守护任务时返回。
  所有的RTOS演示应用程序项目都包含使用vTaskStartScheduler()的例子,通常是在main.c中的main()函数中。
使用示例:

void vAFunction( void )
 {
    
    
     // Tasks can be created before or after starting the RTOS
     scheduler
     xTaskCreate( vTaskCode,
                  "NAME",
                  STACK_SIZE,
                  NULL,
                  tskIDLE_PRIORITY,
                  NULL );

     // Start the real time scheduler.
     vTaskStartScheduler();

     // Will not get here unless there is insufficient RAM.
 }

七、vTaskEndScheduler()

API原型:

void vTaskEndScheduler( void );

  注意:这只在x86真实模式PC端口实现。
  停止RTOS内核的勾选。所有创建的任务将被自动删除,多任务(无论是抢占式还是合作式)将停止。然后从调用vTaskStartScheduler()的地方恢复执行,就像vTaskStartScheduler()刚刚返回一样。
  关于使用vTaskEndScheduler()的例子,请参见demo/PC目录下的演示程序文件main.c。
  vTaskEndScheduler()需要在可移植层中定义一个退出函数(参见port.c中的vPortEndScheduler(),用于PC端口)。这将执行特定的硬件操作,例如停止RTOS内核的勾选。
  vTaskEndScheduler()将导致所有由RTOS内核分配的资源被释放 - 但不会释放由应用程序任务分配的资源。
使用示例:

void vTaskCode( void * pvParameters )
 {
    
    
     for( ;; )
     {
    
    
         // Task code goes here.

         // At some point we want to end the real time kernel processing 
         // so call ...
         vTaskEndScheduler ();
     }
 }

 void vAFunction( void )
 {
    
    
     // Create at least one task before starting the RTOS kernel.
     xTaskCreate( vTaskCode, "NAME", STACK_SIZE, NULL, tskIDLE_PRIORITY, NULL );

     // Start the real time kernel with preemption.
     vTaskStartScheduler();

     // Will only get here when the vTaskCode () task has called 
     // vTaskEndScheduler ().  When we get here we are back to single task 
     // execution.
 }

八、vTaskSuspendAll()

API原型:

void vTaskSuspendAll( void );

  暂停调度程序。暂停调度程序可以防止上下文切换的发生,但会使中断处于激活状态。如果在调度器暂停时有中断请求进行上下文切换,那么该请求将被保留,只有在调度器恢复(非暂停)时才会执行。
  对xTaskResumeAll()的调用将调度器从之前对vTaskSuspendAll()的调用后的暂停状态转换出来。
  对vTaskSuspendAll()的调用可以被嵌套。对xTaskResumeAll()的调用次数必须与之前对vTaskSuspendAll()的调用次数相同,然后调度器才会离开暂停状态并重新进入活动状态。
  xTaskResumeAll()只能从一个正在执行的任务中调用,因此当调度器处于初始化状态(在调度器启动之前)时不能调用。
  其他FreeRTOS的API函数不能在调度器暂停时被调用。
  有可能导致上下文切换的API函数(例如,vTaskDelayUntil(), xQueueSend()等)不能在调度器暂停时调用。
使用示例:

/* A function that suspends then resumes the scheduler. */
void vDemoFunction( void )
{
    
    
    /* This function suspends the scheduler.  When it is called from vTask1 the 
    scheduler is already suspended, so this call creates a nesting depth of 2. */
    vTaskSuspendAll();
        
    /* Perform an action here. */
        
    /* As calls to vTaskSuspendAll() are nested, resuming the scheduler here will 
    not cause the scheduler to re-enter the active state. */
    xTaskResumeAll();
}

void vTask1( void * pvParameters )
{
    
    
    for( ;; )
    {
    
    
        /* Perform some actions here. */
            
        /* At some point the task wants to perform an operation during which it does 
        not want to get swapped out, or it wants to access data which is also 
        accessed from another task (but not from an interrupt).  It cannot use
        taskENTER_CRITICAL()/taskEXIT_CRITICAL() as the length of the operation may
        cause interrupts to be missed. */
            

        /* Prevent the scheduler from performing a context switch. */
        vTaskSuspendAll();
            

        /* Perform the operation here.  There is no need to use critical sections as 
        the task has all the processing time other than that utilized by interrupt 
        service routines.*/           
            
            
        /* Calls to vTaskSuspendAll() can be nested so it is safe to call a (non API) 
        function which also contains calls to vTaskSuspendAll().  API functions 
        should not be called while the scheduler is suspended. */
        vDemoFunction();

            
        /* The operation is complete.  Set the scheduler back into the Active 
        state. */
        if( xTaskResumeAll() == pdTRUE )
        {
    
    
            /* A context switch occurred within xTaskResumeAll(). */
        }
        else
        {
    
    
            /* A context switch did not occur within xTaskResumeAll(). */
        }
    }
}

九、xTaskResumeAll()

API原型:

BaseType_t xTaskResumeAll( void );

  在调用vTaskSuspendAll()暂停后,恢复调度程序。
  xTaskResumeAll()只恢复调度程序。它不会取消之前通过调用vTaskSuspend()而暂停的任务。
使用示例:

void vTask1( void * pvParameters )
 {
    
    
     for( ;; )
     {
    
    
         /* Task code goes here. */

         /* ... */

         /* At some point the task wants to perform a long operation
         during which it does not want to get swapped out.  It cannot
         use taskENTER_CRITICAL ()/taskEXIT_CRITICAL () as the length
         of the operation may cause interrupts to be missed -
         including the ticks.

         Prevent the RTOS kernel swapping out the task. */
         vTaskSuspendAll();

         /* Perform the operation here.  There is no need to use critical
         sections as we have all the microcontroller processing time.
         During this time interrupts will still operate and the real
         time RTOS kernel tick count will be maintained. */

         /* ... */

         /* The operation is complete.  Restart the RTOS kernel.  We want to force
         a context switch - but there is no point if resuming the scheduler
         caused a context switch already. */
         if( !xTaskResumeAll () )
         {
    
    
              taskYIELD ();
         }
     }
 }

九、vTaskStepTick()

API原型:

void vTaskStepTick( TickType_t xTicksToJump );

  如果RTOS被配置为使用无滴答的空闲功能,那么只要空闲任务是唯一能够执行的任务,滴答中断就会被停止,微控制器就会进入一个低功率状态。在退出低功率状态时,tick的数值必须被修正,以考虑到它被停止时的时间。
  如果FreeRTOS端口包括一个默认的portSUPPRESS_TICKS_AND_SLEEP()实现,那么vTaskStepTick()将在内部使用,以确保保持正确的tick值。vTaskStepTick()是一个公共API函数,允许覆盖默认的portSUPPRESS_TICKS_AND_SLEEP()实现,如果正在使用的端口没有提供默认,则可以提供一个portSUPPRESS_TICKS_AND_SLEEP()。
  configUSE_TICKLESS_IDLE配置常量必须被设置为1,才能使用vTaskStepTick()。
参数:

  • xTicksToJump: 自停止tick中断以来,RTOS的ticks数量。为了正确操作,该参数必须小于或等于 portSUPPRESS_TICKS_AND_SLEEP() 参数。

使用示例:
  这个例子显示了对几个函数的调用。只有vTaskStepTick()是FreeRTOS API的一部分。其他函数是针对使用中的硬件的时钟和省电模式的,因此,必须由应用程序编写者提供。

/* First define the portSUPPRESS_TICKS_AND_SLEEP().  The parameter is the time,
in ticks, until the kernel next needs to execute. */
#define portSUPPRESS_TICKS_AND_SLEEP( xIdleTime ) vApplicationSleep( xIdleTime )

/* Define the function that is called by portSUPPRESS_TICKS_AND_SLEEP(). */
void vApplicationSleep( TickType_t xExpectedIdleTime )
{
    
    
unsigned long ulLowPowerTimeBeforeSleep, ulLowPowerTimeAfterSleep;

    /* Read the current time from a time source that will remain operational
    while the microcontroller is in a low power state. */
    ulLowPowerTimeBeforeSleep = ulGetExternalTime();

    /* Stop the timer that is generating the tick interrupt. */
    prvStopTickInterruptTimer();

    /* Configure an interrupt to bring the microcontroller out of its low power
    state at the time the kernel next needs to execute.  The interrupt must be
    generated from a source that is remains operational when the microcontroller
    is in a low power state. */
    vSetWakeTimeInterrupt( xExpectedIdleTime );

    /* Enter the low power state. */
    prvSleep();

    /* Determine how long the microcontroller was actually in a low power state
    for, which will be less than xExpectedIdleTime if the microcontroller was
    brought out of low power mode by an interrupt other than that configured by
    the vSetWakeTimeInterrupt() call.  Note that the scheduler is suspended
    before portSUPPRESS_TICKS_AND_SLEEP() is called, and resumed when
    portSUPPRESS_TICKS_AND_SLEEP() returns.  Therefore no other tasks will
    execute until this function completes. */
    ulLowPowerTimeAfterSleep = ulGetExternalTime();

    /* Correct the kernels tick count to account for the time the microcontroller
    spent in its low power state. */
    vTaskStepTick( ulLowPowerTimeAfterSleep - ulLowPowerTimeBeforeSleep );

    /* Restart the timer that is generating the tick interrupt. */
    prvStartTickInterruptTimer();
}

十、xTaskCatchUpTicks()

API原型:

BaseType_t xTaskCatchUpTicks( TickType_t xTicksToCatchUp );

  在应用程序代码长时间禁用中断后,纠正滴答计数值。这个函数类似于vTaskStepTick(),但是,与vTaskStepTick()不同的是,这个函数可能会将tick计数向前移动,超过一个任务应该从阻塞状态移除的时间。这意味着xTaskCatchUpTicks()可以将任务从阻塞状态移除。
参数:

  • xTicksToCatchUp: 由于中断被禁用而错过的tick中断的数量。它的值不是自动计算的,所以必须由应用程序编写者计算。

返回:
  pdTRUE 如果向前移动tick计数导致一个任务离开阻塞状态并执行上下文切换。否则pdFALSE。
使用示例:

void vExampleFunction( void )
{
    
    
    unsigned long ulTimeBefore, ulTimeAfter;
    /* Read the current time before arbitrary processing takes place. */
    ulTimeBefore = ulGetExternalTime();
    /* Stop the timer that is generating the tick interrupt. */
    prvStopTickInterruptTimer();
    /* Perform some arbitrary processing. */
    arbitrary_processing();
    /* Read the current time for computing elapsed time since ticks 
    were disabled. */
    ulTimeAfter = ulGetExternalTime();
    if ( xTaskCatchUpTicks( ulTimeAfter - ulTimeBefore ) == pdTRUE ) 
    {
    
    
        /* Moving the tick count forward resulted in a context switch. */
    }
    /* Restart the timer that is generating the tick interrupt. */
    prvStartTickInterruptTimer();
}

猜你喜欢

转载自blog.csdn.net/believe666/article/details/127205525