一、前言
在上一篇「ESP32教學系列(五):數位脈衝寬度調變(PWM)」我們學到了PWM數位輸出的進階用法,這一篇要來說明數位輸入的進階用法—ADC。
二、ADC 介紹
ADC(Analog-to-Digital Converter,數位類比轉換器)是使用微控制器時經常用到的一項功能,它的作用是將類比訊號轉換成數位訊號。為什麼會需要ADC呢?由於微控制器只能讀取和處理數位訊號,因此如果外部輸入的來源是類比訊號,就要用ADC轉換成數位訊號,微控器才能夠讀取或者是做進一步的處理。有許多元件或感測器都會產生類比訊號,像是Joystick搖桿、可變電阻、光感測器、彎曲感測器、壓力感測器、霍爾感測器…等等。
三、ESP32 的 ADC
⚠️ 重要
ESP32 的 ADC 存在許多已知的問題,如果有精確量測的需求,建議使用專門的 ADC 晶片。(例如:ADS1115)
ESP32 晶片具有兩個 SAR ADC(ADC1和ADC2),兩個 ADC 加起來總共有 18 個通道,並且每個通道都有 12 位元的解析度。在 ESP32 開發板上,我們有 16 個通道可以使用。
💡 Tips
當我們開啟 ESP32 的 Wi-Fi 時,就無法使用 ADC2 的功能。並且因為 Arduino Framework 的 ADC 函式只需要設定 GPIO 接腳,在函式內部才會去區分 ADC1 或 ADC2,因此在使用 Wi-Fi 與 ADC 接腳時需多加留意是否有腳位衝突的問題。
ADC1有6個通道連接到GPIO接腳,分別是GPIO32~36和GPIO39。
ADC2有10個通道連接到GPIO4、GPIO0、GPIO2、GPIO15、GPIO13、GPIO12、GPIO14、GPIO27、GPIO25與GPIO26。
解析度
ESP32 的 ADC 能將 0~3.3V 的類比電壓值轉換成 0~4095 的數值。ADC 具有 12 位元的解析度,代表它可以檢測4096(212)個離散的類比電壓值,範圍從 0 到 4095。其中,0V 電壓對應到數值 0,3.3V 電壓對應到 4095,所以每單位解析度約為 0.8mV(毫伏)。
理想上電壓和數值的對應關係是線性的。例如,當數值為 1024 時對應的電壓值會是 1024 * 0.8 = 819.2mV;當數值為 3100 時,電壓值應為 2480mV。可惜的是,ESP32 的 ADC 並不完全線性。
四、ESP32 ADC的問題
一般來說,所有的 ADC 在轉換過程中都可能會因為受到多種因素的干擾而產生誤差,而不同的 ADC 之間好與壞的具體差別就在於能否將這些誤差消除。換句話說,轉換品質決定了 ADC 的好壞。
筆者在實際測試後發現目前 ESP32 的 ADC 有兩個較嚴重的問題,非線性的ADC以及雜訊干擾。關於 ADC 的問題討論,有興趣的讀者可以參考 Github上 的兩則issue(來源1)(來源2)。
非線性的 ADC
造成量測誤差的其中一項重要原因是因為內部參考電壓的誤差過大。ESP32 的 ADC 在取樣過程中會透過與內部的參考電壓 Vref 比較,以確定當前電壓所對應的數值。根據官方設計,晶片內部的標準參考電壓是1100mV,但是實際的參考電壓會因設備而異,每一片的 Vref 大約會落在 1000mV~1200mV 之間不等,這對 ADC 來說是相當大的誤差範圍,也因此導致每片 ESP32 測量到的數值與實際電壓有所落差(參考:官方文件)。
圖二是筆者自行測試的結果,圖三是擷取自國外網站的測試結果(來源)。
由於測試過程中發現 ADC 很容易受到雜訊干擾,因此本次測試中有加上去耦電容以及軟體重複取樣,每筆資料間隔 50mV,取樣次數 20 次,每次間隔 100ms。
實驗說明
這個實驗以 ESP32 上的 3.3V 作為電源,透過轉動可變電阻改變電壓值,依數位電表的量測值作為基準值,測試程式碼類似於範例 1,在此就不多加說明。
將實際輸出結果與理想輸出結果(虛線)比對後,不難發現 ESP32 的 ADC 真的很不準XD。
雜訊干擾
如前所述,筆者在測試過程中發現,ADC 數值很容易受到雜訊干擾,量測到的數值很不穩定。
對於雜訊干擾的問題,官方建議在 ADC 接腳與 GND 之間加上一顆 0.1uF 的電容過濾雜訊,同時,多次取樣也有助於得到穩定的數值。
五、用ESP32讀取類比數值
基礎語法
- analogRead 使用ADC時不需要設定
pinMide()
,當需要讀取類比數值時只要用analogRead()
指定用來讀取的GPIO接腳,就會回傳ADC的原始數值(0~4095)。
analogRead(GPIO Pin);
參數說明:
GPIO Pin
: GPIO接腳。
六、範例1: 讀取可變電阻上的電壓
材料清單
- ESP32開發板 x1
- micro USB 傳輸線 x1
- 麵包板 x1
- 杜邦線(公-公) x3
- 10K可變電阻 x1
連接示意圖
程式碼
上傳程式到開發板之後,打開序列埠監控視窗,試著轉動可變電阻就可以看到數值的改變了!
七、範例2: 調整彩色LED燈環亮度
WS2812是一顆整合了控制電路與RGB晶片的彩色LED,它使用了一種特殊協議能夠將多顆LED串聯起來,在硬體部分只需要接3條線就能使用,最多能夠顯示多達16,777,216種色彩,是相當常見的全彩LED。在這個範例中我們將調整可變電阻以改變LED光環的亮度。
材料清單
- ESP32開發板 x1
- micro USB 傳輸線 x1
- 麵包板 x1
- 杜邦線(公-公) x3
- 10K可變電阻 x1
- WS2812 環形 LED(8 bit) x1
連接示意圖
安裝函式庫
首先,我們需要先安裝由 Adafruit 提供的 Adafruit NeoPixel 這個函式庫。由於 WS2812 所使用的協議比較特別,使用函式庫封裝好的內容可以幫助我們簡化驅動燈環的程式。
點選Arduino IDE左側的函式庫管理員圖示,在搜尋欄輸入「neopixel」,找到以後點選安裝,這樣就完成了。
程式碼
Adafruit_NeoPixel函式庫中提供了一個更改LED亮度的函式setBrightness
,我們可以透過讀取可變電阻的數值來調整燈環的亮度。
程式說明
- 首先引入 (include) Adafruit_NeoPixel 這個函式庫,定義燈環參數與宣告變數。
- 接下來是宣告 ring 物件,並且在 setup() 函式中將 ring 初始化,然後關閉所有的 LED。
- 在 loop() 函式中,程式每 50 毫秒就會去讀取一次可變電阻的數值,並將電阻的數值轉換成燈環的亮度。
其他事項說明
- 由於可變電阻的數值範圍是 0~4095,而設定燈環亮度的範圍則是 0~255,所以這兩個數值之間還需要經過 map 函式轉換成對應的數值。不過燈環的亮度如果設定成 255 會太刺眼,範例中將最大亮度設為 50。
- 設定顏色的功能參考自 NeiPixel 函式庫的範例,它的功能是將多個不同的色調(Hue)週期性的填滿燈環,在燈環上顯示彩虹效果。
輸出結果
其他(可能會用到的)語法
- analogSetWidth
設定 ADC 讀取解析度,預設是 12 位元。也可以用analogReadResolution(bits)
代替。
analogSetWidth(bits);
參數說明:
bits
: 設定類比讀取解析度,可設定的範圍是 9~12 位元。
- analogReadMillivolts
用來獲得指定接腳/ADC 通道的電壓值,單位是毫伏(mV)。
analogReadMilliVolts(pin);
參數說明:
pin
: 讀取類比值的接腳。
- analogSetAttenuation
輸入的電壓在進入 ADC 之前可以被衰減。對應不同的電壓測量範圍,我們可以設定特定的衰減倍數。 有 4 個衰減參數可以使用,預設衰減參數是ADC_ATTEN_DB_11
。數值越大,可測量的輸入電壓就越大。 下表可以看到 ESP32 的 ADC 衰減有範圍限制。
attenuation(衰減) | 輸入電壓範圍 |
ADC_ATTEN_DB_0 | 100 mV ~ 950 mV |
ADC_ATTEN_DB_2_5 | 100 mV ~ 1250 mV |
ADC_ATTEN_DB_6 | 150 mV ~ 1750 mV |
ADC_ATTEN_DB_11 | 150 mV ~ 2450 mV |
這個函式用來設定所有通道的衰減倍數,
analogSetAttenuation(attenuation);
參數說明:
attenuation
: 設定衰減倍數。
- analogSetPinAttenuation
設定特定接腳的衰減倍數。
analogSetPinAttenuation(pin, attenuation);
參數說明:
pin
: 指定的接腳。
attenuation
: 設定衰減倍數。
八、進階知識:校正 ADC
雖然 ESP32 的 ADC 量測結果並不理想,但原廠還是有做出補救方案。樂鑫在 Arduino 核心的 ADC API 裡面提供了一個校正函式,可以幫助我們校正由內部參考電壓造成的誤差。
實際上的做法是在晶片出廠前測量每一顆晶片的Vref電壓,並將測得的電壓燒錄到eFUSE中,校正函式就可以利用晶片實際的Vref電壓補償ADC的測量誤差,從而提高 ADC 精度。這個方法適用於所有在2018年第一周以後所生產的晶片,基本上目前大部分的ESP32都能以這種方式校正,更精確一些可以由ESP32晶片上面的日期代碼(下圖中的Date Code)辨別,或是向經銷商確認。
如何校正
由於原廠已經幫我們測量好 vref,只需要呼叫原廠提供的校正函式就能得到校正好的結果。
下圖是筆者實測 ADC 電壓校正前後的結果,可以以看到校正後的電壓值基本上相當接近標準電壓,但(在本次測試中)線段兩端仍然都有 120mV 左右的量測死點,也就是說,校正後的有效範圍大約落在 0.2~3.2V 之間。
實驗說明
筆者本次實驗的配置是使用電源供應器做為外部輸入源,搭配桌上型精密數位電表的量測值做為標準電壓,以 0.1V 為一單位,比較 GPIO 36 在不同標準電壓下 ADC 校正前後的電壓值,並將其繪製成圖表。
程式碼
程式碼說明
上面的範例程式執行後會在序列埠監控視窗顯示ADC量到的原始電壓值以及校正後的電壓值,其中校正的內容都寫在ADC_cal()這個函式中,它的功能是將原始ADC數值轉換成修正後的電壓值。
esp_adc_cal_characterize
API 是由官方提供的校正函式,它的功能是在特定衰減條件下,修正ADC的特性,並以 y = coeffa * x + coeffb的形式生成 ADC 電壓曲線。其中,參數 a、b 由衰減值決定。
函式定義如下:
esp_adc_cal_value_t esp_adc_cal_characterize(adc_unit_t adc_num,
adc_atten_t atten,
adc_bits_width_t bit_width,
uint32_t default_vref,
esp_adc_cal_characteristics_t *chars);
範例程式以 11dB 衰減值、12 位元解析度校正 ADC1,並將校正結果儲存在 adc1_chars 中
esp_adc_cal_characteristics_t adc_chars;
esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_11, ADC_WIDTH_BIT_12, 1100, &adc_chars);
最後將原始 ADC 數值與校正後的結果轉換成電壓回傳(單位: mV),就能得到校正後的電壓值。
return(esp_adc_cal_raw_to_voltage(raw_ADC, &adc_chars));
以上是基本的校正程式。想了解如何更進一步校正的讀者可以參考這支影片,以及說明欄中Github專案的程式碼。
它的流程和概念與上面的範例程式相同,只是影片作者在校正過程中還另外加上可調整的參數,調整此參數就更進一步的改善校正後的電壓精度。
九、ADC 的限制
總結一下 ESP32 的 ADC 限制:
- 無法直接測量大於 3.3V 的電壓,但如果要測量更高的電壓值時,可以使用分壓或是電壓準位轉換等方法。
- 開啟 Wi-Fi 功能後,無法使用 ADC2,只能使用 ADC1 。
- ADC 具有非線性的原始量測值,雖然非線性問題經過校正以後可以獲得改善,但在曲線兩端仍然各有一段電壓區間無法被 ADC 量測,可用性仍然不高。
十、小結
在這篇文章中,我們介紹了什麼是 ADC、ESP32 ADC 的問題與校正的方法,並且透過兩個範例來瞭解 ADC 的用法。有興趣的讀者可以延伸範例2 的內容,使用 3顆可變電阻調整 RGB 數值,或是結合前一篇提到的 PWM,讓燈環顯示彩色呼吸燈。
綜合上面的測試,ESP32 的 ADC 比較適合不要求測量精度,或是只需要測量相對值的場合。若專案對於 ADC 精度有所要求,筆者依然建議使用專門的晶片,可以省去校正與補償的麻煩,同時也能夠得到更理想的轉換結果。