MicroPython的午睡(54) ATOMLite、ADCにCdSセルを接続

Joseph Halfmoon

今回はM5 ATOMLiteのアナログ機能を使ってみるべし、と思い立ち、光量によって抵抗が変化するCdSセルを取り付けてみました。以前ラズパイPicoで接続した筈の部品をそのまま流用したので、MicroPythonのコードもほぼほぼ流用。しかしね、なんか特性変わってるんじゃね。まずはATOMLiteとの接続ですが、端子少ない割に意外と使えます。

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

(実験で使用したMicroPythonスクリプト全文は末尾に)

M5ATOMLiteの端子でADC入力に使えるもの一覧

超小型デバイス、M5ATOMLiteはそれほど多くの端子が外に出ているわけではないですが、調べてみるとADコンバータの入力に使える端子が5端子もありました。以下のとおり。

Pin番号 ADC 接続
G25 ADC2_CH8 当方、I2Cに接続中
G26 ADC2_CH9 Grove
G32 ADC1_CH4 Grove
G33 ADC1_CH5 今回使用
G34* ADC1_CH6 使用可能*

M5ATOMLiteに搭載のマイコンはESP32でもPICOと呼ばれる機種です。PICOのデータシートを見ると、ADCなど周辺についてはそのまま「フルスペック」のESP32を見よ、みたいに書いてありました。上記は、ESP32のデータシートを見て表にしたものです。

左端のピン番号はM5ATOMLiteの拡張端子にでているPinのお名前です。ちょっと注意が必要なのは、*のついているG34端子です。「表向きには」G34という端子は拡張端子には出てないように見えるのです。しかし、M5ATOMLiteの回路図見るとG23端子内部に100Ωの抵抗を介してG34端子も接続されています。2端子が同一のピンソケットに接続されているので使用には注意が必要です。

G23を入力設定、プルアップなど無し

としたところG34端子からアナログ入力可能でした。でもなんでこの端子だけこんなアクロバティックなことをしてあるのだろう?どこかに設計意図など書いておいて欲しいな。私が見落としているだけ?

上記の特殊なG34を除く4端子のうち、2本は本体Groveポートにでている2端子です。GroveポートはI2Cとして接続することが多いと思いますが、柔軟というか、「接続規格」としてとらえたら相当いい加減で、デジタルIOでもアナログ入力でも可能です。なんでもつながる便利なコネクタと捉えるべきか。Groveの2本は現状空いているのですが、後で使うこともあるだろーということで今回はつかいませなんだ。

G25については、すでに5VのI2Cバスとの接続用に使用してしまっているのでこれもパス。ということでアナログ入力にはG33端子を使用することにいたしました。

継ぎ足ししながら部品を接続しているので、今回は現状の回路(現物写真は先頭のアイキャッチ画像)の全貌を下に示しました。といって追加したのは、回路図右下のCdSセル(R11)とプルダウン抵抗(R12)の小さな2部品だけですが。

Schematic20111125

電圧読み取りおよびCdSセルの抵抗値計算スクリプト

MicroPythonからの CdSセルの読み取りは、以下の回でラズパイPico上で既にやってみたことがあります。

MicroPython的午睡(24) ラズパイPico、CDSセンサをADCに接続

今回は、上の回で作成したスクリプトを持ってきて

    • 後でモジュールとして上位のスクリプトから呼び出せるように
    • 単独でADCの動作確認ができるように

しただけのものです。ラズパイPico上でのMicroPythonのADC部分の実装と、使用しているESP32 “generic” ポートのMicroPythonのADC部分の実装には細かい差がありましたが、以下のドキュメントを見ながら若干調整すれば動きました。

ESP32 用クイックリファレンス ADC (アナログ/デジタル変換)

ザックリした設定は、

    • G33端子
    • フルスケール9ビット
    • アッテネータ、11dB

です。

動作結果

以下にスクリプトを単独で走らせたときの、動作結果を示します。RAWとあるのは、ADCの生の読み取り値で、デフォルトの12ビットでなくコンサバな9ビット指定なので0-511までの整数です。AVGとあるのは、移動平均(3サンプル)とっているものです。VOLTAGEはフルスケールを3.3Vと見たてて、移動平均値を電圧換算したもの。Rは測定電圧とプルダウン抵抗値との比較からCdSの抵抗値に換算したものです。

