サイトロゴ

【Raspberry Pi】spi.xfer2関数の使い方を徹底解説

著者画像
Toshihiko Arai

はじめに

この記事では、Raspberry PiでSPI通信する時に使うspi.xfer2関数の使い方を詳しく解説する。なお、本記事で紹介するPythonプログラムは、Raspberry PiとADコンバータMCP3008でSPI通信するプログラムとなっている。

Raspberry PiでMCP3008とSPI通信するPythonプログラム

Raspberry PiでMCP3008とSPI通信するPythonプログラムがこちら。

import time
import spidev

Vref = 3.334  # 電圧をテスターで実測する

spi = spidev.SpiDev()

spi.open(0, 0)  # bus0,cs0
spi.max_speed_hz = 100000  # 100kHz 必ず指定する


def readAdc(channel):
    adc = spi.xfer2([1, (8 + channel) << 4, 200])
    data = ((adc[1] & 3) << 8) + adc[2]
    return data


def convertVolts(data, vref):
    volts = (data * vref) / float(1023)
    return volts


def convertTemp(volts):
    temp = (volts - 0.75) / 0.01 + 25.0
    return temp


if __name__ == '__main__':
    try:
        while True:
            data = readAdc(channel=0)
            volts = convertVolts(data, Vref)
            temp = convertTemp(volts)
            print("CH0 volts: {:.2f}".format(volts))
            print("temp : {:.2f}".format(temp))

            time.sleep(2)

    except KeyboardInterrupt:
        spi.close()

spi.xfer2([1, (8 + channel) << 4, 0])の解説

それでは先ほどのPythonプログラムの中身を詳しくみていこう。

次の一行は、多くの人が謎に思われたのではないだろうか。

adc = spi.xfer2([1, (8 + channel) << 4, 0])
  • spi.xfer2の引数のリストはなぜ3つなのか?
  • そして返り値adcとはいったい?

色々と調べた結果、自分なりに理解できたので説明していく。

はじめに重要なことをお伝えする

  • spi.xfer2の引数に渡すリストは8ビットデータとして解釈される。

これを頭におきながら、先を読み進めてもらいたい。

MCP3008のデータシートから、送受信データのフォーマットを見てみよう。データシートを読むと、3バイト(8ビットx3)のデータで送受信する仕様となっている。

MCP3008のデータシートはこちら http://akizukidenshi.com/download/ds/microchip/mcp3008.pdf

まずは送信データだけに絞って考えてみよう

送信データの1バイト目を見てみると、start bit として 1 を送信している。

spi.xfer2([1, (8 + channel) << 4, 0]) のリストの最初が1だったのはそのためだ。次に送信データの2バイト目を見てみよう。

わかりやすくするために、上位4ビット SGL/DIFF D2 D1 D0 だけ考えてみる。コンフィギュレーションビットで説明した通り、今回はSGL/DIFFを1の値で固定する。残りの3ビットはチャネル番号になるので、1 0 0 0 の形にするにはチャネル番号に二進数で表される 1 0 0 0 すなわち8を足し合わせれば良い。

>>> bin(8)
'0b1000'

そして下位4ビットの X X X X の値は気にしなくて良いので、<< 4 で4ビット分を左にシフトして0で埋め合わせている。だからリストの2番目は (8 + channel) << 4 で計算された値を代入しているのだ。ちなみに、<< 左ビットシフトは、その数だけ左にずらしてゼロで埋める演算だ。

>>> bin(8 << 4)
'0b10000000'

最後に送信データの3バイト目を見ていこう。

これは8bit分データを送れば値は気にすることはない。だから spi.xfer2([1, (8 + channel) << 4, 0]) の第3引数にはゼロを入れてあるが、べつにゼロでなくても構わないのだ。

  • 第三引数まで入れる理由は、第二引数までだとシリアルクロック(CLK)が3バイト分働いてくれず、Doutからデータを取り出せないからである。

つまりは、spi.xfer2関数の引数のリストは、ひとつに付き8回のシリアルクロックを送信する仕組みとなっているようだ。 spi.xfer2([1, (8 + channel) << 4, 0]) がなぜ3つの引数をとるのか、これでお分かりいただけたと思う。

data = ((adc[1] & 3) << 8) + adc[2]の解説

今度は受信データをみていこう。

はじめにひとつ、重要なポイントを。

  • 関数spi.xfer2は、たとえばDinに3バイトデータ送信したら、同時にDoutから3バイトデータを取り出せる仕組みになっている。

adc = spi.xfer2([1, (8 + channel) << 4, 0]) の一行は3バイトのデータをDinへ書き込んでいるとともに、 Doutの値を3バイト分読み込んで、返り値 adc に格納しているのだ。

  • spi.xfer2の返り値のリストは、引数(送信データ)と同じ数だけある。
  • その値は8ビットをlong型にした値である。

これが分かれば、受信データのフォーマットの理解も簡単であろう。データシートの受信データのフォーマットを一気に見ていく。

1バイト目

2バイト目

3バイト目

図から、次の3つがわかる。

  1. 1バイト目は無視してよい。
  2. 2バイト目も上位6ビット分は無視して、下位2ビットを取り出せばよい。
  3. 3バイト目には8ビットのデータがつまっている。

つまり、2バイト目の下位2ビットと3バイト目の8ビットをつなぎ合わせた形が、10bitで表現される電圧値となる。だからチャネルから取り出す値は、data = ((adc[1] & 3) << 8) + adc[2] で計算されることがお分かりだろう。

論理演算がややこしいので、少し補足しておく。adc[1] & 3& とはAND演算(論理積)の計算である。たとえば、3を二進数で表すと、

>>> bin(3)
'0b11'

となり、11を二進数で表すと、

>>> bin(11)
'0b1011'

となる。この11と3のAND演算をやってみよう。

>>> bin(11 & 3)
'0b11'

これは、11の下位2ビットを取り出したのと同じである。つまり、11を3でマスクしたことになる。

話を戻して、先ほどの ((adc[1] & 3) << 8) をもう一度みてみよう。この式でやっていることは、2バイト目の受信データの下位2ビットを取り出して、左に8ビットシフトし、ゼロで埋め合わせている。。だからこれにadc[2] を足すことで、10bitで表現されたアナログ電圧値を読み取れるわけだ。

以上でspi.xfer2の使い方の説明を終わる。データシートをよく見て送受信のやりとりをしっかりイメージできれば、SPI通信も難しくないことが分かったと思う。

関連記事