Arduino|实现多任务操作,从此告别果奔

Arduino任务调度器

是否在玩arduino过程中出现按键控制带来不灵敏问题,是否在为只有一个循环loop()而烦恼,不否认可以使用中断解决问题,但我觉得,多任务处理起来更香。

本文将介绍arduino协作多任务的轻量级实现,让arduino实现类似操作系统(比如FreeRTOS、uC/OS-II)般的任务调度功能,不再尴尬倮奔。延时带来的不灵敏、数据刷新(比如温湿度、光照强度等数据同时实时获取)等问题迎刃而解。

概述:

  1. 定期任务执行,动态执行周期以毫秒(默认值)或微秒(如果明确启用)-执行频率

  2. 迭代次数(有限或无限次迭代)

  3. 按预定顺序执行任务

  4. 任务执行参数的动态变化(频率、迭代次数、回调方法)

  5. 在未计划运行任务时,通过进入空闲睡眠模式来节省电源

  6. 通过状态请求对象支持事件驱动的任务调用

  7. 支持任务ID和错误处理控制点以及看门狗定时器

  8. 支持本地任务存储指针(允许对多个任务使用相同的回调代码)

  9. 支持分层任务优先级划分

  10. 支持std::函数(仅限ESP8266和ESP32)

  11. 支持总体任务超时

任务调度功能适用于Arduino Uno R3、Arduino Nano、ESP8266、ESP32等。每个调度过程的开销在15us~18us,属于单调度。

任务

每个任务都通过回调方法执行其功能。调度器定期调用任务的回调方法,直到任务被禁用或迭代结束。除了“常规”回调方法外,每个任务还可以使用另外两种方法:一种是每次启用任务时调用的回调方法,另一种是在禁用任务时调用的回调方法。这两种特殊方法允许任务正确启动以执行,并在执行结束后进行清理(例如,在启用时设置管脚模式,并始终在结束时将管脚级别降低)。

任务通过成为好邻居来支持协同多任务处理,即以非阻塞方式快速运行其回调方法,并尽快将控制权释放回调度程序。

调度

调度程序按照任务添加到链中的顺序从头到尾执行任务的回调方法。调度器在处理链一次后停止并存在,以便允许loop()方法的主代码中的其他语句运行。这被称为“计划通行证”。通常,除了调度程序的execute()方法外,loop()方法中不需要任何其他语句。相当于loop()里只有execute()。如下所示:

void loop () {
    
    
  runner.execute();
}

这个execute()让我想到uC/OS-II的OSStart()。说这么多大家肯定是一头雾水没啥概念,直接上案例吧,细品。

案例

  1. 创建两个任务,分别是t1和t2。

  2. t1每隔两秒执行一次打印,执行10次后自我毁灭(销毁任务)。

  3. t2每隔3秒执行一次打印,不会自我销毁。

  4. 如果t1任务创建成功,在t1里边创建任务t3,t3每5秒执行一次打印。

  5. 在第二点里说了,t1执行10次后销毁。之后让任务t3也跟着销毁,同时让仅剩的任务t2变为0.5秒执行一次打印,一直下去。

Arduino使用的库是TaskScheduler.h,库压缩包已上传到资源,可前往下载。

库使用的编程语言基本是C++,把功能都封装起来,有各种类以及成员提供给arduino的IDE里去编程调用。关于C++内容,可以参考该系列内容 C++学习.

其中主要的类有两个:TaskScheduler

Task

Task负责任务初始化(定义任务的一些特性)。

案例要求有三个任务,分别是t1、t2、t3,t1是只能执行10次的自我销毁型任务,t2和t3是长久型任务。在类Task里有个成员原型是:

