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 0x60000000. 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
0x6000 0000 0 register select
0x6002 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?

    • 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.

    • 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 !

    • 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.

  • 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 “0x6002 0000” and “0×6000 0000” for A16 ?
    How come there are 2 addresses ?? Also, shouldn’t it be (0x6000 0000 | (1 << 16)) = 0x6001 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 (0x6000 0000 | 1<<17) = 0x6002 0000 correct for RS=0, and RS=1 ?

    • 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 0x6004 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)(0x60000000 | (0UL << 27) | (0UL << 26)))))
        for RS = 1
        #define LCD_RAM (*((volatile uint16_t *) ((uint32_t)(0x60000000 | (0UL << 27) | (0UL << 26) | (1UL << (17+1))))))

  • Elias

    Hi Andy, thank you for creating these libraries. I bought a STM32 mini development board with an LCD that I believe uses the ILI9325 controller. Unfortunately the company isn’t get back to me on the LCD but after doing some research, I feel this is the controller. It has an STM32f103VCT6, so I changed Linker.ID to reflect the 256K Rom and 48K Ram on the chip. I uploaded the .bin file to the board and it’s behaving strangely and I can’t figure out what the issue can be. When I unplug power and plug it back in, sometimes it will give me a white screen and sometimes it will be a black background. At times I’ll get text reflecting that it is doing a test but it’s frozen. Most of the time it doesn’t show text however. It seems to be breaking somewhere and I am hoping you could point me to the right direction. Unfortunately I don’t have a programmer/debugger and am using the USB to program the board.

  • Balogh Nándor

    It’s an old article. but I have a question.
    How could achive that the display isn’t flickering at all?

    I use some minimaples ( stm32f3 ), I used ili9341 with spi, and I tried an LG 4535 display with i80 (8bit parallel) interface. But the speed is not enough for avoiding the flickering. I think the double buffer would be the only correct solution, but of course the memory is not enough for that.

    Is there any other solution to avoid flickering????

    And an other intersting thing for me, ili9341 with spi is faster than the LG4535 with i80 interface. But the drivers optimized, and there isn’t any magic in them.

    • Hi Balogh,

      The only way to avoid flickering is to synchronise the start of your updates with the TE signal and then update in the same direction as the display refresh and always stay ahead of the progress of the refresh. In practice this means double buffering and that implies either an MCU with direct support for that or something like an FPGA like I used in the sprite graphics accelerator project.

      Regards,
      – Andy

      • Balogh Nándor

        Hi Andy!

        Thanks for replaying.
        Yeah, I read your article carefully again. And cheeked this video, so if I can see well there is flickering in his demo too, but in this application there isn’t separated RAM for buffering data for TFT. Am I right?

        But your FPGA sprite board is amazing, obviously is state of engineering art but for me a little bit over complicated.

        So to avoid flickering I will need an external sram and write the frame in there then in a proper time I have to copy that buffer to TFT gram.

        Could you give me an advice which board I should buy?
        I only want to deal 240*320 * 16 bit TFT-s so I need 156 KB for tft buffer, and I don’t want to complicate it.
        So I need a board with about 200 KB RAM.

        But I am working on a little 3d engine, maybe I need more memory for Z buffering It means I need minimum 16 bit Z value for each pixel. So far
        I implemented only flat shading, but If I want textures I need much more memory.

        So which board I can easily expand external memory and whic tyype of memory?
        Can I use such a memory:

        http://www.ebay.co.uk/itm/IS62WV51216BLL-SRAM-Board-Memory-Storage-Module-CMOS-Evaluation-Development-Kit-/261009984105?hash=item3cc5684a69:m:mirrvzr88_hllKFZa6HqOzQ ?

        Thank you your time, and your great jobs.

        Regards,

        Nandor

        • Hi Balogh. There are not many cheap MCU boards with LCD support to choose from. One of them is the STM32F429 which is a very powerful MCU with direct support for LCD controllers.

          If you buy the ‘disco’ board that I linked to then you can work on perfecting your firmware on a known-good board and then move on to designing your own custom board if you need to. I would do some research on the embedded LCD controller (LTDC) and the Chrom-ART (DMA2D) peripheral. There should be some examples from ST around that you can compile and use to validate that it works for your use case.