前言:ESP32 如何讀取「不是只有 HIGH / LOW」的訊號?
前面的 ESP32 GPIO 教學中,我們主要處理:
HIGH
LOW
這種數位訊號。
例如:
digitalRead(BUTTON_PIN);
讀到的基本上就是:
HIGH
或:
LOW
但是現實世界中的很多感測器並不是只有兩種狀態。
例如:
可變電阻
光敏電阻
類比溫度感測器
壓力感測器
搖桿
電池電壓
光線強弱
電流感測器
氣體感測器
輸出的可能是一段連續變化的電壓。
例如:
0.3V
0.8V
1.2V
1.7V
2.3V
2.8V
ESP32 要怎麼知道這些電壓是多少?
答案就是:
ADC
ADC 全名:
Analog-to-Digital Converter
中文稱為:
類比數位轉換器
ESP32 的 ADC 可以把輸入的類比電壓轉換成數位數值,讓程式可以進一步進行:
顯示
判斷
計算
記錄
Wi-Fi 傳送
PWM 控制
警報
自動控制
Arduino-ESP32 官方目前提供 analogRead() 進行單次 ADC 轉換,也提供 analogReadMilliVolts() 取得經校準換算後的毫伏值。
本章將完成:
ADC 原理
↓
12-bit
↓
0~4095
↓
analogRead()
↓
ADC1 / ADC2
↓
Wi-Fi 注意事項
↓
可變電阻
↓
LDR 光敏電阻
↓
電壓換算
↓
ADC + PWM
↓
光線自動調光
一、ADC 是什麼?
ADC:
Analog-to-Digital Converter
功能就是把:
Analog Voltage
轉換成:
Digital Number
例如:
輸入電壓
│
▼
ESP32 ADC
│
▼
數位數值
假設使用 12-bit ADC,
程式可能看到:
0
512
1024
2048
3072
4095
這些數字就代表不同的類比輸入程度。
Espressif 官方說明,ADC Conversion 的本質就是把輸入類比電壓轉換成數位 Raw Data,供微控制器進一步處理。
二、ESP32 ADC 是幾位元?
Arduino-ESP32 目前 analogRead() 的預設解析度為:
12-bit
也就是:
2^12
=
4096
個數位階級。
因此一般看到的 Raw ADC 範圍就是:
0 ~ 4095
官方 Arduino-ESP32 文件也明確指出:
analogReadResolution(12);
所對應的範圍是:
0 ~ 4095
而且目前所有支援的 Arduino-ESP32 SoC 預設 analogRead() 回傳解析度都是 12-bit;原始 ESP32 的硬體解析度可設定在 9~12 bit。
三、為什麼 12-bit 是 0~4095?
因為:
2^12 = 4096
總共有:
4096 個狀態
但是數字從:
0
開始。
所以最大值是:
4095
也就是:
0
1
2
3
...
4093
4094
4095
共:
4096
個數值。
因此:
12-bit ADC
=
0 ~ 4095
四、ESP32 ADC 與 Arduino UNO 有什麼差別?
傳統 Arduino UNO 常見 ADC 是:
10-bit
所以:
0 ~ 1023
ESP32 Arduino 預設則為:
12-bit
因此:
0 ~ 4095
簡單比較:
| 平台 | ADC Resolution | Raw Range |
|---|---|---|
| Arduino UNO | 10-bit | 0~1023 |
| ESP32 Arduino 預設 | 12-bit | 0~4095 |
因此從 Arduino UNO 轉到 ESP32 時,不要直接假設:
analogRead() 最大值 = 1023
在本章 12-bit 設定下應該使用:
4095
Arduino-ESP32 官方目前的 ADC API 預設值確實為 12-bit、0~4095。
五、ESP32 analogRead()
Arduino 環境中最簡單的 ADC 函式就是:
analogRead();
格式:
uint16_t value =
analogRead(pin);
例如:
int adcValue =
analogRead(34);
代表:
讀取 GPIO34 的 ADC Raw Value。
Arduino-ESP32 官方定義:
uint16_t analogRead(uint8_t pin);
回傳的是:
未校準的 ADC Raw Value。
六、第一個 ESP32 ADC 程式
本章以常見原始 ESP32 開發板為例,先使用:
GPIO34
作為類比輸入。
const uint8_t ADC_PIN = 34;
void setup()
{
Serial.begin(115200);
analogReadResolution(12);
}
void loop()
{
int adcValue =
analogRead(ADC_PIN);
Serial.print("ADC = ");
Serial.println(adcValue);
delay(500);
}
Serial Monitor 可能看到:
ADC = 432
ADC = 780
ADC = 1540
ADC = 2217
ADC = 3250
數字會依輸入電壓而改變。
七、analogReadResolution()
Arduino-ESP32 可以使用:
analogReadResolution(bits);
設定 analogRead() 回傳值的解析度。
本章使用:
analogReadResolution(12);
因此範圍:
0 ~ 4095
官方文件指出,目前預設值就是 12-bit;若設定不同的解析度,Arduino-ESP32 會依 SoC 能力改變硬體解析度或對讀值進行位元調整。
所以即使預設已經是:
12-bit
教材中仍可以明確寫:
analogReadResolution(12);
讓讀者清楚知道目前使用的 ADC Range。
八、ESP32 ADC1 與 ADC2 是什麼?
原始 ESP32 具有兩組 ADC:
ADC1
ADC2
Espressif 官方文件指出,原始 ESP32 有兩個 SAR ADC,最多可以提供 18 個類比量測 Channel。
對常見原始 ESP32 而言,ADC Pin 大致可分為:
ADC1
GPIO32
GPIO33
GPIO34
GPIO35
GPIO36
GPIO37
GPIO38
GPIO39
也就是:
GPIO32 ~ GPIO39
共 8 個 Channel。
ADC2
原始 ESP32 的 ADC2 包括:
GPIO0
GPIO2
GPIO4
GPIO12
GPIO13
GPIO14
GPIO15
GPIO25
GPIO26
GPIO27
共 10 個 Channel。
但是:
不代表每一塊 ESP32 開發板都把這些 GPIO 全部拉到排針,也不代表所有腳位都適合在你的電路中自由使用。
有些 GPIO 同時具有:
Boot Strapping
Flash
Debug
板上電路
其他特殊功能
因此仍應以實際開發板 Pinout 為準。Espressif 官方也提醒,不同 SoC、Module 與 Board 的 ADC 數量、Channel 與可用 Pin 並不相同。
九、ADC1 / ADC2 最重要的差異:Wi-Fi
這是 ESP32 ADC 教學中非常重要的一點:
ADC2 與 Wi-Fi 會有資源衝突
Espressif 官方目前仍明確指出:
ADC2 同時會被 Wi-Fi 使用。
官方 GPIO 文件更直接建議:
如果使用 Wi-Fi 時遇到 ADC2 GPIO 無法正常讀取,可改用 ADC1 GPIO。
因此對初學者而言,可以記住:
ESP32 專案要使用 Wi-Fi
+
需要 ADC
↓
優先考慮 ADC1
例如本章選:
GPIO34
就是 ADC1。
十、為什麼 ESP32 IoT 專案推薦 ADC1?
假設後面做:
ESP32
+
LDR
+
Wi-Fi
+
MQTT
+
Web Server
如果一開始把 LDR 接在 ADC2,
可能遇到:
Wi-Fi 開啟
↓
ADC2 資源被共用
↓
ADC 讀取產生問題
官方 ESP32 GPIO 文件仍明確列出:
ADC2 pins cannot be used when Wi-Fi is used
並建議改用 ADC1 GPIO。
因此本系列 ESP32 教材在有 Wi-Fi 擴充可能性的 ADC 專案中,優先使用:
GPIO32 ~ GPIO39
中的合適 ADC1 Pin。
十一、注意:不同 ESP32 型號不能直接套用同一張 ADC Pin 表
ESP32 現在是一整個系列:
ESP32
ESP32-S2
ESP32-S3
ESP32-C3
ESP32-C6
...
它們:
ADC Channel
GPIO Mapping
Voltage Range
ADC 數量
並不完全相同。
Arduino-ESP32 官方 ADC 文件也特別提醒:
每一種 SoC / Module 的 ADC 數量、Channels 與可用 Pins 可能不同。
因此本文的:
ADC1 GPIO32~39
ADC2 GPIO0、2、4、12~15、25~27
主要指:
原始 ESP32
例如常見的 ESP32-WROOM-32 類開發板。
不要直接把這張表套到 ESP32-C3 或 ESP32-S3。
十二、本章為什麼選 GPIO34?
本章 ADC Input 使用:
const uint8_t ADC_PIN = 34;
主要是因為對常見原始 ESP32:
GPIO34
屬於:
ADC1
因此適合後續加入:
Wi-Fi
MQTT
Web Server
IoT
功能。原始 ESP32 的 ADC1 Channel 位於 GPIO32~39。
另外要注意,GPIO34~39 在原始 ESP32 上屬於輸入用途為主的 GPIO,因此很適合作為 ADC Sensor Input,但不要把它們當一般輸出腳使用。實際 Board 的可用 Pin 仍應查看對應硬體資料。
十三、實驗一:ESP32 + 可變電阻
可變電阻是學習 ADC 最容易理解的元件之一。
常見:
10K Potentiometer
具有三個腳位:
A
Wiper
B
接線:
3.3V
│
│
┌─────────┐
│ POT │
└─────────┘
│ │
│ └──────── GPIO34
│
GND
整理成:
| 可變電阻 | ESP32 |
|---|---|
| 外側腳 1 | 3.3V |
| 中間腳 | GPIO34 |
| 外側腳 2 | GND |
轉動旋鈕時:
中間腳電壓
會隨位置變化。
ESP32 ADC 就可以讀出不同數值。
十四、可變電阻完整程式
const uint8_t POT_PIN = 34;
void setup()
{
Serial.begin(115200);
analogReadResolution(12);
}
void loop()
{
int adcValue =
analogRead(POT_PIN);
Serial.print(
"Potentiometer ADC = "
);
Serial.println(
adcValue
);
delay(200);
}
轉動旋鈕時,Serial Monitor 可以觀察:
ADC = 238
ADC = 726
ADC = 1420
ADC = 2186
ADC = 3165
ADC = 3890
理想化概念上:
低電壓
↓
ADC 小
高電壓
↓
ADC 大
十五、ESP32 ADC 可以直接用 3.3 / 4095 算電壓嗎?
教學中常見這個公式:
Voltage =
ADC Value × 3.3 / 4095
例如:
float voltage =
adcValue * 3.3 / 4095.0;
這可以幫助理解:
ADC Raw
→
Voltage
的數學關係。
但是:
它不應被當成 ESP32 精密電壓計算法
原因是 ESP32 ADC 的:
實際 Vref
Attenuation
非線性
晶片個體差異
都會影響結果。
Espressif 官方指出,原始 ESP32 ADC 的設計參考電壓約為 1100 mV,但不同晶片實際 Vref 可能約落在 1000~1200 mV,因此官方提供 ADC Calibration 機制來提高電壓換算準確度。
所以:
adcValue * 3.3 / 4095.0
比較適合:
理解 ADC 比例概念。
如果要取得比較可靠的實際電壓:
優先考慮 analogReadMilliVolts() 或 ESP-IDF ADC Calibration。
十六、analogReadMilliVolts()
Arduino-ESP32 官方提供:
analogReadMilliVolts(pin);
例如:
uint32_t voltageMv =
analogReadMilliVolts(ADC_PIN);
官方文件指出:
analogRead()
回傳的是:
Raw ADC
而:
analogReadMilliVolts()
會回傳經校準換算後的:
mV
數值。
因此:
1650 mV
就是:
1.650 V
十七、ESP32 ADC 電壓量測範例
const uint8_t ADC_PIN = 34;
void setup()
{
Serial.begin(115200);
analogReadResolution(12);
}
void loop()
{
uint16_t raw =
analogRead(ADC_PIN);
uint32_t millivolts =
analogReadMilliVolts(
ADC_PIN
);
float voltage =
millivolts / 1000.0;
Serial.print(
"RAW = "
);
Serial.print(
raw
);
Serial.print(
" Voltage = "
);
Serial.print(
voltage,
3
);
Serial.println(
" V"
);
delay(500);
}
Serial Monitor 可能看到:
RAW = 1258 Voltage = 1.012 V
RAW = 1875 Voltage = 1.493 V
RAW = 2490 Voltage = 1.985 V
實際數字會依:
ESP32 晶片
ADC Attenuation
輸入電壓
校準資料
硬體雜訊
而有所不同。analogReadMilliVolts() 是 Arduino-ESP32 官方提供的校準毫伏轉換 API。
十八、什麼是 ADC Attenuation?
ESP32 ADC 內部參考電壓並不是簡單等於:
3.3V
要量測較高輸入電壓時,ADC 可以透過:
Attenuation
先對輸入訊號進行衰減。
Arduino-ESP32 目前提供:
analogSetAttenuation();
以及:
analogSetPinAttenuation();
可設定 ADC Attenuation。
十九、ESP32 ADC Attenuation 設定
以 Arduino-ESP32 為例,可使用:
analogSetPinAttenuation(
ADC_PIN,
ADC_11db
);
官方目前列出的 Arduino API 選項包括:
ADC_0db
ADC_2_5db
ADC_6db
ADC_11db
對原始 ESP32,官方 Arduino 文件列出的典型可量測範圍大約是:
| Attenuation | 原始 ESP32 官方文件量測範圍 |
|---|---|
| ADC_0db | 約 0.10~0.95 V |
| ADC_2_5db | 約 0.10~1.25 V |
| ADC_6db | 約 0.15~1.75 V |
| ADC_11db | 約 0.15~3.10 V |
不同 ESP32 SoC 的範圍並不相同。
所以:
不要把「ESP32 是 3.3V 系統」直接理解成「ADC 一定可以精確量到 0~3.3V」。
二十、本章 ADC 初始化建議
因此本章正式範例可以寫:
const uint8_t ADC_PIN = 34;
void setup()
{
Serial.begin(115200);
analogReadResolution(12);
analogSetPinAttenuation(
ADC_PIN,
ADC_11db
);
}
這樣程式就明確指定:
Resolution = 12-bit
Attenuation = ADC_11db
而不是依賴不明確的預設狀態。
二十一、ESP32 ADC 可以直接量 5V 嗎?
不可以把 5V 直接送進 ESP32 ADC GPIO。
ESP32 是低電壓 MCU,ADC Attenuation 的作用是擴展 ADC 的內部可量測範圍:
它不是把 GPIO 變成 5V tolerant。
如果要量:
5V
9V
12V
電池組
更高電壓
需要先利用:
Voltage Divider
把電壓降低到 ESP32 GPIO 安全範圍。
二十二、什麼是電阻分壓?
最基本的 Voltage Divider:
Vin
│
R1
│
├──────── ADC
│
R2
│
GND
輸出電壓:
Vout =
Vin × R2 / (R1 + R2)
例如:
R1 = 10K
R2 = 10K
則:
Vout
=
Vin × 1/2
所以:
Vin = 5V
理論上:
Vout = 2.5V
ESP32 ADC 量到的是:
Vout
程式再反推:
Vin
二十三、分壓電路電壓反推
因為:
Vout =
Vin × R2 / (R1 + R2)
所以:
Vin =
Vout × (R1 + R2) / R2
如果:
R1 = 10K
R2 = 10K
就是:
Vin =
Vout × 2
程式:
float inputVoltage =
adcVoltage
* (R1 + R2)
/ R2;
但是實際設計還需要考慮:
ADC 最大安全輸入
Attenuation Range
電阻誤差
ADC 校準
ADC Input Impedance
雜訊
保護電路
因此精密電壓量測不能只靠一條公式。
二十四、LDR 光敏電阻是什麼?
LDR:
Light Dependent Resistor
中文通常稱為:
光敏電阻
它的電阻值會隨環境光線改變。
一般概念:
光線變強
↓
LDR 電阻下降
光線變暗
↓
LDR 電阻升高
但是 ESP32 ADC 量的是:
Voltage
不是:
Resistance
所以需要利用:
Voltage Divider
把 LDR 的電阻變化轉成電壓變化。
二十五、ESP32 LDR 分壓電路
本章使用:
3.3V
│
LDR
│
├──────── GPIO34
│
10KΩ
│
GND
接線:
| 元件 | 接線 |
|---|---|
| LDR 一端 | ESP32 3.3V |
| LDR 另一端 | GPIO34 |
| 10KΩ 一端 | GPIO34 |
| 10KΩ 另一端 | GND |
GPIO34 連接:
LDR
+
10K
中間的分壓點。
二十六、這個 LDR 電路的亮暗關係
在本章接法:
3.3V
↓
LDR
↓
ADC
↓
10K
↓
GND
光線越強:
LDR Resistance ↓
分壓點電壓通常:
↑
因此 ADC Raw:
↑
也就是:
亮
↓
ADC 大
相反:
暗
↓
LDR Resistance ↑
↓
ADC Voltage ↓
↓
ADC 小
所以程式可以利用:
ADC 小
=
環境暗
來提高 LED 亮度。
二十七、ESP32 LDR 基本程式
const uint8_t LDR_PIN = 34;
void setup()
{
Serial.begin(115200);
analogReadResolution(12);
analogSetPinAttenuation(
LDR_PIN,
ADC_11db
);
}
void loop()
{
int lightValue =
analogRead(LDR_PIN);
Serial.print(
"LDR ADC = "
);
Serial.println(
lightValue
);
delay(200);
}
用手遮住 LDR,
觀察:
ADC
如何改變。
再用手機手電筒照射 LDR,
再觀察:
ADC
數值。
這是實際建立:
光線
↔
ADC
關係最好的方式。
二十八、不要直接假設「2000 就一定代表某個 Lux」
這一點很重要。
LDR 的:
Resistance
與:
Light Intensity
並不是簡單的固定線性比例。
而且:
LDR 型號
固定電阻值
供電電壓
ADC 特性
安裝位置
入射角
環境光源
都會影響結果。
因此本章:
ADC = 2000
只能理解成:
此硬體配置下的一個相對亮度數值。
如果要得到:
Lux
需要知道 LDR 的特性曲線並進行校準,或者直接使用數位光照度 Sensor。
二十九、ESP32 ADC 為什麼會跳動?
實際測量時可能看到:
1820
1827
1816
1832
1824
即使光線看起來沒有變化。
這是正常需要處理的工程問題。
ESP32 ADC 會受到:
電源雜訊
RF / Wi-Fi
Sensor Noise
走線
ADC 本身非線性
參考電壓
外部干擾
等因素影響。
Espressif 官方 ADC Calibration 文件也專門提供「降低雜訊」相關建議,因為 ADC 實際量測並不是完全沒有 Noise。
三十、最簡單的 ADC 平均濾波
可以連續取:
10 次
再平均。
int readAverageADC(
uint8_t pin,
int samples
)
{
long total = 0;
for (
int i = 0;
i < samples;
i++
)
{
total += analogRead(pin);
delay(2);
}
return total / samples;
}
使用:
int value =
readAverageADC(
LDR_PIN,
10
);
這樣數值通常會比單次:
analogRead()
穩定。
三十一、ESP32 ADC 平均讀取完整範例
const uint8_t LDR_PIN = 34;
const int SAMPLE_COUNT = 10;
int readAverageADC(
uint8_t pin
)
{
uint32_t total = 0;
for (
int i = 0;
i < SAMPLE_COUNT;
i++
)
{
total +=
analogRead(pin);
delay(2);
}
return
total / SAMPLE_COUNT;
}
void setup()
{
Serial.begin(115200);
analogReadResolution(12);
analogSetPinAttenuation(
LDR_PIN,
ADC_11db
);
}
void loop()
{
int adcValue =
readAverageADC(
LDR_PIN
);
Serial.print(
"Average ADC = "
);
Serial.println(
adcValue
);
delay(200);
}
三十二、ADC 數值如何轉成百分比?
12-bit ADC:
0 ~ 4095
如果只是要顯示相對百分比,可以使用:
int percent =
map(
adcValue,
0,
4095,
0,
100
);
例如:
ADC = 0
→ 0%
ADC ≈ 2048
→ 50%
ADC = 4095
→ 100%
但要注意:
這只是 ADC Raw 的比例,不代表 LDR 的 Lux 百分比。
三十三、ESP32 ADC + PWM 是非常重要的組合
前一章已經學過:
PWM
可以控制 LED:
0 ~ 255
本章 ADC 則是:
0 ~ 4095
所以可以建立:
ADC Sensor
↓
0~4095
↓
map()
↓
0~255
↓
PWM
↓
LED
這就是一個完整的:
Sensor → Controller → Output
控制系統。
三十四、實作:ESP32 光線感測器 + LED 自動亮度控制
這就是本章主要實作。
目標:
環境很亮
↓
LED 變暗
環境變暗
↓
LED 變亮
形成:
自動感光燈
使用:
LDR
+
ADC
+
PWM
+
LED
三十五、實作材料
| 材料 | 數量 |
|---|---|
| ESP32 開發板 | 1 |
| LDR 光敏電阻 | 1 |
| 10KΩ 電阻 | 1 |
| LED | 1 |
| 220Ω~330Ω 電阻 | 1 |
| 麵包板 | 1 |
| 杜邦線 | 若干 |
GPIO:
LDR ADC → GPIO34
LED PWM → GPIO18
三十六、完整接線
LDR
3.3V
│
│
LDR
│
├──────── GPIO34
│
10KΩ
│
│
GND
LED
GPIO18
│
220Ω
│
LED +
│
LED -
│
GND
整理:
| ESP32 | 元件 |
|---|---|
| 3.3V | LDR 一端 |
| GPIO34 | LDR / 10KΩ 分壓中心點 |
| GND | 10KΩ 另一端 |
| GPIO18 | 220Ω~330Ω → LED 正極 |
| GND | LED 負極 |
三十七、ADC 值如何控制 PWM?
我們現在有:
ADC
0 ~ 4095
LED PWM:
0 ~ 255
但是因為我們希望:
環境亮
→ LED 暗
因此必須反向 Mapping。
int ledDuty =
map(
lightValue,
0,
4095,
255,
0
);
也就是:
ADC 小
↓
PWM 大
↓
LED 亮
以及:
ADC 大
↓
PWM 小
↓
LED 暗
三十八、ESP32 光線自動調光第一版
const uint8_t LDR_PIN = 34;
const uint8_t LED_PIN = 18;
const uint32_t PWM_FREQ = 5000;
const uint8_t PWM_RESOLUTION = 8;
void setup()
{
Serial.begin(115200);
analogReadResolution(12);
analogSetPinAttenuation(
LDR_PIN,
ADC_11db
);
ledcAttach(
LED_PIN,
PWM_FREQ,
PWM_RESOLUTION
);
}
void loop()
{
int lightValue =
analogRead(
LDR_PIN
);
int ledDuty =
map(
lightValue,
0,
4095,
255,
0
);
ledDuty =
constrain(
ledDuty,
0,
255
);
ledcWrite(
LED_PIN,
ledDuty
);
Serial.print(
"Light = "
);
Serial.print(
lightValue
);
Serial.print(
" LED Duty = "
);
Serial.println(
ledDuty
);
delay(100);
}
三十九、為什麼實際效果可能不理想?
如果直接把:
0~4095
映射到:
255~0
可能發現:
大部分環境
只落在例如:
800~2800
而不是完整:
0~4095
結果 LED 只會在中間一小段變化。
所以實際做 Sensor 時,最好先進行:
Calibration
四十、先找出實際亮暗 ADC 值
先只執行:
Serial.println(
analogRead(LDR_PIN)
);
然後測試:
最暗
例如得到:
600
最亮
例如得到:
3200
那就不要使用:
map(
adc,
0,
4095,
255,
0
);
而改成:
map(
adc,
600,
3200,
255,
0
);
這樣實際效果會更明顯。
四十一、LDR 校準參數
例如設定:
const int DARK_ADC = 600;
const int BRIGHT_ADC = 3200;
然後:
int ledDuty =
map(
lightValue,
DARK_ADC,
BRIGHT_ADC,
255,
0
);
再:
ledDuty =
constrain(
ledDuty,
0,
255
);
這比直接假設 ADC 一定會跑滿:
0~4095
更符合實際 Sensor 應用。
四十二、正式版:ESP32 光線感測器 + LED 自動亮度控制
下面這一支程式適合作為本章正式下載範例。
/*
第 6 章
ESP32 ADC 類比輸入完整教學
實作:
ESP32 光線感測器
+ LED 自動亮度控制
功能:
1. GPIO34 讀取 LDR
2. 12-bit ADC
3. ADC_11db
4. 10 次平均濾波
5. ADC 校準範圍
6. ADC 轉換為 LED PWM
7. 環境越暗 LED 越亮
8. Serial Monitor 顯示 ADC / mV / PWM
適用:
常見原始 ESP32 / ESP32-WROOM-32 類開發板
LDR:
3.3V
|
LDR
|
+------ GPIO34
|
10K
|
GND
LED:
GPIO18
|
220R
|
LED
|
GND
*/
// -----------------------------
// GPIO
// -----------------------------
const uint8_t LDR_PIN = 34;
const uint8_t LED_PIN = 18;
// -----------------------------
// ADC
// -----------------------------
const uint8_t ADC_BITS = 12;
const int ADC_MAX = 4095;
// -----------------------------
// LDR Calibration
// 請依實際環境重新校準
// -----------------------------
const int DARK_ADC = 600;
const int BRIGHT_ADC = 3200;
// -----------------------------
// ADC Average
// -----------------------------
const int SAMPLE_COUNT = 10;
// -----------------------------
// PWM
// -----------------------------
const uint32_t PWM_FREQ = 5000;
const uint8_t PWM_RESOLUTION = 8;
const int PWM_MAX = 255;
// -----------------------------
// Read Average ADC
// -----------------------------
int readAverageADC(
uint8_t pin
)
{
uint32_t total = 0;
for (
int i = 0;
i < SAMPLE_COUNT;
i++
)
{
total +=
analogRead(pin);
delay(2);
}
return
total / SAMPLE_COUNT;
}
// -----------------------------
// setup()
// -----------------------------
void setup()
{
Serial.begin(115200);
delay(500);
Serial.println();
Serial.println(
"ESP32 ADC + LDR + PWM Demo"
);
// -------------------------
// ADC
// -------------------------
analogReadResolution(
ADC_BITS
);
analogSetPinAttenuation(
LDR_PIN,
ADC_11db
);
// -------------------------
// LED PWM
// Arduino-ESP32 3.x
// -------------------------
if (!ledcAttach(
LED_PIN,
PWM_FREQ,
PWM_RESOLUTION))
{
Serial.println(
"LEDC initialization failed!"
);
while (true)
{
delay(1000);
}
}
ledcWrite(
LED_PIN,
0
);
Serial.println(
"System Ready"
);
}
// -----------------------------
// loop()
// -----------------------------
void loop()
{
// -------------------------
// Read LDR
// -------------------------
int lightValue =
readAverageADC(
LDR_PIN
);
// -------------------------
// Read calibrated mV
// -------------------------
uint32_t voltageMv =
analogReadMilliVolts(
LDR_PIN
);
// -------------------------
// Dark -> Bright LED
// Bright -> Dim LED
// -------------------------
int ledDuty =
map(
lightValue,
DARK_ADC,
BRIGHT_ADC,
PWM_MAX,
0
);
ledDuty =
constrain(
ledDuty,
0,
PWM_MAX
);
// -------------------------
// PWM Output
// -------------------------
ledcWrite(
LED_PIN,
ledDuty
);
// -------------------------
// Relative Light %
// -------------------------
int lightPercent =
map(
lightValue,
DARK_ADC,
BRIGHT_ADC,
0,
100
);
lightPercent =
constrain(
lightPercent,
0,
100
);
// -------------------------
// Serial Monitor
// -------------------------
Serial.print(
"ADC = "
);
Serial.print(
lightValue
);
Serial.print(
" / "
);
Serial.print(
ADC_MAX
);
Serial.print(
" | Voltage = "
);
Serial.print(
voltageMv
);
Serial.print(
" mV"
);
Serial.print(
" | Light = "
);
Serial.print(
lightPercent
);
Serial.print(
"%"
);
Serial.print(
" | LED PWM = "
);
Serial.println(
ledDuty
);
delay(100);
}
本程式的 ADC 部分使用 Arduino-ESP32 官方目前提供的 analogReadResolution()、analogSetPinAttenuation()、analogRead() 與 analogReadMilliVolts() API。
四十三、完整程式執行流程
ESP32 開機:
setup()
↓
Serial 115200
↓
ADC = 12-bit
↓
GPIO34 = ADC Input
↓
ADC_11db
↓
GPIO18 = LEDC PWM
↓
System Ready
接著:
loop()
↓
LDR ADC 讀取 10 次
↓
平均
↓
取得 Light Value
↓
轉換成 LED PWM
↓
輸出 GPIO18
↓
顯示 Serial Data
↓
重新循環
四十四、環境變暗時發生什麼?
環境變暗:
Light ↓
LDR Resistance:
↑
在本章分壓方式下:
ADC Voltage ↓
所以:
ADC Value ↓
程式:
map(
lightValue,
DARK_ADC,
BRIGHT_ADC,
255,
0
);
因此:
ADC ↓
↓
PWM Duty ↑
↓
LED 變亮
形成:
自動夜燈
四十五、環境變亮時發生什麼?
光線增加:
Light ↑
LDR Resistance:
↓
ADC Voltage:
↑
ADC:
↑
因此:
PWM Duty ↓
LED:
變暗
也就是:
白天
↓
LED 暗
晚上
↓
LED 亮
四十六、為什麼要使用平均值?
如果每次直接:
analogRead()
可能看到:
1801
1827
1796
1814
1835
PWM 也跟著:
124
122
126
124
121
LED 就可能出現輕微抖動。
使用:
10 Samples Average
後,例如變成:
1814
1817
1812
1815
控制會比較穩定。
這是 Sensor Control 很基本也很實用的:
Filtering
概念。
四十七、進階:再加入 Deadband
如果希望 LED 更穩,
還可以設定:
Deadband
例如只有 ADC 變化超過:
20
才更新 PWM。
if (
abs(
lightValue -
lastLightValue
) >= 20
)
{
lastLightValue =
lightValue;
// 更新 LED
}
這樣可以避免 ADC 在:
1800
1805
1797
1808
附近微小跳動時一直更新 LED。
四十八、進階:用指數平滑濾波
除了平均,
還可以使用:
Exponential Moving Average
例如:
filteredValue =
filteredValue * 0.9
+
newValue * 0.1;
效果是:
Sensor 突然改變
↓
輸出不會立刻跳動
↓
而是平滑跟隨
這很適合:
光線
溫度
電池電壓
旋鈕
Joystick
等 Sensor。
四十九、ADC 電壓為什麼不一定完全線性?
ESP32 ADC 很方便,
但不能把它當成:
精密桌上型數位電表。
Espressif 官方指出,ESP32 不同晶片的 ADC 參考電壓存在個體差異,設計值約 1100 mV,但實際可能約 1000~1200 mV,因此需要 Calibration 來降低 Vref 差異造成的誤差。
所以:
ADC Raw
適合:
相對量測
Sensor Threshold
控制
亮度
旋鈕
趨勢
如果要做:
高精度電壓計
精密資料擷取
精密電池監控
高解析 Sensor
則應考慮:
ADC Calibration
External ADC
精密 Reference
前端濾波
精密電阻
等設計。
五十、什麼時候適合使用 analogReadMilliVolts()?
如果只是:
LDR 亮暗
其實使用:
analogRead()
即可。
因為只需要比較:
大
小
但如果想顯示:
Sensor Voltage
則:
analogReadMilliVolts()
更方便。
例如:
uint32_t mv =
analogReadMilliVolts(
ADC_PIN
);
官方 Arduino-ESP32 API 會將 ADC 結果轉換為校準後的 mV 數值。
五十一、Raw ADC 與 Voltage 最好同時保留
工程上很建議同時記錄:
RAW
+
mV
例如:
RAW = 1832
Voltage = 1486 mV
原因是:
RAW
方便:
Threshold
map()
Filtering
Debug
而:
mV
方便:
理解實際訊號
電壓紀錄
校準
硬體檢查
五十二、ADC1 + Wi-Fi 的實際設計策略
假設之後專案變成:
ESP32
+
LDR
+
Wi-Fi
+
MQTT
+
Web Dashboard
建議架構:
LDR
↓
ADC1 GPIO
↓
analogRead()
↓
Filtering
↓
Light Value
↓
PWM LED
+
Wi-Fi Upload
不要到了最後才發現:
Sensor 接到 ADC2
+
Wi-Fi 開啟
造成 ADC 資源使用問題。
對原始 ESP32,官方仍建議 Wi-Fi 與 ADC 同時使用時優先選 ADC1。
五十三、ESP32 Continuous ADC
本章主要使用:
analogRead()
也就是:
OneShot
每呼叫一次:
analogRead()
就做一次轉換。
但目前 Arduino-ESP32 還提供:
ADC Continuous Mode
可以在背景持續進行多個 ADC Conversion,並取得平均 Raw 與 mV 結果。官方 Arduino-ESP32 文件目前也提供 analogContinuous()、analogContinuousStart()、analogContinuousRead() 等 API。
對本章:
LDR
Potentiometer
一般 Sensor
使用:
analogRead()
就已經足夠。
Continuous ADC 可以留到高速資料擷取或進階 Sensor Sampling 再學。
五十四、常見錯誤:把 ADC 接到 5V
這是最需要避免的硬體錯誤之一。
不要:
5V
↓
ESP32 ADC GPIO
如果要量高於 ADC / GPIO 允許範圍的電壓,
必須先:
Voltage Divider
↓
Protection
↓
ESP32 ADC
而且要確認:
最大輸入
即使異常狀況也不會超過 ESP32 Pin 的電氣限制。
五十五、常見錯誤:ADC 一定等於 3.3 / 4095
這只是簡化的理論比例。
ESP32 實際 ADC 會受到:
Vref
Attenuation
Calibration
Non-linearity
Chip Variation
影響。
因此:
raw * 3.3 / 4095.0
適合教學理解,
而需要實際電壓時,應優先使用:
analogReadMilliVolts()
或更完整的 ADC Calibration。官方也明確指出實際 Vref 會有晶片個體差異。
五十六、常見錯誤:ADC2 + Wi-Fi
例如:
GPIO25
在原始 ESP32 屬於 ADC2。
如果專案同時:
Wi-Fi ON
則 ADC2 會涉及 Wi-Fi 資源共享問題。
所以:
Wi-Fi + Analog Sensor
入門專案優先使用:
ADC1
例如:
GPIO32
GPIO33
GPIO34
GPIO35
GPIO36
GPIO39
再依開發板實際 Pinout 挑選。Espressif 官方 GPIO 文件仍明確建議 Wi-Fi 使用時遇到 ADC2 問題可改用 ADC1。
五十七、常見錯誤:把 ESP32-S3 Pin 表當 ESP32 Pin 表
不要看到:
ESP32
三個字就認為所有版本都完全一樣。
例如:
ESP32
ESP32-S2
ESP32-S3
ESP32-C3
ESP32-C6
ADC GPIO Mapping 並不完全相同。
Arduino-ESP32 官方因此沒有在通用 API 文件中宣稱所有 SoC 共用相同 ADC Pin 表,而是要求依各 SoC / Module Datasheet 判斷。
五十八、常見錯誤:LDR 接法不同,亮暗結果相反
如果:
3.3V
↓
LDR
↓
ADC
↓
10K
↓
GND
一般會得到:
越亮 → ADC 越大
如果改成:
3.3V
↓
10K
↓
ADC
↓
LDR
↓
GND
則結果會反過來:
越亮 → ADC 越小
所以不要只複製:
map()
一定要先確認自己的:
Voltage Divider Direction
五十九、常見錯誤:LED 自動調光一直閃
可能原因:
ADC Noise
LDR Noise
電源雜訊
光線本身變動
PWM Mapping 太敏感
可以依序加入:
Average
↓
Calibration
↓
Deadband
↓
Smoothing
改善。
這就是從「範例程式」進入「真正控制系統」的重要一步。
六十、ESP32 ADC 可以應用在哪些專題?
掌握 ADC 後,可以做:
可變電阻控制器
Potentiometer
↓
ADC
↓
PWM
↓
LED / Motor
自動感光燈(補充說明)
LDR
↓
ADC
↓
PWM LED
Joystick
VRx
VRy
↓
2 路 ADC
電池電壓監測
Battery
↓
Voltage Divider
↓
ADC
類比溫度感測器
Sensor Voltage
↓
ADC
↓
Temperature
土壤濕度 Sensor
Analog Output
↓
ADC
↓
Moisture Level
類比氣體 Sensor
Sensor
↓
ADC
↓
Threshold
↓
Alarm
六十一、ADC 與 PWM 的關係非常重要
目前已經學過:
第 5 章
PWM
以及:
第 6 章
ADC
現在已經形成完整控制鏈:
Real World
↓
Sensor
↓
ADC
↓
ESP32
↓
程式演算法
↓
PWM
↓
Actuator
例如本章:
環境光
↓
LDR
↓
ADC
↓
ESP32
↓
map()
↓
PWM
↓
LED
這就是很多自動控制系統最基本的架構。
六十二、FAQ:ESP32 ADC 常見問題
Q1:ESP32 ADC 是幾 bit?
Arduino-ESP32 目前 analogRead() 預設為:
12-bit
因此一般 Raw Range:
0 ~ 4095
Q2:ESP32 analogRead() 回傳什麼?
回傳指定 GPIO / ADC Channel 的:
Raw ADC Value
官方 API:
uint16_t analogRead(
uint8_t pin
);
Q3:ESP32 ADC 0~4095 代表 0~3.3V 嗎?
不能這麼簡化理解。
ADC Raw 與實際電壓關係會受到:
Vref
Attenuation
Calibration
ADC Characteristics
影響。
官方指出原始 ESP32 的設計 Vref 約 1100 mV,但不同晶片實際可能約為 1000~1200 mV。
Q4:如何取得 ESP32 ADC 電壓?
Arduino-ESP32 可以使用:
analogReadMilliVolts(pin);
取得經校準轉換後的:
mV
值。
Q5:ESP32 ADC1 與 ADC2 有什麼差異?
對原始 ESP32:
ADC1
GPIO32~39
ADC2:
GPIO0
GPIO2
GPIO4
GPIO12~15
GPIO25~27
ADC2 還與 Wi-Fi 共用相關硬體資源,因此 Wi-Fi ADC 專案通常優先選 ADC1。
Q6:ESP32 使用 Wi-Fi 時 ADC2 可以用嗎?
這是需要特別注意的限制。
官方目前仍明確說明:
ADC2 is also used by Wi-Fi
並在 ESP32 GPIO 文件建議 Wi-Fi 使用時改用 ADC1 GPIO 避免 ADC2 問題。
Q7:ESP32 ADC 可以直接量 5V 嗎?
不應直接將 5V 接到 ESP32 ADC GPIO。
應使用:
Voltage Divider
把電壓降低至安全範圍後再量測。
Q8:LDR 為什麼需要電阻?
因為:
LDR
本身主要改變的是:
Resistance
ESP32 ADC 量的是:
Voltage
所以使用固定電阻與 LDR 組成:
Voltage Divider
把 Resistance Change 轉換成 Voltage Change。
Q9:ESP32 ADC 為什麼一直跳?
ADC 真實量測會受到:
Noise
Power
Sensor
RF
ADC Characteristics
等影響。
可以使用:
Average
Moving Average
Low-pass Filter
Deadband
Calibration
改善。ESP-IDF 官方 ADC Calibration 文件本身也提供降低 ADC Noise 的相關章節。
Q10:ADC 值可以直接控制 PWM 嗎?
可以。
例如:
ADC
0~4095
透過:
map()
轉成:
PWM
0~255
再使用:
ledcWrite();
即可控制 LED Brightness。
六十三、本章學習重點整理
第一個核心:
ADC(補充說明)
是:
Analog Voltage
↓
Digital Value
第二個核心:
Arduino-ESP32 預設:
12-bit
所以:
0 ~ 4095
第三個核心:
最基本讀取:
analogRead(
ADC_PIN
);
如果需要電壓:
analogReadMilliVolts(
ADC_PIN
);
第四個核心:
對原始 ESP32:
ADC1
GPIO32~39
ADC2
GPIO0、2、4、12~15、25~27
第五個核心:
Wi-Fi 專案優先考慮 ADC1
因為:
ADC2
+
Wi-Fi
有硬體資源共享限制。
第六個核心:
LDR 必須使用:
Voltage Divider
才能把:
Resistance
轉成:
Voltage
供 ADC 讀取。
第七個核心:
ADC 不應直接被當成:
精密電壓計
實際量測需要注意:
Vref
Attenuation
Calibration
Noise
Non-linearity
Espressif 也提供專門的 ADC Calibration Driver 處理這些誤差來源。
六十四、本章總結
在第 6 章中,我們正式學會 ESP32:
ADC 類比輸入
首先理解:
ADC
=
Analog
↓
Digital
接著掌握:
12-bit
↓
0~4095
並使用:
analogRead();
取得 ADC Raw Value。
需要電壓時,可以使用:
analogReadMilliVolts();
取得 Arduino-ESP32 提供的校準毫伏值。
我們也認識原始 ESP32 的:
ADC1
ADC2
其中 ADC2 與 Wi-Fi 有硬體資源共享限制,因此後續 IoT Sensor 專案應優先考慮 ADC1。
接著實作:
Potentiometer
↓
ADC
以及:
LDR
↓
Voltage Divider
↓
ADC
最後把前一章 PWM 與本章 ADC 串接:
光線
↓
LDR
↓
ESP32 ADC
↓
0~4095
↓
Filtering
↓
Calibration
↓
map()
↓
0~255
↓
LEDC PWM
↓
LED Brightness
完成:
ESP32 光線感測器 + LED 自動亮度控制
這一章真正重要的地方,是開始建立:
Sensor Input → Data Processing → Output Control
的完整控制系統觀念。
掌握這個架構後,下一步就能很自然地延伸到:
Joystick 控制
電池電壓監測
類比溫度 Sensor
土壤濕度
氣體 Sensor
光線監控
ADC + OLED
ADC + Wi-Fi
ADC + MQTT
智慧照明
IoT Sensor Node
等更完整的 ESP32 專案。