MicroPython的午睡(103) ESP32版、12bit ADC、アッテネータ設定と読み取り

Joseph Halfmoon

最近別シリーズの「Rubyちゃん」の次に「MicroPython」する巡りあわせ。RX631マイコン上のmruby処理系で動作させたものをほぼそのままESP32のMicroPython処理系でもやってみる展開です。今回はADC(アナログデジタルコンバータ)。処理系の違いというよりマイコンの考え方の違い、割り切り?

※「MicroPython的午睡」投稿順 Indexはこちら

ESP32のADC入力

まずはESP32マイコンのADCのハードウエアについて調べておきます。ESP32 Techninal Reference Manual によると、12 bitの逐次比較型のADCを2ユニット搭載しています。それぞれのADCは複数の外部端子に接続可能です。しかし制約がありました。

ADC2 … WiFiで使用

ESP32系の「売り」であるWiFi機能を使う場合、信号強度なのか何なのか測定するためにADC2は使われてしまうようです。WiFiを使用しないのであればADC2を外部端子で使うのは有りでしょうが、WiFiを使用しないのにESP32を使うケースはあまり無いようにも思われるのでADC2は「とっておいた」方がよろしいかと。今回は外部端子で使用するのはADC1としておきます。

さて、ADコンバータというと、アナログデジタル変換のための参照電圧(基準電圧)が必要です。ここでESP32のADCは結構スッパリ割り切っている感じがします。

ADCの参照電圧は内蔵の1.1V電圧源のみ

さらに、ADCが「線形な特性」で精度よく変換できるのは

100mV – 950mV範囲

ということなんであります。0V付近、1.1V付近になると精度が落ちるから使わないでね、という感じ。他社のADコンバータであると、アナログ権化の偉い人がギリギリまで精度がでるように頑張ってる感があるのですが、ESP32の場合、真ん中辺で使ってね、端はダメ、という割り切りかと。まあ「それで許せる」用途でつかってね。

でも上記の線形な範囲、狭すぎるような気がしないでもないです。1.5Vとか2Vとかの電圧測れないの?というと秘策があるのでした。

アッテネータ内蔵

AD入力に接続する数種類のアッテネータを内蔵し、高い電圧を上記の線形な範囲に落とし込もうというのです。このアッテネータにより、最大2.45Vまでのアナログ入力電圧を上記範囲に収めることができると。オシロスコープなどでアッテネータを使って高電圧を測定可能な範囲に落とし込むような感じかと。

ADCで使用できる端子

さて使用するのはADC1ということになりましたが、ボードによって使用できる端子は異なります。今回ターゲットのESP32 WROOM-32D搭載、ESP32 DevKitCボードでは以下のようでした。

端子番号 ADC1チャネル DevKitC端子
36 ADC1_CH0 ×
37 ADC1_CH1 ×
38 ADC1_CH2 ×
39 ADC1_CH3 ×
34 ADC1_CH6
35 ADC1_CH7
25 ADC1_CH8
26 ADC1_CH9
33 ADC1_CH5
32 ADC1_CH4

一番右のDevKitC端子で〇の端子はボード端面にピンヘッダに出力されてますが、×は出てません。〇の端子の「端子番号」はそのままボード上にシルクで印刷されている番号と一致してます。

ESP32版MicroPythonでのADC入力関数

以下のMicroPythonの日本語ドキュメントページにはADCに関しての記述がバッチリ書かれてます。

ESP32 用クイックリファレンス

ハードウエアのところにあった、アッテネータの切り替え用の関数もあります。また、ADCの入力をハードウエアよりの整数値で読みとる関数以外に、μV単位に読み取る関数というものが定義されてます。

12bit ADCだし、μV単位なんて意味ないので、実際に返る値はmVステップなので1000単位です。1000分の1した方が良さそうです。しかしADCの読み取りに使うお勧めはこちらの関数です。

read_uv()

どうもESP32の場合、工場出荷時にADCの個別補正値的なものを書き込んでくれているらしく、上記の関数はそれも加味してμV(実際にはmVステップだけれども)単位の数値に換算してくれてるみたいです。知らんけど。また、整数値で読み取る関数はアッテネータの設定次第で同じ入力電圧でも異なる数値となりますが、上記関数は単位系が決まっているので、アッテネータの設定で換算する必要もありません。お楽。

今回実験のMicroPythonコード

実験に使用したスクリプトは以下です。アッテネータの設定を4種類変更しながら、端子番号32のアナログ電圧を測って、整数値とmV単位の値で表示しつづけるものです。

from machine import Pin, ADC
import time

def readADC(opt):
    if   opt == 0:
        print("ATTN_0DB, 100mV - 950mV")
        atn = ADC.ATTN_0DB
    elif opt == 1:
        print("ATTN_2_5DB, 100mV - 1250mV")
        atn = ADC.ATTN_2_5DB
    elif opt == 2:
        print("ATTN_6DB, 150mV - 1750mV")
        atn = ADC.ATTN_6DB
    else:
        print("ATTN_11DB, 150mV - 2450mV")
        atn = ADC.ATTN_11DB
    time.sleep_ms(500)
    adc1 = ADC(Pin(32),atten=atn)
    time.sleep_ms(500)    
    valu16 = adc1.read_u16()
    time.sleep_ms(500)
    valuv  = adc1.read_uv()
    print("U16:   ", valu16)
    print("valuv: ", valuv/1000, "[mV]")

def main():
    opt = 0
    while True:
        readADC(opt)
        opt += 1
        if opt > 3:
            opt = 0

if __name__ == "__main__":
    main()
実機実験結果

別シリーズで、GR-CITRUSボードに取り付けてあった抵抗と可変抵抗を引っぺがし、ESP32 DevKitCに取り付けなおしてあります。3.3V側に1kΩの固定抵抗、GND側に最大1kΩの可変抵抗です。この抵抗間の電位を端子32に与えてます。DevKitC_ADC_DUT

可変抵抗はかなり回転数が多いので、ハンディDMMで電圧を見ながら調整してます。今回はアッテネータ無でも範囲内に入るように800mVとするつもりでしたが、以下のように802mV(DMM読み値)です。
ADCinputDMM

上記プログラムを動作させたところが以下に(見やすいように途中改行いれました。)

ATTN_0DB, 100mV - 950mV
U16: 49724
valuv: 817.0 [mV]

ATTN_2_5DB, 100mV - 1250mV
U16: 37945
valuv: 809.9999 [mV]

ATTN_6DB, 150mV - 1750mV
U16: 26390
valuv: 820.0 [mV]

ATTN_11DB, 150mV - 2450mV
U16: 13667
valuv: 829.0 [mV]

U16が整数の読み取り値です。アッテネータを効かせていくにつれ、値が小さくなっていくのが分かります。アッテネータ0dBの結果から推測するに、1.1V付近の参照電圧あたりで16ビットフルスケール 65535 になるように思われます。ただしハード的には12bitのADCなので、下4ビットが0でない16ビットフル幅の整数値が読めるのは謎です。何か換算しているのか、複数回の平均か、補正か?

それにくらべると read_uv()で読み取った値はアッテネータの設定にかかわりなく物理単位系なので楽っす。

MicroPython的午睡(102) ESP32版、8bitのDAC使えた へ戻る

MicroPython的午睡(104) ESP32版、改めてModule群をながめてみる へ進む