Talking to a 320x240 colour LCD Mar 2016

Now that we have a fast SPI driver, we can tackle a more ambituous task of driving a 320x240 colour LCD display. In this example, we’ll use the HyTiny board with this 3.2” display, because the two can be connected via a simple 12-pin FPC cable (included with the display).

When you do the math, you can see that there’s a lot of data involved: 240 x 320 x 16-bit colour (max 18-bit) requires 153,600 bytes of memory (172,800 bytes in 18-bit mode). And to refresh that entire screen, we’ll have to send all those pixels to the display.

Note that although SPI-connected LCD displays are fine for many purposes, they cannot handle video or moving images - you’ll need to use a faster parallel-mode connection for that (with a much higher wire count). At 10 MHz - the maximum specified rate for the ILI9325 LCD driver - each individual pixel takes 1.6 µs to send, i.e. almost a quarter second for the entire image.

Still, with only a few dozen lines of Forth, we can tie Mecrisp’s graphics library to such a display:

Here are some excerpts from this code, which is available in full on GitHub, as usual:

$0000 variable tft-bg $FC00 variable tft-fg : tft-init ( -- ) PB0 ssel ! \ use PB0 to select the TFT display spi-init \ switch to alternate SPI pins, PB3..5 iso PA5..7 $03000001 AFIO-MAPR ! \ also disable JTAG & SWD to free PB3 PB4 PA15 IMODE-FLOAT PA5 io-mode! IMODE-FLOAT PA6 io-mode! IMODE-FLOAT PA7 io-mode! OMODE-AF-PP PB3 io-mode! IMODE-FLOAT PB4 io-mode! OMODE-AF-PP PB5 io-mode! OMODE-PP PB2 io-mode! PB2 io-1! %0000000001010110 SPI1-CR1 ! \ clk/16, i.e. 4.5 MHz, master, CPOL=1 (!) tft-config ; \ clear, putpixel, and display are used by the graphics.fs code : clear ( -- ) \ clear display memory 0 $21 tft! 0 $20 tft! tft-bg @ 320 240 * 0 do dup $22 tft! loop drop ; : putpixel ( x y -- ) \ set a pixel in display memory $21 tft! $20 tft! tft-fg @ $22 tft! ; : display ( -- ) ;

We have to tinker a bit more with the hardware I/O settings to switch to a different set of pins, matching the HyTiny’s LCD connector. The way it’s done here is to initialise hardware SPI as before, and then undo those I/O pin configurations and redo a few others instead.

The call to tft-config sends a whole slew of little commands to the ILI9325, which needs quite some configuration before it can actually be used after reset.

A common trick to keep the colour details out of the drawing code, is to keep two colour values in variables, used as “background” and “foreground” colour, respectively - with the background used for clearing and filling areas, and the foreground used for lines and individual pixels. By changing these variables before calling a graphics command, you can draw with any colour.

One surprise with this particular ILI9325 chip was that the SPI mode needed CPOL=1 mode. Subtle “gotcha’s” like this can eat up a lot of debug time!

Unlike the OLED driver presented earlier, we don’t have enough RAM to keep a full image buffer in memory. The clear and putpixel primitives defined above will need to immediately send their data to the display hardware. And because of this, the display code used to update what is shown on the screen is now a dummy call.

It takes almost 2 seconds to clear the entire screen with the implementation shown above. This could be optimized quite a bit further by sending all data as one long stream instead of pixel-by-pixel. But hey, as proof-of-concept, it’s fine!

For even more performance, the SPI hardware could be driven from DMA. While this requires some memory to transfer from, it can be useful to “fill” rectangles to a fixed colour by keeping the input address fixed. Still, the upper limit is 10 MHz serial, limiting frame rates to 4 Hz max.