Variables globales en Arduino

Una variable global en Arduino es una variable cuya visibilidad se extiende a todo el programa, es visible en todos los módulos y funciones. En este artículo conoceremos algunos ejemplos de uso de variables globales y analizaremos cuándo conviene usar una variable global y cuándo es mejor no usarla.

Visibilidad de variables en Arduino

Como bien sabemos, en cualquier lenguaje de programación se utilizan variables para almacenar cualquier dato. Cada variable tiene un nombre y un tipo, y para crear una variable sólo es necesario especificar ambas cosas a la vez. Por ejemplo, si escribimos «int sum;» en el código, le diremos al compilador que necesitamos crear una celda de memoria con el tipo de datos int y la suma del nombre.

Una vez creada una variable, podemos usarla para guardar y leer valores. Pero la pregunta sigue siendo: ¿dónde podemos hacerlo exactamente? De acuerdo con las reglas de Arduino (en realidad, estas reglas vienen de C++), cada variable tiene su propio hábitat, un área donde se puede trabajar con ella, guardar valores y leerlos. Para programar con Arduino debemos comprender que abarca esta área de trabajo.

Variables locales

Las variables locales son variables cuya visibilidad se limita al bloque «local» actual. Por ejemplo, podemos definir una variable en un bloque limitado por los operadores if (condiciones) y la variable sólo será visible en su interior. Ejemplos de bloques locales:

  • Dentro del ciclo (for, while)
  • Dentro de la función
  • Dentro de un bloque condicional (if, else).

La limitación significa lo siguiente: el compilador te permitirá usar el nombre de la variable dentro del bloque pero «no la reconocerá» si la dirigimos desde el exterior. Veamos un ejemplo:

int sum(int a, int b){
  int internalSum = a + b;
  return internalSum;
}

En este ejemplo, hemos creado la variable internalSum y guardamos el resultado de la operación aritmética de sumar los dos valores que se nos pasaron en los argumentos de la función.

Imaginemos que queremos usar esta variable para enviar valores al monitor de puerto. Esto no causará ningún problema:

int sum(int a, int b){
  int internalSum = a + b;
  Serial.print("Sum = ");
  Serial.println(internalSum);
  return internalSum;
}

Creamos la variable, calculamos su valor y la enviamos al monitor del puerto en la función Serial.println.

Ahora crearemos una nueva función e intentaremos también dar salida al valor obtenido en la función anterior. ¿Podremos hacerlo?

void calculadora(){
  Serial.print("Sum = ");
  Serial.println(internalSum);
}

Por supuesto que no. En la función calculadora, el compilador intentará encontrar la definición de la variable internalSum dentro de la función, pero no la encontrará (hemos definido internalSum en la función sum()). Al no encontrar la variable, el compilador generará un error (más adelante veremos como podemos evitar este error utilizando variables globales). Entonces, podemos hacer cualquier cosa con una variable en el bloque en el que fue definida, pero no podemos utilizarla fuera del bloque.

Variables globales

¿Pero qué pasa si necesitamos acceder a la variable desde otro bloque? Tenemos dos opciones para lograrlo.

Si el bloque está dentro del bloque actual (por ejemplo, hay un bloque con un ciclo dentro de la función), los valores del bloque «externo» serán visibles en él. Por ejemplo, en este ejemplo, dentro del bloque para que podamos ver y usar el resultado de la variable:

int factorial(int val) {
  long result = 0;
  for (int i = 0; i < val; i++) {
    result = result * val ; // Tomamos el valor actual del producto, lo multiplicamos por el siguiente número y lo guardamos de nuevo en la variable definida fuera del bloque actual, pero dentro del bloque que lo contiene.
  }
}

La segunda opción es utilizar una variable global.

Una variable global es una variable cuya visibilidad no se limita a funciones individuales o bloques internos. Podemos usar y cambiar la variable global en cualquier parte del programa y al compilador no le importará.

Veamos el siguiente ejemplo del sketch de arduino:

int const pinLed = 13; // Variable global donde guardamos el número de pin. Puede ser reemplazado por una constante.
int lastMillis; // Variable global para almacenar la última vez de la función milis().
int duration = 1000; // Variable global para almacenar la duración de la pausa.
int counter = 0; // Variable global con el valor del contador.
void setup() {
  Serial.begin(9600);
  pinMode(pinLed, OUTPUT);
}
void loop() {
  if (millis() < lastMillis + duration) {
    digitalWrite(pinLed, LOW);    
  } else {
    digitalWrite(pinLed, HIGH);
    delay(1000);
    counter++;
    Serial.println(counter);
    lastMillis = millis();
  }
}

Usamos variables globales para almacenar valores entre llamadas de función de bucle y no para escribir el número de pin varias veces (la variable global pinLed se usa como una constante). La variable lastMills es necesaria para implementar parte de la funcionalidad sin demora().

Problemas de las variables globales

Al tener la oportunidad de cambiar el valor de una variable en cualquier lugar, podemos cometer un error, cambiando accidentalmente el valor de la variable en diferentes partes del programa. Si esto sucede, tendremos que revisar todo nuestro código buscando todos los casos en que usamos la variable global que nos dá el error.
También tendremos que tener en cuenta cómo afectarán los cambios que hagamos en una variable global en las diferentes partes del programa. Este problema se presenta por lo general en grandes proyectos donde tenemos muchas líneas de código, funciones y bloques.

Otro problema específico con el uso de variables globales en arduino es la memoria. Como sabes, los controladores Arduino están limitados en cantidad de memoria. En proyectos reales se debe tener muy en cuenta que uso le damos a cada byte. Las variables locales se crean dinámicamente y desaparecen de la memoria tras la finalización de la función, liberando espacio en la memoria. ¿Cómo se comportan las variables globales? Totalmente lo opuesto, ya que sus sus valores deben estar disponibles en todo el programa y todo el tiempo, por lo que ocupan espacio en memoria constantemente. Y si tenemos muchas variables globales en el código, también necesitaremos mucha memoria para ellas, lo que puede causar problemas.

Este segundo problema podemos solucionarlo utilizando constantes. Si declaramos la constante int const pinLed = 13 en el código, conseguimos que el compilador simplemente inserte un número en los lugares necesarios eliminando esta variable del área de memoria global.

Otros artículos relacionados de nuestro Curso Arduino que pueden interesarte: