Sunday 6 March 2016

Nokia 6100 - PFC8833 from a Raspberry Pi

Philips PCF8833 on Pi

The Nokia 6100 display has been around for many years, but I picked one up on the cheap as they are now pretty much out of stock and selling cheaper than ever before. 

I picked up a Sparkfun version whilst buying some other components and thought I'd hook it up to my Pi for inclusion in some new projects. I could tell the display was old by the tarnishing on the connectors and a bit of cleaning was needed before the soldering iron came out. 

The big issue is that you don't know if you'll get an Epson or Philips chip with the display. I believe that the Epson were included in older Sparkfun boards and Philips were later, but this is only reading between lines from various posts about the display.

Support for Raspberry Pi

There are various examples of implementations with the Pi on the Internet. Most of these are bit banged examples or some bizarre serial hack to work in 9bit mode. Most seem to report poor performance or complex arrangement of pins. 

I downloaded one example to only get garbage on the screen and this was for a Philips.
In the end I decided to roll my own and create yet another example of working code for this display. My world is C/C++ but the examples can easily be converted to Python once the implementation is understood.

The best source of information was from Jim Lynch which is linked to from Sparkfun's site at http://www.sparkfun.com/tutorial/Nokia%206100%20LCD%20Display%20Driver.pdf.
The old link for the Philips driver is now missing from Sparkfun, but I found it linked from engpedrorafel's github site. 

Writing 9 bit SPI to Raspberry Pi

The shield has been built for Arduino boards, hence the pin layout and extra pins which are not used.In addition to the physical layout the Arduino's can implement 9bit serial communications to the display. Try and do this with Linux on a Pi and it really doesn't want to work. This is due to the Linux spidev driver being limited and not supporting LoSSI protocol.

Deep down on the Broadcom 2835 it does support LoSSI but after some playing with the BCM2835 library I only got as far as my code hanging and the SDA output doing nothing. I'm sure this can be made to work but in the meantime I hit on a better way to work using SPI using most drivers. 

Writing with 8 bit SPI


Whilst looking at reasons why the Linux driver doesn't really support LoSSI there was a simple solution presented. Why not just use 9 bytes of data for transmission and use NOOP commands for empty bytes. 
Despite the overhead of writing additional bytes, the advantage is still being able to use the SPI hardware to transfer the data and avoid too much unnecessary work in software (for instance bit banging data). 
Bytes fit into the transmission as shown above. You actually only transmit 8 bytes of data and the 9th bit for D/C uses up the space for the 9th byte of data. 

Example code

I wrote some example code using the BCM library. This has a wrapper which is good to go for C/C++. In hindsight a Python example with spidev may have been better and I may still provide one. WiringPi uses spidev but doesn't expose enough options from spidev to be useful.

The code below is very simple and sets up the display and then flashes red, green and blue screen fills as fast as possible. You should barely see the red and green fills in this code.

#include <stdio.h>
#include <stdint.h>
#include "bcm2835.h"

#define NOKIA_CMD 0
#define NOKIA_DTA 1

// global buffer
static char bitbuff[9] ;
static char prevByte ;
static int nCurByte ; // 0-8 value for 9 bytes

void writeSPI(int nDC, char byte)
{
  char out = 0x00 ;
  
  if (nCurByte == 0){  // First byte to write - exception as no prior byte
    out = (byte >> (nCurByte+1));
  }else{
    out = (byte >> (nCurByte+1)) | (prevByte << (8-nCurByte)) ;
  }
  if (nDC == NOKIA_CMD){
    out &= ~(1<<(7-nCurByte)) ;
  }else{
    out |= 1 << (7-nCurByte) ;
  }
  
  bitbuff[nCurByte] = out ;
  prevByte = byte ;

  // Fix the last byte and flush to spi
  if (nCurByte >= 7){

    // Last byte fits fully into the final 9th byte of buffer
    bitbuff[8] = byte ;

    bcm2835_spi_transfern(bitbuff, 9) ;

    prevByte = 0x00 ; 
    nCurByte = 0 ; // reset counter, just flushed 9 bytes
  }else{
    // Next byte
    nCurByte++ ;
  }
}

void flushSPI(char noop)
{
  // Add rest of bytes as NOOPS. Note Epson and Philips are different
  while (nCurByte > 0){
    writeSPI(NOKIA_CMD, noop) ;
  }
}

