Nine Techniques to Optimize Your Arduino Code

Arduino, like most microcontroller boards, is limited with its resources. In this tutorial, we will try to work with that by learning nine techniques to optimize your Arduino code.

Code Optimization

Optimizing code means improving the code to produce an efficient program. A well-optimized code must be succinct, occupies lesser memory, has a faster execution time, throughput, low power consumption, and most importantly, the output must be the same as the output of the non-optimized code.

If you’re worried the techniques below are too advanced for your project, do not! They are pretty straightforward. In fact, it’s better to know these techniques early on so that you can truly maximize the boards in your brilliant projects.

Also, we will use an Arduino UNO as an example, but the techniques can be applied to other boards as well.

Techniques for Code Optimization

Remove Unnecessary Code

Unnecessary code means any unused variable, function, or library that you may have included in your sketch. These dead codes are often from the early stage of development. Trying out functions here and there and forgetting them is a savorless recipe for disaster.

Take note that every line of code occupies memory space. It is imperative to delete such useless code to free up your microcontroller real-estate.

To check unused code in your Arduino IDE, see the following blinking LED example:

void setup() {
  int led = 7;
  pinMode(LED_BUILTIN, OUTPUT);
}

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

As you can see, the led variable set to pin 10 is unused. If you try to compile that code in the IDE, you will receive a message like below.

C:\Users\Admin\Documents\Samples\CodeOptimize1.ino: In function 'void setup()':
C:\Users\Admin\Documents\Samples\CodeOptimize1.ino:7:7: warning: unused variable 'myPin' [-Wunused-variable]

   int led = 7;
       ^~~~~

Use Smaller Data Types

A data type tells what a variable can hold in programming. A variable is a name you give to a piece of data in your memory. There are a couple of data types available in Arduino programming. Take a look at the table below for the whole list.

Data TypeSize (bits)Values
bool81 or 0
char8-128 to127
unsigned char, byte80 to 255
short, int16-327768 to 32767
unsigned int, word160 to 65535
long32-2147483648 to 2147483648
unsigned long320 to 4294967295
float, double321.175e-38 to 3.402e38

Data types have different sizes. To preserve space, use the smallest type that can accommodate your data. The most extreme example for this might be using a float variable to store a pin assignment. There are less than 20 pins on the Arduino. Your float variable can store values up to 340,282,300,000,000,000,000,000,000,000,000,000,000.

Use Functions

Functions are named sections of a program that performs a specific task. It prevents code repetition. It saves memory because the CPU only loads the code from the memory when the function is called.

Use Local Variables Instead of Global Variables

Global variables are declared before the void setup() function and can be called throughout your sketch, while Local variables are only available in their parent function. Global variables are set every time the program runs on your Arduino, meaning it takes up resources and contributes to the execution time. It loads even if your main loop doesn’t use it. On the other hand, local variables only load when their parent function is called.

F() Strings

F() Strings provide another way to print text in the serial monitor or a display. The traditional printing of strings consumes a significant amount of RAM. One way to remedy this is by saving the strings to flash memory instead. To do that, we add F() to the beginning of the string variable. For instance, to convert Serial.print("Hello World"); , we can use Serial.print(F("Hello World"));.

Printing a lot of strings on the serial monitor or the LCD consumes a lot of RAM. To save the precious RAM, such strings can be saved on the Flash memory instead. To achieve this, the Arduino employs the F() macro. This simple, yet powerful solution forces the compiler to put the enclosed string in PROGMEM. Here is an example: Serial.print("Optimizing Code");

Adding the F macro can save as much as 16 bytes. The only downside with this macro is that it only applies to strings. If you want to put other data types in the flash memory, use PROGMEM.

Move Constants to PROGMEM

PROGMEM is a keyword in the Arduino IDE that stores data in the program memory or flash instead of RAM. It is highly recommended to store data that never changes, such as constants, in flash because it has greater memory. But do note that flash memory is slower to load. That is why developers recommend using PROGMEM for data that you only have to access once.

To demonstrate, suppose we have the following data:

const int16_t chars[] = {200, 101, 521, 24, 892, 3012, 100};

We store the array in PROGMEM by:

const int16_t chars[] PROGMEM = {200, 101, 521, 24, 892, 3012, 100};

And we read the data from flash with the following code:

void ReadData() {
  unsigned int displayInt;
  
  for (byte k = 0; k < 7; k++) {
    displayChars = pgm_read_word_near(chars + k);
    Serial.println(displayChars);
  }
  Serial.println();
}

The for loop in the code above assumes that you know the size of the data in your variable. However, if this information is not available at hand, you can replace the for loop with the code below:

for (byte k = 0; k < (sizeof(chars) / sizeof(chars[0])); k++) {
    displayInt = pgm_read_word_near(chars + k);
    Serial.println(displayInt);
  }

Using reserve() for Strings

reserve() is a pre-written Arduino function that allocates memory for strings. We use this function to manage the memory of strings that grow in size. Normally, any variable that grows larger invites error in memory fragmentation. This causes the program to have a decrease in performance or, worst-case scenario, freeze. To use reserve(), see the following code:

  String string;
  string.reserve(50);

You should declare the string variable first before reserving the number of bytes in the memory to use this function successfully.

Direct Port Manipulation

Ports are codes that represent the registers inside a microcontroller. It allows you to control and read the state of the pins directly.

You can do Arduino port manipulation with pure C programming thanks to the Arduino IDE’s avr-gcc compiler. Every pin consists of the following register bits: PINxn, DDxn, and PORTxn. Most libraries use these register bits to build their source code to make their code more efficient. We won’t discuss register bits here but you can visit the official datasheet instead.

Direct port manipulation allows faster I/O control and frees up memory space. To demonstrate, below are two code blink sketches. The first one uses Arduino digitalWrite() functions, while the second uses direct port manipulation.

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop() {
  digitalWrite(LED_BUILTIN, HIGH);   
  delay(1000);                       
  digitalWrite(LED_BUILTIN, LOW);    
  delay(1000);                       
}
void setup() {
  DDRB |= (1<<PD5);
}

void loop(){
  PORTB |= (1<<PD5);
  _delay_ms(1000);
  PORTB &= !(1<<PD5);
  _delay_ms(1000);
}

Using the pre-written digitalWrite function, the first program takes 924 bytes of memory. Meanwhile, the second program only occupies 488 bytes.

Remove the Bootloader

Lastly, you can remove the Arduino bootloader to free up space. Microcontrollers are usually programmed through a programmer unless you have a piece of firmware in your microcontroller that allows installing new firmware without the need of an external programmer. This is called a bootloader, according to the official Arduino website.

Unfortunately, this piece of software takes about 2000 bytes of flash memory. Consider removing the bootloader and program with an external programmer or through ISP instead if you’re tight with memory.