协程驱动
协程驱动

协程驱动

这是一个协程驱动,可以用于GD32、STM32,甚至是 51 单片机中。

协程简介

协程是一种任务管理组件,介于裸机和操作系统之间,广泛应用于小型单片机中。协程按照模块组织应用,每个模块都可以是一个任务,任务可以注册、注销,也可以挂起和从挂起态恢复。

协程的移植

为方便移植,本驱动只有“CoRoutine.c”和“CoRoutine.h”两个文件,使用协程之前需要将“CoRoutine.c”和“CoRoutine.h”添加到工程里,并添加包含驱动所在路径。协程的移植需要经过两个步骤,一是初始化并启动协程,二是用硬件定时器为协程提供时钟源。

初始化并启动协程

协程的初始化由 CoRoutineInit 函数实现,CoRoutineInit 函数不涉及任何硬件相关的初始化,可以放到初始化软硬件之前,如下所示。注意:注册任务之前必须要先调用 CoRoutineInit 函数初始化协程驱动。系统初始化完毕后,即可调用 CoRoutineStart 函数开启协程。

推荐系统初始化时先关闭系统总中断,初始化完毕后再开启。如果不想操作总中断开关,那么需要保证协程的初始化要在定时器之前。先启动定时器再初始化协程可能会出现未知的bug。

#include "CoRoutine.h"

//主函数
void main(void)
{
  //关闭全局中断
  DisableIRQ();

  //初始化协程模块
  CoRoutineInit();

  //初始化软硬件
  InitHardware();
  InitSoftware();

  //开启全局中断
  EnableIRQ();

  //开启协程
  CoRoutineStart();
  
  //不应该运行到这里
  while(1)
  {
  }
}

为协程提供时钟源

协程需要一个硬件定时器提供系统时钟源,用以驱动协程运行,定时器的周期就是协程的时间片。定时器协程相关处理如下所示。推荐定时器周期为 1ms,如果如果将定时器周期配置为 1ms,那么协程的时间片也将是 1ms。用户可以根据自己的需要自行配置定时器周期大小。

#include "CoRoutine.h"

//定时器中断服务函数,每隔 1ms 执行一次
void TIMER_IRQHandler(void)
{
  //协程定时器处理
  CoRoutineISRPoll();
}

至此,协程的移植已经完成。

任务的注册

可以通过 CoRoutineCreate 函数注册任务,任务的注册可以在协程启动之前,也可以在协程启动之前,但一定要先调用 CoRoutineInit 函数初始化协程模块。任务的注册如下所示,以 LED 为例。LEDTask 即为任务函数,后边将会介绍。

#include "CoRoutine.h"

//为任务控制块开辟内存空间,注意这里必须是静态变量
static StructCRCB s_structLEDTask;

//注册 LED 任务
CoRoutineCreate(&s_structLEDTask, LEDTask);

任务函数

任务函数的形参为 “void task(void* crcb)”,返回值为 void,形参为任务控制块首地址。任务函数是一个死循环,使用 CR_DELAY_MS 或 CR_DELAY 函数延时,如下所示。应用里可以同时注册多个任务,每个任务都是一个死循环,可以独立、并行运行。

注意:协程函数里边不得使用“switch case”语句,但子函数里可以使用。

#include "CoRoutine.h"

//LED 任务函数
void LEDTask(void* crcb)
{
  //协程开始
  CR_START(crcb);

  //协程循环
  while(1)
  {
    //点亮 LED
    LED_ON();
    CR_DELAY_MS(crcb, 500);

    //熄灭 LED
    LED_OFF();
    CR_DELAY_MS(crcb, 500);
  }

  //协程结束
  CR_END();
}

协程没有任务栈一说,所有任务共用一个栈区,所以协程任务函数里的变量都必须要是静态变量,如下所示,否则任务函数运行将出错。

#include "CoRoutine.h"

//任务函数
void xxxTask(void* crcb)
{
  //所有的变量都必须是静态变量
  static int a, b, c;

  //协程开始
  CR_START(crcb);

  //协程循环
  while(1)
  {
    //协程处理
    ...

    //延时 500ms
    CR_DELAY_MS(crcb, 500);
  }

  //协程结束
  CR_END();
}

延时函数

协程通过 CR_DELAY 和 CR_DELAY_MS 函数实现延时。CR_DELAY 函数即为延时 n 个时间片,CR_DELAY_MS 即为延时 n 毫秒。CR_DELAY_MS 函数的定义如下所示。

#define CR_DELAY_MS(crcb, ms) CR_DELAY(crcb, ms)

如果定时器周期不是 1ms,而是 10ms,那么一个时间片相当于 10ms,此时需要将 CR_DELAY_MS 函数修改成如下所示代码。

#define CR_DELAY_MS(crcb, ms) CR_DELAY(crcb, ms / 10)

注意:请不要将协程延时函数并排使用,如下所示,否则编译器会报错。同时,延时函数只能在任务函数里调用,不能在子函数里调用。

LED_ON(); CR_DELAY_MS(crcb, 500); LED_OFF(); CR_DELAY_MS(crcb, 500);

协程模块使用增量列表的方式管理任务延时,大大节省了 CPU 资源,任务延时处理十分高效。

任务的挂起和恢复

有些时候我们需要将任务挂起,例如接收并处理串口数据时,不确定什么时候接收到串口数据,这时候我们可以选择性的将任务挂起,接收到串口数据后再唤醒任务。进入挂起态的任务是不消耗 CPU 资源的。任务的挂起和恢复可以通过 CR_SUSPEND 和 CR_RESUME 函数实现,CR_SUSPEND 函数用于挂起任务,只能在本任务的任务函数里使用,CR_RESUME 函数用于将任务从挂起态恢复,需要预先知道目标任务的控制块首地址。

注意:CR_RESUME 函数并未设置中断保护,因此在中断里请谨慎使用。

源码