Secret Code

Efficient Arduino Programming

Liam Davies

Issue 68, March 2023

Make your Arduino projects better than ever by learning some awesome programming tips and tricks. We’ll show you new approaches to writing and testing your own code!

No matter how good your 3D printing and soldering skills are, at the end of the day, you’ll need to sit in front of your computer and infuse some magic 0’s and 1’s into your Arduino board! Arduino code is well-known for being easy to learn and use, however, there are many tricks that can be done to streamline performance, improve the readability of your code and reduce bugs.

In this edition of Secret Code, we’re diving right into making your code professional, speedy, and easy to understand. We’re assuming you have some Arduino knowledge under your belt, but we’ve still made everything as simple as possible to follow along with.

Readability

To many beginners, code readability may seem like a purely aesthetic pursuit. Similar to organising your workshop desk, it has absolutely no functional effect on what you make. So, why worry about it?

It’s an easy enough assumption to make, especially when you expect you are the only programmer that will work on the code you’re writing. Why bother to organise it when you know how it works?

Rather than providing an explanation of code readability right away, we’re going to provide a practical demonstration. Below are two boxes of Arduino code - A and B - which do exactly the same thing. When uploaded, they will both do precisely the same task. Cover the second box (labelled B) with your palm and try to figure out what box A will do if it was uploaded to your Arduino.

Code A

uint8_t a[]={1,0,1,0,1,0,1,1,1,0,1,1,1,0,1,1,1,0,1,0,1,0,1,0};uint32_t b=0;void setup() { pinMode(13,1);}void loop(){b=(millis()/500)%(sizeof(a)/sizeof(uint8_t));digitalWrite(13,a[b]);}

Pretty horrible, right? It’s difficult to infer the meaning of the variables, and there is no indentation, comments or new lines. The code is poorly spaced and we’ll wager even experienced programmers will take a few minutes to figure out what it does.

Code B

uint8_t sos_sequence[] = 
{1,0,1,0,1,0,1,1,1,0,1,1,1,0,1,1,1,0,1,0,1,0,1,0};
uint16_t counter = 0;
void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
}
void loop() {
  //Sets the counter variable to a number between 
  //0 and the size of the SOS sequence (-1).
  //It increments every 500 milliseconds.
  counter = (millis() / 500) % (sizeof(sos_sequence) / sizeof(uint8_t));
  //Writes the SOS sequence to the builtin LED.
  digitalWrite(LED_BUILTIN, sos_sequence[counter]);
}

This code is much clearer - it displays an SOS sequence on your Arduino’s built-in LED. Because the code is appropriately commented, the variables are clearly named, and everything is formatted well, it allows even beginners who are not confident with code to get a grasp on what it does.

Formatting

If you’re looking to improve your Arduino code, formatting it properly is an essential first step. The first thing to recognise about this is there is no ‘right’ or ‘wrong’ way of doing it. However, there is most definitely ‘good’ and ‘bad’ formatting, and as such, most Arduino programmers have adopted formatting conventions to help with this. Most Arduino code that you’ll find written by experienced programmers will look very similar, and this is a great thing.

Check out the line of code below:

x=gettemperaturedata(pinarray[o+2*y-1]);x+=1;

There isn’t anything inherently incorrect about this code. Whatever it’s intended to do, it will work fine. However, its formatting is very poor, which makes it uncomfortable and annoying to read. Most programmers will add space between operators like =, + and * in their code.

x = gettemperaturedata(pinarray[o + 2 * y - 1]); x += 1;

That’s slightly better, but there is much more we can do to make this code work properly. Since we can’t name variables with spaces in most modern programming languages, we often use a variable naming style to make reading them easier.

Naming convention

Example

Lower Camel Case

someVariableName

Upper Camel Case

SomeVariableName

Snake Case

some_variable_name

Macro Case

SOME_VARIABLE_NAME

There are many case styles, but above are some of the most common. These can differ in use depending on the language used. C++ (of which the Arduino language is based on and compatible with) conventionally uses Snake Case for both function and variable names. However, in many projects at DIYODE you’ll see us using Snake Case for variables, and Lower Camel Case for function names.

