Node.js(2) GPIO

Raspberry PiにNode.jsをインストールして、ExpressフレームワークでWebアプリを簡単に作成して動かすことができたが、何もデバイスを制御しない状態ではRaspberry Piを使う意味が無いので、まずはGPIOを制御するモジュールを追加して簡単な制御を行ってみる。

Node.jsのRaspberry Pi用GPIOパッケージは何種類も存在していて、ざっと調べてみた限りでは以下の三種類が良く使われているようだ。

  1. pi-gpio
  2. rpi-gpio
  3. onoff

今回はその三種類の中からonoffを試してみたが、最初に目についたというだけで特に理由はない。他のパッケージでも恐らく同様に使えるはず。

インストールはExpressでプロジェクトのひな型(プロジェクト名はledとする)を作ってからそのディレクトリに移動して

$ express -e led
$ cd led
$ npm install
$ npm install onoff

を実行するか、あるいは生成されたpackage.jsonにパッケージ名”onoff”を追加してから npm install を行えば良い。

{
  "name": "led",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "start": "node ./bin/www"
  },
  "dependencies": {
    "body-parser": "~1.12.0",
    "cookie-parser": "~1.3.4",
    "debug": "~2.1.1",
    "ejs": "~2.3.1",
    "express": "~4.12.2",
    "morgan": "~1.5.1",
    "serve-favicon": "~2.2.0",
    "onoff": "~1.0.2"
  }
}

このパッケージを使用してGPIO操作を行う場合、ユーザがgpioグループに属していればルート権限が不要なのでデフォルトの「pi」ユーザならそのままで使えるが、新たに作成したユーザでログインしている場合はsudoを付けてNode.jsを起動するか、あるいはそのユーザをgpioグループに属させる必要がある。

$ sudo gpasswd -a ユーザID gpio

GPIO操作のサンプルとして、まずは定番のLED点滅を行ってみる。
GPIOの適当なポートにLEDと電流制限抵抗をつなぎ、ポートに”1″を出力すると点灯、”0″を出力すると消灯する状態になっている場合、ブラウザからの操作でLEDをON/OFFするには/routes/users.jsファイルに以下のようなコードを書く。LEDはGPIO17(ピン番号11)に接続した。

var express = require('express');
var router = express.Router();

var GPIO = require('onoff').Gpio;
var led = new GPIO(17, 'out');

/* GET users listing. */
router.get('/', function(req, res, next) {
  console.log(req.query);
  var onoff = req.query.onoff;
  var msg = '';
  if (onoff == 'on') {
    led.writeSync(1);
    msg = 'LED ON';
  }
  if (onoff == 'off') {
    led.writeSync(0);
    msg = 'LED OFF';
  }
  res.send(msg);
});

module.exports = router;

ブラウザからHTTPのGETでパラメータが送られてきた時、”onoff”の値が”on”ならばLEDを点灯、”off”ならば消灯という動きをするので、本来ならばブラウザで表示するHTMLファイルを作成しなければならないが、動作を試すだけならばGETのパラメータを直接URLに書いて確認できる。

プロジェクトのディレクトリに移動して

$ npm start

でWebアプリを起動した後、サーバが起動して待受け状態になるまで少し待ってから、PCのブラウザで以下のようにアクセスする。

Raspberry PiのIPアドレス:3000/users?onoff=on

スクリーンショット 2015-05-12 10.38.35
全て正しければこれでRaspberry PiのGPIOに接続したLEDが点灯するはず。URLの最後を”onoff=off”に変えれば消灯する。
続きを読む »

広告

PySide

pythonでGUIを構築するためのライブラリ(ツールキット)はTkinter、wxPython、PyQt、PySide、PyGTK、PyGame等色々と存在しているが、種類が多くなると何を使うべきなのか悩むことになるし、ネット上での情報が分散してしまうので選択肢が多いのは良いことばかりとは限らない。

