Adding A Colour Display to an Embedded ProjectTraditionally, most projects of an embedded nature have either used alphanumeric LCD displays, or more recently, black and white graphics LCD modules. They are simple to operate and relatively cheap. Thanks to the mobile market, colour LCD displays have become available, and at low cost. I want to expand my GNU graphics library for embedded systems, so I acquired a suitable LCD module (with touchscreen) for the job.
Choosing a DisplayMost colour displays have been the cellphone size i.e. 80 x 60 pixels and somewhat bigger. A decent size, for use in the industrial automation and kinds of fields I work in would have to be at least 320 x 240, so I acquired one, via a local distributor, manufactured by WINSTAR, which is a TFT with a built in graphics controller. Most TFT panels are just a video interface i.e. HSYNC, VSYNC and a pixel bus. This module, includes a graphics controller, the SSD1963 (which I have reasons to suspect, is what was running in the Nokia N70). The module I chose was the WF35DTIBCDE, a 3.5 inch TFT panel, with controller and touchscreen.
The first problemMost of these displays have what is known as a FFC connector. FFC stands for (Flex Foil Connector) which is a flexible plastic strip with conductive traces, which plugs into thin slotted connectors. To counteract this problem and to provide a decent platform to hold the thing together, I usually make
breakout boards. So, just before Christmas, I quickly had one made, just as the PCB manufacturer was closing for the holidays. I assembled it during my holidays, and it looks like this:

The above board provides a connection point to normal wires, which I can then wire into a development board, or into a breadboard as in this case I will be doing. The interface to the controller is in the form of an 8-bit databus and control lines, and power. The display operates at 3.3V and includes a backlight. The interface is designed to be attached to the Intel Architecture i.e. 80x86, 8051 and virtually all Intel processors and microcontrollers, and mapped into the memory space.
Bus InterfaceMost embedded microcontrollers today do not have exposed busses, and consequently all memory is inside the chip. This is done for reasons of economy and to make it easy to comply with CE mark requirements. They do however have copious numbers of I/O ports. These I/O ports, are mostly 8 bits wide and each bit can be configured to be either an input, or an output. Some port pins can be configured to connect to internal peripherals such as the UARTs, timers, SPI/I2C and other interfaces, not to mention A/D and D/A converters. Now, the lack of exposed bus, presents a problem at first glance, however, not strictly so. The solution to drive this kind of device from I/O ports is to use the tried-and-tested method of
bit-banging. So for this project we will bit-bang the x86 bus.
The LCD therefore has the following signals:
RESET = An active low signal, when pulled to logic 0, the LCD controller logic is reset
RD = READ. When pulled low, the LCD controller will output data onto the bus, which can then be read by the microprocessor
WR = WRITE. When pulled low, the LCD controller will accept data from the microprocessor and store it internally.
CS = CHIP SELECT. When asserted low, the LCD controller will respond to the microprocessor. This is used traditionally with
address decoding logic to map the display into the microprocessor's memory or I/O map.
RS = REGISTER SELECT. This pin if tied low (0) allows the microprocessor to access the control registers and command register. If tied
high(1) it allows the microprocessor to access the graphics memory buffer
D0 ~ D7 = Databus. This is the bi-directional data bus used to read/write data from the graphics controller.
Now, to get this to work, I have to connect it to my choice of microcontroller. I have a Freescale MC9S08DZ60 available, and it has some I/O ports. So what we do is connect it up as follows:
D0 ~ D7 -> PTA0 ~ PTA7. All data pins to the entire set of PORT A pins. This means a byte can be written in one go to the LCD. The control signal pins can be connected as desired. I randomly wired them up as follows:
CS -> PTE2 (PORT E bit 2)
WR -> PTE3
RD -> PTE4
RS -> PTE5
RESET ->PTD0 (PORT D bit 0)
Now, once all that was done, it looked like this:

Now, let's take a closer look:

The power supply is 3.3V. Since it was Dec holidays and the stores are closed, I hacked this item out of a MultiChoice DSD990 STB.
(I installed it into a packaging case of a hard drive to protect myself or my son from being electrocuted)
The In-Circuit Debugger connects via USB to my PC and integrates into Freescale CodeWarrior (formerly Metrowerks CodeWarrior).
Getting StartedNow, the time is to get this baby going. Let's start writing some code to perform some basic initialization.
The first step is to create an empty project in Codewarrior, using the wizard. Once that's done lets start doing some work.
First we define our hardware in main.c
/* Hardware Definitions */
#define LED PTDD_PTDD1
#define CS PTED_PTED2
#define WR PTED_PTED3
#define RD PTED_PTED4
#define RS PTED_PTED5
#define RESET PTDD_PTDD0
To then correctly emulate the 80x86 databus, we need to set up the ports correctly when the micro starts, let's just do that in main()
void main(void) {
InitCOP(); /* Disable watchdog for now */
InitPLL(); /* Set up PLL and clock CPU @ 40MHz */
/* Initialise I/O ports */
InitPTA(0xFF, 0x00);
InitPTE(0x3C, 0x3C);
InitPTD(0x03, 0x01);
InitRTC(0x13); /* Initialise the RTC timer for 10mS */
TIMER_COUNTER = 0;
PRESCALER = 0;
EnableInterrupts; /* Enable Interrupts (macro) */
And now, the magic part: let's create the workhorse function calls to do the bit-banged bus interface:
void Write_Command (unsigned char command){
RD = 1; /* Ensure read signal is high */
RS = 0; /* RS = 0, access command register */
WR= 0; /* Pull WR signal low */
CS = 0; /* Pull CS signal low, LCD controller is now in write cycle */
PTAD = command; /* Present data to LCD controller */
CS = 1; /* Pull CS high to end "bus cycle" */
WR= 1; /* Pull WR to finish the end of the "bus cycle" */
}
void Write_Data (unsigned char data1){
RD = 1;
RS = 1;
WR = 0;
CS = 0;
PTAD = data1;
CS = 1;
WR= 1;
}
Now that we have those two routines in place, we can start with initialization code to get the LCD set up correctly. That will be covered in Part 2
