使用TimerOne程式庫改寫Arduino交流電調光器程式

本文旨在修訂《超圖解Arduino互動設計入門》附錄D的交流電調光器程式。最近再次做實驗時,發現書本的diyD_4.ino程式檔,有時候會讓燈泡不停閃爍,我不確定是否是delayMicroseconds()指令的時間誤差所導致(請參閱3-7頁)。後來在arduino.cc官方論壇上,看到AC Light Dimming這篇討論,並採用TimerOne程式庫來改寫交流電調光器程式,問題就解決了。

Arduino的ATmega328微處理器內部具有三個計時器(timer),TimerOne程式庫集合了一組用於設置和運用微處理器Timer1計時器的程式碼,最基本的用法就是讓程式定時去觸發執行某一項工作。

定時點滅LED

本節將以TimerOne程式庫提供的 LED閃爍程式,來說明此程式庫的使用方式。請先下載TimerOne程式庫,將它解壓縮之後,重新命名成“TimerOne”,存入Arduino應用程式路徑裡的”libraries”資料夾裡面。 Mac電腦的使用者,請存放在「文件」裡面的“Arduino/libraries”路徑底下:

安裝TimerOne程式庫

使用TimerOne程式庫所提供的各項指令之前,必須先執行底下的敘述,進行初始化:

其中的「微秒」參數,最大可能值是8388480(約8.3秒),若不設定參數,則採用預設值1000000(1秒)。

初始化之後,即可透過attachInterrupt(直譯為「附加中斷」)指令,設定要定時觸發的自訂函數(或者說「中斷常式」),第二個「微秒」參數是選擇性的,可不填寫。

請選擇 Arduino主功能表的「檔案→範例→TimerOne→ISRBlink」範例程式,其主程式片段如下:

TimerOne範例程式的主程式碼

透過XOR(互斥或)來達成切換開關功能

ISRBlink範例的timerIsr()自訂函數當中,包含一段開、關第13腳LED的敘述,它把目前第13腳的狀態(0或1),和1做XOR運算(指令寫法:^),因此每一次執行這個敘述,第13腳的輸出就會和上一次相反

XOR的邏輯符號及其意義,說明如下:

XOR(互斥或)運算

底下是自訂函數timerIsr()的內容說明:

自訂函數timerIsr()的內容

補充說明,Timer1計時器也負責控制數位9和10腳Arduino Mega板則是11, 12和13腳)的PWM頻率,所以,採用此程式碼時,不要將控制輸出接在這些數位腳。

使用TimerOne改寫交流電調光程式

本單元程式的原理如下(請搭配《超圖解Arduino互動設計入門》附錄D,D-11頁上面的圖說),我們將設置一個每隔65微秒觸發執行的程式,每執行一次,就將變數i值加1,並且判斷i值是否等於或大於調光變數dim。

TimerOne交流電訊號觸發TRIAC說明

如果i值大於或等於dim變數值,隨即觸發TRIAC,否則, TRIAC維持在關閉狀態。完整的程式碼如下(電路和附錄 D-13頁相同):

延伸閱讀

