トップページ 組込みソフト Linux搭載ボード Androidでセンサーを使えるようにする

Androidでセンサーを使えるようにする
 Beagel Bone BlackでAndroidの開発はソースファイルからAndroid OSをビルドするというができました。
次はセンサーを使えるようにします。Begle Bone Blackはスマートフォンなどと違ってセンサーの類は何も実装されていません。つまり自分でセンサーを接続してそれを動かすためのプログラムを作成することになります。
センサーの接続方法は、この本を参考にしました。

しかし、ダメダメ!
たぶんAndroidが初期の頃のソースを元に何とか動いた!レベルです。
何がダメかというと、
1.複数のセンサーが実装されることがまったく前提になっていない
(加速度センサーをつけて動いたレベルです。)
普通にまともな設計なら、1つのセンサーを実装する例であっても2つ以上のセンサーの実装が容易になるようにすべきだが、そのような考えがまったくなさそうだ。

2.必要な処理をサボっている!
加速度センサーなら,

sensorManager.registerListener(this,
tempmeter,SensorManager.SENSOR_DELAY_UI);

のようにセンサーに検出するインターバルを短くしないとデータを取り損ねることががる。いろいろ自力で調べてみると、次の関数で時間を渡していることがわかった。
static int poll__setDelay(struct sensors_poll_device_t *dev,
int handle, int64_t ns)
{
   // TODO
   return 0;
}
//TODO
だって!結局必要な処理をやっていない。
調べた結果はSENSOR_DELAY_UIで66.66msだった。

またまた、この本の悪口になってしまいました。
ちなみに、この本の正誤表がでていますが、ぜんぜん足りないですよ。
2014/8/6現在:
2014年2月25日から正誤表が変わっていない。再度、以前と同じ文句を、
「著者や編集者がちゃんと記事をチェックしてば、こんなに余計な時間を費やさないですみました。オラは本のチェック要員じゃないぞ!」

ダメ本のおかげでいろいろ苦労させられました。
そこで、
複数のセンサーが実装できるように設計したソースを公開します。

結局、わかったことを元にしてソースファイルを全部書き直しです。

というわけで再度同じことやるのにこの本を元にしたら、また同じ記事の間違いで時間を浪費することを避けるため、備忘録的にメモしておきます。
どんな作業をすればいいのか?
1.sensors.default.soを作成する
2.sensors.default.soをSDカードにコピーする
3.デバイスファイルのパーミッションを666にする
4.Androidアプリを作成する
1.sensors.default.soを作成する
Androidのソースファイルが実装されているディレクトリを
~/src-android
として説明を進めます。
sunaga@android:~$ cd ~/src-android

次にターゲット製品を設定します。
sunaga@android:~/src-android$ export TARGET_PRODUCT=beagleboneblack

次に環境の設定をします。
sunaga@android:~/src-android$ source build/envsetup.sh
including sdk/bash_completion/adb.bash

ソースファイルを置くディレクトリを決めます。何も考えず上のダメ本にあったディレクトリを流用しました。
~/src-android/hardware/libhardware/modules/sensors
上記ディレクトリがなければ新規作成してください。
上記でディレクトリに移動します。
sunaga@android:~/src-android$ cd hardware/libhardware/modules/sensors
sunaga@android:~/src-android/hardware/libhardware/modules/sensors$

このディレクトリに以下のソースファイルを配置します。
my_i2c_sensors.c
my_i2c_sensors.h
my_sensors.c
i2c-dev.h
Android.mk

ビルドは簡単です。mmとやるだけ!
sunaga@android:~/src-android/hardware/libhardware/modules/sensors$ mm

