当前位置: 首页 > 文档资料 > freeRTOS 使用教程 >

1.3.1.2 协程

优质
小牛编辑
132浏览
2023-12-01

协程是freeRTOS提供的另外一种用以实现用户任务的一种机制,主要使用在RAM较小的处理器上,在32位的处理器上几乎不使用。本文包括:协程状态、协程优先级、实现协程、协程与任务混合使用、不足之处、demo。

状态

协程相对于任务状态只有三种,没有挂起态:

  1. 运行态
    与任务的运行态意义相同。

  2. 就绪态
    与任务的就绪态意义相同,不过条件不同,除了因为此时有更高优先级的协程在运行导致无法执行外,在任务与协程混用的系统中,任何一个任务处在运行态都会导致协程无法执行。

  3. 阻塞态
    与任务类似,协程在等待时间或者外部事件,会进入阻塞态,与vTaskDelay()类似,协程使用crDELAY()来等待一段时间。

协程的状态转换图:

优先级

与任务类似,每个协程都有一个从0到configMAX_CO_ROUTINE_PRIORITIES - 1的优先级,共享优先级,且优先级之相对于协程来说。意思是,即使协程的优先级比任务的优先级高,系统仍然会优先调度到任务上,而非协程。

可以概括为:高优先级任务 > 低优先级任务 > 高等级协程 > 低等级协程

实现协程

与任务类似(...标准的开头语...),freeRTOS也要求用户定义固定形式的协程,如下:

    void vACoRoutineFunction( CoRoutineHandle_t xHandle,      UBaseType_t uxIndex ){
crSTART( xHandle );

for( ;; )
{-- Co-routine application code here. --
}

crEND();}

CoRoutineHandle_t xHandleUBaseType_t uxIndex 协程所接收的参数,关于这两个参数的含义和用法,后面会有介绍,带着疑问往下看吧。

值得注意的几点:

  • 所有的协程必须通过调用crSTART()crEND()来开始和结束。
  • 与任务相同,不允许返回,是一个死循环。
  • 多个协程可以通过同一个协程"模板"创建,彼此之间通过uxIdex区分。

调度

如任务不类似,协程的调度是才用重复调用vCoRountinueSChedule()来实现的,最佳的调用方式是在空闲任务的钩子函数中调用,这是因为即使你只使用协程,空闲任务仍然会自动创建当调度器启动的时候。

在空闲任务的钩子函数中调度协程,会让协程在与任务混合使用的系统中,总是在所有的任务执行完之后才会执行。因此,建议有使用混合任务与协程需求的小伙伴,把重要性低且占用时间短,对实时性要求不高的事情放在协程中处理

缺点

虽然相比同等数量的任务,协程所占用的RAM比较少,在低内存的处理器上更加适合,但是同样协程也有很多限制,同时使用上也比任务复杂。

协程中的变量

协程的堆栈不会在协程阻塞的时候保持,这意味着协程在栈上所申请的变量可能会丢失它们的值,为了解决这点,协程中需要保持数据的变量必须定义位静态的static

void vACoRoutineFunction( CoRoutineHandle_t xHandle,UBaseType_t uxIndex )
{
static char c = 'a';

   // Co-routines must start with a call to crSTART().
   crSTART( xHandle );

   for( ;; )
   {  // 如果我们在这里将c的值设为'b' ...  c = 'b';
  // ... 然后阻塞协程 ...  crDELAY( xHandle, 10 );
  // ... c的值只有在申明成静态类型才会一定等于'b'  // (as it is here).
   }

   // Co-routines must end with a call to crEND().
   crEND();
}

协程中的阻塞API调用

另一个问题因此导致的问题就是,所有能导致协程阻塞的API调用,只能由协程本身调用,不能由其内部调用的函数来调用。

void vACoRoutineFunction( CoRoutineHandle_t xHandle, UBaseType_t uxIndex )
{
   // Co-routines must start with a call to crSTART().
   crSTART( xHandle );

   for( ;; )
   {  // It is fine to make a blocking call here,  crDELAY( xHandle, 10 );
  // but a blocking call cannot be made from within  // vACalledFunction().  vACalledFunction();
   }

   // Co-routines must end with a call to crEND().
   crEND();
}

