协程是freeRTOS提供的另外一种用以实现用户任务的一种机制,主要使用在RAM较小的处理器上,在32位的处理器上几乎不使用。本文包括:协程状态、协程优先级、实现协程、协程与任务混合使用、不足之处、demo。
状态
协程相对于任务状态只有三种,没有挂起态:
运行态
与任务的运行态意义相同。就绪态
与任务的就绪态意义相同,不过条件不同,除了因为此时有更高优先级的协程在运行导致无法执行外,在任务与协程混用的系统中,任何一个任务处在运行态都会导致协程无法执行。阻塞态
与任务类似,协程在等待时间或者外部事件,会进入阻塞态,与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 xHandle
和UBaseType_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_0 0
#define NUM_COROUTINES 8
void 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();
}