x = getTemperatureData(pin_array[o + 2 * y - 1]); x+=1;

Is this conventionally correct? No, but it is consistent and simple, which can be applied to any personal decision when choosing your coding style. Most Arduino programmers will also use Macro Case for their macros, which helps other programmers reading realise when a macro is involved. For example, the code below will illuminate an LED on Pin 9 with the use of macros.

#define RED_LED_PIN 9
void setup() {
    pinMode(RED_LED_PIN, OUTPUT):
    digitalWrite(RED_LED_PIN, HIGH);
}

There are actually three macros called in this code! Yep, RED_LED_PIN, OUTPUT and HIGH are all macros! OUTPUT and HIGH are defined within the Arduino libraries, and if you use a software like Visual Studio Code to hover over the macro, you can see what it’ll be replaced with when the code is compiled.

Because of the naming conventions used by the Arduino developers, beginners can easily identify macros, even if it isn’t entirely clear what the value’s purpose is.

Okay, back to our badly formatted example. We need to do a couple more things before the code is readable enough by most standards. First of all, let’s give the statement at the end its own line. There aren't many times where we want two different actions on the same line.

x = getTemperatureData(pin_array[o + 2 * y - 1]);
x += 1;

Nearly there, but let’s format our operators so the order of operations becomes clearer. Like in regular mathematics, the order of operations is vitally important - BODMAS if that acronym is familiar! This step is somewhat optional, but it makes the code much easier to read. In some situations, using brackets is critical to obtain the desired result.

x = getTemperatureData(pin_array[o + (2 * y) - 1]);
x += 1;

Now, it is clear that 2 is multiplied by ‘y’ (whatever y is) first, before being added to ‘o’ and subtracted by 1. The original code would do this anyway, but it is an improvement to the readability. We’re done!

Indentation

Indentation is forgotten by a lot of beginners. It’s very important to help understand the flow of a program. Similar to how you might create headings and subheadings in a document, indentations allow programmers to highlight ‘scope’ in programs. In a coding context, ‘scope’ refers to a particular section of code that only runs under certain conditions, or when a specific function is called.

Global functions, variables and other objects do not usually have any indentation in our Arduino programs. This means that they are positioned as far left on the page as possible.

#define MOTOR_OUTPUT_A 12
#define USE_DEBUG false
const float pi = 3.1415;
uint32_t last_access_timestamp = 0;

The name and brackets enclosing a function also do not usually have an indentation, but their contents do. This demonstrates to a reader that the contents inside are executed based on the thing that encloses them - in this case, this function being called.

void setup() {
    pinMode(9, OUTPUT);
    Serial.begin(9600);
    Serial.println("Starting program. Please wait...");
    while(!digitalRead(READY_SIGNAL));
    Serial.println("Program Reading. Continuing...");
}

Here is another example; a ‘for’ loop. The contents of this for loop will be run multiple times, and the indentation helps us realise what is inside, and what is outside the loop. Notice also that the for loop is already inside a function, so the for loop’s code has a second level of indentation to maintain readability.

void exampleFunction() {
    Serial.println("Starting accessing array.");
    for(int c = 0; c < 15; c++) {
        time_array[c] = millis() / 100;
    }
    Serial.println("Finished accessing array.");
}

To add indentation to a block of code in most modern programming environments, just highlight it and press ‘Tab’ on your keyboard. To remove an indentation, press ‘Shift-Tab’ - this moves the code block left. Luckily, most programming environments are pretty intelligent in 2023 - they’ll help you indent properly, as well as start new lines of code with the correct formatting and position.

Indentation is very important to get comfortable with early in your programming journey. It’s much like learning to add paragraphs to your writing - it makes everything much easier to digest. Some programming languages like Python make indentation compulsory!

Documentation

