都花錢買了一堆零件,那肯定要用用看吧

板子上的模組

首先,還是看到手上的這塊 ESP32 開發板。 它上面除了整個微控制器的核心,還有許多已經內建的零件。我們先來用用看這些吧。

內建 LED

內建 LED 應該是大家最熟悉的部份吧,畢竟它最常被我們拿來 Blink 測試板子有沒有問題。

提示

各型的板子的內建 LED 所在的 GPIO 位置可能都不一樣, BUILTIN_LED 的定義也就都不盡相同。

如果預設沒有定義好的 BUILTIN_LED,就請你去找找你的開發板的說明文檔囉。

c
void loop() {
  digitalWrite(BUILTIN_LED, HIGH); // 設成高電位(亮)
  delay(1000); // 等待 1 秒
  digitalWrite(BUILTIN_LED, LOW); // 設成低電位(暗)
  delay(1000); // 等待 1 秒
}

不過 LED 可不是只有暗或亮兩個選項。它有這麼多種亮度可以發出來,不用就太可惜了吧? 透過 PWM 訊號,調整 Duty Cycle,LED 的亮度就會改變。

pwm signal
PWM 訊號——透過調整 Duty Cycle(高電位的時間占比),可以改變 LED 的亮度。

在 Arduino IDE 裡發出不同 Duty Cycle 的訊號也很簡單:

c
analogWrite(BUILTIN_LED, VALUE); // VALUE 為數值,範圍 0 ~ 255

這樣 LED 就可以顯示不同亮度了!透過亮度,就很容易能表示一個數值(像是時間、距離、溫度)的大小了吧!

我順便做了個(類)呼吸燈來用:)

c
int brightness = 0;

void loop() {
  while (brightness <= 255) {
    analogWrite(LED_BUILTIN, brightness++);
    delay(10);
  }
  while (brightness >= 0) {
    analogWrite(LED_BUILTIN, brightness--);
    delay(10);
  }
}

按鈕

我們知道板子上的 EN 按鈕是重置用的,可以讓板子的程式重新開始;至於 BOOT,是用來讓開發版進入 Bootloader,進行程式或韌體的上傳。 不過有些板子在上傳程式時不需要按著 BOOT,因為每張開發版上有的零件不大一樣。

重點是,BOOT 按鈕是連接到 GPIO0,意味著我們可以透過程式讀取到它的狀態,作為輸入(Input)的管道。

重要

按鈕在釋放時是高電位HIGH)、按下時是低電位LOW)。

這樣,我們就能寫個簡單的程式:

c
void loop() {
  if (digitalRead(0) == LOW) { // 如果 GPIO0 (BOOT 按鈕)是低電位(按著)
    digitalWrite(LED_BUILTIN, HIGH); // 就對 LED 送出高電位(亮)
  } else {
    digitalWrite(LED_BUILTIN, LOW); // 按鈕釋放就送出低電位(暗)
  }
  delay(10);
}

WiFi

參考:

WiFi 和藍芽一樣,底層邏輯已經有函式庫幫忙寫好了,我們只要知道如何調用它們提供的 API 就行了。 那就來看看這個 WiFi 函式庫怎麼用:

  1. 連接 WiFi 網路
c
#include <WiFi.h> // 調用 WiFi.h (函式庫的標頭檔)
const char *ssid     = "********"; // ssid: 網路名稱
const char *password = "********"; // password: 網路密碼

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

  // 連接 WiFi
  WiFi.begin(ssid, password);
  Serial.printf("Connecting to %s", ssid);

  // 檢查 WiFi 狀態,如果未連接就執行迴圈
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("\n--------------------");

  // 讀取 IP 位置
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());

  Serial.println("--------------------");

  // 顯示 WiFi 連線狀態資訊:工作模式、Channel、SSID、Passphrase、BSSID
  Serial.println("WiFi status:");
  WiFi.printDiag(Serial);
}

void loop() { }
  1. 設定 WiFi 模式、掃描 WiFi 網路

這邊 WiFi 有 3 種模式,並用 WIFI.mode() 設定:

模式代號WiFi 模式說明
WIFI_APAccess Point (AP)熱點,可以讓其他設備接入。
WIFI_STAStation (STA)無線終端模式,可以連接其他 WiFi 網路。
WIFI_AP_STAAP+STA兩個模式並存。
c
#include "WiFi.h"

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

  // 設定為連接(無線終端)模式
  WiFi.mode(WIFI_STA);
}