最も手軽に利用できるのは標準でPythonに含まれているTkinterで、何もインストールせずに使えるし単純なGUIならコードも短く書ける。その反面複雑なGUIになると思ったようにデザインするのが難しいという印象。これについては以前使ってみたPyGameでも似たような感じだったので、VisualStudioのようなインタラクティブにコントロールを配置できるリソースエディタが用意されているものという基準で選択すると、wxPythonとQt(PyQt、PySide)が良さそうに思える。サンプルコードの量などもその二つが豊富なようだ。

今回はその中からPySideを使ってみる事にした。同じQtのPythonバインディングであるPyQtではなくPySideを選んだのはライセンスの違いによるものだが、個人的に使うにはあまり気にしなくても良いかもしれない。機能的にはPyQtのほうが先行していて、コミュニティベースのPySideがその後を追うというのが現状らしいが、wxPythonの開発者がPySideの開発に加わったりと目まぐるしく状況が変化しているので、この先どうなるかは分からない。

QtにはQt DesignerというGUIコントロールを配置するためのリソースエディタが存在していて、PySideを使ってみようと思ったのはそれも理由の一つなのだが、最初はそれを使わずにコードだけでGUIアプリを作成してみた。

まず最初にPySideのインストールだが、Pyhton3では

$ sudo apt-get install python3-pyside

で簡単に行える。しかし、virtualenvでPythonの仮想環境を構築してある場合、このインストール方法では仮想環境下に追加されない。apt-getではなくpipでインストールすれば仮想環境下に追加されるはずなのだが、pipでのインストールは上手く行かなかった。ソースからのビルドも試してみたがやはりダメ。

import sys
sys.path.append('/usr/lib/python3/dist-packages')

と記述しておけば元のパッケージを参照できるようになるので、あまりスマートな方法ではないがとりあえずはこれで動かしておく。

Raspberry Piでやるからには何らかのハードを動かさないと面白く無いので、aitendoの100円コーナーで買った極小フルカラーLEDモジュールをGPIOに接続した。GPIOの制御にはQuick2Wireを使う。
画像1: 極小フルカラーLEDモジュール

RGBの三個のLEDが内蔵されていて、3ビットで7色の表示ができる。PWMを使えばもっと微妙な色の表現も可能だが、今回はそこまではやらない。

ウィンドウにボタンを三個表示させて、クリックするとRGBそれぞれをON/OFFできるようにした。接続はGPIO0(ピン番号17)をR、GPIO1(ピン番号18)をG、GPIO2(ピン番号27)をBに割り当てる。プログラムは以下の通り

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import sys
sys.path.append('/usr/lib/python3/dist-packages')
from PySide import QtCore, QtGui
from quick2wire.gpio import pins, Out

class ColorLED:
    led = [pins.pin(0, direction=Out),
           pins.pin(1, direction=Out),
           pins.pin(2, direction=Out)]
    def __init__(self):
        for e in self.led:
            e.open();
    def __del__(self):
        for e in self.led:
            e.close();
    def set(self, color):
        self.led[0].value = color & 0x01
        self.led[1].value = color & 0x02
        self.led[2].value = color & 0x04

class Window(QtGui.QWidget):
    def __init__(self):
        QtGui.QWidget.__init__(self)    
        self.btn_r = QtGui.QPushButton('RED')
        self.btn_g = QtGui.QPushButton('GREEN')
        self.btn_b = QtGui.QPushButton('BLUE')
        layout = QtGui.QHBoxLayout()
        layout.addWidget(self.btn_r)
        layout.addWidget(self.btn_g)
        layout.addWidget(self.btn_b)
        self.setLayout(layout)
        self.setMinimumSize(400, 100)
        self.btn_r.clicked.connect(lambda: self.click_btn(0x01))
        self.btn_g.clicked.connect(lambda: self.click_btn(0x02))
        self.btn_b.clicked.connect(lambda: self.click_btn(0x04))
        self.led = ColorLED()
        self.color = 0
    def click_btn(self, n):
        self.color = self.color ^ n
        self.led.set(self.color)

if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)
    win = Window()
    win.show()
    sys.exit(app.exec_())

実行するとこのように表示される。

Raspberry Pi_001

次回はQt Designerを使ってもう少し複雑なGUIを作ってみる予定。

