Atom & Arduino :: First Program (pt. 2)
Last time we talked about a few changes I made to Atom. This time were going to start writing some programs. Our first one will be pretty simple.
First, we need a short C file (this is the only C code, besides the
#includes and the prototype we use in
blink.h, we need to write our selves):
#include "blink.h" /* PORTB corresponds some how to Pin 13. We get * a blinking LED on the board. We can also plug * an LED into Pin 13 and the GND pins. */ void setLEDuint8_t value PORTB = 0xFF * !!value; /* main only needs to continously call blink_atom(). */ int mainint argc, char * argv while1 blink_atom;
Next, lets make
Main in our Haskell file, pull in some modules, and define the AtomConfig:
module Main where import Language.Atom import Data.Word -- Override some default values cfg = AtomConfig -- This pulls some stuff in we use below. cIncludes = Just "blink.h", -- The (u)intXX_t types are defined in -- stdint.h--which we include from blink.h cTyper = Just $ \t -> case t of Bool -> "uint8_t" Int8 -> "int8_t" Int16 -> "int16_t" Int32 -> "int32_t" Int64 -> "int64_t" Word8 -> "uint8_t" Word16 -> "uint16_t" Word32 -> "uint32_t" Word64 -> "uint64_t" Float -> "float" Double -> "double"
We reference the C include file blink.h. This is here to pull in some utility AVR chip utility functions and stdint.h (which defines
uint8_t and friends). We also prototype the
blink_atom function Atom will eventually define. Well go over what to do with this in just a bit.
Next we define main:
-- Main just has to compile the Atom expression main :: IO main = compile "blink_atom" Just cfg blink
Remember that the version of
compile used here is not the version included with atom-0.0.2. This is the altered version we discussed earlier.
The first argument tells Atom what to name the top level functionin this case,
blink_atom. Youll notice that this was the function prototyped in
blink.h. Secondly, we pass the config we just defined. If one does not wish to define any include files or override the default C types, its perfectly fine to pass Nothing here insteadthe defaults will be used.
The last argument,
blink, is the name of the
Atom ()the type used to describe our systemwere about to define.
Lets take a look at that:
-- Simple Atom to toggle an LED blink :: Atom blink = do -- Is the LED currently on? (Assume it starts False/off) isOn <- bool "isOn" False -- Does the toggle counter need a reset? (Assume it starts False/no) doReset <- bool "doReset" False -- Initialize the toggle counter to delayCycles toggle <- word16 "toggle" delayCycles -- Decrements the toggle counter when it -- is greater than 0. period 1 $ atom "decrement" $ do cond $ value toggle >. 0 toggle <== value toggle - 1 -- Checks if we need to perform a toggle -- reset, and performs it when we need one. period 2 $ atom "reset" $ do cond $ value doReset doReset <== Const False toggle <== Const delayCycles -- Checks if the toggle counter has expired. -- Toggles the LED if it has, then requests -- a reset. period 2 $ atom "flip" $ do cond $ value toggle <=. 0 setLED isOn isOn <== not_ $ value isOn doReset <== Const True
cond acts as a guard. It ensures that the boolean statement passed to it is true before allowing the rest of the atom to execute.
<== is the assignment operator. We'll discuss
period in more depth later, but for now, you just need to know that something with a period of 2 will run every other tick1 through the system whereas something with a period of 3 will run every third tick.
delayCycles refers to the number of cycles through the main loop we want to wait before toggling the state of the LED. Since this particular CPU is running at about 16Mhz, we define this as some relatively large number:
-- How many cycles do we want to delay before -- we flip the LED? delayCycles :: Word16 delayCycles = 30000
setLED describes how to call the corresponding
setLED C function we defined in
blink.c. Lets look at the type signature of
action :: String -> String -> UE -> Atom
So, it expects, for its first argument, a function which takes a list of
Strings and returns a
String. Its second is a list of "untyped expressions", and it produces an
Atom takes the list of UE's, converts them into the C symbols (often times something like "e1" or "e2"), and then passes them to the provided function for inclusion in some C statment. We can see this at work in the code snippet below:
-- An action (basically raw C code) to set the value -- of the LED. setLED() is defined in blink.c. setLED :: V Bool -> Atom setLED v = action \x -> "setLED(" ++ x ++ ")" v' where v' = ue . value $ v
Here, we take a Bool, and we pass it to the
setLED function, which, presumably, turns the LED on when True, and turns it off when False.
This has been a brief discussion of the Haskell code needed to generate the C code for the Arduino board. Next time, we'll investigate the C code that was generated (along with some other artifacts) and how they correspond to the Haskell we have written.
Here are links to the files we've dicussed today: