2017年12月4日 星期一

紅外線發射接收器

作者:王一哲
Ver. 1:2016/11/20
Ver. 2:2018/3/29



所需元件



  1. 紅外線發射 LED 2 個
  2. 紅外線接收 LED 2 個
  3. 3 mm 或 5 mm LED 1 個
  4. 220 Ω 電阻 1 個(串聯普通 LED)
  5. 560 Ω 電阻 2 個(串聯紅外線發射 LED)
  6. 1 MΩ 電阻 2 個(串聯紅外線接收 LED)
  7. 麵包板 1 塊
  8. Arduino Uno 開發板 1 塊
  9. 麵包板連接線數條
  10. 自製的 LED 支架 2 組

圖中藍色的是紅外線發射 LED、透明的是紅外線接收 LED。
請注意,各廠牌的發射器及接收器顏色不一定相同,購買前最好先向店家確認。

自製的LED支架


使用類比輸入測試



我們所用的紅外線 LED 發射及接收器需要對準才能讀到較強的訊號,因此下圖只是示意圖,請參考以下的接法但另外用較長的線將發射、接收器拉出來。我自己測試的結果,當發射、接收器對準時且距離 5 cm,讀數可達 1000,若中間有障礙物時約為 300。
線路圖

實際的裝置照片


/* 實驗7-1: 紅外線發射、接收器                          *
 *          用類比輸入讀取,檢測讀數範圍                *
 *          對準時距離5cm讀數可達1000,隔絕時約300      *
 * Ver. 1: Sep. 16, 2016                                 *
 * Ver. 2: Mar. 20, 2018                                 *
 * 作者: 王一哲                                          */

#define SENSOR A0
#define BAUDRATE 9600

void setup() {   
    pinMode(SENSOR, INPUT);     
    Serial.begin(BAUDRATE);
}

void loop(){
    int state = analogRead(SENSOR);
    Serial.println(state);
    delay(1000);
}


使用數位輸入測試



由於我想要把紅外線發射、接收器當成光電閘使用,只要能讀出兩者間是否有東西經過即可,因此需要用數位輸入測試看看。除了紅外線發射、接收器,另外再接上一顆普通的 LED ,當光電閘之間沒有障礙物時點亮 LED,有障礙物時熄滅 LED。
線路圖

實際的裝置照片


/* 實驗7-2: 紅外線發射、接收器                    *
 *          用數位輸入讀取,檢測讀數是否正常     *
 * Ver. 1: Sep. 16, 2016                           *
 * Ver. 2:Mar. 20, 2018                           *
 * 作者: 王一哲                                    */

#define LED 13
#define SENSOR 2
#define BAUDRATE 9600

void setup() {
    pinMode(LED, OUTPUT);      
    pinMode(SENSOR, INPUT);     
    Serial.begin(BAUDRATE);
}

void loop(){
    int state = digitalRead(SENSOR);
    Serial.println(state);
    if(state == HIGH) {
        digitalWrite(LED, HIGH);
    } else {
        digitalWrite(LED, LOW);
    }
    delay(100);
}


迴圈



迴圈(loop)是用來重複執行某段程式碼的工具,在C、C++、Arduino中的迴圈有while、do while、for等三種,Arduino網站對於這三種迴圈的說明如下:
  1. while: https://www.arduino.cc/en/Reference/While
  2. do while: https://www.arduino.cc/en/Reference/DoWhile
  3. for: https://www.arduino.cc/en/Reference/For

while 的語法



while (條件) {
    迴圈內容......;
    通常會有遞增或遞減條件;
}

當條件成立時會執行迴圈內容,當條件不成立時結束迴圈。另一個結束迴圈的狀況則是在迴圈中遇到 break,一旦遇到 break 會立即跳出迴圈,位在迴圈內但在 break 之後的程式碼不會被執行。

do while 的語法



do {
    迴圈內容......;
    通常會有遞增或遞減條件;
} while (條件);

用法與 while 迴圈幾乎一樣,差別在於 do while 迴圈會先執行一次迴圈內容,執行完後再檢查條件是否成立,當條件成立時會再次執行迴圈內容,當條件不成立時結束迴圈。一樣可以用 break 跳出迴圈。

for 的語法



for (起始狀態; 條件; 遞增或遞減) {
    迴圈內容......;
}

for 迴圈通常會用在已知執行次數的狀況,例如 1+2+3+...+10 = ? 這類的計算,這會寫成
int sum = 0;

for (int i = 1; i <= 10; i++) {
    sum += i;
}