Quick2Wire(2)

前回インストールしたQuick2WireはGPIOのピンに対する入出力と、I2Cによるデバイスとの通信機能を持つので、まずはGPIOの機能について試してみる。

Pythonでプログラムを組んで入出力を行う事はもちろんできるが、Raspberry Piのコンソールから仮想デバイスに対してリダイレクトするだけでも同じことが行える。GPIOピンヘッダの11番ピンがBCMチップのGPIO17で、Quick2WireではPin0(ゼロ)として扱われているので、ここに適当なLEDと抵抗が外付けされているものとする。
ちなみにQuick2WireのGPIO番号とピンヘッダとBCMチップのGPIO番号は以下のように対応している(Raspberry Piのリビジョンがv2の場合)

GPIO 0 1 2 3 4 5 6 7
Header 11 12 13 15 16 18 22 7
BCM 17 18 27 22 23 24 25 4

この状態でコンソールから以下のコマンドを実行する

$ gpio-admin export 17

これで /sys/devices/virtual/gpio/ にgpio17というデバイスが作成されるので、あとはこのデバイスに対してIn/Outの設定と値の入出力を行えば良い。

$ echo out > /sys/devices/virtual/gpio/gpio17/direction
$ echo 1 > /sys/devices/virtual/gpio/gpio17/value

これで接続されているLEDが光るはずだ。
出力されている値はcatコマンドで確認できる。

$ cat /sys/devices/virtual/gpio/gpio17/value
1

使い終わったら以下のようにunexportしておく

$ gpio-admin unexport 17

この一連の仮想デバイスに対する操作は、実はRaspberry Piの基本機能でも可能である。Quick2Wireを使用しなくても、以下のようにすれば同じことができる。

$ echo 17 > /sys/class/gpio/export
$ echo out > /sys/class/gpio/gpio17/direction
$ echo 1 > /sys/class/gpio/gpio17/value
$ echo 0 > /sys/class/gpio/gpio17/value
$ echo 17 > /sys/class/gpio/unexport

以前のRaspbianでは管理者権限が無いとこの操作は行えなかったのだが、現在のバージョンではgpioグループに所属していれば一般ユーザでもGPIOの操作が可能になっている。デフォルトのユーザであるpiは既にgpioグループに所属しているのでそのままで使用できるが、新たに作成したユーザの場合は以下のコマンドでgpioグループに所属させる必要がある。

$ sudo adduser $USER gpio

ユーザをグループに所属させるには usermod コマンドを使用する方法もあるが、元々所属していたグループ情報が失われてしまう危険性があるので、使用しないほうが無難。

PythonでQuick2Wireを使用してGPIOの操作を行う場合は、quick2wire.gpioモジュールをインポートしてその中のPinsオブジェクトを使用すると簡単に入出力が行える。GPIO0(11番ピン)に接続されたLEDを点滅させるプログラムは、以下のようになる。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from quick2wire.gpio import pins, Out
from time import sleep

if __name__ == '__main__':
    try:
        with pins.pin(0, direction=Out) as led:
            while True:
                led.value = 1 - led.value
                sleep(1)
    except KeyboardInterrupt:
        print('\nbreak')

pinオブジェクトの生成時にはIn/Out以外にもプルアップの設定や、信号の変化による割り込みの設定も行える。細かく説明すると長くなるので、入力変化によるイベントのサンプルだけ書いておく。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import select
from quick2wire.gpio import pins, In, Out, Rising, Falling, Both

if __name__ == '__main__':
    led = pins.pin(0, direction=Out)
    btn = pins.pin(7, direction=In, interrupt=Both)
    with led,btn:
        epoll = select.epoll()
        epoll.register(btn, select.EPOLLIN|select.EPOLLET)
        try:
            while True:
                events = epoll.poll()
                for fileno,event in events:
                    if fileno == btn.fileno():
                        led.value = btn.value
        except KeyboardInterrupt:
            print('\nbreak')