#ifdef _TASK_OO_CALLBACKS
Task::Task( unsigned long aInterval, long aIterations, Scheduler* aScheduler, bool aEnable ) {
    reset();
    set(aInterval, aIterations);
#else
Task::Task( unsigned long aInterval, long aIterations, TaskCallback aCallback, Scheduler* aScheduler, bool aEnable, TaskOnEnable aOnEnable, TaskOnDisable aOnDisable ) {
    reset();
    set(aInterval, aIterations, aCallback, aOnEnable, aOnDisable);
#endif

    if (aScheduler) aScheduler->addTask(*this);

#ifdef _TASK_WDT_IDS
    iTaskID = ++__task_id_counter;
#endif  // _TASK_WDT_IDS

    if (aEnable) enable();
}

所以可以通过类Task创建该成员对象,参数分别是(延时ms,循环次数,回调函数)

Task t1(2000, 10, &t1Callback);//延时2s,重复10次,回调函数名为t1Callback
Task t2(3000, TASK_FOREVER, &t2Callback);//延时3s,永远重复,回调函数名为t2Callback
Task t3(5000, TASK_FOREVER, &t3Callback);//延时5s,永远重复,回调函数名为t3Callback

这里的回调函数就是用来实现功能的函数,定义成void t1Callback、void t2Callback、void t3Callback

void t1Callback() {
    
    
	Serial.print("t1: ");
	Serial.println(millis());//millis()函数返回开发板运行程序的毫秒数数值,该数值约50天后溢出而从零开始。
}

Scheduler

Scheduler负责处理任务(任务添加、删除等)。

所以先用类声明对象(本例对象名为runner)

Scheduler runner;

然后根据对象去访问类成员函数,有

runner.init();
runner.addTask(name);//添加name任务,name为上面的t1或t2或t3
runner.deleteTask(name);
runner.execute();

除了以上这些,函数句柄还有disableAll、currentTask、allowSleep等。

代码

#include <TaskScheduler.h>

// Callback methods prototypes
void t1Callback();
void t2Callback();
void t3Callback();

//Tasks (delay_ms,times,func)
Task t4();
Task t1(2000, 10, &t1Callback);
Task t2(3000, TASK_FOREVER, &t2Callback);
Task t3(5000, TASK_FOREVER, &t3Callback);

Scheduler runner;


void t1Callback() {
    
    
    Serial.print("t1: ");
    Serial.println(millis());
    
    if (t1.isFirstIteration()) {
    
    
      runner.addTask(t3);
      t3.enable();
      Serial.println("t1: enabled t3 and added to the chain");
    }
    
    if (t1.isLastIteration()) {
    
    
      t3.disable();
      runner.deleteTask(t3);
      t2.setInterval(500);
      Serial.println("t1: disable t3 and delete it from the chain. t2 interval set to 500");
    }
}

void t2Callback() {
    
    
    Serial.print("t2: ");
    Serial.println(millis());
  
}

void t3Callback() {
    
    
    Serial.print("t3: ");
    Serial.println(millis());
  
}

void setup () {
    
    
  Serial.begin(115200);
  Serial.println("Scheduler TEST");
  
  runner.init();
  Serial.println("Initialized scheduler");
  
  runner.addTask(t1);
  Serial.println("added t1");
  
  runner.addTask(t2);
  Serial.println("added t2");
  
  t1.enable();
  Serial.println("Enabled t1");
  t2.enable();
  Serial.println("Enabled t2");
}


void loop () {
    
    
  runner.execute();
}

执行结果:

Scheduler TEST
Initialized scheduler
added t1
added t2
Enabled t1
Enabled t2
t1: 1
t1: enabled t3 and added to the chain
t2: 5
t3: 6
t1: 2000
t2: 3000
t1: 4000
t3: 5002
t1: 6000
t2: 6000
t1: 8000
t2: 9000
t1: 10000
t3: 10002
t1: 12000
t2: 12000
t1: 14000
t2: 15000
t3: 15002
t1: 16000
t2: 18000
t1: 18000
t1: disable t3 and delete it from the chain. t2 interval set to 500
t2: 18500
t2: 19000
t2: 19500
t2: 20000
t2: 20500
t2: 21000
t2: 21500
t2: 22000
t2: 22500
t2: 23000
t2: 23500
t2: 24000
t2: 24500
t2: 25000
t2: 25500

结果分析:

Scheduler TEST		------------------->从void setup()开始,Serial.println("Scheduler TEST");
Initialized scheduler	--------------->Serial.println("Initialized scheduler");
added t1			------------------>Serial.println("added t1");
added t2
Enabled t1			------------------>Serial.println("Enabled t1");t1被激活,跳去执行t1Callback()
Enabled t2
t1: 1				------------------>t1Callback()函数里的Serial.println(millis());时间数值代表毫秒
t1: enabled t3 and added to the chain	
							    ----->Serial.println("t1: enabled t3 and added to the chain");
t2: 5				-------------->t2抢在了t3前面显示,说明t1Callback()还没执行完,证明了多任务的存在
t3: 6
t1: 2000			-------------->现在才第二秒刚结束,于是t1打印
t2: 3000			-------------->t2是隔三秒打印一次
t1: 4000
t3: 5002			-------------->t3是隔五秒
t1: 6000
t2: 6000
t1: 8000
t2: 9000
t1: 10000
t3: 10002
t1: 12000
t2: 12000
t1: 14000
t2: 15000
t3: 15002
t1: 16000
t2: 18000
t1: 18000			-------------->t1从0、2、...到18共执行了10次,然后自我销毁
t1: disable t3 and delete it from the chain. t2 interval set to 500
					------------->t3跟着t1销毁,然后t2变为每半秒打印一次
t2: 18500
t2: 19000
t2: 19500
t2: 20000
t2: 20500
t2: 21000
t2: 21500
t2: 22000
t2: 22500
t2: 23000
t2: 23500
t2: 24000
t2: 24500
t2: 25000
t2: 25500			-------------->后续会一直剩下t2不断半秒循环下去

案例总结:
这个例子已经把基本的任务框架和执行流程体现出来,多任务最简单直接方式理解,好处就是提供多个循环,然后各循环执行自己的功能,相当于存在多个while()死循环体。而且有些情况下还可以实现定义执行几次,比较常规,和for循环类似

猜你喜欢

转载自blog.csdn.net/weixin_44035986/article/details/123412711
今日推荐