Kent Display Driver

Hi all,

Just wanted to show off my Kent Display Driver and maybe get some input on how to implement my next stage, writing text and drawing on the screen. Videos and pics are up on my blog:

/*
 * Kent 320x240 LCD Driver
 * Open source code written for open source hardware
 * Programmer: A1
 * 
 * This code was written to interface the Arduino Duemilanove to the Kent 320x240 LCD Display
 * It includes all of the functions for the SPI interface listed in the Kent datasheet referenced below 
 */

#define DATAOUT 11  //MOSI (SI line on Sparkfun breakout board)
#define DATAIN  12  //MISO (SO line on Sparkfun breakout board)
#define SPICLOCK  13  //sck (SCK line on Sparkfun breakout board)
#define SLAVESELECT 10  //ss (CS line on Sparkfun breakout board)
#define BUSY 9  //Busy (BUSY line on Sparkfun breakout board)

char clr = 0;

void setup()
{
  //Initialize I/O pins
  pinMode(DATAOUT, OUTPUT);
  pinMode(DATAIN, INPUT);
  digitalWrite(DATAIN,LOW);
  pinMode(SPICLOCK,OUTPUT);
  pinMode(SLAVESELECT,OUTPUT);
  digitalWrite(SLAVESELECT,HIGH);
  pinMode(BUSY, INPUT);
  digitalWrite(BUSY, LOW);
}

void loop()
{
  SPCR = (1 << SPE) | (1 << MSTR) | (1 << SPR0) | (1 << SPR1);  // Initialize SPI communication
  clr=SPSR;
  clr=SPDR;
  /* 
   * Driver example code  
   */
  int std_delay = 25;
  
  delay(std_delay);  //Discovered that some delay is needed after setting SCPCR
  FILL(0x00,0x00,0x25,0x7F,0xFF);  //Clear entire screen buffer (bright)
  delay(125);  //Fill command seems to take a long time to process, this is where a busy() wait function would be useful
  
  WRITE(0x00,0x29,0x55);  //Write 01010101 to the first byte in the active area of the display's RAM
  WRITEmore(0x66);  //Write 01100110 to the second byte in the display's RAM (not necessary but shows how to use WRITEmore)
  WRITEend();  //Since WRITE is a variable length command we must invoke the end function
  delay(std_delay);
  
  char RecievedData = 0x00;  //Create char to store from display's RAM read
  
  RecievedData = READ(0x00,0x2A);  //Read second char from display's RAM
  READend();  //Since READ is a variable length command we must invoke the end function
  delay(std_delay);
  
  FILL(0x25,0x31,0x25,0x56,RecievedData);  //Fill last row of display active area with the byte that was read in (should be 01100110)
  delay(std_delay);
  
  DISP_FULLSCRN(0x00,0x00);
  delay(5000);  //To let the viewer see the updated screen, the command actually took about 1.63 seconds to run at room temperature
  
  CLR_DISP_DRK();  //Use one of the CLR commands to demonstrate how much faster they run
  delay(5000);
  
  CLR_SECT_BRT(0x40,0x80);  //Another of the CLR commands the clears based on row number, clearing rows 64 to 128
  delay(5000);
  
  DISP_FULLSCRN(0x00,0x00);  //Refresh the display from RAM, showing that the information is still there (it is lost only if power is lost)

  while(1) {
  }
}

/*
 * Functions below are the basic commands available listed in the Kent 320x240 Display Datasheet (25085b_320x240_SPI_datasheet.pdf)
 * available at: http://www.kentdisplays.com/services/resources/datasheets/25085b_320x240_SPI_datasheet.pdf
 */

//Writes data to screen RAM starting at the target memory address, variable length command
//Note: the WRITEend() function must be called after sending the variable amount of Data Bytes
void WRITE(int HighAddress, int LowAddress, int Data) {
  select();
  spi_transfer(0x00);  //Write Command
  spi_transfer(HighAddress);  //High byte of the target memory address
  spi_transfer(LowAddress);  //Low byte of the target memory address
  spi_transfer(Data);  //The first value to be written, more may follow
}

//Optional if more data needs to be sent
void WRITEmore(int Data) {
  spi_transfer(Data);  //Send additional byte to screen
}

//Deselect screen so that it knows there is no more data to be sent
void WRITEend() {
  deselect();
}