GPIO0(11番ピン)にLED、GPIO7(7番ピン)にスイッチが接続されているものとして、スイッチのH/L変化によるイベントでLEDの状態を変化させている。
このサンプルのような単純な処理ではあまりメリットは無いが、selectを使うと複数のI/O処理を登録しておいてイベントで一括処理する事が可能になるので、複雑なプログラムになってくると必要とされる場面もあるかと思う。

Quick2WireのI2C機能については次回に。

Quick2Wire

Raspberry PiはLinuxベースのPCでありながら、I/Oポートに接続されたセンサから値を取得したり、モータやサーボを動かしたりというマイコンボード的な事が手軽にできるのが特徴で、Python言語からGPIOやI2Cを操作する手段も何種類か用意されている。

初期の頃から存在しているI2Cデバイス制御用モジュールsmbusは手軽にpythonから使えるのだが、実行時に管理者権限が必要となる。ユーザと管理者が通常は同一なRaspberry Piではあまり問題にはならないと思うが、リモートデスクトップ等の環境でどうしても一般ユーザ権限で動かさなければならないという場合に困る。

調べてみるとQuick2WireモジュールならI2Cの操作に管理者権限が不要という事なので、インストールして試してみる事にした。
apt-getではインストール出来ないので、少しばかり手順が煩雑になる。

Quick2Wireのドキュメントではvirtualenvを使用した仮想環境での使用が推奨されているので、virtualenvがインストールされていない場合は最初にその環境をセットアップ。ここでは仮想環境の名前を「python32env」とした。

$ sudo apt-get install python-pip
$ sudo apt-get install python-virtualenv
$ virtualenv -p python3 python32env

python32envディレクトリに移動してから

$ source bin/activate

で仮想環境を起動。

次にQuick2Wire-Gpio-Adminをインストール

$ git clone https://github.com/quick2wire/quick2wire-gpio-admin.git
$ cd quick2wire-gpio-admin/
$ make
$ sudo make install
$ cd ..

Quick2Wire-Python-APIをインストール

$ git clone https://github.com/quick2wire/quick2wire-python-api.git
$ cd quick2wire-python-api
$ export QUICK2WIRE_API_HOME=`pwd`
$ export PYTHONPATH=$PYTHONPATH:$QUICK2WIRE_API_HOME
$ git checkout
$ python3 setup.py install
$ cd ..

Quick2Wireを使うためにはユーザがgpioとi2cグループに所属している必要があるので、所属していない場合は以下のコマンドを実行してから再起動する。

$ sudo adduser $USER gpio
$ sudo adduser $USER i2c

pythonを起動して、Quick2WireモジュールがインポートできればOK

~/python32env $ python
Python 3.2.3 (default, Mar  1 2013, 11:53:50)
[GCC 4.6.3] on linux2 Type "help", "copyright", "credits" or "license" for more information.
>>> import quick2wire
>>>

Quick2Wireの使用法については次回に。

Dynamic LED

7セグメントLEDをマイコンにつないで表示するためには、ドットを含めると8本の制御線が必要になる。一個だけならば良いが、複数個になると個数x8本の制御線が必要になるので、そのままでは不可能。

良く使われているのがダイナミック点灯という方式で、これは7セグメントLEDを一個ずつ順番に点灯させ、高速に切り替えることで全ての桁が点灯しているように見せる、残像を利用した方式。この方式なら制御線は8本+LEDの個数だけで済む。

秋月電子通商で7セグメントLED4個をダイナミック点灯用に並べたモジュールが出ているので、これをRaspberry Piで試してみた。使用したのはカソードコモンの高輝度青色LEDタイプ

LEDのダイナミック点灯プログラムは高速なコンパイラ言語で作るのが普通で、Pythonのようなインタプリタ言語ではチラついたりして上手く行かないのでは無いか?と思ったのだが、結論から言うとそれほど問題なく使うことができた。
ただし他に重い処理が走っていない場合で、負荷が高くなるとチラつきが発生してしまう可能性は十分にある。

Raspberry PiとLEDモジュールの接続は、GPIOの左列でI2C用のポートを除いたGPIO4以降をLEDのセグメントA~Fの信号線、右列のGPIO14以降をLED1~4のセレクト用、GPIO7をDPの信号線として使用した。

