bclose

El ámbito de las variables en Arduino

Ambito de las variables.

Objetivos

 

 

    • Presentar el concepto de ámbito de una variable.
    • Mostrar porque es necesario un ámbito de variables.
    • Variables globales y locales.
    • Variables estáticas: static.

 

 

Material requerido.

Imagen de Arduino UNO

 Arduino UNO o equivalente.

 

Las variables

 

En los tutoriales de introducción a Arduino, hemos ido jugando con variables de una forma más o menos informal, por una decisión deliberada. Hemos usado los diferentes tipos de variables y hemos operado con ellas, pero sin entrar en muchos detalles.

En concreto hemos utilizado variables locales y globales, pero evitando, discretamente, hacer comentarios sobre el tema.  Sin embargo ha llegado el momento de entrar en detalle en una propiedad clave de las variables en C++ (y en prácticamente cualquier lenguaje de programación moderno), llamada el ámbito de las variables y su persistencia.

Para entender el concepto de ámbito (Scope) de una variable, nos conviene dar un pequeño rodeo y hacer historia (Tranquilos, será una batallita muy corta).

Históricamente, en los primeros lenguajes de programación, todas las variables eran variables globales. Esto quiere decir, que cuando definías una de cualquier tipo, su valor era accesible desde cualquier punto del programa. Podías leerla o modificar su valor sin restricciones.

Al principio, puede parecer lo ideal. Pero a medida que los programas van creciendo en tamaño y complejidad, la cosa se complica y tiende a generar problemas complicados de detectar y resolver.

Por ejemplo, a lo largo de las sesiones del curso de introducción hemos usado variables tipo como i, j hasta la saciedad en los bucles for, por ejemplo. Sin un programa tiene media docena de bucles, y por costumbre usamos estas nombres de variables para indexar la iteración, es cuestión de tiempo (y poco, creedme) que la variable de un bucle, se confunda o mezcle con otra de un bucle distinto.

De repente el programa empezara a actuar de  forma imprevisible, porque una variable que creemos controlada y sencilla en una zona de programa, toma valores diferentes de los previstos porque usamos ese mismo nombre en otra zona y sencillamente no nos hemos percatado de ello

Establecer la relación entre esa confusión de nombres y los problemas extraños que aparecen, puede ser mucho menos evidente en la realidad de lo que parece ahora en un ejemplo ad hoc, y a medida que el programa crece el problema tiende a crecer hacia el infinito.

Por eso, se propuso una solución muy elegante (Que son las que nos gustan), que las variables no fueran visibles en todo el ámbito del programa, si no solamente en ciertas zonas, de acuerdo a unas reglas claras.

Como C++ exige que las variables se declaren, antes de usarlas, Las variables que se declaran fuera de un bloque de código, son variables Globales, están disponibles desde cualquier punto de un programa.

 
  • Ya hablamos como sin querer en sesiones previas, del concepto de bloque de código como aquel grupo de instrucciones contenidas entre llaves (Apertura y cierre).
 

Las variables que se declaran dentro de un bloque, se llaman variables locales, y solo existen dentro del bloque en que se definen y no fuera. Cuando nuestro programa entra en el bloque, la variable que se declara, se crea sobre la marcha para su uso interno.

Cuando el programa abandona el bloque, esa variable local se destruye y desaparece en el olvido. La próxima vez que regresemos al bloque se creará de nuevo, sin recordar si ha tenido existencia previa.

De este modo, cuando necesitamos definir variables instrumentales, como para indexar un for, por ejemplo, las variables se crean ad hoc y se destruyen al salir, imposibilitando que una variable local se confunda con otra variable local de otro bloque de código.

Sencillamente no existen variables externas al bloque, excepto las globales,  y por tanto su confusión no puede provocar errores imprevistos por  utilizar el mismo nombre.

Resolvemos el problema de un plumazo y definitivamente. ¿Elegante, NO?

Vamos a ver algunos ejemplos de esto.

 

Ejemplos de variables locales y globales

 

Os recomiendo que no leáis simplemente los ejemplos que os pongo. Copiad el programa en vuestro Arduino y haced pruebas, porque el tema es más delicado de lo que parece y no es fácil ir cogiendo una idea clara.

Usaremos una variable global llamada control, para impedir que el resultado se repita en la consola. Para definir una variable global, basta con declararla fuera de un bloque. Al principio está bien

