ハードウェア技術者のスキルアップ日誌

某家電メーカーの技術者がスキルアップのために勉強したことを記録するブログです

ラズパイでSPI通信を行う

ラズパイを使ってハードウェアを制御するシリーズ、第四回目はSPI通信です。
前回と同じく、加速度センサーをラズパイに接続して動かしてみました。

 

SPIとは?

SPIは Serial Peripheral Interface の略でデバイス同士を接続するシリアルバスの1種です。I2Cと同じく、一つのマスターが同じバスで複数のデバイスと通信ができます。通信速度はI2Cに比べて比較的速いです。信号線はSCK, MISO, MOSI, CSの4本から成ります。

規格の詳細は割愛しますが、こちらに日本語の解説があります。
https://www.analog.com/jp/analog-dialogue/articles/introduction-to-spi-interface.html

 

ラズパイとデバイスの接続

ラズパイでSPIを使用するにはデバイスと以下のように接続します。
ラズパイのSPI端子は設定を変えることでGPIO8をCE0#に, GPIO9をMISOに, GPIO10をMOSIに, GPIO11をSCLKに割り当てることができます。

   f:id:masashi_k:20200506223949p:plain

ちなみに、ラズパイにはCE端子がもう一本用意されており、GPIO7がCE1#となります。

今回もアナログデバイセズの加速度センサーADXL345を使います。同一デバイスでI2CとSPIインターフェースの両方を使用できます。今回はSPIインターフェースで動かしてみます。

購入サイト
http://akizukidenshi.com/catalog/g/gM-06724/
データシート
http://akizukidenshi.com/download/ds/freescale/ADXL345_jp.pdf

センサーモジュールとの接続は以下の通りです。

f:id:masashi_k:20200506225233p:plain

 

SPIを使うための設定

Rasbianのデフォルト設定ではラズパイでSPIを使うことはできません。
GUIの設定メニューからSPIを有効にします。

設定ーRaspberry Piの設定ーインターフェース

f:id:masashi_k:20200503232411p:plain

f:id:masashi_k:20200506225701p:plain

SPIを有効にした後、再起動するとSPIが使用できるようになります。

 

バイスがI2C接続で認識されているかを確認するために以下のコマンドを実行します。spi_bcm2835が表示されればOKです。

$ lsmod | grep spi
# spi_bcm2835 16384 0

 

Pythonライブラリのインストール

ラズパイのpythonでSPI通信を行うにはpi-spidevライブラリを使用します。
pipコマンドでインストールします。

$ sudo apt-get install python-pip
$ sudo pip3 install spidev

 

SPIで通信を行うプログラム

上記でインストールしたspidevライブラリを使って値を読み書きするサンプルコードを以下に示します。

import spidev

read_addr = 0x00 
write_addr = 0x1E 
write_data = 0x08

#初期設定
spi = spidev.SpiDev()
spi.open(0,0)
spi.mode = 3  #このデバイスはSPI mode3で動作
spi.max_speed_hz = 1000000

#アドレス"read_addr "の値を読み出す
read_data = spi.xfer2([0x80 | read_addr,0x00]) 
print('read_data = 0x{:02x}'.format(read_data[1]))  # デバイスID 11100101 が読めるはず
# read_data = 0xe5

#アドレス"write_addr "に対してwrite_dataを書き込む
spi.xfer2([write_addr, write_data])

#正しく書き込めたことを確認
read_data = spi.xfer2([0x80 | write_addr,0x00])
print('write_data = 0x{:02x}'.format(read_data[1]))
# write_data = 0x08


いくつかソースコードについての補足です。
①SPI mode

SPIには4つのモードがあります。ポイントはデータをラッチするタイミングがSCLKの立ち上がりエッジか、立下りエッジがということと、SCLK未送信時の電圧レベルがHighかLowかという2点です。今回使用するADXL345のデータシートのP15(図36-37)を見ると、このデバイスはmode3であることが分かるのでmode3で制御します。

f:id:masashi_k:20200510223623p:plain

