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.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#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

1
2
3
4
5
#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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
    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

1
2
3
// 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

1
2
3
4
// 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

1
2
3
4
5
6
7
// 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

1
2
3
4
// 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).