stm32plus: ILI9325 TFT driver

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


The ILI9325 controller

This second article in the series of documentation-by-example posts will present a C++ driver for 320×240 (QVGA) TFT LCD panels that have an ILI9325 controller built in to them. This driver is included with my open source stm32plus C++ library and this article will show you how to use it with the STM32F103* ARM Cortex M3 microcontroller family running at 72Mhz. As of stm32plus 2.0.0 the driver is fully compatible with the STM32 F4 series of microcontrollers.

Updated sample code is available with stm32plus 3.0.0 or later and there’s a video hosted on YouTube that accompanies this article.

I like Ilitek controllers. They’re consistent across the range, they’re well documented and they’re easy to program if you’re familiar with TFT controllers, which I am.

The ILI9325 is a 320×240 (QVGA) device that supports 64K (5-6-5 RGB) or 262K (6-6-6 RGB). To the outside world (that’s us by the way) it presents 18-bit, 16-bit, SPI or a direct-drive RGB interface. It has its own onboard GRAM frame-buffer and expects you to send it commands that read and write from that buffer unless you’re in direct RGB mode in which case you are directly addressing the GRAM via a synchronised clock.

The panel


The 2.4″ TFT plays host to the ILI9325 controller, probably fitted COG-style.

Here’s the panel that I’ll be demonstrating. Mine happened to arrive as part of a “Mini STM32″ development board that I got on ebay but they’re also available in various incarnations either ‘bare’ or pre-mounted on to a PCB that breaks out the controller pins for you.


The pinout was documented as part of the overall schematic.

The schematic for the STM32 dev board documents the pinout for the TFT panel. Helpfully, the port numbers are annotated as well as the function of each pin. 16 data lines are broken out, so that implies we’re talking to the controller over its 16 bit bus (it has 18-bit, serial and RGB capabilities as well). Register-select (/RS), chip-select (CS), read (nOE) and write (nWE) are all there. There are additional pins for the reset line (RST) and the backlight. A pleasant surprise is the presence of the touch-screen interface on SPI1 up at the top right; we’ll be kicking the tires of the ADS7843 touch screen IC in a future article.

Here’s a tip for anyone who’s got this same dev board. The pins on the XS2 header are back-to-front on the schematic versus the board itself. That is, as you look down at the XS2 connector pins 3V is on the top right and PA5-SPI1-SCK is on the top left. 5V is on the bottom right and PD10-D15 is on the bottom left.

This dev board plays host to the STM32F103VET6 MCU. The V in ST’s nomenclature means that the device has 100 pins. Those of you that are familiar with the limitations of the 100 pin device will know that means that the Flexible Static Memory Controller (FSMC) only has one 64Mbyte NOR/SRAM bank at address 0×60000000. That’s fine, it gives me the chance to show stm32plus addressing a different bank than the usual #4 that I use on the STM32F103ZET6 board I use most often.

Since we’re using the FSMC, we need to choose an address line to attach to the RS line to control whether we are writing data or register selection to the controller. We’ll use line 16, resulting in the following addressing.

Memory address A16 line transfer
0×6000 0000 0 register select
0×6002 0000 1 GRAM data


stm32plus code

Here’s the code used to initialise the LCD. When this code has completed the LCD will be reset, initialised with your chosen colour mode, gamma and orientation and ready to use.

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

using namespace stm32plus;
using namespace stm32plus::display;

/*
 * ILI9325 LCD test, show a looping graphics demo
 */

class ILI9325Test {

