2019年7月6日土曜日

NodeMCU DevKit & MicroPython & SPI MPC23S17

MCP23S17 "SPI"のピン配置と配線

MCP23S17のピン配置
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 CS I チップセレクト ハードウェアリセット I RESET 18
12 SCK I シリアルクロック入力 ハードウェア
アドレスピン
I A2 17
13 SI I シリアルデータ入力 A1 16
14 SO O シリアルデータ出力 A0 15
MCP23S17により拡張されるポートは、GPA0GPA7 & INTAGPB0GPB7 & INTBとA、B2つに別れています。
A、Bの各GPIOは8個なので1byte(8bit)単位で管理しやすいのと、8bitのみのMCP23008という製品もあるので、その辺りも兼ね合いがあるのかもですけどね。

実際の配線を見て行きましょう。
まずは、NodeMCU DevKitのハードウェアSPIのピン配置を確認。
次にMCP23S17との配線。
ブレッドボードの電源は、NodeMCU DevKitの3V3GNDへ接続。
SCKSISOは、NodeMCU DevKitのHSPI_CLKHSPI_MOSIHSPI_MISOと接続。 CSは、先の3つと異なり空いているGPIOならどれでも良いのですが、D8HSPI_CSが割り当てられているので、こちらを使用。
A0A1A2の3つは、 CSでの選択が既定となるため必須ではありませんが、回路的に必ずプルアップもしくはプルダウン接続しておく必要があります。
RESETをプルアップしてリセットを無効化。
以上で検証に必要な配線は完了。

MivroPythonでMCP23S17へSPIで接続

ここから先は、NodeMCU DevKitでの作業。
SPI通信では、データシートに従いスレーブアドレスに続けて以下のようにR/Wビットを設定し制御バイトとして送信します。
CS
制御バイト
D7 D6 D5 D4 D3 D2 D1 D0
0 1 0 0 A2 A1 A0 R/W
スレーブアドレス Read : 1
Write : 0
制御バイトアドレス一覧
D7 D6 D5 D4 D3 D2 D1 D0
0 1 0 0 A2 A1 A0 R W
0 1 0 0 0 0 0 1 0 0x41/0x40
0 1 0 0 0 0 1 1 0 0x43/0x42
0 1 0 0 0 1 0 1 0 0x45/0x44
0 1 0 0 0 1 1 1 0 0x47/0x46
0 1 0 0 1 0 0 1 0 0x49/0x48
0 1 0 0 1 0 1 1 0 0x4B/0x4A
0 1 0 0 1 1 0 1 0 0x4D/0x4C
0 1 0 0 1 1 1 1 0 0x4F/0x4E
で、いよいよ実際に動かしてみます。
NodeMCUを起動して、必要となるSPIと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 SPI, Pin
spi = SPI(1, baudrate=10000000, polarity=0, phase=0)
cs = Pin(15, Pin.OUT)
cs.value(1)
# SPI、Pinクラスのインポート
# SPIオブジェクトの作成
# CSピンのアサイン
# CSピンをHIGHに

===
>>>
idは、0 : 使用不可 / 1 : ハードウェアSPI / -1 : ソフトウェアSPI
baudrateは、ハードウェアSPIの最大値10MHz、極性と位相も設定。
CSは、GPIO15を指定。

SPIでの接続確認は、、、、残念ながらありません。
という事で、SPIで使用するメソッドを先に確認。
SPI. read(nbytes, write=0x00)
nbytes:受信するバイト数を指定。
write :連続して送信する1byteを数値型(16進数)で指定
SPI.readinto(buf, write=0x00)
buf:受信するバッファをbytesオブジェクトで指定
write:連続して送信する1byteを数値型(16進数)で指定
SPI.write(buf)
buf:送信するバッファをbytes型で指定
SPI.write_readinto(write_buf, read_buf)
write_buf:受信するバッファをbytearrayで指定
read_buf:送信するバッファをbytesオブジェクトで指定
write_buf read_buf の両方のバッファは同じ長さに指定。
SPI. read()以外は戻り値はナシ
pybの様に、"Read"ではなく"Receive"、"Write"ではなく"Send"が正しい気がする。

MivroPythonでMCP23S17の操作

MCP23S17の操作は、基本的に以下のレジスタアドレスを呼び出して読み書きするだけです。それ以上も以下も出来ません。
MCP23S17のレジスタアドレス
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になるように設定されています。
話が外れはじめたので、実際にレジスタアドレスを読み込んで値を確認。
>>>
paste mode; Ctrl-C to cancel, Ctrl-D to finish
===
===
===
===
cs.value(0)
spi.write(b'\x41\x00')
spi.read(22)
cs.value(1)
# CSピンをLOWに変更
# 制御バイト(0x41)、レジスタアドレス(0x00)を送信
# 上で指定したアドレス(0x00)から22byte(0x00〜0x15)を受信
# CSピンをHIGHに戻す

===
b'\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
>>>
SPI通信では、CSピンのHIGH、LOWが通信開始の終了のタイミングとなります。
受信した22byteの冒頭の戻り値b'\xff'('0b11111111')は、I/O方向レジスタレジスタ(IODIRA)で、0:OUTPUT/1:INPUTなので、全てのGPIOが入力に設定されている事を示しています。取得された値の上位ビットから下位ビットへの並びは、GPIOポートGPx7GPx0の並びに対応しています。
これらの詳細は、データシートの"TABLE 3-2"、"TABLE 3-3"を参照。
書込みも試してみます。
>>>
paste mode; Ctrl-C to cancel, Ctrl-D to finish
===
===
===
cs.value(0)
spi.write(b'\x40\x00\x55')
cs.value(1)
# CSピンをLOWに変更
# 制御バイト(0x40)、レジスタアドレス(0x00)に"0x55"を送信
# CSピンをHIGHに戻す

>>>
制御バイトは、書き込み用に0x40(0b01000000)、IODIRA(0x00)へ0x55(0b01010101)の値を書き込み。
正常に書き込みが行われたのか確認。
>>>
paste mode; Ctrl-C to cancel, Ctrl-D to finish
===
===
===
===
cs.value(0)
spi.write(b'\x41\x00')
spi.read(22)
cs.value(1)
# CSピンをLOWに変更
# 制御バイト(0x41)、レジスタアドレス(0x00)を送信
# 上で指定したアドレス(0x00)から22byte(0x00〜0x15)を受信
# CSピンをHIGHに戻す

===
b'U\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
>>>
先頭の値が"U"なのは、キャラクタに変換表示されているためなので、問題なく送信されているようです。ただ、ちょっと動作がわかりづらいのでLEDを繋いでみましょう。
※IODIR(0x00、0x01)などポートに関連するアドレスの変更直後は、GPIO(0x12〜0x15)の値に影響が出るようです。

MivroPythonとMCP23S17でLチカ

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

spi = SPI(1, baudrate=10000000, polarity=0, phase=0)
cs = Pin(15, Pin.OUT)
cs.value(1)

cs.value(0)
spi.write(b'\x40\x00\x00')
cs.value(1)

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

b = 0xFF

spi = SPI(1, baudrate=10000000, polarity=0, phase=0)
cs = Pin(15, Pin.OUT)
cs.value(1)

cs.value(0)
spi.write(b'\x40\x00\x00')
cs.value(1)

for i in range(10) :
 cs.value(0)
 spi.write(b'\x40\x12\xFF')
 cs.value(1)
 sleep(0.5)
 cs.value(0)
 spi.write(b'\x40\x12\x00')
 cs.value(1)
 sleep(0.5)

for i in range(8) :
 cs.value(0)
 spi.write(b'\x40\x12')
 spi.write(b.to_bytes(1, 'big'))
 cs.value(1)
 sleep(0.5)
 b = b >> 1

b = 0x01

for i in range(9) :
 cs.value(0)
 spi.write(b'\x40\x12')
 spi.write(b.to_bytes(1, 'big'))
 cs.value(1)
 sleep(0.5)
 b = b << 1
10回点滅したあと、一つづつ点灯しているLEDが現象し、残った1つが左方向に移動して消灯するだけのコード。
ベタに書くと長くなるので、メソッドでまとめないと使いづらいですね。

0 件のコメント:

コメントを投稿