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

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

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

▼ ADコンバータMCP3008の詳しい使い方はこちら

▼ Raspberry PiでSPI初期設定が済んでない方はこちら

▼ I2C通信のADコンバータADS1115もおすすめ!

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

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

py
# -*- coding:utf-8 -*-
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プログラムの中身を詳しくみていこう。

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

py
adc = spi.xfer2([1, (8 + channel) << 4, 0])

  • spi.xfer2の引数のリストはなぜ3つなのか?
  • そして返り値adcとはいったい?

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

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

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

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

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

MCP3008のデータシートはこちら MCP3008データシート - 秋月電子通商

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

送信データの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を足し合わせれば良い。

py
>>> bin(8)
'0b1000'

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

py
>>> 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を二進数で表すと、

py
>>> bin(3)
'0b11'

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

py
>>> bin(11)
'0b1011'

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

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

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

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

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

関連記事

最後までご覧いただきありがとうございます!

▼ 記事に関するご質問やお仕事のご相談は以下よりお願いいたします。
お問い合わせフォーム

人気のArduino互換機
Arduinoで人気の周辺パーツ
あると便利な道具
Arduinoのオススメ参考書

▼ Arduino初心者向きの内容です。ほかのArduino書籍と比べて図や説明がとてもていねいで読みやすいです。Arduinoで一通りのセンサーが扱えるようになります。

▼ 外国人が書いた本を翻訳したものです。この手の書籍は、目からうろこな発見をすることが多いです。

▼ Arduinoの入門書を既に読んでいる方で、次のステップを目指したい人向きの本です。C言語のプログラミングの内容が中心です。ESP32だけでなく、ふつうのArduinoにも役立つ内容でした。

関連記事