Code Documentation is a bit like the word ‘Artificial Intelligence’ - it’s a blanket term that can be applied in many different ways. It can mean anything from making a website to show how to use your code, to actually writing comments in the source code. Making a website is a bit out of scope of this edition of Secret Code, but if you are interested in doing this, services like GitHub and ReadTheDocs are more than up to the task.

Our DIYsplay library uses GitHub to display guides and information for easy use.

Example of a code library using ReadTheDocs

In this edition of Secret Code, we’re focused on internally documenting your code with comments. Comments do not increase the size of the resulting program on your Arduino, so it doesn’t matter whether you have 10 or 10,000 comments in your project.

As you’re probably already aware, comments can be made in Arduino code like this:

// This is a comment!

You can also add multiline comments like this:

/* Multi-line comments are an easy way to document
 * large blocks of code, provide theoretical information
 * about a technique your code is using, list copyright
 * information or even leave notes to future programmers.
 */

Multi-line comments are started with /* and ended with */, and can be as long as you like. Conventionally, many C++ programmers will add asterisk characters on each new line.

Okay, so comments are great, and show what our code does to anybody reading! Awesome! But, like an unsupervised kid with access to a permanent marker, it’s easy to make a mess that’s no fun for anybody to deal with.

// Prints "Hello World" to the Serial Console.
Serial.println("Hello World");
// Subtracts 15 from x each time this code is run.
x -= 15;
// Wait one second until we do the next thing.
delay(1000);
//This is a for loop which prints "I love Arduino!" to the Serial Console 3 times.
for(int i = 0; i < 3; i++) {
    Serial.println("I love Arduino!");
}

None of the comments in the code above are necessary. Why? Because they don’t provide any meaningful information to the programmer, while doubling the length of the code for no reason. They are simply stating what the code is doing, which can be inferred from the statements themselves.

To an absolute beginner, these comments may be helpful. However, to an intermediate programmer, they are completely useless. As this code becomes more complex, we end up creating a very large and cumbersome code file that has comments strewn everywhere for no reason.

As a rule of thumb, comments should always describe the “How?” or “Why?” of a particular block of code. If the meaning of the code isn’t obvious, and can’t be made obvious, it’s time to add a comment describing why the code is written this way. For instance, have a look at a snippet of code from our EZ-ATX project last month:

  // If the sleep timer isn't yet active, we have just turned off all channels,
  // AND we are on the main channel display, start the sleep timer.
  if(!power_state && sleep_timer == -1 && mode == M_MAIN) {
    sleep_timer = millis();
  } else if (power_state && sleep_timer != -1) {
    // If the sleep timer IS active, and a channel was just turned on, reset the sleep timer.
    sleep_timer = -1;
  }

This code probably isn’t totally efficient, but it IS easy to read. The ‘if’ statement is somewhat mystifying on its own, but comments provide context and a human-readable translation of its purpose.

Efficient Code

If you have the need for speed, you’ve come to the right place. In this part of Secret Code, we’re showing you the basics of speeding up and making your code efficient. Aside from simply running faster, there are many instances where a different approach to gathering and using data will make your life substantially easier when programming projects.

Blocking Functions

Many Arduino novices will be familiar and comfortable with functions like delay(), Serial.print() and control structures like ‘while’ and ‘for’ loops.

Using these in your code is easy and efficient, right up to the point where you have to do multiple things at the same time. Let’s look at a very common code snippet that blinks an LED once every second:

void loop() {
    digitalWrite(LED_BUILTIN, HIGH);
    delay(1000);
    digitalWrite(LED_BUILTIN, LOW);
    delay(1000);
}

This, or something like this, was probably contained in the first Arduino code you ever uploaded. It’s as simple as it gets. We made a timing diagram to show you what this might look like:

However, let’s say we want to add another LED to our Arduino that blinks every half of a second. Both LEDs have to blink at their respective rates at the same time. We’ll put the next LED on Digital Pin 5.

Sounds tricky? You might be able to make it work with more delays like this:

void loop() {
    digitalWrite(LED_BUILTIN, HIGH);
    delay(500);
    digitalWrite(5, HIGH);
    delay(500);
    digitalWrite(5, LOW);
    digitalWrite(LED_BUILTIN, LOW);
    delay(500);
    digitalWrite(5, HIGH);
    delay(500);
    digitalWrite(5, LOW);
}

Okay, it’s getting a bit harder, but nothing we couldn’t manage with a little bit of mental arithmetic, right? Let’s add another LED that blinks every 300 milliseconds. It’ll probably give you a headache trying to imagine how to program that, but be our guest!

Instead of tackling it head-on, let’s look at the fundamental issue here: delay() is a blocking function. This means that whenever it’s waiting to complete, no code can run in the meantime. So, if one of our LEDs needs to turn on or off while that delay() is running, we’re out of luck unfortunately.

An analogy of this problem is to imagine that you have three pizza ovens, all of which cook pizzas at different speeds. If our objective is to take out cooked pizzas and put in new ones as quickly as possible, how do we do it?

We never thought we’d be making an analogy involving pizza ovens, but here we are!

Standing in front of one oven and waiting for it to be finished is efficient, as long as we have just one oven. But, if others are cooking, we’ll miss those while we wait. Instead, we need to be checking each pizza as we go along the line. If any need to be cooked or taken out of the oven, we do that as we check each oven. Once we reach the third oven, we go back to the first oven and the process repeats.

How do we do that in code? We first need to set up a few variables for each LED. Each LED has a number that corresponds to the last time (tracked in milliseconds) it was toggled, as well as a variable to keep track of whether we turned it on or off when we wrote to it.

uint32_t last_led_a_toggle = 0;
bool led_a_state = false;
uint32_t last_led_b_toggle = 0;
bool led_b_state = false;
uint32_t last_led_c_toggle = 0;
bool led_c_state = false;

In our loop function, we can ‘check our ovens’ with some if statements. For each LED, we’re checking if the difference between the current time (called with the millis() function) and the last time we toggled is larger than our blinking period. If it is, we toggle the state of the LED.

The critical thing to notice is that if it isn’t time to toggle the LED yet, the code doesn’t stop running! It just goes and ‘checks the other ovens’ while it waits! This code could be easily expanded to support any number of LEDs, each with its own blinking speeds. No delay() functions were used at all in this code. If this project needed to do something else at the same time, it would be incredibly easy to add that functionality.

void loop() {
    if(millis() - last_led_a_toggle > 1000) {
        led_a_state = !led_a_state;
        last_led_a_toggle = millis();
        digitalWrite(LED_A, led_a_state);
    }
    if(millis() - last_led_b_toggle > 1000) {
        led_b_state = !led_b_state;
        last_led_b_toggle = millis();
        digitalWrite(LED_B, led_b_state);
    }
    if(millis() - last_led_c_toggle > 1000) {
        led_c_state = !led_c_state;
        last_led_c_toggle = millis();
        digitalWrite(LED_C, led_c_state);
    }
}

But, there is an even easier way! Using some clever maths, we can turn each LED on/off with just three lines of code!

void loop() {
    digitalWrite(LED_BUILTIN, (millis()/1000) % 2);
    digitalWrite(5, (millis()/500) % 2);
    digitalWrite(3, (millis()/300) % 2);
}

0 % 2 = 0

1 % 2 = 1

2 % 2 = 0

3 % 2 = 1

4 % 2 = 0

5 % 2 = 1

6 % 2 = 0

7 % 2 = 1

The ‘%’ (modulo) operator divides one number by another, but only gives us the remainder of the result. Notice in the table right that using the ‘% 2’ on any whole number results in a sequence that switches between 0 and 1, exactly what we need for a blinking LED!

When fed into the digitalWrite() function, it is super easy to create blinking LEDs! This approach only works for simple patterns, and won’t work if you have more complicated functionality you need to do at the same time.

Data Types

When we first learn Arduino programming, we are quickly introduced to some of the most important data types to use with our variables. ‘int’ is one of the most commonly used, and simply refers to a whole number that can be positive or negative. There are, of course, many more types such as char, boolean and String, however, let’s stick to basic integers to begin with.

