Nokia 6110 Part 2 – Display driver

This is a four part series:

Talking to the LCD module

First, the pin mappings as shown in the previous diagram:

                     // Description  Pin on LCD display
                     // LCD Vcc .... Pin 1 +3.3V (up to 7.4 mA) Chip power supply
#define PIN_SCLK  2  // LCD SPIClk . Pin 2 Serial clock line of LCD                          // Was 2 on 1st prototype
#define PIN_SDIN  5  // LCD SPIDat . Pin 3 Serial data input of LCD                          // Was 5 on 1st prototype
#define PIN_DC    3  // LCD Dat/Com. Pin 4 (or sometimes labelled A0) command/data switch    // Was 3 on 1st prototype
                     // LCD CS  .... Pin 5 Active low chip select (connected to GND)
                     // LCD OSC .... Pin 6 External clock, connected to vdd
                     // LCD Gnd .... Pin 7 Ground for VDD
                     // LCD Vout ... Pin 8 Output of display-internal dc/dc converter - Left floating - NO WIRE FOR THIS.  If we added a wire, we could connect to gnd via a 100nF capacitor
#define PIN_RESET 4  // LCD RST .... Pin 9 Active low reset   // Was 4 on prototype
#define PIN_BACKLIGHT 6 // - Backlight controller. Optional.  It's connected to a transistor that should be connected to Vcc and gnd

#define LCD_C     LOW
#define LCD_D     HIGH

We need to initialize the LCD:

void LcdClear(void)
{
  for (int index = 0; index < LCD_X * LCD_Y / 8; index++)
    LcdWrite(LCD_D, 0x00);
}

void LcdInitialise(void)
{
 // pinMode(PIN_SCE, OUTPUT);
  pinMode(PIN_RESET, OUTPUT);
  pinMode(PIN_DC, OUTPUT);
  pinMode(PIN_SDIN, OUTPUT);
  pinMode(PIN_SCLK, OUTPUT);
  pinMode(PIN_BACKLIGHT, OUTPUT);
  digitalWrite(PIN_RESET, LOW); // This must be set to low within 30ms of start up, so don't put any long running code before this
  delay(30); //The res pulse needs to be a minimum of 100ns long, with no maximum.  So technically we don't need this delay since a digital write takes 1/8mhz = 125ns.  However I'm making it 30ms for no real reason
  digitalWrite(PIN_RESET, HIGH);
  digitalWrite(PIN_BACKLIGHT, HIGH);
  LcdWrite(LCD_C, 0x21 );  // LCD Extended Commands.
  LcdWrite(LCD_C, 0x80 + 0x31 ); // Set LCD Vop (Contrast).  //0x80 + V_op    The LCD voltage is:  V_lcd = 3.06 + V_op * 0.06
  LcdWrite(LCD_C, 0x04 + 0x0 );  // Set Temp coefficent. //0 = Upper Limit.  1 = Typical Curve.  2 = Temperature coefficient of IC.  3 = Lower limit
  LcdWrite(LCD_C, 0x10 + 0x3 );  // LCD bias mode 1:48. //0x10  + bias mode.  A bias mode of 3 gives a "recommended mux rate" of 1:48 
  LcdWrite(LCD_C, 0x20 );  // LCD Basic Commands
  LcdWrite(LCD_C, 0x0C );  // LCD in normal mode.
}

/* Write a column of 8 pixels in one go */
void LcdWrite(byte dc, byte data)
{
  digitalWrite(PIN_DC, dc);
  shiftOut(PIN_SDIN, PIN_SCLK, MSBFIRST, data);
}

/* gotoXY routine to position cursor 
   x - range: 0 to 84
   y - range: 0 to 5
*/
void gotoXY(int x, int y)
{
  LcdWrite( 0, 0x80 | x);  // Column.
  LcdWrite( 0, 0x40 | y);  // Row.  
}

So this is pretty straightforward. We can’t write per pixel, but must write a column of 8 pixels at a time.

This causes a problem – we don’t have enough memory to make a framebuffer (we have only 2kb in total!), so all drawing must be calculated on the fly, with the whole column of 8 pixels calculated on the fly.

Snake game

The purpose of all of this is to create a game of snake. In our game, we want the snake to be 3 pixels wide, with a 1 pixel gap. It must be offset by 2 pixels though because of border.

The result is code like this:

/* x,y are in board coordinates where x is between 0 to 19 and y is between 0 and 10, inclusive*/
void update_square(const int x, const int y)
{
  /* Say x,y is board square 0,0  so we need to update columns 1,2,3,4
   * and rows 1,2,3,4  (column and row 0 is the border).
   * But we have actually update row 0,1,2,3,4,5,6,7  since that's how the
   * lcd display works.
   */
  int col_start = 4*x+1;
  int col_end = col_start+3; // Inclusive range
  for(int pixel_x = col_start; pixel_x <= col_end; ++pixel_x) {
    int pixelrow_y_start = y/2;
    int pixelrow_y_end = (y+1)/2; /* Inclusive.  We are updating either 1 or 2 lcd block, each with 2 squares */
    int current_y = pixelrow_y_start*2;
    for(int pixelrow_y = pixelrow_y_start; pixelrow_y <= pixelrow_y_end; ++pixelrow_y, current_y+=2) {
      /* pixel_x is between 0 and 83, and pixelrow_y is between 0 and 5 inclusive */
      int number = 0; /* The 8-bit pixels for this column */
      if(pixelrow_y == 0)
        number = 0b1; /* Top border */
      else
        number = get_image(x, current_y-1, (pixel_x-1)%4) >> 3;
      if( current_y < ARENA_HEIGHT) {
        number |= (get_image(x, current_y, (pixel_x-1)%4) << 1);
        number |= (get_image(x, current_y+1, (pixel_x-1) %4) << 5);
      }
      gotoXY(pixel_x, pixelrow_y);
      LcdWrite(1,number);
    }
  }
}

int get_image(int x, int y, int column)
{
  if( y >= ARENA_HEIGHT)
    return 0;
  int number = 0;
  if( y == ARENA_HEIGHT-1 )
    number = 0b0100000; /* Bottom border */

  if(board.hasSnake(x,y))
    return number | get_snake_image(x, y, column);
  
  if(food.x == x && food.y == y)
    return number | get_food_image(x, y, column);

  return number;
}

int get_food_image(int x, int y, int column)
{
  if(column == 0)
    return 0b0000;
  if(column == 1)
    return 0b0100;
  if(column == 2)
    return 0b1010;
  if(column == 3)
    return 0b0100;
}

/* Column 0 and row 0 is the gap between the snake*/

/* Column is 0-3 and this returns a number between
 * 0b0000 and 0b1111 for the pixels for this column
   The MSB (left most digit) is drawn below the LSB */
int get_snake_image(int x, int y, int column)
{
  if(column == 0) {
    if(board.snakeFromLeft(x,y))
      return 0b1110;
    else
      return 0;
  }
  if(board.snakeFromTop(x,y))
    return 0b1111;
  else
    return 0b1110;
}

Pretty messy code, but necessary to save every possible byte of memory.

Now we can call

update_square(x,y)

when something changes in board coordinates x,y, and this will redraw the screen at that position.

So now we just need to implement the snake logic to create a snake and get it to play automatically. And do so in around 1 kilobyte of memory!

Advertisement

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s