Básicos de Software Embebido #2: Apuntadores.

Básicos de Software Embebido #2: Apuntadores.

tecnoingenia / septiembre 2, 2020

Ya hace casi dos meses que les comentaba sobre esta serie de entradas en el blog y por fin le llegó a la hora al siguiente tema. hoy daremos un acercamiento a lo que puede ser el dolor de cabeza de muchos y una gran ayuda para muchos más, cuando de hacer un código escalable y de ahorro de memoria se trata.
En palabras simples, un apuntador es un tipo de variable que guarda el valor de la dirección física de otra variable.
Se declara de la siguiente manera, iniciando con el tipo de dato y seguido de un «*»:
int *apuntador; //declaración de un apuntador de tipo entero

Para asignarle una dirección se hace usando el caracter especial «&» antes de la variable a la que queremos apuntar:
/*Se pude asignar la dirección en la declaración*/
int variable1;
int variable2;
int *apuntador = &variable1;     //apuntador contiene la dirección de variable1

/*Se puede asignar la dirección después de la declaración*/

apuntador = &variable2;          //ahora apuntador contiene la dirección de variable2

Ahora siguiendo las lineas anteriores, podemos acceder a la dirección o al valor de «variable2» desde «apuntador» con el uso de «*»
Serial.println(apuntador + " Esta es la dirección en la que se encuentra el valor variable2");

variable2 = 500;

