Embedded Wednesdays: Functions
We need to build a bridge, a strong bridge that will last a long time, it has to be built correctly and cost nothing to maintain. We go out to the iron ore mine, dig up a bunch of ore, build a fire, build some bellows to force air into the fire, add the ore, and out pops… wait this isn’t going to work very well, is it? You need something bigger than iron ore, you need beams and arches.
And yet, in embedded systems, that is what can happen when you start a new project. You have your compiler, and a text editor with a blank page and a cursor blinking at you. You need to write all of the code to start your processor, get all of the sub modules started, then you can start using the modules, building larger and larger structures until you finally get usefully large pieces that you can use easily.
Much like girders and plates are made of refined iron ore, we use larger structures called functions and libraries that are made of refined code.
Functions
Also known as subprograms, subroutines, procedures, and methods, in C these are all just known as functions. In their simplest form, functions take a bunch of lines of code and give them a name. In the rest of your program you execute that bunch of code just by referring to it by name. This is known as a function call.
Using functions to represent a block of code doesn’t make your program faster, actually it is often very slightly slower by two or three instructions, but the maintainability, understandability, and debuggability are vastly improved.
In our first example, the syntax is going to look a bit weird, I will use the void keyword before I tell you what it means, and it will look like I’ve written a broken version of the function (yay for C syntax).
This example is intentionally useless; we initialize a random number generator, then generate random numbers. When we get the value 42, we print out a congratulatory message.
#include <stdio.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
int main( void) {
int32_t randomNumber;
uint32_t nowTime;
nowTime = HAL_GetTick(); // Get the current time
srand( nowTime); // Initialize the random number generator
while (true) {
randomNumber = rand();
if ( randomNumber == 42) {
printf("I found the answer!\n");
}
}
}
Note that the above code uses four functions. In fact, we've been using printf for a long time, it is a function (and one of the more complicated ones!). But now we're going to write functions, not only use them.
So, let's rewrite this using two functions:
#include <stdio.h>
#include <stdlib.h>
#include <stdio.h>
void Initialize( void);
void Find42( void);
int main( void) {
Initialize();
while (true) {
Find42();
}
}
void Initialize( void) {
uint32_t nowTime;
nowTime = HAL_GetTick();
srand( nowTime);
}
void Find42( void) {
int32_t randomNumber;
randomNumber = rand();
if ( randomNumber == 42) {
printf("I found the answer!\n");
}
}
Main consists of two function calls, Initialize and an infinite loop calling Find42. Following the main routine are the function bodies.
If you have used Arduinos, you’ve seen this before. Arduino uses two routines, setup() and loop(). The Arduino stuff behind the scenes takes care of calling setup, then repeatedly calling loop.
Getting Started
A perfectly valid way of starting to write your program is to begin with a vague notion of how the system will flow and put in fake calls to functions that haven’t been written yet, just to get the basic structure of the system straight in your mind.
Here is some code that will initialize stuff, then go into a FOREVER loop, grab data, process it, then make your system do something in reaction to the data. Like so:
int main( void) {
Initialize();
FOREVER {
SensorGetData();
ProcessData();
MotorMove();
}
}
You don’t need to know what Initialize() does, just get the basic flow approximately right, then you just need to fill in the blanks. Think of the system as a bunch of interacting chunks, each with the responsibility to do what they are told, at this point don’t worry about the “how”. This would be like designing a bridge and using really big pieces, decks, trusses, abutments, but you don’t care how they get there, we’ll fill that in later.
Now if there is a problem with the motor movement, there is no need to sift through the sensor code, you can look in the MotorMove function. You have a nice separation of responsibility. Debugging becomes easier.
This is the concept of information hiding. We play a bit of a game when programming, we want something to happen but we don’t care how it happens. This gives us the ability to change how a function works without disturbing the rest of the system.
That’s it for this week. Next week I’ll show you how to pass information in and out of functions, actually tell you what void is all about, and discuss prototypes, headers, and good habits for functions.
This post is part of a series. Please see the other posts here.