3.外部インタフェースを作成する
BrickPiを介してRaspberry PiからLego Mindstormsを、Haskellで制御する方法を検討してきた。これまでに実装方法として次の二通りあることが分かった。
1) C言語の開発環境では、BrickPi.hというヘッダー・ファイルを用意して、モータやセンサにアクセスできるようにしている。Haskellでもこれに倣って、BrickPi.hと同等の機能をHskellで実装するのが一つの方法である。このとき、ハードウェアのBrickにアクセスするためにLinuxの標準的なライブラリを利用する。
2) BrickPi.hの機能を活用して実装する。この時、BrickPi.hでは、ユーザのプログラムとの間で、モータやセンサの制御データを構造体BrickPi(あちらこちらにBrickPiがでてきてややっこしいのだが)で行っているが、Haskellの方からこの構造体に直接アクセスするのはコストが高すぎる(C言語の配列は、Haskellではリストとして実装される。このため、C言語での配列の一つの要素に対するアクセスでも、Haskellではリストを用いての配列全体へのアクセスとなってしまう)ので、構造体のそれぞれの要素にアクセスできるようにsetter,gtterを用意する。
Raspberry PiとBrickPiの間のデータのやり取りは、UARTと呼ばれるシリアル通信で行われ、そのプロトコルの内容まで分かったので、できることなら1)で実装したいのだが、UARTとの通信が難しそうであることが判明した。
一つは、WiringPiで実装されたBrickPi.hが心地よく動かないことである。WiringPi.hのコメントにも書かれているのだが、UARTとの情報のやり取りは、データが文字列で構成され、構成する文字を一つずつ連続して送出することで行われる。しかし、次の文字の送出に時間がかかってしまうと、ハードウェアのBrickPiはエラーと見なしてしまう。このため、UARTとの通信には安定したライブラリを使う必要がある。
二つ目は、シリアル通信用にHaskellで実装されたライブラリにserialportというのがある。これは1)の方針にそって実装されていると思われるが、やはりうまく動かない。
UARTプロトコルの内容を折角理解したのに残念だが、これを理解しなくてもよかった2)で実装することにした。
前回の記事で説明したが、C言語用に用意されているBrickPi.hをヘッダーファイルではなく、cのプログラムファイルになるように、ファイル名の添え字をhからcに変更してBrickPi.cとする。そして、BrickPi.cには、構造体BrickPiの各要素へのsetter,getterを用意する。ここでは、取りあえず、モータを動かしたいので、モータに関するものだけを用意する。次に、外部から呼ばれる関数をまとめ、これをBrickPi.hとする。前の記事では、これは次のようになっていた。
/* BrickPi.h */ void SetAddress(int Index, unsigned char Value); void SetMotorSpeed(int Index, int Value) ; void SetMotorEnable(int Index, unsigned char Value); int BrickPiSetTimeout(); int BrickPiSetupSensors(); int BrickPiUpdateValues(); int BrickPiSetup();
これらの関数をHaskellから呼べるようにするためには、外部関数インタフェースを用意する。Haskellでは、このために、c2hscやhsc2hsなどの自動化ツールを用意しているが、ここでは数も少ないし、それほど難しい作業でもないので、手動で作成する。
外部関数インタフェースは、基本的には、C言語の方で用意された関数に対応させて、Haskellの側でも関数を用意し、それぞれの関数に対して、型シグネチャを定義することである。型シグネチャの定義はちょっと厄介だが、規則性があるので、それを理解すればそれほど難しくはない。
例で示すことにする。C言語で書かれた関数
void SetAddress(int Index, unsigned char Value);
の外部関数インタフェースは次のようになる。
foreign import ccall "BrickPi.h SetAddress" c_setAddress :: CInt -> CUChar -> IO () setAddress :: Int -> Int -> IO () setAddress i v = c_setAddress (fromIntegral i) (fromIntegral v)
最初の一行はC言語側の関数の説明である。ここでは、ヘッダーファイルがBrickPi.hで関数名がSetAddressを呼び出す(foreign import ccall)ことを宣言し、C言語側の関数名をc_setAddreaaで置き換えている。関数SetAddressの定義を見ると、
void SetAddress(int Index, unsigned char Value){ BrickPi.Address[Index] = Value; }
となっている。C言語でのint,unsigned charはHaskellではCInt,CUCharで表すので、c_setAddressの型シグネチャはCInt -> CUChar -> IO ()となる。
次の二行目と三行目は、これに対応したHaskell側の関数で、ここでは、setAddressと名前が付けられている。C言語側でのCInt,CUCHarは32ビット、8ビットでの整数表現である。Haskellではここまで詳細な表し方はせず、整数はすべてIntで表す。従って、型シグネチャはInt -> Int -> IO ()となる。
三行目はこの関数の実装である。実際は、c言語の環境で行うので、引数については、Haskellの型からC言語の型への変換が必要となる。ここでは、IntをCInt,CUCharに変換する必要があるので、fromIntegralを用いて変換を行えるようにする。
他の関数についても同じように定義できる。このファイルをBrickHs.hsと呼ぶこととする。
{-# LANGUAGE ForeignFunctionInterface #-} module BrickHs where import Foreign.C -- get the C types foreign import ccall "BrickPi.h SetAddress" c_setAddress :: CInt -> CUChar -> IO () setAddress :: Int -> Int -> IO () setAddress i v = c_setAddress (fromIntegral i) (fromIntegral v) foreign import ccall "BrickPi.h SetMotorSpeed" c_setMotorSpeed :: CInt -> CInt -> IO () setMotorSpeed :: Int -> Int -> IO () setMotorSpeed i v = c_setMotorSpeed (fromIntegral i) (fromIntegral v) foreign import ccall "BrickPi.h SetMotorEnable" c_setMotorEnable :: CInt -> CUChar -> IO () setMotorEnable :: Int -> Int -> IO () setMotorEnable i v = c_setMotorEnable (fromIntegral i) (fromIntegral v) foreign import ccall "BrickPi.h BrickPiSetup" c_brickPiSetup :: IO CInt brickPiSetup :: IO Int brickPiSetup = fmap fromIntegral c_brickPiSetup foreign import ccall "BrickPi.h BrickPiSetupSensors" c_brickPiSetupSensors :: IO CInt brickPiSetupSensors :: IO Int brickPiSetupSensors = fmap fromIntegral c_brickPiSetupSensors foreign import ccall "BrickPi.h BrickPiUpdateValues" c_brickPiUpdateValues :: IO CInt brickPiUpdateValues :: IO Int brickPiUpdateValues = fmap fromIntegral c_brickPiUpdateValues foreign import ccall "BrickPi.h BrickPiSetTimeout" c_brickPiSetTimeout :: IO CInt brickPiSetTimeout :: IO Int brickPiSetTimeout = fmap fromIntegral c_brickPiSetTimeout foreign import ccall "tick.h ClearTick" c_clearTick :: IO CInt clearTick :: IO Int clearTick = fmap fromIntegral c_clearTick foreign import ccall "tick.h CurrentTickMs" c_currentTickMs :: IO CULong currentTickMs :: IO Int currentTickMs = fmap fromIntegral c_currentTickMs foreign import ccall "tick.h CurrentTickUs" c_currentTickUs :: IO CULong currentTickUs :: IO Int currentTickUs = fmap fromIntegral c_currentTickUs
4.モータ駆動のHaskellプログラム
今、定義した外部関数インタフェースを利用して、モータを駆動するプログラムを作成する。二つのモータが備わっている車で、キーをたたくと、前進、後退、左折、右折するプログラムMotor-test.hsを考えると次のようになる。
import BrickHs main = do result <- brickPiSetup print (show result) setAddress 0 1 setAddress 1 2 setMotorEnable 0 1 setMotorEnable 1 1 result <- brickPiSetupSensors print (show result) print "Go Forward" setMotorSpeed 0 200 setMotorSpeed 1 200 result <- brickPiUpdateValues print (show result) getLine print "Go Backward" setMotorSpeed 0 (-200) setMotorSpeed 1 (-200) result <- brickPiUpdateValues print (show result) getLine print "Turn Left" setMotorSpeed 0 (-200) setMotorSpeed 1 200 result <- brickPiUpdateValues print (show result) getLine print "Turn Right" setMotorSpeed 0 200 setMotorSpeed 1 (-200) result <- brickPiUpdateValues print (show result) return ()
このプログラムをコンパイルする(これにはghc --makeを用いる。これは一つのルートモジュールとそれに付随するモジュールから実行ファイルを作成する。また、ルートモジュールは、moduleとしない。下の例はMotor-test.hsがルートモジュールで、上記のプログラムではmoduleの指定がない)。
ghc --make Motor-test.hs BrickHs.o tick.o
実行は、
./Motor-test
二つのモータが思い通りに回転すれば成功である。
これで、Haskellからモータが制御できることが判明したので、後は、センサを実装して、ロボットを作成する。