stm32plus: ILI9481 TFT driver

The code presented in this article requires a minimum of version 3.0.0 of my stm32plus library.

The TFT panel

The ILI9481 is a driver IC for 480×320 (HVGA) TFT panels. These panels are typically found in mobile phones (for example the iPhone 3G although the display in that phone probably does not have a controller) and other portable devices. HVGA panels contain double the number of pixels of the common 320×240 (QVGA) panels.

I picked up one of these panels on ebay. It didn’t come with a breakout board so I had to create one of my own.


The bare 3.5″ panel with the FPC tail

I could have tried to track down a connector for the 37-pin, 0.5mm FPC connector but decided against it and just soldered the FPC directly to the adaptor board that the e-bay seller included with the display.

Pinout

The data sheet for the panel included the pinout. It’s a familiar 8080-style interface that is easily connected to the FSMC of the STM32 microcontroller.


The pinout

The pins include the outputs from the resistive touch screen. It would be possible to create a breakout board that includes the popular ADS7843 to handle the decoding but I’m not going to do it here.

Backlight

The datasheet also included a schematic for the backlight. Since this is a relatively large 3.5″ panel it has a total of 6 white LEDs connected in parallel to act as a backlight.


The backlight is a parallel LED array

Having the LEDs connected in parallel means that I can power them directly from a 3.3V supply without the need for a step-up DC-DC converter that would be required had the LEDs been connected in series.

Driving the LEDs at 20mA means that my voltage regulator is going to have supply 120mA to the backlight. It’s always worth double-checking your development board’s specification at times like these to ensure that the voltage regulator can cope.

If you’re creating a real application that has a backlight like this then you should be using a dedicated constant-current backlight controller. OnSemi have a good range to choose from. These devices all offer PWM control of their output as well as the basic function of providing a constant current to the LED array.


All hooked up and running the usual demo

My demo sequence exercises some of the common functions used in graphics operations such as rectangle, line and ellipse drawing as well as text rendering and hardware scrolling when the panel supports it (the ILI9481 does).

The ILI9481

The datasheet for the ILI9481 is readily available on the internet. It’s written to the high standard that I’ve come to expect from ILITek making the job of writing a driver really straightforward.

The panel supports 16 bit colour (5-6-5 format) and 18-bit colour (6-6-6 format). The STM32F103 can easily drive either colour format at a fast pace. I’ve come to realise that the observable difference between 64K and 262K colours is pretty low and generally I’ll use 64K for the extra speed that it offers.




Data transfer protocol for the 16-bit interface (click for larger)

Using a 16-bit interface we can transfer a 64K colour pixel in one operation. A 262K colour pixel costs an extra transfer per-pixel and therefore halves the speed just for those extra 2 bits.

STM32 wiring

Here’s the wiring mapping table that you’re going to need if you intend to hook one of these up to an STM32 MCU. You’re free to change the address line that you use for RS, the bank selector that you use for /CS and the GPIO pin for RESET.

LCD signal STM32 port port function
D0 PD14 FSMC_D0
D1 PD15 FSMC_D1
D2 PD0 FSMC_D2
D3 PD1 FSMC_D3
D4 PE7 FSMC_D4
D5 PE8 FSMC_D5
D6 PE9 FSMC_D6
D7 PE10 FSMC_D7
D8 PE11 FSMC_D8
D9 PE12 FSMC_D9
D10 PE13 FSMC_D10
D11 PE14 FSMC_D11
D12 PE15 FSMC_D12
D13 PD8 FSMC_D13
D14 PD9 FSMC_D14
D15 PD10 FSMC_D15
/WR PD5 FSMC_nWE
/RD PD4 FSMC_nOE
/CS PD7 FSMC_nE1
/RESET PE1 GPIO
RS (D/CX) PD11 FSMC_A16

stm32plus driver

stm32plus 2.0.0 comes with an updated ILI9481 demo application. Here’s an extract from the source code that shows how to set it up.

#include "config/stm32plus.h"
#include "config/display/tft.h"

