2019年7月4日木曜日

NodeMCU DevKit & MicroPython & I2C MPC23017

MCP23017 "I2C"のピン配置と配線

MCP23017のピン配置
No. Name Type Function Type Name No.
1 GPB0 I/O 双方向入出力ピンです。
"変更時割り込み"(IOC:Interrupt-on-change)または内臓の微弱なプルアップ抵抗、あるいはその両方を有効化出来ます。
I/O GPA7 28
2 GPB1 GPA6 27
3 GPB2 GPA5 26
4 GPB3 GPA4 25
5 GPB4 GPA3 24
6 GPB5 GPA2 23
7 GPB6 GPA1 22
8 GPB7 GPA0 21
9 VDD P 電源 1.8〜5.5V PORTA B
割り込み出力
O INTA 20
10 VSS グランド INTB 19
11 NC 未接続 ハードウェアリセット I RESET 18
12 SCL I シリアルクロック入力 ハードウェア
アドレスピン
I A2 17
13 SDA I/O シリアルデータ入出力 A1 16
14 NC 未接続 A0 15
MCP23017により拡張されるポートは、GPA0GPA7 & INTAGPB0GPB7 & INTBとA、B2つに別れています。
A、Bの各GPIOは8個なので1byte(8bit)単位で管理しやすいのと、8bitのみのMCP23008という製品もあるので、その辺りも兼ね合いがあるのかもですけどね。

では、実際の配線を見ながら確認。
3V3GNDD1(IO5)D2(IO4)の4つはNodeMCU DevKitの各端子に接続。
SCLSDAは、ざっくり10kΩの抵抗でプルアップ。正確を喫するなら要抵抗値計算。
A0A1A2の3つは、以下の様にプルアップ、プルダウンして4 ビット固定アドレスに続くハードウェアアドレスビットを定義するために用います。この計7ビットがスレーブアドレスとなります。
D7 D6 D5 D4 D3 D2 D1
0 1 0 0 A2 A1 A0
0 1 0 0 0 0 0 0x20
0 1 0 0 0 0 1 0x21
0 1 0 0 0 1 0 0x22
0 1 0 0 0 1 1 0x23
0 1 0 0 1 0 0 0x24
0 1 0 0 1 0 1 0x25
0 1 0 0 1 1 0 0x26
0 1 0 0 1 1 1 0x27
RESETをプルアップしてリセットを無効化。
以上で検証に必要な配線は完了。

MivroPythonでMCP23017へI2Cで接続

ここから先は、NodeMCU DevKitでの作業。
データシートでは、スレーブアドレスに以下のようにR/Wビットを続けて制御バイトとして送信する旨記載されていますが、MicroPythonでは直接指定しないのでスルー。
制御バイト
D7 D6 D5 D4 D3 D2 D1 D0
Start bit 0 1 0 0 A2 A1 A0 R/W ACK bit
スレーブアドレス Read : 1
Write : 0

NodeMCUを起動して、必要となるI2CとPinクラスををインポート。
Ctrl + eを押してからコピペしてCtrl + d。
MicroPython v1.10-8-g8b7039d7d on 2019-01-26; ESP module with ESP8266
Type "help()" for more information.
>>>
paste mode; Ctrl-C to cancel, Ctrl-D to finish
===
===
from machine import I2C, Pin
i2c = I2C(scl=Pin(5), sda=Pin(4), freq=400000)
# I2C、Pinクラスのインポート
# I2Cオブジェクトの作成

===
>>>
freqは、400kHzで指定していますが、MCP23017では、1.7MHzでも稼働出来るようです。ただし、電圧が4.5~5.5V必要となるためNodeMCU(3.3V駆動)では使用しません。

I2Cでの接続確認は、以下。
>>> i2c.scan()
[32]
>>>
正常に接続されていればスレーブアドレスが10進数で表示。"32"は、16進数だと"0x20"。

下記は、以降で使用するI2Cの主だったメソッド。
I2C.readfrom_mem(addr, memaddr, nbytes, *, addrsize=8)
addr:スレーブアドレス - 0x20〜0x27
memaddr:メモリアドレス / レジスタアドレス
nbytes:読み込むデータサイズをbyte単位で指定
addrsize:アドレスサイズ(ESP8266では8bit固定で使用不可)
I2C.writeto_mem(addr, memaddr, buf, *, addrsize=8)
addr:スレーブアドレス - 0x20〜0x27
memaddr:メモリアドレス / レジスタアドレス
buf:バッファ / byte型 - コマンド/データ
addrsize:アドレスサイズ(ESP8266では8bit固定で使用不可)
bufは、コマンドとデータをまとめて書く事も可能ですが、ここでは理解しやすいように分けて書くようにしています。興味のある方は試してみて下さい。

MivroPythonでMCP23017の操作

