Initially, I thought about abstracting the low level commands to the LCD module so that the host firmware would not have to know much about the display module. This abstraction would require the backpack module to know and remember what the LCD configuration data was. It did not seem worth the effort to do this abstraction, and I opted for pushing that knowledge up to the host processor. A simple command language that would not change much between the UART or the I2C interface was cooked up.
The LCD has 2 addresses, a command address for configuration and addressing commands and a data address to accept display data. The commands look like:
@@ Cmd N D1D2D3Dn LF
@@ two literal characters to lead the command packet. (only used in serial mode).
Cmd is a command code, 0=command, 1=display data, 2=system command.
N is the number of data bytes.
D1 D2 D3 Dn are the data bytes, 8 bit, binary.
LF is a literal Line Feed Character to end the command (only used in serial mode).
The I2C protocol that I implemented is very SMBus like with the command code and transaction length. The I2C mode even implements the optional packet error checking for SMBus.
Because the I2C physical layer has detectable start and stop operations, the leading "@@" and the trailing LF characters were not used in I2C mode. A multi-character async serial sequence does not really have a detectable start or stop event, so the "@@" and LF characters provide that.
When the LPC1114 comes out of reset, it configures the clock block and enough of the GPIO to read the communication select jumpers. The value of the communication select jumpers goes into RAM and is kept until reset or power loss. After reading the jumpers, the selected interface (UART, I2C) gets its pinmux configured and initialized.
All of the UART and I2C I/O handling is done in interrupt routines. The LCD interface is implemented in the foreground, since it is less time sensitive and may take significantly longer.
LCD commands like clearing the display may take a long time (up to 44mS). The Hitachi LCD controller manual says that you can wait the max time for a command or you can read the command address, and it will return a busy flag until each command is done. Bidirectional data is easy on a real microprocessor data bus (which has not been seen in the wild in decades), but slightly more trouble when using GPIO bits. In order to read the busy flag, the GPIO pins need to be configured as inputs, then to write data and commands, the GPIO bits need to be configured as outputs.
At this time, the only system command implemented is the read user I/O command that returns 1 byte with the 4 user bits in the low end.
Code for the I2C SMBus operation was re-used from a project that I did about 8 years ago. This application is a LOT simpler that the previous one, so there was a lot of code removed and a little bit of new code.
The UART code is mostly just interrupt driven data in and data out operations with some buffer management to not lose data while the LCD interface is active.
Since the UART interface is handling binary data, it was necessary to write a simple tool to talk to the display module from a PC for testing. Code for the UART version was modified somewhat to create a tool for testing the I2C configuration of the backpack.