10907 電資營 - 感應鬧鈴

大家手上都拿到一個實驗包,裡面主要包括一片 Arduino Uno 開發板,還有一塊麵包板上面插了許多元件

總計大概有下面這些東西,今天我們的軟體操控實驗就是基於這些裝置

基本元件

  1. 實驗麵包板
  2. 按鈕開關
  3. 發光二極體 LED
  4. 無源蜂鳴器
  5. 七段共陰極顯示器
  6. 超音波模組

單晶片 (微控制器, MCU) ATMega 328P

在萬物聯網的時代裡,我們要透過單晶片來擷取資料並且操控這些電子設備,這個單晶片就是在 Arduino UNO 板子中央那顆小小黑色的晶片

Arduino 開發版簡介

Uno

Mega

Due

Nano

Pro mini

有趣的應用

開發軟體

界面

兩個重要按鈕:編譯(驗證)&上傳

實驗步驟:

1. 開啟 Arduino IDE

  1. 將 Arduino Uno 用 USB 連接線連上電腦

  2. 選擇 工具/開發板/Arduino Uno

  3. 選擇 工具/序列埠/COM??

  4. 選取 檔案/範例/Basic/Blink

  5. 驗證 (編譯)

  6. 上傳

上傳有點慢,需要一點點時間,如果失敗的話,調整一下 COM??,如果設定都是對的,別擔心,有時候重新上傳一次就會成功了

  1. 板子上除了有一個恆亮的 LED 之外,另一個 LED 一明一滅的,一秒鐘亮,一秒鐘暗,恭喜你成功了!! 整個程序到這裡如果一直出現問題,先找隊輔幫忙解決
