Author:teacherXue
一、什么是多线程
从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。具有这种能力的系统包括对称多处理机、多核心处理器以及芯片级多处理或同时多线程处理器。在一个程序中,这些独立运行的程序片段叫作“线程”(Thread),利用它编程的概念就叫作“多线程处理”。
简单总结就是,他让我们感觉设备同时在处理很多事情,例如敲着代码听着歌。单线程模式的化,我们可能敲一下键盘,歌曲停一下。而且实际上,显示器显示本身也是一个任务,所以你不得不象在卡PPT一样的体验中操作设备。
单片机多任务问题
单片机系统没有对称多处理机,在没有多核心的情况下,貌似没有办法进行多线程处理。上一章节提到过,在旋钮控灯的代码中注释或者删掉之前的呼吸灯代码。你可以尝试重新让呼吸灯代码生效,就会发现,旋钮的控制变得非常的卡顿。
这是因为loop方法内部本身是单线程运行,呼吸灯在循环进行亮度逐步改变的过程中,是不会执行到后面的读取旋钮值的代码的。于是只能在完成一次明暗转换的过程后才能读取一次旋钮值。
榨干CPU的运算时间
ESP8266 等芯片虽然属于单片机系统,但象ESP8266这样的芯片,80MHZ运行频率已经赶上我最早的PC系统了,执行速度远远大于人的反应速度。所以很多情况下我们需要人为的让CPU运算闲置,例如前面代码中的delay操作。
如果完全利用主程序的loop循环方法,通过计时器在每次任务循环中判断程序执行耗时,来确定是否执行,就可以让cpu的尽可能得多运算时间被利用。通俗的说,就是只要我足够快,你就不知道我作弊。
Arduino开发框架中可以使用状态机,定时器,中断等来实现多任务,也可以使用Arduino封装好的库函数,比如TaskScheduler.h库函数。
二、定时器实现多任务处理
我们先使用较为简单的计时器来实现多任务并行操作,其核心原理就是在任务开始时进行计时,并在每次loop循环中判断是否满足等待时间延迟。满足再调度任务,不满足轮空即可,参考下图理解一下。
![](https://i-blog.csdnimg.cn/blog_migrate/d9fd5fe7783f2f361fb39062cdd6baf8.png)
需要注意的是单片机运算能力还是有限,某些任务需要较高响应速度,不能一味的往上堆任务。在旋钮控灯的基础上,先让LED1闪烁起来吧(呼吸效果需要较多任务计时,先搞清楚基本原理)。
新建项目
1)创建项目Lamp_ task_v1.0并确定。
![](https://i-blog.csdnimg.cn/blog_migrate/cb49c1b591e5b1a468de398b9e6d66df.png)
2)修改platformio.ini配置文件增加串口通讯波特率monitor_speed = 115200
代码实现
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>
#define analogInPin A0 // 模拟输入引脚A0
#define LED_1 D5 // D5白色LED引脚,本例中先不使用
#define LED_2 D6 // D6绿色LED引脚
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();//注册开始时间
int knobValue = 0;
void setup()
{
// put your setup code here, to run once:
Serial.begin(115200); // 设置串口通信波特率
pinMode(LED_1, OUTPUT);
pinMode(LED_2, OUTPUT);
}
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)烧录代码成功后,控制旋钮观察两盏LED的状态。
![](https://img-blog.csdnimg.cn/img_convert/c9864f35a1997f3a1be5c6f9bae68c01.gif)