Roadblocks -- Programming a PIC24 with Atom

Posted on August 15, 2016


Today I tried to start work on an I2C implementation in Atom. This is where I started to run into problems. Also, some other confusion came about during further abstractions. I’ll just list them out piecewise

Clock and cycle are the same

While reading Atom’s (very limited) documentation, I got the impression that the scheduler was intelligent enough to break a cycle (i.e. a single invocation of the tick function) into sub timings (clock ticks) so that higher resolution waits could be performed.

I was wrong. The only reason to use the hardwareClock setting on the Atom configuration is to perform runloop ticks at a rate slower than the timer (or clock) allows.

Generating direct code (e.g. initialization) with Atom

Ideally, I wanted to use the Atom DSL to generate not only the runloop with scheduling, but also to generate an initialization method to be called once. While it appears that the initialization can be done by using the oneShot family of functions, this feels like it will add unnecessary overhead to the runloop itself, since it will check to see if it’s time to initialize (yes, I know, it’s a single fetch and compare, but still) every time through the runloop.

Instead, I have to expand each variable’s name twice – once for initialization, and then once as the external symbol to use in the DSL.

I will be investigating this further, as I begin to abstract the PIC’s registers further.

Channels seem dumb.

I was really excited by the channel family of functions in Atom. I saw these as a beautiful way to atomically pass information from one block to another. However, they amount to nothing more than a blind edge detection without even passing a value (the value they return is constant).

Enum types aren’t usable (by default)

I ran into a problem while trying to find a way to implement I2C in Atom. I wanted to use an ADT deriving Enum as a state variable; this way I could be ensured that I never used an illegal value in the code: the enumeration would be typesafely restricted in the eDSL to only valid values.

I have found a workaround to this.

I am not comfortable enough with haskell’s compiler extensions to begin using them willy-nilly; so when I tried to implement something like:

instance Enum a => Expr a where

The compiler initially barked at me to enable FlexibleInstances and then something else – this was not a rabbit hole I was ready for.

So, at this time, for all enumerations I can create some helper methods:

enumVToWord8 :: Enum a => V a -> V Word8
enumVToWord8 (V (UVExtern s t)) = V $ UVExtern s t
enumVToWord8 (V (UV i s t)) = V $ UV i s t

enumEToWord8 :: Enum a => E a -> E Word8
enumEToWord8 (Const b) = Const $ fromIntegral $ fromEnum b
enumEToWord8 (VRef v) = VRef $ enumVToWord8 v

Then implement the specific enumeration’s class as such. This has incomplete matches, but sufficient for my purposes at this time; I should probably improve the functions to be more complete.

data MyEnum = One | Two | Three
            deriving Enum

instance Expr MyEnum where
  eType _ = Word8
  constant = CWord8 . fromIntegral . fromEnum
  expression = EWord8 . enumEToWord8
  rawBits (Const b) = Const $ fromIntegral $ fromEnum b
  variable (V u) = VWord8 $ V u

instance EqE MyEnum

instance Assign MyEnum where
  (V bv) <== be = V bv <== enumEToWord8 be

With this added, a MyEnum variable can be created, tested, reassigned, and copied:

mystate <- var "myState" One
laststate <- var "oldState" Two

atom "assigntest" $ do
  cond $ value mystate ==. Const One
  laststate <== mystate
  mystate <== Const Two

The code generated uses a uint8_t since I do not expect to ever use more than 256 values for an enumeration, and it will fit in a single register on any processor.

Buffers? Structs? … Streams?

Buffers and streams feel like such a fundamental part of programming that I am somewhat bewildered at their absence from Atom. When I embarked on this quest, I had beautiful notions of data streams from which my [copilot] based embedded firmware would be sipping in a confined realtime environment. It was a beautiful vision. Unfortunately, most of the eDSLs seem to have an aversion to dealing with any level of variable execution and the simple task of writing to a UART isn’t covered in any of the examples that I’ve been able to dig up.

With that in mind, I find that Atom feels very limited in terms of operating in any kind of mixed environment. Since the board that I’m working on is going to require all of I2C, SPI, and serial communication, I feel as though I’ve run into a huge roadblock.

My breakthrough with the enumerations above gives me hope, though. I can vaguely imagine an abstraction on an abstraction which implements serial communication from and into a predefined circular buffer. So, this is the direction in which I will head next. Once I’ve exhausted this possibility, I will probably be forced to drop back to C due to time constraints. Which means that combining haskell and firmware will have to wait until the next project.

I hope it works, but the next few days will be the only way to tell.

To be continued…