Cで書いた距離センサモジュールをjavaから呼び出す

4つの距離センサは、Cで制御し、4つのモータのエンコーダーからの回転数取得はjavaで制御しなければならない事情を先に書いた。そこで、2つのMCP23S17を使って、それぞれを距離センサとエンコーダ処理用にする戦略をとることにした。

RaspberryPi の一つのSPI0チャンネルに、上記の二つのMCP23S17をバスでぶら下げることにする。デバイスの識別は、A0,A1,A2端子を1、0で区別すればいい(8個区別出来る)。基本的に、0番と1番のデバイスにする。

距離センサはCプログラムで制御するのだが、ロボットの方は、javaで制御システムが書かれているので、センサのCプログラムは、ライブラリ化(libdistance.so)して、jna経由で、javaから直接そのライブラリを使うようにすればいい。これはすでにやったことがあるので、簡単にできると思ったが、結局、昨日の夜から今日の夜まで、かかった(途中、大学に行って学生の修論指導はしたが)!

第一に手こずった問題は、libdistance.soを作成して、javaのjnaを使ったプログラムを書いて、lib distance.soをLD_LIBRARY_PATHの環境変数で与えられる場所において、動かしてみたのだが、どうやっても、
「java.lang.UnsatisfiedLinkError: /tmp/jna-3577/jna760224136558438898.tmp: /tmp/jna-3577/jna760224136558438898.tmp: 共有オブジェクトファイルを開けません: そのようなファイルやディレクトリはありません」
というエラーが出る。色々やって、かなり長時間格闘して、結論的には、最新のjna.jar (4.5.1)とraspberrypiの何らかの相性が悪いという判断をした。同じ状況で、linux(ubuntu16.04)では、ちゃんと動くのだから。

これは間違っているかもしれない。が、私の能力の範囲での結論はそうなのだ。だから、去年ちゃんとraspberrypi上で動いたjna.jarを持ってきて、それでセットしたらちゃんと動いた。あ〜あ。

もう一つ、こちらは大したことはないのだが、wiringPiのライブラリもちゃんとくっつけて、libdistance.soを作成しないと、wiringPiの関数が見つからないと怒られる!

プログラムをいかに掲載する。まず、Cライブラリのプログラム distance.cは以下のようになる。

/*
距離センサーライブラリ
MCP23S17をデバイス0番でセットしておく
使い方:
getdistance('センサー番号:整数 0-3')
*/

#include <stdio.h>
#include <wiringPi.h>
#include <mcp23s17.h>
#include <sys/time.h>
#include <unistd.h>

// 64より大きな数字、適当 wiringpiの要請
#define    BASE       123 

int trigPin, echoPin;

int pulseIn(int pin, int level, int timeout){
  struct timeval tn, t0, t1;
  long micros;

  gettimeofday(&amp;t0, NULL);
  micros = 0;
  while (digitalRead(pin) != level){
      gettimeofday(&amp;tn, NULL);
      if (tn.tv_sec &gt; t0.tv_sec) micros = 1000000L; else micros = 0;
      micros += (tn.tv_usec - t0.tv_usec);
      if (micros &gt; timeout) return 0;
    }

  gettimeofday(&amp;t1, NULL);
  while (digitalRead(pin) == level){
      gettimeofday(&amp;tn, NULL);
      if (tn.tv_sec &gt; t0.tv_sec) micros = 1000000L; else micros = 0;
      micros = micros + (tn.tv_usec - t0.tv_usec);
      if (micros &gt; timeout) return 0;
    }

  if (tn.tv_sec &gt; t1.tv_sec) micros = 1000000L; else micros = 0;
  micros = micros + (tn.tv_usec - t1.tv_usec);
  return micros;
}

// セットアップ関数の呼び出しは、最初の1回だけである
// 何度も呼び出してはならない
void setup(int pin){ // pin -&gt; 0-3
	trigPin = BASE+pin+8; // B0-B3
	echoPin = BASE+pin;   // A0-A3
	
    wiringPiSetup () ;
    mcp23s17Setup (BASE, 0, 0) ;
    pinMode (trigPin, OUTPUT) ;
    pinMode         (echoPin, INPUT) ;
    pullUpDnControl (echoPin, PUD_UP) ;
}

int getdistance(){
    long duration, distance;

	// HIGHが10μs継続するパルスを出す
    digitalWrite(trigPin, LOW);  // Added this line
    delayMicroseconds(2); // Added this line
    digitalWrite(trigPin, HIGH);
    delayMicroseconds(10); // Added this line
	digitalWrite(trigPin, LOW);
	// その結果出力された超音波を受け取る
	// その時間差から距離を測る
    duration = pulseIn(echoPin, HIGH, 1000000);
    //printf("duration=%d ",duration);
    distance = (duration/2) / 29.1;
    //printf("距離 = %d cm\n",distance);
    //sleep(1);
	return (int)distance;
}

これをコンパイルするためには次のようにする。
gcc -fPIC -shared -o libdistance.so distance.c -lwiringPi
最後に、wiringPiのライブラリをくっつけているところ大事。というのも、これをつけなくても、コンパイル自身は難なく通ってしまうから、実行時に叱られる。
export LD_LIBRARY_PATH=../XXX :$LD_LIBRARY_PATH
sudo ldconfig
とかする。../XXXはlibdistance.soをおく場所である。あるいは、
/etc/ld.so.conf.d
の設定ファイルに、そのパスを書いておき、ldconfigを実行するとかする。

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

// ここの部分がlibdistance.soと接続するおまじない
interface DistanceLib extends Library {
    DistanceLib INSTANCE = (DistanceLib) Native.loadLibrary("distance", DistanceLib.class);
    // 使うべき関数を宣言する
    int getdistance() ;
    void setup(int pin);
}

/**
 *
 * @author washida
 */
public class Distance {
    DistanceLib dlib = DistanceLib.INSTANCE;
    
    void show(){
        dlib.setup(0);
        for(int i=0;i&lt;100;i++){
            int dis = dlib.getdistance();
            System.out.println("No."+ i +": 距離 = "+dis+"cm");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException ex) {
                Logger.getLogger(Distance.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        // TODO code application logic here
        Distance dis = new Distance();
        dis.show();
    }
    
}

こちらは特に問題はないだろう。