ESP32 教學系列(六):類比數位轉換器(ADC)

ESP32 教學系列(六):類比數位轉換器(ADC)

一、前言

在上一篇「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 個通道可以使用。

ESP32 教學系列(六):類比數位轉換器(ADC)
圖一 ESP32 的 ADC 接腳

💡 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 測量到的數值與實際電壓有所落差(參考:官方文件)。

圖二是筆者自行測試的結果,圖三是擷取自國外網站的測試結果(來源)。

ESP32 教學系列(六):類比數位轉換器(ADC)
圖二
ESP32 教學系列(六):類比數位轉換器(ADC)
圖三

由於測試過程中發現 ADC 很容易受到雜訊干擾,因此本次測試中有加上去耦電容以及軟體重複取樣,每筆資料間隔 50mV,取樣次數 20 次,每次間隔 100ms。

實驗說明

這個實驗以 ESP32 上的 3.3V 作為電源,透過轉動可變電阻改變電壓值,依數位電表的量測值作為基準值,測試程式碼類似於範例 1,在此就不多加說明。

ESP32 教學系列(六):類比數位轉換器(ADC)
圖四 ADC 數值與電壓比較實驗電路
ESP32 教學系列(六):類比數位轉換器(ADC)
圖五 調整過程以桌上型數位電表做為基準電壓參考。圖為調整 2.6V 參考電壓。

將實際輸出結果與理想輸出結果(虛線)比對後,不難發現 ESP32 的 ADC 真的很不準XD。

ESP32 教學系列(六):類比數位轉換器(ADC)
圖六 虛線是理想ADC的輸出數值,實線是ADC實際的輸出曲線。

雜訊干擾

如前所述,筆者在測試過程中發現,ADC 數值很容易受到雜訊干擾,量測到的數值很不穩定。

ESP32 教學系列(六):類比數位轉換器(ADC)
圖七 未加電容 數值範圍 (min: 1481, max: 1562)

對於雜訊干擾的問題,官方建議在 ADC 接腳與 GND 之間加上一顆 0.1uF 的電容過濾雜訊,同時,多次取樣也有助於得到穩定的數值。

ESP32 教學系列(六):類比數位轉換器(ADC)
圖九 截取自官方文檔(來源)。可以看到加上電容與多次取樣平均後能獲得更加穩定的ADC數值。

五、用ESP32讀取類比數值

基礎語法

  • analogRead 使用ADC時不需要設定 pinMide(),當需要讀取類比數值時只要用 analogRead() 指定用來讀取的GPIO接腳,就會回傳ADC的原始數值(0~4095)。
analogRead(GPIO Pin);

參數說明:

GPIO Pin : GPIO接腳。

六、範例1: 讀取可變電阻上的電壓

ESP32 教學系列(六):類比數位轉換器(ADC)
圖十 範例1

材料清單

連接示意圖

ESP32 教學系列(六):類比數位轉換器(ADC)
圖十一 範例1 連接示意圖

程式碼

上傳程式到開發板之後,打開序列埠監控視窗,試著轉動可變電阻就可以看到數值的改變了!

七、範例2: 調整彩色LED燈環亮度

WS2812是一顆整合了控制電路與RGB晶片的彩色LED,它使用了一種特殊協議能夠將多顆LED串聯起來,在硬體部分只需要接3條線就能使用,最多能夠顯示多達16,777,216種色彩,是相當常見的全彩LED。在這個範例中我們將調整可變電阻以改變LED光環的亮度。

材料清單

連接示意圖

ESP32 教學系列(六):類比數位轉換器(ADC)
圖十二 範例2連接示意圖

安裝函式庫

首先,我們需要先安裝由 Adafruit 提供的 Adafruit NeoPixel 這個函式庫。由於 WS2812 所使用的協議比較特別,使用函式庫封裝好的內容可以幫助我們簡化驅動燈環的程式。

點選Arduino IDE左側的函式庫管理員圖示,在搜尋欄輸入「neopixel」,找到以後點選安裝,這樣就完成了。

ESP32 教學系列(六):類比數位轉換器(ADC)
圖十三 範例2函式庫安裝說明

程式碼

Adafruit_NeoPixel函式庫中提供了一個更改LED亮度的函式setBrightness,我們可以透過讀取可變電阻的數值來調整燈環的亮度。

程式說明

  1. 首先引入 (include) Adafruit_NeoPixel 這個函式庫,定義燈環參數與宣告變數。
  2. 接下來是宣告 ring 物件,並且在 setup() 函式中將 ring 初始化,然後關閉所有的 LED。
  3. 在 loop() 函式中,程式每 50 毫秒就會去讀取一次可變電阻的數值,並將電阻的數值轉換成燈環的亮度。

其他事項說明

  • 由於可變電阻的數值範圍是 0~4095,而設定燈環亮度的範圍則是 0~255,所以這兩個數值之間還需要經過 map 函式轉換成對應的數值。不過燈環的亮度如果設定成 255 會太刺眼,範例中將最大亮度設為 50。
  • 設定顏色的功能參考自 NeiPixel 函式庫的範例,它的功能是將多個不同的色調(Hue)週期性的填滿燈環,在燈環上顯示彩虹效果。

輸出結果

ESP32 教學系列(六):類比數位轉換器(ADC)
圖十四 範例2顯示效果
註:實際連接時,只要接3隻腳即可。圖中的黃色線(DOUT接腳)可以不用接

其他(可能會用到的)語法

  • 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_0100 mV ~ 950 mV
ADC_ATTEN_DB_2_5100 mV ~ 1250 mV
ADC_ATTEN_DB_6150 mV ~ 1750 mV
ADC_ATTEN_DB_11150 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)辨別,或是向經銷商確認。

ESP32 教學系列(六):類比數位轉換器(ADC)
圖十五 擷取自官方文件(v4.4.5) ESP32晶片的標記說明。

如何校正

由於原廠已經幫我們測量好 vref,只需要呼叫原廠提供的校正函式就能得到校正好的結果。

下圖是筆者實測 ADC 電壓校正前後的結果,可以以看到校正後的電壓值基本上相當接近標準電壓,但(在本次測試中)線段兩端仍然都有 120mV 左右的量測死點,也就是說,校正後的有效範圍大約落在 0.2~3.2V 之間。

ESP32 教學系列(六):類比數位轉換器(ADC)
圖十六 ADC校正前後的電壓比較結果。

實驗說明

筆者本次實驗的配置是使用電源供應器做為外部輸入源,搭配桌上型精密數位電表的量測值做為標準電壓,以 0.1V 為一單位,比較 GPIO 36 在不同標準電壓下 ADC 校正前後的電壓值,並將其繪製成圖表。

ESP32 教學系列(六):類比數位轉換器(ADC)
圖十七 實驗電路。中間 0.1uF 的電容用來濾除雜訊。
ESP32 教學系列(六):類比數位轉換器(ADC)
圖十八 標準電壓以電表顯示的數值為基準。
ESP32 教學系列(六):類比數位轉換器(ADC)
圖十九 在標準電壓為 3V 時的原始電壓與校正電壓值。

程式碼

程式碼說明

上面的範例程式執行後會在序列埠監控視窗顯示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 精度有所要求,筆者依然建議使用專門的晶片,可以省去校正與補償的麻煩,同時也能夠得到更理想的轉換結果。