MicroPython的午睡(35) ラズパイPico、MCP23017でIO端子数拡張

Joseph Halfmoon

ある目的のためIO本数を32本ほど欲しくなりました。低速で可。ラズパイPicoのGPIOは全部数えても29本です。そこで思い出したのがIOエキスパンダーMCP23017です。I2C接続(ラズパイPico端子は2本のみ使用)で16端子までのIOを使えるようにしてくれるマイコンの友。2チップ使えば合計32端子拡張できます。今回は2チップ搭載の小ボードを作って動作チェックまで。

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

(実験に使ったMicroPythonのコードの全文は末尾に)

今回「拡張する」IO端子への要求仕様的なものをまず書き留めておきたいと思います。

    • 本数32本
    • 双方向入出力、HiZにも設定できること
    • IO電圧5Vに対応できること

最近ラズパイPicoのMicroPythonに頼り切っているのでそのままやりたいのですが、ラズパイPicoのIO端子では賄いきれないことが明らかです。そこでラズパイPicoのIO本数を大幅に増強する名刺サイズの小型ボードを作成することにいたしました。

マイクロチップ社製MCP23017 IO Expanderを2個搭載しました。これの兄弟チップにはSPI接続のものもあるのですが、手元在庫の関係でI2C接続版です。SPIに比べるとI2Cは速度はでないですが、SPIのように端子毎のCS信号など不要だし、何かと接続が楽です。ラズパイPicoの1組のSCLとSDA端子に最大8個までのMCP23017を接続可能。それぞれの識別は各々3ビット分のアドレス端子でI2Cアドレスの下のビットを設定です。今回は以下のようにアドレスを割り付け、勝手な名前を付けました。

    • I2Cアドレス 0x20を IOEX0
    • I2Cアドレス 0x21を IOEX1

MCP23017は3.3Vでも5Vでも動作可能なので、IO Expanderボード側の電源はラズパイPicoとは別にとることといたしました。後で5V化するためであります。今回はラズパイPicoに直結し、結線の導通テスト(いちおうテスタで配線チェックしたので動くはずなのですが、動かしてみないと分からんし)だけをするつもりなので電源は3.3Vです。回路図はこんな感じ。RPiPico_MCP23017_schematic

回路図的にはバス表記でスッキリですが、実際の配線は結構ヤバイです。いまどきパラレルに32本とか手結線は辛い。それに導通テストには以前に作ってあったLEDボードを使うのですが、16ビット分しかありません。2回にわけてテストするしかない。だいたい、ピンソケット対ピンのジャンパ線の数も足りないし、作るのメンドイし。

とりあえずIOEX0と名付けた方のMCP23017のBポート8本だけを結線して動かしているところがこちら。Aも接続すると配線込み入って酷く汚くなります。

RPiPico_MCP23017_DUT

動作テスト用のコードを末尾に示しましたが簡単です。

    1. 1度にMCP23017の1個分のIO本数しかテストできないので、deviceという変数で切り替える。
    2. MCP23017は内部のレジスタアドレスを自動でインクリメントするモードがデフォルトになっているのでIOCONというレジスタを操作してディセーブルにする
    3. IO端子は8ビットずつA, Bの2組に分かれているが、これらをすべて出力とする。
    4. 端子には3秒に一回、0と1をトグルするように出力させる。その際、隣り合う端子の出力値は必ず異なるようなパターンとする。端子から0が出たときにLEDが光るように治具ボードを取り付けているので、LEDが交互に光ればOKという判断です。もし隣り合う端子同士でショートなどあれば点灯したまま?になるはず。

MCP23017のデータシートでチップ内部の制御レジスタのアドレスとビットの意味を確認しながら、以下のMicroPythonのドキュメントを参照してテストプログラムを書いてみました。

class I2C – a two-wire serial protocol

プログラムの冒頭でI2CバスをスキャンしてMCP23017が存在していれば、

IO Expander is available.

などと報告することになっています(2個だった。複数形の方が良くはないかか。)とりあえずI2C接続はOKみたい。

LEDが点滅しています。隣同士のショートなども無さそうだし。次は5V化してターゲットに接続だな。しかしそれにしても手配線本数32本で、十分面倒、老眼の目はショボショボ。まだやるのか。。。

MicroPython的午睡(34) ラズパイPico、MEMSマイクのPDM生波形を取得 へ戻る

MicroPython的午睡(36) ThonnyでATOM Liteにインストール へ進む

接続テストに使用したMicroPythonコード全文
import time
from machine import Pin, I2C

IOEX0 = 0x20
IOEX1 = 0x21
IOCONA = 0x0A
IOCONB = 0x0B
IODIRA = 0x00
IODIRB = 0x01
GPIOA = 0x12
GPIOB = 0x13
OLATA = 0x14
OLATB = 0x15

i2c = I2C(0, scl=Pin(17), sda=Pin(16), freq=100000)

def isIOEXavailable():
    lis = i2c.scan()
    if (IOEX0 in lis) and (IOEX1 in lis):
        return True
    return False

def readByte(i2cADR, regADR):
    bufW = bytes([regADR])
    ackW = i2c.writeto(i2cADR, bufW)
    bufR = bytearray(1)
    ackR = i2c.readfrom_into(i2cADR, bufR)
    return int.from_bytes(bufR, 'little')  

def writeByte(i2cADR, regADR, dat):
    bufW = bytes([regADR, dat])
    ackW = i2c.writeto(i2cADR, bufW)
    return ackW  

print("IO Expander is ", end="")
if not isIOEXavailable():
    print("not available.")
else:
    print("available.")

device = IOEX0

writeByte(device, IOCONA, 0x20)
writeByte(device, IODIRA, 0x00)
writeByte(device, IODIRB, 0x00)
writeByte(device, OLATA,  0xFF)
writeByte(device, OLATB,  0xFF)

for _i in range(10):
    writeByte(device, OLATB,  0xAA)
    writeByte(device, OLATA,  0xAA)
    time.sleep(3)
    writeByte(device, OLATB,  0x55)
    writeByte(device, OLATA,  0x55)
    time.sleep(3)    

print("DEV{0}:IOCONA={1:08b}".format(device,readByte(device, IOCONA)))
print("DEV{0}:IOCONB={1:08b}".format(device,readByte(device, IOCONB)))
print("DEV{0}:IODIRA={1:08b}".format(device,readByte(device, IODIRA)))
print("DEV{0}:IODIRB={1:08b}".format(device,readByte(device, IODIRB)))

writeByte(device, OLATA,  0xFF)
writeByte(device, OLATB,  0xFF)