风筝
发表于: 2019-5-17 16:36:23 | 显示全部楼层

多任务处理使计算机发生了一场革命,一个或多个程序可以同时运行,从而提高了效率、灵活性、适应性和生产率。在嵌入式系统中,微控制器还可以处理多任务处理并同时执行两个或多个任务,而无需停止当前指令。


在本篇文章中,我们将学习Arduino如何执行多任务处理。通常在Arduino中使用delay()函数来执行LED闪烁等周期性任务,但是此delay()函数会暂停程序一段时间,并且不允许执行其他操作。因此,本文解释了我们如何避免使用delay()函数并将其替换为millis()以同时执行多个任务,并使Arduino成为多任务控制器。在详细介绍之前,我们先来谈谈多任务处理。


什么是多任务处理?

多任务处理意味着同时同时执行多个任务或程序。几乎所有操作系统都具有多任务处理功能。这种操作系统称为MOS(多任务操作系统)。 MOS可以是移动或台式PC操作系统。计算机中多任务处理的好例子是用户同时运行电子邮件应用程序、互联网浏览器、媒体播放器、游戏,如果用户不想使用该应用程序,如果该应用没有关闭,那么它就在后台运行。最终用户同时使用所有这些应用程序,但OS对待这些概念有点不同。我们来讨论操作系统如何管理多任务处理。

What-is-Multitasking-in-Arduino.png


如图所示,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()进行计时和延迟,您需要记录并存储操作开始时间的时间,然后定期检查定义的时间是否已过。 如上所述,将当前时间存储在变量中。

  1. unsigned long currentMillis = millis();
复制代码

我们还需要两个变量来确定是否已经过了所需的时间。 我们已将当前时间存储在currentMillis变量中,但我们还需要知道定时周期何时开始以及周期有多长。 因此声明了Interval和previousMillis。 Interval将告诉我们时间延迟,previosMillis将存储事件发生的最后时间。

  1. unsigned long previousMillis;
  2. unsigned long period = 1000;
复制代码

为了理解这一点,我们举一个简单的闪烁LED示例。 period = 1000将告诉我们LED将闪烁1秒或1000ms。

  1. const int ledPin =  4; // the LED pin number connected
  2. int ledState = LOW;             // used to set the LED state
  3. unsigned long previousMillis = 0;  //will store last time LED was blinked
  4. const long period = 1000;         // period at which to blink in ms

  5. void setup() {
  6.   pinMode(ledPin, OUTPUT); // set ledpin as output
  7. }

  8. void loop() {
  9. unsigned long currentMillis = millis(); // store the current time
  10.   if (currentMillis - previousMillis >= period) { // check if 1000ms passed
  11.    previousMillis = currentMillis;   // save the last time you blinked the LED
  12.    if (ledState == LOW) { // if the LED is off turn it on and vice-versa
  13.      ledState = HIGH;
  14.    } else {
  15. ledState = LOW;
  16. }
  17.    digitalWrite(ledPin, ledState);//set LED with ledState to blink again
  18. }
  19. }
复制代码

这里,语句< if (currentMillis - previousMillis >= period)>检查是否已经过去了1000ms。如果超过1000ms,则LED闪烁并再次进入相同状态。这继续下去。就是这样,我们学会了使用millis而不是延迟。这样它就不会停止特定间隔的程序。


Arduino的中断与其他微控制器的工作方式相同。 Arduino UNO板有两个独立的引脚,用于连接GPIO引脚2和3上的中断。我们在Arduino中断教程中详细介绍了它,您可以在其中了解有关中断及其使用方法的更多信息。


在这里,我们将通过同时处理两个任务来显示Arduino多任务。任务将包括在不同时间延迟中闪烁两个LED以及用于控制LED的ON / OFF状态的按钮。因此,将同时执行三项任务。

Circuit-Hardware-for-Arduino-Multitasking.jpg


需要的组件

●    Arduino UNO开发板

●    三个LED(任意颜色)

●    电阻(470和10k)

●    跳线

●    面包板


电路原理图

演示Arduino多任务处理的电路图非常简单,没有太多组件可以连接,如下所示。

Circuit-Diagram-for-Arduino-Multitasking.png


编程Arduino UNO进行多任务处理

编程Arduino UNO进行多任务处理只需要背后millis()工作的逻辑,如上所述。建议在开始编程Arduino UNO进行多任务处理之前,一次又一次地使用millis练习闪烁LED以使逻辑清晰并使自己熟悉millis()。在本文中,中断也与millis()同时用于多任务处理。该按钮将是一个中断。因此,每当产生中断,即按下按钮时,LED将切换到ON或OFF状态。

首先,声明LED和按钮连接的引脚编号。

  1. int led1 =  6;
  2. int led2 =  7;
  3. int toggleLed = 5;
  4. int pushButton = 2;
复制代码

接下来,我们编写一个变量来存储LED的状态以供将来使用。

  1. int ledState1 = LOW;
  2. int ledState2 = LOW;
复制代码

正如上面在闪烁示例中所解释的那样,声明了period和previousmillis的变量来比较并产生LED的延迟。第一个LED每1秒闪烁一次,另一个LED在200ms后闪烁。

  1. unsigned long previousMillis1 = 0;
  2. const long period1 = 1000;
  3. unsigned long previousMillis2 = 0;
  4. const long period2 = 200;
复制代码

另一个millis函数将用于产生去抖延迟,以避免多次按下按钮。将采用与上述类似的方法。

  1. int debouncePeriod = 20;  
  2. int debounceMillis = 0;
