Arduino Spaceship Interface in the Atom DSL
Tags: programming.arduino, programming.c, programming.atomdsl
In my previous
post,
I discussed some of the simple details behind a pure C example programming an
Arduino.
Using that understanding, I was able to implement a pure C implementation of
the Spaceship Interface
project. (“Spaceship Interface” = “green LED + 2 blinking red LEDs”, btw).
The Atom EDSL is a DSL embedded in Haskell; and is advantageous in certain use cases. (That “Haskell Embedded” blog is fascinating – well worth reading; and one of the few places online to have a fairly involved example of Atom code).
Translating the code to Atom wasn’t too hard. (If you’re willing to relax your
definition of “translation”; my Atom implementation stops ‘blinking’ as soon
as the switch is released, whereas the Arduino implementation delays up to
250ms. – My translation captures the essence).
I thought it’d be an interesting, simple comparison.
Code examples in Arduino, pure C, and Atom can be found at
rgoulter/arduino-atom-examples.
e.g. the Blink example in Atom, for a look at a less-involved Atom example.
The main snippet from the C code:
if (switchState == 0) {
/* enable PortD3 (green), disable PortD4 & PortD5 (red) */
|= _BV(PORTD3);
PORTD &= ~(_BV(PORTD4) | _BV(PORTD5));
PORTD } else {
/* disable PortD3 (green) */
&= ~_BV(PORTD3);
PORTD
/* disable PortD4, enable PortD5 (red) */
&= ~_BV(PORTD4);
PORTD |= _BV(PORTD5);
PORTD (BLINK_DELAY_MS);
_delay_ms
/* enable PortD4, disable PortD5 (red) */
|= _BV(PORTD4);
PORTD &= ~_BV(PORTD5);
PORTD (BLINK_DELAY_MS);
_delay_ms}
The whole Atom code (src):
{-# LANGUAGE QuasiQuotes #-}
module Spaceship (main) where
import Text.Heredoc
import Language.Atom
= "PORTD3"
greenLED = "PORTD4"
redLED1 = "PORTD5"
redLED2
= action (\v -> "PORTD |= _BV(" ++ led ++ ")") []
ledOn led = action (\v -> "PORTD &= ~_BV(" ++ led ++ ")") []
ledOff led
-- | Our main Atom program.
spaceship :: Atom ()
= do
spaceship <- bool "switchState" True
switchState
-- read into switchState
"readButton"
call
"standBy" $ do
atom $ not_ (value switchState)
cond
ledOn greenLED
ledOff redLED1
ledOff redLED2
"blinking" $ do
atom
cond (value switchState)
ledOff greenLED
let halfDelay = 25000
let blinkPeriod = 2 * halfDelay
$ phase 0 $ atom "blink1" $ do
period blinkPeriod
ledOff redLED1
ledOn redLED2
$ phase halfDelay $ atom "blink2" $ do
period blinkPeriod
ledOn redLED1
ledOff redLED2
cHeader :: String
= [here|
cHeader
#include <avr/io.h>
static inline void readButton(void);
|]
cFooter :: String
= [here|
cFooter
static inline void readButton() {
// read switch state into the Atom variable
state.Spaceship.switchState = (PIND & _BV(PORTD2)) != 0;
}
int main (void) {
// Set input PD2,
// Set output PD3, PD4, PD5
DDRD &= ~_BV(DDD2);
DDRD |= _BV(DDD3) | _BV(DDD4) | _BV(DDD5);
while(1) {
Spaceship();
}
return 0; // Never reaches
}
|]
main :: IO ()
= do
main let atomName = "Spaceship"
let code _ _ _ = (cHeader, cFooter)
let cfg = defaults {cCode = code,
= False,
cRuleCoverage = False}
cAssert <- compile atomName cfg spaceship
(schedule, _, _, _, _) putStrLn $ reportSchedule schedule
Remarks:
There’re obviously other ways of writing the “interface with C” bits in the Haskell code. – There’re other ways to implement the C code, too.
The Atom code is longer.
But. In a contest between “which is more obvious to read?”, I’d say the Atom code wins hands-down, once you understand that Atom is declarative. (Only 1 comment in the Atom part!).
– In a contest of “refactorability”/“changeability”, surely Atom wins here, too: the seam-points and abstractions, and thus where and what to change, are evident.
– The extra length comes from various `atom “ruleName $ do” lines (which build ‘abstractions’ of instructions), as well as the Atom boilerplate, and the C footer/header to do the low-level operations (where the code is basically the same as my C implementation).If I must explain on the program:
It’s structured:- Stuff which
spaceship :: Atom ()
uses, - The main Atom program (
spaceship :: Atom ()
),
– This is further broken down into “declaration (of a variable)”, and the various “rules” of “compute-what, when-what, at what period/phase”. - The C interface & low-level code,
- The Atom boilerplate.
- Stuff which
Haskell usually has a promise like “if it compiles, it works” (with a footnote of “until you get a memory leak”). – Whether that applies to Atom constructs depends on the Atom compiler.
There are implicit couplings between the Atom and the C code in the above program:
state.Spaceship.switchState
(in the C) depends on variables in Haskell’smain
, as well as in thespaceship :: Atom ()
construct. – Similarly,ledOn
, etc. in Haskell referencePORTD
.
– I’m not sure how to minimise such couplings.For a contrivedly (or, pedagogically) simple program, the advantage to using Atom is outweighed by the length.
The
25000
seems pretty magic to me. (And I guess if you need precise timing, you’d be more explicit about that in the C code).Wow, Make can be useful.