How to drive an LCD with KS0108 controller

I’m involved into developing a little instrument to display graphs and numerical values with characters of various sizes. I’m pretty expert with the classic controller standard “HD44780” for alphanumeric LCDs.  But this time I need something more flexible such as a graphic LCD, with the possibility of freely control every pixel of the screen. If you are interested in the topic I really recommend to thoroughly understand the operating logic, so you can freely create high-performance graphics to suit your needs… and also obviously having fun with tech stuff!

When you choose the correct device for you application, you must consider:

  • connector type
  • controller chip
  • dimensions in pixels
  • retroillumination color
  • LCD contrast regulation voltage

To briefly summarize these parameters, I chose a product that have connector holes spaced with 2.54mm as standard connectors, so I can easily pay off the cables / connectors, and make everything as flexible as a first prototype needs.

I chose the KS0108 controller; because looking online I found a lot of documentation and datasheet about it, so, i guess also a lot of support if in need.

king about the pixel size, my choice fell on the 128×64, which is internally divided into two separate controllers but with the same connections: by sending the information to the first controller i will work on the 64×64 pixels box at the top, while the second controller works on the bottom 64x64px box. For example, if i had purchased a 192×64 pixels, i would find three separate controllers. In fact the management is easier than you think, you will see below.

I will not use the backlight because I have to keep the consumption very low, but if you need it, in the datasheet the white backlight consumes much less compared to other colors (however life in hours seems less!): To be considered.

As a final point, speaking about the contrast adjustment voltage, some LCDs require a negative voltage: this becomes a problem because it’s not so easy to get a negative voltage with a prototype board if you do not have the necessary components available, thus not seems appropriate to engage a bench supply just to obtain the negative voltage in question. The model I have chose, despite this characteristic, is equipped with an internal DC / DC converter to provide the negative voltage required, simply connecting it to a contrast trimmer and then to the contrast pin… and that’s it! … Anyhow I had some trouble with the contrast because I found out, after a tedious and meticulous research that the reset pin needs to be connected to VCC through a 10kohm pull-up resistor at least, to limit the current. Initially I was driving the LCD with RST pin directly connected to VCC and this was cause of an “unstable contrast” issue.

With the availability of different FRDM_KL25Z boards I used as a programmer for ARM micro, I decided to exploit one to drive the graphic display in question. As explained in this article KL25Z as an OpenSDA programmer I first of all prepared the board so that it can be easily programmed via USB (with the J6 jumper inserted to connect the SDA clock with the clock of the onboard micro!).

Before connecting the LCD to the board I checked into their details if they are all freely controllable with digital I/O:

  • CS1, CS2: to enable controller 1 or controller 2, the two half of the screen;
  • DB0-DB7: pins enabled to describe the 8 bits of a byte sent to the l’LCD;
  • EN: enable pin, needs to be enabled to inform the controller to receive commands or data;
  • RW/DI: to set the communication mode (reading, writing, command);
  • RST: reset pin, needs to be high level (5v) to enable the LCD controllers;
  • Vee: LCD contrast, need to be connected to Vo through a trimmer, see below;
  • Vo: Voltage ouput from the internal DC/DC converter.

Then I have connected the LCD to the board checking the KL25z micro datasheet if some pins are open drain/collector (in that case to obtain a high logic level is necessary to add a pull-up resistor) or, as in the case of pin “PTD1” being in common with the RGB LED mounted on the board … I would avoid these mistakes if possible.

Graphical LCD pin diagram

In this scheme you can see all the necessary connections, but keep in mind the pullup resistor on the RST pin. I recommend: first of all make sure that your Vcc voltage does not exceed the limits specified by the LCD datasheet, and then double check if you have properly connected all the pins, otherwise you risk to irreparably compromise the LCD! At this point I just have to write down some code! Without going into specific detail of CodeWarrior with ProcessorExpert and arm-gcc compiler, I will explain how to properly communicate with the KS0108 controller so you will be able to use it regardless of the language, compiler or mcu used.

First of all the controllers need to be initialized, and to do that we must send to each one some commands. Now we will see some code to communicate with the LCD.

In my case, using CodeWarrior, i’ve configured every pin in ProcessorExpert directing them as shown in the diagram above, and then writing two functions to set them as outputs and manage the logic levels of the single bits as needed.