(引用:http://www.lapis-semi.com/lazurite-jp/contents/reference/spi.html

②spi.xfer2の仕様

spidevのサイトを見ると以下の記載があります。Chip selectを使った通信方法で引数には送信データのリストを渡します。

xfer2(list of values[, speed_hz, delay_usec, bits_per_word])
Performs an SPI transaction. Chip-select should be held active between blocks.

引数の詳細はADXL345のデータシートのP15(図36-37)を確認します。

まずReadの場合、最初のByteのbit7を1にしておく必要があります。bit6は複数Byte読み出し指定。複数Byteを連続で読み出す場合はここを1にする。それ以降5-0bitは読み出すアドレスを指定します。

2Byte目以降は任意の値でよいのですが、0x00としています。読み出すデータが1Byteであれば0x00を一つ、複数Byteを連続で読み出したい場合はその数だけ0x00を送信します。

読み出したデータは2Byte目以降に入ってくるのでリストの2個目以降を参照します。

f:id:masashi_k:20200510230949p:plain

次にWriteの場合ですが、Readとほぼ同じですが最初のByteのbit7が0となります。bit6はReadの時と同様に複数Byte書き込みビットです。以降の5-0bitは書き込むアドレスを指定します。

2Byte目以降は書き込むデータを転送します。複数Byteを連続して書き込む場合はその数だけデータを送ればOKです。

f:id:masashi_k:20200510231131p:plain

リアルタイムに3軸加速度を読み出すプログラム

SPI通信を使ってリアルアイムにx,y,z方向の加速度を読み出すプログラムを作って動かしてみました。

import spidev
import time

#初期設定
spi = spidev.SpiDev()
spi.open(0,0)
spi.mode = 3  #このデバイスはSPI mode3で動作
spi.max_speed_hz = 1000000

spi.xfer2([0x2D, 0x08]) #測定スタート

try:
    while True:
        #x,y,z方向の加速度を取得(2の補数表現)
        x_data_list = spi.xfer2([0xc0|0x32, 0x00, 0x00])
        y_data_list = spi.xfer2([0xc0|0x34, 0x00, 0x00])
        z_data_list = spi.xfer2([0xc0|0x36, 0x00, 0x00])
        x_data = x_data_list[1] | (x_data_list[2] << 8)
        y_data = y_data_list[1] | (y_data_list[2] << 8)
        z_data = z_data_list[1] | (z_data_list[2] << 8)
        #2の補数を10進に変換
        if(x_data & 0x8000):
            x_data = ((~x_data & 0xFFFF) + 1)*-1
        if(y_data & 0x8000):    
            y_data = ((~y_data & 0xFFFF) + 1)*-1
        if(z_data & 0x8000):
            z_data = ((~z_data & 0xFFFF) + 1)*-1
        #加速度に変換(Dレンジ ±2g)
        x_data = 2 * 9.8 * x_data / 0x7FFF
        y_data = 2 * 9.8 * y_data / 0x7FFF
        z_data = 2 * 9.8 * z_data / 0x7FFF
        print('x: {:4.2f}, y: {:4.2f}, z: {:4.2f} [m/s^2]'.format(x_data, y_data, z_data))
        time.sleep(0.1)
    
except KeyboardInterrupt:
    print('!FINISH!')

実行結果は以下の通りです。I2Cと同様に動かすことができました。

f:id:masashi_k:20200510233444p:plain

まとめ

ラズパイでSPI通信を行う方法をまとめてきました。今回使用したspidevはI2Cのモジュールwiringpiと比較して送受信データのフォーマットを意識する必要があるため少し面倒ですが、慣れれば問題なさそうです。

ここまで、UART, I2C, SPIと主要なデータ通信のインターフェースの使い方を勉強できたので、大抵のデバイスはラズパイに接続して制御できるようになったと思います。次回はPWM制御をやってみたいと思っています。

参考サイト

https://stackoverflow.com/questions/48321492/adxl345-device-id-and-offset-being-wrong-raspberry-pi
https://teratail.com/questions/182635