2012/03/25

Arduino一個好用的計時器程式庫

這篇要介紹一個好用的Arduino計時器(Timer)程式庫,由Dr. Monk所開發,由Jack Christensen稍加修改後放在github上

下載並解壓縮後,把目錄改名成Timer,搬移到Arduino軟體開發環境的sketchbook目錄的libraries子目錄下,你可以從File-Preferences-Sketchbook location:查到sketchbook目錄在哪,若裡面沒有libraries這個子目錄,請自行建立,然後把計時器程式庫整個Timer目錄搬進去。

需要重新開啟Arduino軟體開發環境,它才知道你新安裝的程式庫。

先舉個例子,假設要閃爍一個LED,點亮1秒,熄滅1秒,不斷循環,應該很簡單(這通常會是學習Arduino的第一支程式),程式碼如下:

假設Arduino腳位2接到LED的長腳(中間串接220 ohm電阻),短腳接地。
void setup() {               
  pinMode(2, OUTPUT);    
}

void loop() {
  digitalWrite(2, HIGH);
  delay(1000);
  digitalWrite(2, LOW);
  delay(1000);
}

好,那接下來,如果腳位3、4、5也都接了LED,我們想要讓每個LED以不同頻率閃爍,那該怎麼寫呢。

unsigned long previous_time;

void setup() {               
  pinMode(2, OUTPUT);
  pinMode(3, OUTPUT);
  pinMode(4, OUTPUT);
  pinMode(5, OUTPUT);
 
  digitalWrite(2, HIGH);
  digitalWrite(3, HIGH);
  digitalWrite(4, HIGH);
  digitalWrite(5, HIGH);
 
  previous_time = millis();
}

void update2(unsigned long time_passed){
  static unsigned long time = 0;
  time += time_passed;
  if((time / 200) % 2 == 0){ // 腳位2的LED明滅頻率為0.2秒
    digitalWrite(2, HIGH);
  }
  else{
    digitalWrite(2, LOW);
  }
}

void update3(unsigned long time_passed){
  static unsigned long time = 0;
  time += time_passed;
  if((time / 300) % 2 == 0){ // 腳位3的LED明滅頻率為0.3秒
    digitalWrite(3, HIGH);
  }
  else{
    digitalWrite(3, LOW);
  }
}

void update4(unsigned long time_passed){
  static unsigned long time = 0;
  time += time_passed;
  if((time / 400) % 2 == 0){ // 腳位4的LED明滅頻率為0.4秒
    digitalWrite(4, HIGH);
  }
  else{
    digitalWrite(4, LOW);
  }
}

void update5(unsigned long time_passed){
  static unsigned long time = 0;
  time += time_passed;
  if((time / 500) % 2 == 0){ // 腳位5的LED明滅頻率為0.5秒
    digitalWrite(5, HIGH);
  }
  else{
    digitalWrite(5, LOW);
  }
}

void loop() {
  unsigned long current_time = millis();
  unsigned long time_passed;

  if(current_time > previous_time ){
    time_passed = current_time - previous_time;
  }
  else{
    time_passed = ULONG_MAX - previous_time + current_time;
  }

  if(time_passed >= 100){
    update2(time_passed);
    update3(time_passed);
    update4(time_passed);
    update5(time_passed);
    previous_time = current_time;
  }
}

每次在loop裡,檢查距離上次更新是否經過了100ms(0.1秒),若是,呼叫update2、update3、update4、update5四個函式,分別負責更新腳位2、3、4、5的狀態。

你可以看得出來,有越多東西要處理,程式就會變得越複雜。若是有Timer計時器程式庫的話,就會變得簡單許多。

改用計時器後的程式碼如下:
// 首先匯入程式庫標頭檔Timer.h
#include <Timer.h>

// 用四個計時器控制四個LED
Timer t2;
Timer t3;
Timer t4;
Timer t5;

void setup() {
  pinMode(2, OUTPUT); // 腳位設定為輸出模式
  pinMode(3, OUTPUT);
  pinMode(4, OUTPUT);
  pinMode(5, OUTPUT);
 
  t2.oscillate(2, 200, HIGH); // 以oscillate設定每幾毫秒切換一次狀態
  t3.oscillate(3, 300, HIGH); // 第一個參數:哪個腳位
  t4.oscillate(4, 400, HIGH); // 第二個參數:幾毫秒
  t5.oscillate(5, 500, HIGH); // 第三個參數:初始狀態
}

void loop() {
  // 在loop裡,呼叫每個計時器的update,它才能運作更新狀態
  t2.update();
  t3.update();
  t4.update();
  t5.update();
}

如何,是不是簡單許多、合理許多了呢。

還有個every方法,每經過一段時間,就呼叫某回呼函式。範例:
#include <Timer.h>

Timer tcb;

void writeToSerial(){
  static  unsigned long count = 0;
  Serial.println(count); // 從0開始輸出,每次加1
  count++;
}

void setup() {
  Serial.begin(115200);
  tcb.every(1000, writeToSerial); // 每經過1000毫秒,就會呼叫writeToSerial
}

void loop() {
  tcb.update();
}

其他功能還有after(經過一段時間後,呼叫某函式,只呼叫一次)、pulse(經過一段時間後,切換某腳位的狀態,只一次)、stop(取消某個計時器事件)、等等,可看看原文章的最下面,有API解說。

PS 另外有個計時器程式庫叫Metro,不過我覺得比較不好用。