void setup() { //這裡面要放最一開始執行的程式碼 pinMode(LED_BUILTIN, OUTPUT); } void loop() { //此處程式碼會不斷地執行 digitalWrite(LED_BUILTIN, HIGH); // 點亮 LED(HIGH就是亮) delay(1000); //讓它維持亮1秒(=1000毫秒) digitalWrite(LED_BUILTIN, LOW); // LED暗了(LOW就是暗) delay(1000); //讓它維持暗著1秒(=1000毫秒) }

電路檢查

接下來很快地檢查麵包板上的電路,請先將超音波模組拔下,看到下面這張照片,從左邊開始很快地核對這些接線,最後再將超音波模組插回圖裡面紅色方格:

實驗麵包板

如下圖麵包板是不需要經由焊接過程,就可以將實驗所使用的電子元件以杜邦線連接,進而量測電路的特性,以驗證電路功能是否正常的實驗室工具。


接下來所有的實驗,我們都不需要把麵包板上的元件拔下來,只需要用杜邦線連接 Arduino Uno 和 麵包板上的元件。

實驗 1: 按鈕開關與LED

實驗步驟:

  1. 拔掉 USB 與電腦的連線
  2. 連接5V (下圖中紅線,由麵包板火線+接到UNO 5V)
  3. 連接地線 (下圖中黑線,由麵包板地線-接到UNO GND)
  4. 連接 LED 正極(麵包板12) 到 UNO輸出 13 (下圖中黃線)
  5. 連接 按鈕開關(麵包板30) 到 UNO類比輸入 A0 (下圖中綠線)

  1. 插上 USB 與電腦連線
  2. IDE 中選取 檔案/新增,複製下列程式到視窗中
  3. 點選 上傳 按鈕,存檔,上傳完畢
  4. 程式已經開始執行,按下按鍵 LED 點亮,放開按鍵 LED 熄滅
void setup() { pinMode(A0, INPUT); pinMode(13, OUTPUT); } void loop() { int switchStatus; switchStatus = digitalRead(A0); // 讀取按鍵電壓 if (switchStatus==HIGH) // 按鍵被按下 digitalWrite(13, HIGH); // LED 點亮 else digitalWrite(13, LOW); }

當你按下按鍵,發生了什麼事?

UNO 輸入輸出腳位控制

認識 if 敘述

if (switchStatus==HIGH) // 如果讀取的按鍵狀態是高電位,按鍵被按下
    digitalWrite(13, HIGH); // 點亮 LED

想想看

怎樣修改程式功能變成「按鍵放開時 LED 長亮,按鍵按下時 LED 熄滅

程式碼
void setup() { pinMode(A0, INPUT); pinMode(13, OUTPUT); } void loop() { int switchStatus; switchStatus = digitalRead(A0); if (switchStatus==HIGH) digitalWrite(13, LOW); else digitalWrite(13, HIGH); }

資工領域特別強調軟體的操控模型

實驗步驟:

  1. 拔掉 USB 與電腦的連線
  2. 將 綠色 LED 接到 UNO 輸出 12
  3. 將 透明 LED 接到 UNO 輸出 11
  4. 插上 USB 與電腦連線
  5. IDE 中選取 檔案/新增,複製下列程式到視窗中
  6. 點選 上傳 按鈕,存檔,上傳完畢
  7. 程式已經開始執行,三個 LED 同時以不同頻率計時閃動
const int LEDYellow = 13; const int LEDGreen = 12; const int LEDRed = 11; void blinkingLED() { int i; const int duration[3] = {700, 351, 571}, onDuration[3]={345, 147, 367}; const int outputPin[3] = {LEDRed, LEDYellow, LEDGreen}; static unsigned long lastOnTime[3]={0,0,0}; unsigned long currentTime = millis(); for (i=0; i<3; i++) if((currentTime - lastOnTime[i]) > duration[i]) { digitalWrite(outputPin[i], HIGH); lastOnTime[i] = currentTime; } else if ((currentTime - lastOnTime[i]) > onDuration[i]) { digitalWrite(outputPin[i], LOW); } } void setup() { pinMode(LEDRed, OUTPUT); pinMode(LEDYellow, OUTPUT); pinMode(LEDGreen, OUTPUT); } void loop() { blinkingLED(); delay(10); }
解釋與心得

關鍵在於「不要再用 delay() 這種佔著資源卻躺著什麼事都不做的函式了」,用 millis() 去檢查現在的時間,時間到了該做什麼事就做什麼事


實驗 2: 蜂鳴器

實驗步驟:

  1. 拔掉 USB 與電腦的連線
  2. 拔掉 黃色 LED 接到 UNO 輸出 13
  3. 拔掉 綠色 LED 接到 UNO 輸出 12
  4. 拔掉 透明 LED 接到 UNO 輸出 11
  5. 把 蜂鳴器一端(麵包板22) 接到 UNO 輸出 13
  6. 插上 USB 與電腦連線
  7. IDE 中選取 檔案/新增,複製下列程式到視窗中
  8. 點選 上傳 按鈕,存檔,上傳完畢
  9. 程式已經開始執行,蜂鳴器長響三次後靜音
const int buzzer = 13; int j=0; void setup() { pinMode(buzzer,OUTPUT); } void loop() { int i; for (i=0; i<10&&j<3; i++) { tone(buzzer,1000); delay(50); tone(buzzer,500); delay(50); } noTone(buzzer); delay(2000); j++; }

無源蜂鳴器

無源蜂鳴器的優點是:

  1. 便宜
  2. 聲音頻率可控,可以做出音階 “Do Re Mi Fa Sol La Si” 的效果
  3. 在一些特例中,可以和 LED 共用一個控制腳

常用操控蜂鳴器函式

tone(腳位,頻率)
ex: tone(13,1000); //透過13腳輸出頻率1000的聲音

noTone(腳位)
ex: noTone(13); //13腳無聲

int i;
for (i=0; i<10; i++) { (執行10次)
   想要重複執行的程式敘述
}

音階

 66, 131, 262, (523), 1046 // C Do
 74, 147, 294, (587), 1175 // D Re
 83, 165, 330, (659), 1318 // E Mi
 88, 175, 349, (698), 1397 // F Fa
 98, 196, 392, (784), 1568 // G So
110, 220, 440, (880), 1760 // A La
124, 247, 494, (988), 1976 // B Si

來個 Do

tone(13,523);
delay(50)

Fa 呢? So 呢?

程式片段
Fa:
tone(13,523);
delay(50);

So:
tone(13,523);
delay(50);

寫一小段旋律吧

const int buzzer = 13; int j = 0; void setup() { pinMode(buzzer,OUTPUT); } void loop() { int i; for (i=0; i<3&&j<2; i++) { tone(buzzer,784); delay(333); noTone(buzzer); delay(10); tone(buzzer,659); delay(333); noTone(buzzer); delay(10); tone(buzzer,659); delay(333); noTone(buzzer); delay(150); tone(buzzer,698); delay(333); noTone(buzzer); delay(10); tone(buzzer,587); delay(333); noTone(buzzer); delay(10); tone(buzzer,587); delay(333); noTone(buzzer); delay(150); tone(buzzer,523); delay(333); noTone(buzzer); delay(10); tone(buzzer,587); delay(333); noTone(buzzer); delay(10); tone(buzzer,659); delay(333); noTone(buzzer); delay(10); tone(buzzer,698); delay(333); noTone(buzzer); delay(10); tone(buzzer,784); delay(333); noTone(buzzer); delay(10); tone(buzzer,784); delay(333); noTone(buzzer); delay(10); tone(buzzer,784); delay(333); noTone(buzzer); delay(333); } noTone(buzzer); delay(2000); j++; }

額、再一小段旋律???

小祕密

哈哈,哪是祕密呀!!
Google 大神
https://dragaosemchama.com/en/2019/02/songs-for-arduino/
https://github.com/robsoncouto/arduino-songs


實驗 3: 七段顯示器

實驗步驟:

  1. 拔掉 USB 與電腦的連線
  2. 參考下圖,接上共陰極七段顯示器的排線到 UNO 輸出 2,3,4,5,6,7,8 腳
  3. 插上 USB 與電腦連線
  4. IDE 中選取 檔案/新增,複製下列程式到視窗中
  5. 點選 上傳 按鈕,存檔,上傳完畢
  6. 程式已經開始執行,七段顯示器顯示 9,8,7,6,5,4,3,2,1,0 休息四秒鐘重複
byte seven_seg_digits[10][7] = { { 1,1,1,1,1,1,0 }, // = 0 { 0,1,1,0,0,0,0 }, // = 1 { 1,1,0,1,1,0,1 }, // = 2 { 1,1,1,1,0,0,1 }, // = 3 { 0,1,1,0,0,1,1 }, // = 4 { 1,0,1,1,0,1,1 }, // = 5 { 1,0,1,1,1,1,1 }, // = 6 { 1,1,1,0,0,0,0 }, // = 7 { 1,1,1,1,1,1,1 }, // = 8 { 1,1,1,0,0,1,1 } // = 9 }; void setup() { pinMode(2, OUTPUT); pinMode(3, OUTPUT); pinMode(4, OUTPUT); pinMode(5, OUTPUT); pinMode(6, OUTPUT); pinMode(7, OUTPUT); pinMode(8, OUTPUT); pinMode(9, OUTPUT); digitalWrite(9, 0); // 關閉小數點 } // 在七段顯示器上顯示指定的一個數字 void sevenSegWrite(byte digit) { byte seg; for (seg=0; seg<7; ++seg) digitalWrite(seg+2, seven_seg_digits[digit][seg]); } void loop() { byte digit; for (digit=10; digit>0; --digit) { sevenSegWrite(digit-1); delay(1000); } delay(4000); // 暫停 4 秒鐘 }

七段顯示器

實際上只是 8 顆 LED 排列整齊

重要控制函式

sevenSegWrite(digit)

例如:sevenSegWrite(8); // 顯示數字 8

陣列

想要10個變數?
byte x1, x2, x3, x4, x5, x6, x7, x8, x9, x10;

你該試試「陣列」
byte x[10]; // 一行搞定

陣列有什麼功能特別強大嗎?

void sevenSegWrite(byte digit) {
  byte seg;
  for (seg=0; seg<7; ++seg)
    digitalWrite(seg+2, seven_seg_digits[digit][seg]);
}

沒有陣列時

現在讓七段往上數

程式片段
for (byte digit=0; digit<10; ++digit) {
  sevenSegWrite(digit);
  delay(1000);
}

現在讓七段只數奇數

程式片段
for (byte digit=10; digit>0; digit=digit-2) {
  sevenSegWrite(digit-1);
  delay(1000);
}

實驗 4: 超音波感測器

超音波感測模組 HC-SR04 操作原理


上面是發射器 (Transmitter),會發出 40 kHz 的聲波,由於這個聲波的頻率超過人類可聽見的 20 kHz,因此被稱為超音波。下面是接收器 (Receiver),可以接收超音波。它可以感測的距離為 2cm 到 400cm,感應角度為 15 度。

如上圖,Trig 腳送進至少維持 10 微秒以上的高位準訊號,便能觸發模組中的超音波發射器送出 8 個連續的 40KHz 超音波脈衝,接收器收到反射波後便會在 Echo 腳輸出一個與量測距離成正比的高位準脈衝,此高位準脈衝上緣可以看成超音波開始發射時間;而下緣則是接收到反射波的時間,所以整個高位準脈衝的寬度就是超音波往返的總時間,要特別注意的地方是被測物體最好大於 0.5 平方公尺,否則反射的聲音沒有辦法接收到,而 Trig 時間間隔最好大於 60ms,避免 Trig 與 Echo 互相干擾。

量測物體距離

實驗步驟:

  1. 拔掉 USB 與電腦的連線
  2. 把超音波模組的 Echo(麵包板21) 接到 UNO 輸出 11
  3. 把超音波模組的 Trig(麵包板20) 接到 UNO 輸出 12
  4. 插上 USB 與電腦連線
  5. IDE 中選取 檔案/新增,複製下列程式到視窗中
  6. 點選 上傳 按鈕,存檔,上傳完畢
  7. 程式已經開始執行,如下圖在 IDE 右上角點選序列埠監控視窗
  8. 試試看拿一本書放在超音波感測器前方,看到監控視窗中的距離了嗎?
int trigPin = 12; // 超音波 Trig Pin int echoPin = 11; // 超音波 Echo Pin long duration, cm, inches; void setup() { Serial.begin(9600); // 設定序列埠監控視窗傳輸速率 pinMode(trigPin, OUTPUT); // 定義輸出腳位 pinMode(echoPin, INPUT); // 定義輸入腳位 } void loop() { digitalWrite(trigPin, LOW); // 輸出 Trig 低電位,持續 5 微秒 delayMicroseconds(5); digitalWrite(trigPin, HIGH); // 輸出 Trig 高電位,持續 10 微秒 delayMicroseconds(10); digitalWrite(trigPin, LOW); // 回復 Trig 低電位 pinMode(echoPin, INPUT); // 讀取 echo 的電位 duration = pulseIn(echoPin, HIGH); // 收到高電位時的時間 cm = (duration/2) / 29.1; // 將時間換算成距離 cm 或 inch inches = (duration/2) / 74; Serial.print("Distance : "); Serial.print(inches); Serial.print("in, "); Serial.print(cm); Serial.print("cm"); Serial.println(); delay(250); }

倒車雷達

試試看常用到的倒車雷達,越接近時蜂鳴器越急促

int buzzer = 13; // 蜂鳴器 int trigPin = 12; // 超音波 Trig Pin int echoPin = 11; // 超音波 Echo Pin long duration, cm, inches; void setup() { Serial.begin(9600); // 設定序列埠監控視窗傳輸速率 pinMode(echoPin, INPUT); // 定義輸入腳位 pinMode(trigPin, OUTPUT); // 定義輸出腳位 pinMode(buzzer, OUTPUT); } void loop() { digitalWrite(trigPin, LOW); // 輸出 Trig 低電位,持續 5 微秒 delayMicroseconds(5); digitalWrite(trigPin, HIGH); // 輸出 Trig 高電位,持續 10 微秒 delayMicroseconds(10); digitalWrite(trigPin, LOW); // 回復 Trig 低電位 pinMode(echoPin, INPUT); // 讀取 echo 的電位 duration = pulseIn(echoPin, HIGH); // 收到高電位時的時間 cm = (duration/2) / 29.1; // 將時間換算成距離 cm 或 inch inches = (duration/2) / 74; Serial.print("Distance : "); Serial.print(inches); Serial.print("in, "); Serial.print(cm); Serial.println("cm"); tone(buzzer,500); delay(200); noTone(buzzer); delay(cm*30); }

距離控制音階

讓我們用距離來控制蜂鳴器的頻率

int buzzer = 13; // 蜂鳴器 int trigPin = 12; // 超音波 Trig int echoPin = 11; // 超音波 Echo long duration, cm, inches, step; int freq[] = {523, 587, 659, 698, 784, 880, 988}; void setup() { Serial.begin(9600); // 設定序列埠監控視窗傳輸速率 pinMode(echoPin, INPUT); // 定義輸入腳位 pinMode(trigPin, OUTPUT); // 定義輸出腳位 pinMode(buzzer, OUTPUT); } void loop() { digitalWrite(trigPin, LOW); // 輸出 Trig 低電位,持續 5 微秒 delayMicroseconds(5); digitalWrite(trigPin, HIGH); // 輸出 Trig 高電位,持續 10 微秒 delayMicroseconds(10); digitalWrite(trigPin, LOW); // 回復 Trig 低電位 pinMode(echoPin, INPUT); // 讀取 echo 的電位 duration = pulseIn(echoPin, HIGH); // 收到高電位時的時間 cm = (duration/2) / 29.1; // 將時間換算成距離 cm 或 inch inches = (duration/2) / 74; Serial.print("Distance : "); Serial.print(inches); Serial.print("in, "); Serial.print(cm); Serial.println("cm"); step = 10; for (int i=0; i<7; i++) { if (cm/step==i) tone(buzzer, freq[i]); delay(250); }

綜合實驗: 感應鬧鈴

這個實驗裡,你可以按著按鍵來增加 LED 顯示的數字,然後只要在超音波感測器前揮揮手,可以開始倒數計時或是停下蜂鳴器的聲音,試看看吧!

// buttons // A0: increase the LED count // A1: start the counting down / break the music // note: cannot pause the counting down (single tasking) // aa // f b // gg // e c // dd // a b c d e f g byte seven_seg_digits[10][7] = { { 1,1,1,1,1,1,0 }, // = 0 { 0,1,1,0,0,0,0 }, // = 1 { 1,1,0,1,1,0,1 }, // = 2 { 1,1,1,1,0,0,1 }, // = 3 { 0,1,1,0,0,1,1 }, // = 4 { 1,0,1,1,0,1,1 }, // = 5 { 1,0,1,1,1,1,1 }, // = 6 { 1,1,1,0,0,0,0 }, // = 7 { 1,1,1,1,1,1,1 }, // = 8 { 1,1,1,0,0,1,1 } // = 9 }; const int buzzer = 13; const int sevenSegA = 2; // seg a int trigPin = 12; //Trig Pin int echoPin = 11; //Echo Pin long duration, cm, inches; #define DEBOUNCE_DELAY 200 #define TEMPO 333 const int toneTable[7][5]={ // 5 octaves { 66, 131, 262, 523, 1046}, // C Do { 74, 147, 294, 587, 1175}, // D Re { 83, 165, 330, 659, 1318}, // E Mi { 88, 175, 349, 698, 1397}, // F Fa { 98, 196, 392, 784, 1568}, // G So {110, 220, 440, 880, 1760}, // A La {124, 247, 494, 988, 1976} // B Si }; char beeTone[]="GEEFDDCDEFGGGGEEFDDCEGGEDDDDDEFEEEEEFGGEEFDDCEGGC"; char starTone[]="CCGGAAGFFEEDDCGGFFEEDGGFFEEDCCGGAAGFFEEDDC"; int beeBeat[]={ 1,1,2, 1,1,2, 1,1,1,1,1,1,2, 1,1,2, 1,1,2, 1,1,1,1,4, 1,1,1,1,1,1,2, 1,1,1,1,1,1,2, 1,1,2, 1,1,2, 1,1,1,1,4 }; int starBeat[]={ 1,1,1,1,1,1,2, 1,1,1,1,1,1,2, 1,1,1,1,1,1,2, 1,1,1,1,1,1,2, 1,1,1,1,1,1,2, 1,1,1,1,1,1,2 }; // (symbol-'C'+7)%7 int getTone(char symbol) { static const char *toneName="CDEFGAB"; for (int i=0; i<7; i++) { if (toneName[i]==symbol) { return i; } } return 0; } void measuringDistance(){ digitalWrite(trigPin, LOW); delayMicroseconds(5); digitalWrite(trigPin, HIGH); // 給 Trig 高電位,持續 10微秒 delayMicroseconds(10); digitalWrite(trigPin, LOW); pinMode(echoPin, INPUT); // 讀取 echo 的電位 duration = pulseIn(echoPin, HIGH); // 收到高電位時的時間 cm = (duration/2) / 29.1; // 將時間換算成距離 cm 或 inch inches = (duration/2) / 74; Serial.print("Distance : "); Serial.print(inches); Serial.print("in, "); Serial.print(cm); Serial.print("cm"); Serial.println(); } void playMusic() { int i, length, toneNo, duration; while(1) { // bee length = sizeof(beeTone)-1; for (i=0; i<length; i++) { toneNo = getTone(beeTone[i]); duration = beeBeat[i]*TEMPO; tone(buzzer,toneTable[toneNo][3]); delay(duration); noTone(buzzer); // separate each sound delay(15); measuringDistance(); if(cm <= 10) return; // breaking the music } delay(2000); if(cm <= 10) return; // star length = sizeof(starTone)-1; for (i=0; i<length; i++) { toneNo = getTone(starTone[i]); duration = starBeat[i]*TEMPO; tone(buzzer,toneTable[toneNo][3]); delay(duration); noTone(buzzer); // separate each sound delay(15); measuringDistance(); if(cm <= 10) return; } delay(2000); if(cm <= 10) return; } } // 在七段顯示器上顯示指定的 0~9 數字 void sevenSegWrite(int digit) { int pin = sevenSegA; for (int seg=0; seg<7; ++seg) { digitalWrite(pin, seven_seg_digits[digit][seg]); ++pin; } } static int ledCounter; static int countDownActivated; void debounceButtonInputA0() { static unsigned long lastDebounceTime=0; unsigned long currentTime = millis(); if((currentTime - lastDebounceTime) > DEBOUNCE_DELAY) { lastDebounceTime = currentTime; if(!countDownActivated) { // this is almost useless in single tasking ledCounter++; } } } void debounceButtonInputA1() { static unsigned long lastDebounceTime=0; unsigned long currentTime = millis(); if((currentTime - lastDebounceTime) > DEBOUNCE_DELAY) { lastDebounceTime = currentTime; if (ledCounter>0) countDownActivated = 1; } } // executed @ powerup or every time the reset button is pressed void setup() { pinMode(buzzer, OUTPUT); noTone(buzzer); pinMode(A0, INPUT); pinMode(A1, INPUT); ledCounter = 0; countDownActivated = 0; for (int seg=0; seg<7; ++seg) { pinMode(sevenSegA+seg, OUTPUT); } Serial.begin (9600); // Serial Port begin pinMode(trigPin, OUTPUT); //Define inputs and outputs pinMode(echoPin, INPUT); // pinMode(buzzer,OUTPUT); } void loop() { measuringDistance(); if(digitalRead(A0)) { debounceButtonInputA0(); } if(cm <= 10) { debounceButtonInputA1(); } if(countDownActivated==1) { delay(1000); ledCounter--; if(ledCounter==-1) { playMusic(); ledCounter = 0; countDownActivated = 0; } } if(ledCounter>9) { ledCounter = 0; } sevenSegWrite(ledCounter); delay(500); }

微觀世界的彈跳現象

這裡的按鈕有點怪?

if (digitalRead(A0))
{
  updateLed(0);
}

為什麼不寫成下面這樣就好?

digitalRead(A0);

原因就是彈跳現象

按鈕開關按下時會在極短時間內產生多次開合現象(稱為機械彈跳)

而使用控制板偵測腳位狀態時會讀取到多次的HIGH、LOW的切換 但實際上使用者只按下一次開關,因此會造成程式錯誤 所以設計者需加入「防彈跳」(Debounce)設計來避免此現象

unsigned long currentTime = millis();
if ((currentTime - lastDebounceTime) > DEBOUNCE_DELAY) {
  //程式碼內容
  lastDebounceTime = currentTime;
}

如果你還會有機會使用 Arduino 的話,下面的連結和小抄整理是有用的