Juegos con Arduino y LCD 5110

Arduino 5110

¿Recuerdas el mítico Nokia 5110? El teléfono, obviamente, ya no se vende, pero su pantalla sigue siendo muy valorada por los makers y aficionados a la electrónica. La razón es que, gracias a su interfaz SPI es muy sencilla de manejar desde un microcontrolador. Como hace tiempo que tengo uno dando vueltas por mis cajones me he animado a probarlo, así que os traigo un pequeño montaje realizado con un Arduino Nano, un potenciómetro y, por supuesto, un LCD 5110. Y que mejor que un juego para probar este pequeño LCD.

Las siguientes imágenes muestran la pantalla LCD por delante y también por la parte trasera donde se pueden ver las patillas de conexión. Hay que tener cuidado, ya que he encontrado este mismo LCD con diferente patillaje. El que yo he usado es como éste.

La pantalla es monocroma con una resolución de 84×48 píxeles. Utiliza el controlador PCD8544 y dispone de LEDs para iluminar la pantalla. Se alimenta con una tensión de 3.3V, y sus los pines digitales también funcionan a 3.3V, por lo que hay que adaptar las señales digitales del Arduino, que funcionan a 5V. Aunque no es lo más óptimo, con unas cuantas resistencias de 10KΩ podemos apañarnos. El esquema completo queda así.

Arduino LCD 5110

Las resistencias son de 10KΩ, excepto la primera por la izquierda que conecta el pin 7 del Arduino, que es de 1KΩ, y la última de la derecha, que conecta el LCD con el pin 9 del Arduino, que es de 330Ω. Finalmente las conexiones entre el 5110 y el Arduino quedan de la siguiente manera.

       1-VCC       ----------------  3.3V
       2-GND       ----------------  GND
       3-SCE       ----------------  7
       4-RST       ----------------  6
       5-D/C       ----------------  5
       6-DN(MOSI)  ----------------  11
       7-SCLK      ----------------  13
       8-LED       ----------------  9

El código completo del juego es el siguiente.

#include <Arduino.h>
#include <SPI.h>

/* Comandos PCD8544 */
#define LCD_COMMAND  0
#define LCD_DATA     1

/* LCD 84x48 */
#define LCD_WIDTH   84 
#define LCD_HEIGHT  48 
#define WHITE       0  
#define BLACK       1  

/* Conexionado de pines */
const int scePin = 7;   // SCE - Chip select, pin 3 en LCD.
const int rstPin = 6;   // RST - Reset, pin 4 en LCD.
const int dcPin = 5;    // DC - Data/Command, pin 5 en LCD.
const int sdinPin = 11;  // DN(MOSI) - Serial data, pin 6 en LCD.
const int sclkPin = 13;  // SCLK - Serial clock, pin 7 en LCD.
const int blPin = 9;    // LED - Backlight LED, pin 8 en LCD.

const int analog_in = A0; // entrada analogica potenciometro

/* frame buffer para el display */
byte displayMap[LCD_WIDTH * LCD_HEIGHT / 8];


/* Envio de datos al LCD
 *  El primer parámetro indica si estamos enviando un comando o un dato
 *  El segundo parámetro es el comando o el dato
 */
void LCDWrite(byte data_or_command, byte data)
{
  digitalWrite(dcPin, data_or_command);
  digitalWrite(scePin, LOW);
  SPI.transfer(data); 
  digitalWrite(scePin, HIGH);
}


/* establece fila columna
 *  x - columna
 *  y - fila
 */
void gotoXY(int x, int y)
{
  LCDWrite(0, 0x80 | x);
  LCDWrite(0, 0x40 | y);
}

/* Cambia el estado de un pixel en el framebuffer
 *  x, y - posicion
 *  bw - BLACK o WHITE
 */
void setPixel(int x, int y, boolean bw)
{
  if ((x >= 0) && (x < LCD_WIDTH) && (y >= 0) && (y < LCD_HEIGHT))
  {
    byte shift = y % 8;

    if (bw) 
      displayMap[x + (y/8)*LCD_WIDTH] |= 1<<shift;
    else   
      displayMap[x + (y/8)*LCD_WIDTH] &= ~(1<<shift);
  }
}

/* Dibuja una línea de x0,y0 a x1, y1
 * bw - BLACK o WHITE
 */
void setLine(int x0, int y0, int x1, int y1, boolean bw)
{
  int dy = y1 - y0; 
  int dx = x1 - x0; 
  int stepx, stepy;

  if (dy < 0)
  {
    dy = -dy;
    stepy = -1;
  }
  else
    stepy = 1;

  if (dx < 0)
  {
    dx = -dx;
    stepx = -1;
  }
  else
    stepx = 1;

  dy <<= 1; // dy = 2*dy
  dx <<= 1; // dx = 2*dx
  setPixel(x0, y0, bw); 

  if (dx > dy)
  {
    int fraction = dy - (dx >> 1);
    while (x0 != x1)
    {
      if (fraction >= 0)
      {
        y0 += stepy;
        fraction -= dx;
      }
      x0 += stepx;
      fraction += dy;
      setPixel(x0, y0, bw);
    }
  }
  else
  {
    int fraction = dx - (dy >> 1);
    while (y0 != y1)
    {
      if (fraction >= 0)
      {
        x0 += stepx;
        fraction -= dy;
      }
      y0 += stepy;
      fraction += dx;
      setPixel(x0, y0, bw);
    }
  }
}

/* dibuja un rectángulo
 *  x0, y0 - esquina superior izquierda
 *  x1, y1 - esquina inferior derecha
 *  fill - 1: relleno, 0: sólo perímetro
 *  bw - BLACK o WHITE
 */