void vACalledFunction( void )
{
   // Cannot make a blocking call here!
}

协程中的switch语句

freeRTOS的默认协程实现中不能使用switch语句

void vACoRoutineFunction( CoRoutineHandle_t xHandle, UBaseType_t uxIndex )
{
   // Co-routines must start with a call to crSTART().
   crSTART( xHandle );

   for( ;; )
   {  // It is fine to make a blocking call here,  crDELAY( xHandle, 10 );
  switch( aVariable )  {
 case 1 : // Cannot make a blocking call here!    break;
 default: // Or here!  }
   }

   // Co-routines must end with a call to crEND().
   crEND();
}

一个简单的栗子

下面的例子是演示如何使用协程来是LED闪烁,在这要说明的是,不要以为所有的LED闪烁都可以放在协程中来做,别如电能表的脉冲控制,对脉冲宽度有要求的,如果放在协程里,很有可能导致忽慢忽快。协程的实时性要差点,最好放在这里的任务都是些对实时性要求不高的。

  • 简单的LED闪烁代码
void vFlashCoRoutine( CoRoutineHandle_t xHandle,     UBaseType_t uxIndex )
{
   // Co-routines must start with a call to crSTART().
   crSTART( xHandle );

   for( ;; )
   {  // Delay for a fixed period.  crDELAY( xHandle, 10 );
  // Flash an LED.  vParTestToggleLED( 0 );
   }

   // Co-routines must end with a call to crEND().
   crEND();
}
  • 调度协程
    协程的调度通过重复调用vCoRoutineSchedule()实现,最好放在空闲任务的钩子函数中,因此首先需要使能钩子函数,设置configUSE_IDLE_HOOK为1,接着写空闲任务的钩子函数:
void vApplicationIdleHook( void )
{
   vCoRoutineSchedule( void );
}

如果,空闲任务的钩子函数不包含其他功能,建议这样写,效率会高点:

    void vApplicationIdleHook( void ){   for( ;; )   {
  vCoRoutineSchedule( void );   }}
  • 创建协程并启动调用器

      #include "task.h"
      #include "croutine.h"
      #define  PRIORITY_0  0
      void main( void )
      { // In this case the index is not used and is passed // in as 0. xCoRoutineCreate( vFlashCoRoutine, PRIORITY_0, 0 );
     // NOTE:  Tasks can also be created here!
     // Start the RTOS scheduler. vTaskStartScheduler();
      }
    
  • 使用index来实现多个LED闪烁
    如前所述,协程函数的申明实际上类似申明了一个"模板",可以通过这个"模板"声明多个协程,每个协程之间使用index区分:

    #include "task.h"#include "croutine.h"#define PRIORITY_00#define NUM_COROUTINES    8void main( void ){int i;
   for( i = 0; i < NUM_COROUTINES; i++ )   {
  /* 通过index区分不同协程. */
  xCoRoutineCreate( vFlashCoRoutine, PRIORITY_0, i );   }
   // 除了协程,这里仍然可以创建任务!
   // 启动调度器   vTaskStartScheduler();}The co-routine function is also extended so each uses a different LED and flash rate.const int iFlashRates[ NUM_COROUTINES ] = { 10, 20, 30, 40, 50, 60, 70, 80 };const int iLEDToFlash[ NUM_COROUTINES ] = { 0, 1, 2, 3, 4, 5, 6, 7 }
void vFlashCoRoutine( CoRoutineHandle_t xHandle, UBaseType_t uxIndex ){   // Co-routines must start with a call to crSTART().   crSTART( xHandle );
   for( ;; )   {
  // 不同的协程阻塞时间不同,从而实现闪烁频率不同
  crDELAY( xHandle, iFlashRate[ uxIndex ] );

  // 自然,每个协程可根据index选择不同的LED
  vParTestToggleLED( iLEDToFlash[ uxIndex ] );   }
   // Co-routines must end with a call to crEND().   crEND();}