ラズパイで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に割り当てることができます。
ちなみに、ラズパイにはCE端子がもう一本用意されており、GPIO7がCE1#となります。
今回もアナログデバイセズの加速度センサーADXL345を使います。同一デバイスでI2CとSPIインターフェースの両方を使用できます。今回はSPIインターフェースで動かしてみます。
購入サイト
http://akizukidenshi.com/catalog/g/gM-06724/
データシート
http://akizukidenshi.com/download/ds/freescale/ADXL345_jp.pdf
センサーモジュールとの接続は以下の通りです。
SPIを使うための設定
Rasbianのデフォルト設定ではラズパイでSPIを使うことはできません。
GUIの設定メニューからSPIを有効にします。
設定ーRaspberry Piの設定ーインターフェース
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で制御します。
(引用: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個目以降を参照します。
次にWriteの場合ですが、Readとほぼ同じですが最初のByteのbit7が0となります。bit6はReadの時と同様に複数Byte書き込みビットです。以降の5-0bitは書き込むアドレスを指定します。
2Byte目以降は書き込むデータを転送します。複数Byteを連続して書き込む場合はその数だけデータを送ればOKです。
リアルタイムに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と同様に動かすことができました。
まとめ
ラズパイで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