Spi用のCライブラリをJAVAから呼び出す

ロボットの5個の加速度センサーをRaspberryPIのSpi1の元にぶら下げたいと、先週末から四苦八苦している。そもそも、Spi1が、デバイス上は、ちゃんと認識させているのだが動かなかった。

結局それは、WiringPiのSpiライブラリ関数、およびそれを元にしたJAVAのpi4jライブラリは、RspberryPIのSpi0をしか使わないことを前提にしているからだった(そうではないという方もいるかもしれないが、私にはそうとしか見えなかった)。PythonのSpiライブラリはSpi0もSpi1も使えるようになっていた。

ここでは、JAVAで統括することが基本なので、どうしたものかと思ったが、結局、WiringPiなどを使わずに、C言語でSpi用のダイナミックライブラリを作成して、それをJNAを使って、JAVAから呼ぶ出すという戦略にした。

Cでは、Spiデバイス、
/dev/spidev0.0
/dev/spidev0.1
/dev/spidev1.0
/dev/spidev1.1
/dev/spidev1.2
から直接読み書きするのがいい。もともと、Raspiは、Spi0しか有効化していないのだが、Spi1を有効化する方法は別の記事に書いておいた。

ライブラリは、
https://raw.githubusercontent.com/raspberrypi/linux/rpi-3.10.y/Documentation/spi/spidev_test.c
のC言語コードspidev_test.cを元に作成した。
Cの共有ライブラリの作成方法については、
http://www.koikikukan.com/archives/2016/10/27-000300.php
を、AVAからCライブラリを呼び出す方法については、
http://qiita.com/kiida/items/9d26b850194fa1a02e67
を参照させていただいた。

ライブラリ用に作成したソースをspi.c(main関数はいらない)とすると、
gcc -fPIC -shared -o libspi.so spi.c
とすれば、ライブラリはできる。

作成したライブラリlibspi.soを以下の場所に置いておく。(バグが多々存在したので公開中止)
libspi.so
ただし、使用される場合は、一切の保証はありませんし、完全に自己責任で使っていただきたい。また、非常に、荒っぽく、当面必要なことを目的に作成したものだということはあらかじめお断りしておく。時間と必要性があったら、もっと精緻なものにしたい。

このライブラリを適当なフォルダにおいて、RasPIのターミナルから、
export LD_LIBRARY_PATH=/to/your/library/
として、
$ sudo ldconfig
を実行させる。ldconfigはいらないかもしれない(適当だな笑、普通やるので癖)。

これは、Cライブラリなので、Cから呼び出すことはできるが、ここではJNAを利用して、JAVAのライブラリのように使う。(JAVAから、Cの実行ファイルを呼び出すのは、オーバーヘッドが大きそうでやめた。)
https://github.com/java-native-access/jna/tree/master/dist
から
jna.jar
をダウンロードして、javaプログラムのライブラリに組み込む。

RasPIのSpi1テャンネルにKXSD9という三軸加速度センサーをぶらせげてスレーブデバイス番号0から読み取るサンプルプログラムを最後のところにおいておく。

データはこんな感じで綺麗に取れる。

JAVAのプログラムは、次のようになる。Spi1には、スレーブ1個しかぶら下げていないので、スレーブ  ピンの操作はしていない。いずれ、たくさんのスレーブをぶら下げた場合の結果は、そのうち報告できるはずだ。

ここで使っている、libspi.soの関数は、
int setupSpi(int ch, int ss, int spd)
void transfer(byte [] tx, int size)
void closeSpi()
だが、他に、
void option_usage(const char *prog)
void setOptions(int argc, char *argv[])
という関数もある。
setupSpiはデバイスを初期化します。引数は、チャンネル番号(0 or 1)とスレーブデバイスポート番号(チャンネル0の場合は、0か1、チャンネル1の場合は、0、1、2のいずれか)、そしてクロックスピードである(20Mくらいまではいけるのではないかと思うが、やったことはない)。
transfer関数は、バイト配列に入っているデータを送信し、結果を入れるバッファにもなっている。Spi特有のデータの「玉突き送受信」を再現している。(別々にもできたのだが・・・・・・)。例えば、2バイト送信する場合、1バイトめにコマンド、その結果が2バイト目に入れられて戻ってくるという感じである。センサーのデータの場合は、1バイトめに軸アドレス、2バイトと3バイトめには結果の12ビットデータが入っていたりするわけである。
closeSpiは、デスクリプタとストリームを閉じる関数。
option_usageとsetOptionsの関数は、元々のspidev_test.cの関数をそのまま入れている感じだ。特に後者は、モードの設定などができるようになっている。この終わりの二つの関数は、使う必要もなく、使ったこともないので、これでいいのかどうかは、よくわからない。

