|
多任务处理使计算机发生了一场革命,一个或多个程序可以同时运行,从而提高了效率、灵活性、适应性和生产率。在嵌入式系统中,微控制器还可以处理多任务处理并同时执行两个或多个任务,而无需停止当前指令。
在本篇文章中,我们将学习Arduino如何执行多任务处理。通常在Arduino中使用delay()函数来执行LED闪烁等周期性任务,但是此delay()函数会暂停程序一段时间,并且不允许执行其他操作。因此,本文解释了我们如何避免使用delay()函数并将其替换为millis()以同时执行多个任务,并使Arduino成为多任务控制器。在详细介绍之前,我们先来谈谈多任务处理。
什么是多任务处理? 多任务处理意味着同时同时执行多个任务或程序。几乎所有操作系统都具有多任务处理功能。这种操作系统称为MOS(多任务操作系统)。 MOS可以是移动或台式PC操作系统。计算机中多任务处理的好例子是用户同时运行电子邮件应用程序、互联网浏览器、媒体播放器、游戏,如果用户不想使用该应用程序,如果该应用没有关闭,那么它就在后台运行。最终用户同时使用所有这些应用程序,但OS对待这些概念有点不同。我们来讨论操作系统如何管理多任务处理。
如图所示,CPU将时间分成三个相等的部分,并将每个部分分配给每个任务/应用程序。这就是在大多数系统中完成多任务处理的方式。除了时间分布会有所不同之外,Arduino多任务处理的概念几乎相同。由于Arduino以低频运行并且RAM与笔记本电脑/移动/ PC相比很小,因此每项任务的时间也将不同。 Arduino还有一个广泛使用的delay()函数。但在开始之前,让我们讨论为什么我们不应该在任何项目中使用delay()函数。
为什么跳过Arduino中的delay()? 如果考虑Arduino的参考文档,那么有两种类型的延迟函数,第一种是delay(),第二种是delayMicroseconds()。两个函数在产生延迟时间方面是相同的。唯一的区别在于,在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()? 为了克服使用delay()函数引起的问题,开发人员应该使用millis()函数,这个函数在习惯使用后很容易使用,它将使用100%的CPU性能而不会在执行指令时产生任何延迟。 millis()函数只返回自Arduino开发板开始运行当前程序以来没有冻结程序所经过的毫秒数。大约50天后,这个时间数将溢出(即回到零)。
就像Arduino有delayMicroseconds()一样,它也有微型版本的millis(),那就是micros()。 micros和millis之间的差异是,micros()在大约70分钟后会溢出,而millis()则是50天。因此,根据应用程序,您可以使用millis()或micros()。
使用millis()而不是delay() 要使用millis()进行计时和延迟,您需要记录并存储操作开始时间的时间,然后定期检查定义的时间是否已过。 如上所述,将当前时间存储在变量中。 - unsigned long currentMillis = millis();
复制代码我们还需要两个变量来确定是否已经过了所需的时间。 我们已将当前时间存储在currentMillis变量中,但我们还需要知道定时周期何时开始以及周期有多长。 因此声明了Interval和previousMillis。 Interval将告诉我们时间延迟,previosMillis将存储事件发生的最后时间。 - unsigned long previousMillis;
- unsigned long period = 1000;
复制代码
为了理解这一点,我们举一个简单的闪烁LED示例。 period = 1000将告诉我们LED将闪烁1秒或1000ms。 - const int ledPin = 4; // the LED pin number connected
- int ledState = LOW; // used to set the LED state
- unsigned long previousMillis = 0; //will store last time LED was blinked
- const long period = 1000; // period at which to blink in ms
- void setup() {
- pinMode(ledPin, OUTPUT); // set ledpin as output
- }
- void loop() {
- unsigned long currentMillis = millis(); // store the current time
- if (currentMillis - previousMillis >= period) { // check if 1000ms passed
- previousMillis = currentMillis; // save the last time you blinked the LED
- if (ledState == LOW) { // if the LED is off turn it on and vice-versa
- ledState = HIGH;
- } else {
- ledState = LOW;
- }
- digitalWrite(ledPin, ledState);//set LED with ledState to blink again
- }
- }
复制代码
这里,语句< if (currentMillis - previousMillis >= period)>检查是否已经过去了1000ms。如果超过1000ms,则LED闪烁并再次进入相同状态。这继续下去。就是这样,我们学会了使用millis而不是延迟。这样它就不会停止特定间隔的程序。
Arduino的中断与其他微控制器的工作方式相同。 Arduino UNO板有两个独立的引脚,用于连接GPIO引脚2和3上的中断。我们在Arduino中断教程中详细介绍了它,您可以在其中了解有关中断及其使用方法的更多信息。
在这里,我们将通过同时处理两个任务来显示Arduino多任务。任务将包括在不同时间延迟中闪烁两个LED以及用于控制LED的ON / OFF状态的按钮。因此,将同时执行三项任务。
需要的组件 ● Arduino UNO开发板 ● 三个LED(任意颜色) ● 电阻(470和10k) ● 跳线 ● 面包板
电路原理图 演示Arduino多任务处理的电路图非常简单,没有太多组件可以连接,如下所示。
编程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每1秒闪烁一次,另一个LED在200ms后闪烁。 - unsigned long previousMillis1 = 0;
- const long period1 = 1000;
- unsigned long previousMillis2 = 0;
- const long period2 = 200;
复制代码另一个millis函数将用于产生去抖延迟,以避免多次按下按钮。将采用与上述类似的方法。 - int debouncePeriod = 20;
- int debounceMillis = 0;
复制代码
这三个变量将用于存储按钮状态作为中断、切换LED和按钮状态。 - bool buttonPushed = false;
- int ledChange = LOW;
- int lastState = HIGH;
复制代码定义引脚的作用,哪个引脚将用作INPUT或OUTPUT。 - pinMode(led1, OUTPUT);
- pinMode(led2, OUTPUT);
- pinMode(toggleLed, OUTPUT);
- 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();
复制代码
多任务处理共有三个功能,1秒闪烁一个LED,200ms闪烁第二个LED,按下按钮然后关闭/打开LED。因此,我们将编写三个部分来完成此任务。
第一个功能是通过比较经过的毫秒来每1秒切换LED状态。 - if (currentMillis - previousMillis1 >= period1) {
- previousMillis1 = currentMillis;
- if (ledState1 == LOW) {
- ledState1 = HIGH;
- } else {
- ledState1 = LOW;
- }
- digitalWrite(led1, ledState1);
- }
复制代码
类似地,第二个功能,它通过比较经过的毫秒来每隔200ms切换LED。本文前面已经解释了这种方式。 - if (currentMillis - previousMillis2 >= period2) {
- previousMillis2 = currentMillis;
- if (ledState2 == LOW) {
- ledState2 = HIGH;
- } else {
- ledState2 = LOW;
- }
- digitalWrite(led2, ledState2);
- }
复制代码
最后,监视buttonPushed标志,并在产生20ms的去抖延迟后,它只是切换LED的状态对应于作为中断附加的按钮。 - if (buttonPushed = true) // check if ISR is called
- {
- if ((currentMillis - debounceMillis) > debouncePeriod && buttonPushed) // generate 20ms debounce delay to avoid multiple presses
- {
- debounceMillis = currentMillis; // save the last debounce delay time
- if (digitalRead(pushButton) == LOW && lastState == HIGH) // change the led after push button is pressed
- {
- ledChange = ! ledChange;
- digitalWrite(toggleLed, ledChange);
- lastState = LOW;
- }
- else if (digitalRead(pushButton) == HIGH && lastState == LOW)
- {
- lastState = HIGH;
- }
- buttonPushed = false;
- }
- }
复制代码
以上就是Arduino millis()函数的全部内容。 请注意,为了习惯使用millis(),只需练习在其他一些应用程序中实现此逻辑。 您还可以将其扩展为使用电机、伺服电机、传感器和其他外围设备。 如有任何疑问,请在本帖下面进行回复。
代码 本文使用的完整代码如下:
main.rar
(1.08 KB, 下载次数: 169)
|