Saturday, June 28, 2014

Hacking microcontroller assembler macros to provide C like conditional blocks

I started off with microcontrollers about 10 years ago using Microchip PICs. While they haven't kept pace with some of the other stuff out there and the limited development tools has relegated them to obscurity compared to the Atmel powered Arduino, I still have a place in my heart for these old favorites.

PICs are dirt cheap, and have very good internal oscillators, which means a lot less fussing around to get something working. You can get away with just the chip and a capacitor, and you can pick up chips for under a buck. They also have oodles of variations and a wide selection of peripherals that compensate for the less powerful core.

The new MPLAB X and free-tier XC8 compiler provides a usable but suboptimal development environment. The free tier compiler includes anti-optimizations that needlessly bloat your code. Some times you need to really eek out a high performance loop or control the timing of something very precisely, and the inline assembly options here just suck. For example, consider this limitation as seen in the XC8 Compiler User Guide:
The #asm and #endasm directives are used to start and end a block of assembly instructions which are to be embedded into the assembly output of the code generator. The #asm block is not syntactically part of the C program, and thus it does not obey normal C flow-of-control rules. This means that you should not use this form of in-line assembly inside or near C constructs like if(), while() , and for() statements. 

Prior to MPLAB X, I used SourceBoost, which was a very basic IDE with limited features glued on to an amazing compiler. Unfortunately active development on this seems to have stopped, and only works on Windows.

SourceBoost supported C++ style templates for their C compiler, another feature I found missing when I switched to XC8. It was slightly better than the MPLAB assembler macros as it would create a reusable function tailored depending on the template args vs assembler macros that just insert the generated code inline, saving flash. In addition, it was possible to insert some assembly mostly pain-free if you needed to.

Prior to SourceBoost, I coded in assembly in the old MPLAB IDE. I had quite a bit of fun eeking out every byte of ram and flash on a PIC12F629. To make thinks easier, I took good advantage of the call stack to create functions, and the assembler macro capability to create inline functions. The dynamic code generating capabilities of the macro language they provide is more elegant and powerful than a mess of #IF directives in C.

My latest project finds me back to where I started - pure assembly. 90% of my project involves hand stitched code that is doing 4 things at once, while doing a very timing specific bit-banging with precision +- 150ns at a ~1.25us interval. At 12 million instructions per second giving 83.33ns between instructions, that doesn't really leave a lot of wiggle room, nothing close to what would be needed to implement this using a timer and interrupt service routine. With all of the headaches of gluing assembly into C code, it made sense to keep the whole thing in assembly.

The one thing I notice coding in assembly more than anything else is the lack of if-else style blocks. I don't mind adding a label, increment/decrement, and test/goto for a loop, but creating dozens of silly labels for conditionals somehow short circuits the part of my brain responsible for variable naming.

Taking an idea from Karl Lunt's PIC macro library, which implemented a for-next, repeat-until, and switch-case, I created a set of nestable if-else-endif macros. Combined with the fact that the assembler is indentation agnostic after the first column (labels), this makes it really pretty easy to create a regular C like conditional block.

This allows you to take a goto-poorly-named-label style of coding like this:

  btfss flags, hasData ;bit test, skip next if set
  goto noData
  call readFromBuffer
  movfw tmp
  call putch
  goto endDataCheck
noData:
  ;all done!
  blink LED1, 1, 1000
endDataCheck:

Into this:
  ifbtf flags, hasData ;if bit test
     call readFromBuffer
     movfw tmp
     call putch
  ifelse
     ;all done!
     blink LED1, 1, 1000
  ifend

You can download my library here: bensmacros.asm.

No comments:

Post a Comment