复制代码

这三个变量将用于存储按钮状态作为中断、切换LED和按钮状态。

  1. bool buttonPushed = false;
  2. int ledChange = LOW;  
  3. int lastState = HIGH;
复制代码

定义引脚的作用,哪个引脚将用作INPUT或OUTPUT。

  1.   pinMode(led1, OUTPUT);            
  2.   pinMode(led2, OUTPUT);
  3.   pinMode(toggleLed, OUTPUT);
  4.   pinMode(pushButton, INPUT);
复制代码

现在通过附加ISR和中断模式定义的中断来定义中断引脚。注意,在声明attachInterrupt()函数将实际数字引脚转换为特定中断号时,建议使用digitalPinToInterrupt(pin_number)。

  1. attachInterrupt(digitalPinToInterrupt(pushButton), pushButton_ISR, CHANGE);
复制代码

写入中断子程序,它只会改变buttonPushed标志。请注意,中断子程序应尽可能短,因此请尝试编写并最小化额外指令。

  1. void pushButton_ISR()
  2. {
  3.   buttonPushed = true;
  4. }
复制代码

循环开始时将millis值存储在currentMillis变量中,该变量将存储每次循环迭代时经过的时间值。

  1. unsigned long currentMillis = millis();
复制代码

多任务处理共有三个功能,1秒闪烁一个LED,200ms闪烁第二个LED,按下按钮然后关闭/打开LED。因此,我们将编写三个部分来完成此任务。


第一个功能是通过比较经过的毫秒来每1秒切换LED状态。

  1. if (currentMillis - previousMillis1 >= period1) {
  2.     previousMillis1 = currentMillis;  
  3.     if (ledState1 == LOW) {
  4.       ledState1 = HIGH;
  5.     } else {
  6.       ledState1 = LOW;
  7.     }
  8.     digitalWrite(led1, ledState1);   
  9.   }
复制代码

类似地,第二个功能,它通过比较经过的毫秒来每隔200ms切换LED。本文前面已经解释了这种方式。

  1.   if (currentMillis - previousMillis2 >= period2) {
  2.     previousMillis2 = currentMillis;  
  3.     if (ledState2 == LOW) {
  4.       ledState2 = HIGH;
  5.     } else {
  6.       ledState2 = LOW;
  7.     }
  8.     digitalWrite(led2, ledState2);
  9.   }
复制代码

最后,监视buttonPushed标志,并在产生20ms的去抖延迟后,它只是切换LED的状态对应于作为中断附加的按钮。

  1.   if (buttonPushed = true)    // check if ISR is called
  2.   {
  3.     if ((currentMillis - debounceMillis) > debouncePeriod && buttonPushed)  // generate 20ms debounce delay to avoid multiple presses
  4.     {
  5.       debounceMillis = currentMillis;      // save the last debounce delay time
  6.       if (digitalRead(pushButton) == LOW && lastState == HIGH)     // change the led after push button is pressed
  7.       {
  8.         ledChange = ! ledChange;
  9.         digitalWrite(toggleLed, ledChange);   
  10.         lastState = LOW;
  11.       }
  12.       else if (digitalRead(pushButton) == HIGH && lastState == LOW)   
  13.       {
  14.         lastState = HIGH;
  15.       }
  16.      buttonPushed = false;
  17.     }
  18.   }
复制代码

以上就是Arduino millis()函数的全部内容。 请注意,为了习惯使用millis(),只需练习在其他一些应用程序中实现此逻辑。 您还可以将其扩展为使用电机、伺服电机、传感器和其他外围设备。 如有任何疑问,请在本帖下面进行回复。


代码

本文使用的完整代码如下: main.rar (1.08 KB, 下载次数: 162)

跳转到指定楼层
undefined
发表于: 2020-8-3 16:03:45 | 显示全部楼层

谢谢大佬,新手,写了一个电机驱动,后面跟了一个舵机驱动,但电机转,舵机转!!
回复

使用道具 举报

shadow1988
发表于: 2022-5-26 09:39:54 | 显示全部楼层

我似乎明白了,其实就是通过检测当前时间和之前时间的差值看是否等于我需要定时的值,比如我需要定时1000ms,第一次循环,程序运行了10ms,比较值初始为0,那我现在检测到的就是延时10ms,没达到我改变IO输出的条件,程序继续运行,时间累加到1000ms,判断条件满足,切换IO状态,同时将当前时间赋给比较值,继续下一次计时周期,由于比较值已经变成了之前一次切换状态的时间,所以下一次切换状态就会是同样时间,说白了millis就是检测程序运行时间来达到定时目的,delay就是延时,millis算是中断(初学者浅见,有错希望大佬指正
回复

使用道具 举报

郭小宏
发表于: 2022-8-3 11:34:58 | 显示全部楼层

本帖最后由 郭小宏 于 2022-8-3 11:36 编辑

請問有辦法設定按下按鈕後,其中一個燈只亮十秒一次而已嗎,不要循環,然後其他正常運行
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

主题 700 | 回复: 1483



手机版|

GMT+8, 2024-5-6 15:43 , Processed in 0.039360 second(s), 5 queries , Gzip On, MemCache On. Powered by Discuz! X3.5

YiBoard一板网 © 2015-2022 地址:河北省石家庄市长安区高营大街 ( 冀ICP备18020117号 )

快速回复 返回顶部 返回列表