7segWire

LEDセレクト用の信号線には47Ωの抵抗を入れてある。

プログラムは以下の通り。ダイナミック点灯処理はスレッドとして実装した。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import RPi.GPIO as GPIO
import threading
import time
import socket

ledport = (14, 15, 18, 23)
segport = (4, 17, 27, 22, 10, 9, 11, 7)
digit = ((1, 1, 1, 1, 1, 1, 0),     # 0
         (0, 1, 1, 0, 0, 0, 0),     # 1
         (1, 1, 0, 1, 1, 0, 1),     # 2
         (1, 1, 1, 1, 0, 0, 1),     # 3
         (0, 1, 1, 0, 0, 1, 1),     # 4
         (1, 0, 1, 1, 0, 1, 1),     # 5
         (1, 0, 1, 1, 1, 1, 1),     # 6
         (1, 1, 1, 0, 0, 0, 0),     # 7
         (1, 1, 1, 1, 1, 1, 1),     # 8
         (1, 1, 1, 1, 0, 1, 1),     # 9
         (0, 0, 0, 0, 0, 0, 0),     # Blank
         (0, 0, 0, 0, 0, 0, 1))     # Minus

class DynamicLed:
    def __init__(self):
        for n in range(8):
            GPIO.setup(segport[n], GPIO.OUT)
            GPIO.output(segport[n], False)
        for n in range(4):
            GPIO.setup(ledport[n], GPIO.OUT)
            GPIO.output(ledport[n], True)
    def set_digit(self, no, num):
        dot = num & 0x80
        num = num & 0x7F
        if(no == 0):
            GPIO.output(ledport[3], True)
        else:
            GPIO.output(ledport[no - 1], True)
        for n in range(7):
            GPIO.output(segport[n], digit[num][n])
        GPIO.output(segport[7], dot)
        GPIO.output(ledport[no], False)

rlock = threading.RLock()

