bitterharvest’s diary

A Bitter Harvestは小説の題名。作者は豪州のPeter Yeldham。苦闘の末に勝ちえた偏見からの解放は命との引換になったという悲しい物語

HaskellでLego Mindstormsを制御する(2)

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からモータが制御できることが判明したので、後は、センサを実装して、ロボットを作成する。