Why do we do it? Instead of writing an elegant and practical state machine we choose to just loop around a unsuspecting register until:
1. It changes value.
2. Processor gets bored and resets.
3. God becomes angry and sends us or the board some lightning to snap out of it.
Ok, so number 3 is probably not going to happen and number 2 will also neeeever happen because the hardware design is everything-proof (notice the sarcasm depicted by the extra three ‘e’ in never).
I still think people should be punished for doing blocking code, but there are some reasons to do it:
1. Quick tests that will never ever end up in a product (highly unlikely if your boss ever sees that code working).
2. Time sensitive tasks that actually could benefit from that extra milli, micro or nano second (depending on the processor, bus clock, etc.)
3. When you cannot absolutely leave a function until something has happened. There are few examples of this, and even fewer I can think of (2): when waiting for a clock source to stabilize to make sure the rest of the code will run predictably or when writing a variable in flash memory while running from RAM (because you cannot run in the same flash bank you are writing).
Why should this blocking code be avoided? Mainly because it’s ugly, but because we are trying to be all engineery and scientificky and what not, I’ll give you a couple of good reasons (did’ya already notice how I like lists?):
1. Processor could be doing other stuff instead of waiting.
2. Processor could be doing other (life saving) stuff instead of waiting.
The difference is not subtle. The first case only means that processor resources are not being used to their full potential, so instead of waiting, the processor could be using that time to run a second task. The second case means that there could be a task, that is there for a safety reason (for example: turning off a motor when it becomes overheated) and it absolutely needs to run periodically to avoid any catastrophe.
The solution is dividing big tasks into small tasks that get called periodically and in a certain order and priority, the periodicity with which these are called depends on the importance of the task. Now an important note, some people will tell you I’m talking about some sort of operating system. I am and I am not. I am, because this is what operating systems basically do, and I am not because I’m talking about small systems that may or may not use an operating system. Yes, I know the world is full of all these really nice, muscular and tough 32-bit processors that have enough resources to run gigantic OS or RTOS, but it also is full of small 8-bit, 16-bit and even lower cost 32-bit processors (have you seen Freescale’s Flexis Microcontrollers family?) that don’t need or will not have any advantage from an operating system. These processors are, however, doing critical tasks and would also benefit from a non-blocking architecture.
So it’s time to put my ideas to work, how could I implement this idea? We will use master IIC driver as our example as it nicely fits the state machine concept as there are several steps we need to take to do IIC communications:
- Prepare the start of the transmission with slave address, initialize the IIC hardware and send the start signal.
- Check if the start signal was transmitted correctly (one of the loops we want to avoid).
- Send next byte.
- Check if this was the last byte.
- Repeat if it wasn’t the last byte to transmit (another place where we want to avoid a loop).
So I’ve seen a bunch of IIC driving code and it usually ends up being something as I described earlier and in a single function (this is my own version of this idea in pseudo-C * code):
void UglyIICWrite(*PointerToData, SlaveAddress, DataSize)
SlaveAddressRegister = SlaveAddres;
IICStartSignal = 1;
while(StartSignalNotSent); //Ugly loop here
IICDataRegister = *PointerToData;
while(!IICDataSent); //Another nasty loop
}while(DataSize–); //Yet another horrible loop
I probably even skipped some steps, still it reflects the idea, so much time lost inside a function. So what to do? Start with a set of functions:
Each of those functions has a prototype and related actions, but none of those functions has nasty loops inside, instead, they just do something (move a pointer, prepare some data, set a register) and then check if it’s time to go to the next function. If it is time to go to the next function then they change some state variable that another process evaluates periodically. This process, put in simple terms, is a function in the application main loop that evaluates the state variables and calls the appropriate function. A very simple implementation is a switch-case statement, it makes sense for simple state machines but more complicated state machines could probably end up generating too much overhead code with a switch-case statement, it always is a matter of knowing the processor architecture and the compiler to decide what to implement here. Another option is using pointers to functions with an array such as the following:
void (*const vfnapIICV1Driver)(void) =
The main loop will have something like this:
//…the rest of the application…
Then each of the functions determines which the next state is and assigns a new value to gu8IICV1ActualState (according to the position of that function on the array) or doesn’t change the value at all if that state or task still doesn’t need to change. For extra-fancy points, you could add some variables for previous and future states so that past states could control future states and stuff like that (which is not the scope of this scope).
That was a long post so I’ll summarize (in my own words):
- Don’t do blocking code because it is nasty, it will waste valuable resources in your processor and your friends will be disappointed if you do.
- When you approach writing a piece of code, determine what this code has to do, do some diagrams (UML or SysML are nice options to model your code), and think of how to divide into small tasks whatever the code has to do. A state diagram is an excellent way of representing this.
- There are several ways to implement a state machine, the most obvious approach is not always best, if you are writing all the application with no OS to help, chances are you are using a small microcontroller with limited resources, so it can help to actually dig into the processor architecture and do some tests with your compiler to find out how a switch-case statement, or a pointer to function will be solved, some compilers may even easily allow you to determine how an expression will be solved.
- Keep visiting my blog for embedded stuff reading and fun.