// this function sets every pin from D0 to D7 as outputs
void DATA_DIR_OUT() {

// this function sets every output data pin with 
// the respective bit of the byte that we want to sent
void WR_DATA(char lcddata) {
	LCD_D0_PutVal((lcddata) >> 1);
	LCD_D1_PutVal((lcddata >> 1) & 1);
	LCD_D2_PutVal((lcddata >> 2) & 1);
	LCD_D3_PutVal((lcddata >> 3) & 1);
	LCD_D4_PutVal((lcddata >> 4) & 1);
	LCD_D5_PutVal((lcddata >> 5) & 1);
	LCD_D6_PutVal((lcddata >> 6) & 1);
	LCD_D7_PutVal((lcddata >> 7) & 1);

// send simply a little pulse through the EN pin
void send_enable() {
	Delay100us(1); // delay di 1 microsecondo

// controller selection (0-1)
//    0               1
// CS1 = 1         CS1 = 0
// CS2 = 0         CS2 = 0
void ChipSelect(bool chip)
      if (chip == 0)
      if (chip == 1)

glcd output bitsThe result is to transmit the byte bit by bit distributing it on pins DB0-DB7. This is called normally parallel transmission. In addition to transmitting the bits we need also to tell the controller what type of operation we are doing. Leafing through the datasheet of the LCD I found all the information needed to drive the controller.

control bits e data bits
I removed from this pattern all the other functions, to show only the interesting ones: “Display on/off” to initialize the LCD and “Write data” to send a byte. I have also divided the first row by the color of the data bits and the control bits, respecting the colors of the wiring diagram. The “EN” enable pin is missing, because the latter is very simple: first you prepare the check bits and the data bits, then a small pulse through the EN bit, to say to the LCD “ok I’m ready, listen to me”.

In summary, to activate the display must be set the RW pin to 0, the DI pin to 0, and next thing, send the BYTE “3F” followed by an ENABLE pulse:

void InitDisplay(void)

Now, if we want to turn on a pixel on the screen, it would be enough to send one byte of data. But a moment … one byte? And if I want to light up a single bit? Right, but we’ll talk about later. The question now is: where would appear this pixel? We have not created any function to set the “cursor” position. Let us return then to the datasheet and then add some detail to the functions table:

ks0108 goto xy istructions

So we have a command called set page that corresponds to the X axis, and a set address command that corresponds to the Y axis. Both seem to work with RW and DI to zero. Differently, set address is 0 to 63 while set page only goes from 0 to 7 (including zero, then only 8 positions). This is because on the X axis we write 8 bits at a time, our byte! Obviously, in this way we do not need 64 positions, but 64/8 or 8.

Now we can see our position functions and write function for a byte:

// Page select (x axis divided by eight positions for 8 bit blocks)
void PageSelect(unsigned char page)
        // RW e DI like suggested by table
        // joining "B8" or 10111 like suggested by table
        // with the 3 bits of the page number (0 to 7)
	WR_DATA(0xB8 | page);

// row select (y axis divided by 64 positions for single bits)
void RowSelect(unsigned char row)
        // RW e DI like suggested by table
	WR_DATA(0x40 | row);

Done that, whe need only to send a first byte to the screen, at the 0,0px position, simply changing the main.c file:

InitDisplay(void);  // LCD initialization
ChipSelect(0);      // obviously chip 0 selection
PageSelect(0);      // x = 0
RowSelect(0);       // y = 0
WriteByte(0xFF);    // lighting up 8 pixel 
while(1);           // infinite waiting loop

If you did everything correctly, you’ll see a 8px line. Changing 0xFF with for example 0x01 will switch on only one pixel, while 0xAA will show alternating pixels. You can also try changing the values of PageSelect and RowSelect, to see what effect does on the pixel positions.

glcd byte sent

From this initial result you can start to develop all graphics functions such as writing characters or displaying bitmap images, but I will talk about this in the next article!


  1. I’ve been browsing online more than 3 hours today, yet I never found any interesting article like yours. It is pretty worth enough for me. In my view, if all website owners and bloggers made good content as you did, the net will be a lot more useful than ever before.|

Leave a Reply

Your email address will not be published. Required fields are marked *