SImple C code, Arduino compatible, for setting up and writing text on ST7920 based graphic 128x64 LCD.
With this in mind and wanting to learn how to control a graphic LCD, I started to develop my own code. It turned out to be simpler than I thought. Simple code also means simple porting to other platforms. So I started this project with a ST7920 128x64 graphic LCD. I chose ST7920 because it supports serial protocol (SPI) and is 3.3V and 5V compatible. When I bought it I thought I could directly interface it with an OpenWRT router.
When you start writing your own code/library for a device, the first place to look is the device datasheet. ST7920 is a chip manufactured by Sitronix and has support for Chinese alphabet. Don't worry, it can display also the English alphabet with 8x16 characters.
ST7920 supported English characters (source: datasheet) |
Connecting the display in serial mode is easy. You need to set PSB pin to 0 (connect it to GND) to inform the controller that you will be sending serial data. Then, RS becomes CS, RW becomes MOSI and EN becomes SCK. It is also recommended to connect the reset pin. Look on the back of your LCD board. If it has a preset trimmer resistor, that can be used for contrast adjustment and you don't need to connect anything to VO pin.You should also note that some manufacturers hardwire the LCD to either parallel or serial mode. Use a continuity tester (ohmmeter) to check if PSB is connected to VCC or GND.
Connect ST7920 LCD to Arduino Nano SPI pins |
According to datasheet, SPI clock cycle should be at least 600ns. This means a maximum frequency of 1.66 MHz. However, I couldn't get output on display with frequencies higher than 400 kHz. I don't know if it's ATmega328 or ST7920 fault. Let's have a look at the datasheet and see how a single byte is sent in serial mode.
ST7920 SPI transfer (source: datasheet) |
void ST7920_Write(boolean command, byte lcdData) { SPI.beginTransaction(SPISettings(200000UL, MSBFIRST, SPI_MODE3)); digitalWrite(10, HIGH); SPI.transfer(command ? 0xFA : 0xF8); SPI.transfer(lcdData & 0xF0); SPI.transfer((lcdData << 4) & 0xF0); digitalWrite(10, LOW); SPI.endTransaction(); }SPI bus is configured for mode 3 (data read on clock falling edge), with MSB first. Wikipedia has a good article on SPI modes. Since we know how to transfer data to LCD, let's perform its initialization. To improve code readability, Zhongxu used some definitions for ST7920 registers.
#define LCD_DATA 1 // Data bit #define LCD_COMMAND 0 // Command bit #define LCD_CLEAR_SCREEN 0x01 // Clear screen #define LCD_ADDRESS_RESET 0x02 // The address counter is reset #define LCD_BASIC_FUNCTION 0x30 // Basic instruction set #define LCD_EXTEND_FUNCTION 0x34 // Extended instruction setWith this, the initialization sequence can look like this:
void ST7920_Init() { digitalWrite(LCD_RST, LOW); delay(100); digitalWrite(LCD_RST, HIGH); ST7920_Write(LCD_COMMAND, LCD_BASIC_FUNCTION); // Function set ST7920_Write(LCD_COMMAND, LCD_CLEAR_SCREEN); // Display clear ST7920_Write(LCD_COMMAND, 0x06); // Entry mode set ST7920_Write(LCD_COMMAND, 0x0C); // Display control }LCD_RST is assigned to pin 8 and it is configured as output. It is active low. After reset, the display is configured by writing four instructions (refer to datasheet, page 16):
- Function set clears RE bit (switches to basic instruction).
- Display clear fills character ram with spaces and resets address counter.
- Entry mode set configures cursor direction to right (I/D=1) and disables display shift.
- Display control turns display on (D=1) and disables cursor display and blink.
Now, the display is ON and awaiting text to be displayed. Let's see how character RAM is organised.
Character RAM of ST7920 |
The RAM is optimized for Chinese 16x16 characters. Therefore, writing regular 8x16 characters is a bit different. You write two characters at a time. So, if you want to put 'A' at 0x93 position, you must also write a space before it like this:
ST7920_Write(LCD_COMMAND, 0x93); ST7920_Write(LCD_DATA, ' '); ST7920_Write(LCD_DATA, 'A');For 'B' character, it's enough to send only this character. The one next to it will remain unchanged unless you sent something.
ST7920_Write(LCD_COMMAND, 0x9A); ST7920_Write(LCD_DATA, 'B');You don't need to adjust address before each character. To write a string, set initial address then continue transferring the string one character by character. Like this example on Line 2:
ST7920_Write(LCD_COMMAND, 0x88); const char *text = "ST7920 Graphic LCD"; for (int i = 0; i < 18; i++) ST7920_Write(LCD_DATA, *text++);It produced the following output (see how it placed text on next line, which is actually above):
Text output on ST7920 LCD |
This code is adapted from Experimenting with ST7920 128×64 graphical LCD on a PIC and zhongxu/avr.ST7920. My complete Arduino sketch can be download from here.
Thanks for the code but it doesn't work with my LCD yet the waveforms on E,R/W & RS are pretty much identical to the SPI transfer figure shown above. I might have fried my display in my testing so I will have to purchase another one. Its the external cat & mouse applied to HW/SW
ReplyDeleteFinally found the wrong resistor position R9 had the display in parallel mode...
ReplyDeleteThis comment has been removed by the author.
ReplyDeletehi nice tutorial. i do have a question can we use 20 pin 12c interface for this lcd..?
ReplyDeletei have purchased this https://www.aliexpress.com/item/32882157852.html?spm=a2g0s.9042311.0.0.27424c4dwPYRUq
but not working
I guess you can use that too while ST7920 is in parallel mode. But there's no need for it, since ST7920 supports SPI protocol which uses only 3 wires.
DeleteManyTHANKS. I had absolutely same ideas this morning (forget huge u8 lib because of space 10kbytes code and write only couple of procedures with spi.h)... but I stopped my effort cause I dont know hot to do it. Now its easy!!!
ReplyDeleteI has a sketch that works using u8g2 on a mega. I have changed your references to pin 10 to pin 53, but doesn't work. In one instance changing between u8g2 and your code it displayed your data, usually it just shows a line of one or two of the pixels from the text row. Any ideas?
ReplyDeleteIs the SPI clock speed too high?
DeleteI tried lower clock speeds (down to 100000). I also rebuilt the circuit with a nano. Exact same result. The nano worked with the GraphicsTest sample from the u8g2 library, but couldn't get your code to run.
Deletefor some reason its not working for me, but working well with u8g2(U8G2_ST7920_128X64_F_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* CS=*/ 10, /* reset=*/ 8);
Delete)
Can i use 12864 lcd from transistor tester t4
ReplyDeleteIf it has ST7920 controller, then yes, you can. I don't know the controller used by that LCD.
DeleteThank you for this, especially for spi commands
ReplyDeleteThank you very much for the help. However I fear the SPI mode may be incorrect, since the datasheet seems to correlate with mode 0, and this is the only clock mode I had success with using the same ST9720 driver. Other than that, stuff like the data rate I would have never guessed, so really appreciate the help with that :)
ReplyDeleteAlso just a tip for other users, if you’re using crappy quality jumper cables like I was, you’ll want to lower the data rate even further for this to be reliable. Especially for when you are writing lots of data to graphic RAM. It’s slower but much much more reliable. Solid core wire though improves bandwidth greatly from my experience so that’s something to experiment with…
DeleteI had a similar issue as some comments above with it not working, the issue is that the ST7920 seems to be quite slow compared to the Arduino, so I used clock_prescale_set(clock_div_2); to slow down the Arduino's clock and therefore reduce the SPI speeds. You can also tune the SPI speed and use SPI.setClockDivider(divider); too but the clock_prescale on its own worked best for me. Hope that helps.
ReplyDelete