Serial. print(El valor de variable2 es: " + *apuntador);

/* Por lo tanto *apuntador es igual a 500
 * y apuntador es igual a la dirección en memoria de variable 2
 * que puede ser un numero aleatorio 
**/
Esto nos permite modificar el valor de una variable fuera del contexto en que fue declarada sin necesidad de tener que declararla en un ámbito global.
Veamos un ejemplo práctico, hace unos días hice un controlador muy simple para un tira de led Neopixel, básada en la placa que diseñé para la entrada «Relay programable», esta tiene una pequeña pantalla OLED 64*128px y 6 botones (UP, DOWN, RIGTH, LEFT, OK, CANCEL). 
Bueno, para el interface pensé en 5 pantallas diferentes una para seleccionar el valor de cada color (rojo, verde, azul) desde 0 – 255, otra para seleccionar entre 5 diferente modos de operación y otra más para configurar la cantidad de leds en la tira.
Ahora la forma más sencilla (la facilidad no siempre es sinónimo de eficiencia ni de menor tiempo de desarrollo) sería hacer una función para cada página y después ir cambiando entre funciones, pero esto sería poco eficiente y muy repetitivo ya que cada pagina haría básicamente lo mismo mostrar un valor tipo byte y  aumentar o disminuir este valor por medio de los botones (UP y DOWN).
Analicemos un momento esta alternativa:
/*Tengo 5 valores de configuracion de la tira*/
byte red; //Es el valor de intensidad del color rojo [0-255]
byte green; //Es el valor de intensidad del color verde [0-255] 
byte blue; //Es el valor de intensidad del color azul [0-255]
byte mode; //Es el modo de operacion de la tira
byte NumPixels; //Es la cantidad de leds a controlar

/*Pagina 1, configuración de color rojo*/

void page_1 (void)
{
  display.clearDisplay();
  display.setTextSize(2); 
  display.setTextColor(WHITE);        
  display.setCursor(20,0);
  display.print("ROJO");

  display.setTextSize(4); 
  display.setCursor(10, 25);
  display.print(red);
  display.display();

  if(millis() - keyReadTime >= KEY_RATE)
  {
    if(digitalRead(RIGHT_BUTTON))
    {
      page = (page < numItems - 1) ? page + 1 : 0;
    }
    else if(digitalRead(LEFT_BUTTON))
    {
      page = (page > 0) ? page - 1 : 3;
    }
    else if(digitalRead(UP_BUTTON))
    {
      red = (*dato < 255) ? red + 1 : 0;
    }
    else if(digitalRead(DOWN_BUTTON))
    {
      red = (*dato > 0) ? red - 1 : 255;
    }

    keyReadTime = millis();
  }
}

/*Pagina 2, Configuración del color verde */

void page_2 (void)
{
  display.clearDisplay();
  display.setTextSize(2); 
  display.setTextColor(WHITE);        
  display.setCursor(20,0);
  display.print("VERDE");

  display.setTextSize(4); 
  display.setCursor(10, 25);
  display.print(green);
  display.display();

  if(millis() - keyReadTime >= KEY_RATE)
  {
    if(digitalRead(RIGHT_BUTTON))
    {
      page = (page < numItems - 1) ? page + 1 : 0;
    }
    else if(digitalRead(LEFT_BUTTON))
    {
      page = (page > 0) ? page - 1 : 3;
    }
    else if(digitalRead(UP_BUTTON))
    {
      green = (green < 255) ? green + 1 : 0;
    }
    else if(digitalRead(DOWN_BUTTON))
    {
      green = (green > 0) ? green - 1 : 255;
    }

    keyReadTime = millis();
  }
}

/* ...
 * ...
 */ 

void page_5()
{
 /*El mismo código*/
}

Si lo pudieron notar el código para cada página es idéntico, solo hay que cambiar donde dice «red», por «green», luego «blue», «mode» y «NumPixel»…
Pero si usamos apuntadores podemos hacer lo mismo en una solo función y casi la quinta parte del código. Por si fuera poco lo que es mejor, nuestro código quedaría listo por si en el futuro hay que agregar más parámetros, solo agregándolos a una lista (array).
Analicémoslo…

static void page_settings(void)
{
  byte numItems = 5;
  char *leyends[numItems] = {"RED", "GREEN", "BLUE", "MODE", "NUMPIXELS"};
  int memAddress[numItems] = {RED_VAL_EEPROM_ADD, GREEN_VAL_EEPROM_ADD, BLUE_VAL_EEPROM_ADD, MODE_EEPROM_ADD, NUM_PIXELS_EEPROM_ADD};
  uint16_t pixelSettings[numItems] = {&red, &green, &blue, &amp;mode, &NumPixels};
  byte *dato = pixelSettings[selValue];

  display.clearDisplay();
  display.setTextSize(2); 
  display.setTextColor(WHITE);        
  display.setCursor(20,0);
  display.print(leyends[selValue]);

  display.setTextSize(4); 
  display.setCursor(10, 25);
  display.print(*dato);
  display.display();

  if(millis() - keyReadTime >= KEY_RATE)
  {
    if(digitalRead(RIGHT_BUTTON))
    {
      selValue = (selValue < numItems - 1) ? selValue + 1 : 0;
    }
    else if(digitalRead(LEFT_BUTTON))
    {
      selValue = (selValue > 0) ? selValue - 1 : 3;
    }
    else if(digitalRead(UP_BUTTON))
    {
      *dato = (*dato < 255) ? *dato + 1 : 0;
    }
    else if(digitalRead(DOWN_BUTTON))
    {
      *dato = (*dato > 0) ? *dato - 1 : 255;
    }
    else if(digitalRead(OK_BUTTON))
    {
      for(byte itemIndex = 0; itemIndex < numItems; itemIndex++)
      {
        dato = pixelSettings[itemIndex];
        EEPROM.write(memAddress[itemIndex], *dato);
      }
    }
    else if(digitalRead(CANCEL_BUTTON))
    {
      red = 0;
      green = 0;
      blue = 0;
    }

    keyReadTime = millis();
  }
}
Básicamente se agregó un listado del valor que queremos manipular (pixelSettings), las etiquetas que deben aparecer y las memoria EEPROM para almacenar estos valores.
 
Con «selValue» podemos ir navegando entre los diferentes valores y el apuntador «dato» almacena la dirección del valor que nos interesa por lo tanto podemos modificarlo o mostrarlo en pantalla con «*dato».

 

El siguiente video ilustra el funcionamiento de de las pantalla. Pueden descargar el código completo desde mi perfil github NeoPixel_Controller. La cosa que solo tenía un pixel para probar jaja.

Leave a Reply

TecnoIngenia | Blog personal -israel.barreras@tecnoingenia.com