void loop() {
  Serial.println("scan start");

  // WiFi.scanNetworks() 回傳找到的 WiFi 網路數目
  int n = WiFi.scanNetworks();
  Serial.println("scan done");

  if (n == 0) {
      Serial.println("no networks found");
  } else {
    Serial.print(n);
    Serial.println(" networks found");

    for (int i = 0; i < n; ++i) {
      // 印出找到的 WiFi 網路的資訊
      Serial.print(i + 1);
      Serial.print(": ");
      Serial.print(WiFi.SSID(i)); // 網路名
      Serial.print(" (");
      Serial.print(WiFi.RSSI(i)); // 強度
      Serial.print(")");
      Serial.println((WiFi.encryptionType(i) == WIFI_AUTH_OPEN)?" ":"*"); // 加密狀態
      delay(10);
    }
  }
  Serial.println("");

  // 等一下再掃一次
  delay(5000);
}

網路模組還有其他很多功能,像是網路連接的 TCP、UDP 協定,甚至可以做 HTTP、HTTPS、Server、DNS Server 等等, 雖然那些並不是 WiFi 的範圍,但也是 Networking 的一環,不過這堆東西我就講不完啦

藍芽

參考:https://shop.mirotek.com.tw/iot/esp32-start-19/

藍芽的話,我們也是使用內建的函式庫 BluetoothSerial,接下來要做的就是連接與資料傳輸了。

  1. 初始化SerialBT.begin("藍芽裝置名字")
  2. 讀文字SerialBT.read()
  3. 寫文字SerialBT.write("內容")

所以,以下就是個從電腦 Serial Console 讀取文字、在送到藍芽的程式:

c
#include <BluetoothSerial.h>
// 創建 BluetoothSerial 的實例(instance)
BluetoothSerial SerialBT;

void setup() {
  Serial.begin(115200);
  SerialBT.begin("ESP32_BT"); // 藍牙顯示名稱,可自行更改,需避免與他人重複命名
}

void loop() {
  // 確認有內容讀取
  if (Serial.available()) {
    // 在 SerialBT 寫入 Serial 讀取的東西
    SerialBT.write(Serial.read());
  }

  // 確認有內容讀取
  if (SerialBT.available()) {
    // 在 Serial 寫入 SerialBT 讀取的東西
    Serial.println(SerialBT.readString());
  }
  delay(50);
}
Note

使用「Arduino Bluetooth Control」App 可以透過藍芽和開發板傳送文字。

arduino Bluetooth control app on play store

詳細使用方式詳見:https://shop.mirotek.com.tw/iot/esp32-start-19/


接下來,就來看看有什麼其他零件可以接上吧。

但首先,我們有這麼多零件,要怎麼把它們都接上去啊? 尤其在 3V3 和 GND 腳位少的開發版上,直接把線插上針腳就沒辦法一次使用好幾個零件。 但我們測試用的東西又不可能直接焊接上去。

所以,我們需要另一塊板子幫我們連接電線,又不能直接黏死。這就是麵包板的用處啦

麵包板

麵包板為電子電路設計中常用的一種基底。 與印刷電路板不同,麵包板無需焊接或損壞電路軌道,因此可以反覆使用。 麵包板非常適合用於打造原型產品,在學生和技術教育領域非常受歡迎。 ——麵包板 - 維基百科

總之,就是讓你簡單接線的板子。因為它已經在裡面幫你接好了。

breadboard wires
麵包板的連接線路。來源:iT 邦幫忙

接著把 ESP32 放到麵包板上,大概會長這樣:

esp32 on breadboard
把 ESP32 兩邊的針腳插在麵包板的兩邊,這樣電路才不會相連。

不過看到上面的圖你可能會發現,ESP32 有一邊的針腳剛好在麵包板的最後一排,這應該就是尺寸規格的問題啦。 那邊接線大概就接不到了,所以 3V3 的針腳確認一下要在接得出來的地方。

esp32 on breadboard with wires
從 ESP32 把線接出來,電源(3V3)通常會接到 `+` 的直條(紅色)、接地就接到 `-` 那條(黑色)。

剩下的針腳就一樣是自由運用了!

LED

一樣,我們先從最簡單的開始。

上面我們已經有提到內建 LED 的使用,另外的 LED 接上去也不會差太多。 我們就簡單帶過吧

  1. 接線
注意

LED 的長腳(正極)要接到 GPIO、短腳(負極)要接到接地!

