ILI9341 LCD Controller

The ILI9341 is a QVGA (Quarter VGA) driver integrated circuit that is used to control 240×320 VGA LCD screens.   The ILI9341 is commonly found in low cost LCD screens that can be purchased from various vendors on the web.  The following information is written for the ER-TFTM028-4 from buydisplay.com.  While the information targets the ER-TFTM028-4, it is applicable to most ILI9341 based LCDs.

A good question to ask yourself is “Why do we use the ILI9341 in the first place?”  To answer that, let’s consider how many pixels are in a QVGA screen.  A QVGA screen is an array of 240 x 320 pixels.  That’s 76800 pixels.  Each pixel can be set to one of  sixty four thousand different colors.  That’s a lot of information for a single microcontroller to handle.  In fact, it would be too much information for most microcontrollers to handle and still perform other tasks that are critical to the overall operation of the system.  The ILI9341 acts as a dedicated video processor that offloads some of the video related tasks from the main microprocessor.

Pixel Organization

Before we look at how to communicate between the ILI9341 and a microprocessor, let’s examine how the pixels in the LCD screen are organized.  In portrait mode, the LCD is 240 pixels wide and 320 pixels high.  Each pixel is addressable by supplying  X and Y coordinates (x,y).

The image below demonstrates where each pixel is located on the ECE353 development board.

pixellayout

The 0,0 pixel is located in the upper left corner of the LCD.  The red square is located at (318,1) and the blue square is located at (1,239).

Note:  The orientation of which pixel is (0,0) is configurable in software, but the configuration above is the one that we will use in class.

Pixel Format

Each individual pixel in the 240×320 display holds data used to represent what color the pixel should be.  The ILI9341 supports several different color modes, but we will use a 16-bit color mode.  Each pixel therefore needs to be written with 16-bits of data that represent its current color.   Bits 15:11 are used to set the red intensity of the pixel,  Bits 10:5 are used to set the green intensity of the pixel, and bits 4:0 are used to set the blue intensity of the pixel.

Supported Interfaces

The ILI9341 can be interfaced with using either a serial interface (SPI) or 8, 9, 16, or 18bit parallel interfaces.  We are going to use an 8-bit parallel interface to communicate between the microprocessor and the ILI9341.  The parallel interface will allow us to render images at a faster rate than the SPI interface.

The parallel interface is comprised of an 8-bit data bus and 4 control signals.

Signal Description

D[7:0]

The data interface is an 8-bit parallel interface that is used to supply the values that will be written to a specific command in the ILI9341 command set.

CSX

The chip select signal is active low.  The ILI9341 will only read/write data from/to the DATA[7:0] when /CS is low.

WRX

The write pin is an active low signal.  When /WR is set to logic 0, the value on DATA[7:0] is written to the currently selected command.

RDX

The read pin is an active low signal.  When /RD is set to logic 0, the ILI9341 will place the contents of the currently selected command on DATA[7:0].  We will not read data from the ILI9341, so we will set this signal to a 1.

D/CX

The data/command pin allows the microcontroller to specify if the data on DATA[7:0] is a command used to change the behavior of the LCD or data that will be written to the currently active command.

ILI9341 Command Set

The ILI9341 has a set of commands that are exposed to the microprocessor using one of these external interfaces.  Below is a partial snippet of those commands ( You can find the a complete list of available commands on page 83 of the ILI9341 data sheet.)

register_snippet

The behavior of LCD, along with the data being displayed, can be altered by writing data to the currently active command.   Each command holds a variable amount of data that allows the MCU to change the behavior of the LCD display.  Section 8.2 of the ILI9341 data sheet provides the details of what each register does, and which bits within a command set specific behaviors of the LCD.

The supported commands act like a set of registers that are external to the microprocessor.  We can access these “commands” using the GPIO pins on the Tiva Launchpad by setting the values of the GPIO pins connected to the data bus and the 4 command signals to match the requirements set by the ILI9341 datasheet.

Writing Commands

Before we write any data to the ILI9341, we need to set the active command.  Page 31 of the ILI9341 data sheet provides the information we need to set command.  The image below is a simplified version of how to set the current command to be Column Address Set command.

commandwaveform       The following steps are required to change the currently active command.

  1. The chip select signal (CSX) transitions from high to low.
  2. The Data/Command signal (D/CX) transitions from high to  low to indicate that the value on the data bus represents the value of the command that will be modified.
  3. The value of the command being set should be placed on the data bus(D[7:0]).  In this case, since we are writing to the Column Address Set command, we need to set this value to 0x2A.  The value of 0x2A is determined by looking at the last column of the Command List that starts on page 83 of the ILI9341 datasheet.
  4. The write signal (WRX) transitions from high to low.
  5. The write signal (WRX) transitions from low to high.  The commandis clocked in on the rising edge of the write signal.
  6.  The Data/Command signal (D/CX) transitions from low to high.
  7. The chip select signal (CSX) transitions from low to high.

Using these sequence of steps, the microcontroller can set the currently active command to any of the supported ILI9341 commands.

So how does the microcontroller set transition CSX from high to low?  The CSX signal is connected to a GPIO pin on the microcontroller.  When the the CSX pin needs to be at a logic level of 0, the microcontroller will set its own GPIO pin to a value of 0.  When the the CSX pin needs to be at a logic level of 1, the microcontroller will set its own GPIO pin to a value of 1.  In fact, all the signals for the LCD are connected to GPIO pins on the microprocessor, so if we write to the correct GPIO pins, we can generate the required wave forms to interface with the ILI9341.

Writing 1 Byte of Data

