Some time ago, there was a tv-ad about a syringe named “Pic indolor” (I guess, if not clear, it could be translated as “Pic painless”). Fast forward some decades, long is gone that ad – now Pic, to me, is only Pic painful, the regrettable MCU from Microchip that I am so unwillingly forced to use in my daily job. I already wrote about it, but there are still complains.
The current device I am working on sports quite a comprehensive set of complex features that are expected to run at once:
• proprietary field bus communication with failure detection and avoidance;
• distributed monitoring;
• Usb communication;
• Graphical User Interface, with status and menus;
• Audio (analogical, thanks god);
We have the top range Pic 18f, meaning 128k of program memory and about 3.5k of Ram (or, as Microchip engineers meant us to say, general purpose registers or shortly GPR).
Pic is renowned for its code density, or, to put it better is known for the lack of thereof. The Harvard architecture makes things even worse. In fact there isn’t such a thing as a generic pointer. The good ol’ void* is not. Pointers have to be differentiated in Program Memory pointers (24 bit wide) and GPR pointers (16 bits wide). But the difference does not end here: it goes down to the assembly level – different instructions and registers have to be used whether you want to access Program Memory or GPR. That means that the same algorithm, that can be coded in C with the help of some macro tricks, has to be translated in two copies or into a combinatorial explosion of copies if the pointers involved are more than one. A perfect example of this nightmare is in the standard library that comes with Microchip C Compiler (MCC18) – take a strcpy and you will find four versions since you may want to copy from ram to ram, ram to rom, rom to rom or rom to ram. That is annoying only up to the point where you run out of memory. At this point you can no longer afford the flexibility.
That was my case for image blitting functions – I left open the chance to copy images either from Gpr or Program Memory. Now that chance is gone.
The troubles do not end here.
Pic 18f architecture provides two levels of interrupts – high and low priority, since the name tells it all, I will not insult your wits by explaining it.
High level interrupt has some hardware facilities to save and restore non-interrupt context, but we can not even think of use it since it is strictly dedicated to the proprietary field bus driver. This ISR has some hard time constraints needing a run every bunch of microseconds, but this is another story.
So we use the low priority interrupt vector for anything else, most notably timers.
Since we are a gang of C programmers we try to stay away as much as possible from assembly. That’s fine, but writing interrupt code on Pic18 with MCC18, comes with a hefty price.
On entering the interrupt the C language run time support dumps 63 bytes of context into the stack. Given that, if you don’t want to incur in extra penalties, the stack size is 256 bytes, CRT eats up one quarter of the stack.
To be fair it is not just MCC18 fault, it is more how the PIC architecture has been designed – rather than having real CPU registers and operations that work on those registers, PIC has memory mapped hardware registers that both implements CPU registers and addressing modes.
For example, when saving the context you have to save:
• FSR0, FSR2 indirect addressing into RAM registers;
• TBLPTR indirect addressing into Program Memory;
• TABLAT indirect addressing into Program Memory read value,
• PRODH, PRODL multiplication operands.
By comparison you save the whole Z80 context in 20 bytes, but if you restrict code to not use the alternate register set, then you just save 12 bytes moreover you don’t have dumb limitation on the stack size.
Well enough with digression. What is in my ISR?
There is an interrupt source detection routine that calls the specific ISR for the occurred interrupt. If the specific ISR is a timer tick, the timer list is swept and triggered timers are notified by using a deferred procedure call.
That’s another piece of code I am proud of having written. Basically rather than performing callbacks from within the interrupt callbacks you just register your callback with your defined arguments and let a handler to perform the call later from non interrupt context.
This has the main advantage that the callback code can take its time to do what it is supposed to do since interrupts are enabled. Also the called back code could mostly ignore interrupt re-entrance problems since it is always called synchronously with the non-interrupt main loop.
This proved to be also a life saver in this occasion so that stack doesn’t grow too much in interrupt adding to those 63 bytes. To ease the impact I moved all the timer code from interrupt to non-interrupt via deferred procedure call.
This bought me some oxygen, but still the application was suffocating in limited amount of stack.
I turned my attention to display driver and its functions. Their weight on the stack was considerable and basically they don’t need to be re-entrant. Consider masked blit aside from offset registers, coordinates, read and write value you have one pointer to data, one pointer to mask and, in my case two pointers to the previous data and mask line, that’s quite a lot of stuff living in stack space.
With a deep sense of sadness I moved all those auto variables into the static universe and almost by magic I solved both the stack problem and freed enough Program Memory to fit in the memory space even without optimization.
The emergency alert just went off, but I am not sleeping relaxed sleeps because I know that it is just a question of time, soon or later, well before the end of the project, the problem will rise its ugly head again.
The cost impact on the project is unlikely to be light…