不然可能會損害到 LED 喔。

led connected on a breadboard

  1. Blink

這個大家應該也看過好幾次了,我們就速速直接照抄

c
// 注意:記得自己定義你的 LED_PIN 針腳,或直接寫在下面程式!
void setup() {
  pinMode(LED_PIN, OUTPUT);
}

void loop() {
  digitalWrite(LED_PIN, HIGH);
  delay(1000);
  digitalWrite(LED_PIN, LOW);
  delay(1000);
}
  1. 變換亮度

這個在上面也提過了,只要用 PWM 訊號,調整 Duty Cycle 就可以改變亮度了:

c
int brightness = 0;

void loop() {
  while (brightness <= 255) {
    analogWrite(LED_PIN, brightness++);
    delay(10);
  }
  while (brightness >= 0) {
    analogWrite(LED_PIN, brightness--);
    delay(10);
  }
}

按鈕

按鈕應該是最常見、也是最直覺的輸入裝置了。 他的構造也很簡單,就是兩條電線、按鈕按下去就會接通。

  1. 接線

button connect on a breadboard

提示

電源的部份也可以接在接地(GND)上。這會反應在你讀取的電位高低上:

按鈕電源釋放時的電位按壓時的電位連接電阻
VCC下拉(pull-down)
GND上拉(pull-up)

上拉或下拉電阻可以讓零件沒有訊號(高阻抗)的時候, 保證讀取到的訊號是高或低電位。 在上面的表格裡,可以看到「釋放時的電位」就是高阻抗的時候, 這時下拉電阻就可以保證讀取到的訊號是 LOW。

而上拉或下拉電阻在開發板裡面已經有內建,所以在指定 pinMode() 的時候, 只要指定 INPUT_PULLUPINPUT_PULLDOWN 就好了喔。

  1. 按壓時觸發 LED

接上按鈕,只要讀到高或低的觸發電位,就讓 LED 亮起來!

c
// 注意上面的表格,設定要設對,讀取也要讀對!
#define BUTTON_PIN 5
#define LED_PIN 23

void setup() {
  pinMode(BUTTON_PIN, INPUT_PULLUP);
  pinMode(LED_PIN, OUTPUT);
}

void loop() {
  // 我們用了上拉電阻(INPUT_PULLUP),所以按鈕按下時是 LOW 訊號!
  if (digitalRead(BUTTON_PIN) == LOW) {
    digitalWrite(LED_PIN, HIGH);
  } else {
    digitalWrite(LED_PIN, LOW);
  }
  delay(100);
}

觸摸按鈕

參考:https://esp32io.com/tutorials/esp32-touch-sensor

  1. 接線

VCC 電源、GND 接地、SIG(訊號 signal)就是接 GPIO 了

  1. 按壓時觸發 LED

跟上面的按鈕差不多, 只要在 SIG 的針腳讀到高電位的訊號,那就是有按壓啦

c
#define TOUCH_PIN 19
#define LED_PIN 23

void setup() {
  pinMode(TOUCH_PIN, INPUT_PULLUP);
  pinMode(LED_PIN, OUTPUT);
}

void loop() {
  if (digitalRead(TOUCH_PIN) == HIGH) {
    digitalWrite(LED_PIN, HIGH);
  } else {
    digitalWrite(LED_PIN, LOW);
  }
  delay(100);
}

蜂鳴器

  1. 接線

VCC 電源、GND 接地、I/O 接 GPIO

  1. 發出聲音、調整音高

要發出聲音,肯定就得在 I/O 的針腳發送訊號。 這邊只要發出 PWM 訊號,就可以透過頻率讓它發出不同音高。

Arduino 有內建 tone() 函式,可以直接輸出不同頻率的 PWM 訊號:

c
tone(PIN, 262);
tone(PIN, 262, 200);
// tone(PIN, FREQUENCY [, DURATION(ms)])
// tone(針腳, 頻率 [, 時間(毫秒)])

noTone(PIN);
// 停止 `tone()` 函式開始的訊號。
// 因為 `tone()` 函式呼叫後會在程式運行的同時持續發送訊號,
// 如果你沒有指定時間(DURATION),你就需要 `noTone()` 停止這個訊號。
  1. 停止發聲

你應該會在 MH-FMD 蜂鳴器的板子上看到「低電平觸發」的字樣。 顧名思義,他在收到低電位的訊號時,反而會發出聲音。

