millis()函式、按鈕開關防彈跳及長按功能

電路接線圖

millis()函式:從開機後到當下所經歷的毫秒時間。

例:LED燈每秒改變一次狀態。

const int ledPin = 15;               // LED接腳
unsigned long previousMillis = 0;    // 前一次的millis()時間
const long interval = 1000;          // 預設計時的時間
bool state = HIGH;                   // LED狀態,初始值為HIGH(OFF)

void setup() {
  pinMode(ledPin, OUTPUT);           // 宣告ledPin為輸出腳
  digitalWrite(ledPin, state);       // 輸出狀態
}

void loop() {
  unsigned long currentMillis = millis();          // 當前的millis()時間

  if (currentMillis - previousMillis >= interval)  // 若達到預設計時的時間
  {
    //---------------------------------------------------------------------
    // 每經過一個interval的時間,要做的事
    //---------------------------------------------------------------------
    state = !state;                  // 改變LED狀態
    digitalWrite(ledPin, state);     // 輸出狀態
    //---------------------------------------------------------------------

    previousMillis = currentMillis;  // 更新前一次的millis()時間
  }
}

按鈕開關若使用delay()做為防彈跳延遲時間,程式會暫停一段時間,為避免造成阻塞,可改用millis()的方式計算防彈跳延遲時間,基本做法如下:

const int pb = 25;                   // 按鈕開關接腳
bool lastButtonState = HIGH;         // 按鈕開關前一次的狀態
unsigned long lastDebounceTime = 0;  // 按鈕開關前一次狀態改變時的millis()時間
int debounceDelay = 80;              // 防彈跳預設的延遲時間

void setup() {
  Serial.begin(9600);     // 啟用串列埠監看視窗
  pinMode(pb, INPUT);     // 宣告pb為輸入腳,並使用外部拉上電阻
}

void loop() {
  unsigned long currentTime = millis();  // 當前的millis()時間
  bool buttonState = digitalRead(pb);    // 當前的按鈕開關狀態:未按下(HIGH),按下(LOW)

  // 若按鈕開關狀態發生變化,且超過防彈跳預設的延遲時間
  if ((buttonState != lastButtonState) && (currentTime - lastDebounceTime) > debounceDelay)
  {
    // --------------------------------------------------------------------------------------
    if (lastButtonState == HIGH && buttonState == LOW)        // 若按鈕從未按下(HIGH)到按下(LOW)
    {
      // 按鈕按下時,想做的事
      Serial.println("Button pressed.");
    }
    else if (lastButtonState == LOW && buttonState == HIGH)   // 若按鈕從按下(LOW)到未按下(HIGH)
    {      
      // 按鈕放開時,想做的事
      Serial.println("Button released.");
    }
    lastButtonState = buttonState;    // 更新按鈕狀態
    // --------------------------------------------------------------------------------------
    lastDebounceTime = currentTime;   // 更新前一次狀態改變時的millis()時間
  }
}

若要判斷按鈕開關是否被長按,可參考下列程式:

const int pb = 25;                   // 按鈕開關接腳
bool lastButtonState = HIGH;         // 按鈕開關前一次的狀態
unsigned long lastDebounceTime = 0;  // 按鈕開關前一次狀態改變時的millis()時間
int debounceDelay = 80;              // 防彈跳預設的延遲時間
unsigned long buttonDownTime = 0;    // 按鈕開關被按下的時間
unsigned long longPressTime = 1500;  // 長按時間
int count = 0;                       // 按鈕計數器

void setup() {
  Serial.begin(9600);     // 啟用串列埠監看視窗
  pinMode(pb, INPUT);     // 宣告pb為輸入腳,並使用外部拉上電阻
}

void loop() {
  unsigned long currentTime = millis();  // 當前的millis()時間
  bool buttonState = digitalRead(pb);    // 當前的按鈕開關狀態:未按下(HIGH),按下(LOW)

  // 若按鈕開關狀態發生變化,且超過防彈跳預設的延遲時間
  if ((buttonState != lastButtonState) && (currentTime - lastDebounceTime) > debounceDelay)
  {
    // --------------------------------------------------------------------------------------
    if (lastButtonState == HIGH && buttonState == LOW)        // 若按鈕從未按下(HIGH)到按下(LOW)
    {
      // 按鈕按下時,想做的事
      Serial.println("Button pressed.");
      buttonDownTime = currentTime; //記錄按鈕按下的時間

      count++;    // 計數器加1
      Serial.print("Button pressed count: ");   // 印出計數器內容
      Serial.println(count);                    // 印出計數器內容
    }
    else if (lastButtonState == LOW && buttonState == HIGH)   // 若按鈕從按下(LOW)到未按下(HIGH)
    {      
      // 按鈕放開時,想做的事
      Serial.println("Button released.");
      
      if((currentTime - buttonDownTime) >= longPressTime)
      {
        Serial.println("Long press detected, count reset.");
        count=0;    // 清空計數器
        Serial.print("Button pressed count: ");   // 印出計數器內容
        Serial.println(count);                    // 印出計數器內容
      }      
    }
    lastButtonState = buttonState;    // 更新按鈕狀態
    // --------------------------------------------------------------------------------------
    lastDebounceTime = currentTime;   // 更新前一次狀態改變時的millis()時間
  }
}

作業練習:

  • 開機後,8個LED燈全亮,1秒後全滅。
  • 每按一下按鈕開關,從自LED0到LED7依序增加點亮1個燈,8個燈全亮後全滅後,循環顯示。
  • 任何狀態下,只要長按按鈕開關1.5秒以上,8個燈全滅。
  • 各種燈亮的狀態必須顯示在監看視窗上。
  • 在Wokwi網站上進行模擬,並完成實體接線。