using namespace stm32plus;
using namespace stm32plus::display;

class ILI9481Test {

  protected:
    typedef Fsmc16BitAccessMode<FsmcBank1NorSram1> LcdAccessMode;
    typedef ILI9481_Landscape_64K<LcdAccessMode> LcdPanel;

    LcdAccessMode *_accessMode;
    LcdPanel *_gl;
    Font *_font;

  public:
    void run() {

      // reset is on PE1 and RS (D/CX) is on PD11

      GpioE<DefaultDigitalOutputFeature<1> > pe;
      GpioD<DefaultFsmcAlternateFunctionFeature<11>> pd;

      // set up the FSMC with RS=A16 (PD11). The 60Mhz FSMC bus on the F4 needs
      // slower timings.

#if defined(STM32PLUS_F1)
      Fsmc8080LcdTiming fsmcTiming(0,2);
#else
      Fsmc8080LcdTiming fsmcTiming(2,10);
#endif

      _accessMode=new LcdAccessMode(fsmcTiming,16,pe[1]);

      // declare a panel

      _gl=new LcdPanel(*_accessMode);

      // apply gamma settings

      ILI9481Gamma gamma(0,0xf3,0,0xbc,0x50,0x1f,0,7,0x7f,0x7,0xf,0);
      _gl->applyGamma(gamma);

      // clear to black while the lights are out

      _gl->setBackground(0);
      _gl->clearScreen();

      // create the backlight on timer4, channel2 (PD13)

      DefaultBacklight backlight;

      // fade up to 100% in 4ms steps

      backlight.fadeTo(100,4);

      // create a font

      _font=new Font_VOLTER__28GOLDFISH_299;
      *_gl << *_font;

Let’s look at the code in a little more detail.

Includes and namespaces

#include "config/stm32plus.h"
#include "config/display/tft.h"

using namespace stm32plus;
using namespace stm32plus::display;

Firstly we need to include the headers that define the classes we’re going to use. Secondly, since all of stm32plus lives either in the stm32plus namespace or a sub-namespace (in this case stm32plus::display) we will import them into the global namespace to make the declarations of the objects less clumsy looking.

FSMC access mode and reset

    typedef Fsmc16BitAccessMode<FsmcBank1NorSram1> LcdAccessMode;
    typedef ILI9481_Landscape_64K<LcdAccessMode> LcdPanel;

    LcdAccessMode *_accessMode;
    LcdPanel *_gl;
    Font *_font;

  public:
    void run() {

      // reset is on PE1 and RS (D/CX) is on PD11

      GpioE<DefaultDigitalOutputFeature<1> > pe;
      GpioD<DefaultFsmcAlternateFunctionFeature<11>> pd;

      // set up the FSMC with RS=A16 (PD11). The 60Mhz FSMC bus on the F4 needs
      // slower timings.

#if defined(STM32PLUS_F1)
      Fsmc8080LcdTiming fsmcTiming(0,2);
#else
      Fsmc8080LcdTiming fsmcTiming(2,10);
#endif

      _accessMode=new LcdAccessMode(fsmcTiming,16,pe[1]);

We use an ‘access-mode’ class to control how we communicate with the panel. Here we initialise it to use the FSMC in 16-bit mode with A16 as the RS line (PD11). We will also be wiring up the panel’s RESET line to PE1.

We declare an Fsmc8080Lcdtiming object that takes care of the timing details. The two parameters are the address setup and data setup times in HCLK cycles. At full speed the STM32F1 has a 36MHz FSMC bus and the STM32F4 has a 60MHz bus. Therefore the timings may be different for each MCU if the bus is faster than the panel.

To determine the value of these parameters you need your panel’s data sheet and the equations in the ST Microelectronics application note AN2790 (google it). Alternatively you can just start with some large-ish numbers and decrease until the timings get too tight and the panel doesn’t respond!

Declare a panel

// declare a panel

_gl=new LcdPanel(*_accessMode);

Now that we have an access mode we can declare the panel. The constructor will ask the access mode class to reset the panel and then it will do the initialisation sequence.

Our example initialises it in landscape mode, 16 bit colour (64K). If you take a look at TftInterfaces.h you will see that following modes are available:

ILI9481_Portrait_64K
ILI9481_Landscape_64K
ILI9481_Portrait_262K
ILI9481_Landscape_262K

The predefined drivers are just C++ typedefs that bring together the necessary combination of template instantiations to create a coherent graphics library.

Gamma correction

      // apply gamma settings

      ILI9481Gamma gamma(0,0xf3,0,0xbc,0x50,0x1f,0,7,0x7f,0x7,0xf,0);
      _gl->applyGamma(gamma);

Gamma correction involves the manual adjustment of the panel’s response to different shades of grey to compensate for differences in the manufacturing process. Every panel will be slightly different.

Setting up the tweaks to the gamma curve are optional, a reasonable display will be obtained by using the default linear curve but if true-to-life colours are a requirement then a custom gamma curve is essential.

stm32plus includes an interactive gamma adjustment application, and that will be introduced in a future blog post. For now, you can just use the default settings and maybe come back to it later.

Backlight

// create the backlight on timer4, channel2 (PD13)

DefaultBacklight backlight;

// fade up to 100% in 4ms steps

backlight.fadeTo(100,4);

stm32plus comes with a PWM backlight controller template class, and a subclass of that called DefaultBacklight that assumes you can connect the backlight regulator to PD13.

This PWM output should be used to drive the base of suitable transistor, or the ‘enable’ pin of a constant current backlight driver. It should not be used to directly power the backlight because it’s likely to draw too much current from the MCU pin.

The fadeTo method allows smooth transitioning of the backlight between levels to give a pleasing user experience.

Font selection

// create a font

_font=new Font_VOLTER__28GOLDFISH_299;
*_gl << *_font;

stm32plus (as of 1.2.0) now supports the very convenient stream ‘<<' operator to output text and numbers to the display. To do this the operator needs to know which font to use, and we do that by using the << operator with the font object as the operand.

Get the source code

It’s all in stm32plus 2.0.0 (or later) available from my downloads page.

Watch the videos

I’ve uploaded a pair of short, out of focus and badly produced video that you can waste some of your valuable time by watching if you want to see the ILI9481 panel in action.

First the STM32F103. In the video I’m running the debug version of the code. The optimised build runs about 2.5x faster. For example, a full-screen clear takes 25ms when optimised and 65ms when in debug mode.

Secondly the STM32F4 Discovery board. This is running a build optimised for speed (-O3).

  • Very nice

  • Ritez

    Would this setup be fast enough to play a movie with no sound?

    • I don't think it would be practical to do that. To get a fast enough frame rate you would probably need to pre-convert a movie to a sequence of uncompressed frames and then stream it in over a raw SDIO interface (i.e. a card with no filesystem).

      A one hour 16:9 sequence at 25fps and 16-bit colour would occupy 480*270*2*25*3600 = 22.6Gb of space on the SD card.

  • roee

    does any one have a code to active this LCD in ARDUINO MEGA2560? any example?
    Thanks
    my email
    roeebloch@walla.co.il
    Thanks

    • I don't have such a driver. It could be done but is likely to be slow because the common case is that only the ILI9481 16-bit bus is exposed to the world meaning that you can't drive it with the Mega's fast 8-bit XMEM interface and would have to use GPIO.

    • Juppeck

      An ATMEGA MCU is to slow and had not enough Pins to drive the Display directly. Most of the TFT-Driver Chips like SSD1963 and ILI9325 can be initialized in 8 bit parallel oder SPI data Interface mode. Some rare Atmel MCU's Support external ram memory by usinge address latching. This could be a possible way to go to drive the TFT, but i can't imagine, that the result will satisfies you.
      I had tested an ILI9325 Display, connected to an ATMega32 runing at 16Mhz. With a few programming tricks like extensive use of the hardware accelerations support of the ILI-Chip, some simple moving bars are displayed fluid – but not more. It looks colored, but there is no other advantage so had later decided me to use a B&W LCD Display driven by a T6963c, instead. The code is smaller and needs less power.

      The Cortex-M3 or M4F MPU's running up tp 168Mhz and have an external linear mapped address range for external memory devices available, that is clocked up to 72Mhz and uses a 16-Bit parallel Interface to drive the TFT-Driver chip directly. This makes it possible to get a fluid moved presentation.
      If you don't wanna uses ARM based MPU's, check out Atmel AT32 Series MPU's. They are also quite fast and got the potential to drive this kinda display's without flicker and got enough code memory for the application.
      Juppeck

  • Hi,
    In the video you say the back-light is connected through a 0.5ohm resistor, didn't understand how :s

    Thanks

    • The backlight circuit on this panel is 6 white LEDs in parallel. A common anode is exposed and 6 cathodes. The quickest and simplest way for me to drive that was to hook up 3.3V to the anode through a 0.5 ohm resistor and attach all the cathodes to ground. Not recommended for a production system but good enough for a breadboard prototype.

      • but 3,3V/0.5R=6.6A. There is something that I didn't understand well :s

  • Do you have the link or seller name of this lcd? I found it http://goo.gl/mizwm but in the information ti says NC(i think it comes to not connected) in touch info.

  • I think I comment about where you bought it in another post and not here and I can't find where I posted :s Can you answer here? Thanks

  • Stephane

    Thanks for your very good website. It is clear and very useful. We are presently designing a board for intercom devices in a new building. We plan to use LCD screens with the ILI9481 driver. There should be around 50 intercoms in the building, so this is an important project for us. When I was reading this page, I was wondering why you say that if we are creating a real application we should drive the LEDs with a dedicated constant-current backlight controller. Why is that so? Is it to avoid LED intensity variations over time?

    • Hi Stephane,

      I agree with all the reasons given in this app note from Maxim:
      http://www.maximintegrated.com/app-notes/index.mv

      Additionally, for the low-cost displays that I tend to play with you don't get the datasheet for the white LEDs embedded as the backlight. A constant current driver means that I don't have to know the Vf for the LEDs and can be sure that each one will be driven to an equal luminous intensity even if there are variations in Vf across the string.

      • Stephane

        Thanks a lot Andy, your answer is very useful. From the datasheet of our display, the LEDs are rated 3.0 to 3.4 volts, 20 mA maximum. Because the normal current-sink drivers produce a voltage drop of at least 0.4 volts, and since our voltage source is 3.3 volts, we will use the following charge-pump LED driver: ADM8845.

  • Hi,

    Thanks for very good tutorial.I have a question.How to driver tft screen without ILI9481 with arm or other microcontroller?

    • If it's a parallel interface to the LCD then you can either use one of the new STM32F429 series MCUs that has a built-in TFT controller or you could go it alone and drive it with an FPGA. The requirement to provide a frame buffer and to generate a continuous high-frequency pixel clock makes it impractical to do in an MCU without hardware support. You would have no time remaining to run your program logic.

      If it's a MIPI DSI serial interface to the LCD then you're all out of luck. There's a MIPI DSI interface on the Raspberry PI but, sadly, it's proprietary and therefore completely useless to us.

      • Thanks for give brilliant idea 😀

  • Hi,
    Where in the datasheet did you see it has 6 leds?

    • It's in the panel datasheet. You should have received two datasheets with your panel. One for the controller (which I know that you have) and one for the panel. The backlight in your panel will not necessarily be the same as the one in my panel.

  • Alex S.Elec

    I’m driving a similar display using a parallel master port (PMP) on a PIC32 and a DMA controller to write the display data in the back ground. Works reasonably well, although you need to use a large portion of the uPs RAM for the frame buffer, and you can’t have another buffer for rendering etc…so can’t do anything too fancy with it. It’s a cheap controller-less option though for basic text and shapes. Great post by the way, very useful! Thanks

  • Xaidi

    I got the same sheild on which the lili9481 UNO is written it fits perfect on my arduino uno board but i didn’t find any program or example to display on it?? Any Help??