When an ‘int’ is defined on an Arduino Uno like in the code below, it uses 16 bits - or two bytes - of memory to store its value.

int example_variable = 14032;

The maximum number of combinations that a 16-bit value can store is 65,536. We can find this by raising the number of possible values in each bit - 2 - to the power of the number of bits we have - 16.

So, the biggest number we can store in an ‘int’ is 65536, right? Not quite. An ‘integer’ can also be negative, so the first bit of the number is used for dictating the sign - either negative or positive. Experienced Arduino programmers will know there is more to it than this, where the ‘Two’s Complement’ method is used to simplify arithmetic, but for this explanation, we’re disregarding this.

Long story short, because we have one less bit for our variable size, we are restricted to values -32,768 to 32,767. For those who are still getting to grips with working with variables, this is an extremely important fact! For example, if you were assigning the number of sensor detections to an ‘int’ variable, after 32,767 detections, we’ve now run out of space in our ‘int’ variable!

What happens now is called an ‘integer overflow’ - the variable simply wraps back from the highest to the lowest value! So, one minute you’ll have 32,767 in your variable, and the next you may have -32,768. This causes huge issues in many programs, so when working on your own projects, be very mindful of this! You can imagine the variable size as a big wheel like this:

As our variable increases or decreases, the clock’s ‘hand’ will rotate around until it hits the other end of the range. At that point, the value of the variable has changed drastically, but the clock hand rotates as normal.

For instance, in the year 2038, Integer Overflow could affect more than just us makers! Millions of devices around the world use the Unix time system, which is a constantly incrementing number, starting from 0 on the date January 1st, 1970. However, devices that store this number as a 32-bit signed integer will overflow in 2038, causing their time to wrap back to the year 1901. It’s ‘only’ a 137 year discrepancy, no biggie!

Luckily, most modern devices now store this number in a 64-bit integer, which we won’t have to worry about for a few billion years or so.

Choosing a Data Type

The easiest way to resolve these integer problems is to choose an appropriately sized variable! Why not choose a ‘long’ variable, which is twice as long as an ‘int’ variable?

Instead of writing this:

int counter = 0;

Use this instead:

long counter = 0;

This can store a much bigger range of numbers; -2,147,483,648 to 2,147,483,647 to be exact. So, let’s just make all of our variables as big as possible, right? This is wasteful and efficient - we are just wasting RAM space on our Arduino by using large data types. If you only need to store a number that will only reach 10, for example, using a ‘long’ variable will waste 28 bits (or nearly 4 bytes) of RAM.

On a desktop computer, we have literally billions of bytes of RAM, so we can use large variables all day long. On an Arduino Uno, we only have 2KB of RAM to use, which can be filled up very quickly by large variables!

The best strategy is to pick the smallest data type that will let your program run in an efficient, bug-free way.

To really illustrate the astronomical distinction between the size of these data types, let’s pretend we are assigning the value of the millis() function to some different data types. The millis() returns the number of milliseconds the Arduino has been running for, so how long would it take for these data types to overflow - i.e. reach their maximum values?

Note that the last two data types are seldom used because they become increasingly slow, memory-intensive and hard to work with on an Arduino. If you want a large variable, we recommend working with the ‘unsigned long’ data type and being mindful of the limitations involved.

Data Type

Number of Bits

Time to Overflow

byte

8

0.256 seconds

int

16

32.7 seconds

long

32

24.9 seconds

unsigned long

32

49.7 days

long long

64

292 million years

unsigned long long

64

585 million years

Fixed Data Types

If you want to be super professional with your variable types, you can choose to use data types that are more fixed in terms of their size. For example, instead of writing this:

int a_variable = 10;

We can write this for the same effect:

int16_t a_variable = 10;

Why would we want to do this? Isn’t this just complicating things more than necessary? For beginner-level code, we would tend to agree. However, as you become more familiar with Arduino code, it becomes important that our code becomes reproducible across different Arduino chips and platforms.

Image Credit: Jaycar XC3800