ライブラリが以下のフォルダーに作成されています。
~/src-android/out/target/product/beagleboneblack/system/lib/hw/sensors.default.so
2.sensors.default.soをSDカードにコピーする
SDカードを入れます。
SDカードでライブラリを配置するディレクトリは、
/media/rootfs/system/lib/hw
です。ここにsensors.default.soをコピーします。
sunaga@android:~/src-android/hardware/libhardware/modules/sensors$ sudo cp ~/src-android/out/target/product/beagleboneblack/system/lib/hw/sensors.default.so /media/rootfs/system/lib/hw
3.デバイスファイルのパーミッションを666にする
SDカードのinit.rcで/dev/i2c-3のパーミッションを666にします。
エディタで
/media/rootfs/init.rc
を開きます。このファイルは管理者権限でないと変更できないので、
sunaga@android:~/src-android/hardware/libhardware/modules/sensors$ sudo gedit /media/rootfs/init.rc
で開きます。
検索でon bootがある場所で次のようにchmodを加えます。
--------------------------------------------------------------------
#set i2c
chmod 0666 /dev/i2c-3


on boot
# basic network init
--------------------------------------------------------------------
保存します。
センサーの接続
大気圧センサー
秋月電子通商 LPS331使用 高精度大気圧センサーモジュール
取得した気圧データが正しいのか?比較するための気圧計がないのでわかりません。なんとなくそれなりのデータかな?と思っています。温度も計測できます。ただし精度が±2度。ちょっとがっかりですね。(2014/8/8販売中止中みたいです。)
注意:
4番ピンをVDDにしてアドレス0x5dになります。GNDで0x5cです。どちらにも接続しないと0x5dになったり0x5cになったり不安定になります。必ずどちらかに繋いでください。
回路図:

それ以外のセンサーを繋ぐ場合は、Raspberry Piで紹介した
I2C 制御コード、サンプル公開
AM2321湿度センサーが使えた!
を参考にしてください。
ソースファイルの説明
i2c-dev.h
これは何も変更を加えてないヘッダーファイルです。

my_i2c_sensors.h
int openI2C(char* deviceName);
I2Cデバイスをオープンする関数です。

int activateLPS331AP(int fd);
電源投入時は、センサーはスリープになっています。これを起こしてやるための関数です。

float getTempareture(int fd);
センサーから温度データを取得する関数です。

float getPressure(int fd);
センサーから大気圧データを取得する関数です。

my_i2c_sensors.c
上記以外の関数の説明:
int SetSmbus (int fd, char rw, int command, int size, union i2c_smbus_data *data)
SMbusにデータをセットする関数です。

static u_char readSensor(int fd, int sensorAddr, u_char regNo)
センサーからデータを読み出す関数です。
sensorAddr:センサーのアドレスを指定します。LPS331APは0x5dです。
regNo:読み出すレジスタ番号を指定します。

static int writeSensor(int fd, int sensorAddr, u_char regNo, u_char data)
センサーにデータを書き込む関数です。
sensorAddr:センサーのアドレスを指定します。LPS331APは0x5dです。
regNo:書き込むレジスタ番号を指定します。
data:書き込むデータ

my_sensors.c
これがセンサーでのデータ取得とAndroidアプリにデータを渡すインターフェースになります。

Android Appプログラムではセンサーがあるか?リストを取得します。
//センサーマネージャの取得(1)
sensorManager=(SensorManager)getSystemService(
Context.SENSOR_SERVICE);
//センサーの取得(2)
List<Sensor> list;
list=sensorManager.getSensorList(Sensor.TYPE_TEMPERATURE);
if (list.size()>0) tempmeter=list.get(0);
list=sensorManager.getSensorList(Sensor.TYPE_PRESSURE);
if (list.size()>0) pressmeter=list.get(0);
このリストを定義しているのが、g_SensorList[]です。
      