class LedThread(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
        self.ss = DynamicLed()
        self.dig = [0, 0, 0, 0]
        self.running = True
    def run(self):
        while self.running:
            rlock.acquire()
            for n in range(0, 4):
                self.ss.set_digit(n, self.dig[n])
                time.sleep(0.005)
            rlock.release()
    def stop(self):
        self.running = False
    def set(self, a, b, c, d):
        rlock.acquire()
        self.dig[0] = a
        self.dig[1] = b
        self.dig[2] = c
        self.dig[3] = d
        rlock.release()
    def set_num(self, num, width=0, dot=-1):
        str = '{0:>.{width}f}'.format(num, width=width)
        dp = 0;
        pos = 3;
        rlock.acquire()
        for n in range(len(str) - 1, -1, -1):
            if str[n] == '.':
                dp = 0x80
            elif str[n] == '-':
                self.dig[pos] = 11  # MINUS
                pos = pos - 1
            else:
                self.dig[pos] = int(str[n]) | dp
                dp = 0
                pos = pos - 1
            if pos < 0:
                break
        for n in range(0, pos + 1):
            self.dig[n] = 10    # BLANK
        if dot >= 0:
            self.dig[dot] = self.dig[dot] | 0x80
        rlock.release()

if __name__ == "__main__":
    GPIO.setmode(GPIO.BCM)
    GPIO.setwarnings(False)
    # get ip address
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.connect(('google.com', 0)) # dummy connect
    ip = sock.getsockname()[0]
    sock.close()
    array = ip.rsplit('.')
    # led thread start
    led = LedThread()
    led.start();
    try:
        while True:
            led.set_num(int(array[0]), dot=3)
            time.sleep(1)
            led.set_num(int(array[1]), dot=3)
            time.sleep(1)
            led.set_num(int(array[2]), dot=3)
            time.sleep(1)
            led.set_num(int(array[3]))
            time.sleep(1)
    except KeyboardInterrupt:
        print '\nbreak'
    led.stop()
    led.join()
    GPIO.cleanup()

LEDに表示させる適当なデータが思い付かなかったので、Raspberry Pi自身のIPアドレスを取得して4回に分けて表示させるようにしてみた。実行すると以下のように表示される。

7segip

数字4桁だけでは表示できるデータ量が少ないので用途が限られるが、バックライト付きLCDよりも消費電力が少なく、離れた場所からでも目立つという利点がある。

桁数がこれ以上多くなるとRaspberry PiのGPIOで直接制御するのは難しくなるが、その場合はデコーダICを使うと、少ない制御線で更に多くのLEDをダイナミック点灯させる事が可能になる。
ただし、その場合はタイミングがまた変わってくるので、注意が必要だ。

I/O Expander(2)

I2C接続のI/O拡張チップMCP23017をRaspberry Piに接続して、I/Oポートを16bit分増やすことができたので、試しにそのポート全てにLEDをつなげてみた。R0012002

写真の最も右側のLEDが拡張ポートBのB7、最も左のLEDが拡張ポートAのB0になるように接続してある。LEDの+極が電流制限抵抗を介してVCCにつながっているので、拡張ポートの出力を0にするとLEDが点灯、1にするとLEDが消灯する。

これを見ただけで分かる人には何をやろうとしているか判ると思うが、残像によって空間に文字や図形を描くPOV(Persistent Of Vision)またはバーサライタと呼ばれるものをRaspberry Piで試してみようと思った次第。

実際に暗い所でこれを左右に振ると、こんなふうに見える。
R0012001表示したい図形を縦一列ごとに高速に切り替えて表示しているから、左右に振ると残像で元の画像が見える。

プログラムは前回のテストプラグラムとほとんど同じで、表示する図形データを順番に読み込んでMCP23017のポートAとポートBに書き込んでいるだけ。負論理でLEDが点灯するようになっているので、図形データは予めビット反転させてある。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import smbus
import time

pb = (0x80,0x80,0xFC,0xFC,
      0xFC,0x80,0x80,0x80,
      0xFF,0xFF,0xFC,0xF8,
      0xF9,0xF9,0xF8,0xFC,
      0xFF,0x80,0x80,0xFF,
      0xFF,0x80,0x80,0xFF,
      0xFF,0xFC,0xF8,0xF8,
      0xF1,0xF8,0xF8,0xFC,
      0xFF,0xFF,0xFF,0x80,
      0x80,0x9E,0x9E,0x8E,
      0x80,0xC0,0xF1,0xFF,
      0x88,0x88,0xFF,0xFF)
pa = (0x01,0x01,0xFF,0xFF,
      0xFF,0x03,0x01,0x01,
      0xFF,0x0F,0x03,0x03,
      0x31,0x21,0x23,0x27,
      0xFF,0x01,0x01,0xFF,
      0xFF,0x01,0x01,0xFF,
      0xFF,0x07,0x03,0xE1,
      0xF1,0xE1,0x03,0x07,
      0xFF,0xFF,0xFF,0x01,
      0x01,0x7F,0x7F,0x7F,
      0x7F,0xFF,0xFF,0xFF,
      0x01,0x01,0xFF,0xFF)

class i2c:
    def __init__(self, bus, addr):
        self.b = smbus.SMBus(bus)
        self.addr = addr
    def put(self, cmd, data):
        self.b.write_byte_data(self.addr, cmd, data)
    def get(self, cmd):
        return self.b.read_byte_data(self.addr, cmd)

class MCP23017(i2c):
    def __init__(self, porta, portb):
        i2c.__init__(self, 1, 0x27)
        i2c.put(self, 0x05, 0x00)   # Config
        i2c.put(self, 0x00, porta)  # PortA I/O Set
        i2c.put(self, 0x01, portb)  # PortB I/O Set
    def invert(self, port, data):
        i2c.put(self, 0x02 + port, data)    # Invert Input Bit
    def pullup(self, port, data):
        i2c.put(self, 0x0C + port, data)    # Pull-Up Input
    def write(self, port, data):
        i2c.put(self, 0x12 + port, data)
    def read(self, port):
        return i2c.get(self, 0x12 + port)

if __name__ == '__main__':
    try:
        expand = MCP23017(0x00, 0x00)
        while True:
            for i in range(48):
                expand.write(1, pb[i])
                expand.write(0, pa[i])
                time.sleep(0.001)
    except KeyboardInterrupt:
        print '\nbreak'

ソースを見ても判るように、このコードではLEDの振られている方向を関知しないので、逆方向に振ると裏返しの図形が表示されてしまう。これを防ぐためには何らかのセンサーが必要で、水銀スイッチや加速度センサを使って順方向の時だけ表示するようにすれば良い。

そのあたりについてはまた後日。

I/O Expander

Raspberry PiのGPIOには17ビット分のI/Oピンが存在しているが、他の機能と兼用になっているピンも多いため、17ビット全てを使用する事ができない場合もある。

I/Oが不足した時にそれを拡張する方法は何種類かあって、I/O拡張チップを使うのがそのうちの一つ。Microchip社のMCP23017はI2C接続で16bitのI/Oポート拡張ができる。

比較的入手が容易で、このチップを搭載した基板も出ているので試しやすい。一個では16bitまでだが、I2Cバスに8個まで同時に接続できるので、最大で128bitの拡張が可能だ。出力ポートのラッチや、入力ポートのプルアップ、割り込み機能もある。

今回はこのキットを使用したが、表面実装ICのハンダ付けが必要なので慣れていないと組み立ては難しいかも知れない。実装サービスを利用するか、DIPタイプを入手してブレッドボードで試すという手もある。DIPタイプはマルツや共立エレショップで入手可能。

Raspberry PiのI2Cポート(SCL/SDA)と基板のSCL/SDAピンを接続し、MCP23017のポートAのA0とA1にLED、ポートBのB0にタクトスイッチを接続した。スイッチは押すとGNDに接続されるようにしてある。
2013-05-23 11.23.46

このままだとスイッチが押されていない時は入力が開放状態となり、値が不安定になるので初期設定で内蔵プルアップが有効になるようにしてある。プログラムは以下の通り。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import smbus
import time

class i2c:
    def __init__(self, bus, addr):
        self.b = smbus.SMBus(bus)
        self.addr = addr
    def put(self, cmd, data):
        self.b.write_byte_data(self.addr, cmd, data)
    def get(self, cmd):
        return self.b.read_byte_data(self.addr, cmd)

class MCP23017(i2c):
    def __init__(self, porta, portb):
        i2c.__init__(self, 1, 0x27)
        i2c.put(self, 0x05, 0x00)   # Config
        i2c.put(self, 0x00, porta)  # PortA I/O Set
        i2c.put(self, 0x01, portb)  # PortB I/O Set
    def invert(self, port, data):
        i2c.put(self, 0x02 + port, data)    # Invert Input Bit
    def pullup(self, port, data):
        i2c.put(self, 0x0C + port, data)    # Pull-Up Input
    def write(self, port, data):
        i2c.put(self, 0x12 + port, data)
    def read(self, port):
        return i2c.get(self, 0x12 + port)

if __name__ == '__main__':
    try:
        expand = MCP23017(0xFC, 0xFF)
        expand.pullup(1, 0x01)
        expand.invert(1, 0x01)
        blink = False
        ss = expand.read(1) & 0x01
        while True:
            sw = expand.read(1) & 0x01
            if ss != sw:
                blink = not blink if sw else blink
                if sw:
                    print 'Blink Start' if blink else 'Blink Stop'
                ss = sw
            if blink:
                expand.write(0, 0x01)
                time.sleep(0.5)
                expand.write(0, 0x02)
                time.sleep(0.5)
    except KeyboardInterrupt:
        print '\nbreak'

プログラムを実行するとスイッチが押されるまで待機して、押されるとLEDを交互に点滅、再びスイッチを押すと点滅が停止する。

線2本(VCCとGNDで4本)だけで16bitのI/O拡張が可能で、必要なら更に増やすこともできる。値段も200円以下と安価なので、GPIOのI/Oピンだけでは不足するような場合に役立つだろう。