MCP23017の操作は、基本的に以下のレジスタアドレスを呼び出して読み書きするだけです。それ以上も以下も出来ません。
MCP23017のレジスタアドレス
Access to: Address
IOCON.BANK = 0
Address
IOCON.BANK = 1
Function
IODIRA 0x00(0b00000) 0x00(0b00000) I/O方向レジスタ
0:OUTPUT/1:INPUT
IODIRB 0x01(0b00001) 0x10(0b10000)
IPOLA 0x02(0b00010) 0x01(0b00001) 入力極性ポートレジスタ
0:same logic state/1:opposite logic state
IPOLB 0x03(0b00011) 0x11(0b10001)
GPINTENA 0x04(0b00100) 0x02(0b00010) 状態変化割り込みピン設定レジスタ
0:Disable/1:Enable
GPINTENB 0x05(0b00101) 0x12(0b10010)
DEFVALA 0x06(0b00110) 0x03(0b00011) 状態変化割り込み用の既定値 コンペアレジスタ
7〜0 bitに比較値を設定
DEFVALB 0x07(0b00111) 0x13(0b10011)
INTCONA 0x08(0b01000) 0x04(0b00100) 状態変化割り込み制御レジスタ
0:ピンの変化のみ参照/1:DEFVAL設定値との比較参照
INTCONB 0x09(0b01001) 0x14(0b10100)
IOCON 0x0A(0b01010) 0x05(0b00101) I/Oエクスパンダコンフィグレーションレジスタ
7〜1 bitで設定
IOCON 0x08(0b01011) 0x15(0b10101)
GPPUA 0x0C(0b01100) 0x06(0b00110) GPIOプルアップ抵抗レジスタ
0:無効化 / 1:有効化
GPPUB 0x0D(0b01101) 0x16(0b10110)
INTFA 0x0E(0b01110) 0x07(0b00111) 割り込みフラグレジスタ ※読込みのみ
0:割り込み無し/1:割り込みを発生
INTFNB 0x0F(0b01111) 0x17(0b10111)
INTCAPA 0x10(0b10000) 0x08(0b01000) 割り込み発生ポート値レジスタ ※読込みのみ
0:Low / 1:High
INTCAPB 0x11(0b10001) 0x18(0b11000)
GPIOA 0x12(0b10010) 0x09(0b01001) 汎用I/Oポ ートレジスタ
0:Low / 1:High
GPIOB 0x13(0b10011) 0x19(0b11001)
OLATA 0x14(0b10100) 0x0A(0b01010) 出力ラッチレジスタ
0:Low / 1:High
OLATNB 0x15(0b10101) 0x0A(0b11010)
表を見ていると、レジスタアドレスがBank0、Bank1に分かれていますね。
これは、A、B二つのグループのレジスタアドレスを連続して(16bit)管理するか、分割して(8bit)管理するのかの違いです。BANK 1のアドレスは一見バラバラに見えますがBANK 0を右ビットローテーション(環状シフト)した値に統一されています。
ちなみに、ビットローテーションとは以下の様な働き。
呼称はローテート・ビット、ビット・ローテーション、Circular Shiftなど様々。
この辺りの設定はコンフィグレーションレジスタ(IOCON)で行い、標準でBank0になるように設定されています。
話が外れはじめたので、実際にレジスタアドレスを読み込んで値を確認。
>>> i2c.readfrom_mem(0x20, 0x00, 1)
b'\xff'
>>>
I/O方向レジスタレジスタ(IODIRA)の値を取得してみました。
結果b'\xff'('0b11111111')の戻り値。
IODIRAは、0:OUTPUT/1:INPUTなので、全てのGPIOが入力に設定されているようです。
取得された値の上位ビットから下位ビットへの並びは、GPIOポートGPx7GPx0の並びに対応しています。
書込みも試してみます。
>>>
>>>
i2c.writeto_mem(0x20, 0x00, b'\x55')
i2c.readfrom_mem(0x20, 0x00, 1)

b'\x55'
>>>
IODIRAへ0x55(0b01010101)の値を書き込み、戻り値を確認。
ちょっと動作がわかりづらいのでLEDを繋いでみましょう。

MivroPythonとMCP23017でLチカ

まずは、ブレッドボードを増設してLEDを配線。
LEDと抵抗は、手元にあったものを適当に接続しています。
ちゃんと抵抗値を計算して接続しましょう!
って事で、Lチカコード。
from time import sleep
from machine import I2C, Pin

i2c = I2C(scl=Pin(5), sda=Pin(4), freq=400000)
i2c.writeto_mem(0x20, 0x00, b'\x00')

for i in range(10) :
	i2c.writeto_mem(0x20, 0x12, b'\xFF')
	sleep(0.5)
	i2c.writeto_mem(0x20, 0x12, b'\x00')
	sleep(0.5)
8個のLEDがまとまって点滅するので、眩しいです。
続いて、もう少しヒネったLチカ。
from time import sleep
from machine import I2C, Pin

i2c = I2C(scl=Pin(5), sda=Pin(4), freq=400000)
i2c.writeto_mem(0x20, 0x00, b'\x00')

b = 0xFF

for i in range(10) :
	i2c.writeto_mem(0x20, 0x12, b'\xFF')
	sleep(0.5)
	i2c.writeto_mem(0x20, 0x12, b'\x00')
	sleep(0.5)

for i in range(7) :
	i2c.writeto_mem(0x20, 0x12, b.to_bytes(1, 'big'))
	sleep(0.5)
	b = b >> 1

for i in range(9) :
	i2c.writeto_mem(0x20, 0x12, b.to_bytes(1, 'big'))
	sleep(0.5)
	b = b << 1
10回点滅したあと、一つづつ点灯しているLEDが現象し、残った1つが左方向に移動して消灯するだけのコード。

0 件のコメント:

コメントを投稿