2012/02/16

Arduino練習:呼吸燈

之前利用Arduino板子的數位腳位,以開關的狀態控制LED明滅,這一篇要讓LED變成會呼吸的燈(漸亮、漸暗、漸亮、漸暗、不斷循環),並利用可變電阻控制呼吸燈明滅循環的時間長度。

電路圖(Fritzing格式)與程式原始碼,可在此下載

呼吸燈:

LED不只能有明滅兩種狀態,我們還可以讓它有最亮、普通亮、不太亮、稍暗、很暗、最暗、等等狀態,Arduino利用所謂的pulse width modulation (PWM)技術,可以做到這點。

雖然數位腳位只能輸出開(5V)、關(0V)兩種狀態,但可以在極短的時間內不斷切換,讓"開"的狀態佔某百分比,達到類比輸出的效果。看看下面這張圖,如果一直都是0V,那就類似於LED滅掉,如果5V狀態佔全部的25%,就等於LED的亮度只有全亮時的四分之一,依此類推。


聽起來好像很難,但用起來很簡單,繼續往下吧。

電路圖如下:

從Arduino板子上的5V腳位、GND腳位,接線到麵包板上。
從Arduino板子的腳位3接到220 ohm電阻、再接到LED的長腳(陽極、正極)、LED的短腳接地。

我們將利用數位腳位3,以PWM控制LED的明亮程度。



注意,每個板子可輸出PWM的腳位有所不同,我用的Uno板,可用腳位有3、5、6、9、10、11。

程式碼:
int brightness = 0;
int fadeAmount = 5;
int delayDuration = 30;

void setup()  {
  pinMode(3, OUTPUT);
}

void loop()  {
  analogWrite(3, brightness);
  brightness = brightness + fadeAmount;
  if (brightness <= 0 || brightness >= 255) {
    fadeAmount = -fadeAmount ;
  }   

  delay(delayDuration);                           
}
在setup()裡,將腳位3的模式設為OUTPUT。但其實,使用analogWrite()不需要此設定。

在loop()裡,以函式analogWrite()改變LED的明亮程度,第一個參數是腳位,第二個參數是個從0到255的值,在此例裡就會改變LED的亮度,以變數brightness記錄亮度,然後,每執行一次loop()就加上fadeAmount、改變brightness亮度值,若brightness超過了0~255的範圍,就把fadeAmount正變負、負變正,最後,延遲30 milliseconds。

好了,你應該會看到LED從全暗,慢慢逐漸變亮、變成最亮,然後再逐漸變暗,不斷循環。可以修改delay(30)這個數值,調整循環時間。


以可變電阻控制呼吸燈的循環時間:

電路圖如下:

加入一個10k ohm的可變電阻,左腳接5V,右腳接地,中腳交到Arduino板子的A0腳位(類比腳位)。



程式碼:
int brightness = 0;
int fadeAmount = 5;
int delayDuration = 30;

void setup()  {
  pinMode(3, OUTPUT);
}

void loop()  {
  analogWrite(3, brightness);
  brightness = brightness + fadeAmount;
  if (brightness <= 0 || brightness >= 255) {
    fadeAmount = -fadeAmount ;
  }  

  delay(delayDuration);
 
  int potValue = analogRead(A0);
  delayDuration = map(potValue, 0, 1023, 1, 50);
}
多了一個全域變數delayDuration,用來記錄延遲的值。

在loop()裡,以analogRead()讀取類比腳位的值,Arduino裡有類比數位轉換器(analog-to-digital converter),會將這個腳位的電壓轉為0~1023範圍裡的數值。取得後放在potValue,然後,我們利用函式map()將這個數值對應到1~50的區間內。隨著可變電阻的轉動而改變,進而控制呼吸燈的循環時間。


參考資料:

62 comments:

  1. 要多加led要怎麼改

    ReplyDelete
    Replies
    1. 嗯,有什麼問題嗎?

      Delete
  2. 大大有徵信号吗

    ReplyDelete
  3. 請問下 如果我要整個重新循環
    要在哪邊增加LOOP?
    EX:A→B→C 然後回到A 直到電池消耗完畢
    這樣要怎設計呢?

    ReplyDelete
    Replies
    1. 就寫在loop()裡,不是嗎?

      Delete
    2. LOOP不是單一腳位的循環嗎?
      剛接觸不是很熟
      我現在只想讓腳位單純的輸出電壓
      是不是把 int XXX 改成電壓英文?

      Delete
    3. 剛剛利用程式碼測試 確實能改成電壓
      int voltage = 13;

      void setup() {
      // put your setup code here, to run once:
      pinMode(voltage,OUTPUT);
      }

      不過有個疑問是 如果想改變電壓
      是否能在程式內設定輸出的電壓值
      還是要用外接電阻降壓?

      Delete
    4. > LOOP不是單一腳位的循環嗎?
      loop()的意思是微控制器會不斷地執行它。

      > 剛接觸不是很熟
      找本書看看。先看看我其他Arduino文章吧。
      http://yehnan.blogspot.tw/2012/02/arduino_21.html

      > 想改變電壓
      以pinMode設為OUTPUT模式的話,這是數位輸出,然後使用digitalWrite,只有HIGH與LOW兩種,通常會是5V(或3.3V)與0V,視板子而定。

      > 是否能在程式內設定輸出的電壓值
      可使用analogWrite,輸出PWM。

      > 還是要用外接電阻降壓?
      可以。

      Delete
    5. 下午測試寫程式把電壓輸入數值 結果是成功
      但只差在實際運作 打算明天測試是否真的有按照數值輸出
      int voltage = 13;

      void setup() {
      // put your setup code here, to run once:
      pinMode(voltage,OUTPUT);
      }

      void loop() {
      // put your main code here, to run repeatedly:
      digitalWrite(voltage,0.8);
      delay(8000);
      digitalWrite(voltage,2.0);
      delay(3500);
      digitalWrite(voltage,3.9);
      delay(3500);
      digitalWrite(voltage,4.0);
      delay(30000);
      digitalWrite(voltage,3.0);
      delay(10000);
      }

      目前寫到一半 測試也沒發現問題
      但現在才知道電壓最低值只有3.3 0.8V可能還要想辦法了

      Delete
    6. digitalWrite的參數,只會被當做HIGH(非0值)與LOW(0),
      所以你傳入2.0、3.9,都是被當做HIGH。

      想要0.8V,可使用PWM模擬。使用analogWrite。

      Delete
    7. digitalWrite的第一個參數是腳位,不是電壓。

      Delete
    8. 如果是用PWM腳位 我記得數值是0~255是吧?
      那這樣電壓的高低就要自己去找是吧?

      Delete
    9. 若你的板子是3.3V的板子,
      那麼使用analogWrite時,0會代表0V,255會代表3.3V,
      127大概就會是1.65V。

      但要記住這是PWM訊號,並非真正的類比輸出。

      Delete
    10. 哇 那這下麻煩了
      所以若是用PWM寫的話 還需要個PWM轉類比訊號的轉接器喽?
      另外我不確定我的板子是否滿是3.3V 明天我來試試看
      假如需要轉接器做轉換 那我只好用外接電阻降壓了
      但需要7個輸出端

      Delete
    11. 你要類比,不是應該使用DAC(digital-to-analog converter)嗎?
      譬如MCP4725之類的。

      Delete
    12. 我看等我明天實驗後再繼續討論吧
      畢竟我對程式算是第一次接觸
      目前還在研究當中

      Delete
    13. 您好 今天測試時很成功
      不過當我使用多一個腳位時候
      電壓HIGH才2.35左右
      想請問當使用多個腳位時候電壓是平均分攤嗎?
      以下是我的程式碼
      int A = 13;
      int B = 12;
      int C = 11;

      void setup() {
      // put your setup code here, to run once:
      pinMode(A,OUTPUT);
      pinMode(B,OUTPUT);
      pinMode(C,OUTPUT);
      }

      void loop() {
      // put your main code here, to run repeatedly:
      digitalWrite(A,HIGH);
      delay(3000);
      digitalWrite(A,LOW);
      delay(3000);
      digitalWrite(B,HIGH);
      delay(5000);
      digitalWrite(B,LOW);
      delay(3000);
      }

      目前我只測試兩個腳位
      假如說使用多個腳位會造成電壓分攤
      那我還要再想想辦法了

      Delete
    14. > 電壓是平均分攤嗎?
      不是。除非接的東西吃掉過大電流,那可能會影響其他腳位。

      Delete
    15. 您好 好久不見了
      續上次的問題 我花了時間了解有關PWM寫法
      其中有個最讓我了解的寫法是這樣

      int ledPin = 3; // 把 LED 接上 PWM pin3
      void setup()
      {
      pinMode(ledPin, OUTPUT); // 設定 pin 3 為輸出
      }
      // 下面這個 loop 會讓 LED 燈由暗變為一半亮度,最後變成最大亮度
      void loop()
      {
      analogWrite(ledPin, 0); // LED 不亮
      delay (1000);
      analogWrite(ledPin, 127); // LED 一半亮度
      delay (1000);
      analogWrite(ledPin, 255); // LED 最大亮度
      delay (1000);
      }

      這是從範例複製過來的
      我的板子最高輸出為4.95V
      因此若要達到我規定的電壓的話,就要自己去找PWM的值喽?

      Delete
  4. 你好!
    請問我要用超音波,聲音感測器,光敏電阻,以上功能.結合一起 以藍芽HC-06控制
    用App Inventor 2 做按鈕 一個按鈕一種功能

    想請教一下要怎麼把程式整合 App Inventor 2 要怎麼用

    有找到以下需要用到的程式 參考
    超音波http://blog.lyhdev.com/2012/10/arduino-1hc-sr04.html

    聲音感測器http://deskfactory.de/arduino-projekt-geheimnisvolle-geraeusche-mit-sound-sensor-erkennen

    光敏電阻http://ming-shian.blogspot.tw/2013/05/ardunioled.html

    ReplyDelete
    Replies
    1. 你問的問題太大了,無法在留言裡回答。

      Delete
    2. > 結合一起
      > 一個按鈕一種功能
      什麼意思?請詳細描述。

      > 怎麼把程式整合
      遇到什麼問題?

      > App Inventor 2 要怎麼用
      請參閱坊間書籍。

      Delete
  5. 你好 抱歉沒問詳細
    結合一起是超音波,聲音感測器,光敏電阻這三種零件+藍芽模組 的程式都放進pro mini板子
    功能都可以使用 目的都是讓LED亮滅 超音波是感測距離遠近 近則亮遠則滅 聲音感測器偵測到聲音亮沒聲音滅 光敏電阻 有光滅 無光亮 這3個功能都玩過後想要把這3種功能結合一起
    用藍芽模組發射信號連結手機利用App Inventor2 以藍芽控制 製作3個按鈕 第一個按鈕按下去就是超音波的功能 第二個按下去就是聲音感測器的功能 第三個按下去就是光敏電阻的功能 請問能做成這樣嗎!!?

    我參考使用的程示範例
    超音波http://blog.lyhdev.com/2012/10/arduino-1hc-sr04.html

    聲音感測器http://deskfactory.de/arduino-projekt-geheimnisvolle-geraeusche-mit-sound-sensor-erkennen

    光敏電阻http://ming-shian.blogspot.tw/2013/05/ardunioled.html

    ReplyDelete
    Replies
    1. > 請問能做成這樣嗎!!?
      可以。

      雖然你說的很詳細,但問題是什麼呢?碰到什麼問題嗎?

      Delete
  6. 你好!抱歉
    我的問題是
    怎麼將這些程式合併在一起且功能,能正常使用!
    超音波
    #define TRIGPIN 10
    #define ECHOPIN 13
    #define LED1 8
    #define LED2 9

    long ping() {
    digitalWrite(TRIGPIN, LOW);
    delayMicroseconds(2);
    digitalWrite(TRIGPIN, HIGH);
    delayMicroseconds(10);
    digitalWrite(TRIGPIN, LOW);
    return pulseIn(ECHOPIN, HIGH)/58;
    }

    void setup() {
    pinMode(TRIGPIN, OUTPUT);
    pinMode(ECHOPIN, INPUT);
    pinMode(LED1, OUTPUT);
    pinMode(LED2, OUTPUT);
    }

    void loop() {
    long cm = ping();

    if (cm <= 100) {
    digitalWrite(LED1, HIGH);
    delay(cm*1.5 + 10);
    digitalWrite(LED1, LOW);
    digitalWrite(LED2, HIGH);
    delay(cm*1.5 + 10);
    digitalWrite(LED2, LOW);
    }

    delay(100);
    }

    聲音感測器
    int ledPin = 2;
    int AudioPin = A0;
    int Schwellenwert = 500;

    int volume;

    void setup() {
    Serial.begin(9600);
    pinMode(ledPin, OUTPUT);
    }

    void loop() {

    volume = analogRead(AudioPin);

    Serial.println(volume);
    delay(10);

    if(volume<=Schwellenwert){
    digitalWrite(ledPin, HIGH);
    }
    else{
    digitalWrite(ledPin, LOW);
    }

    }

    光敏電阻
    int photocellPin = 2;
    int photocellVal = 0;
    int minLight = 200;
    int ledPin = 9;
    int ledState = 0;

    void setup() {

    pinMode(ledPin, OUTPUT);

    Serial.begin(9600);

    }

    void loop() {



    photocellVal = analogRead(photocellPin);

    Serial.println(photocellVal);

    if (photocellVal < minLight && ledState == 0) {
    digitalWrite(ledPin, HIGH); // turn on LED
    ledState = 1;

    }

    if (photocellVal > minLight && ledState == 1) {
    digitalWrite(ledPin, LOW); // turn off LED
    ledState = 0;

    }

    delay(100);
    }

    ReplyDelete
    Replies
    1. > 怎麼將這些程式合併在一起且功能,能正常使用!
      這是需求,不是問題。

      重點在於delay(),使用delay()的話,功能之間就會互相干擾對方,
      應該先設法以「不使用delay()」的方式改寫程式。

      Delete
    2. 你給的程式碼,除了函式ping()裡的delay,其他的應該都可以刪掉而不影響。

      Delete
  7. 你好!
    設法以「不使用delay()」的方式改寫程式。
    請問要怎麼改寫不太懂 請求指點一下!!
    看不太懂程式碼 麻煩你了!

    ReplyDelete
    Replies
    1. 請參閱http://yehnan.blogspot.tw/2014/03/arduinoled.html

      另外,我之前說過
      你給的程式碼,除了函式ping()裡的delay,其他的應該都可以刪掉而不影響。

      所以你或許可試試直接合併。
      我沒實際試過,不確定是否還有其他問題。

      Delete
  8. 您好 請問arduino的頻率從哪得知呢?
    是從規格的Clock Speed嗎?
    如果是 那UNO最高才980Hz 但規格是寫16MHz
    還是是我看錯了呢?

    ReplyDelete
    Replies
    1. 16MHz是Arduino的微控制器的頻率,新的板子可以更高,但為了維持相容性,所以保持16MHz。
      請參考https://www.arduino.cc/en/Main/ArduinoBoardUno

      980Hz應該是指腳位變化的頻率上限吧,
      https://www.arduino.cc/en/Reference/AnalogWrite

      Delete
    2. 昨天知道可以調整PWM的頻率
      現在運作很成功
      不過還是感謝您的回答

      Delete
  9. 您好 昨天更改PWM頻率後 發現電壓比更改前不穩定
    我是改 timer2 的 照理說應該是不會影響到電壓吧?

    ReplyDelete
    Replies
    1. 什麼電壓?

      你的問題,我大概無法回答。

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

    ReplyDelete
  11. 葉前輩您好:

    敝人想結合開關控制LED與漸亮漸暗效果,
    也就是按一下開關--->LED漸亮 (增量到255後停止增量,維持最亮狀態)
    再按一下開關--->LED漸暗 (減量到0後停止,維持0全暗狀態)
    敝人將開關輸入接至 腳2
    LED輸出接至 腳3
    另將程式列於下:

    #include

    Bounce bouncer = Bounce(2, 30);
    static int ledStatus = LOW;
    int led = 3; // the PWM pin the LED is attached to
    int brightness = 0; // how bright the LED is
    int fadeAmount = 5; // how many points to fade the LED by

    // the setup routine runs once when you press reset:
    void setup() {
    Serial.begin(115200);
    pinMode(2, INPUT);
    pinMode(led, OUTPUT);
    }

    // the loop routine runs over and over again forever:
    void loop() {
    brightness = brightness + fadeAmount;
    analogWrite(led, brightness);

    if(bouncer.update() == true && bouncer.read() == HIGH) {
    ledStatus= ledStatus == HIGH ? LOW : HIGH;
    Serial.print(ledStatus);

    if(ledStatus == HIGH && brightness << 255 ){
    fadeAmount = fadeAmount;
    }
    if(ledStatus == LOW && brightness >> 0 ){
    fadeAmount = -fadeAmount;
    }
    } else {
    brightness = brightness ;
    }
    delay(30);
    }


    但是結果變成LED一開始持續從全暗到全亮然後它不斷自動循環
    然後按下開關一次,ledStatus=HIGH--->無任何變化
    再按下開關一次,ledStatus=LOW--->變成持續從全亮到全暗然後又不斷自動循環
    也就是要遇到下一次ledStatus=LOW 的時候,LED狀態才會改變...

    不知道是甚麼問題???
    因為敝人是第一次接觸Arduino,所以還望葉前輩能給予指導,在此感激不盡~

    ReplyDelete
    Replies
    1. 你在loop一開始就改變brightness, 這樣不對吧. 它的值會不斷變大或變小.



      #include

      Bounce bouncer = Bounce(2, 30);
      int ledStatus = LOW;
      int led = 3; // the PWM pin the LED is attached to
      int brightness = 0; // how bright the LED is
      int fadeAmount = 5; // how many points to fade the LED by

      void setup() {
      Serial.begin(115200);
      pinMode(2, INPUT);
      pinMode(led, OUTPUT);
      }

      void loop() {
      if(bouncer.update() == true && bouncer.read() == HIGH) {
      ledStatus = ledStatus == HIGH ? LOW : HIGH;
      Serial.print(ledStatus);
      }

      if(ledStatus == HIGH){
      brightness = min(255, brightness + fadeAmount);
      }
      else{ // ledStatus == LOW
      brightness = max(0, brightness - fadeAmount);
      }
      analogWrite(led, brightness);
      }

      http://pastebin.com/yS7LQDRf

      但這支程式的行為,跟你要的有點不同。
      一開始LED會漸亮,按一下開關,LED會漸暗。
      漸亮漸暗的途中,都可以按下開關改變。
      請自己改改。

      Delete
    2. 謝謝 葉前輩~

      讓我這次又學到了min()還有max()的指令
      只簡單加了delay() 跟嘗試調換 min與max內的資料位置測試看看
      已經有達到我想要的效果~
      再次感謝葉前輩的指導~ 我會再繼續努力的...

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

    ReplyDelete
  13. 請問有辦法用超音波模組去偵測距離,回傳給arduino然後轉換成PWM訊號,自動控制LED亮度嗎?

    ReplyDelete
    Replies
    1. 先偵測距離,
      然後你要定義距離與PWM強度的對應關係,使用map()進行對應轉換,
      接著使用analogWrite控制LED亮度。

      Delete
    2. 有類似的範例可以參考嗎?

      Delete
    3. 參考Arduino內建範例,03.Analog - Calibration。
      裡頭使用analogRead、map、analogWrite,
      你需要把analogRead的部份轉成超音波模組偵測距離。

      Delete
    4. 好的,謝謝您。

      Delete
    5. 請問有辦法拿呼吸燈的程式碼來修改嗎?

      Delete
    6. 要改當然都可以。

      Delete
    7. int sensorPin1=A0;
      int sensorPin2=A1;
      int ledPin=12;
      int sensorValue=0;
      int sensorMin=255;
      int sensorMax=0;

      void setup(){
      pinMode(12,OUTPUT);
      digitalWrite(12,HIGH);
      }
      void loop(){
      sensorValue=analogRead(sensorPin2);
      sensorValue=map(sensorValue,sensorMin,sensorMax,0,255);
      sensorValue=constrain(sensorValue,0,255);
      analogWrite(ledPin,sensorValue);
      }


      這是我改好的程式碼,可是為什麼會閃爍,是要加加些什麼嗎?

      Delete
    8. 閃爍?什麼?

      腳位12不支援analogWrite吧。

      Delete
    9. 我改成11了

      我的板子是pro mini 3.3v 8MHz

      Delete
    10. 還是會閃爍, 看不出來是否有自動調光的功能

      Delete
    11. 我也看不出來。

      除非sensorValue值的變動速度非常快。

      Delete
  14. 你好 請問ARDUINO 能否控制繼電器在斷電5秒後通電

    ReplyDelete
  15. 你好 請問ARDUINO 能否控制繼電器在斷電5秒後通電

    ReplyDelete
    Replies
    1. 可以。

      不就是用delay或計時器嗎?
      有何問題?

      Delete
  16. 我是用超音波觸發呼吸燈 請問可以觸發幾秒後呼吸燈熄滅或是循環幾次後熄滅嗎?

    ReplyDelete
    Replies
    1. 可以。寫程式囉。

      Delete
    2. 想請問程式如何寫呢?
      這是我目前的程式碼 不過我再多加上delay無法達到上面要的效果
      const byte trigPin = 10;
      const int echoPin = 9;
      const int LED1 = 3;
      const int LED2 = 5;
      int brightness = 0;
      int fadeAmount = 5;
      int delayDuration = 10;


      unsigned long d;

      unsigned long ping(){
      digitalWrite(trigPin,HIGH);
      delayMicroseconds(5);
      digitalWrite(trigPin,LOW);

      return pulseIn(echoPin,HIGH);
      }

      void setup(){
      pinMode(trigPin,OUTPUT);
      pinMode(echoPin,INPUT);
      pinMode(LED1,OUTPUT);
      pinMode(LED2,OUTPUT);

      Serial.begin(9600);
      }

      void loop(){
      d = ping()/58;
      Serial.print(d);
      Serial.print("cm");
      Serial.println();

      if (d<20){

      analogWrite(3, brightness);
      analogWrite(5, brightness);

      brightness = brightness + fadeAmount;
      if(brightness <= 0 || brightness >= 255) {
      fadeAmount = -fadeAmount ;

      }
      delay(delayDuration);
      }else{

      digitalWrite(LED1,LOW);
      digitalWrite(LED2,LOW);

      }
      }

      Delete
    3. 採用 有限狀態機 的寫法,
      大概是這樣吧,沒實際跑過,後果自負:

      http://pastebin.com/wES3pGYj



      const byte trigPin = 10;
      const int echoPin = 9;
      const int LED1 = 3;
      const int LED2 = 5;
      int brightness = 0;
      int fadeAmount = 5;
      int delayDuration = 10;

      unsigned long d;

      unsigned long ping(){
      digitalWrite(trigPin,HIGH);
      delayMicroseconds(5);
      digitalWrite(trigPin,LOW);

      return pulseIn(echoPin,HIGH);
      }

      void setup(){
      pinMode(trigPin,OUTPUT);
      pinMode(echoPin,INPUT);
      pinMode(LED1,OUTPUT);
      pinMode(LED2,OUTPUT);

      Serial.begin(9600);
      }
      typedef enum{
      S_Sonic,
      S_Breathe,
      } State;
      State state = S_Sonic;
      unsigned long t_old;

      void loop(){
      switch(state){
      case S_Sonic:{
      d = ping()/58;
      Serial.print(d);
      Serial.print("cm");
      Serial.println();

      if(d<20){
      Serial.println("triggered");
      t_old = millis();
      state = S_Breathe;
      }
      }
      break;

      case S_Breathe:{
      unsigned long t = millis();
      if(t - t_old > 5000){
      analogWrite(3, 0);
      analogWrite(5, 0);
      state = S_Sonic;
      }
      else{
      analogWrite(3, brightness);
      analogWrite(5, brightness);

      brightness = brightness + fadeAmount;
      if(brightness <= 0 || brightness >= 255) {
      fadeAmount = -fadeAmount ;

      }
      delay(delayDuration);
      }
      }
      break;
      }
      }


      Delete