  protected:
    typedef Fsmc16BitAccessMode<FsmcBank1NorSram1> LcdAccessMode;
    typedef ILI9325_Portrait_262K<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 timing. these numbers (particularly the data
      // setup time) are dependent on both the FSMC bus speed and the
      // panel timing parameters.

      Fsmc8080LcdTiming fsmcTiming(0,2);

      // set up the FSMC with RS=A16 (PD11)

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

      // apply gamma settings

      ILI9325Gamma gamma(0x0006,0x0101,0x0003,0x0106,0x0b02,
                         0x0302,0x0707,0x0007,0x0600,0x020b);
      _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;

That’s all there is to it. If you’ve also read my previous article on driving the HX8347A controller then this will all look familiar. That’s because stm32plus hides away all the device-specific details and presents you with a unified interface for controlling graphic devices. Here’s a quickie image taken from the rolling demo. As usual the camera is less than kind to the TFT. The actual display is sharp and contrasty.


A freeze-frame from the rolling demo that shows the ILI9325 in 262K colour mode.

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 ILI9325_Portrait_262K<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 timing. these numbers (particularly the data 
    // setup time) are dependent on both the FSMC bus speed and the
    // panel timing parameters.

    Fsmc8080LcdTiming fsmcTiming(0,2);

    // set up the FSMC with RS=A16 (PD11)

    _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 portrait mode, 18 bit colour (262K). If you take a look at TftInterfaces.h you will see that following modes are available:

ILI9325_Portrait_262K
ILI9325_Landscape_262K
ILI9325_Portrait_64K
ILI9325_Landscape_64K

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

ILI9325Gamma gamma(0x0006,0x0101,0x0003,0x0106,0x0b02,
                   0x0302,0x0707,0x0007,0x0600,0x020b);
_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.

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.

Demo code and video

The stm32plus package contains the full source code to the demo that you can see in the video below.

Visit youtube to view this video in high resolution

The clear screen demo shows that I can transfer data to this display in 262K colour mode at roughly 2.45 megapixels/second. This will vary per-display based on the specifications for the address and data setup time. If you need a super-fast display, always check out the timing specs in the datasheet before you buy.

More to come…

Watch this space for much more free stuff to come. ILI9327 and ILI9481 touch screens and more. It’s all here!

Download source code

The stm32plus source package is available from my downloads page.

  • Andy_ry

    Nice! Well done!

  • fernando

    Hi Andy, i don't know if you can help me but i will try, i'm work with a STM32f103vet6 and i don't know how to identify the tft controller, i'm working like ili9325 but the display works only between 190 to 240, please, can you help me?

    • http://www.andybrown.me.uk Andy Brown

      Where did you buy the TFT? Without documentation it can be very hard, but not impossible to identify the controller. Check the datasheets for registers that you can read that may contain id information.

      • fernando

        Thinking as ILI9325 the register is R00h and it's the id registers for many controllers, but i can't read this register in a var and put in the lcd, only for know about the controller? If yes, how can i do this?

  • dienbk

    Thank you for your sharing. I'v bought 1 tft 7 inches with ssd1963 controller. I hope in the next release of stm32flus, you'll implement the driver for ssd1963, thank so much!

  • BO LI

    can you tell me where did you buy it, i really need one for my stmf4.

    • http://www.andybrown.me.uk Andy Brown

      That particular LCD comes with an STM32F1 development board. Search "stm32f103vet6" on ebay and you'll see a lot of them.

  • phongnguyen

    It old project ! But i have just begin do it, I want to ask about RS pin . Some project choice FSMC A0 for LCD . Anyone give me how do i choice pin address for tí pin ? Thanks you !

    • http://www.andybrown.me.uk Andy Brown

      Hi,

      On the 100-pin version of the STM32F103 A0-15 are multiplexed with D0-15 so A0 is not available for use as RS. If you want to use A0 as RS then you need the 144-pin version of the STM32F103 because the A/D0..15 pins are not multiplexed.

  • himsha

    hi,

    i gone through your youtube video

    SSD1963 480×272 display on STM32 F1

    i am using SSD1963 800×480 TFT display on STM32F103 with keil compiler able to change color but not at good refresh rate as seen in your video u achieved good speed and fonts work and lot of things you display so my question is how you doing it with tool you using and how u displaying fonts.

  • http://www.andybrown.me.uk Andy Brown

    gcc (CodeSourcery arm-none-eabi lite). The ARM launchpad edition will also work.

  • reuNor

    Hi Andy, love your site, and your work too!
    In your post above, how have you arrived at the addresses “0×6002 0000″ and “0×6000 0000″ for A16 ?
    How come there are 2 addresses ?? Also, shouldn’t it be (0×6000 0000 | (1 << 16)) = 0×6001 0000 ?
    Would you be kind enough to explain how one calculates addresses for RS=0 and RS=1 ?

    I've designed a board with STM32F103VGT(which is 100 pin). I'm trying to
    drive an LCD, and my conf. is such that LCD RS is A17, and since only
    one NE is available, viz, NE1, I've used Bank1, NORSRAM1
    Is the address (0×6000 0000 | 1<<17) = 0×6002 0000 correct for RS=0, and RS=1 ?

    • http://www.andybrown.me.uk Andy Brown

      Hi,

      I’m glad you asked that question about the addressing because it’s a while since I looked at that and I had to refresh my own memory (no pun intended!).

      The apparent off-by-1-bit is because the FSMC is a 16-bit device and can only be addressed on even addresses. Hence A0 is mapped to bit 1, A1 to bit 2 and so on. See AN2790 for further examples and information.

      In your example where you want to use A17 you’ll need to shift up one more to 0×6004 0000. See here for the calculation.

      • reuNor

        Andy, thanks for pointing me in the right direction, after some trial and error, I managed to get it working. For those who might land up here, and with your permission, I’d like to post a snippet of my working code
        For A17, Bank 1, NOR/SRAM1
        for RS = 0
        #define LCD_REG (*((volatile uint16_t *) ((uint32_t)(0×60000000 | (0UL << 27) | (0UL << 26)))))
        for RS = 1
        #define LCD_RAM (*((volatile uint16_t *) ((uint32_t)(0×60000000 | (0UL << 27) | (0UL << 26) | (1UL << (17+1))))))