124 comments:

  1. 我使用你的every來跑count,然後我顯示在LCD上的圖片就會突然的消失
    然後砍掉那些Timer的程式碼,就又出現了==

    ReplyDelete
    Replies
    1. 呃,哪裡出錯了嗎?

      Delete
    2. This comment has been removed by the author.

      Delete
  2. 如果使用every那要怎停止它,我寫tcb.stop就失敗==“

    ReplyDelete
    Replies
    1. 呼叫every時會回傳計時器事件ID,之後要停止時須傳入stop。

      Delete
  3. 我不曉得要怎麼stop....

    ReplyDelete
    Replies
    1. 呼叫every時會回傳計時器事件ID,必須記住,
      之後想要停止時,呼叫stop並傳入該ID。

      Delete
    2. 還有人回嗎?請教.stop的ID要記住??這句話我實在不懂

      Delete
    3. 一個Timer可以控制好幾個事件,
      每次呼叫every安排新的事件,every會回傳該事件的ID,
      當你要stop時,須傳入ID,這樣才知道你想stop哪個事件。

      Delete
    4. #include "Timer.h"
      Timer t;
      int ledEvent;
      void setup(){
      Serial.begin(9600);
      pinMode(13, OUTPUT);
      ledEvent = t.oscillate(13, 50, HIGH);
      }
      void loop(){
      if(Serial.available()>0){
      char c = Serial.read();
      if(c == '1'){
      t.stop(ledEvent);
      }

      t.update();
      }

      Delete
  4. 你好~我想寫一個程式類似time的功能但是我的想法是
    程式開使跑的時候開始計時 一直到程式結束(或者程式中間)時停止
    我想知道總共多少時間 想要顯示出來

    ReplyDelete
    Replies
    1. 用millis()不就可以得知微控制器重置後經過多少時間嗎?
      然後再呼叫一次,兩者相減,便可得知相差多少時間。

      Delete
  5. 是大概像下面那樣寫嗎?

    #include "Timer.h"
    int t = 0;
    int t1 = 0;
    int t2 = 0;

    void setup()
    {

    t = millis() ;
    .
    .
    .
    .
    t1 = millis() ;

    t2 = t1 - t ;
    Serial.print("時間:");
    Serial.println(t2, HEX);
    }

    ReplyDelete
    Replies
    1. millis()回傳的是unsigned long,

      millis()是內建函式,不是Timer程式庫裡的東西。

      Delete
  6. 請問使用
    裡面的Timer用完了,可以呼叫出另外一個Timer2嗎??

    ReplyDelete
    Replies
    1. 在請問一下Arduino TFT板上面的LCD CS, SD CS, RESET的線可以設定到其他pin腳位上嗎??

      Delete
    2. 可以建立多個Timer物件,
      Timer t1;
      Timer t2;
      等等。

      某個Timer物件若想更動設定,可先呼叫stop,但須傳入當初取得的id;然後再呼叫every或oscillate或其他設定方法。

      Delete
    3. 我沒有Arduino TFT板。

      看http://arduino.cc/en/Guide/TFT的介紹,應該可以把你說的腳位連接到其他pin,同時也須修改程式碼裡的腳位定義。

      Delete
  7. Anonymous4/12/14 21:01

    葉大 查了很多網站在LOOP裡
    點亮10秒熄滅,不循環
    都查不到怎麼寫
    有沒有甚麼方法可以用?

    ReplyDelete
    Replies
    1. 使用Timer程式庫的
      int every(long period, callback, int repeatCount)
      period傳入10000,
      callback回呼函式裡把LED熄滅,
      repeatCount傳入1,
      應該就可以達到你的要求。

      Delete
  8. 請問我想要一段時間後讓arduino重置(reset pin 接 12 pin)
    用 every/after/pulse 哪個比較好?

    ReplyDelete
  9. 你好, 我目前製作一個簡單的電路,用作讀取三軸加速規的訊號。目前我使用arduino uno 做三軸加速規的類比數位轉換。在類比轉換之前我加入了三組自行設計的5階濾波器在加速規的輸出端,在訊號做類比轉換前能濾掉高頻雜訊。現在我希望能加入一個4對1的多工器,在加速規及濾波器之間做切換,如此一來只需要一組濾波器。在多工器的控制方面我是使用aradino的PORT B 最後兩個腳位做00,01,10的變化來做時序控制,日前參考你這篇的counter來撰寫多工器的時序控制,同時做AD轉換,但是我碰到了一些問題,還望你能指教。

    為了不在類比轉換時做多工器的切換,我是使用every來撰寫每隔100ms,時序旗標會變成1,PORTB會加1,進而可以切換多工器,但我發現我的程式在進入loop後,雖然有寫t.update();
    但是不會進入我所寫的每隔100ms所要呼叫的程式,導致無法做多工器的切換。
    不知哪裡修改後就能work呢? 還望指點, 謝謝。

    詳細程式碼如下:

    #include "Timer.h"

    Timer t;

    int acc = A1;
    int timeflag;
    int count;


    void mulswitch()
    {
    int timeflag = 1;
    count++;
    }

    void setup()
    {
    Serial.begin(9600);
    PORTB = 000000;
    timeflag = 0;
    t.every(100,mulswitch);
    }

    void loop()
    {
    t.update();
    while (timeflag != 1)
    {
    if (PORTB == 000000)
    {
    Serial.println(analogRead(acc));
    Serial.print("AccX\t");

    }
    else if (PORTB == 000001)
    {
    Serial.print(analogRead(acc));
    Serial.print("AccY\t");
    }
    else if (PORTB ==000010)
    {
    Serial.print(analogRead(acc));
    Serial.print("AccZ\n");
    }

    }
    while (timeflag == 1)
    {
    PORTB++;
    timeflag = timeflag - 1;
    while (PORTB == 2)
    {
    PORTB = PORTB - 2;
    }
    }


    }



    ReplyDelete
    Replies
    1. 在setup裡timeflag = 0;

      然後在loop裡
      while (timeflag != 1)
      {
      // ...
      }

      timeflag != 1永遠為真,所以while迴圈不斷執行,這是無窮迴圈。


      Delete
    2. 在setup裡面寫timeflag = 0; 不是表示初始值為0嗎?

      還是我應該在宣告的地方寫 int timeflag = 0呢?

      Delete
    3. 問題在於造成無窮迴圈。
      大致上,應要把while改成if,至於細節要自己推敲。

      Delete
  10. 您好
    如果我要讓arduino每10分鐘執行某個動作
    但是又不影響其他程式碼的運行
    請問是否能用此程式庫
    謝謝您

    ReplyDelete
    Replies
    1. 可以,大致上作法如下:
      把動作獨立出來放在某個函式裡,
      建立Timer物件,呼叫every,參數是10*60(秒)與該函式,
      然後在loop裡上述Timer物件的update。

      Delete
    2. 我成功囉!!!
      謝謝您

      Delete
    3. 請問一下我如果要讓伺服馬達從0轉180度在從180度轉為0~~這一個動作每10分鐘執行一次~~該怎麼用
      #include

      Servo myservo;

      void setup()
      {
      myservo.attach(9);
      }

      void loop()
      {
      for(int i = 0; i <= 180; i+=1){
      myservo.write(i); //
      delay(20);
      }
      for(int i = 180; i >= 0; i-=1){
      myservo.write(i);
      delay(20);
      }
      }

      Delete
    4. 在loop最後面加上delay(10 * 60 * 1000);

      Delete
    5. Servo myservo;

      void setup()
      {
      myservo.attach(9);
      }

      void loop()
      {
      for(int i = 0; i <= 180; i+=1){
      myservo.write(i); //
      delay(20);
      }
      for(int i = 180; i >= 0; i-=1){
      myservo.write(i);
      delay(20);
      }
      delay(10 * 60 * 1000);
      }

      請問是這樣嗎??

      Delete
    6. 呃,試試看不就知道了。

      Delete
    7. 有試了~~但是還是一樣呢

      Delete
    8. 啊,抱歉,忘了int只有2 bytes。
      10 * 60 * 1000會變成10秒左右,
      請改成10 * 60 * 1000L

      Delete
  11. 請問 如果要讓arduino動作10分鐘然後停止 停止之後再用一個觸發鍵來重新啟動
    要怎麼用timer做
    目前我用arduino睡眠模式做
    但他只能控制一個時間
    不能控制多個時間

    ReplyDelete
    Replies
    1. > 停止之後再用一個觸發鍵來重新啟動
      這個就用睡眠模式來做。
      > 讓arduino動作10分鐘然後停止
      可以自己用millis()計算時間,或是用軟體計時器程式庫來做,譬如https://github.com/JChristensen/Timer

      Delete
  12. 假如我想讓我的analog read每10ms抓一次data到serial print,還有我的電壓峰對峰值每一秒抓一次data
    我跑了一次發現一秒鐘之內是會在serial monitor上顯示10ms 的data, 可是過一秒鐘之後就無法回到我的analog voltage
    就只能變成一秒鐘顯示一次 不知道是哪出了問題 謝謝
    #include

    Timer tcb;
    Timer tcb2;
    void analog(){
    // read the input on analog pin 0:
    int sensorValue = analogRead(A0);
    // Convert the analog reading (which goes from 0 - 1023) to a voltage (0 - 5V):
    float voltage = sensorValue * (5.0 / 1023.0);
    // print out the value you read:
    Serial.println(voltage);
    }

    void vpp() {
    const int sampleWindow = 1000; // Sample window width in mS
    unsigned int sample;
    const int sensorIn = A0;

    unsigned long startMillis= millis(); // Start of sample window
    unsigned int peakToPeak = 0; // peak-to-peak level

    unsigned int signalMax = 0;
    unsigned int signalMin = 1024;

    // collect data for 1S
    while (millis() - startMillis < sampleWindow)
    {
    sample = analogRead(sensorIn);
    if (sample < 1024) // toss out spurious readings
    {
    if (sample > signalMax)
    {
    signalMax = sample; // save just the max levels
    }
    else if (sample < signalMin)
    {
    signalMin = sample; // save just the min levels
    }
    }
    }
    peakToPeak = signalMax - signalMin; // max - min = peak-peak amplitude
    double volts = (peakToPeak * 5.0) / 1024.0; // convert to volts

    Serial.println(volts);
    }
    // the setup routine runs once when you press reset:
    void setup() {
    // initialize serial communication at 9600 bits per second:
    Serial.begin(9600);
    tcb.every(1000,vpp);
    tcb2.every(10,analog);
    }

    // the loop routine runs over and over again forever:
    void loop() {
    tcb.update();
    tcb2.update();

    }

    ReplyDelete
    Replies
    1. 因為你在vpp()裡使用while、millis、sampleWindow,又拖了一秒。因此影響analog()。

      建議改法:
      analog()裡讀取A0,並且記錄signalMax與signalMin。
      vpp()裡就只要算出peakToPeak與volts即可,並且把signalMax與signalMin重置為某特定值。

      Delete
    2. 不好意思 把signalMax與signalMin重置為某特定值是甚麼意思 謝謝

      Delete
    3. 能夠再被
      if (sample > signalMax)
      {
      signalMax = sample; // save just the max levels
      }
      else if (sample < signalMin)
      {
      signalMin = sample; // save just the min levels
      }
      處理的值。

      譬如把signalMax重置為-9999,把signalMin重置為9999。

      Delete
  13. 不好意思,我現在正在製作偵測紫外線相關的裝置,遇到困難,想請問如果我想累積劑量(將a0轉換後對照出之級數乘上照射時間),應該如何攥寫我的程式碼 ?(比如說現在6級的時候照了15分鐘,8級照了10分鐘,我應該如何將我的時間在跳級的時候及時重新計算?該如何使用timer?)謝謝你😬

    ReplyDelete
    Replies
    1. 嗯,聽起來不需要這一篇介紹的timer吧。

      就不斷地偵測紫外線劑量a0,得知是幾級,同時也會看時間,
      「一旦跳級時」,就記錄劑量與時間,然後繼續偵測接下來的劑量與時間。

      Delete
  14. This comment has been removed by the author.

    ReplyDelete
  15. 謝謝你��
    但是我現在已經可以偵測到級數了跳級也可以馬上偵測到 但是不知道如何加入乘上時間累積劑量這個功能(因為想在到達一定的劑量時發出警報)

    ReplyDelete
    Replies
    1. 這是我一部分的程式碼
      忘了說我來有用lcd(16x2)來顯示
      數據那些都孩是我亂設定的(但是每一級都是對應的A0輸出都有一個特定範圍)
      現在就是不知道應該如何加入累積劑量 時間還有緊報 這塊
      救救我吧~~~plz~~~~乾蝦哩~~~~

      #include
      LiquidCrystal lcd(7,6,5,4,3,2);
      int sensorPin = A0;
      int sensorValue = 0;


      void setup()
      {
      Serial.begin(9600);



      lcd.begin(16, 2);
      lcd.setCursor(5,0);
      lcd.print("HELLO");
      lcd.setCursor(4,1);
      lcd.print("WELCOME");
      delay(1000);
      }
      void loop()
      {


      sensorValue = analogRead(sensorPin);

      lcd.clear();
      lcd.setCursor(0, 0);
      lcd.print("NO SUNSCREAM");
      lcd.setCursor(0,1);
      lcd.print("A0:");
      lcd.setCursor(3,1);
      lcd.print(sensorValue);
      lcd.setCursor(8,1);
      lcd.print("Lvl:");

      if(sensorValue<=93){
      lcd.setCursor(13,1);
      lcd.print("1");
      }
      else if(sensorValue<=186){
      lcd.setCursor(13,1);
      lcd.print("2");

      }
      else if(sensorValue<=297){
      lcd.setCursor(13,1);
      lcd.print("3");
      }
      else if(sensorValue<=372){
      lcd.setCursor(13,1);
      lcd.print("4");
      }
      else if(sensorValue<=465){
      lcd.setCursor(13,1);
      lcd.print("5");

      }
      else if(sensorValue<=558){
      lcd.setCursor(13,1);
      lcd.print("6");
      }
      else if(sensorValue<=651){
      lcd.setCursor(13,1);
      lcd.print("7");

      }
      else if(sensorValue<=744){
      lcd.setCursor(13,1);
      lcd.print("8");

      }
      else if(sensorValue<=837){
      lcd.setCursor(13,1);
      lcd.print("9");

      }
      else if(sensorValue<=930){

      lcd.setCursor(13,1);
      lcd.print("10");

      }
      else if(sensorValue<=1023){
      lcd.setCursor(13,1);
      lcd.print("11");

      }

      delay(1000);
      }

      Delete
    2. 呼叫millis(),得知時間。宣告全域變數,記錄之前的強度與照射時間。

      累積劑量不知道怎麼算。

      大概是這樣吧:
      unsigned long v_old; 代表上次偵測到的強度
      unsigned long t_old; 代表偵測到上次強度的最初時間
      unsigned total; 累積劑量

      void setup(){
      total = 0;
      v_old = 1; 假設上次強度是1
      t_old = millis();
      }

      void loop(){
      偵測強度
      若強度與上次強度相同,不做事。
      若不同,就更新累積劑量
      total = v_old * ((millis() - t_old)/1000); 假定累積劑量是強度乘上時間
      更新強度 v_old = 新強度
      更新偵測到新強度的起始時間 t_old = millis()
      }

      大概是這樣吧,沒仔細看過也沒側試過。請斟酌採用。

      Delete
    3. 謝謝你~但是我還是寫不出來嗚嗚

      請問這應該要怎麼寫

      若強度與上次強度相同,不做事。
      若不同,就更新累積劑量

      假定累積劑量是強度乘上時間(是這樣算沒有錯)
      但是現在的時間應該怎摩表示?

      Delete
    4. > 請問這應該要怎麼寫
      我已經回答了。

      > 但是現在的時間應該怎摩表示?
      一般Arduino板子並無即時時鐘,沒辦法得知絕對時間,
      但內部有個計時器,每當Reset時,就會從0開始起跳,
      呼叫millis()的話,可得到該計時器的值(單位是千分之一秒),
      因此兩次millis()呼叫的值,相減後,便可得到時間差。

      Delete
  16. 我現在在做8顆燈泡
    每一顆燈泡都是點亮10秒然後關閉2秒
    然後每顆燈泡都是和前面那一顆點亮時間相差1秒
    請問這個要怎麼做
    我都一直不成功

    ReplyDelete
    Replies
    1. 使用這篇介紹的計時器程式庫,應該很容易吧。

      你說的是需求,不是問題。
      請問有什麼問題。

      Delete
    2. 我不知道怎麼寫
      我寫的他都沒有辦法執行

      Delete
    3. 大概是這樣吧
      http://pastebin.com/6ErnsCC8
      不過裡頭只控制3顆LED。

      其中的Flasher類別,修改自
      https://learn.adafruit.com/multi-tasking-the-arduino-part-1/a-classy-solution

      Delete
  17. 如果現在以壓力感測器測出的數值是A,過了五分鐘後的數值是B,然後如果數值沒有變,以LED亮紅燈,如果A<B,亮綠燈。程式要怎麼打呢? 我主要是想知道那個過了五分鐘後的時間程式怎麼寫?

    ReplyDelete
    Replies
    1. 你需要記住A,以及測得A的時間(譬如呼叫millis()),
      然後不斷呼叫millis(),當新時間與測得A的時間相減後,超過5分鐘,
      就去測量得到B。然後比較A與B,若<,就亮紅燈。

      Delete
    2. 我有點不太懂欸 可不可以打一小段millis()的程式給我看看

      Delete
    3. 請參考
      Arduino練習:以開關切換LED是否閃爍
      http://yehnan.blogspot.tw/2014/03/arduinoled.html

      Delete
  18. 我有壓力計測出假如50kpa,今天我開始洩壓,洩至30kpa,我想要計時50至30之間的時間,這程式怎麼打?

    ReplyDelete
    Replies
    1. 記住兩次測量的時間,然後相減。

      Delete
    2. 我回去試試,謝謝你

      Delete
  19. 葉大,
    我想請問一下
    我將Timer.cpp, Timer.h, Event.cpp, Event.h都複製到專案底下了
    Compile也沒問題
    但是到了Linker那邊卻出現多重定義的錯誤
    i.e.
    sketch\lib/Event.cpp:34: multiple definition of `Event::Event()'

    sketch\Event.cpp.o:sketch/Event.cpp:34: first defined here


    sketch\lib\Event.cpp.o: In function `Event::Event()':

    sketch\lib/Event.cpp:34: multiple definition of `Event::Event()'

    ... 之類的

    不知道問題出在哪
    謝謝!!

    ReplyDelete
    Replies
    1. 你的主程式檔.ino大概有#include 吧,
      改成"Timer.h"應該就好了。

      Delete
    2. 自問自答一下好了
      在IDE當中
      把那四有被自動加入的.h與.cpp在程式裡刪除後
      重新再把檔案加入到專案裡就ok了
      想不懂為什麼@@

      Delete
    3. 錯誤訊息是multiple definition,代表重複定義,應該是專案納入兩支相同的檔案。

      > 將Timer.cpp, Timer.h, Event.cpp, Event.h都複製到專案底下了
      為何如此?那是程式庫啊,應該放在程式庫目錄裡吧。

      > 重新再把檔案加入到專案裡就ok了
      不懂。

      Delete
  20. This comment has been removed by the author.

    ReplyDelete
  21. 請問我要做一個鬧鐘並在七段顯示器上呈現,當受定的時間到了就響鈴,然後按了一個按鈕讓響鈴停止並繼續時鐘模式,請問該怎麼做?

    ReplyDelete
    Replies
    1. 拙作<>
      http://yehnan.blogspot.tw/2014/02/arduino_21.html

      第5.4節,介紹四合一型的七段顯示器,以及即時時鐘(RTC)晶片,製作出一般的時鐘。但沒有鬧鐘功能。

      鬧鐘功能需使用EEPROM來記錄使用者設定的響鈴時間。

      至於你問該怎麼做,就做啊,呵呵。請不要問這種可以寫成一篇文章的問題。

      Delete
    2. 那你可以告訴我停止時間的程式該怎麼寫嗎?

      Delete
    3. 停止響鈴?
      不就是停止輸出訊號給響鈴嗎?

      Delete
    4. 可是我不是這樣寫的

      if (mmm<0,sss<1) {
      tone(Buzzer,440,1000);


      }

      要在響完之後加甚麼

      Delete
    5. if (mmm<0,sss<1) 這樣寫不對吧,應該用&&或||之類的。

      tone(Buzzer,440,1000);
      不是有第三個參數1000嗎?應該會在1秒後停止。

      Delete
  22. 是的,可是他變每隔一分鐘又在響一秒持續下去

    該怎麼讓它也停止時間呢?

    ReplyDelete
    Replies
    1. if (mmm<0,sss<1) 的部份沒寫好。

      Delete
  23. if(mmm<0,sss<1){
    tone(Buzzer,440,1000);}
    之後要加甚麼才能讓他停止繼續數

    ReplyDelete
    Replies
    1. 之前已經說了,if(mmm<0,sss<1)沒寫好。

      程式碼?

      Delete
  24. if(mmm<0&&sss<1){
    tone(Buzzer,440,1000);}
    改了他到00:00的時候也不會叫了

    ReplyDelete
  25. 什麼?

    給我全部的程式碼。

    ReplyDelete
  26. #include
    #include
    #define Buzzer 6
    #define DSP 1
    // 鍵盤相關變數宣告
    const byte ROWS = 3; // 鍵盤列數
    const byte COLS = 3; // 鍵盤行數
    char keys[ROWS][COLS] = { // 鍵盤對應按鍵值
    {'0','1','2'},
    {'3','4','5'},
    {'6','7','8'} };
    // 鍵盤的列與行接腳配置
    byte rowPins[ROWS] = {A2,A1,A0};
    byte colPins[COLS] = {A5,A4,A3};
    Keypad keypad = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS );
    const unsigned char Pin_seg7[] = {13,12,11,10,9,8,7}; // 七段資料 abcdefg
    const unsigned char Pin_scan[]={5,4,3,2}; // 七段掃描 千百十個
    const unsigned char TAB[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x27,0x7f,0x67,0x00};
    char dig[]={1,2,3,4}; // 七段顯示的數値()
    char i,key,hh=10,mm=20,ss=10,mod=0;
    char mmm=0,sss=11;
    char mod1;
    char m=12,x=33,z=30;
    char mod2;
    char mod3;
    unsigned char ii=0,jj,s;
    void ShowData(unsigned char dat);
    // =============================================================
    void setup() {
    // 初始化:掃描式七段顯示器
    for(i=0;i<7;i++) pinMode(Pin_seg7[i],OUTPUT);
    for(i=0;i<4;i++) {pinMode(Pin_scan[i],OUTPUT);digitalWrite(Pin_scan[i],LOW);}
    // 初始化:矩陣式鍵盤初始化
    keypad.addEventListener(keypadEvent); // Add an event listener for this keypad
    pinMode(Buzzer, OUTPUT); pinMode(DSP, OUTPUT);
    digitalWrite(DSP,LOW);
    Timer1.initialize(1000000);
    Timer1.attachInterrupt( timerIsr ); // attach the service routine here
    }
    // =============================================================
    void loop() {
    ShowData(TAB[dig[ii]]);
    // 指定掃描位置
    digitalWrite(Pin_scan[ii],HIGH); delay(5); digitalWrite(Pin_scan[ii],LOW);
    if(++ii>=4) ii=0;

    key = keypad.getKey();

    if (key) {tone(Buzzer,440,100);
    switch(key) {
    case '0': if(++mod>=2) mod=0; break;
    case'1':if(++mod1>=2) mod1=0 ;break;
    case'3':
    hh++;if(hh>24) hh=0;break;
    case'4':
    mm++;if(mm>=60) mm=0;break;
    case'6':
    mmm++;if(mmm>=60) mmm=0;break;
    case'7':
    sss++;if(sss>=60) sss=0;
    }

    }

    }

    // =============================================================
    void ShowData(unsigned char dat)
    {
    s=0x01;
    for(jj=0;jj<7;jj++) {
    if(dat & s) digitalWrite(Pin_seg7[jj],HIGH);
    else digitalWrite(Pin_seg7[jj],LOW);
    s<<=1;
    }

    }
    // =============================================================
    void keypadEvent(KeypadEvent key){
    switch (keypad.getState()){
    case PRESSED:
    if (key == '1') {

    }
    break;
    case RELEASED:
    if (key == '2') {

    }
    break;
    case HOLD:
    if (key == '3') {

    }
    break;
    }
    }
    // =============================================================
    void timerIsr()
    { digitalWrite( DSP, digitalRead( DSP ) ^ 1 );

    {if(++ss>=60) {ss=0;
    if(++mm>=60) {mm=0;
    if(++hh>=24) hh=0;
    }
    }
    if (hh<11,mm<1) {
    tone(Buzzer,440,1000);

    }
    switch(mod) {

    case 0:
    dig[0]=mm/10;dig[1]=mm%10;dig[2]=ss/10;dig[3]=ss%10;
    break;
    case 1:
    dig[0]=hh/10;dig[1]=hh%10;dig[2]=mm/10;dig[3]=mm%10;
    break;}}


    {if(--sss<0) {sss=59;
    if(--mmm<0) mmm=59;
    }
    if (mmm<1,sss<1) {
    tone(Buzzer,440,1000);

    }
    switch(mod1){
    case 0:
    dig[0]=mmm/10;dig[1]=mmm%10;dig[2]=sss/10;dig[3]=sss%10;
    break;}
    }
    }

    ReplyDelete
  27. 我要問要怎麼設定倒數時間跟鬧鐘時間
    以及時間到了它不再繼續數下去

    ReplyDelete
  28. #include
    #include
    #define Buzzer 6
    #define DSP 1
    // 鍵盤相關變數宣告
    const byte ROWS = 3; // 鍵盤列數
    const byte COLS = 3; // 鍵盤行數
    char keys[ROWS][COLS] = { // 鍵盤對應按鍵值
    {'0','1','2'},
    {'3','4','5'},
    {'6','7','8'} };
    // 鍵盤的列與行接腳配置
    byte rowPins[ROWS] = {A2,A1,A0};
    byte colPins[COLS] = {A5,A4,A3};
    Keypad keypad = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS );
    const unsigned char Pin_seg7[] = {13,12,11,10,9,8,7}; // 七段資料 abcdefg
    const unsigned char Pin_scan[]={5,4,3,2}; // 七段掃描 千百十個
    const unsigned char TAB[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x27,0x7f,0x67,0x00};
    char dig[]={1,2,3,4}; // 七段顯示的數値()
    char i,key,hh=10,mm=20,ss=10,mod=0;
    char mmm=0,sss=11;
    char mod1;
    char m=12,x=33,z=30;
    char mod2;
    char mod3;
    unsigned char ii=0,jj,s;
    void ShowData(unsigned char dat);
    // =============================================================
    void setup() {
    // 初始化:掃描式七段顯示器
    for(i=0;i<7;i++) pinMode(Pin_seg7[i],OUTPUT);
    for(i=0;i<4;i++) {pinMode(Pin_scan[i],OUTPUT);digitalWrite(Pin_scan[i],LOW);}
    // 初始化:矩陣式鍵盤初始化
    keypad.addEventListener(keypadEvent); // Add an event listener for this keypad
    pinMode(Buzzer, OUTPUT); pinMode(DSP, OUTPUT);
    digitalWrite(DSP,LOW);
    Timer1.initialize(1000000);
    Timer1.attachInterrupt( timerIsr ); // attach the service routine here
    }
    // =============================================================
    void loop() {
    ShowData(TAB[dig[ii]]);
    // 指定掃描位置
    digitalWrite(Pin_scan[ii],HIGH); delay(5); digitalWrite(Pin_scan[ii],LOW);
    if(++ii>=4) ii=0;

    key = keypad.getKey();

    if (key) {tone(Buzzer,440,100);
    switch(key) {
    case '0': if(++mod>=2) mod=0; break;
    case'1':if(++mod1>=2) mod1=0 ;break;
    case'3':
    hh++;if(hh>24) hh=0;break;
    case'4':
    mm++;if(mm>=60) mm=0;break;
    case'6':
    mmm++;if(mmm>=60) mmm=0;break;
    case'7':
    sss++;if(sss>=60) sss=0;
    }

    }

    }

    // =============================================================
    void ShowData(unsigned char dat)
    {
    s=0x01;
    for(jj=0;jj<7;jj++) {
    if(dat & s) digitalWrite(Pin_seg7[jj],HIGH);
    else digitalWrite(Pin_seg7[jj],LOW);
    s<<=1;
    }

    }
    // =============================================================
    void keypadEvent(KeypadEvent key){
    switch (keypad.getState()){
    case PRESSED:
    if (key == '1') {

    }
    break;
    case RELEASED:
    if (key == '2') {

    }
    break;
    case HOLD:
    if (key == '3') {

    }
    break;
    }
    }
    // =============================================================
    void timerIsr()
    { digitalWrite( DSP, digitalRead( DSP ) ^ 1 );

    {if(++ss>=60) {ss=0;
    if(++mm>=60) {mm=0;
    if(++hh>=24) hh=0;
    }
    }
    if (hh<11,mm<1) {
    tone(Buzzer,440,1000);

    }
    switch(mod) {

    case 0:
    dig[0]=mm/10;dig[1]=mm%10;dig[2]=ss/10;dig[3]=ss%10;
    break;
    case 1:
    dig[0]=hh/10;dig[1]=hh%10;dig[2]=mm/10;dig[3]=mm%10;
    break;}}


    {if(--sss<0) {sss=59;
    if(--mmm<0) mmm=59;
    }
    if (mmm<1,sss<1) {
    tone(Buzzer,440,1000);

    }
    switch(mod1){
    case 0:
    dig[0]=mmm/10;dig[1]=mmm%10;dig[2]=sss/10;dig[3]=sss%10;
    break;}
    }
    }

    ReplyDelete
    Replies
    1. mod1若為0應該是代表倒數模式吧,而mmm和sss代表要倒數的分鐘與秒數。
      如果我說的沒錯,那麼應該把
      if(--sss<0){
      sss=59;
      if(--mmm<0)
      mmm=59;
      }
      if(mmm<1,sss<1){
      tone(Buzzer,440,1000);
      }
      搬進switch(mod1)的case 0裡。

      而且,if(mmm<1,sss<1)應該改成if(mmm<1 && sss<1),也就是當倒數到0分0秒時,發出叫聲。
      然後應該把mod1設為1,解除倒數模式。

      Delete
  29. 對你說的沒錯,mmm和sss是倒數的分秒

    ReplyDelete
  30. mod1設為1該寫在哪?

    ReplyDelete
  31. 我現在還有一個問題,一開始燒錄進去就開始倒數,請問要一開始是時鐘,按下一個按鈕才變倒數該怎麼做?

    ReplyDelete
  32. > mod1設為1該寫在哪?
    需要解除倒數模式的地方。

    > 要一開始是時鐘,按下一個按鈕才變倒數該怎麼做?
    改程式。

    你說的是需求,不是問題。

    程式裡有switch(mod)和switch(mod1)的部份,混雜了一般時鐘模式和倒數模式,應從該處下手開始改。
    要回答的話,非三言兩語辦得到。
    加油。


    ReplyDelete
  33. case '0': if(++mod>=2) mod=0; break;
    case'1':if(++mod1>=2) mod1=0 ;break;
    這段該怎麼改,才不會混淆

    ReplyDelete
  34. int setringcount=0;

    void setRing()

    {

    switch(setringcount)

    {

    case 0:

    h += key*10;

    break;

    case 1:

    h+=key;

    break;

    case 2:

    m+=key*10;

    break;

    case 3:

    m+=key;

    break;

    }




    if(setringcount<3)

    setringcount++;

    else

    {

    setringcount=0;

    setring=false;

    }

    鬧鐘這樣寫有錯嗎?

    ReplyDelete
    Replies
    1. 你這樣問,不知如何回答。

      Delete
  35. 您好!
    我用這個TIMER加上開關控制LED燈在固定的時間內閃爍,時間到自己關掉,程式如下
    #include "Timer.h"
    #include
    Bounce bouncer = Bounce(2, 50);
    static int ledStatus = LOW;

    Timer t;

    int ledEvent;
    int ledEvent2;



    void setup()
    {
    Serial.begin(9600);
    pinMode(2,INPUT);
    pinMode(13, OUTPUT);
    pinMode(3, OUTPUT);
    ledEvent = t.oscillate(13, 20, ledStatus);
    ledEvent2 = t.oscillate(3, 1000, ledStatus);
    int afterEvent = t.after(9000, doAfter);
    }

    void loop()
    {

    if(bouncer.update() == true && bouncer.read() == HIGH) {

    t.update();

    }

    }


    void doAfter() ///after的callback
    {
    Serial.println("stop the led event");
    t.stop(ledEvent2);
    t.stop(ledEvent);
    }
    但是執行後按下開關會造成燈泡恆亮,不會閃爍也不會自行關掉,請問是哪邊出問題了呢
    加了開關的程式才會這樣,只用TIMER可以定時關掉。

    ReplyDelete
    Replies
    1. 嗯,t.update()要不斷呼叫,timer才能運作。
      大概是這樣吧,沒測試過,請試試看。
      http://pastebin.com/4esFRjn6

      #include "Timer.h"
      #include

      Bounce bouncer = Bounce(2, 50);
      static int ledStatus = LOW;
      Timer t;
      int ledEvent;
      int ledEvent2;

      void setup()
      {
      Serial.begin(9600);
      pinMode(2,INPUT);
      pinMode(13, OUTPUT);
      pinMode(3, OUTPUT);
      ledEvent = t.oscillate(13, 20, ledStatus);
      ledEvent2 = t.oscillate(3, 1000, ledStatus);
      }

      void loop()
      {
      t.update();
      if(bouncer.update() == true && bouncer.read() == HIGH) {
      int afterEvent = t.after(9000, doAfter);
      }
      }

      void doAfter() ///after的callback
      {
      Serial.println("stop the led event");
      t.stop(ledEvent2);
      t.stop(ledEvent);
      }



      Delete
    2. 您好,經過改良有達到我的需求囉!!!謝謝您!

      Delete
  36. 老師想請問一下
    如果使用stop後有辦法再重新update嗎?

    ReplyDelete
    Replies
    1. 不行。

      重新呼叫every或oscillate或after或pulse。

      Delete
    2. 我現在用兩塊板子
      我使用every讓第一塊板子丟訊號給第二塊使第二塊板子作動
      但是我希望第二塊作動完後丟訊號回第一塊使第一塊板子停止丟訊號
      但是我兩塊板子是反覆作動的,所以我想說可否使用stop去達成

      Delete
    3. 下達stop,之前的every設定的東西就不見了。

      再重新下達every囉。

      Delete
    4. 不好意思 如果stop的是every那我stop的括號要寫甚麼呢?
      像是這樣printYtimer.stop();
      因為範例是要再()裡放Event
      可是我沒有Event可以放耶

      Delete
    5. 呼叫stop時,要傳入event的id,
      要不然它怎麼知道該停止哪個event。

      呼叫every時,會回傳event的id。

      Delete
  37. 您好,
    請問我想讓伺服馬達每1秒轉10度用timer該怎麼做呢?

    ReplyDelete
    Replies
    1. 嗯,譬如每隔0.1秒下達一次控制指令,每次往前加1度;
      直到(10次)1秒。

      自己控制滿麻煩的吧,找找有無現成的程式庫。

      Delete
    2. 葉老師~
      為什麼我按照您的方法匯入程式庫但是一直失敗,有丟到libraries資料夾了
      但是還是一直編譯錯誤,跟軟體的版本有關係嗎? 謝謝!

      Delete
  38. 錯誤訊息是什麼。

    Timer程式庫有新舊版。

    ReplyDelete
  39. Arduino:1.6.10 (Windows 7), 板子:"Arduino/Genuino Uno"

    In file included from C:\Users\ives\Documents\Arduino\bending\_20160811-timer\_20160811-timer.ino:5:0:

    C:\Users\ives\Documents\Arduino\libraries\ServoTimer1/ServoTimer1.h:25:20: fatal error: wiring.h: No such file or directory

    #include

    ^

    compilation terminated.

    exit status 1
    板子Arduino/Genuino Uno編譯錯誤

    This report would have more information with
    "Show verbose output during compilation"
    option enabled in File -> Preferences.

    ReplyDelete
  40. 再麻煩您幫我看看~謝謝囉

    ReplyDelete
  41. 你用的程式庫ServoTimer1,需要wiring.h,但找不到。

    ReplyDelete
  42. 我想問一下有沒有60秒倒數像投籃機一樣的程式

    ReplyDelete
  43. This comment has been removed by the author.

    ReplyDelete
  44. 我想問一下有沒有60秒倒數像投籃機一樣的程式

    ReplyDelete
    Replies
    1. 寫出來就有了。

      有何問題?

      Delete
  45. 請問如何知道通過用串口發送數據時,兩個數據接收的時間差?

    ReplyDelete
    Replies
    1. 要由接收方告知傳送方吧。

      Delete
  46. 葉難大大,不好意思,請問。
    void setup() {
    Serial.begin(9600);
    }
    void loop(){
    Serial.println("1");
    Serial.println("2");
    delay(1000);
    我能不能夠一開始只print "1"
    之後讓程式重頭開始然後只print"2" 呢?
    有什麼方式或是指令能夠讓loop分開執行嗎?

    ReplyDelete
    Replies
    1. 開機後只需執行一次的,放在setup裡。

      什麼叫做"讓程式重頭開始"?

      Delete
  47. 葉大您好~我想請問如果現在我要將兩組電路訊號輸入到ARDUINO裡面
    想經由ARDUINO判斷兩個訊號到達的峰值的時間差大小有什麼資料可以參考的嗎??
    時間差介於90~130us之間(擷取頻率大約500KHZ),不知ARDUINO是否能夠偵測到這麼短的時間差?

    ReplyDelete
    Replies
    1. 根據analogRead的文件
      https://www.arduino.cc/en/Reference/AnalogRead

      the maximum reading rate is about 10,000 times a second.
      也就是10KHZ,

      沒法滿足你的需求。

      Delete
  48. 請問葉大如果想在程式內加插一些功能:
    原本的程式是升到某個溫度就會開風扇,降到某個溫度就會關閉,
    如果想開了那個風扇後,過了5分鐘,它降不了那個溫度自動就會關閉,

    應該怎樣做??

    ReplyDelete
    Replies
    1. > 自動就會關閉,
      關閉什麼?

      用有限狀態機的程式寫法。

      Delete
  49. 葉大想請教你
    如果我的A程式控制 ,但需要每隔1小時叫出B程式,並由B程式優先控制一段時間,再回A程式控制,這也可以用計時器寫嗎?我有查到有人說可以用watch dog 但不知道怎麼下手

    ReplyDelete
    Replies
    1. void func_a(){}
      void func_b(){}
      int state;
      void loop(){
      if(state){
      func_a();
      }
      else{
      func_b();
      }
      }

      或許可以這麼寫,裡面要加入計時器與程式邏輯,控制state的值,切換要執行的函式。

      Delete
    2. 至於watchdog,那是另一回事。

      Delete