一開始設定了變數 i 並將其值指定為1,接著檢查 i 是否小於、等於10,若條件成立則執行迴圈內容,執行完後將 i 的值加1;若條件不成立則結束迴圈。在這個例子當中有一些不太符合數學運算規則的寫法
i++        i += 1        i = i + 1

這三種寫法的功能相同,將 i 的值加1後再存到 i 當中,若 i 原為1,運算後變為2。
sum += i        sum = sum + i

這兩種寫法的功能相同,將 sum 的值加上 i 的值後再存到 sum 當中,若 sum 原為45、i 原為 10,運算後 sum 變為55。for 迴圈一樣可以用 break 跳出迴圈。

三種迴圈的流程圖


使用1組光電閘設次計時



線路圖與裝置請參考使用數位輸入測試,接法完全相同。程式碼如下:
/* 實驗7-3: 紅外線發射、接收器                     *
 *          用數位輸入讀取,設次計時               *
 * Ver. 1: Sep. 16, 2016                            *
 * Ver. 2: Mar. 20, 2018                            *
 * 作者: 王一哲                                    */

#define LED 13          //設定LED接腳
#define SENSOR 2        //設定IR LED接收器接腳
#define BAUDRATE 9600   //設定鮑率
const int times = 21;   //設定計時次數
int lastState, currentState;
bool blocked = false;

//更新接收器狀態函式
bool updateSensor(int pin){
    currentState = digitalRead(pin);
    if (currentState == LOW && lastState == HIGH) {
        lastState = currentState;
        blocked = true;
    } else if (currentState == HIGH && lastState == LOW) {
        lastState = currentState;
        blocked = false;
    } else {
        blocked = false;
    }
    return blocked;
}

//使LED閃爍1下
void blinkLed(int pin) {
    digitalWrite(pin, HIGH);
    delay(50);
    digitalWrite(pin, LOW);
    delay(50);
    digitalWrite(pin, HIGH);
}

void setup() {
    pinMode(LED, OUTPUT);      
    pinMode(SENSOR, INPUT);
    digitalWrite(LED, HIGH);     
    Serial.begin(BAUDRATE);
}

void loop(){
    int counts = 0;
    unsigned long t1, t2;
    lastState = digitalRead(SENSOR);
//直到接收器第1次被擋住時記錄時間t1、次數+1、使LED閃爍1次
    while(1){
        if(updateSensor(SENSOR) == true) {
            t1 = millis();
            counts += 1;
            blinkLed(LED);
            Serial.print("counts: ");
            Serial.println(counts);
            Serial.print("t1: ");
            Serial.println(t1);
            break;
        }
    }

//當接收器被擋住時記錄時間t2、次數+1、使LED閃爍1次,到達設定的次數時停止
    while(counts < times){
        if(updateSensor(SENSOR) == true) {
            t2 = millis();
            counts += 1;
            blinkLed(LED);
            Serial.print("counts: ");
            Serial.println(counts);
            Serial.print("t2: ");
            Serial.println(t2);
        }
    }

//印出測量結果
    unsigned long dt = t2 - t1;
    Serial.print("t1 = ");
    Serial.println(t1);
    Serial.print("t2 = ");
    Serial.println(t2);
    Serial.print("dt = ");
    Serial.println(dt);
}


程式的流程大致可分為
  1. 設定接腳編號、鮑率、計時次數、全域變數
  2. 設定自定函式updateSensor、blinkLed
  3. 在 void setup() 中設定接腳模式、開啟連接埠
  4. 在 void loop()
    1. 設定區域變數counts、t1、t2,讀取sensorPin的狀態
    2. 在第1個 while loop 中,直到接收器第1次被擋住時記錄時間t1、次數+1、使LED閃爍1次
    3. 在第2個 while loop 中,當接收器被擋住時記錄時間t2、次數+1、使LED閃爍1次,到達設定的次數時停止
    4. 印出測量結果


在這個程式當中應用了一個技巧,利用 while 迴圈及 if 使程式停在某個部分,直到指定的條件發生為止。
while(1) {
    if(updateSensor(SENSOR) == true) {
        t1 = millis();
        counts += 1;
        blinkLed(LED);
        Serial.print("counts: ");
        Serial.println(counts);
        Serial.print("t1: ");
        Serial.println(t1);
        break;
    }
}

