Dataflow programming for PIC microcontrollers

Recently I've been working on a language for programming some of the higher-end PIC microcontrollers. These are very resource-constrained machines, but they can be programmed in C; the one I use the most, the PIC18F4520, has only 32 Kbytes of flash program memory and a whopping 1536 bytes of RAM.

The problem is, while C is a semi-decent language (at least compared to PIC assembly), it still sucks for PIC programming. A lot. In order to make any non-trivial program, you need to set up a bunch of the onboard hardware modules which all have low-level access methods, and you need to do a bunch of interrupt programming (pragmas everywhere!) which gets you mired in a mess of low-level details -- did you remember to clear a certain bit in some obscure configuration register?

For most of the PIC programs I write, I actually think about the problem in terms of data flowing between blocks, similar to Simulink. For example, I might envision a program which performs analog to digital conversions and passes those values on to a bank of LEDs as a simple block diagram with two blocks, one for the A/D converter and another for the LED output. If blocks of arbitrary C code are supported and blocks for other hardware modules are added, this concept turns out to be powerful enough to handle almost every PIC program I've written, and without the pain of writing C code for the PIC. For example, here's a fairly simple program in a lisp-like syntax:

;; Define the configuration for the hardware devices
(config-bits
 (a/d :an1)
 (trisb 0)
 (trisd 0))

;; Define a custom block of C code
(defcode-inline inverter (x)
  "(~x)")

;; Connect analog input 1 (an1) to a bank of LEDs (latb)
(-> an1 latb)
;; Another bank of LEDs (latd) will be the bitwise inverse of latb
(-> an1 inverter latd)     ; Equivalent to (-> an1 inverter) (-> inverter latd)

This would be a lot harder to write in C, and I've got a partial implementation of a compiler for this language which is working fairly well, so I think I'm onto something good here.

Unfortunately, there's probably a mess of useful information about these sorts of blocks-and-connections languages, and I don't know any of it. So, better informed people: what do you think of this type of programming language? Are there any practical issues I should know about? And how would I go about typechecking this sort of language? Since type mismatches are likely to be caught by the C compiler and presented as baffling error messages complaining about perplexingly named mangled symbols, some form of static typechecking before the intermediate C code is generated takes on more than a little urgency.

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

For typechecking: are you

For typechecking: are you connecting blocks, or are you connecting ports on the blocks? Seems to me the latter probably solves most of your woes, you just check you're expecting the input end to match the output it's receiving.

I'm connecting ports on blocks

You're right, since I'm connecting ports on blocks, I can get simple typechecking by just making sure that input and output types match. However, there are some complications.

For example, suppose you have a block that accepts 32-bit signed inputs and a block that gives 8-bit unsigned outputs, and for some reason you want to connect these blocks together. Should the compiler simply allow this and silently convert the values, or should it give a warning or error?

Some blocks take or accept 10-bit unsigned values, which are usually put into an unsigned 16-bit int. Should my typechecker have any notion of a 10-bit value, or should it remain blissfully unaware? The latter option is easier to write, but having a 10-bit int type might make it easier to support the surprisingly useful "convert unsigned 10-bit int into unsigned 8-bit int by dropping the 2 least significant bits" operation, I think.

I may be overthinking this....

These are now issues in your

These are now issues in your language rather than with getting a translation via C to work, right?

Personally I'm inclined towards as much info as I can add and no implicit conversions - but it's your call, and it should be fairly quick to modify. It's more work for your typechecker to do implicit coercions, if that tips the balance on which part of the design space you explore first that's cool. Typefully solving the 10bit case in general sounds like a little bit of work, but probably worth exploring - you can always just set up the equivalent of a typedef manually and build the operations in C in the meantime, right?

Speaking from a position of ignorance ...

I know next to nothing about PICs but, on general principle, silent type conversions are a bad thing. For instance, spot the bug in the following C:

float to_fahrenheit (const float centigrade) {
  return centigrade * (9/5) + 32;
}
If your language is based on the metaphor of making connections between ports on blocks, would it make sense to implement explicit type conversions with "adapter block" primitives? My reasoning is that, if we can have adapters that convert 240V AC to 120V AC, why not an adapter that converts an 8 bit port to a 32 bit port? E.g.:
(-> output_8_bits adapter_8_bit->32_bit input_32_bits)

Apologies if this is old hat, or if I have misunderstood the nature of the problem!