だいたいこの計算は「疑問」があるのです。self.avddを3.3としていますが、アッテネータ11dBだと、上記ドキュメントによれば約3.6vがフルスケールになるみたいなこと書いてあります。つまり、3.3V電圧のいっぱいを入力してもADCは振り切れない筈(論理的には)しかし、実際にボリューム外付けして電圧を変更してみたところフルスーケールの0から511まで出力されとりました。アッテネータの設定とADCの測定値との関係は?どうせ内部の回路的にADCの基準電源に3.3vをとらざるを得ないと思うので3.3v入れたら511でてくるのは普通に思えるんですけど。外から電圧与えて特性を調べてみるかと思ったのですが、ADの評価なんて恐ろしくてやりたくないし、そんな元気も無いです。とりあえず接続するのがCdSセルだしねえ。今回は見送りとしました。

以下でCdSの電圧が低くなるところは両手でセルを包みこんで「暗く」してみているところです。それ以外は室内照明の下。

RAW: 350 AVG: 333.0 VOLTAGE: 2.15 [V] R: 2.5 [k Ohm]
RAW: 351 AVG: 350.7 VOLTAGE: 2.26 [V] R: 2.1 [k Ohm]
RAW: 353 AVG: 351.3 VOLTAGE: 2.27 [V] R: 2.1 [k Ohm]
RAW: 40 AVG: 248.0 VOLTAGE: 1.60 [V] R: 5.0 [k Ohm]
RAW: 37 AVG: 143.3 VOLTAGE: 0.93 [V] R: 12.1 [k Ohm]
RAW: 36 AVG: 37.7 VOLTAGE: 0.24 [V] R: 59.1 [k Ohm]
RAW: 37 AVG: 36.7 VOLTAGE: 0.24 [V] R: 60.8 [k Ohm]
RAW: 36 AVG: 36.3 VOLTAGE: 0.23 [V] R: 61.4 [k Ohm]
RAW: 350 AVG: 141.0 VOLTAGE: 0.91 [V] R: 12.3 [k Ohm]
RAW: 352 AVG: 246.0 VOLTAGE: 1.59 [V] R: 5.1 [k Ohm]
RAW: 355 AVG: 352.3 VOLTAGE: 2.28 [V] R: 2.1 [k Ohm]

CdS自体は、人の目に感じる明るさではなく、幅広い周波数の光を感じるようです。明るい、暗いはちゃんと検出してはいるのですが、これにも疑問あり。ラズパイPicoに接続したときとCdSセルの特性変わってないかい?ラズパイPicoの時のプルダウン抵抗値だと、ちょっと明るくするとすぐにフルスケール振り切れてしまいます。それで今回はプルダウン抵抗値を下げてます。ううん、これまた追及する元気ないし、また今度。

とりあえず次回はCdSセルの電圧をMQTT経由でNode-REDに報告。調べるのはその後だな~(何時?)

MicroPython的午睡(53) ESPRESSIF、ESP-EYEでLチカを へ戻る

MicroPython的午睡(55) ATOMLite、NodeREDにCdSセル結果報告 へ進む

実験に使用したMicroPythonスクリプト全文
from machine import Pin, ADC
import time

class MovingAVG:
    
    def __init__(self, ival=0, inum=3):
        if inum < 3:
            inum = 3
        self.buf = [ival] * inum
        self.nSample = 0
        self.nBuf = inum
        self.ok = False
        self.avg = None
        
    def set(self, dat):
        if type(dat) not in (float, int):
            return None
        self.nSample += 1
        self.buf.append(dat)
        self.buf.pop(0)
        self.avg = sum(self.buf) / self.nBuf
        if self.nSample >= self.nBuf:
            self.ok = True
        return self.ok

class LiteCDS:
    """CDS sensor on Pin33 ADC

    """
    
    def __init__(self):
        self.avgCalc = MovingAVG()
#        Pin(23, Pin.IN, None)
#        self.adc = ADC(Pin(34))
        self.adc = ADC(Pin(33))
        self.resistor = 4700
        self.avdd = 3.3
        self.fullscale = 511
        self.adc.atten(ADC.ATTN_11DB)
        self.adc.width(ADC.WIDTH_9BIT) # RESULT 0-511
        self.lastResult = None

    def measure(self):
        cdsRawValue = self.adc.read()
        ok = self.avgCalc.set(cdsRawValue)
        while not ok:
            cdsRawValue = self.adc.read()
            ok = self.avgCalc.set(cdsRawValue)
        volt = self.avdd * (self.avgCalc.avg / self.fullscale)
        if volt != 0:
            res = ((self.avdd - volt) / volt) * self.resistor / 1000
        else:
            res = 0
        return ( cdsRawValue, self.avgCalc.avg, volt, res )

def main():
    cds = LiteCDS()
    while True:
        tpl = cds.measure()
        if len(tpl) == 4:
            print("RAW: {0} AVG: {1:5.1f} VOLTAGE: {2:1.2f} [V] R: {3:3.1f} [k Ohm]".format(tpl[0],tpl[1], tpl[2], tpl[3]))
        time.sleep(5)

if __name__ == "__main__":
    main()