如果要停止它的發聲,就給它 HIGH 訊號就好了:digitalWrite(PIN, HIGH)

超音波測距

  1. 接線

VCC 電源、GND 接地、TRIG 和 ECHO 接 GPIO

  1. 送出觸發訊號、讀取時間

簡單來講,給 TRIG pin 一個訊號(10 微秒的 HIGH),他就會發射超音播進行測距, 期間 ECHO pin 會給一個訊號代表超音波來回的時間,接著就能在程式裡運用這段時間、計算距離了。

Note

ECHO pin 的回傳時間除以 58 就是公分的距離了。

如果你好奇是怎麼算的:https://shop.mirotek.com.tw/iot/esp32-start-10/

c
#define TRIG_PIN 3 // 發出觸發訊號腳位
#define ECHO_PIN 2 // 接收測量結果訊號腳位

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

void loop() {
  unsigned long d = ping() / 58; // 計算距離(傳回時間 / 58 就是公分的距離)
  Serial.print(d);
  Serial.println("cm");
  delay(1000);
}

unsigned long ping() {
  digitalWrite(TRIG_PIN, HIGH); // 啟動超音波
  delayMicroseconds(10);  // 維持 10 微秒的 HIGH 訊號
  digitalWrite(TRIG_PIN, LOW);  // 關閉超音波
  return pulseIn(ECHO_PIN, HIGH); // 計算傳回時間
}

麥克風傳感器

  1. 接線

VCC (+) 接到電源、GND (G) 接到接地、OUT (DO 或 AO) 接到 GPIO

  1. 接收音訊

你會發現這邊的訊號分成數位和類比兩種,差別在這裡:

  • 數位(Digital)訊號:只要偵測到聲音,就會輸出 HIGH 高電位訊號。
  • 類比(Analog)訊號:聲音越大,訊號越強。

兩個就分別對應使用 digitalRead(PIN)analogRead(PIN) 讀取。

c
#define SOUND_PIN 19
#define LED_PIN 23

void setup() {
  pinMode(SOUND_PIN, INPUT);
  pinMode(LED_PIN, OUTPUT);
}

void loop() {
  if (analogRead(SOUND_PIN) >= 1024) {
    digitalWrite(LED_PIN, HIGH);
  } else {
    digitalWrite(LED_PIN, LOW);
  }
  delay(100);
}
Note

通常音訊的讀取會先取一段時間的樣,再使用這段時間內的訊號最大值、最小值或平均值。

溫度傳感器

  1. 接線

這個零件使用 I2C 進行資料傳輸,所以你會看到 SDA 和 SCL 兩個 I2C 的針腳。

電源就接電源、GND 接接地、SDA 和 SCL 分別接在你的開發板上對應的針腳、ALT 接在 GPIO。

ADD0 我們不會接在板子上,它是用來更變這個零件的位址。如果你有多個溫度傳感器,你才會需要做更改。

  1. 接收溫度資訊

已經有現成的函式庫可以幫忙處理與 TMP102 的溝通: sparkfun/SparkFun_TMP102_Arduino_Library

參考:https://learn.sparkfun.com/tutorials/tmp102-digital-temperature-sensor-hookup-guide/all

Note

以下的程式碼是由函式庫的範例修改與刪減。 如果想要看原本函式庫提供的程式碼,走這邊

c
#include <Wire.h> // 用來建立 I2C 通訊
#include <SparkFunTMP102.h> // 用來處理 TMP102 的訊號

const int ALERT_PIN = A3;

TMP102 sensor0;

void setup() {
  Serial.begin(115200);
  Wire.begin(); // 加入 I2C 匯流排

  pinMode(ALERT_PIN,INPUT);

  // TMP102 使用預設設定,地址為 0x48,使用上面的 Wire。
  // 成功連接返回 true,否則返回 false。
  if (!sensor0.begin()) {
    Serial.println("無法連接 TMP102。");
    while(1);
  }

  Serial.println("已連接到 TMP102!");
  delay(100);

  // 初始化 sensor0 設置
  // 這些設置會保存在傳感器中,即使斷電也會保留

  // 設定觸發警報前的連續故障數量。
  // 0-3: 0:1 次故障, 1:2 次故障, 2:4 次故障, 3:6 次故障。
  sensor0.setFault(0);  // 立即觸發警報

  // 設定警報的極性。(0:有效LOW, 1:有效HIGH)。
  sensor0.setAlertPolarity(1); // HIGH

  // 將傳感器設置為比較器模式 (0) 或中斷模式 (1)。
  sensor0.setAlertMode(0); // 比較器模式。

  // 設定轉換速率(傳感器獲取新讀數的速度)
  // 0:0.25Hz, 1:1Hz, 2:4Hz, 3:8Hz
  sensor0.setConversionRate(2);

  // 設置擴展模式。
  // 0:12 位元溫度 (-55°C 到 +128°C), 1:13 位元溫度 (-55°C 到 +150°C)
  sensor0.setExtendedMode(0);

  // 設置 T_HIGH,上限以觸發警報
  sensor0.setHighTempC(29.4);

  // 設置 T_LOW,下限以關閉警報
  sensor0.setLowTempC(26.67);
}

