Author:teacherXue
一、TaskScheduler多任务库函数
上一章节的计时器伪多线程操作中需要定义大量的变量,封装一下会提高代码的可读性。拿来主义是个不错的选择。Arduino框架提供了TaskScheduler多任务库函数。
特点
任务周期性执行,执行频率以毫秒(默认)或微秒(如果显式启用)为单位;
支持设定执行次数(有限或无限次)
按预定义的顺序执行任务
支持任务执行参数的动态变化(频率、执行次数、回调方法)
支持在没有任务运行时进入睡眠模式以省电
支持事件驱动的任务调度
支持任务优先级
每次调度在Arduino UNO rev 3 @ 16MHz 时钟平台上需消耗 15 到 18 微秒调度开销,ESP8266上只有几微秒)
导入支持库
第二章节中已经讲过,这里简单陈述步骤。
1)先创建一个新项目Lamp_ task_v2.0。
2)查找支持库并下载安装到执行项目。
3)在main.cpp文件中引入头文件,如果报错,说明扩展库安装失败。
#include <TaskScheduler.h>
基本使用
创建任务调度对象,虽然C是面向过程的,但并不影响我们使用面向对象的思想去思考。其实看提示你会发现Scheduler是一个class文件。
Scheduler runner;
声明包含时间周期的任务
//声明任务执行函数
void t1Callback();
void t2Callback();
void t3Callback();
// 定义任务
Task t1(1000, 10, &t1Callback); //任务名称t1,调度间隔1000ms,总共执行10次,执行的代码为t1Callback()
Task t2(3000, TASK_FOREVER, &t2Callback);//任务名称t2,间隔3000ms,一直执行,执行的代码为t2Callback()
Task t3(5000, TASK_FOREVER, &t3Callback);//任务名称t3,间隔5000ms,一直执行,执行的代码为t3Callback()
loop()中只要调用调度器的execute()函数即可
//loop()中只要调用调度器的execute()函数即可
runner.execute();
常见函数
该库提供了很多方法供我们使用,下面是一些可能比较常用的方法。
isFirstIteration() | 如果任务是第一次运行,则执行该代码块 |
addTask(Task) | 将任务添加到调度器的任务链中,添加之后要打开(使能)才能够真正运行 |
enable() | 打开任务,执行该语句后,任务会立即执行 |
isLastIteration() | 如果任务是最后一次运行,则执行该代码块 |
disable() | 关闭任务 |
deleteTask(Task) | 从任务链中删除任务 |
setInterval(long) | 将任务的执行间隔设置为指定ms |
二、TaskScheduler实现多线程灯光控制
根据上一节的讲解,我们发现想要直观的控制两盏灯的开关,就需要将相关操作封装成回调方法,有任务调度器在相应的时间策略里来执行调用。
我们通过全局变量来存储等缘故
需要注意的是单片机运算能力还是有限,某些任务需要较高响应速度,不能一味的往上堆任务。在旋钮控灯的基础上,先让LED1闪烁起来吧(呼吸效果需要较多任务计时,先搞清楚基本原理)。
新建项目
1)创建项目Lamp_ task_v1.0并确定。
2)修改platformio.ini配置文件增加串口通讯波特率monitor_speed =115200
2、代码实现
1)编辑main.cpp主文件,为旋钮控灯和闪烁控灯分别创建计时器。
unsigned long currentTime=0; //当前时间,每次loop时判断一次,因此一个即可
//闪灯计时器
unsigned long intervalTime1=1000; //等待间隔
unsigned long previousTime1 = millis();//注册开始时间
bool switch_led1=false;//灯光开关状态
//旋钮计时器
unsigned long intervalTime2=30;//等待间隔
unsigned long previousTime2 = millis();//注册开始时间
2)loop中先取当前时间
currentTime = millis();
3)判断当前时间和计时器登记时间差是不是达到等待要求时间,达到则调用任务,同时,再重置计时器登记时间。两个任务都是这样处理。
void loop()
{
currentTime = millis();//获得当前loop的时间
//判断当前时间和计时器登记时间是不是超过等待时间间隔
if(currentTime-previousTime1>=intervalTime1){
switch_led1=!switch_led1;//改变灯光开关状态
previousTime1=currentTime;//重新登记计时器1时间
}
//根据开关状态控制LED_1开关
digitalWrite(LED_1, switch_led1?HIGH:LOW);
//判断当前时间和计时器登记时间是不是超过等待时间间隔
if(currentTime-previousTime2>=intervalTime2){
knobValue=analogRead(analogInPin);
// 读取模拟数值
analogWrite(LED_2, map(knobValue, 15, 1008, 0, 255));
// 打印串行监视器中的读数
Serial.print("sensor = ");
Serial.println(knobValue);
previousTime2=currentTime;//重新登记计时器2时间
}
}
4)完整代码
#include <Arduino.h>
#include <TaskScheduler.h>
#define analogInPin A0 // 模拟输入引脚A0
#define LED_1 D5 // D5白色LED引脚,本例中先不使用
#define LED_2 D6 // D6绿色LED引脚
bool switch_led1=false;//灯光开关状态
int knobValue = 0;//旋钮读数
Scheduler runner; //任务调度器对象
int dutyCycle =0; //亮度值
// 亮灯任务
void task_led1_H()
{
analogWrite(LED_1,switch_led1?dutyCycle+=3:dutyCycle--);
}
//旋钮任务
void task_knob()
{
knobValue=analogRead(analogInPin);
// 读取模拟数值
analogWrite(LED_2, map(knobValue, 15, 1008, 0, 255));
}
// 定义任务
Task t1(3, TASK_FOREVER, &task_led1_H); //任务名称t1,间隔3ms一直执行.
Task t3(30, TASK_FOREVER, &task_knob);//任务名称t3,间隔50ms,一直执行。
void setup()
{
// put your setup code here, to run once:
Serial.begin(115200); // 设置串口通信波特率
pinMode(LED_1, OUTPUT);
pinMode(LED_2, OUTPUT);
runner.init();//初始化任务调度器
runner.addTask(t1);//增加任务调度
runner.addTask(t3);
t1.enable();//任务开始
t3.enable();
}
void loop()
{
//达到最亮后后转为逐渐熄灭,熄灭200毫秒后改为逐渐亮起,有400毫秒的全黑时间
if(dutyCycle>=255){
switch_led1=false;
}
if(dutyCycle<=-200){
switch_led1=true;
}
runner.execute();//执行任务
}
}
5)烧录代码成功后,控制旋钮观察两盏LED的状态。