For example, while an 'int' variable on an Arduino Uno has 16 bits, an 'int' on an ESP32 - an Arduino-compatible WiFi board - is actually 32 bits!

What if we wanted to write some Arduino code that would be compatible with C++ code written for executables on a Windows computer? A ‘short’ variable on a desktop computer mostly refers to a 16 bit number, while a ‘long’ refers to 64 bits!

If you’re writing code that relies on manipulating bits or the size of numbers, this could lead to confusion when your code is run on other platforms. This could lead to some very hard-to-track-down bugs.

Using variable types such as ‘int16_t’ fixes the size of the variable to 16 bits, so no matter what platform you’re running on, your code will work the same way. You could defined an unsigned 16-bit number like this:

uint16_t another_variable = 1000;

If you’re only needing 8 bits - 1 byte - we can define a variable like this:

uint8_t a_small_number = 153;

This is a great option for numbers that will never get big - such as an index for a button press or something similar. Just make sure to pick values that suit your application!

Optimising Code

For many programs we write on our Arduinos, the speed of processing usually isn’t an issue - the code runs fast enough that, on a human scale, we can’t really tell the difference. However, as we start to build more complex programs, execute hundreds of calculations per second, or multitask in our projects, speed becomes a much more pressing concern.

In this part of Secret Code, we’re looking into how to speed up our Arduino code. Generally speaking, this process is called optimisation. The objective is to minimally impact the functionality or accuracy of the code, and maximise the speed at which it runs.

In Issue 65, we used an Arduino and a DIYsplay to make a Frequency Generator. After optimising the code, we could generate frequencies of up to 2MHz!

General Optimisation

There are tricks you can use to optimise your code, regardless of what it’s for.

One good example is optimising your IF conditionals. Often, these branches will have multiple conditions, where they will only run if they are evaluated as true. Below, we’ve written some code that might be used in a data logger project. It would submit sensor data over WiFi if the WiFi module is connected AND we’ve set the ‘transmit_data’ variable to true.

if(WiFiClient.status() == CONNECTED && transmit_data) {
    //other code here
}

However, we can actually speed up this code by simply re-ordering the conditions we’re testing in the IF conditional. If we put the ‘transmit_data’ variable first, the Arduino will test this condition first. If it’s false, it won’t bother checking if the WiFi module is connected, since we won’t be running the code anyway. Checking the state of a boolean variable is very fast, and checking the status of WiFi may be a lot slower, so if we can avoid checking WiFi, we can save a ton of time!

Here is our re-ordered code:

if(transmit_data && WiFiClient.status() == CONNECTED) {
    //other code here
}

Lookup Tables

The words “Lookup Table” may bring up some old memories from high school! Before calculators and microcontrollers were compact enough to fit in a pencil case, trigonometric and logarithmic values would often be pre-calculated and printed into paper tables for student reference. I even used them for Z-scores and standard deviations in high school only a few years ago! But, what point do they have when we have such capable microcontrollers at our disposal?

Ironically, while a lookup table is slow when used by a human on paper, it can be very fast when used in code! There are quite a few functions that are computationally very expensive on the Arduino. This means that they take a long time to execute, and in a program where you might want to use the function a few thousand times every second, you might not have enough speed to make it all work.

The most commonly used ‘slow’ functions are the trigonometric functions - sin(), cos(), tan() and the inverse versions of those functions. If you’re still new to Arduino programming, we use these to do maths with anything involving circles - they’re also very useful for providing ‘pulsing’ effects to LEDs due to their repetitive nature.

The reason why these functions are slow is that there is no simple way to work them out exactly. They are usually approximations requiring complex algorithms, which require a lot of processor cycles to get the required precision.

Lookup tables avoid the process of working out a computationally complex function by pre-calculating a big list of potential values, which can be ‘looked-up’ in an array. For example, here is a ‘sin table’ that can be copy-pasted into your Arduino sketch:

