It is great to be an embedded developer. We are drowning in riches. The job market is good despite the economy (knock wood), and the world we live in is rich with new parts and fun things to do with them.
My recent work is a departure from the multicore and bigger processors I worked with in my past. I have to go way back over a decade since I last worked on an 8051 or 6805. (If you don’t know what those numbers mean, don’t worry about it. ) Recently I have been working with smaller Atmel AVR microcontrollers.
Once again the great myth of small processors has run over me like a truck. Many coders believe the myth. An because they believe it, it continues, sort of like an evil Moores Law.
A good design won’t fit on a small processor. It is wrong, a myth, and it needs to be busted.
Small processors have less memory, less code space and more limitations. That is not an excuse. Actually, it is an excuse, for poor coding and bad design.
So, what gets broken and how do you fix it?
First, fix the state. All programs have states. The person writing the code may think they don’t have state, then the state gets hidden. Much better to put it out into the open where it can be seen. If there is a flag that gets set, that is state. Someplace in the code has a do this before you do that, but don’t do something before the first two, that is state. In a small system these things become globals and flags. That is wrong. Don’t add a flag, build a state machine.
There can be only one. It is good for states too. Flags are considered harmful. Bit wise flags in a global are considered evil! A set of bits in a global variable do not represent states. Every possible combination of flags in the set of bits represents the states. Are you ready to test every one of them?
I recently looked at some code that used flags. All through the code were statements like (if this flag do x). For a tiny microcontroller this causes code bloat and uses up the available memory fast. Much better to build the simple switch state machine with one variable. Don’t set a flag, add a state. Too many states? Not really.
Say the code requires the security message before the 25 other messages. OK, so we add the SECURITY_MSG_RX flag that gets set when it is received and validated. Now every other message handler has to check if(SECURITY_MSG_RX == (flags & SECURITY_MSG_RX)) (yes I do write my bit wise tests like that). No, you say, put the check in the receive handler, not every message handler. Except the security message has to get past the checking. One exception, not a problem. No add two more flags, rinse and repeat. You quickly have a mess.
The better solution is to make a state variable. Call it recvState for this paragraph. Now, set the state to RECV_STATE_WAIT_FOR_SECURITY. That one state checks for the security message and dumps anything else. Don’t move to the next state until secure. All states after can assume the security is valid, no need to keep rechecking.
A nice way to handle the state changes in add a setNewState(_stateType) call. You did make your states a typedef, right? Grep for all calls to the module private (static) call to setNewState() and every single state transition is visible. Add a last state variable and a log message that reads “Changing state from STATE_ONE to STATE_TWO” and life is good.
Another piece of code I could not understand had a buf[BUFF_LEN * 2]. Then all the code checked on BUFF_LEN. Why? If I had to guess, one of the checks was wrong, or misplaced, so sometimes the buffer over flowed. This is a bad fix for bad code that hides problems. Please make the code correct, and then there is no reason to waste space.
One last strategy that helps save more space but I never ever see. Write programs that handle tables. This is not my idea. It comes from Code Complete by Steve McConnell.
Every possible place, build a table. Write code to go through the table, then handle only the parts that are different. This is the object oriented concept of programming only the difference. Once the table handling and common case code is working and tested, adding more lines to the table is easy. For an embedded system declare the tables to be const, static const if possible. The const key word should (compilers vary so take care) keep the table in the program space, not in RAM. Don’t put variables in the table. The resulting code will be much smaller, easier to test and expandable.
This works great for networking. Tables of messages are easy to implement and save code space over a switch on the message ID. Extra points to anyone who comments with the way to find the table size as a constant at build time, in C.
If there are worries about traversing the list, put the list in order and use a simple binary search. For 10 items it is not worth the effort, but every little bit helps.
Here are just a couple of tips that can make life with a small system happy and worth while. Please comment if you have others.