Ir al contenido principal

Juegos con Arduino y LCD 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.

Juego con LCD 5110

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.

LCD 5110 delante
LCD 5110 detras

La pantalla es monocroma con una resolución de 84x48 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); 
}

//Envía comandos mágicos al 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.

Comentarios

Entradas populares de este blog

Creando firmas de virus para ClamAV

ClamAv es un antivirus opensource y multiplataforma creado por Tomasz Kojm muy utilizado en los servidores de correo Linux. Este antivirus es desarrollado por la comunidad, y su utilidad práctica depende de que su base de datos de firmas sea lo suficientemente grande y actualizado. Para ello es necesario que voluntarios contribuyan activamente aportando firmas. El presente artículo pretende describir de manera sencilla cómo crear firmas de virus para ClamAV y contribuir con ellas a la comunidad.

Ingeniería inversa de un parche de Microsoft Windows

A estas alturas ya hemos asumido que la seguridad total en Internet es, cuanto menos, un mito. Ningún software está libre de vulnerabilidades, y por mucha auditoría, test de intrusión o pruebas de fuzzing que nos empeñemos en hacer, ninguna metodología puede demostrar sin lugar a dudas que un software es 100% seguro. Ante este panorama, hay dos tendencias mayoritarias a la hora de hacer públicas las vulnerabilidades. Por un lado, están los que piensan que cuando se descubre un fallo ha de hacerse público de forma inmediata. En este escenario, la publicación de una vulnerabilidad es el pistoletazo de salida para una carrera entre el desarrollador para sacar el parche y los "malos" para lograr explotarla. Esta política es conocida como full disclosure . Por otro lado, tenemos a los siguen una política responsible disclosure , que abogan por mantener la vulnerabilidad en secreto hasta la salida del parche por parte del desarrollador. Sin entrar en cuestiones filosóficas que no...

Manejo de grafos con NetworkX en Python

El aprendizaje computacional es un área de investigación que en los últimos años ha tenido un auge importante, sobre todo gracias al aprendizaje profundo (Deep Learning). Pero no todo son redes neuronales. Paralelamente a estas técnicas, más bien basadas en el aprendizaje de patrones, también hay un auge de otras técnicas, digamos, más basadas en el aprendizaje simbólico. Si echamos la vista algunos años atrás, podemos considerar que quizá, la promesa de la web semántica como gran base de conocimiento ha fracasado, pero no es tan así. Ha ido transmutándose y evolucionando hacia bases de conocimiento basadas en ontologías a partir de las cuales es posible obtener nuevo conocimiento. Es lo que llamamos razonamiento automático y empresas como Google ya lo utilizan para ofrecerte información adicional sobre tus búsquedas. Ellos lo llaman Grafos de Conocimiento o Knowledge Graphs . Gracias a estos grafos de conocimiento, Google puede ofrecerte información adicional sobre tu búsqueda, ad...