端っこプログラマーの手帳

主にプログラムに関する手記です

【ラズパイ】LEDをたくさん点灯させたい!! IOエキスパンダ(MCP23017)によるGPIOポート拡張

「LEDをたくさん点灯させたいけど、ラズパイのGPIOポートでは足りない..さてどうしよう?」

方法を調べてみると、IOエキスパンダ(MCP23017)というデバイスを使えば増設できるということ。 I2Cを使ってラズパイからデータを送ると、16本あるIOエキスパンダのポートをうまいこと制御できる。 さらに、ラズパイ1台に対して最大で8個まで複数接続OK (IOエキスパンダのICアドレスは電流を加えることにより変更可能。それが8通りある) つまり最大、16 x 8 で 128ポート!! これは結構な数です!! 値段も、1個 120円と安価なのがうれしい限り。早速試してみた。

f:id:kzhishu:20160718101351j:plain:w600

ラズパイ
RaspberryPi2(ModelB)

I2Cデバイス
MCP23017
http://akizukidenshi.com/catalog/g/gI-09486/

ラズパイ側の準備

I2C有効化

#「9 Advanced Options」でI2Cを有効化する
$ raspi-config  

# backlist i2c-bcm2708 が記述してあればコメントアウトする
$ cat /etc/modprobe.d/raspi-blacklist.conf

# i2c-dev が記述されていること確認(なければ追記する)
$ cat /etc/modules

I2C用のコマンドラインツール

$ sudo apt-get install i2c-tools

smbusモジュール(PythonからI2Cを使用するため)

python3対応版があったのでそれを使用

$ sudo apt-get install python3-smbus
$ python3
>> import smbus

ちゃんとimportできること確認

配線

まずは、ラズパイに対してMCP2301が1つの場合の配線。こんな感じになりました。

f:id:kzhishu:20160718102741j:plain

配線に関してはこちらのサイトを参考にしました。 raspi.tv

I2Cの仕組みについてはここら辺で理解。

I2Cバスを使ってみよう - やまねこのマイコン実験室

実処理は、smbusライブラリに任せるので詳細はなんとなく(←情けないが...)で SCL(Serial Clock)のクロック信号でタイミングを取り、SDA(Serial Data)でデータを通信。 そのときに、SDAでスレーブ側のアドレスとデータを送るということを把握しておきましょうか。

コマンドラインから出力制御

接続デバイスの確認

$ sudo i2cdetect -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: 20 -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

無事配線が済み、スレーブ側のICアドレスが、0x20であることがわかる。
コマンドラインでデータを送ってみる。以下の形式で実行。

$ sudo i2cset -y 1 [ICアドレス] [レジスタアドレス] [データ]

ICアドレス

上でみた、スレーブ側のICアドレス。今回は、0x20 となる

レジスタアドレス

バイスのどの機能を使うのかを指定。GPIOポートの初期化や出力など。 レジスタアドレスを指定することによりその機能を使用できる。

ここでデータシートで、各ポートとレジスタアドレスについて確認しておきます。

f:id:kzhishu:20160718105324p:plain

f:id:kzhishu:20160718105335p:plain

(引用元:http://akizukidenshi.com/download/ds/microchip/mcp23017_mcp23s17.pdf

これを踏まえて整理するとこんな感じになります。

使用するレジスタ 説明
IODIRA GPA0(21) 〜 GPA7(28)の出入力設定をする(1:入力、0:出力) IO0 ~ IO7 と GPA0 ~ GPA7 が対応する
OLATA GPA0(21) 〜 GPA7(28)の出力制御(出力オン/オフ)を行う OL0 ~ OL7 と GPA0 ~ GPA7 が対応する

データ

例えば、GPA7, GPA5, GPA3 を出力して、LED点灯を行いたいときは GPA0(21) 〜 GPA7(28) の出力の設定(1:出力する、0:出力しない)をビットで渡してあげれば良いので 0b10101000 となる(GPA7が先頭となる点に注意!!)最終的には、16進数の 0xA8 で渡す。

上記内容をもとにコマンドラインを実行する

使用GPIOピンの初期化

$ sudo i2cset -y 1 0x20 0x00 0x00

LED点灯(GPA7, GPA5, GPA3 を オン)

$ sudo i2cset -y 1 0x20 0x14 0xA8

LED消灯(GPA7, GPA5, GPA3 を オフ)

$ sudo i2cset -y 1 0x20 0x14 0x00

Pythonから制御

シンプルに、指定のピンを5秒点灯したあと消灯するプログラム
基本的には、コマンドでやったことを bus.write_byte_data 関数を使いPythonから行っているのみ。

# -*- coding: utf-8 -*-

import smbus
import time

CHANNEL   = 1      # i2c割り当てチャンネル 1 or 0
ICADDR    = 0x20   # スレーブ側ICアドレス
REG_IODIR = 0x00   # 入出力設定レジスタ
REG_OLAT  = 0x14   # 出力レジスタ

bus = smbus.SMBus(CHANNEL)

# ピンの入出力設定
bus.write_byte_data(ICADDR, REG_IODIR, 0x00)

# GPA3, GPA5, GPA7 出力オン
bus.write_byte_data(ICADDR, REG_OLAT, 0xA8)
time.sleep(5)
# GPA3, GPA5, GPA7 出力オフ
bus.write_byte_data(ICADDR, REG_OLAT, 0x00)

複数接続!!

f:id:kzhishu:20160718110559j:plain

$ sudo i2cdetect -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: 20 21 -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

スレーブ側のアドレスがきちんと2つになっているのでそれぞれで指定してあげる Pythonのプログラムはこんな感じ

# -*- coding: utf-8 -*-

import smbus
import time

CHANNEL    = 1      # i2c割り当てチャンネル 1 or 0
ICADDR1    = 0x20   # スレーブ側ICアドレス1
ICADDR2    = 0x21   # スレーブ側ICアドレス2
REG_IODIR  = 0x00   # 入出力設定レジスタ
REG_OLAT   = 0x14   # 出力レジスタ

bus = smbus.SMBus(CHANNEL)

# ピンの入出力設定
bus.write_byte_data(ICADDR1, REG_IODIR, 0x00)
bus.write_byte_data(ICADDR2, REG_IODIR, 0x00)

# GPA3, GPA5, GPA7 出力オン
bus.write_byte_data(ICADDR1, REG_OLAT, 0xA8)
bus.write_byte_data(ICADDR2, REG_OLAT, 0xA8)
time.sleep(5)
# GPA3, GPA5, GPA7 出力オフ
bus.write_byte_data(ICADDR1, REG_OLAT, 0x00)
bus.write_byte_data(ICADDR2, REG_OLAT, 0x00)

これで複数接続ができた。 データシートを読むのはきつかったが実現できました。 さて、たくさんLEDを点灯してみますか♪