bool control = true;

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

void loop()
   {   int j = 3 ;
       if ( control)
            {  for (j=0 ; j<10 ;j++)
                  {    Serial.print("j = ");
                       Serial.println(j);
                  }
               Serial.println("...........................");
               Serial.println(j);
            }
       control = false ;   // Para que no vuelva a imprimir el resultado
   }

Piensa un momento antes de ver el resultado. ¿Cuál crees tú que será?

Fijaros que definimos la variable j al principio del loop, pero no la declaramos de nuevo en el for, sino que usamos una variable preexistente.

Por eso el for asume que queremos usar una variable pre declarada y la utiliza para la iteración

Consola arduino

El resultado es 10, naturalmente. Hagamos una ligera modificación del programa previo. Vamos a volver a declarar la variable j como int, pero dentro del for.

void loop()
   {   int j = 3 ;
       if ( control)
            {  for (int j=0 ; j<10 ;j++)     // Atentos aquí
                  {    Serial.print("j = ");
                       Serial.println(j);
                  }
               Serial.println("...........................");
               Serial.println(j);
            }
       control = false ;   // Para que no vuelva a imprimir el resultado
   }

Esta vez, el resultado cambia radicalmente:

Display de la consola de Arduino

 

La última línea es 3. La variable j del loop es diferente de la j del for, porque la hemos definido dentro de un bloque distinto. Por eso al salir del for su variable local j, se desvanece y la que existe es la variable local del loop y el resultado tras la línea de  puntos es 3 y no 10 como en el ejemplo anterior.

 
  • En la instrucción for, y en cualquier otra que se aplica a un bloque, las definiciones que   se hagan, se consideran incluidas  dentro del bloque al que afecta y no a la parte exterior.
  • Una variable solo se puede declarar una vez dentro de un bloque. Cualquier intento de declarar la misma variable por segunda vez, provocara un error fulminante del compilador:
 

Consola de errores

 

Es importante que comprendáis que esta regla no solo tiene que ver con el concepto formal del bloque, como instrucciones contenidas entre apertura y cierre de llaves, sino que se aplica también a los bloques implícitos en los que prescindimos de estas:

void loop()
   {   int j = 3 ;
       if ( control)
           {   for (int j=0 ; j<10 ;j++)         // Atentos aqui
                    Serial.println(j);
               Serial.println("...........................");
               Serial.println(j);
           }
       control = false ;
   }

Cuyo resultado es el mismo que antes, aunque ahora no hay llaves de por medio, pero si un bloque implícito:

Display de la consola de Arduino

Todos los lenguajes de programación modernos incluyen el concepto de variables locales y variables globales y aplican las reglas que hemos descrito aquí (Los lenguajes se copian unos a otros claro, y las buenas ideas se reproducen)

A esa propiedad de las variables, de existir durante un cierto tiempo, se le suele llamar persistencia de la variable.

 

Variables static

 

Esta propiedad característica de las variables locales, su persistencia, para crearse de la nada y desaparecer en el olvido al salir de un bloque, es una gran ventaja y ahorra muchas horas de vagar en pena por los pasillos a los programadores (Mientras meditan en qué demonios será lo que está mal. De aquí la injusta fama de pirados).

Pero a veces (La vida es complicada) esto puede ser un inconveniente y nos gustaría que ciertas variables locales, no desapareciesen al salir, o al menos que no perdiéramos su valor.

Imagínate un Arduino que está leyendo datos de un sensor y almacenando su valor en una variable local. Nos gustaría saber el último valor de lectura cuando volvamos a esa función.

 
  • Siempre podemos definir una variable global, pero puede no tener mucho sentido si solo se utiliza en una función en exclusiva.
 

Para esto, podemos definir una variable local como estática (static). Quiere decir que solo será accesible desde esta función, pero que su valor no desaparecerá al salir, sino que persistirá en el tiempo, mientras no cerremos el programa, conservando su último valor.

Veamos un ejemplo sencillo sacado de la Wikipedia:

void loop()
   {  if ( control)
         {   Funcion1 (); // muestra 0
             Funcion1 (); // muestra 1
             Funcion1 (); // muestra 2
         }
      control = false ; 
   }
void Funcion1 ()
   {  int x = 0;          // Declaramos x como local
      Serial.println(x);
      x = x + 1;
   }

