最近別シリーズの「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に関しての記述がバッチリ書かれてます。
ハードウエアのところにあった、アッテネータの切り替え用の関数もあります。また、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に与えてます。
可変抵抗はかなり回転数が多いので、ハンディDMMで電圧を見ながら調整してます。今回はアッテネータ無でも範囲内に入るように800mVとするつもりでしたが、以下のように802mV(DMM読み値)です。
上記プログラムを動作させたところが以下に(見やすいように途中改行いれました。)
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()で読み取った値はアッテネータの設定にかかわりなく物理単位系なので楽っす。