I have a bunch of ST7820 based 128 x 64 graphics LCD modules that were purchased to replace the LCD module on my M2 Printer. These modules were very low cost, draw low to moderate current, have a wide range of interfaces (SPI, 4 and 8 bit parallel) and incorporate back lighting.
Figure 1. ST7820 12864B3 Graphics LCD Module (Front)
Figure 2. ST7820 12864B3 Graphics LCD Module (Rear)
These modules are based on the ST7820 LCD display driver integrated circuit. Be warned that the data sheet for this chip is isn’t very user friendly and has many poor or misleading descriptions.
Click here to link to the ST7820 Data Sheet
These modules are not particularly fast (typically 72 us per command, or 576 machine cycles (m/c) at 8 MHz with an 8 bit interface). Don’t expect to be displaying video on these devices any time soon!
I’ll be driving the LCD module with an old ATMega 8515 microcontroller using an 8 bit parallel interface with full read/write functionality. The processor is clocked at 8 MHz and has limited memory resources so the code needs to be small. I want the display to update at the limits of the LCD module with plenty of latency for other program functions so the code also needs to be optimised for speed even though the LCD is relatively slow.
Note that the ATMega 8515 is not fully supported in the most recent releases of AVR Studio. The initial software was written in C in just a few hours and compiled just fine but I could not simulate this in AVR Studio 6.2 or 7.0. After a lot of messing about I ended up reverting to AVR Studio 4.18 and assembler in order to fully simulate the software prior to programming the processor. This exercise took a couple of days. Next I had to adjust to the 8515 instruction set. There is no CALL, the INC and DEC instructions don’t set the carry flag, and I made that common error with SBR and CBR. The mnemonics are Set Bits in Register and Clear Bits in Register. If you incorrectly interpret these as Bit (singular) your code will likely start doing really weird stuff.
In a number of my previous projects I have used 2 line x 16 character only LCD modules which are relatively simple to apply and quite functional. The have a low to moderate current drain and the interface is relatively straight forward (SPI, I2C, 4 bit or 8 bit parallel). However these display modules are more expensive my graphics modules, many have no back lighting, and they are somewhat limited in terms of how much information can be displayed at any one time. This limitation can be partially resolved using scrolling lines and menu structures but this increases program overheads and requires user interaction to scroll or navigate the menu structure.
Figure 3. SDEC E78022 2 Line x 16 Character LCD Module
(Jaycar Part Number QP5515)
Best we start with something simple. The ST7820 graphics LCDs have a native text mode with 4 lines x 16 characters using a half width character font (16 x 8). This is relatively easy to implement (about 500 bytes of code including LCD control and text subroutines) but the characters are large and chunky and cripple the display text capacity. Figure 4 shows the native half width characters. Please note that photographing the display was a bit of challenge until I eventually adjusted the display contrast - so I have adjusted the contrast, brightness and sharpness of some of the images and I have not done the display clarity justice. Figures 8 and 9 are closer to reality.
Figure 4. ST7820 Native Half Width Character Display
So I set about implementing a graphics mode character display of 8 lines x 21 characters complete with Binary to ASCII conversion and a full range of character display routines and LCD control functions.
As mentioned above, the ATMega 8515 has quite limited internal resources. SRAM is just 512 bytes and a portion of this is allocated to the stack. There is insufficient SRAM to mirror the LCD graphics display, but enough to make a character map in RAM and implement a reasonable Character Generator in EEPROM (512 bytes).
If you are porting this code to a processor with sufficient RAM (> 1K) then it could be made faster and complete with line graphics by mirroring the LCD graphics RAM in the processor. While you could also do this in flash (program memory) there are write cycle limits to flash (typically 100,000 cycles) which you risk using up in relatively short order.
Because of the processor RAM limitation the only way of keeping track of the graphics memory on the LCD is to read it from the module, modify it and write it back. Although this works and allows mixed graphics and graphical text it is relatively slow as it takes 72 us for each read, write and address setup. Optimised worst case character position reads and writes from the LCD will take more than 5 ms (less than 200 characters per second). However if we maintain a character map in processor RAM (168 bytes) we won’t need to do any LCD reads and we can reduce the worst case character updates to less than 2.5 ms. We can do an awful lot of processing in the 2.5 ms which we have just saved (20,000 m/c).
So for now I’m going to limit the display to graphical characters, but without the ability to do concurrent line graphics.
Aside from prompt strings there are some dynamic counters that I want to display. These are currently implemented as 8 and 16 bit positive binary numbers so they need to be converted to BCD digits and then ASCII for display. Using brute force division this conversion takes 150 m/c worst case for 4 digits with leading zero suppression. A faster technique is to maintain the counters as BCD with a 36 m/c worst case for BCD to ASCII conversion. However this causes a programming overhead with manipulating the counters as the processor does not have native BCD maths. I’m sticking with brute force division for now.
Before I progress too much further I need some software flow charts to avoid making too many stupid mistakes. Rest assured that I have already made a few of these resulting in no LCD function at all, a bunch of interesting Chinese characters, display corruption, and the like. Flow charts are really valuable and programming language independent. They also enable you to rapidly identify common blocks of code which can then be implemented as subroutines.
Figure 5. Flow Chart
While looking at other peoples source code can be useful, it can take an age to transport it to your target system. While higher level languages like C can reduce transport issues you may experience problems with limited resource hardware and critical timing (both of which apply in this case), and maybe there won’t be a compiler with all of the libraries that you need.
So my scheme is to have an LCD character map in processor RAM and a character generator ROM in processor flash (program ROM). For a character update we regenerate the LCD graphical data from RAM and ROM and write the update to the LCD graphics memory as efficiently as possible.
The LCD graphics memory is arranged as an array of 16 bit horizontal lines and two 8 bit vertical pages. The LCD module address counter auto increments in the current horizontal row for reads and writes. Each character position is fixed but doesn’t necessarily align with the 16 bit horizontal address word. So for each of the 7 character rows we must build one or two 16 bit words from the updated character and the characters preceding and following it. The optimised code for updating each character position is significantly faster than a single read from the LCD graphics RAM.
Figure 6. Memory Mapping for Generating Scan Lines
Flash ROM is a relatively abundant resource on the ATMega 8515 (we have a whole 8K) and with optimum storage we can store a full 128 character 5 x 7 bit font in just 560 bytes. However this does not lead to optimized character data recall as each character must be scanned horizontally to match the LCD CGRAM layout and addressing. The Character Generator ROM (CGROM) has been implemented in 768 bytes (less the first 32 control characters that have no display functionality).
The code is progressing slowly, but there is a lot going on here including indirect subroutine calls, look-up tables and the LCD character buffer. I am putting a air bit of effort into making the code lean and mean (small and fast).
The LCD graphics character display has now passed assembly and simulation. The code is implemented in just 792 bytes of flash, with an additional 810 bytes for the CGROM and lookup tables; and 168 bytes of RAM for character storage. The code uses just 12 Registers (including the R1:R0, X, Y and Z), only two of which are required to be preserved for string writes. Stack depth for subroutine calls is shallow (about 10 bytes).
Click here to download commented assembler source code.
The actual speed of the LCD instructions is dependant on the LCD module RC clock. We can optimize speed by using the LCD busy flag read as opposed to using fixed delays for LCD functions, and we can also tweak the LCD RC values (but note that this requires hardware modification of the LCD module).
Figure 7. Display Speed Test (63,536 Characters in 94 Seconds)
If you look carefully at Figure 7 you’ll see some corrupted characters and in particular ‘Q’, ‘1’, and ‘q’. These occur consistently, independent of the character position. The cause was determined to be due to an unanticipated interrupt delay affecting the LCD module instructions.
After some debugging (incorporated in the code listing above) the graphics character display is up and running reliably with an average character display rate of 697 characters per second for non-repeating characters (thyat’s over four screens per second).
Figure 8. ST7820 Graphics Mode Character Set
Note that the LCD module timing for LCD functions and the RS, E and R/W control lines is absolutely critical. If the display doesn’t work or becomes corrupted then chances are the LCD timing is not right. Be particularly conscious of the potential for timing to change due to interrupts.
Figure 9. ST7820 Coil Winder Application
The display rate increases significantly when new text matches the existing contents of the character buffer. For example over-writing a space with a space is almost instantaneous as there is no actual write to the LCD.