2015/10/20

Mathematica for RPi2 でGPIOのSPIデータプロット&Webで確認

RaspbianにはMathematicaが入っている。今回はこのMathematicaからSPIデータを取得してプロットしてみようという試み。

組み込みAPIでは1か0だけ
Mathematica/Wolfram言語にGPIOへアクセスする方法が書かれている。 →GPIO
標準APIでは1と0しか扱えないようだ。

SPI/I2C/PWMのためには?
MathLinkというプラグイン的なものを使うことで出来そうだ。
Wolframドキュメントセンター上では、MathLinkは WSTP に置き換えられているが、自分のRasbianでは、、mathlinkのまま。最新を取ってくるとまた違うかも。


MathLink開発環境
/opt/Wolfram/WolframEngine/10.0/SystemFiles/Links/MathLink/DeveloperKit/Linux-ARM
に、必要なライブラリやサンプルプログラムが入っている。情報はこれだけで十分。

WiringPi
下回りのライブラリとして、GPIO制御の定番らしいWiringPiを使う。
$ sudo apt-get install wiringpi
で入れてもいいと思うけど、最新がいいらしいので
$ git clone git://git.drogon.net/wiringPi
$ cd wiringPi
$ sudo ./build 
で入れる。

やってみたこと
お試しで買った工作キットに、mcp3002(ADC)とフォトトランジスタ、温度センサーが入っていたので、SPIにmcp3002をつけ、mcp3002にフォトトランジスタと温度センサーをつけて、部屋の明るさと温度をプロットしてみる。そして、それをWebで見れるようにする。という感じ。


SPIプログラム(mcp3002.c)
#include 
#include 
#include 

#define PINBASE 64
#define SPI_CH  0
#define TO_VOLT (3.3f/1024)

extern float spi(int ch)
{
  return TO_VOLT * analogRead(PINBASE + ch);
}

int main(int argc, char* argv[])
{
  mcp3002Setup(PINBASE,SPI_CH);
  return MLMain(argc, argv);
}
SPIデータを取得するMathLinkプログラムはこんな感じ。WiringPiのmcp3002 API使うと非常に簡素だ。MathLink対応はmathlink.hをインクルードして、mainでMLMainを呼ぶようにするだけで良い。

MathLinkライブラリ化(mcp3002.tm)
float spi P((int));

:Begin:
:Function:       spi
:Pattern:        Spi[ch_Integer]
:Arguments:      { ch }
:ArgumentTypes:  { Integer }
:ReturnType:     Float
:End:

:Evaluate: Spi::usage = "Spi[ch] gives the SPI value."
単にプログラムをコンパイルするだけではダメで、上のようなテンプレートファイルを作成して、 mprep ツールで tmからCに変換したものをセットでビルドする。

uuidライブラリがなくて、リンク時にこけたので
$ sudo apt-get install uuid-dev
で入れた。

Makefile
CADDSDIR = /opt/Wolfram/WolframEngine/10.0/SystemFiles/Links/MathLink/DeveloperKit/Linux-ARM/CompilerAdditions
INCDIR = ${CADDSDIR}
LIBDIR = ${CADDSDIR}

MPREP = ${CADDSDIR}/mprep
CXX = /usr/bin/c++
RM = rm

EXTRA_CFLAGS= -O2

PROGRAM = mcp3002
OBJS = $(PROGRAM).o $(PROGRAM)tm.o

all : $(PROGRAM)

$(PROGRAM) : $(OBJS)
        ${CXX} ${EXTRA_CFLAGS} -I${INCDIR} $(OBJS) -L${LIBDIR} -lwiringPi -lML32i4 -lm -lpthread -lrt -lstdc++ -ldl -luuid -o $@

.c.o :
        ${CXX} -c ${EXTRA_CFLAGS} -I${INCDIR} $<

$(PROGRAM)tm.c : $(PROGRAM).tm
        ${MPREP} $? -o $@

clean :
        @ ${RM} -rf *.o *tm.c $(PROGRAM)
makeビルドすると、mcp3002という実行プログラムが出来上がる。
これをMathematicaでInstallで読み込むと、Spi[0]とSpi[1]でCのspi(int ch)を呼べるようになる。

Mathematicaスクリプト(spi.m)
(* ::Package:: *)

(* Load 'mcp3002' MathLink Module *)
Install["mcp3002"]


(* Function *)
lux := {DateList[], Spi[0] * (100/(7500*0.000033))}
deg := {DateList[], (Spi[1]-0.5)*100}

(* Data List *)
luxData = {}
degData = {}
interval = 5 * 60;


(* Plot *)
(* Lux *)
luxNow = Dynamic[Last[AppendTo[luxData,lux];
             If[Length[luxData]>144,luxData=Drop[luxData,1]];
             luxData],
        UpdateInterval->interval, TrackedSymbols->{} ]
luxPlot = Dynamic[ DateListPlot[luxData, Joined->True, PlotLabel->"Lux"], SynchronousUpdating->False]

(* Tempeture *)
degNow = Dynamic[Last[AppendTo[degData,deg];
             If[Length[degData]>144,degData=Drop[degData,1]];
             degData],
        UpdateInterval->interval, TrackedSymbols->{} ]
degPlot = Dynamic[ DateListPlot[degData, Joined->True, PlotLabel->"Temp"], SynchronousUpdating->False]

(* Export *)
Dynamic[
        Export["lux_now.gif",luxNow];
        Export["lux_plot.gif",luxPlot];
        Export["temp_now.gif",degNow];
        Export["temp_plot.gif",degPlot];
        Share[],
        SynchronousUpdating->False,
        UpdateInterval->interval]