uint8_t sinTable8[] = { 
  0, 4, 9, 13, 18, 22, 27, 31, 35, 40, 44,
  49, 53, 57, 62, 66, 70, 75, 79, 83, 87,
  91, 96, 100, 104, 108, 112, 116, 120, 124, 128,
  131, 135, 139, 143, 146, 150, 153, 157, 160, 164,
  167, 171, 174, 177, 180, 183, 186, 190, 192, 195,
  198, 201, 204, 206, 209, 211, 214, 216, 219, 221,
  223, 225, 227, 229, 231, 233, 235, 236, 238, 240,
  241, 243, 244, 245, 246, 247, 248, 249, 250, 251,
  252, 253, 253, 254, 254, 254, 255, 255, 255, 255,
  255
};

If we plot these values on a graph, it looks like this:

This only returns 8-bit values, but it could be used to make an LED pulse in a ‘heartbeat’ like this:

analogWrite(LED_PIN, sinTable8[(millis()/10) % 90]);

This is fast code - much faster than using the standard sin() function with the Arduino. Many code implementations such as the FastTrig library use “interpolated lookups”, which involve a lookup table like above, but adds precision by guessing the values in between.

Even without those fancy code tricks, the lookup table is still accurate enough for most uses with an Arduino. We tested this by creating a graph to demonstrate the accuracy of the sin table for angles between 0 and 90 degrees.

You can see that most of the time, the sin table stays within 0.5% of the real value! This is a great tradeoff for massively improved speed.

Saving Space

Spoiler Alert - Our Arduino isn’t a supercomputer! It doesn’t have limitless resources to spend on every variable and array, so we need to be mindful of what we’re using. One of the easiest ways to save space in an Arduino program is to put constant variables in the flash memory rather than the SRAM. In layman's terms, we can tell the compiler to place variables that won’t change into long-term memory, rather than keeping them in short-term memory. We do this with the keyword ‘PROGMEM’:

const PROGMEM uint8_t possible_values[] = {0, 144, 1, 223, 32, 5, 84, 112};

If this variable was stored in SRAM, where it would take 64 bytes of storage. On an Arduino Uno, this would eat up about 3% of its 2KB SRAM just like that. However, moving it to its much larger 32KB flash memory uses much less, allowing space for more variables to be used.

To read from this variable, we need to use some special syntax:

result = pgm_read_byte(&possible_values[index]);

A similar trick can be used for printing strings to the Serial console. This is something that a lot of Arduino programmers use to debug their programs, and unfortunately, the String messages contained within them use quite a bit of memory when used.

To do this, just wrap any string in F() and the compiler will be directed to place the string in flash memory instead.

Serial.println(F("Hello World! I’m stored in flash memory, so I’m saving 82 bytes of program memory!"));

There are some limitations to doing this, though. Flash memory is not modifiable at run-time, so everything you put into it must be constant. The other downside to using flash memory is that it’s slower than the SRAM. For large strings that are only printed once onto a screen, this isn’t an issue. However, where speed is needed, it will need to be tested to ensure it’s adequate for your needs.

Final Thoughts

This isn’t one of our usual discussions about transistors, motors or logic gates, so we’ll hope you appreciate our mostly theoretical exploration of Arduino code. We’ve probably written hundreds of Arduino programs over the years, and as with any hobby or area of interest, we’ve gotten better at writing code. There have been a number of times when we’ve needed to get more functionality or performance out of Arduinos, and the tips we’ve explained in this guide should help you to get your own programs running faster, with less resource use.

Image Credit: DigiKey

There are, of course, other microcontroller platforms that have a different set of programming requirements and syntaxes. The STM32 series is extremely popular for more commercially-oriented projects, as well as PIC microcontrollers that have their own programming setups. However, many of the general code tips we’ve shown today apply to almost any programming language, so there’s no excuse for not keeping your programs organised no matter what or how you code!

But most of all, experimentation is key. You can search all of the online forums and tutorials you want, but trying out new coding techniques and testing them on your Arduino is the best way to learn! There are many tricks that we haven’t listed here, or may not have been discovered at all, that will improve your programs too. Enjoy!