Introduction
My Nokia 6110 LCD didn't last long. About three months after inserting the display into the PC, it started showing connectivity issues. So I needed a replacement. Instead of gutting another cellphone, I got myself a cheap 1.44" display off EBay this time.
Interesingingly, the seller advised to use an ILI9631 driver, while the packaging of the display said ST7735. After some digging into datasheets I'm pretty confident that the ST7735 is a very forgiving chip that accepts multiple variants of command codes. Nevertheless, the driver I tried at first was working but way too slow for my taste.
This driver is named ILI9163 but is designed to support both mentioned controller types.
So in the following sections, my efforts towards a versatile display driver are outlined. Please have a look at the demo videos on the bottom of this page. Apart from testing the entire functionality in different display color depths, they also show the performance that can be expected from 8/16 MHz Atmel MCUs.
The main reason for me to implement an own driver was the desire to support
all three color depths of the display controller. Especially the 12 Bit mode
is of interest for fast applications (like demos) with real-time graphics.
The 18 Bit mode (which was to my knowledge not implemented before in open source code) on the other hand comes in handy for natural images.
Features:
- Initialization / SPI Write
- either HW SPI at up to f/2 (f=MCU clock) _or_ fast Software SPI at f/4, i.e. 4 MHz SPI with 16 MHz MCU
- 3,4 or 5 wire connections
- just 1 Byte of RAM required (for display orientation state)
- modular, just disable #defines for functionality you don't need
- 12/16/18 Bit bit depths supported
- Display orientation change in 90 degree units
- Pixel setting
- Area Fill
- High/True Color image display (see included image converter for 12/16/18 Bit)
- High/True Color scaled image display (bresenham based nearest neighbor, currently requires hardware SPI to be enabled)
- 8 Bit image display (with individual color table)
- low Flash requirements with just 8 Bit per pixel instead of 12/16/18 BPP
- automatic conversion to active display mode (12/16/18 Bit)
- PutLine function, which shows a single line (or column when changing the display orientation) from RAM on screen, useful for demo effects (input line in RAM as indexed color, output in current display mode)
- Text functions with multiple font support
- foreground/background color selectable
- more than one font at once usable in programs
Schematics, Code size estimates and some API guidelines are posted below.
Schematics
Below, you'll find my suggested wiring for ATMega328p and ATTiny85 MCUs matching the default settings in ILI9163.h. You may choose any free GPIO port in software SPI mode. Please consult the manual for the correct pins in hardware SPI mode. There is just one catch: In order to use the fast software SPI implementation instead of the slow one (which is also available with my driver, just in case), CLK and DTA must be pins on the same port. The other GPIOs may be located to your convenience.
The standard 5-wire layout allows all device functions to be software-controlled, including multiple SPI device addressing (/CS) and software control over the display reset (/RES). Please note the pullup resistor I suggested for the MISO port. When hardware SPI is in use, the MCU will switch the port to input. In that case, when the input goes low, the Atmel will give up being SPI master. So in my approach, you can use the MOSI port for resetting the LCD and forget about that pin (which would be useless as GPIO in hardware SPI mode, anyway) afterwards.
When pin economy is of utmost importance, the display also works with 3 GPIOs. The reset pin of the display goes into a simple power-on reset RC-circuit. The chip-select pin (/CS) is in this case connected to ground.
In order to make the software aware of changes in pin assignments, refer to ILI9163.h. I've defined three directives per pin there. The first is the port PORTA,PORTB,PORTC or PORTD (LCD_PORT_*, depending on actual MCU). The second directive gives the data direction register (DDRA,DDRB,...) and the third defines the pin of the port. In case that 4-wire or 3-wire schematics are used, un-define LCD_PIN_CS and/or LCD_PIN_RES, respectively.
I've tested one ATMega328p with a 5-wire setup and an ATTiny85 with 5-wire and 3-wire layout. Please note that my schematics assume 5V MCUs and therefore include simple voltage divider based level shifters to the 3.3V of the display. When the MCU runs with 3.3V, you can leave out the 6.7k/10k cascades. Please look also here for more information on level shifting with ST7735 LCDs.
Driver Usage
In typical cases, the driver consists of three files: ILI9163.c, ILI9163.h and one font file. I've provided different fonts in the archive: ASCII 5x8, ASCII6x8 and two 16 point fonts. The default font is the first one included in ILI9163.c.
In case that text support is undesired, just disable the _LCD_FONT_SUPPORT directive in ILI9163.h.
Other directives, which enable/disable specific functionality can be found in the header file as well. Especially, the bitmap functions might be left out of the compilation by fine-grained choices.
Please note that you might need to enable software SPI (#define _LCD_SOFT_SPI) for ATtinys. The driver archive should already be in software mode by default. In fact, I'd suggest to start off with software SPI for initial experiments with the display. Especially, when the circuits are built up on breadboards first, long wiring might cause the display to fail initializing at higher clock speeds (this happened to me with my LPH8731 btw.).
The driver will support one bit depth at a time, so enable just one of _LCD_12BIT, _LCD_16BIT or _LCD_18BIT. If unsure, use _LCD_16BIT as fair balance between speed and image quality.
The initialization is done by a simple call to LCD_Init() without arguments. The init sequence is scripted to reduce code size. Afterwards, the display is ready to be used.
The sample code in the download archive is accompanied by a Makefile for GNU Make (sorry, no Arduino IDE sketch stuff from me). Please adjust the actual MCU, it's speed and your programmer type in the Makefile. The call "make flash" will then write the demo application via avrdude to your MCU.
Like many MCUs, the Atmel chips are short on the flash side. Hence, an important question is always the storage requirement of a given piece of code.
The demo application compiles to 7530 bytes in 12 Bit mode with software SPI. The driver features I left out were LCD_PutLine() and LCD_ShowImageSC(). The total size includes a 5x8 font (475 Bytes), a 48x39 pixel logo (2867 bytes) and a 32x32 pixel logo in 8 Bit (1024 Byte + 128 Byte color table). With the demoloop left out, the whole program (with an empty main()) compiles to 2438 Bytes. Adding LCD_ShowImageSC() will require 332 Bytes more.
With an empty main(), the minimal configuration with PutPixel, area fill and text functions (no bitmap graphics, 12 Bit, software SPI) results in a file of 1920 Bytes. If the bitmap graphics (except PutLine) are enabled in said case, the uploadable file will be 2770 Bytes. When hardware SPI is enabled, the file results in 2972 Bytes (due to the added PutLine call which requires the hardware SPI presence).
API overview
The orientation of the display may be configured at runtime on-the-fly. It is implemtented so that the user doesn't need to worry about coordinate transformation. The display will be adjusted to a new upper left corner, depending on the argument of the LCD_Orientation() call. Every drawing command is conveniently relative to the orientation-dependent upper left corner. The only thing to be taken care of is to swap the maximum width/height constants in the 90 and 270 degree orientations (see LCD_FillScreen() in the C-File). It should be noted that orientation changes affect the pixel write direction registers only. Hence, different parts of the screen may be painted with differently oriented text and graphics.
The basic functions like LCD_FillRect(), LCD_FillScreen(), LCD_Putc(), LCD_Puts() and of course LCD_SetPixel() should be fairly self-explaining. It shoud be noted, that the mentioned functions take 1-2 color parameters. As the driver supports three different color depths, I chose to replicate the macro-based approach from a previous driver. The LCD_RGB() macro takes three arguments: Red, Green and Blue where all three components are in 8 Bit resolution. So Black would be LCD_RGB(0,0,0), maximum Blue LCD_RGB(0,0,255) etc. A number of common colors are defined as shortcuts in the header file.
In case that text output with different fonts is desired, refer to the LCD_PutcF(), LCD_PutsF(),... functions. These take a pointer to the font as last argument.
Where this driver stands out among the available choices is it's bitmap support. The included graphics converter (see below) outputs high/true color images for any of the supported display modes. These images may be displayed on-screen by LCD_ShowImage(). In case that image scaling support is compiled-in, the images may be displayed in scaled form by LCD_ShowImageSC().
Frankly, the quite constrained flash memory sizes of typical Atmel MCUs can be quite a challenge for storing bitmap images, especially at higher bit depths. This driver also offers indexed color support as an alternative. The conversion from 8 Bit indexed color to the current display mode is done on the fly. This way, just 8 Bit per pixel (plus color table overhead) are required, instead of 12/16/24 Bit. The function is called LCD_ShowImage8(). Since the colortable is passed to the function as one of the parameters, you might want to create funky color shifting effects by just alternating between color tables. The color table is not required to cover all 256 color entries. If less than 256 colors are in use, abbreviated color tables can of course be applied.
Another function targeted at 8 Bit indexed color images is LCD_PutLine(). This function takes one row/column from RAM and converts it to the current display mode. I implemented this function to port my demo effects from the old LPH8731 display to this driver.
Other primitives like diagonal lines, circles etc. found in other drivers are not included here. If needed, they can easily be added but I myself rarely have use for them.