【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プログラムがこちら。
# -*- 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プログラムの中身を詳しくみていこう。
次の一行は、多くの人が謎に思われたのではないだろうか。
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を足し合わせれば良い。
>>> 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バイトデータを取り出せる仕組みになっている。
- spi.xfer2の返り値のリストは、引数(送信データ)と同じ数だけある。
- その値は8ビットをlong型にした値である。
これが分かれば、受信データのフォーマットの理解も簡単であろう。データシートの受信データのフォーマットを一気に見ていく。
1バイト目
2バイト目
3バイト目
図から、次の3つがわかる。
- 1バイト目は無視してよい。
- 2バイト目も上位6ビット分は無視して、下位2ビットを取り出せばよい。
- 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通信も難しくないことが分かったと思う。