在多任务已经导致了计算机革命,其中一个或多个程序可以同时运行从而提高了效率,灵活性,适应性和效率。在嵌入式系统中,微控制器还可以处理多任务,并同时执行两个或多个任务,而无需暂停当前指令。
在本教程中,我们将学习Arduino如何使用 Arduino millis功能执行多任务处理。通常,在Arduino中使用 delay() 函数来执行诸如LED闪烁之类的定期任务,但是该delay()函数会使程序暂停一定的时间,并且不允许其他操作执行。因此,本文说明了如何避免使用delay()函数并将其替换为millis()以同时执行多个任务并使Arduino成为多任务控制器。在详细介绍之前,让我们从低估多任务处理开始。
什么是多任务处理?
多任务只是意味着同时执行多个任务或程序。几乎所有操作系统都具有多任务处理功能。这种操作系统称为MOS(多任务操作系统)。 MOS可以是移动或台式PC操作系统。当用户同时运行电子邮件应用程序,Internet浏览器,媒体播放器,游戏时,如果用户不希望使用该应用程序,则它会在后台运行(如果未关闭),这是计算机中多任务的一个很好的例子。最终用户同时使用所有这些应用程序,但是OS对此概念稍有不同。让我们讨论一下OS如何管理多任务。
如图所示,CPU将时间分为三个相等的部分,并将每个部分分配给每个任务/应用程序。这就是大多数系统中完成多任务处理的方式。Arduino多任务处理的概念几乎相同,只是时间分配有所不同。由于Arduino的运行频率较低,RAM与笔记本电脑/移动设备/ PC相比,因此分配给每个任务的时间也将有所不同。Arduino还具有被广泛使用的 delay() 函数。但是在开始之前,让我们讨论一下为什么不应该在任何项目中使用 delay() 函数。
为什么要在Arduino中跳过delay()?
如果考虑Arduino的参考文档,则有两种类型的延迟函数,第一种是delay(),第二种是delayMicroseconds()。就产生延迟而言,这两个功能是相同的。唯一的区别是,在delay()函数中,传递的参数整数以毫秒为单位,即,如果我们编写delay(1000),则延迟将为1000毫秒,即1秒。类似地,在delayMicroseconds()函数中,传递的参数以微秒为单位,即,如果我们编写delayMicroseconds(1000),则延迟将为1000微秒,即1毫秒。
到了这一点,两个函数都将程序暂停一段延迟函数中经过的时间。因此,如果我们给出1秒的延迟,那么处理器必须经过1秒才能转到下一条指令。同样,如果延迟为10秒,则程序将停止10秒,并且处理器将不允许经过下一个指令,直到经过10秒。在速度和执行指令方面,这会影响微控制器的性能。
解释延迟功能缺点的最佳示例是使用两个按钮。考虑我们要使用两个按钮切换两个LED。因此,如果按下一个按钮,则相应的LED应发光2秒钟,类似地,如果按下第二个按钮,则LED应发光4秒钟。但是,当我们使用delay()时,如果用户按下第一个按钮,则程序将停止2秒钟;如果用户在延迟2秒钟之前按下第二个按钮,则微控制器将不接受输入,因为程序是在停止阶段。
Arduino的官方文档在其delay()函数说明的注释和警告中明确提到了这一点。您可以检查一下以使其更加清晰。
为什么要使用millis()?
为了克服使用延迟引起的问题,开发人员应该使用 Millis() 函数,一旦习惯 后就 可以轻松使用它,它将使用100%的CPU性能,而不会在执行指令时产生任何延迟。millis()是一个函数,它仅返回自Arduino开发板开始运行当前程序起经过的毫秒数,而不会冻结程序。大约50天后,该时间数字将溢出(即返回零)。
就像Arduino具有delayMicroseconds()一样,它也具有millis()的微版本作为micros()。micros和millis之间的区别在于,与50天的millis()相比,micros()将在大约70分钟后溢出。因此,根据应用程序,您可以使用millis()或micros()。
使用millis()而不是delay():
要将millis()用于计时和延迟,您需要记录并存储操作开始的时间,然后间隔检查所定义的时间是否过去。因此,如上所述,将当前时间存储在变量中。
unsigned long currentMillis = millis();
我们还需要两个变量来确定所需时间是否已过去。我们将当前时间存储在 currentMillis 变量中,但是我们还需要知道计时周期从何时开始以及该周期有多长。因此,声明了Interval和 previousMillis 。时间间隔将告诉我们时间延迟,previosMillis将存储事件的上一次发生时间。
未签名的长期以前的Millis; 无符号长周期= 1000;
为了理解这一点,让我们以一个简单的LED闪烁为例。周期= 1000将告诉我们LED会闪烁1秒钟或1000ms。
const int ledPin = 4; //连接的LED引脚号 int ledState = LOW; //用于将LED状态设置为 无符号长long previousMillis = 0; //将存储上一次LED闪烁的时间 const long period = 1000; //毫秒内闪烁的时间段 void setup(){ pinMode(ledPin,OUTPUT); //将ledpin设置为输出 } void loop(){ unsigned long currentMillis = millis(); // //存储当前时间 if(currentMillis-previousMillis> = period){//检查是否传递了1000ms的 previousMillis = currentMillis; //保存上一次LED闪烁的时间, 如果(ledState == LOW){//如果LED熄灭,则将其打开,反之亦然, ledState = HIGH; }其他{ ledState = LOW; } digitalWrite(ledPin,ledState); //将LED设置为ledState再次闪烁 } }
在这里,声明
Arduino中的中断与其他微控制器中的中断相同。Arduino UNO板有两个单独的引脚,用于在GPIO引脚2和3上附加中断。我们在 Arduino中断教程中对其进行了详细介绍,您可以在其中了解有关中断以及如何使用它们的更多信息。
在这里,我们将通过同时处理两个任务来展示Arduino Multitasking。任务将包括两个LED在不同的时间延迟中闪烁以及一个按钮,该按钮将用于控制LED的ON / OFF状态。因此,将同时执行三个任务。
所需组件
- Arduino UNO
- 三个LED(任何颜色)
- 电阻(470,10k)
- 跳线
- 面包板
电路原理图
演示使用Arduino Millis()功能的电路图 非常简单,并且没有要连接的太多组件,如下所示。
为多任务编程Arduino UNO
对Arduino UNO进行多任务编程将仅需要millis()工作原理的逻辑,如上所述。建议在开始对Arduino UNO进行多任务编程之前,反复使用 millis 练习闪烁LED ,以使逻辑清晰并让自己适应millis()。在本教程中,中断也与millis()同时用于多任务处理。 该按钮将是一个中断。因此,每当产生中断(即按下按钮)时,LED就会切换到ON或OFF状态。编程从声明连接LED和按钮的引脚号开始。
int led1 = 6; int led2 = 7; int toggleLed = 5; int pushButton = 2;
接下来,我们编写一个变量来存储LED的状态以备将来使用。
int ledState1 = LOW; int ledState2 = LOW;
就像上面的眨眼示例中所解释的那样,声明了period和previousmillis的变量以比较LED并产生延迟。第一个LED每隔一秒钟闪烁一次,另一个LED在每隔200ms闪烁一次。
unsigned long前一个Millis1 = 0; const long period1 = 1000; unsigned long前一个Millis2 = 0; const long period2 = 200;
另一个毫秒功能将用于产生反跳延迟,从而避免多次按下按钮。将有与上述类似的方法。
int debouncePeriod = 20; int debounceMillis = 0;
这三个变量将用于将按钮的状态存储为中断,切换LED和按钮状态。
bool buttonPushed = false; int ledChange = LOW; int lastState = HIGH;
定义引脚的作用,即哪个引脚将用作输入或输出。
pinMode(led1,输出); pinMode(LED2,输出); pinMode(toggleLed,输出); pinMode(pushButton,INPUT);
现在,通过将中断附加到ISR和中断模式来定义中断引脚。请注意,在声明 attachInterrupt() 函数以将实际数字引脚转换为特定的中断号时,建议使用 digitalPinToInterrupt(pin_number) 。
attachInterrupt(digitalPinToInterrupt(pushButton),pushButton_ISR,CHANGE);
写入了中断子例程,它将仅更改buttonPushed标志。请注意,中断子程序应尽可能短,因此请尝试编写该子程序并尽量减少额外的指令。
void pushButton_ISR() { buttonPushed = true; }
循环从将Millis值存储在currentMillis变量开始,该变量将存储每次循环迭代时所经过的时间值。
unsigned long currentMillis = millis();
多任务处理共有三项功能,一秒闪烁一个LED,一秒闪烁200ms,如果按下按钮,则关闭/开启LED。因此,我们将编写三个部分来完成此任务。
第一个是通过比较经过的毫秒数,每隔1秒切换一次LED状态。
if(currentMillis-previousMillis1> = period1 ){ previousMillis1 = currentMillis; 如果(ledState1 == LOW){ ledState1 = HIGH; } else { ledState1 = LOW; } digitalWrite(led1,ledState1); }
类似地,其次,它通过比较经过的毫秒数,每200毫秒切换一次LED。该解释已在本文前面进行了解释。
if(currentMillis-previousMillis2> = period2 ){ previousMillis2 = currentMillis; 如果(ledState2 == LOW){ ledState2 = HIGH; } else { ledState2 = LOW; } digitalWrite(led2,ledState2); }
最后,对 buttonPushed 标志进行监视,并在产生20ms的反跳延迟后,仅切换LED的状态,该状态对应于作为中断连接的按钮。
if(buttonPushed = true)//检查是否调用了ISR { if(((currentMillis-debounceMillis)> debouncePeriod && buttonPushed)// //产生20ms的反跳延迟,以避免多次按下 { debounceMillis = currentMillis; //保存最后的反跳延迟时间, 如果(digitalRead(pushButton)== LOW && lastState == HIGH)//按下按钮后更改led { ledChange =! ledChange; digitalWrite(toggleLed,ledChange); lastState = LOW; } 否则,如果(digitalRead(pushButton)== HIGH && lastState == LOW) { lastState = HIGH; } buttonPushed = false; } }
这就完成了Arduino millis()教程。请注意,为了习惯使用 millis(), 只需练习在其他一些应用程序中实现此逻辑即可。您还可以扩展它以使用电动机,伺服电动机,传感器和其他外围设备。如有任何疑问,请写信给我们的论坛或在下面发表评论。
下面提供了用于演示在Arduino中使用millis功能的完整代码和视频。