void setRect(int x0, int y0, int x1, int y1, boolean fill, boolean bw)
{
  if (fill == 1)
  {
    int xDiff;

    if(x0 > x1)
      xDiff = x0 - x1;
    else
      xDiff = x1 - x0;

    while(xDiff > 0)
    {
      setLine(x0, y0, x0, y1, bw);

      if(x0 > x1)
        x0--;
      else
        x0++;

      xDiff--;
    }
  }
  else
  {
    setLine(x0, y0, x1, y0, bw);
    setLine(x0, y1, x1, y1, bw);
    setLine(x0, y0, x0, y1, bw);
    setLine(x1, y0, x1, y1, bw);
  }
}


/* 
 *  vuelca el contenido del framebuffer a la memoria del LCD.
 */
void updateDisplay()
{
  gotoXY(0, 0);
  for (int i=0; i < (LCD_WIDTH * LCD_HEIGHT / 8); i++)
  {
    LCDWrite(LCD_DATA, displayMap[i]);
  }
}

/* establece el contraste del LCD
 *  normalmente entre 40 y 60
 */
void setContrast(byte contrast)
{
  LCDWrite(LCD_COMMAND, 0x21); 
  LCDWrite(LCD_COMMAND, 0x80 | contrast); 
  LCDWrite(LCD_COMMAND, 0x20); 
}

//This sends the magical commands to the PCD8544
/* 
 *  Inicializar el LCD 5110
 */
void lcdBegin(void)
{
  pinMode(scePin, OUTPUT);
  pinMode(rstPin, OUTPUT);
  pinMode(dcPin, OUTPUT);
  pinMode(sdinPin, OUTPUT);
  pinMode(sclkPin, OUTPUT);
  pinMode(blPin, OUTPUT);
  analogWrite(blPin, 255);

  SPI.begin();
  SPI.setDataMode(SPI_MODE0);
  SPI.setBitOrder(MSBFIRST);

  digitalWrite(rstPin, LOW);
  digitalWrite(rstPin, HIGH);

  LCDWrite(LCD_COMMAND, 0x21);
  LCDWrite(LCD_COMMAND, 0xB0);
  LCDWrite(LCD_COMMAND, 0x04);
  LCDWrite(LCD_COMMAND, 0x14);
  LCDWrite(LCD_COMMAND, 0x20);
  LCDWrite(LCD_COMMAND, 0x0C);
}

/* borrar pantalla
 *  bw - BLACK o WHITE
 */
void clearDisplay(boolean bw)
{
  for (int i=0; i<(LCD_WIDTH * LCD_HEIGHT / 8); i++)
  {
    if (bw)
      displayMap[i] = 0xFF;
    else
      displayMap[i] = 0;
  }
}

int val, pos, bloq_size = 5;
int way_lenght=5;
int way_pos = (LCD_WIDTH/2)-(way_lenght*bloq_size); // posición inicial
int way_dir;
int way[10] = {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}; // tramos de carretera
int i;

void setup() {
  lcdBegin();
  setContrast(40);
}

void loop() {
  val = analogRead(analog_in); // leer potenciometro
  pos = map(val, 0, 780, LCD_WIDTH-bloq_size, 0);
  way_dir = random(-1,2);
  way_pos = way_pos + (way_dir*bloq_size); // nueva posioción de la carretera
  /* evitar salirnos de la pantalla */
  if (way_pos<0) way_pos = 0;
  if (way_pos>LCD_WIDTH-(bloq_size*way_lenght)) way_pos = LCD_WIDTH-(bloq_size*way_lenght); 
  /* scroll de la carretera */
  for (i=9; i>=1; i--) {
    way[i] = way[i-1];
  }
  way[0] = way_pos;

  /* dibujar pantalla */
  clearDisplay(WHITE);
  /* dibujar coche */
  setRect(pos, bloq_size*8, pos+bloq_size, bloq_size*8-10+bloq_size, 1, BLACK);

  /* dibujar carretera */
  for (i=0; i<=9; i++) {
    if (way[i] > 0) {
      setRect(way[i], i*bloq_size, way[i]+bloq_size, (i*bloq_size)+bloq_size, 0, BLACK);
      setRect(way[i]+(way_lenght*bloq_size), i*bloq_size, way[i]+(way_lenght*bloq_size)+bloq_size, (i*bloq_size)+bloq_size, 0, BLACK);
    }
  }

  /* comprobar colisión */
  if ((pos<way[7]+bloq_size) || (pos+bloq_size>way[7]+bloq_size*way_lenght) && way[7]>0) {
    // hay colision
    setRect(pos, bloq_size*8, pos+bloq_size, bloq_size*8-10+bloq_size, 1, WHITE);
    setRect(pos, bloq_size*8, pos+bloq_size, bloq_size*8-10+bloq_size, 0, BLACK);  
    updateDisplay();
    delay(5000);
    // reiniciar el juego
    for (i=0; i<=9; i++) {
      way[i] = -1;
    }
  }
  
  updateDisplay();
  delay(500);
}

Para las funciones de control del LCD me he basado en un código de Sparkfun. Las operaciones de dibujado se realizan sobre una zona de memoria del Arduino (frame buffer). En este caso esa zona de memoria se almacena en la variable displayMap, que es un array de representa el contenido de la pantalla. Cuando deseamos volcar el contenido del frame buffer en la pantalla física, usamos la función LCDWrite. Esta función hace uso del bus SPI para enviar comandos y el contenido del frame buffer al 5110. El resto de funciones son bastante auto explicativas y tienen un pequeño encabezado en su cabecera.

Sé el primero en comentar en «Juegos con Arduino y LCD 5110»

Dejar un comentario

Tu dirección de correo electrónico no será publicada.


*