En la definición de Funcion1, declaramos x como int, sin más, por que el resultado será así:

Resultado con variable local

 

Cuando entramos em Funcion1, la int x se declara, se crea sobre la marcha y se le asigna el valor 0.

Cada vez que salimos de Funcion1, la variable x se desvanece y se vuelve a crear la próxima vez que entremos en ella, tal y como corresponde a una variable local obediente. Y se repite el proceso.

Pero hagamos un pequeño cambio. En Funcion1 vamos a declarar la variable x como static.

void loop()
   {  if ( control)
         {   Funcion1 (); // muestra 0
             Funcion1 (); // muestra 1
             Funcion1 (); // muestra 2
         }
      control = false ; 
   }
void Funcion1 ()
   {  static int x = 0;          // Declaramos x como static
      Serial.println(x);
      x = x + 1;
   }

El resultado es, que la variable x no se desvanece ya al salir, sino que conserva su valor para la próxima vez que volvemos a Funcion1. Sigue siendo una variable local, no puede ser usada por ningún programa o función fuera de Funcion1, pero no desaparece al salir sino que persiste.

Resultado con variable estatica

Nótese además, de que como la variable estática no se destruye al salir,  se crea solo una vez y persiste indefinidamente. La declaración de variable y la asignación de la variable se realizan una única vez.

 
  • Esto hace que a x solo se le asigna el valor 0 cuando se crea y no cada vez que entra en la Funcion1, como podría pensarse inicialmente. Mucho cuidado con esto
 

 

Resumen de la sesión

 

 
    • Hemos visto las diferencias entre variables locales y las variables globales.
    • La propiedad de las variables que define donde existen y pueden ser usadas y donde no, se le conoce como ámbito de la variable.
    • Hemos visto unos pocos ejemplos sencillos que ilustran ese comportamiento, un tanto anti intuitivo que se derivan de la definición ámbito.
    • Hemos visto también el concepto de variables estáticas, como variables locales que persisten, aun cuando abandonamos su ámbito de referencia.
 

 

 

 