Once the current command has been set, the microcontroller needs to write the value of the command to the ILI9341.  The following image shows you how a single byte of data is written to the current command.data_8bit_waveformThe following steps are required to write data to the currently active command.

  1. The chip select signal (CSX) transitions from high to low.
  2. The Data/Command signal (D/CX) must remain high  indicate that the value on the data bus represents the data being written to the currently active command.
  3. The data being written to the current command  is placed on the data bus(D[7:0]).  Depending on which command you are writing to, this value will change.  Consult the datasheet for each command to determine what the value should be.
  4. The write signal (WRX) transitions from high to low.
  5. The write signal (WRX) transitions from low to high.  The command data is clocked in on the rising edge of the write signal.
  6. The chip select signal (CSX) transitions from low to high.

Writing 2 Bytes of Data

The following image shows you how  two bytes of data is written to the current command.

data_16bit_waveform

  1. The chip select signal (CSX) transitions from high to low.
  2. The Data/Command signal (D/CX) must remain high  indicate that the value on the data bus represents the data being written to the currently active command.
  3. The the 1st byte of data is placed on the data bus(D[7:0]).
  4. The write signal (WRX) transitions from high to low.
  5. The write signal (WRX) transitions from low to high.
  6. The the 2nd byte of data is placed on the data bus(D[7:0]).
  7. The write signal (WRX) transitions from high to low.
  8. The write signal (WRX) transitions from low to high.
  9. The chip select signal (CSX) transitions from low to high.

You’ll notice that when writing multiple bytes of data to the current command, the ILI9341 will automatically increment its internal address pointer after each byte is written.

Defining LCD Active Area

Before you send pixel data to the ILI9341, you will need define an active area for the LCD.  Setting the active area tells the ILI9341 which pixels you are going to modify.

You can set the active area by setting the Column Address and Page Address.   After the active command has been set to Column Address Set, you will need to send two bytes of data for the starting column address and two bytes of data for the ending column address.

The following example shows how to update the Column Address Set command with a starting column address of 0 and an ending column address of 239.column_address_set-00ef In addition to setting the active column, you will also need to set the Page Address Set command.  You can think of the page start and stop addresses as the active row numbers.  The following example sets  the starting page address as 0 and the ending page address as 319.

page_address_set

 

By setting the starting column address to 0, the ending column address to 239, the starting page address to 0, and the ending page address to 319, the microprocessor can now begin to write data to every pixel of the 240×320 LCD.

Drawing an Image

Often times, you do not want to update every pixel on the LCD.  Instead, you will only want to update a small fraction of the pixels.  When you want to draw an image to the screen, you will make use of the ability to define the active region that you want to update.   As an example, lets say you wanted to draw the image below.

draw_image

Based on the size of this image, you would want to set the active area of the LCD to be Column(1,6) and Page (1,6).   The code below demonstrates what this might look like.

  lcd_write_cmd(LCD_CMD_SET_COLUMN_ADDR);
  lcd_write_data_u16(1);
  lcd_write_data_u16(6);

  lcd_write_cmd(LCD_CMD_SET_PAGE_ADDR);
  lcd_write_data_u16(1);
  lcd_write_data_u16(6);

Once the active region is configured, we need to tell the ILI9431 that we are ready to send it data for the pixels.  This can be done by setting the active command to Memory Write (0x2C).  Once the the Memory Write commd is the active command, the ILI9341 treats any data it receives as pixel data.

Internally, the ILI9143 maintains the address of the current pixel by initializing a page and column counter to be equal to the starting page and column addresses.  Each time the ILI9341 receives 16-bits of data, it will update the pixel addressed by the page and column counter.  After updating the pixel, the ILI9341 will increment the column counter.  The next time that a 16-bit value is received by the ILI9341, the value will be written to the pixel that is to the right.

The column counter will be incremented until the column counter is is greater than the ending column that was defined by the  Column Address Set command.  When this happens, the page counter is incremented by one and the column counter is reset to the starting column address.

If we numbered the pixels in the previous image, this would be the order in which the pixels were written to the LCD.

draw_image-numbered

 

In order to turn the pixels the correct color, you would need to send the correct color values to the LCD. For the image above, your code would look similar to this:

lcd_write_cmd(LCD_CMD_MEMORY_WRITE);
lcd_write_data_u16(0x0000); // Pixel 00 BLACK
lcd_write_data_u16(0x0000); // Pixel 01 BLACK
lcd_write_data_u16(0x0000); // Pixel 02 BLACK
lcd_write_data_u16(0x0000); // Pixel 03 BLACK
lcd_write_data_u16(0x0000); // Pixel 04 BLACK
lcd_write_data_u16(0xF800); // Pixel 05 RED
lcd_write_data_u16(0x0000); // Pixel 06 BLACK
lcd_write_data_u16(0x0000); // Pixel 07 BLACK
lcd_write_data_u16(0x0000); // Pixel 08 BLACK
lcd_write_data_u16(0x0000); // Pixel 09 BLACK
lcd_write_data_u16(0xF800); // Pixel 10 RED
lcd_write_data_u16(0xF800); // Pixel 11 RED
lcd_write_data_u16(0x0000); // Pixel 12 BLACK
lcd_write_data_u16(0x0000); // Pixel 13 BLACK
lcd_write_data_u16(0x0000); // Pixel 14 BLACK
lcd_write_data_u16(0xF800); // Pixel 15 RED
lcd_write_data_u16(0xF800); // Pixel 16 RED
lcd_write_data_u16(0xF800); // Pixel 17 RED
lcd_write_data_u16(0x0000); // Pixel 18 BLACK
lcd_write_data_u16(0x0000); // Pixel 19 BLACK
lcd_write_data_u16(0xF800); // Pixel 20 RED
lcd_write_data_u16(0xF800); // Pixel 21 RED
lcd_write_data_u16(0xF800); // Pixel 22 RED
lcd_write_data_u16(0xF800); // Pixel 23 RED
.
.
.