//Fills multiple addresses in RAM with FillValue byte, starting with the First address and ending with the Last address, fixed length command
void FILL(int HighFirst, int LowFirst, int HighLast, int LowLast, int FillValue) {
  select();
  spi_transfer(0x01);  //Fill Command
  spi_transfer(HighFirst);  //High byte of the first address in the fill region
  spi_transfer(LowFirst);  //Low byte of the first address in the fill region
  spi_transfer(HighLast);  //High byte of the last address in the fill region
  spi_transfer(LowLast);  //Low byte of the last address in the fill region
  spi_transfer(FillValue);  //Fill Value
  deselect();
}

//Reads data from RAM starting with the target address, variable length command
//Note: the READend() function must be called after recieving the variable amount of Data Bytes
char READ(int HighAddress, int LowAddress) {
  select();
  spi_transfer(0x04);  //Read Command
  spi_transfer(HighAddress);  //High byte of the target memory address
  spi_transfer(LowAddress);  //Low byte of the target memory address
  spi_transfer(0x00);  //Dummy byte
  spi_transfer(0x00);  //Dummy byte
  return(spi_transfer(0x00));  //Final dummy byte, Screen will transfer data (on SO line) during reciept of this byte
}

//Optional if more data needs to be read
char READmore() {
  return(spi_transfer(0x00));  //Additional dummy byte, Screen will transfer data (on SO line) during reciept of this byte
}

//Deselect screen so that it knows there is no more data to be read
void READend() {
  deselect();
}

//Clear Bits sets bits to zero according to the Mask, a 1 in the mask affects that pixel, a 0 leaves the pixel unaffected, fixed length command 
void CLEAR_BITS(int HighAddress, int LowAddress, int Mask) {
  select();
  spi_transfer(0x08);  //Clear Bits Command
  spi_transfer(HighAddress);  //High byte of the target memory address
  spi_transfer(LowAddress);  //Low byte of the target memory address
  spi_transfer(Mask);  //Send Mask
  deselect();
}
 
//Set Bits sets bits to one according to the Mask, a 1 in the mask affects that pixel, a 0 leaves the pixel unaffected, fixed length command 
void SET_BITS(int HighAddress, int LowAddress, int Mask) {
  select();
  spi_transfer(0x09);  //Set Bits Command
  spi_transfer(HighAddress);  //High byte of the target memory address
  spi_transfer(LowAddress);  //Low byte of the target memory address
  spi_transfer(Mask);  //Send Mask
  deselect();
}

//XOR Bits toggles bits according to the Mask, a 1 in the mask affects that pixel, a 0 leaves the pixel unaffected, fixed length command 
void XOR_BITS(int HighAddress, int LowAddress, int Mask) {
  select();
  spi_transfer(0x0A);  //XOR Bits Command
  spi_transfer(HighAddress);  //High byte of the target memory address
  spi_transfer(LowAddress);  //Low byte of the target memory address
  spi_transfer(Mask);  //Send Mask
  deselect();
}

//Clear Display Bright clears entire screen to be bright including the border, fixed length command
void CLR_DISP_BRT() {
  select();
  spi_transfer(0x10);  //Clear Display Bright Command
  deselect();
}

//Clear Display Bright Inverted Border clears entire screen to be bright excluding the border, fixed length command
void CLR_DISP_BRT_IB() {
  select();
  spi_transfer(0x11);  //Clear Display Bright Inverted Border Command
  deselect();
}

//Clear Display Dark clears entire screen to be dark including the border, fixed length command
void CLR_DISP_DRK() {
  select();
  spi_transfer(0x12);  //Clear Display Dark Command
  deselect();
}

//Clear Display Dark Inverted Border clears entire screen to be dark excluding the border, fixed length command
void CLR_DISP_DRK_IB() {
  select();
  spi_transfer(0x13);  //Clear Display Dark Inverted Border Command
  deselect();
}

//Clear Section Bright clears rows of the screen (max of 120 rows) to be bright including the border, fixed length command
void CLR_SECT_BRT(int FirstRow, int LastRow) {
  select();
  spi_transfer(0x14);  //Clear Section Bright Command
  spi_transfer(0x00);  //Not enough rows to need high byte
  spi_transfer(FirstRow);  //First row in the clear region
  spi_transfer(0x00);  //Not enough rows to need high byte
  spi_transfer(LastRow);  //Last row in the clear region
  deselect();
}

//Clear Section Bright Inverted Border clears rows of the screen (max of 120 rows) to be bright excluding the border, fixed length command
void CLR_SECT_BRT_IB(int FirstRow, int LastRow) {
  select();
  spi_transfer(0x15);  //Clear Section Bright Inverted Border Command
  spi_transfer(0x00);  //Not enough rows to need high byte
  spi_transfer(FirstRow);  //First row in the clear region
  spi_transfer(0x00);  //Not enough rows to need high byte
  spi_transfer(LastRow);  //Last row in the clear region
  deselect();
}