I had that in mind for

I had that in mind for coercions too, and probably should've said as much.

Yeah but...

... you still got first post. (Again.)

I'll go back to nurturing my Slashdot karma now.

Goid idea

You're right, that's probably best. Since there are multiple conversion methods that can be used, and to avoid nasty problems creeping in, I'll go with explicit converter blocks. They're not much extra work, as your second example shows.

Feeling the bits between one's toes

For bit-level programming, you might want to look at what they did in http://aggregate.org/SWAR/ . They extend a c-like language with the ability to specify the size (in bits) of elements, and then operate on them in an SIMD manner.

LtU really needs some sort of venue for sort of OT content...

Hardware Description Languages

The standard hardware description languages (HDLs) such as Verilog and VHDL are based on this blocks-ports-connections paradigm.

The notion of time in HDLs is interesting: when a block changes a signal value, how soon is it "seen" by the other blocks; how should one handle circular dependencies between signals, etc. You may want to look into the semantics of these languages to get a sense of problem domain. (These languages also have bit-vector types.)

As an aside, people have written DSLs for hardware description in other languages too -- SystemC (C++) and Lava (Haskell).

I'm doing something very similar

I'm working on something very similar, except the micrcontrollers I happen to be targetting are Atmel AVRs and ARMs and my syntax looks more Haskell-like.

So how are you handling feedback loops where an output to a unit might feed back as an input with a possible delay? And how would you define a digital filter that outputs a weighted average of the last N values to come from an ADC unit, say?

I also tried experimenting with using monads to structure I/O on a microcontroller but it was more of an exercise in perversity than something practical...

So how are you handling

So how are you handling feedback loops where an output to a unit might feed back as an input with a possible delay?

There are two types of inputs to my blocks: entry points and plain inputs. Entry points pass some data and trigger code to be run. Plain inputs are akin to passing parameters in global variables; you put some data somewhere, but nothing else happens.

Some blocks start events in motion in response to interrupts being triggered. The rest of the blocks just respond to data coming into their entry points. Circular links between entry points will make infinite loops, unfortunately. However, plain inputs can be used for feedback. Adding a delay could make it trickier, and require some funky hackery with timer-driven events. It could be done -- I have a vague image in my mind of what this would look like -- but it would be messy.

And how would you define a digital filter that outputs a weighted average of the last N values to come from an ADC unit, say?

Connect the ADC to a "weighted average of last N values" block, and have that block generate C code which would wait for N values to come in via its entry point, then start popping out weighted averages to wherever its output is connected. I can declare a static array of N unsigned ints for storage, and use all the dirty C tricks I know to make the digital filtering code efficient and readable.

This illustrates a key point in my design: it is, at heart, a C generator. It's wonderful not to have to worry about making the language parsimonious yet fully capable (and efficient on top of that). I just have to worry about making okay-looking C code from okay-looking input code. Which brings me neatly to your final paragraph:

I also tried experimenting with using monads to structure I/O on a microcontroller but it was more of an exercise in perversity than something practical...

It's sometimes hard to tell whether you're being perverse or practical with monads. Sometimes I think they're little more than mental excercise... but they really do have some good points, and people like Audrey Tang have pulled off amazing feats in languages with monadic modelling of stateful computations, and they seem to like it.

You've got what looks like a remarkably elegant language for microcontroller programming, but there is a price: it's heavy. Your simple light switch example compiles to about 48K of code, which is a lot more than I have to play with. And I'm a little leery of garbage collection on severely memory-limited machines, although both of these are bigger problems for me (with 16K of program memory and 1.5K of RAM) than for you (with a whopping 128K Flash ROM and 16K RAM).

Connect the ADC to a "weighted average of last N values" block

Does that mean you don't intend to be able to implement such a filter in your language and that instead you'd have to write the filter in C and use your dataflow language to glue the C to other bits of C?

The dataflow language I'm planning would support development of the filter itself, and I think it would produce compact and efficient results. I think I have a scheme for writing this in a functional way without the need for garbage collection - but I'll have to see how it pans out.

Faust

There already is such a thing. Actually, I think this language is LtU front page worthy.

FAUST is a compiled language for real-time audio signal processing

"The name FAUST stands for Functional AUdio STream. Its programming model combines two approaches : functional programming and block diagram composition. You can think of FAUST as a structured block diagram language with a textual syntax."

Several other papers on Faust can be found on this page.