static const struct sensor_t g_SensorList[] = {
{ .name = "LPS331",
.vendor = "ST",
.version = 1,
.handle = ID_TEMPERATURE,
.type = SENSOR_TYPE_TEMPERATURE,
.maxRange = 85.0f,
.resolution = 0.06f,
.power = 0.006f,
.reserved = {}
},

{ .name = "LPS331",
.vendor = "ST",
.version = 1,
.handle = ID_PRESSURE,
.type = SENSOR_TYPE_PRESSURE,
.maxRange = 1260.0f,
.resolution = 0.1f,
.power = 0.0003f,
.reserved = {}
}, };
この配列をAndroidアプリに知らせる定義は、HAL_MODULE_INFO_SYMで定義します。 struct sensors_module_t HAL_MODULE_INFO_SYM = { .common = { .tag = HARDWARE_MODULE_TAG, .version_major = 1, .version_minor = 0, .id = SENSORS_HARDWARE_MODULE_ID, .name = "MySensors", .author = "Sadaji Sunaga", .methods = &sensors_module_methods, }, .get_sensors_list = get_sensors_list }; 実際の関数get_sensors_listは簡単です。戻り値はセンサーの数です。 static int get_sensors_list(struct sensors_module_t* module, struct sensor_t const** list) { *list = g_SensorList; return MAX_SENSORS; }  Androidアプリが設定するセンサーのデータ取得インターバルの指定 sensorManager.registerListener(this, pressmeter,SensorManager.SENSOR_DELAY_UI); を担当するのは、
static int device_setDelay(struct sensors_poll_device_t *dev, int handle, int64_t ns) { fprintf(stderr, "%s: handle=%d ns=%"PRId64"\n", __FUNCTION__, handle, ns); // g_delay[handle] = ns; return 0; } です。もっとも遅いSENSOR_DELAY_NORMALでも200msです。 温度センサーや湿度センサーはこんなに早く処理する必要はありません。 また、センサーによっては2秒以下の周期で読み取ると正常に動かないことがあります。 そこで今回はセンサーの読み取り周期はこの関数で渡されるデータを使わず独自にやります。 g_delay[0] = ms2ns(4000); g_delay[1] = ms2ns(2000); 0は温度センサーで4秒周期 1は大気圧センサーで2秒周期 とテキトーーーに決めました。 Androidアプリでセンサーデータの取得 //センサーリスナーの処理(5) public void onSensorChanged(SensorEvent event) { String str; //温度の取得 if (event.sensor==tempmeter) { values[0]=event.values[0]; str = "" + values[0] + "℃"; android.util.Log.e("temparature", str); } //大気圧の取得 if (event.sensor==pressmeter) { values[1]=event.values[0]; str = "" + values[1] + "hPa"; android.util.Log.e("temparature", str); } } これを担当するのが、 static int device_poll(struct sensors_poll_device_t *dev, sensors_event_t* pSnsEvet, int count) です。 この関数は他のAndroidアプリとインターフェースするテーブルの記述しておきます。 static struct hw_module_methods_t sensors_module_methods = { .open = open_sensors }; //OSとのインターフェース static int open_sensors(const struct hw_module_t* module, const char* name, struct hw_device_t* *device) { int status = -EINVAL; if (!strcmp(name, SENSORS_HARDWARE_POLL)) { memset(&g_device, 0, sizeof(g_device)); g_device.common.tag = HARDWARE_DEVICE_TAG; g_device.common.version = 0; g_device.common.module = (struct hw_module_t*)module; g_device.common.close = device_common_close; g_device.poll = device_poll; g_device.activate = device_activate; g_device.setDelay = device_setDelay; g_fd = -1; *device = &g_device.common; status = 0; } return status; } device_pollはセンサーのインターバル時間に達したときセンサーのデータをAndoidアプリに渡します。 戻り値は、インターバル時間に達したときセンサーの数です。 今回の例では2秒と4秒なので、 2秒たって大気圧センサー検出で戻り値1 4秒たって大気圧センサーと温度センサー検出で戻り値2 それ以外のときは戻り値0です。

温度が4秒周期、大気圧が2秒周期になっていることが、確認できた。
4.Androidアプリを作成する
たぶん、Androidの入門書に例が出ていると思うので省略します。(すごい不親切かも....)

やはりキャラクタLCDに比べると手抜きで作った表示でも見栄えがぜんぜん違います。
電池駆動で動作させ、車で移動してみる。
10kmくらい走ってみると自宅と10hPaも違いがあった。
見かけは平地なのですが、少しずつ標高が上がっているようです。
そういえば、昔自転車で駅まで通学していた頃、行きは楽勝、帰りはしんどいことを思い出しました。
やはり、緩やかな坂が続いていたのだな。と実感しました。