while(1) 其實是個無窮迴圈,因為條件設定為常數1,條件一定成立,迴圈內的程式碼會不斷地被重複執行,直到 if(updateSensor(SENSOR) == true) 成立為止,因為在 if 當中最後一行會碰到 break 跳出迴圈。 第2個 while 迴圈只是單純地計算次數,直到光電閘被擋住的次數到達設定次數為止,其中 Serial.println 的部分會將次數及時間印出來,方便除錯。當第2個 while 迴圈跑完後會印出 t1、t2 及時間差 dt ,如果光電閘沒有再被擋住,程式就會又停在第1個 while 迴圈。

使用2組光電閘測量物體通過所用的時間



線路圖與裝置如下,只是發射、接收器增加為2組。
線路圖

實際的裝置照片


程式碼如下:
/* 實驗7-4: 紅外線發射、接收器作為光電閘               *
 *          用數位輸入讀取,測量通量2組光電閘的時差    *
 * Ver. 1: Sep. 16, 2016                                 *
 * Ver. 2: Mar. 20, 2018                                 *
 * 作者: 王一哲                                         */

#define LED 13          //設定LED接腳
#define START 2         //設定IR LED接收器1接腳
#define STOP 3          //設定IR LED接收器2接腳
#define BAUDRATE 9600   //設定鮑率

int lastState, currentState;
bool blocked = false;

//更新接收器狀態函式
bool updateSensor(int pin) {
    currentState = digitalRead(pin);
    if (currentState == LOW && lastState == HIGH) {
        lastState = currentState;
        blocked = true;
    } else if (currentState == HIGH && lastState == LOW) {
        lastState = currentState;
        blocked = false;
    } else {
        blocked = false;
    }
    return blocked;
}

//使LED閃爍1下
void blinkLed(int pin) {
    digitalWrite(pin, HIGH);
    delay(50);
    digitalWrite(pin, LOW);
    delay(50);
    digitalWrite(pin, HIGH);
}

void setup() {
    pinMode(LED, OUTPUT);      
    pinMode(START, INPUT);
    pinMode(STOP, INPUT);
    digitalWrite(LED, HIGH);
    Serial.begin(BAUDRATE);
}

void loop(){
    unsigned long t1, t2;
    lastState = digitalRead(START);

    while(1) {
        if(updateSensor(START) == true) {
            t1 = millis();
            blinkLed(LED);
            break;
        }
    }

    lastState = digitalRead(STOP);

    while(1) {
        if(updateSensor(STOP) == true) {
            t2 = millis();
            blinkLed(LED);
            break;
        }
    }

    unsigned long dt = t2 - t1;
    Serial.print("t1 = ");
    Serial.println(t1);
    Serial.print("t2 = ");
    Serial.println(t2);
    Serial.print("dt = ");
    Serial.println(dt);
}

程式的流程大致可分為
  1. 設定接腳編號、鮑率、全域變數
  2. 設定自定函式updateSensor、blinkLed
  3. 在 void setup() 中設定接腳模式、開啟連接埠
  4. 在 void loop()
    1. 設定區域變數t1、t2
    2. 讀取啟動組接腳狀態
    3. 在第1個 while 迴圈中,直到啟動組第1次被擋住時記錄時間t1、使LED閃爍1次
    4. 讀取停止組接腳的狀態
    5. 在第2個 while 迴圈中,直到停止組第1次被擋住時記錄時間t2、使LED閃爍1次
    6. 印出測量結果



HackMD 版本連結:https://hackmd.io/@yizhewang/S16-Ba6V4

4 則留言:

  1. 請問第一步若讀取值有遮蔽物時並沒有差很多,有可能是什麼樣的問題呢?
    電阻的瓦數會有差別嗎?

    回覆刪除
  2. 1. 我猜你的讀數是一直都很小吧?這可能是因為發射器和接收器沒有對準,也有可能是因為 LED 接反,最好能看到裝置的照片和讀數,這樣會比較好判斷。

    2. 我們使用開發板上的 5V 電源,電阻器的電阻值也很高,如果計算電功率 P = V^2 / R,對 560 Ω 的電阻器而言,功率約為 0.045 W,我測試時使用 1/4 W 的電阻器,不會燒起來的。

    回覆刪除
  3. 不好意思,我問一下紅外線發射的led燈會一直亮著
    在遮蔽的時候無法熄滅,求解有沒有led熄滅的方法

    回覆刪除
  4. 是按照文章裡的電路圖測試嗎?這裡的發射端一直保持在高電位的發射狀態,並不會熄滅。當發射、接收端兩者之間有物體擋住時,接收端無法收到紅外線,進而改變電阻值,再利用分壓電路偵測到電壓變化。

    回覆刪除