Embedded Programming Tips

| ⌛ 6 minutes read

📋 Tags:


Overall Guidelines

The overall guideline is very similar to your lab report. Prioritize being explicit and clear. As you do more team coding projects like Orbital or CS2113, you will experience firsthand why this matters.

Apart from the specific tips I will showcase later, your standard good coding practices apply.

Make sure your code is well-factored and commented so that your peers (and yourself) don’t forget what your code is supposed to do.

Document your code, please

Remember: Software engineering is not just getting things to work but is also an exercise in communication.

If you need a code review, please feel free to ping me on telegram for a consult.

The following tips are tailored for your CG1111A project to hopefully make the development experience less painful.

If you can apply some of these concepts in your project, I think you will find that they will pay dividends down the road (especially when you get caught in debugging/testing hell).

CG1111A Coding Tips

1. Use Structs to handle complex data groupings

Structs are powerful. They unlock all sorts of data structures, and can help make your code more robust and readable.

How?

Let’s say I’m writing code to control a drone’s roll, pitch and yaw.

We will need to store the roll, pitch and yaw as config.

The simplest way is to use an integer array.

0
1
2
3
4
5
6
// Which is roll, pitch and yaw? 
int drone_params[] = {42, 100, 254}; // dummy values

drone_params[0] = read_input_pitch();
drone_params[1] = read_input_roll();
drone_params[2] = read_input_yaw();

But, when we use drone_params, it’s not immediately clear what 0,1,2 mean in this context.

We can infer that drone_params[0] == pitch and so on. But is it really? Only the person who wrote the line of code knows.

Other people who read it will be confused and won’t really be able to work on the project. We also run the risk of index errors if when we accidentally use 1-based indexing (e.g. drone_params[3])1.

What if we used a struct instead?

0
1
2
3
4
5
6
7
8
9
struct FlightParams {
    int roll;
    int pitch;
    int yaw;
};

struct FlightParams drone_params;
drone_params.pitch = read_input_pitch();
drone_params.roll = read_input_roll();
drone_params.yaw = read_input_yaw();

Much better. This type of readability is especially good if you are inheriting someone else’s code. Instead of magical values like 0,1,2, anyone that reads it will know exactly what’s going on.

Accessing data using descriptive names with structs helps to reduce human error and helps you develop faster. No need to scroll up and down to figure out what 0,1,2 refers to.

When to use structs?

When you have related pieces of data tightly coupled to each other and their state needs to be changed.

2. Pass by reference

You may find that your program might run a tad bit slower2 if you are passing structs or classes. This is because your program in Arduino (C++ variant) passes function arguments by value.

This means that passing a struct or class as an argument will COPY the data structure. You should be aware of this in CS1010.

Copying can be slow3. C++ (which Arduino uses) has a feature called “Pass by reference” (no more pointers!). Do this by appending ‘&’ before the argument name. You can think of pass by reference as a ‘pseudo pointer’, where the actual variable is passed into the function rather than copying it. This saves you time and is useful when you need to actually modify the data. You will find this concept particularly useful in CS2040C.

Any modifications made to the referenced variable in the function will persist once the function call ends.

Reference documentation for pass by reference.

 0
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
struct Foo {
    int bar;
};

void func(Foo& foo){
    // process foo and change its state
    foo.bar = 1;
} 

int main(){
    Foo foo;
    foo.bar = 0;
    printf("%d \n", foo.bar); // prints 0
    func(foo);
    printf("%d \n", foo.bar); // prints 1
}

3. Avoid magic literals

Magic literals are values that seemingly come out of thin air.

There is no obvious meaning to its value.

You will learn this more in-depth later on in CS2113. But here’s a simple example.

Let’s say we need to calculate the resistance of a copper wire.

0
1
2
3
4
5
6
7
double calc_wire_resistance(float length, float area){
    // What does 1.77e-8 represent?
    return (1.77e-8) * length/area;
}

double calc_conductivity(){
    return 1/(1.77e-8);
}

In this case, $1.77*10^{-8}$ is the resistivity of copper. Not obvious at all.

What if we have many functions that need to use the resistivity of copper?
What we decide to change the wire to aluminium?
How will this affect all the functions that use the literal?

It quickly becomes obvious that magic literals are…

  1. Not that readable, especially for people who didn’t write that line of code
  2. Do not scale well, especially if frequently used

Let’s refactor the code with a named constant (you can opt for a #define macro too).

0
1
2
3
4
5
6
7
8
const double RESISTIVITY = 1.77*(10e-8);

double calc_wire_resistance(float length, float area){
    return RESISTIVITY*length/area;
}

double calc_conductivity() {
    return 1/RESISTIVITY;
}

Much better. Need to change to aluminium? No issue, just edit the constant RESISTIVITY. Re-use is much easier compared to re-typing the literal…

We also avoid possible typos4 as well. We remove more possible points of failure by using constants and reducing magic literals. Debugging would be much faster as a result! (This saves you time and headaches…)

For your mBot project, think about what ‘magic’ values you may need to tinker around with often.

These will often be certain “configuration” variables like angles, speeds, etc…

4. Enums help group constants

If you have tons of constants, it might get messy.

You can consider using enums to group together constants that make sense semantically.

Learn more about C++ enums.

 0
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
enum COLORS {
    COLOR_NONE = 0,
    COLOR_RED  = 1,
    COLOR_GREEN = 2,
    COLOR_BLUE = 3,
    COLOR_PURPLE = 4,
    COLOR_ORANGE = 5,
    COLOR_WHITE = 6,
};

// Sample usage
bool isWhite(enum COLORS color){
    return (color == COLOR_WHITE);
}

5. Avoid Global Variables

Global variables are different from global namespace constants or macros. The key difference is that they vary, meaning that their state can change (mutable).

You learnt from CS1010 (probably) and other sources that global variables are evil. This is the same for CG1111A. Avoid using global, mutable variables.

Use global variables if you want to spend more time debugging

If you choose to use them, you may encounter buggy behaviour with accidental changing of the global variable’s state even though the project scope is small. Avoid shooting yourself in the foot!


  1. This tends to happen. Quite some time can be wasted debugging index issues for CG1111A. ↩︎

  2. This should not be that observable, assuming your struct is small. ↩︎

  3. If you pass a C++ array vector by value, then you incur a time cost complexity of $O(n)$ where n is the number of elements in the array. ↩︎

  4. If I had a dollar every time I had a typo error, I could drop out and gamble in crypto. Ha! ↩︎