Macros a Arduino: Guia completa per dominar-les amb exemples

  • Les macros a Arduino permeten optimitzar codi i memòria abans de compilar.
  • L'ús de #define permet crear funcions, constants i estructures adaptatives.
  • Macros avançades permeten concatenar identificadors i accedir a PROGMEM.
  • Un ús disciplinat de macros millora l‟eficiència sense sacrificar llegibilitat.

Arduino Cores Zephyr US beta-1

Arduino ha revolucionat el món de l'electrònica gràcies a la facilitat amb què es poden crear prototips i projectes funcionals. Tot i això, per als que volen portar la seva programació un pas més enllà i optimitzar recursos, mantenir el codi net i guanyar en eficiència, les macros passen a ser una eina clau.

En aquest article ens submergirem de ple en l'ús de macros a Arduino: què són, com es fan servir, els seus avantatges i limitacions. I ho farem reunint la informació més completa i útil dels millors recursos disponibles en línia, reescrita de forma clara i actual per ser veritablement pràctica.

Què són les macros a Arduino?

Les macros són directives del preprocessador en C/C++ que permeten substituir text abans que el codi sigui compilat. En lloc d'executar instruccions com una funció tradicional, una macro actua reemplaçant parts del text font, cosa que té un impacte directe en com es genera el codi binari final.

El preprocessador s'executa abans de la compilació pròpiament dita i és l'encarregat d'aplicar aquestes substitucions. A Arduino, això permet des de definir constants, incloure fitxers condicionalment o fins i tot crear petites funcions en línia que estalvien temps i memòria.

Exemple bàsic: una definició com #define LED_PIN 13 fa que a tot el codi se substitueixi automàticament LED_PIN per 13 abans de compilar.

Això pot semblar trivial, però ofereix una potent manera d'escriure codi més flexible i mantenible.

Avantatges de l'ús de macros

La implementació de macros en projectes amb Arduino pot oferir una sèrie de beneficis concrets:

  • Millora la llegibilitat del codi: en reutilitzar noms simbòlics, és més fàcil entendre el propòsit de cada element.
  • Optimitza el rendiment: en no generar trucades a funcions, les macros poden executar operacions més ràpid.
  • Redueix lús de memòria RAM: especialment útil en plaques amb recursos limitats, com la Arduino UNO.
  • Permet adaptacions condicionals: és possible compilar diferents fragments de codi segons el tipus de placa Arduino utilitzada.

Macros bàsiques: ús de #define

la directiva # definir és la més utilitzada. S'empra tant per definir valors constants com per crear funcions automàtiques injectades en temps de precompilació.

Exemple 1: Definir un pin

#define PINLED 13

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

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

Exemple 2: Macro com a funció inline

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

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

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

Com podeu veure, l'ús del patró do { … } while(0) assegura que la macro es comporti de manera segura fins i tot si es fa servir dins d'estructures condicionals.

Operador ## i concatenació de macros

L'operador ## és una eina poderosa del preprocessador que permet concatenar identificadors. Això és molt útil quan es vol generar noms de variables de manera dinàmica.

Exemple pràctic:

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

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

Advertiment important: aquest operador no és compatible amb tots els models de plaques Arduino per igual. Per exemple, pot funcionar bé en una Un o Esplora, però fallar en una Mega. A més, no es pot niar la creació de macros dins d'altres usant ## directament.

Macros i estalvi de memòria

Un dels avantatges clau de l'ús de macros a Arduino és el estalvi de memòria RAM. Arduino té una capacitat limitada, de manera que carregar cadenes de text directament a la RAM pot tornar-se un problema significatiu.

Una tècnica avançada per evitar això implica fer servir FORCE_INLINE i carregar cadenes des de la memòria del programa (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))

L‟ús d‟aquestes macros pot suposar la diferència entre que un projecte funcioni o no, sobretot en aplicacions amb pantalles o múltiples sensors.

Macros combinats amb funcions

Les macros també poden facilitar la crida a funcions dinàmicament, segons un tipus passat com a paràmetre. Un exemple clar i força gràfic és:

#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);
}

Gràcies a l'operador ## ia les macros, podem evitar repetir estructures i centralitzar la lògica dinàmica.

Macros amb paràmetres de sortida

També és possible utilitzar macros per encapsular petits objectes o 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"));
}

Bones pràctiques i precaucions amb macros

Usar macros en excés o sense cura pot portar a errors difícils de depurar. Per exemple, en realitzar substitucions errònies o definir noms que col·lisionen amb altres de biblioteques externes.

Algunes normes bàsiques per evitar problemes:

  • Evita espais o salts de línia no necessaris dins de la macro.
  • No incloguis comentaris dins de macros complexes que usin diverses línies.
  • Utilitza noms únics o amb prefixos (com el nom del projecte) per evitar conflictes.
  • Reemplaça macros per constants o funcions reals quan sigui possible. C++ modern permet alternatives més netes i segures.

Daltra banda, labús de macros pot reduir la claredat del codi. L'objectiu ha de ser millorar l'eficiència i la modularitat sense comprometre la mantenibilitat.

Directives condicionades i compilació adaptativa

Una de les funcionalitats més pràctiques en projectes escalables és l'ús de macros per a generar codi de forma condicional, una cosa molt útil quan es vol que un mateix esquetx funcioni en diferents plaques.

Exemple típic:

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

També és útil per controlar la depuració o mostrar missatges del compilador amb #pragma message o fins i tot generar errors en determinades condicions amb #error.

Macres internes del compilador

El preprocessador de GCC per a AVR (usat a Arduino) inclou diverses macros especials que ofereix informació del sistema, molt útils durant el desenvolupament:

  • __LINE__: número de línia actual.
  • __FILE__: nom del fitxer en curs.
  • __TIME__ i __DATE__: hora i data de compilació.
  • __func__: nom de la funció actual.

Amb elles es pot portar control de versions, estructures de log, i facilitar el manteniment i el traçat d'errors sense envair el codi principal.

Les macros ofereixen una forma poderosa i flexible per estructurar projectes fets amb Arduino. Permeten definir constants, estalviar memòria, adaptar el codi segons lentorn dexecució i crear blocs reutilitzables sense necessitat de duplicar línies. Això sí, requereixen disciplina, claredat i coneixement per evitar errors subtils o pèrdua de llegibilitat. Si s'apliquen correctament, són un avantatge invaluable per a desenvolupadors intermedis i avançats.