views:

255

answers:

6

I was wondering if any developers in the embedded space know of any interesting tricks to help lessen the pain of developing for microcontrollers with very limited stack space. I've recently been writing some firmware for 8-bit uCs (Microchip PIC18F family, 31 byte stack) and as a consequence I have had to flatten my programs and reduce the number of parameters passed to functions. I've also tried to minimize my dependence on larger local variables. The flattening was designed to put fewer things on the stack and reducing locals variables helps conserve space in the "automatic variable" program section (psect) in RAM. Harvard architecture is not fun, I know, but it is what I'm dealing with. I've noticed issues with calling more than a few functions deep from an ISR, which is probably the result of my stack window being affected by IRQ context saving. I know I'm working with a limiting architecture, but I wonder if anyone has any tips for reducing headaches. I use pointers and bounds checking whenever possible, but I'm sure there are nuggets of wisdom I haven't discovered myself. As a disclaimer, I'm currently using function pointers to facilitate a state machine. I feel like I'm walking a tightrope between 90 line void functions and code that actually uses functions as they are intended.

+5  A: 

Use register variables for parameters and locals. Of course, depending on the number of registers available in the processor and the quality of the code the compiler generates, this may be no benefit at all. Declare locals as static where possible. This will keep them from being allocated on the stack.

BillP3rd
And register-based calling conventions by default rather than cdecl/stdcall.
Matthew Iselin
It's unlikely that an 8-bit microcontroller really has multiple registers to use for storing variables or passing parameters.
Gabe
Excellent point, the compiler manual states that all static variables (local or modular) will be given a defined memory space in RAM that is not of the automatic type. I just have to be conscious that I have the responsibility of clearing certain variables upon function entry (loop counters, strlen, etc.) Thanks for your response.
Nate
@Gabe - I'm not an assembly guru or electrical engineer, but most 8-bit processors have registers local to the instruction cache that do hold values waiting to be fed into the W register. It may not be fast or elegant, but I am using 25 year old technology. I'm only 29, so I'm trying to catch up :)
Nate
I'm being a bit pedantic, but in order for 8-bit uCs to offer context saving prior to an IRQ, they most certainly have the ability to context save variables in RAM. I was just asking how to make my footprint smaller.
Nate
Nate: The PIC architecture has just a single 8-bit general-purpose register (W). The first 96 bytes of data memory are available without bank switching, so those may be considered registers, but they are not implemented as such in the ALU. Since they are just RAM, they're more like global variables. When an interrupt occurs, it saves the W, BSR, and STATUS registers into dedicated holding registers (not RAM).
Gabe
Point taken, thanks for clearing that up. I would probably know my chip better if I was forced to learn assembly first.
Nate
@Nate: I definitely recommend becoming familiar with the instruction set on such a platform, it will help you write better C and avoid some gotchas that you might encounter if you treat it like a generic CPU.
Matt Curtis
+1  A: 
  1. Use a whole-program optimizing compiler that can effectively statically allocate the "stack" -- I believe the Hi-Tech PICC compilers can do this. See section 5.9 in the PICC18 manual

  2. Use protothreads

Doug Currie
Yes, in most cases the compiler is able to make up for coding inefficiencies by implementing look-up tables. I admit, in most cases, the compiler has been able to save me. In some cases, I've managed to have problems. If I thought my method was good enough I wouldn't be asking for the help of my peers ;) I really don't mean to be sarcastic, I don't know much, trying to learn more.
Nate
+1  A: 

Use non-local goto (setjmp() and longjmp(), with the jmp_buf not stored on the stack) to avoid function calls.

Matt Curtis
Just to clarify, I should use jump tables and statically allocate variables within a module? What you're suggesting makes sense, I'm just asking to make sure that I understand it.
Nate
@Nate: you know how `goto` works, but it only works within a function? `setjmp()` and `longjmp()` let you jump between functions. `setjmp()` "saves" the jump point into a `jmp_buf`, and then you can "goto" there later with `longjmp()`. You should store this `jmp_buf` globally, not on the stack. Forget what everyone says about goto, because (a) you have 31 bytes of stack so you should avoid consuming it wherever you can, (b) goto is a useful tool when you're writing a state machine.
Matt Curtis
+2  A: 

For non-recursive functions, you can make your local variables static.