import com.sun.jna.Library;
import com.sun.jna.Native;
import java.util.logging.Level;
import java.util.logging.Logger;

// ここの部分がlibspi.soと接続するおまじない
interface SpiLib extends Library {
    SpiLib INSTANCE = (SpiLib) Native.loadLibrary("spi", SpiLib.class);
    // 使うべき関数を宣言する
    int setupSpi(int ch, int ss, int spd) ;
    void transfer(byte [] tx, int size) ;
    void closeSpi();
}

// staticを多用しているが、mainがスタティックなので
// 止むを得ずそうしているだけで
// 普通にやれば、main関数以外、staticにする必要はない
public class SpiTest {
    static  SpiLib spi = SpiLib.INSTANCE;

    public static void main(String[] args) {
        spi.setupSpi(1, 0, 1000000);
        System.out.println("センサーKXSD9の設定をデバイスに書き込みます");
        byte [] com = new byte[2];
        com[0] = 0x0c;  // address byte
        com[1] = (byte) 0xe3;  // 測りたいところで細かくしている
        spi.transfer(com,2);
        com[0] = 0x0d;
        com[1] = 0x40; // こちらはデフォルト設定のまま
        spi.transfer(com,2);
        // 設定結果を読み取るが、必要ではない
        com[0] = (byte)0x8c;
        com[1] = 0x00;
        spi.transfer(com,2);
        System.out.print("設定結果: "+String.format("0x%x ", com[1]));
        com[0] = (byte)0x8d;
        com[1] = 0x00;
        spi.transfer(com,2);
        System.out.println(String.format("0x%x ", com[1]));
        // センサーデータを10ミリ秒間隔で、500個取得する
        for (int i = 0; i <= 500; i++) {
            double[] sdata = sensorValue();
            // 取得データを書き出す
            System.out.println(" " + sdata[0] + " " + sdata[1] + " " + sdata[2]);
            try {
                Thread.sleep(10);
            } catch (InterruptedException ex) {
                Logger.getLogger(SpiTest.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
        spi.closeSpi();
    }

  static public double[] sensorValue(){
        // デバイスからデータ取得
        double [] sense = new double[3];
        byte [][] spi_buff = new byte[3][3];    //送受信用バッファ
        
        int xregH, xregL, xout;
        int yregH, yregL, yout;
        int zregH, zregL, zout;
        double xac, yac, zac;

        for (int i = 0; i < 3; i++) {
            // 軸アドレス
            spi_buff[i][0] = (byte)(0x80 + 2 * i);
            // 結果受け取りバッファ
            spi_buff[i][1] = 0;
            spi_buff[i][2] = 0;
            spi.transfer(spi_buff[i],3);
        }
        xregH = (spi_buff[0][1] & 0xff);
        xregL = (spi_buff[0][2] & 0xff);
        xout = xregH << 4 | xregL >> 4;

        yregH = (spi_buff[1][1] & 0xff);
        yregL = (spi_buff[1][2] & 0xff);
        yout = yregH << 4 | yregL >> 4;

        zregH = (spi_buff[2][1] & 0xff);
        zregL = (spi_buff[2][2] & 0xff);
        zout = zregH << 4 | zregL >> 4;

        xac = (double) (xout - 2048) / (double) 819;
        yac = (double) (yout - 2048) / (double) 819;
        zac = (double) (zout - 2048) / (double) 819;
        //
        sense[0] = xac;
        sense[1] = yac;
        sense[2] = zac;
        return sense;
    }

}