void LCDClearScreen(uint8_t r, uint8_t g, uint8_t b)
{  
  uint8_t byteout = 0x00 ;
  long i; // loop counter
  long c; // colour counter
  char clear_rgb[] = {0x00, 0x00, 0x00} ;

  clear_rgb[0] = r;
  clear_rgb[1] = g;
  clear_rgb[2] = b;
  
  // Row address set (command 0x2B)
  writeSPI(NOKIA_CMD, 0x2B);
  writeSPI(NOKIA_DTA, 0x00);
  writeSPI(NOKIA_DTA, 131) ;

  // Column address set (command 0x2A)
  writeSPI(NOKIA_CMD, 0x2A);
  writeSPI(NOKIA_DTA, 0x00);
  writeSPI(NOKIA_DTA, 131) ;

  // write to display memory over SPI
  writeSPI(NOKIA_CMD, 0x2C); // RAMWR

  c =0;
  // 2 bytes per pixel for 12bit colours
  for (i=0;i < 131 * 131 *2;i++){
    byteout = 0x00 ;
    byteout = clear_rgb[c] << 4 ;
    c = c>=2?0:c+1 ;
    byteout |= clear_rgb[c] & 0x0F ;
    c = c>=2?0:c+1 ;

    writeSPI(NOKIA_DTA,byteout);
  }

}

void philips_init()
{
  // SLEEPOUT - turn on booster circuits
  writeSPI(NOKIA_CMD, 0x11);
  
  // turn on booster voltage
  writeSPI(NOKIA_CMD, 0x03) ;
    
  // Inversion off
  writeSPI(NOKIA_CMD, 0x20) ;
  
  // Colour pixel format 12 bits
  writeSPI(NOKIA_CMD, 0x3A) ;
  writeSPI(NOKIA_DTA, 0x03) ;
  
    // Set up memory access controller.
  writeSPI(NOKIA_CMD, 0x36) ;
  writeSPI(NOKIA_DTA, 0x10) ;
  
  // Set contrast
  writeSPI(NOKIA_CMD, 0x25) ;
  writeSPI(NOKIA_DTA, 0x30) ;
    
  bcm2835_delay(2000) ;
    
  // display on
  writeSPI(NOKIA_CMD, 0x29) ;
}    


int main()
{
  const uint8_t reset_pin = RPI_V2_GPIO_P1_22 ;  
  const char noop_cmd = 0x00 ;
  
  if (bcm2835_init() <= 0){
    fprintf(stderr, "Cannot initialise BCM library\n") ;
    return -1 ;
  }
  
  // Setup the 9 byte buffer
  nCurByte = 0 ;
  prevByte = 0x00 ;
  
  // Initialise the BCM library
  bcm2835_spi_begin() ;

  // Configure for CS0 or CS1
  bcm2835_spi_chipSelect(BCM2835_SPI_CS0) ;

  // Works with Mode 0
  bcm2835_spi_setDataMode(BCM2835_SPI_MODE0) ;

  // Nokia display sets CS on low
  bcm2835_spi_setChipSelectPolarity(BCM2835_SPI_CS0, LOW) ;

  // About 4 MHz when set to BCM2835_SPI_CLOCK_DIVIDER_64
  // Successfully driven at around 15-16MHz
  bcm2835_spi_setClockDivider(BCM2835_SPI_CLOCK_DIVIDER_64) ;

  // Reset the display using pin
  bcm2835_gpio_fsel(reset_pin, BCM2835_GPIO_FSEL_OUTP);
  bcm2835_gpio_write(reset_pin, LOW) ;
  bcm2835_delay(2000) ;
  bcm2835_gpio_write(reset_pin, HIGH) ;
  bcm2835_delay(2000) ;  

  // Only a demo for Philips PCF8833, but easy to reuse for Epson
  philips_init() ;
  flushSPI(noop_cmd) ;

  // Update as quickly as possible
  // Fill screen first with red then green then blue
  LCDClearScreen(0x0F, 0x00, 0x00) ;
  LCDClearScreen(0x00, 0x0F, 0x00) ;
  LCDClearScreen(0x00, 0x00, 0x0F) ;
  flushSPI(noop_cmd) ; // Flush any remaining bytes as NOOPs

  // Close SPI session
  bcm2835_spi_end() ;

  bcm2835_close() ;
  
  return 1 ;
}