void loop()
{
  float temperature;
  boolean alertPinState, alertRegisterState;

  // 開啟傳感器以開始測量溫度。
  // 通常電流消耗約為 ~10uA。
  sensor0.wakeup();

  // 讀取溫度資料
  temperature = sensor0.readTempC();

  // 讀取警報
  alertPinState = digitalRead(ALERT_PIN); // 從針腳讀取警報
  alertRegisterState = sensor0.alert();   // 從註冊讀取警報

  // 讓感測器進入睡眠模式以節省能源
  // 通常電流消耗約為 <0.5uA.
  sensor0.sleep();

  // 印出溫度及警報狀態
  Serial.print("Temperature: ");
  Serial.print(temperature);

  Serial.print("\tAlert Pin: ");
  Serial.print(alertPinState);

  Serial.print("\tAlert Register: ");
  Serial.println(alertRegisterState);

  delay(1000);  // 等待 1000ms
}

LCD 液晶螢幕

參考:

這個模組應該不需要解釋吧,就是一個可以顯示黑白像素的螢幕。 重點是要怎麼讓它顯示東西:

  1. 沒有 I2C 轉接板

如果你只有一片 1602 LCD 螢幕,沒有接上轉接板(Adapter board), 那麼你會看到螢幕模組的板子上有很多針腳。你需要自己將這些針腳都連接到開發板上。

1602 LCD without I2C board

LCD pinsESP32 pins
VSSGND
VDD電源(3V3 或 5V0)
RSGPIO12
RWGND
E (Enable)GPIO11
D4GPIO5
D5GPIO4
D6GPIO3
D7GPIO2
A電源(3V3 或 5V0)
KGND

然後,打開範例的「LiquidCrystal > HelloWorld」,你應該就能看到 hello, world! 和經過時間的字樣。

Note

在範例程式的檔案裡,你也可以看到應該接上的對應針腳。

txt
 The circuit:
* LCD RS pin to digital pin 12
* LCD Enable pin to digital pin 11
* LCD D4 pin to digital pin 5
* LCD D5 pin to digital pin 4
* LCD D6 pin to digital pin 3
* LCD D7 pin to digital pin 2
* LCD R/W pin to ground
* LCD VSS pin to ground
* LCD VCC pin to 5V

程式部份,我們有 LiquidCrystal 幫忙進行處理,所以只要用 setCursor() 指定位置、print() 顯示文字就行啦!

c
// 範例程式的片段
void loop() {
  // set the cursor to column 0, line 1
  // (note: line 1 is the second row, since counting begins with 0):
  lcd.setCursor(0, 1);
  // print the number of seconds since reset:
  lcd.print(millis() / 1000);
}
  1. 有 I2C 轉接板

有轉接板,它就會幫你處理好這堆針腳,並整理成剩下 4 個:VCC、GND、SDA、SCL。 前兩個就不用說了,後兩個就是傳遞 I2C 資訊的針腳啦, 至於要接在哪裡,看看開發板的 Pinout:

esp32 pinout

看到 SDA 和 SCL 了嗎?就是接在 GPIO21 和 GPIO22 上。(以這片板子來說)

接上之後,就可以顯示東西了! 一樣,這邊會用到 LiquidCrystal 函式庫,不過是 I2C 的版本:

c
/** Edited from MiroTek <3 */
#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd(0x27,16,2);  // set the LCD address to 0x27 for a 16 chars and 2 line display

void setup() {
  lcd.init(); // initialize the lcd
  lcd.backlight();

  // Print a message to the LCD.
  lcd.setCursor(0,0);
  lcd.print("Hello, world!");

  lcd.setCursor(0,1);
  lcd.print("TNFSHCEC");
}

void loop() { }