車輪の再発明

いったい何番煎じだよ!

raspberry piのSPIで3つ以上のデバイスを接続する

raspberry piではドライバが公開されているので、簡単にSPIでデバイスを接続できます。 しかしraspberry piでは仕様上、3つ以上のデバイスをSPIで接続することができません。 今回はもっとたくさんのデバイスをSPIで接続したかったので、pythonで頑張ってみました。

なお、以下の内容は次の環境を前提としています。

raspberry pi 3B+
Raspbian 9
python3.7
py-spidev 3.4

方針

SPIではひとつのバスに複数のデバイス(スレーブ)を接続できます。 しかしただ同じバスに接続しただけではどのスレーブと通信したいかがわかりません。 そこで、CS(Chip Selector)を各スレーブに個別にマスターと繋がるように配線します。 初期状態ではすべてのスレーブのCSをHighにしておき、通信したいときは通信対象のスレーブのCSだけLowにします。 raspberry pi ではCSが2つしかないので、同時に2つまでのスレーブしか接続できません。

スレーブを3つ以上接続するためにはCSとして利用できるピンを増やせばいいわけです。 CSは単純に通信中にLowに落とすことができればいいので、wiringpiで操作することにします。

またSPIをpythonで利用するために、py-spidevを利用させていただきました。

コード

#!/usr/bin/python

import wiringpi
from spidev import SpiDev

class SPIMassChips(SpiDev):
    def __init__(self):
        super().__init__()

    def set_cs(self, cs_pins):
        self.cs_pins = cs_pins
        wiringpi.wiringPiSetup()
        for self.__i in self.cs_pins:
            wiringpi.pinMode(self.__i, 1)
            wiringpi.digitalWrite(self.__i, 1)

    def open(self, bus, chip):
        self.bus = bus
        self.chip = chip
        if 0 > self.chip or self.chip > (len(self.cs_pins) - 1):
            raise ValueError("Invalid chip number.")
        super().open(self.bus, 0)

    def close(self):
        super().close()

    def xfer2(self, args):
        for self.__i in self.cs_pins:
            self.__j = 0
            while not wiringpi.digitalRead(self.__i):
                self.__j = self.__j + 1
                if self.__j > 100:
                    raise UserWarning("Device is busy.")
        wiringpi.digitalWrite(self.cs_pins[self.chip], 0)
        self.__value = super().xfer2(args)
        wiringpi.digitalWrite(self.cs_pins[self.chip], 1)
        return self.__value

見ての通り、py-spidevを継承したクラスになっています。 py-spidevでデータを読み書きする直前直後にwiringpiでCSに見立てたピンの出力を操作することで3つ以上のスレーブに対応できるようになっています。

実際にSPIでデータを読み書きするメソッドはxfer2()のみ実装しています。 他のメソッドは私の中で需要がなかったので対応していません。

xfer2()では一応簡単に排他制御っぽいことやっています。 というのもwitingpiでCSをLowに落としてから継承元のxfer2()でデータを読み書きするまでの間に、CSに指定したピンの出力が変更されると意図したスレーブと通信できなくなってしまいます。 そこで、通信開始前にCSに指定されているピンの中にLowになっているものがないか確認し、もしLowになっているものがあればHighになるまで待つようにしました。 また待ち時間は適当なところでタイムアウトするようにしました(ループ回数の100は適当です)。

使い方

本家py-spidevとそんなに大きくは変わりません。

spi = SPIMassChips()

# CSとしてスレーブに接続しているピンを設定します。
cs_pins = [21, 22, 23, 24]
spi.set_cs(cs_pins)

# 本家py-spdevと同じようにバスの番号とスレーブの番号を指定します。
# スレーブの番号は`set_cs()`で渡した配列のインデックスになります(ここでは24番ピンに接続されたスレーブが対象になる)。
spi.open(0, 3)

# 本家py-spidevと同様にデータをやり取りできます。
send_data = [0, 0, 0]
received_data = xfer2(send_data)