(21) Comments

    • Dimates

    Excelente tutorial… pasoa a paso

  • hola este es el codigo :

    /*
    const byte L1 = 11;
    const byte L2 = 12;
    const byte L3 = 13;
    int L0= 0;
    int ledState = LOW;
    unsigned long previousMillis = 0;
    const long interval = 100;

    void setup() {
    Serial.begin(9600);//iniciar comunicacion serial
    pinMode(L1,OUTPUT); //pin de salida
    pinMode(L2,OUTPUT); //pin de salida
    pinMode(L3,OUTPUT); //pin de salida

    }

    void loop() {
    if (Serial.available()>0) {
    char dato = Serial.read(); //se lee la variable enviada

    switch (dato) { //se seleciona el dato dependiendo de la variable recibida

    case ‘1’ : (L0=L1);
    break;
    case ‘2’ : (L0=L2);
    break;
    case ‘3’ : (L0=L3);
    break;

    }
    switch (dato) {
    case ‘E’ : digitalWrite(L0,HIGH);
    break;

    case ‘A’ : digitalWrite(L0,LOW);
    break;

    case ‘P’ :
    unsigned long currentMillis = millis();
    if (currentMillis – previousMillis >= interval) {
    previousMillis = currentMillis; // if the LED is off turn it on and vice-versa:
    if (ledState == LOW) {
    ledState = HIGH;
    } else {
    ledState = LOW;
    }
    // set the LED with the ledState of the variable:
    digitalWrite(L0, ledState);
    break;
    }
    }
    }
    }
    */

  • Hola como seria en el caso que yo tengo tres led y necesito ejecutar una misma tarea para los tres
    ejemplo

    LED1 = 11;
    LED2= 12;
    LED3= 13;

    OPCION ‘E’ = encender

    necesito que lo haga pero por ejemplo decirle al monitor serie algo asi como

    E LED1
    y que encienda tal led los mismo para apagar

    • Hola Daniela, no estoy seguro de entenderte bien. Te refieres a que quieres encender o apagar los 3 LED en función de alguna condición?

    • Hola daniela,
      Basta con que hagas un programa de lectura del puerto serie para leer la instruccion, por ejemplo E LED1 y luego separes las dos partes para tener la orden, pero una manera mas sencilla , si estas empezando es que des por la puerta serie un simple numero como: 7
      Ahora lo lees directamente con un Serial.parseInt() que te recoge un solo integer y tu por programa inviertes el estado de este mismo pin con lo que consigues encender o apagar pines sin mas que darle el numero de pin

      • HOLAA con un poco mas de logica logre esto

        /*
        const byte L1 = 11;
        const byte L2 = 12;
        const byte L3 = 13;
        int L0= 0;

        void setup() {
        Serial.begin(9600);//iniciar comunicacion serial
        pinMode(L1,OUTPUT); //pin de salida
        pinMode(L2,OUTPUT); //pin de salida
        pinMode(L3,OUTPUT); //pin de salida

        }

        void loop() {

        if (Serial.available()>0) {
        char dato = Serial.read(); //se lee la variable enviada

        switch (dato) { //se seleciona el dato dependiendo de la variable recibida

        case ‘1’ : (L0=L1);
        break;
        case ‘2’ : (L0=L2);
        break;
        case ‘3’ : (L0=L3);
        break;

        }

        switch (dato) {

        case ‘E’ : digitalWrite(L0,HIGH);
        break;

        case ‘A’ : digitalWrite(L0,LOW);
        break;
        }
        }

        }

        */

        ahora le agrege el blink sin delay pero en eso tengo problemas

  • Hola Jesús.
    Te recuerdo que elegí una Arduino Mega ADK.
    Vamos por lo mas fácil.
    Te copio la configuración del Keypad, donde estan los fila pines y columnas pines.
    /*********************************************************************************************************/
    /* CONFIGURACIÓN DEL KEYPAD */
    const byte FILAS=4;
    const byte COLUMNAS=4;
    char Keys[FILAS][COLUMNAS]={
    {‘1′,’2′,’3′,’A’},
    {‘4′,’5′,’6′,’B’},
    {‘7′,’8′,’9′,’C’},
    {‘*’,’0′,’#’,’D’}
    };
    byte filaPines[FILAS]={54,55,56,57};
    byte columnaPines[COLUMNAS]={58,59,60,61};

    /*********************************************************************************************************/
    Respecto del Sketch, ningún problema pero se encuentra recién en sus inicios y con una cantidad innumerable de vicios y errores de programador autodidacta que me dará mucha vergüenza el hacerlo publico. Pero lo haremos!!.
    Entre nosotros que significa “amañao”, quizás “aficionado”,
    Un abrazo y Saludos
    Guillermo

    • Hola Guillermo. No te dé verguenza compartir código. Los míos propios tampoco son un ejemplo de elegancia, depuración y mínimos bytes. El sketch que yo me hice (sacado de otro en Internet) consiste en la espera de 4 dígitos en un orden concreto (clave secreta), y que al llegar al último correcto, pues se enciende un LED, como se podría activar un relé, simulando una caja fuerte, o apertura de una puerta, vamos. Para resetear en código introducido, se debe pulsar asterisco, y me funciona. Comentarte que en un principio tuve que cambiar los pines 0 y 1, que corresponden a las dos columnas de la derecha, por los pines 8 y 9, pues había conflicto con Rx y Tx, pero en tu caso no es así. Prueba a cambiar la almohadilla por una letra a ver si te funciona.

      “Amañao” viende de amañado, habilidoso, mañoso, diestro, etc. Esa típica persona que tanto sabe colocar ladrillos, cambiar una tubería, reformar un baño, etc..

      Saludos y ya nos cuentas a ver.

  • Se me olvidaba. Si tengo un hueco, pruebo a ver si me funciona.

  • Hablando de ámbito, pido disculpas al administrador y a Jesús (ya que has tenido la amabilidad de prestarme atención) quizás no sea este el lugar para comentar todo esto pero ya está. He realizado el consejo, esta vez con el nombre PULSADOR en lugar de Key.
    Sorpresa el cartel de error es diferente.

    La nueva declaración dentro del void loop es:

    char PULSADOR=Teclado.getKey();

    Y como corresponde en el void de la FUNCION:

    if (PULSADOR==’#’)

    Y el error es:

    PULSADOR’ was not declared in this scope

    Frente a este mensaje, declaré a PULSADOR como variable global al principio y otra sorpresa, el compilador, compila el sketch y no genera error. Al cargarlo y correrlo todo anda menos la lectura del caracter “#”.

    Me declaro como un programador a penas novato y frente a la adversidad necesito un consejo de profesores y programadores experimentados como Uds., Me rindo, dejo el parche des-prolijo que me hacia funcionar el programa o continuo peleando..

    Un fuere abrazo desde Argentina.

    • Me alegro Gullermo. Y te comento que no soy programador. Mi profesión no tiene nada que ver con la electrónica, pero soy “amañao” Y como me gusta, pues ahí estamos. Y curiosamente yo también tenía pensado armar una alarma con keypad, LCD, PIR y relé con SCR o algo similar. Pero la falta de tiempo….

      Saludos.

    • Hola de nuevo Guillermo ¿Puedes pasarnos los pines a los que tienes conectado el keypad? Y también sería bueno ver el sketch completo. Esto último es más cómodo subirlo a un contenedor (Dropbox, Box, One Drive, etc), generar un enlace y compartirlo aquí, para no tener que poner todo el sketch.

  • Buena Jesús, esa no la probé.
    Lo intentaré y luego aviso.
    Gracias!!

  • Nop!!! la variable Key ya esta definida en el void loop como:

    char Key=Teclado.getKey();

    Pregunta tonta:
    Hace mucho que no programas un keypad, si no es así voy a tener que ponerte una baja calificación como profesor!!!

    • Hola Guillermo. Puede que no sea esto, pero prueba usar otro nombre para tu variable, en vez de “key”. Puede ser tecla, pulsacion, boton, etc. Puede que haya conflicto.

  • Buen día acá y gracias por tan pronta respuesta.

    La cosa es así:

    Comienza con un switch

    switch (CZNP==0)
    {
    case 0:

    lcd.setCursor(0,2);
    lcd.print(“ZONAS DESPROTEGIDAS”);
    lcd.setCursor(0,3);
    lcd.print(“Ingresar(#) para Ver”);

    if (Key==’#’) // INGRESO A PANTALLA DE REGISTRO DE ZONAS DESPROTEGIDAS
    {
    lcd.clear();
    byte N=0; // Nro. DE ORDEN DE LA ZONA DESPROTEGIDA
    byte x=0; // POSICION EN LA FILA DEL LCD
    byte y=0; // POSICION DE LA COLUMNA DEL LCD
    for (byte i=0;i<19;i++)
    {
    ————————————————————————————————————————————————————————-
    Como lo solucione momentáneamente?

    Creando una variable global caracter llamada TECLA al que luego le asigne el valor de Key, o sea:

    TECLA=Key;

    Entonces el if queda de la forma:

    if (TECLA=='#')

    El compilador no arroja ningún error y funciona pero tengo que encontrar cual es la razón y quedarme tranquilo.

    Gracias

    • jajaja asi me gusta, moral de batalla.
      Pues la verdad es que no le veo el sentido, lo unico que te diria es que definas key como tipo char y deberia ser suficiente

  • Ante todo gracias por tu atención.
    Estoy llevando adelante un proyecto personal que en realidad resulta una excelente excusa para pasar el tiempo aprendiendo, además de poner algo en movimiento las neuronas que me quedan.
    Se trata de una Alarma domiciliaria compuesta inicialmente por una Ardunino Mega ADk, un Keypad de 16 botones y un LCD de 4 x 20 (I2C).
    Ya tengo todo conectado y funcionando.
    Vamos a mi problema. Para la lectura del Keypad he empleado la librería , donde como vos conoces existe una variable llamada “Key”. La he usado en el sketch principal (void loop) satisfactoriamente.
    Pero al querer emplearla desde una función, en este caso “ if (Key==’#’) obtengo una respuesta del compilador del siguiente tenor:

    Alarma-V12-1:596: error: expected primary-expression before ‘==’ token expected primary-expression before ‘==’ token

    Algo me huele que puede estar relacionado con el retorno de la variable en un ámbito que está fuera de donde fue declarada.
    Seguiré luchando aunque una ayuda nunca viene mal.
    Gracias y un fuerte abrazo.

    • Hola Guillermo,
      No parece demasiado grave.¿puedes ponernos la linea que te genera error? para comprobarla

    • Enrique dominguez

    Muy buen artículo para entender las variables. De lo mejor. Muchas gracias!

  • Muy bueno. Muchas gracias.

Give a Reply

WordPress Anti-Spam by WP-SpamShield