本記事について
電子回路経験がありGPIO、I2Cという単語は知っている前提としますのでその辺の用語解説を省きます。
USBGPIO8とは
シリアル通信でGPIOを制御できるデバイスです
Numato Labs社のUSB GPIO(デジタル入出力)ボードで日本だとエレファイン通販から購入できます。
https://www.elefine.jp/SHOP/USBGPIO8.html
ADコンバータも付いているので各種単体実験に便利です。
I2C通信について
http://www.picfun.com/f1/f06.html
こちらの解説がとても分かりやすいです。
なにをするか
python でシリアル通信を制御してUSBGPIO8を制御しEEPROMに1バイト書き込み、その後読み込んで書き込まれている事を確認します。
EEPROM は 24LC64 を使用します。
24LC64はこちらの製品です → http://akizukidenshi.com/catalog/g/gI-00194/
ハードウェアの準備
USBGPIO8 の 0番ポートをSDA とします。
USBGPIO8 の 1番ポートをSCL とします。
各々10kΩでプルアップします。
上記構成でブレッドボード上で接続してます。
USBGPIO8のコマンドについて
エレファインのサイトや公式からマニュアルがダウンロードできますので詳しくはそちらを参照ください。
最初からプログラム制御ではなくTeraterm等のシリアル端末ソフトから制御実験することをおすすめします。
gpio [command] [arg]\r
という形式です、arg は有ったり無かったりします、末尾の ¥r を忘れないように注意します。
書き込み系のコマンドを送信すると
[送ったコマンド]\n\r>
# 例えば gpio set 0\n\r>
とコマンドにプロンプトを付けて返ってきます。
また読み込み系コマンドを送信すると
[送ったコマンド]\n\r[戻り値]\n\r>
# 例えば gpio read 0\n\r1\n\r>
# \n\r に挟まれている 1 が戻り値
とコマンドに戻り値とプロンプトを付けて返ってきます。
GPIOのLOW-HIGH制御
0番ポートをLOWにするには clear コマンドを使います
SerialInstance = serial.Serial("COM4", 115200, timeout=0.01)
SerialInstance.write("gpio clear 0\r".encode())
0番ポートをHIGHにするには set コマンドを使います
SerialInstance = serial.Serial("COM4", 115200, timeout=0.01)
SerialInstance.write("gpio set 0\r".encode())
コマンド
timeout=0.01 としてタイムアウトを設定します、設定しないと一生応答を返さなくなるので設定したほうが良いです
GPIOの読み取り
0番ポートのLOW-HIGH状態を読み取るには
SerialInstance = serial.Serial("COM4", 115200, timeout=0.01)
SerialInstance.write("gpio read 0\r".encode())
とします
gpio read 0\n\r0\n\r>
と応答が返ってくればGPIOの状態は0(LOW)ということになります。
プログラム構造
0=LOW
1=HIGH
2=読み取りクロック
と定義します。
定義に従い配列を作成し、その配列の通りにSCL、SDAを制御します。
例
1バイト書き込む場合
EEPROMデバイスアドレス 0x50
書き込みアドレス 0xa0
書き込みデータ 0x33
この条件の時以下の配列を作成する
[
1, 0, 1, 0, 0, 0, 0, 0, 2,
0, 0, 0, 0, 0, 0, 0, 0, 2,
1, 0, 1, 0, 0, 0, 0, 0, 2,
0, 0, 1, 1, 0, 0, 1, 1, 2
]
各行を解説する
[1, 0, 1, 0, 0, 0, 0, 0, 2] 先頭7ビット 0x50 のアドレスを表し、8ビット目は書き込みフラグ、9ビット目はACKの読み出し
[0, 0, 0, 0, 0, 0, 0, 0, 2] 先頭8ビットは書き込みアドレスの上位バイト、9ビット目はACKの読み出し
[1, 0, 1, 0, 0, 0, 0, 0, 2] 先頭8ビットは書き込みアドレスの下位バイト、9ビット目はACKの読み出し
[0, 0, 1, 1, 0, 0, 1, 1, 2] 先頭8ビットは書き込む1バイトのデータ、9ビット目はACKの読み出し
次に1バイト読み込む場合
[
1, 0, 1, 0, 0, 0, 0, 1, 0,
2, 2, 2, 2, 2, 2, 2, 2, 1
]
各行を解説する
[1, 0, 1, 0, 0, 0, 0, 0, 0] 先頭7ビット 0x50 のアドレスを表し、8ビット目は読み込みフラグ、9ビット目はACKの送信
[2, 2, 2, 2, 2, 2, 2, 2, 1] 先頭8ビットは読み出し、9ビット目はNOACKの送信(読み込み完了の通知)
上記のようにSDAのパターンを配列化しそれに従いクロックとデータを送信・受信する構造とする。
ソースコード
# i2c_byte_write_and_read.py
import serial
import sys
import time
import threading
SerialInstance = None
def SerialInit(comString):
global SerialInstance
SerialInstance = serial.Serial(comString, 115200, timeout=0.01)
#SerialInstance = serial.Serial(comString, 19200, timeout=0.1)
def SerialEnd():
SerialInstance.close()
def SerialTalk(cmd, response=False):
readLen = len(cmd) + 1 # gpio read 0\n\r # 最初から \r が付いているので +2 ではなく +1 する
if response == True:
readLen += 3 # N\n\r
readLen += 1 # >
cnt = SerialInstance.write(cmd.encode())
res = SerialInstance.read(readLen)
res = res.decode("utf-8").strip()
return res
# 返される文字数を正確に読み取り切ること、1文字でも過不足があると応答待ち状態が続く事になる
def gpioHigh(n):
SerialTalk("gpio set {}\r".format(n))
def gpioLow(n):
SerialTalk("gpio clear {}\r".format(n))
def gpioRead(n):
res = SerialTalk("gpio read {}\r".format(n), response=True)
return res
def ByteToLH(b):
lh = []
for i in range(8):
if (b << i & 128) == 0:
lh.append(0)
else:
lh.append(1)
return lh
def SDA_LOW():
gpioLow(0)
def SDA_HIGH():
gpioHigh(0)
def SCL_LOW():
gpioLow(1)
def SCL_HIGH():
gpioHigh(1)
def READ_DATA():
return gpioRead(0)
def parseData(all):
res = []
for l in all:
a = l.split("\n\r")
res.append(a[1])
return res
# 0 = LOW
# 1 = HIGH
# 2 = READ
# スタートコンディション: SCLがHIGHの間にSDAをLOWにする
# ストップコンディション: SCLをHIGHにしてから、SDAをHIGHにする
def WriteProc():
ctrlByte = ByteToLH(constDeviceAddress << 1)
addrHigh = ByteToLH((constDataAdress >> 8) & 0xff)
addrLow = ByteToLH(constDataAdress & 0xff)
write1Byte = ByteToLH(constData)
cmdWrite = ctrlByte + [2] + addrHigh + [2] + addrLow + [2] + write1Byte + [2]
# START CONDITION
SCL_HIGH()
SDA_LOW()
size = len(cmdWrite)
data = []
for i in range(size):
SCL_LOW()
d = cmdWrite[i]
if d == 0:
SDA_LOW()
elif d == 1:
SDA_HIGH()
SCL_HIGH()
if d == 2:
response = READ_DATA()
data.append(response)
SCL_LOW() # LOWに戻す
SDA_LOW() # LOWに戻す
# STOP CONDITION
SDA_LOW()
SCL_HIGH()
SDA_HIGH()
# 書き込み待ち、仕様では5msだが適当に長く待つ
time.sleep(0.1)
print(parseData(data))
print("write end")
def ReadProc():
#
#
# set address
ctrlByte = ByteToLH(constDeviceAddress << 1)
addrHigh = ByteToLH((constDataAdress >> 8) & 0xff)
addrLow = ByteToLH(constDataAdress & 0xff)
cmdSetAddress = ctrlByte + [2] + addrHigh + [2] + addrLow + [2]
# START CONDITION
SCL_HIGH()
SDA_LOW()
size = len(cmdSetAddress)
data = []
for i in range(size):
SCL_LOW()
#print(i)
d = cmdSetAddress[i]
if d == 0:
SDA_LOW()
elif d == 1:
SDA_HIGH()
SCL_HIGH()
if d == 2:
response = READ_DATA()
data.append(response)
SCL_LOW() # LOWに戻す
SDA_LOW() # LOWに戻す
# STOP CONDITION
SCL_HIGH()
SDA_HIGH()
print(parseData(data))
#
#
# read data
ctrlByte = ByteToLH((constDeviceAddress << 1) | 0x01)
readByte = [2] * 8
cmdReadByte = ctrlByte + [0] + readByte + [1]
# START CONDITION
SCL_HIGH()
SDA_LOW()
size = len(cmdReadByte)
data = []
for i in range(size):
SCL_LOW()
#print(i)
d = cmdReadByte[i]
if d == 0:
SDA_LOW()
elif d == 1:
SDA_HIGH()
SCL_HIGH()
if d == 2:
response = READ_DATA()
data.append(response)
SCL_LOW() # LOWに戻す
SDA_LOW() # LOWに戻す
# STOP CONDITION
SCL_HIGH()
SDA_HIGH()
print(parseData(data))
print("read end")
# defines
constDeviceAddress = 0x50 # ICの設定に従う
constDataAdress = 0x0a88 # ICのアドレス範囲内で適当に決める、今回は最大2バイト(0-65535)と想定したコード、3バイトアドレスの場合はコード修正が必要
constData = 0x44 # 0-255 の範囲で適当に決める
def run(comString):
SerialInit(comString)
WriteProc()
ReadProc()
SerialEnd()
if __name__ == "__main__":
run(sys.argv[1])
# python i2c_byte_write_and_read.py COM4
ソースコード解説
使い方
Linuxの場合
python i2c_byte_write_and_read.py /dev/ttyUSB0
Windowsの場合
python i2c_byte_write_and_read.py COM4
戻り値の見方
['0', '0', '0', '0'] # コントロールバイトのACK、アドレス上位バイトのACK、アドレス下位バイトのACK、書き込みデータのACK
write end
['0', '0', '0'] # コントロールバイトのACK、アドレス上位バイトのACK、アドレス下位バイトのACK
['0', '0', '1', '1', '0', '0', '1', '1'] # 読み込んだ8ビットの状態
read end