一板网电子技术论坛

风筝
发表于: 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, 下载次数: 5)

跳转到指定楼层
回复

使用道具 举报

Q82380705
发表于: 2019-5-26 16:41:00 | 显示全部楼层

的我群完全俄武器俄武器 俄武器俄武器俄武器俄武器完全俄武器恶趣味二千
回复

使用道具 举报

Q82380705
发表于: 2019-5-26 16:41:24 | 显示全部楼层

未全额完全恶趣味恶趣味千万请问完全恶趣味俄武器完全俄武器
回复

使用道具 举报

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

本版积分规则

主题 372 | 回复: 666



手机版|

GMT+8, 2019-9-21 10:37 , Processed in 0.140624 second(s), 31 queries . Powered by Discuz! X3.4

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

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