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.
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.)
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.
The following steps are required to change the currently active command.
- The chip select signal (CSX) transitions from high to low.
- 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.
- 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.
- The write signal (WRX) transitions from high to low.
- The write signal (WRX) transitions from low to high. The commandis clocked in on the rising edge of the write signal.
- The Data/Command signal (D/CX) transitions from low to high.
- 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.The following steps are required to write data to the currently active command.
- The chip select signal (CSX) transitions from high to low.
- 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.
- 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.
- The write signal (WRX) transitions from high to low.
- The write signal (WRX) transitions from low to high. The command data is clocked in on the rising edge of the write signal.
- 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.
- The chip select signal (CSX) transitions from high to low.
- 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.
- The the 1st byte of data is placed on the data bus(D[7:0]).
- The write signal (WRX) transitions from high to low.
- The write signal (WRX) transitions from low to high.
- The the 2nd byte of data is placed on the data bus(D[7:0]).
- The write signal (WRX) transitions from high to low.
- The write signal (WRX) transitions from low to high.
- 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. 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.
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.
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.
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 . . .