ESP32 校正 ADC
Tags: esp32, adc
Published: 2021-11-23 03:28:17

根據 [ESP32 ADC Calibration](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/adc.html#adc-calibration) 文件的說明, 雖然說 ADC 的參考電壓是 1.1V, 但其實每一顆 ESP32 的參考電壓都是不一樣的, 這個參考電壓在出廠時會實測後燒寫入 eFuse 中, 因此實際上必須依照此參考電壓校正 ADC 值。

網路上已經有善心人士幫我們把校正版本的 MicroPython 模組寫好了, 可以在這裡找到 [MicroPython-ADC_Cal](https://github.com/matthias-bs/MicroPython-ADC_Cal), 它提供一個繼承自 `ADC` 的 `ADC1Cal` 類別, 內建有 `voltage` 屬性可以取得校正後以 V 為單位的電壓值, 使用範例如下：

```python
from machine import Pin, ADC
from adc1_cal import ADC1Cal
import time

DIV       = 1                 # div = V_measured / V_input; here: no input divider

ubatt = ADC1Cal(
    Pin(36),          # 腳位
    1,                # 分壓電路的倍數 (分壓後的電壓/原始電壓), 1 表示沒有使用分壓電路
    None,             # 參考電壓, None 表示從 eFuse 中取得參考電壓
    10,               # 平均值計算的取樣次數
    "ADC1 Calibrated"
)

ubatt.width(ubatt.WIDTH_10BIT)
ubatt.atten(ubatt.ATTN_6DB) # 不支援 11dB

# 顯示參考電壓
print('ADC Vref: {:4}mV'.format(ubatt.vref))

while True:
    print('Voltage:      {:4.1f}mV'.format(ubatt.voltage))
    time.sleep(0.3)
```

不過這個模組原本因為想要減少記憶體用量以及計算複雜度, 並不支援衰減設為 11dB 的模式, 因此我參考了 [esp-idf 的原始碼](https://github.com/espressif/esp-idf/blob/a18cb7090511d235ebe3c8b833180630f4f8bb33/components/esp_adc_cal/esp_adc_cal_esp32.c#L342), 加入了衰減設為 11dB 時針對非線性區域以查表及[雙線性內插](https://zh.wikipedia.org/wiki/%E5%8F%8C%E7%BA%BF%E6%80%A7%E6%8F%92%E5%80%BC)方式求值的功能。

**更新：2021/12/06 我的修改版本已經提交並且併入原作者的版本中了, 所以已經沒有不支援 11dB 的限制了。**

## ESP32 ADC1 的 ADC 校正方式

實際上在 ESP32 中對於 ADC 的校正, 是把 ADC 值都應對到 12 位元的解析度, 然後將 ADC 值以線性方式對應到電壓值, 這個線性公式為：

```
mV = (a * adc + 32768) / 65536 + b
```

而對 ADC1 來說：

```
a = (vref * atten_scales[atten]) / 4096
b = atten_offsets[atten]
```

其中 atten_scales 與 atten_offsets 就是根據衰減值決定的倍率與位移, 對照表如下, 個別元素分別對應到 ADC.ATTN_0DB、ADC.ATTN_2_5DB、ADC.ATTN_6DB、ADC.ATTN_11DB：

```
vref_atten_scale = [57431, 76236, 105481, 196602];
vref_atten_offset = [75, 78, 107, 142];
```

## 非線性區間的處理

如果是 ADC.ATTN_11DB, 那麼在 ADC 值在 2880~4095 的區間, 則是非線性區域, 它提供參考電壓為 1000mV 和 1200mV 時以 ADC 值 64 為區隔的對應電壓值表格供參考, 以下是個別表格內容：

```
# LUT for VREF 1000mV
lut_adc1_low = [2240, 2297, 2352, 2405, 2457, 2512, 2564, 2616, 2664, 2709,
                2754, 2795, 2832, 2868, 2903, 2937, 2969, 3000, 3030, 3060]
# LUT for VREF 1200mV
lut_adc1_high = [2667, 2706, 2745, 2780, 2813, 2844, 2873, 2901, 2928, 2956,
                 2982, 3006, 3032, 3059, 3084, 3110, 3135, 3160, 3184, 3209]
```

由於是從 2880~4096 間隔為 64, 所以共有 (4096-2880)/64 = 20 項資料。當 ADC 值落入特定區間時, 就以參考電壓為 X 軸、ADC 值為 Y 軸, 電壓值為 Z 軸, 以雙線性內插法求得電壓值。舉例來說, 若 ADC 值為 3040, 從 eFuse 取得的參考電壓為 1070, 那麼首先可知 ADC 值 2976 是 2880+ 64 + 64 + 32, 所以得知落在第 3 個區間, 即可以 (1000, 3008, 2352), (1000, 3072, 2405) 及 (1200, 3008, 2745)、(1200, 3072, 2780) 為已知點, 以雙線性內插求 (1070, 3040) 對應的 Z 值：

```
        ADC
         ^
         |   2405  R2      2780
    3072 +----●----●--------●
         |    |    |        |
         |    |    P        |
    3040 +----|----●--------|
         |    |    |        |
         |   2352  R1      2745
    3008 +----●----●--------●
         |    |    |        |
         +----+----+--------+--->VREF
           1000mV 1070mV 1200mV
```


過程如下：

1. 先在 Y 軸 2880+64 這條線上求內插值：

    ```
    R1 = ((1070-1000) * 2745 + (1200-1070) * 2352)/ (1200-1000)
    R1 = 2489.55
    ```

1. 再在 Y 軸 2880+128 這條線上求內插值：

    ```
    R2 = ((1070-1000) * 2780 + (1200-1070) * 2405)/ (1200-1000)
    R2 = 2536.25
    ```

1. 再在 X 軸 1070mV 這條線上求內插值：

    ```
    P = ((3040 - 3008) * 2536.25 + (3072 - 3040) * 2489.55)/ 64
    P = 2512.9
    ```
    
### 線性到非線性的交界區

對於 ADC.ATTN_11DB 的處理, 除了上述非線性區間外, 如果 ADC 值落在 2880~2880+64 的這個區間, 因為是從線性區往非線性區的邊緣, 所以還會再以上述提到的方式分別計算出線性值與非線性值後, 用 ADC 值在 (2880, 線性值) 與 (2880+64, 非線性值) 間以內插法再求值當成實際的電壓值。