A super loop (a.k.a. mega loop, main loop or infinite loop) is a popular way to architect firmware code for simple firmware applications. It involves looping through your code which loosely follows the following three steps:
- Read inputs.
- Process inputs/data.
- Set outputs.
A super loop does not run on “operating system” (e.g. RTOS), and therefore there is no scheduling or asynchronous code execution, everything is just run in a simple synchronous loop. The idea is to keep the average loop time short (in the microseconds to 10’s of milliseconds range) so that the system remains responsive to changes in inputs.
The advantages of a super loop:
- Simple to understand.
- Don’t have to worry about thread safety (memory contention, access to resources, dead-locks e.t.c).
The disadvantages to a super loop include:
- Not as modular as an application running on an OS, and sometimes you run into complexity issues when using a super loop for large firmware projects.
- No ability to prioritize certain tasks (except for using interrupts). This can lead to responsivity issues for large or complex projects.
The basic idea of a super loop could still be used for a single thread running on an RTOS, so it that sense, it could run on an operating system.
Basic Top-Level Code
For larger firmware projects using the super loop architecture, you will want to break the code into modular .c/.h file combinations which have a specific purpose, for example one module for handling an IMU sensor, one module for controlling a motor and one module for printing serial debug data:
The System Tick
The idea is to call a function to get the system tick (which represents the current time, usually measured as an integer number of microseconds or milliseconds since the microcontroller started) once per iteration of your super loop.
It is important to note that you should only read the system tick once per iteration of the loop. This value should be used for all calculations involving the current time for the rest of the loop, even if the current time has changed. This prevents a whole range of tricky-to-debug issues when things start happening in a different order than what was expected.
Dealing With Tick Roll-over (Overflow)
The great news is, if you are using an unsigned integer data type to store this number, thanks to the maths, much of the code you write that is based on the difference between the current time and the last time an event happened will automatically run correctly even when the tick overflows!
Let’s pretend we need to print something every 100ms, and we’re using the ridiculously small storage of a
uint8_t (to exaggerate the problem) to store the current time and the last time we printed something. Our code to detect when we need to print is:
cur_tick_ms start at
cur_tick_ms starts increasing everytime when iterate around our super loop. When
cur_time_ms gets to
if statement becomes true and we print.
last_print_time_ms is updated to
100. The same thing happens when
cur_tick_ms gets to
200. However, since
cur_tick_ms is a
uint8_t, it overflows after
255 and wraps back around to
0. What’s great is the math still works out. Because the difference
cur_tick_ms - last_print_time_ms is also a
uint8_t, The duration
0 - 200 is not
cur_tick_ms continues to increment until is reaches
44, in where
44-200 gives us
100, and we print again! And the cycle continues.
Full working example showing this phenomenon is below. Run it online at https://replit.com/@gbmhunter/sys-tick-overflow.
Running the above code, we get the following output:
Keeping Track Of Loop Time
A really useful metric to keep track of in a super loop architecture is the loop time. The loop time is the time it takes to perform one complete iteration of the super loop. The loop time will always vary (technically, this is called jitter), due to different things occurring on every loop. The shortest loop times will be when no new events occur, the longest will be when many events occur on the same loop which trigger long-running actions. It is generally a good idea to keep the maximum loop time short, typically in the microsecond to 10’s of milliseconds range, such that the embedded system remains responsive to the outside world and doesn’t appear to “hang”.
It’s easy to compute the loop time from the “tick” from the last iteration of the loop, and the “tick” from the current iteration. Check that this does not exceed a reasonable number (reasonable will entirely depend on your exact application, printing out the loop time might clue you into a suitable limit), and handle the “error” as appropriate (in the example below, a warning is printed to the default debug port).
E. Alvarez (2015, May 1). To keep a Boeing Dreamliner flying, reboot once every 248 days. Engadget. Retrieved 2021-12-06, from https://www.engadget.com/2015-05-01-boeing-787-dreamliner-software-bug.html. ↩︎
This work is licensed under a Creative Commons Attribution 4.0 International License .