如何使用Arduino millis函数执行多任务处理

描述

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

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

  什么是多任务处理?

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

多任务处理

  如图所示,CPU 将时间分成三个相等的部分,并将每个部分分配给每个任务/应用程序。这就是大多数系统中多任务处理的方式。Arduino Multitasking的概念几乎相同,只是时间分布会有所不同。由于 Arduino 与笔记本电脑/手机/PC 相比以低频运行且 RAM 运行,因此分配给每个任务的时间也会有所不同。Arduino 还有一个广泛使用的delay()函数。但在开始之前,让我们讨论一下为什么我们不应该在任何项目中使用delay()函数。

为什么要使用 millis() ?

为了克服使用延迟带来的问题,开发人员应该使用millis()函数,一旦你习惯了它就很容易使用,它会使用100%的CPU性能而不会在执行指令时产生任何延迟。millis()是一个函数,它只返回自 Arduino 板开始运行当前程序而不冻结程序以来经过的毫秒数。大约 50 天后,该时间数将溢出(即回到零)。

就像Arduino有delayMicroseconds()一样,它也有micro版本的millis()作为micros()。micros 和 millis 之间的区别在于,micros() 将在大约 70 分钟后溢出,而 millis() 则为 50 天。因此,根据应用程序,您可以使用millis() 或micros()。   

 

使用毫秒()而不是延迟():

 

要使用millis()进行计时和延迟,您需要记录并存储动作发生的时间以开始时间,然后每隔一段时间检查定义的时间是否已经过去。如前所述,将当前时间存储在一个变量中。

 

无符号长 currentMillis = millis();

 

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

 

unsigned long previousMillis; 
无符号长周期 = 1000;

 

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

 

常量 int ledPin = 4; // 连接的 LED 引脚号
int ledState = LOW; // 用于设置 LED 状态
unsigned 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 的时间
   if (ledState == LOW) { // 如果 LED 关闭,则将其打开,反之亦然
     ledState = HIGH; 
   } 其他 { 
ledState = 低;
} 
   digitalWrite(ledPin, ledState);//设置带ledState的LED再次闪烁
} 
}

 

  在这里,语句《if (currentMillis - previousMillis 》= period)》检查 1000 毫秒是否已过。如果 1000 毫秒过去了,则 LED 闪烁并再次进入相同状态。这种情况还在继续。就是这样,我们已经学会了使用毫秒而不是延迟。这样它就不会在特定的时间间隔内停止程序。

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

  在这里,我们将通过同时处理两个任务来展示 Arduino 多任务处理。这些任务将包括两个 LED 以不同的时间延迟闪烁以及一个按钮,该按钮将用于控制 LED 的开/关状态。所以三个任务将同时执行。

多任务处理

  所需组件

  Arduino UNO

  三个 LED(任何颜色)

  电阻(470、10k)

  跳线

  面包板

  威廉希尔官方网站 原理图

  演示使用Arduino Millis() 函数的威廉希尔官方网站 图 非常简单,无需附加太多组件,如下所示。

多任务处理

为多任务处理编程 Arduino UNO

为多任务编程 Arduino UNO 只需要上面解释的 millis() 工作原理背后的逻辑。建议在开始对 Arduino UNO 进行多任务编程之前,一次又一次地练习使用millis闪烁 LED ,以使逻辑清晰并让自己对 millis() 感到满意。在本教程中,中断还与 millis() 同时用于多任务处理。该按钮将是一个中断。因此,只要产生中断,即按下按钮,LED 就会切换到 ON 或 OFF 状态。

编程从声明连接 LED 和按钮的引脚号开始。

 

诠释 led1 = 6; 
诠释 led2 = 7; 
int toggleLed = 5; 
int 按钮 = 2;

 

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

 

诠释 ledState1 = 低;
诠释 ledState2 = 低;

 

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

 

unsigned long previousMillis1 = 0; 
常量长周期1 = 1000;
unsigned long previousMillis2 = 0; 
常量长周期2 = 200;

 

另一个毫秒函数将用于生成去抖动延迟,以避免多次按下按钮。将有与上述类似的方法。

 

int debouncePeriod = 20;  
int debounceMillis = 0;

 

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

 

bool buttonPushed = false; 
int ledChange = 低;  
诠释最后状态=高;

 

定义引脚的动作,哪个引脚将作为 INPUT 或 OUTPUT 工作。

 

  pinMode(led1,输出);             
  pinMode(led2,输出);
  pinMode(toggleLed,输出);
  pinMode(按钮,输入);

 

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

 

attachInterrupt(digitalPinToInterrupt(pushButton), pushButton_ISR, CHANGE);

 

中断子程序被编写,它只会改变buttonPushed标志。需要注意的是,中断子程序要尽可能的短,所以尽量写,尽量减少多余的指令。

 

无效 pushButton_ISR() 
{ 
  buttonPushed = true; 
}

 

循环首先将毫秒值存储在 currentMillis 变量中,该变量将存储每次循环迭代时经过的时间值。

 