BTW, the term "Harvard architecture" simply refers to the fact that there are separate address spaces for instructions and data, as opposed to a "Von Neumann architecture" that loads instructions from the same address space that data comes from. This is irrelevant to your problem, as all it means is that you can't write self-modifying code.

Gabe
-1 for not understanding the problem and being counter productive
dwelch
dwelch: What's wrong with you? You suggested the same thing as I did, only 40 minutes later and with further suggestions. If I don't understand the question and am being counter-productive, why did you ever bother to respond in the same way?
Gabe
dwelch: I didn't tell him to use recursion! All I did was give the sound advice that using static local variables will only work for non-recursive functions.
Gabe
on this platforms functions are non-recursive not enough room for recursion
dwelch
+1, this is a useful answer (the first paragraph) and a useful clarification to the OP's question (the second paragraph), can't see anything counterproductive here.
Matt Curtis
+1  A: 

EDIT

The PIC family is not a compiler friendly instruction set architecture. The first trick for dealing with the small stack and in general limited resources on a PIC is to program in assembler. You can perform more tasks in the same program space and same execution time or same task in less time than programming in C.

The second is just dont use the stack. Make your variables globals or local globals (local variables with the word static in front), whatever you call it or however you declare it the compiler output is the same the variable has one static memory location and does not use the stack.

For processors other than the PIC family you can use an optimizer or register declaration of variables to prevent use of the stack. The PIC has only two registers and to do anything useful you have to constantly evict the contents to ram, so this will have a minimal effect on the PIC. For non-PIC processors, in addition to encouraging the compiler to retain the data in registers as much as possible, you can aid in that by limiting the number of variables passed into the function as well as the number of additional local variables used by the function. For cases where it is not obvious how many registers are used, disassemble and examine the compiler output, re-arranging your code execution can change the register allocation and eviction. If that does not work consider splitting the function into two and calling one then the other in turn, such that each function is able to execute within the available registers without using the stack.

If this project is for a fun educational experience then programming for the PIC is educational in and of itself, and programming the PIC in C is also an educational experience. Well worth learning, well worth falling into traps and fighting your way back out.

If you are doing this for work (career and/or livelihood hangs in the balance) and do not want to program in assembler, I recommend trying to switch the project to another platform. msp430, avr, or one of the ARM based ones (stellaris perhaps). Knowing ARM is very beneficial to your career in embedded, but the ARM parts are going to be a bit bulkier from a power and cost perspective compared to a PIC. The avr and msp430 parts are more on par. All three of these alternate architectures are much more suited to C programming and the same program will consume less memory/stack than the equivalent on the PIC. Meaning you may be able to replace say an 8K PIC with an 8K something else, you wont necessarily need more memory on the alternate architecture in order to do more. You still have to worry about your stack, and should avoid (non-global) local variables to prevent stack growth. These architectures also vary from the PIC by not having a separate stack and general purpose ram. You are free to balance globally allocated variables and stack based variables and not forced by the architecture. If stack based variables are used, in a work environment, review the code to determine the worst case path of nested functions and count how many non-global variables are in that path and if that causes the stack to collide with the globally allocated variables.

Disassembly is your best friend in embedded, you dont necessarily have to program in assembler but should still be able to read it. Examining the assembly is the best trick of all for reducing all forms of embedded pain: stack problems, memory usage, performance, bootup problems, etc.

dwelch
-2 for advocating global variables, then turning a question about minimizing stack usage to a diatribe on the architecture he's using. Global variables should only be used when the same value needs to be used in different functions, otherwise programs are hard to reason about because you don't know where a variable might be modified. Local variables also allow you to use the same name in multiple places without stepping on another function's values.
Gabe
I've added +1 vote for this answer, because it actually is reasonable for the microcontroller at hand. The baroque 8-bit PIC is particularly unfriendly to C programming (it was created before C gained any acceptance in embedded.) Anybody can bash global (or perhaps static) variables, but there are no other alternatives for the PIC. The existing C compilers for the PICmicro are actually also using static variables behind the scenes where normally they should use automatic (stack based) variables. For these and other reasons, C compilers for the PIC are not strictly speaking standard compliant.
Miro
A: 

I was once programming for 8051 and we were using the Keil C compiler for doing that. This compiler didn't call functions by using the stack but by transferring parameters using global functions (you could still get this behavior by marking the function "reentrant"). I think it also did reduce stack usage by doing some more stuff.

What I want to say is that there are compilers that will use less stack space than others. If there are such compilers available for PIC, I can't tell.

ziggystar