Install["mcp3002"]でMathLinkライブラリを読み込み。これを実行すると実際にmcp3002プログラムがプロセスとして起動する。プロセス間通信でやりとりしているようだ。
その後、明るさ(lux)と温度(deg)関数を定義。Spi[0]とSpi[1]で取得したデータを変換している。

Dynamic[]を使って、5分毎にSPIデータをサンプリングして、Dateリスト型の変数へ追加していく、 さらにそれをプロットする。Mathematicaノートブック上でプロット結果が見れるようになる。

最後が、それぞれの出力結果をGIFイメージへExportする処理。このイメージをWebServerで見れるようにすれば、外のブラウザで結果を見ることができるようになる。

実行
vncサーバーでも立てて、繋いで、プログラムとスクリプトがあるディレクトリから
$ mathematica spi2.m
で起動して、右上の「Run Package」を実行すると、プロットなどのDynamic処理が5分ごとに更新される。

これはこれで問題がある、後述の「考察」が解決策。


軽量Webサーバー立てる
$ sudo apt-get install lighttpd
$ sudo lightly-enable mod userdir
$ sudo /etc/init.d/lighttpd force-reload
で、~/public_htmlにindex.html書いて、http://(pi2)/~pi/ で見れるようになる。
~/public_htmlにmathematicを実行しているカレントフォルダのリンクを貼ってgifへアクセスできるように。
あとは、適当にindex.htmlを用意。

ブラウザからアクセスしてみた結果がこちら
こちらも5分毎に更新される。

考察(メモリ不足)

コンソールから起動すると"can't open display"と言われたため、VNCなり何なりでX11ディスプレイが必要なのだと考えた。実際それで動いた。が、数時間稼働させると、ほぼハング状態に陥った(死んではいないが操作不能状態)。それは、Share[]を処理のサイクルに加える事で改善した。
また数時間稼働させると、今度は
No more memory available.
Mathematica kernel has shut down.
Try quitting other applications and then retry.
となって、停止してしまった。

ディスプレイ(VNC)無しで実行できないか?
と考えて、最初はxvfb経由でバッチ的に動かしたらどうだ?とか試した
$ xvfb-run -s "-screen 0 640x480x8" mathematica spi.m
スクリプトは読まれるが、ノートブックに読み込まれるだけで評価されない。

ディスプレイ(VNC)無しで実行できる!
Creating and Post-Processing Mathematica Graphics が参考になる。

MathematicaのNotebookインターフェース通さずに、直接MathematicaKernelを起動すればいい。

math.sh(Kernel起動シェルスクリプト) 
#!/bin/sh
export LD_LIBRARY_PATH=/opt/Wolfram/WolframEngine/10.0/SystemFiles/Libraries/Linux-ARM
MathKernel=/opt/Wolfram/WolframEngine/10.0/SystemFiles/Kernel/Binaries/Linux-ARM/WolframKernel
${MathKernel} -noprompt -script $1
こんなのを用意して
$ math.sh spi.m
で起動すると直接Kernelでのバッチ起動となって、-scriptで指定したスクリプトファイルが実行される。

Dynamic[]はスルーされる
直接実行は出来たけど、Dynamic[]は無視されてしまい、結果が得られないため、Dynamic[]を使わない処理フローに変更

spi2.m
Install["mcp3002"]

(* Function *)
lux := {DateList[], Spi[0] * (100/(7500*0.000033))}
deg := {DateList[], (Spi[1]-0.5)*100}

(* Data List *)
luxData = {}
degData = {}
interval = 5 * 60;

While[True,
        (* Lux Plot *)
        luxNow = Last[AppendTo[luxData,lux];
                      If[Length[luxData]>144,luxData=Drop[luxData,1]];
                      luxData];
        luxPlot = DateListPlot[luxData, Joined->True, PlotLabel->"Lux"];

        (* Tempeture Plot *)
        degNow = Last[AppendTo[degData,deg];
                      If[Length[degData]>144,degData=Drop[degData,1]];
                      degData];
        degPlot = DateListPlot[degData, Joined->True, PlotLabel->"Temp"];

        (* Export *)
        Export["lux_now.gif",luxNow];
        Export["lux_plot.gif",luxPlot];
        Export["temp_now.gif",degNow];
        Export["temp_plot.gif",degPlot];
        Share[];
        Pause[interval]
]
DynamicをやめてWhileブロック内に全部入れた。サンプリングタイミングはPause[]で。

$ math.sh spi2.m &

しばらくすると、gifイメージが作られ始めた。
VNCサーバー、Mathematica FrontEnd(Notebook)を介さないため、メモリ消費の少ない状態をキープ出来ている。CPUにも余裕がある。lighttpdもストレスが無い。


いやー、長い道のりだった・・・Mathematica使うのなんて10数年ぶりだし。だけど面白かった~

2 件のコメント:

  1. こんにちは(・・)

    すみません、教えてほしいことがあります。
    wiringPiを使ってプログラムを作成されていますが、実行するときはsudoを付けないでも大丈夫でしょうか?
    なにか、工夫されているのでしたら、よろしければ教えていただければと思っております。

    それでは、失礼します。

    返信削除
  2. デフォルトのpiユーザで実行していますか?
    また、普通に
    $ gpio -v
    $ gpio readall
    とやった時にどうなりますか?

    だいぶ前のことなので記憶が曖昧ですが、
    http://wiringpi.com/download-and-install/
    にも書かれていますが、wiringPiをビルドする時にsudoつけてビルド&インストール
    することで全ユーザが使えるところにインストールされるはずです。
    自分はそうしています。

    返信削除