This is a basic sketch to demonstrate sniffing the data bus of an LCD 1602 display to interpret the contents of the display. This could be useful if, for instance, you are using a 3rd party product for which you have no source code and which incorporates one of these displays, and you need to extract some information from it.
Actually, I started this project as an experiment to get familiar with a new logic analyser and have no current plans to develop this further. I've, however, seen a couple of cases where such a tool could be useful so , maybe, this helps someone.
The first thing to say is that this is a basic sketch and demonstrates the main points of capturing and interpreting the data. However, the chip family on which these displays are based (Hitachi HD44780) offers quite a rich feature set, including scrolling support, custom character definition etc. Depending on how the developer has used these features, and which library he has used, it may be quite challenging to interpret the data. In the worst case, it would be necessary to more or less emulate the functionality of the entire chip. Fortunately, you are more likely to encounter a simpler use case.
The HD44780 data sheet is attached as are the demonstration sketches.
The demonstration environment is extremely simple. It consists of two main parts. Part 1 is an Arduino Nano connected to a 5 volt LCD 1602 with an I2C backpack. It simply writes to the LCD 1602 to emulate the behaviour of, say, a third party application. I used an I2C backpack for simplicity of wiring. The I2C protocol is otherwise irrelevant here. The second part consists of an Arduino Uno connected to the data bus and control pins of the LCD 1602 (seven pins marked RS, RW, E, D7, D6, D5, D4 plus a common ground). This second part runs the sniffer sketch. That is it.
Before proceeding with the details of the sniffer sketch, I'm presenting a picture from the logic analyser showing the status of the pins under some test conditions. The first part of the data stream is the initialisation of the LCD 1602 and then the first message "hello world!" . The initialisation, mainly forcing the display into 4 bit mode and cleaning up is interesting but you don't have to understand it.
See post #2 below for an improved picture with protocol specific analysis data
Reading example: The seven main pin of the LCD 1602 are represented by channels on the left margin (E, RS, RW, D7 etc.). A simultaneous trace for each channel is shown. The enable pin of the LCD 1602 is pin 'E'. The falling edge of the enable signal indicates that the data on the remaining channels is valid. So, if we take, say, the second visible pulse on channel E ( the one under the 'm' of + 20 ms on the header) and look vertically downwards, we see that RS is low (0), as is RW, D7, and D6. However, both D5 and D4 are high (1). Since the display is, in this case, driven in 4 bit mode, the LCD pins D3, D2, D1 and D0 have no connections to them. The display still needs this data, however, so the whole bus is loaded twice. That is, you see two pulses of E, once for the four high order and once of the low four low order bits. The trick in the sniffer program is to join these up.
The data presented in the trace matches the sample sniffer output below.
Of course, all the information appearing above is also visible from both the HD44780 data sheet and also by reversing engineering the Arduino Liquid Crystal libraries, but the diagram should help to make it clearer.
The sketch sending the test data is quite trivial. It simply does the standard initialisation, clears, the screen and writes "hello world!". Then, at one second intervals, it writes a counter to a specific part of the display. It is attached below: LCD1602_TestWrite_V0_02.ino
This is a sample of the output from the sniffer when the above mentioned test program is dumping data to the display. It has been annotated to help explain what is going on.
10:46:32.517 -> starting sniffer ...
// 0xMM 0xNN MM bit 7 is error bit , bit 1 is RS, bit 0 is R/W
// ---- ---- NN is LCD DB7 through to DB0
// Start sequence . . .
10:46:35.873 -> control data 0x00 0x33 // 0x03 sent twice (presented as combined)
10:46:35.873 -> control data 0x00 0x32 // 0x03 then 0x02 ( 0x02 = return home ? )
10:46:35.873 -> control data 0x00 0x28 // Function Set 4bit, 2 lines, 5x8 dots
10:46:35.873 -> control data 0x00 0x0C // Display on, cursor home, blink off
10:46:35.873 -> control data 0x00 0x01 // Display Clear
10:46:35.873 -> control data 0x00 0x06 // Entry mode set - cursor increment mode, no display shift ?
10:46:35.873 -> control data 0x00 0x02 // Return Home
10:46:36.391 -> control data 0x00 0x01 // Display Clear
10:46:36.391 -> control data 0x02 0x68 // 'h' 0x02 (RS=1,RW=0 - write data ) 0x68 = 'h'
10:46:36.391 -> control data 0x02 0x65 // 'e'
10:46:36.391 -> control data 0x02 0x6C // 'l'
10:46:36.391 -> control data 0x02 0x6C // 'l'
10:46:36.391 -> control data 0x02 0x6F // 'o'
10:46:36.391 -> control data 0x02 0x20 // ' '
10:46:36.391 -> control data 0x02 0x77 // 'w'
10:46:36.391 -> control data 0x02 0x6F // 'o'
10:46:36.391 -> control data 0x02 0x72 // 'r'
10:46:36.391 -> control data 0x02 0x6C // 'l'
10:46:36.391 -> control data 0x02 0x64 // 'd'
10:46:36.391 -> control data 0x02 0x21 // '!'
10:46:36.391 -> hello world! // The following data item 0xC5 is not detected as a character
// so the sniffer prints out the accumulated printable characters.
10:46:36.391 -> control data 0x00 0xC5 // 0xC5 ( setCursor( 5, 1 ) ) Sets DDRAM address
10:46:36.391 -> control data 0x02 0x30 // '0'
10:46:36.391 -> control data 0x02 0x30 // '0'
10:46:36.391 -> control data 0x02 0x30 // '0'
10:46:36.391 -> control data 0x02 0x30 // '0'
10:46:36.438 -> control data 0x02 0x30 // '0'
10:46:37.418 -> 00000 // again, the sniffer recognises the end of a character stream and
// prints out the accumulated printable characters, the counter
// value in this case.
10:46:37.418 -> control data 0x00 0xC5
10:46:37.418 -> control data 0x02 0x30
10:46:37.418 -> control data 0x02 0x30
10:46:37.418 -> control data 0x02 0x30
10:46:37.418 -> control data 0x02 0x30
10:46:37.418 -> control data 0x02 0x31
10:46:37.511 -> errorCount= 0 // The sniffer prints an error total every few seconds. Errors are
// Queue overflows and indicate that the loop() processing is too slow.
10:46:38.397 -> 00001
10:46:38.397 -> control data 0x00 0xC5
10:46:38.397 -> control data 0x02 0x30
10:46:38.397 -> control data 0x02 0x30
10:46:38.397 -> control data 0x02 0x30
10:46:38.397 -> control data 0x02 0x30
10:46:38.444 -> control data 0x02 0x32
10:46:39.423 -> 00002
// etc.
General description of the sniffer. The sniffer sketch simply watches the enable pin, captures the data on the LCD bus, combines data from 2 consecutive reads of the bus (4 bit mode) and puts the data in a queue. The queue is read in the loop and formatted output is written to the serial console.
The queue is necessary because a synchronous operation of reading the data bus in a data burst and writing it to the serial console overwhelms the serial port, leading to data loss. The sketch can be easily adapted to handle 8 bit mode, if necessary.
I've interpreted the display characters as ASCII, however, there may be some inconsistencies between the standard ASCII table and the font table of the LCD. The sniffer sketch appears in the attached zip file together with its queue library: LCD1602_sniffer_V0_03.zip
If you are attempting to interpret complex patterns, it may help to define an array which matches the display canvas, in this case 2 x 16, and keeping track of screen control commands such as clear and cursor movements etc. On each change to the display, or at regular intervals, check if the part of interest has changed in that iteration. Process such changes accordingly.
Edit:
- LCD1602_sniffer_V0_04.zip has been added to correct the issues described later in this thread. I've retained the older version LCD1602_sniffer_V0_03.zip for those who prefer a simpler version and understand its limitations.
- There is an even newer version of the sniffer attached to post #10. When it all stabilises, I'll put the definitive version here and clean up.
HD44780.pdf (322 KB)
LCD1602_sniffer_V0_03.zip (2.78 KB)
LCD1602_TestWrite_V0_02.ino (684 Bytes)
LCD1602_sniffer_V0_04.zip (4.05 KB)
Note: Post #41 contains the latest version of the sniffer.