Saturday, May 19, 2007

Haskell Incarnate

I'm off to the Make Faire in a few minutes and I'm running in Bay to Breakers tomorrow so I probably won't get much time to write about what I've been up to this weekend. So here's a little taster. My first robot programmed in Haskell:

It's a pretty simple device that's programmed to follow objects that come into the view of the three IR distance sensors mounted on the front of it. (The object in this case being a skull on the end of a stick, must have been left over from Hallowe'en...) But the point, at this stage, wasn't to do something sophisticated with the robot, but to see if I could actually program such a device in a high level language that wasn't C/C++ or assembler.

For hardware I used the Make controller and a Rigel robot base. I also use a bunch of Sharp IR sensors.

So for software I started with Matthew Naylor's module for Lava described in The Monad Reader Issue 7. I didn't have to do much with this code at all so it's almost embarassing to claim I did anything. The Make controller is ARM based and programmable in C. In fact it can run FreeRTOS and I simply modified Matthew's code to spit out a C function that is spawned as a task in FreeRTOS. It was about 30 minutes work to make the changes. With Matthew's code modified I now have a Haskell DSL that is translated to C for compilation for the robot. Strictly speaking there isn't actually a Haskell runtime on the robot. Think of the Haskell as a metaprogram that spits out C.

The actual Haskell DSL code looks something like this:

> main = writeMakeController "rigel" f where
> f inp = Output {
> servoSpeed0 = 545+leftSpeed,
> servoSpeed1 = 550-rightSpeed,
> servoSpeed2 = 550+leftSpeed,
> servoSpeed3 = 540-rightSpeed
> }
> where
> rightSpeed = larger (threshold 300 leftSensor) (threshold 300 centreSensor)
> leftSpeed = larger (threshold 300 rightSensor) (threshold 300 centreSensor)
> centreSensor = smooth $ adc0 inp
> leftSensor = smooth $ adc5 inp
> rightSensor = smooth $ adc4 inp
> smooth x = (x + delay 0 x + delay 0 (delay 0 x) + delay 0 (delay 0 (delay 0 x)))/4
> threshold m x = x />=/ m ? (256,0)
> larger x y = x />=/y ? (x,y)

And the video above is the resulting behaviour.

Note, this is the first thing I got working. I make no great claims for the quality or ingenuity of the code. Most of it should be self-explanatory but note the function smooth which averages over the last inputs. This doesn't make sense when considered as an ordinary function because in a pure functional language you can't add the argument of a function to the value that you passed in last time you called the function. But these values are all streams and in principle this makes the DSL extremely powerful. Anyway, I'll write it up better at a future point and I'll see what I can do about making the code available.