无符号长 currentMillis = millis();

 

多任务处理共有三个功能,1 秒闪烁一个 LED,200 毫秒闪烁第二个 LED,如果按下按钮,则关闭/打开 LED。所以我们将写三个部分来完成这个任务。

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

 

if (currentMillis - previousMillis1 >= period1) { 
    previousMillis1 = currentMillis;  
    如果(ledState1 == 低){ 
      ledState1 = 高;
    } 其他 { 
      ledState1 = 低;
    } 
    digitalWrite(led1, ledState1);   
  }

 

类似地,第二次它通过比较经过的毫秒数每 200 毫秒后切换一次 LED。解释已经在本文前面进行了解释。

 

  if (currentMillis - previousMillis2 >= period2) { 
    previousMillis2 = currentMillis;  
    如果(ledState2 == 低){ 
      ledState2 = 高;
    } 其他 { 
      ledState2 = 低;
    } 
    digitalWrite(led2, ledState2); 
  }

 

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

 

  if (buttonPushed = true) // 检查是否调用了 ISR 
  { 
    if ((currentMillis - debounceMillis) > debouncePeriod && buttonPushed) // 产生 20ms 的去抖延迟以避免多次按下
    { 
      debounceMillis = currentMillis; // 保存最后的去抖动延迟时间
      if (digitalRead(pushButton) == LOW && lastState == HIGH) // 按下按钮后改变LED 
      { 
        ledChange = ! 领导改变;
        digitalWrite(toggleLed, ledChange);   
        最后状态 = 低;
      } 
      else if (digitalRead(pushButton) == HIGH && lastState == LOW)     
      { 
        lastState = HIGH; 
      } 
     buttonPushed = 假;
    } 
  }

 

  这样就完成了Arduino millis​() 教程。请注意,为了习惯使用millis(),只需练习在其他一些应用程序中实现此逻辑即可。

/* 使用 Arduino millis() 函数进行多任务处理

作者:CircuitDigest (circuitdigest.com)

*/


诠释 led1 = 6; // led1 连接在引脚 6

int led2 = 7; // led1 连接在引脚 7

int toggleLed = 5; // 按钮控制的 LED 连接在引脚 5

int pushButton = 2; // 将按钮连接到引脚 2,这也是中断引脚


诠释 ledState1 = 低;// 判断 led1 和 led2 的状态

int ledState2 = LOW;


unsigned long previousMillis1 = 0; //存储上次 LED1 闪烁的时间

const long period1 = 1000; // led1 闪烁的时间,单位为 ms


unsigned long previousMillis2 = 0; //存储上次 LED2 闪烁的时间

const long period2 = 200; // led1 闪烁的时间,单位为 ms


int debouncePeriod = 20; // 20ms 的去抖动延迟

int debounceMillis = 0; // 类似于previousMillis


bool buttonPushed = false; // 中断例程按钮状态

int ledChange = LOW; // 跟踪 LED 状态 last

int lastState = HIGH; // 跟踪最后一个按钮状态



无效设置(){

pinMode(led1,输出);// 将引脚定义为输入或输出

pinMode(led2, OUTPUT);

pinMode(toggleLed,输出);

pinMode(按钮,输入);

attachInterrupt(digitalPinToInterrupt(pushButton), pushButton_ISR, CHANGE); // 使用中断 pin2

}


无效 pushButton_ISR()

{

buttonPushed = true; // ISR 应该尽可能短

}


void loop() {

unsigned long currentMillis = millis(); // 存储当前时间


if (currentMillis - previousMillis1 >= period1) { // 检查是否经过了 1000ms

previousMillis1 = currentMillis; // 保存上次闪烁 LED 的时间

if (ledState1 == LOW) { // 如果 LED 关闭,则将其打开,反之亦然

ledState1 = HIGH; //更改下一次迭代的 LED 状态

} else {

ledState1 = LOW;

}

digitalWrite(led1, ledState1); //用ledState设置LED再次闪烁

}


if (currentMillis - previousMillis2 >= period2) { // 检查是否经过了 1000ms

previousMillis2 = currentMillis; // 保存上次闪烁 LED 的时间

if (ledState2 == LOW) { // 如果 LED 关闭,则将其打开,反之亦然

ledState2 = HIGH;

} 其他 {

ledState2 = 低;

}

digitalWrite(led2, ledState2);//设置带ledState的LED再次闪烁

}


if (buttonPushed = true) // 检查是否调用了 ISR

{

if ((currentMillis - debounceMillis) > debouncePeriod && buttonPushed) // 产生 20ms 的去抖延迟以避免多次按下

{

debounceMillis = currentMillis; // 保存最后的去抖动延迟时间

if (digitalRead(pushButton) == LOW && lastState == HIGH) // 按下按钮后改变LED

{

ledChange = ! 领导改变;

digitalWrite(toggleLed, ledChange);

最后状态 = 低;

}

else if (digitalRead(pushButton) == HIGH && lastState == LOW)

{

lastState = HIGH;

}

buttonPushed = 假;

}

}

}
 

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分