I’ll have to continue the code in a reply…
Main things I’d like to hear people’s thoughts on:

  • How should I implement writing text to the display, drawing, etc.
  • How can I convert an image to a binary matrix of 1’s and 0’s so I can easily write splash screen graphics, etc.

Rest of the code…

//Clear Section Dark clears rows of the screen (max of 120 rows) to be dark including the border, fixed length command
void CLR_SECT_DRK(int FirstRow, int LastRow) {
  select();
  spi_transfer(0x16);  //Clear Section Dark Command
  spi_transfer(0x00);  //Not enough rows to need high byte
  spi_transfer(FirstRow);  //First row in the clear region
  spi_transfer(0x00);  //Not enough rows to need high byte
  spi_transfer(LastRow);  //Last row in the clear region
  deselect();
}

//Clear Section Dark Inverted Border clears rows of the screen (max of 120 rows) to be dark excluding the border, fixed length command
void CLR_SECT_DRK_IB(int FirstRow, int LastRow) {
  select();
  spi_transfer(0x17);  //Clear Section Dark Inverted Border Command
  spi_transfer(0x00);  //Not enough rows to need high byte
  spi_transfer(FirstRow);  //First row in the clear region
  spi_transfer(0x00);  //Not enough rows to need high byte
  spi_transfer(LastRow);  //Last row in the clear region
  deselect();
}

//Display Fullscreen triggers a full screen update from a specified image buffer in the onboard image RAM, fixed length command
void DISP_FULLSCRN(int HighAddress, int LowAddress) {
  select();
  spi_transfer(0x18);  //Display Fullscreen command
  spi_transfer(HighAddress);  //High byte of the target memory address
  spi_transfer(LowAddress);  //Low byte of the target memory address
  deselect();
}

//Display Partscreen triggers a partial per row screen update from a specified image buffer in the onboard image RAM, fixed length command
void DISP_PARTSCRN(int HighAddress, int LowAddress, int FirstRow, int LastRow) {
  select();
  spi_transfer(0x19);  //Display Partscreen command
  spi_transfer(HighAddress);  //High byte of the target memory address
  spi_transfer(LowAddress);  //Low byte of the target memory address
  spi_transfer(0x00);  //Not enough rows to need high byte
  spi_transfer(FirstRow);  //First row in the clear region
  spi_transfer(0x00);  //Not enough rows to need high byte
  spi_transfer(LastRow);  //Last row in the clear region
  deselect();
}

//Sleep puts the display module in the low power sleep mode, fixed length command
void SLEEP() {
  select();
  spi_transfer(0x20);  //Sleep command
  deselect();
}

//Reset triggers a software reset of the display module, fixed length command
void RESET() {
  select();
  spi_transfer(0x24);  //Reset command
  deselect();
}

/*
 * Helpful notes:
 * Screen has a 32kB RAM allowing for the storage of 3.33 full screen buffers
 */ 

/*
 * Below are functions for the SPI interface
 */

void select() {
  digitalWrite(SLAVESELECT,LOW);
}

void deselect() {
  digitalWrite(SLAVESELECT,HIGH);
}

char spi_transfer(volatile char data)
{
  SPDR = data;                    // Start the transmission
  while (!(SPSR & (1<<SPIF))){    // Wait for the end of the transmission
  }
  return SPDR;                    // return the received byte
}

You should take the KS0108 display driver library, and use it as a template to develop a separate library for this GLCD. Then, develop a wrapper library for both...? I am not sure what the best way to go with this would be, but it would be nice if there was a "one stop shop" library for GLCDs like there is for (most) text LCDs - so that code could remain mostly portable across LCDs.

Great job on the first step!

:)

I agree, good idea! I've been noticing huge support and ease of use with 16x2, 20x2, 20x4, etc. text LCD's but every project I come up with seems to have little use for them (I like GUI's and pretty pictures)...

With the micro processing world getting into higher level code and hardware I think this is the next step! Now to figure out how to implement this ::)

Is your driver working for the small kent display ? what need to be modified ?

thx

Is your driver working for the small kent display ? what need to be modified ?

These low level commands don't need any modification to run on the small Kent display! Now that I'm working on higher level functions I will try to add an easily modified #define number for the physical amount of pixels w x h. The only difference is really just a lower amount of memory addresses.

I just realized I should note, the example code DOES need modification but the actual commands do not, if there is interest I can modify the addressing in the example to work with the smaller display 8-)

I have the code working to display a full screen image. You can find it here: http://www.rustynailworkshop.com/Projects/Entries/2010/8/9_Arduino_Powered_Kent_Display.html

Enjoy!