Arduino Macros: A Complete Guide to Mastering Them with Examples

  • Macros in Arduino allow you to optimize code and memory before compiling.
  • Using #define allows you to create adaptive functions, constants, and structures.
  • Advanced macros allow you to concatenate identifiers and access PROGMEM.
  • Disciplined use of macros improves efficiency without sacrificing readability.

Arduino Cores Zephyr OS beta-1

Arduino has revolutionized the world of electronics thanks to the ease with which prototypes and functional projects can be created. However, for those who want to take their programming a step further and optimize resources, keep the code clean and gain efficiency, macros become a key tool.

In this article we are going to dive deep into the use of macros in Arduino: what they are, how to use them, their advantages and limitations. And we'll do so by gathering the most comprehensive and useful information from the best resources available online, rewritten in a clear and up-to-date way to be truly practical.

What are macros in Arduino?

Macros are preprocessor directives in C/C++ that allow you to replace text before the code is compiled. Instead of executing instructions like a traditional function, a macro acts by replacing parts of the source text, which has a direct impact on how the final binary code is generated.

The preprocessor It runs before the actual compilation, and is responsible for applying these substitutions. In Arduino, this allows from define constants, conditionally include files or even create small online features that save time and memory.

Basic example: a definition like #define LED_PIN 13 causes all code to be automatically replaced LED_PIN by 13 before compiling.

This may seem trivial, but it offers a powerful way to write more flexible and maintainable code.

Advantages of using macros

Implementing macros in Arduino projects can offer a number of specific benefits:

  • Improve code readability: By reusing symbolic names, it is easier to understand the purpose of each element.
  • Optimize performance: By not generating function calls, macros can execute operations faster.
  • Reduce RAM usage: especially useful on resource-limited boards, such as the Arduino UNO.
  • Allows conditional adaptations: It is possible to compile different code fragments depending on the type of Arduino board used.

Basic Macros: Using #define

Directive #define It is the most widely used. It is used both for define constant values as if to create injected automatic functions at pre-compilation time.

Example 1: Define a pin

#define PINLED 13

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

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

Example 2: Macro as inline function

int itemCounter = 0;
#define COUNT_ITEM() do { itemCounter++; } while(0)

void setup() {
  Serial.begin(9600);
  COUNT_ITEM();
  COUNT_ITEM();
}

void loop() {
  Serial.println(itemCounter);
}

As you can see, the use of the pattern do { … } while(0) ensures that the macro behaves safely even if used within conditional structures.

## operator and macro concatenation

The ## operator is a powerful preprocessor tool. which allows you to concatenate identifiers. This is very useful when you want to dynamically generate variable names.

Practical example:

#define GENERAR_VARIABLE(no) \
  int var##no = no;

void setup() {
  GENERAR_VARIABLE(3); // crea int var3 = 3
}

Important warning: This operator isn't equally compatible with all Arduino board models. For example, it may work fine on an Uno or Esplora, but fail on a Mega. Additionally, you can't nest macros within other macros using ## directly.

Macros and memory saving

One of the key advantages of using macros in Arduino is the saving RAM. Arduino has limited capacity, so loading text strings directly into RAM can become a significant problem.

An advanced technique to avoid this involves using FORCE_INLINE and load strings from program memory (PROGMEM):

#include <HardwareSerial.h>
#define MYSERIAL Serial
#define FORCE_INLINE __attribute__((always_inline)) inline

FORCE_INLINE void printFromFlash(const char *str) {
  char ch = pgm_read_byte(str);
  while (ch) {
    MYSERIAL.write(ch);
    ch = pgm_read_byte(++str);
  }
}

#define SERIAL_LOG(x) (MYSERIAL.print(x))
#define SERIAL_LOGLN(x) (MYSERIAL.println(x))

Using these macros can make the difference between a project working or not, especially in applications with displays or multiple sensors.

Macros combined with functions

Macros can also facilitate dynamic function calls based on a type passed as a parameter. A clear and fairly graphic example is:

#define FUNC_LENTA(tipo) \
  { funcion_##tipo##_lenta(); }

#define FUNC_RAPIDA(tipo) \
  { funcion_##tipo##_rapida(); }

void funcion_caminar_lenta() {
  Serial.println("Andando despacio");
}

void funcion_caminar_rapida() {
  Serial.println("Andando rápido");
}

void setup() {
  Serial.begin(9600);
  FUNC_LENTA(caminar);
}

void loop() {
  FUNC_RAPIDA(caminar);
}

Thanks to the ## operator and macros, we can avoid repeating structures and centralize dynamic logic..

Macros with output parameters

It is also possible to use macros to encapsulate small objects or conversions:

#define BOOL_OUT() (bool){false}
#define NUM_OUT(a,b) (float){a+b}
#define STR_OUT(msg) (String){msg}

void loop() {
  Serial.println(BOOL_OUT());
  Serial.println(NUM_OUT(1.2, 3.4));
  Serial.println(STR_OUT("Mensaje"));
}

Good practices and precautions with macros

Using macros excessively or carelessly can lead to difficult-to-debug errors. For example, by making incorrect substitutions or defining names that collide with names in external libraries.

Some basic rules to avoid problems:

  • Avoid unnecessary spaces or line breaks within the macro.
  • Do not include comments within complex macros that use multiple lines.
  • Use unique names or with prefixes (such as the project name) to avoid conflicts.
  • Replace macros with real constants or functions whenever possible. Modern C++ allows for cleaner, safer alternatives.

On the other hand, overusing macros can reduce code clarity. The goal should be to improve efficiency and modularity without compromising maintainability.

Conditional directives and adaptive compilation

One of the most practical functionalities in scalable projects is the use of macros to conditionally generate code, something very useful when you want the same sketch to work on different boards.

Typical example:

#ifdef ARDUINO_MEGA
  #define LEDPIN 53
#else
  #define LEDPIN 13
#endif

It is also useful for controlling debugging or displaying compiler messages with #pragma message or even generate errors under certain conditions with #mistake.

Internal compiler macros

The GCC preprocessor for AVR (used in Arduino) includes several special macros that provide system information, very useful during development:

  • __LINE__: current line number.
  • __FILE__: name of the current file.
  • __TIME__ and __DATE__: compilation time and date.
  • __func__: name of the current function.

They allow for version control, log structures, and facilitate maintenance and error tracing without invading the main code.

Macros offer a powerful and flexible way to structure Arduino projects. They allow you to define constants, save memory, adapt the code depending on the runtime environment and create reusable blocks without duplicating lines. However, they require discipline, clarity, and knowledge to avoid subtle errors or loss of readability. When applied correctly, they are an invaluable advantage for intermediate and advanced developers.


Be the first to comment

Leave a Comment

Your email address will not be published. Required fields are marked with *

*

*

  1. Responsible for the data: Miguel Ángel Gatón
  2. Purpose of the data: Control SPAM, comment management.
  3. Legitimation: Your consent
  4. Communication of the data: The data will not be communicated to third parties except by legal obligation.
  5. Data storage: Database hosted by Occentus Networks (EU)
  6. Rights: At any time you can limit, recover and delete your information.