43 thoughts on “使用TimerOne程式庫改寫Arduino交流電調光器程式

  1. 趙先生:
    I had read your book “超圖解 Arduino 互動設計入門”. I plan to add SDcard into my system to record the temperature and humility data. Can you share your idea about what kind of hardware and Arduino code can meet the requirement?
    Thanks!
    Best Regards,
    Phillip

    1. 接兩組交流電調光器的情況,零交越偵測電路只需要一組(因為它們都接到相同的交流電來源,觸發時機一致),因此第二組應該只需要如下的電路,但我沒有驗證過:

      TRIAC調光器控制電路

      每一組都需要個別計算TRIAC開啟和關閉的時間,所以要設定兩組相關變數。即便使用兩組相同的TRIAC控制電路,只需要將其中一組的零偵測輸出,接到中斷腳(數位2),此外:

      假設第一組的TRIAC訊號輸出接在數位腳3;可變電阻接在A0。
      假設第二組的TRIAC訊號輸出接在數位腳5;可變電阻接在A1。

      程式修改如下(編譯無誤,但我手邊沒有多餘的TRIAC控制模組,因此未實際驗證):

      thanks,
      jeffrey

    1. hi zhang:

      如果你可以用一般的可調光燈具控制那個LED燈泡的話,用這個電路也行,但我沒有測試過。請再告訴我結果,謝謝!

      thanks,
      jeffrey

    2. 感謝告知!不過,我以為這LED燈泡的操作結果會跟普通燈泡一樣,原來只分成三段調光啊~

      thanks,
      jeffrey

  2. 記得以前電力電子學教的TRIAC好像是只要觸發他導通,就會持續導通,直到流過
    的電流為零時就自動OFF掉了,也就是通過交越點就不導通了

    1. hi jian:

      是的,電晶體的B極必須要持續流入電流才能讓C, E極導通;TRIAC和SCR的閘極(G)則類似拴鎖,只要觸發一下,它就維持開啟狀態,直到A1和A2極的電流為0就自動關閉了。

      所以程式只需要計算從零交越點到觸發閘極的延遲時間,進而控制TRIAC的導通時間。

      thanks,
      jeffrey

    2. 請教
      有可能設定成外部中斷時(交越時)TIMER1才開始起動計時,計時器溢位中斷時停止計時再觸發TRIAC一小段時間
      然後計時的時間長度是可變的,我想以紅外線遙控器按上下或+ – 來控制觸發延遲的時間
      不曉得這樣可行嗎?

    3. 上文程式的亮度值由dim變數決定,而該變數值的來源是可變電阻的分壓值,您只要將它改成透過紅外線訊號改變dim值即可。

      thanks,
      jeffrey

  3. 遙控器控制調光器試做成功了..
    但是我照圖接triac不會動,似乎是書頁D-10中左手邊A1,A2
    錯誤造成的,我對調2腳就OK了
    書本的腳位與DATASHEET不同喲..
    BTA12-600B,MOC3020M我只買到BTA12-600C,MOC3020

    1. 感謝告知!TRIAC是雙向導通,而且我們也無法預知是正半波或負半波先導通,所以它們應該沒有分別。

      thanks,
      jeffrey

  4. 您好,請問一下
    如果以1N4004替代,電阻45k以兩顆91K並聯代替,但是最後輸出沒有電壓(輸入有110V,並聯電阻也有),可能是什麼原因。

    1. hi bj:

      請問4N25有輸出訊號嗎?可在上文程式第44行:

      if(zeroCross) {

      底下加入一個Serial.println()敘述,在序列埠監控視窗隨意輸出一些訊息,看看中斷是否有作用。

      thanks,
      jeffrey

  5. 您好,請問該如何在if(zeroCross) {底下加入一個Serial.println(),能詳細說明嗎

    另外好像在BTA12-600B的A1時還有電壓(接A1燈泡會量)但接A2燈泡不會亮
    是什麼原因

    謝謝

    1. 接A2腳不會亮,是因為TRIAC沒有導通;請將setup()函數改成:

      dim_check()函數改成:

      如果電路正常,序列埠監控視窗將會不停地呈現”crossed!”訊息。

      關於序列埠以及相關指令應用,請參閱5-17頁。

      thanks,
      jeffrey

  6. Hi Jeffrey,

    我看了你的調光程式,是用市電60HZ頻率分割128等份,每等份65us. 若是這個程式要能同時使用在不同頻率的電源下. 可否請教您的想法及建議. 謝謝.

    1. hi andy:

      我認為有兩個作法,一個是先用兩個變數儲存間隔時間,然後在電路上安排一個切換開關,決定要採用50Hz或60Hz模式。

      另一個可行的方式,程式碼可以從零交越點的觸發時間,回推得知目前的交流電頻率,再去計算相關的延遲時間,這樣應該能達成自動辨別與切換的功能。

      thanks,
      jeffrey

  7. 請問我如何把可變電阻的值改成速度的值,然後利用TimerOne函示庫結合再一起,如何計算顯示出超音波的速度與時間??以下是我利用超音波程式與TimerOne結合 是哪裡出問題了??
    #include

    /*
    設定一個充當計算關閉TRIAC的延遲時間的「計數器」,
    其值將在每65微秒增加1,
    當i的值增加到大於或等於dim(調光值)時,
    再令TRIAC開啟。
    */
    volatile int i=0;
    volatile boolean zeroCross=0; // 儲存零交越狀態的變數
    const byte acPin = 12; // TRIAC訊號輸出接腳
    const byte potPin = A0; // 可變電阻的接腳
    #define trigPin 12
    #define echoPin 13
    int time=0;
    int dim = 64; // 調光器的階段值 (0-128) , 128代表關閉。

    void setup() {
    Serial.begin (9600);
    pinMode(trigPin, OUTPUT);
    pinMode(echoPin, INPUT);
    pinMode(acPin, OUTPUT); // TRIAC的控制輸出腳
    attachInterrupt(0, zeroCrossISR, RISING); // 偵測零交越訊號
    /*
    執行TimerOne程式庫裡的Timer1定時觸發程式,
    參數65代表定時器的運作週期是65微秒。
    */
    Timer1.initialize(65);
    // 設定讓定時器每隔65微秒,自動執行dim_check函數。
    Timer1.attachInterrupt(dim_check);

    }

    // 每當偵測到零交越點,底下的函數就會被執行
    void zeroCrossISR() {
    zeroCross = true;
    i=0;
    digitalWrite(acPin, LOW); // 關閉TRIAC
    }

    // 底下的函數將每隔65微秒觸發一次
    void dim_check() {
    if(zeroCross) { // 若已經過零交越點….
    if(i>=dim) { // 判斷是否過了延遲觸發時間…
    digitalWrite(acPin, HIGH); // 開啟TRIAC
    i=0; // 重設「計數器」
    zeroCross=false;
    } else {
    i++; // 增加「計數器」
    }
    }
    }

    void loop() {
    float duration, distance;
    int intervaltime=500; //每幾毫秒測量一次
    digitalWrite(trigPin, HIGH);
    delayMicroseconds(1000);
    digitalWrite(trigPin, LOW);
    duration = pulseIn(echoPin, HIGH);
    distance = (duration/2) / 29.1;
    Serial.print(“distance,”);
    Serial.print(time);
    Serial.print(“,”);
    Serial.println(distance);
    time = time + intervaltime;
    dim = analogRead(potPin) / 8;

    delay(intervaltime);

    }

    1. hi kevin:

      我猜想你的意思是,要把超音波感測器測得的距離,代換成燈光亮度。從你的程式碼看來,只是把兩個不同功能的程式片段貼在一起,兩者並沒有交集。比方說,你已經決定不再使用可變電阻,那loop()函式裡面就不應該出現底下的敘述:

      dim = analogRead(potPin) / 8;

      我覺得在寫這個程式之前,應該要先規劃好距離跟亮度的比例關係。例如,當感測距離低於30cm時,亮度最高;當感測距離大於或等於120cm時,燈光熄滅。

      按照這個邏輯,只要把距離透過map()函式,置換成對應的dim值即可。

      have fun!
      jeffrey

  8. Jeffrey,您好:
    請問書上的調光器電路,有辦法用 MOC3063 來簡化嗎?
    另外,依照您設計的調光器電路,若是輸出端要驅動兩支 110V 15A 的燈管,程式碼與電路的部分要如完成呢?謝謝。

    1. 我沒嘗試過,但我想沒問題,重點是你要把MOC3063的零交越信號輸出給Arduino的中斷0,程式碼不用改。這個電路無法控制日光燈管,相關的電路再麻煩您搜尋一下。

      thanks,
      jeffrey

    2. 謝謝 Jeffrey 的回覆。
      是這樣的,目前手邊有許多台飛利浦黑晶爐,它別於電磁爐設計,內部是兩條鹵素燈管,燈管通電後產生光與熱,再透過表面的玻璃產生熱能。負責導通燈管的是兩顆 BT139X ,終端由 MCU 來控制,火力六段可以調整,我想用 Arduino 來模擬它的設計。
      另外,以下是我用 Arduino 設計的致冷晶片電路,濕度高於 45%就會啟動致冷片,但是當濕度介於 45~46%的時候,繼電器就會跳來跳去減少壽命,請問程式碼的部分要如何更改較妥當?謝謝。
      連結:http://bbs.pigoo.com/thread-56641-1-1.html

      #include
      #include
      #define dhtread A0
      #define led 3
      #define fan 2
      float display;
      dht DHT;
      LiquidCrystal lcd(9, 8, 7, 6, 5, 4);

      void setup()
      {
      pinMode (fan, OUTPUT);
      lcd.begin(16, 2);
      lcd.setCursor(4, 0);
      lcd.print(“Temp”);
      lcd.setCursor(0, 1);
      lcd.print(“Humidity”);
      pinMode(led, OUTPUT);
      Serial.begin(9600);
      delay(300);
      Serial.println(“Humidity and Tempture Test \n\n”);
      delay(700);
      }

      void loop() {

      lcd.setCursor(9, 0);
      lcd.print((float)DHT.temperature, 2);
      lcd.print((char) 0xDF);
      lcd.print(“C”);

      lcd.setCursor(9, 1);
      lcd.print((float)DHT.humidity, 2);
      lcd.print(“%”);
      delay(2000);

      DHT.read11(dhtread);
      Serial.print(“Humidity = “);
      Serial.print((float)DHT.humidity);
      Serial.print(“% “);

      Serial.print(“Temperature = “);
      Serial.print((float)DHT.temperature);
      Serial.println(“C “);
      delay(1000);

      display = DHT.humidity;
      if (display >= 45) {
      digitalWrite (led, HIGH);
      digitalWrite (2, HIGH);
      } else {
      digitalWrite(2, LOW);
      digitalWrite(led, LOW);
      }

      }

    3. 因為外在環境(溫濕度、亮度、重力加速度…等)經常會有些微的變化、感測器本身有誤差,加上雜訊,所以感測值經常處於變動狀態。

      為了避免感測值在臨界值附近震盪,導致輸出開開關關,程式不要只依賴一個臨界值;您可以參閱「動手做6-2:使用光敏電阻製作小夜燈」單元,設定一個感測區間值,高於某數值時啟動、低於某數值時關閉。

      或者,您可以嘗試在一段時間內讀取數筆感測資料(通常取5筆),然後再予以平均;或是每次取其中最大或最小值,這樣就能降低取樣數據波動的情況。

      thanks,
      jeffrey

  9. 您好~~~我照著書本上的範例, 以可變電阻來控制燈泡亮度有成功了(我的情形跟上面回覆的一樣, A1和A2腳需對調)~~
    我現在想改成以Serial.read()輸入值來改變dim值, 但都不成功…後來我有把dim值Print出來, 發現跟輸入值不同…想請教一下, 是什麼地方還需要做變更嗎?!我的程式碼只有將loop()裡讀取可變電阻值來改變dim值改掉而以. 程式如下:

    不知道是哪裡出了錯誤呢?!

    1. hi kevin:

      問題出在loop()函式的讀取序列值程式碼:

      請參閱5-23頁說明,序列接收到的值是字元的ASCII碼,如果要將輸入字串轉換成數字,請參閱10-10頁說明。

      thanks,
      jeffrey

  10. 原來如此~~~哈哈~~~
    感謝您耐心的回答哦~~~
    PS:這本書真的讓我學到了很多知識~~~解釋很詳細也很直覺~~~真的是很好的一本讀物~~~

  11. Hi Jeffrey
    若我想要用的Timer中斷時間為10分鐘(或以上)
    請問該怎麼辦呢

    小弟不成材
    剛開始起步探索arduino的世界
    還請Jeffrey指教

    1. hi oreo:

      我不太了解你的意思,如果你是想要寫一個延時10分鐘的程式,請參閱這個留言,使用SimpleTimer程式庫撰寫會比較容易。

      把第36行的敘述改成:

      timer.setTimeout(600000L, turnOff);

      就是10分鐘了。

      thanks,
      jeffrey

  12. 您好,
    燈泡未開啟時數值01010101的變動,燈泡開啟時數值都為1
    請問這該如何來做判斷
    謝謝

發表迴響

你的電子郵件位址並不會被公開。 必要欄位標記為 *