How to create a simple menu with maximum 4 rows on ST7920 LCDs, without library on Arduino.
I created a simple RGB Controller that uses a rotary encoder to select and adjust duty cycle of each diode of an RGB LED. By pushing the encoder button you cycle through menu items and by rotating the knob you adjust PWM signal duty cycle.
I've written the basic functions for communicating with ST7920 in the previous article. Now, let's add what's missing. First of all, to start drawing on screen, we need to switch to graphic mode. To do this, there's the Function Set register (see datasheet, page 16-17). First, we set RE bit to 1 to switch to extended function mode and then we set G bit to 1. To revert back to text mode, RE bit should be cleared. This could be an implementation:
void ST7920_setGraphicMode(boolean enabled = true) { ST7920_Write(LCD_COMMAND, enabled ? LCD_EXTEND_FUNCTION : LCD_BASIC_FUNCTION); if (enabled) ST7920_Write(LCD_COMMAND, LCD_EXTEND_FUNCTION | 0x02); // Graphic ON }Now, you have access to graphic RAM. But, after reset, this memory is filled with random data. We need to clear it before drawing something relevant on screen. Before writing a function to clear the memory, let's see how it is organized.
ST7920 graphic memory organization |
In graphic mode, you get access to 16 horizontal pixels at a time. First, you select the row on Y axis (0 to 31) then one of the 16 pixel groups (first 8 on top half, second 8 on bottom half). 0x80 is added to coordinates because this is the RAM base address. To clear RAM we have to write 0x00 to all pixel groups. We don't care about display organization, we just treat the display as 256x32.
void ST7920_ClearGraphicMem() { for (byte x = 0; x < 16; x++) for (byte y = 0; y < 32; y++) { ST7920_Write(LCD_COMMAND, 0x80 | y); ST7920_Write(LCD_COMMAND, 0x80 | x); ST7920_Write(LCD_DATA, 0x00); ST7920_Write(LCD_DATA, 0x00); } }The RAM is cleared progressively, row by row.
Now we need to highlight the title "RGB Controller". All pixels from Y = 0 to 14 and X = 0 to 7 need to be filled. The rectangle is 15 pixels height, not 16 as it would fill the entire row and be in contact with the selection rectangle on first menu item.
ST7920_setGraphicMode(true); ST7920_ClearGraphicMem(); // highlight title for (byte y = 0; y < 15; y++) for (byte x = 0; x < 8; x++) { ST7920_Write(LCD_COMMAND, 0x80 | y); ST7920_Write(LCD_COMMAND, 0x80 | x); ST7920_Write(LCD_DATA, 0xFF); ST7920_Write(LCD_DATA, 0xFF); } ST7920_setGraphicMode(false);First, enable graphic mode, clear RAM then start drawing. At the end, revert to text mode. You can put text before or after drawing the rectangle, the result is the same.
Next, we need to draw item selection rectangle. To ease with deselecting menu items, the same function must be able to clear the rectangle too. Another way would have been to clear graphics memory, then redraw title filled rectangle and new selection rectangle. Item selection rectangle must appear on rows 2 to 4. Not on the first row which contains title. If you are using my code for a different application, without title on top row, you should know that the following function is able to draw selection rectangle on each of the 4 rows.
The selection rectangle is made up of two horizontal lines on top and bottom of row, and two vertical lines at the left and right edges. Horizontal lines are easier to draw because you fill entire fixel groups on a row (0xFF). For the left vertical line, you set only the MSB of first pixel group (0x80) on a column. For the right one, you set the LSB (0x01) of the second byte in the last pixel group. The complete function could look like this:
void ST7920_HighlightMenuItem(byte idx, boolean fill = true) { idx &= 0x03; // 4 rows only byte y = idx * 16; byte x_addr = 0x80; // adjust cooridinates and address if (y >= 32) { y -= 32; x_addr = 0x88; } for (byte x = 0; x < 8; x++) { ST7920_Write(LCD_COMMAND, 0x80 | y); ST7920_Write(LCD_COMMAND, x_addr | x); fill ? ST7920_Write(LCD_DATA, 0xFF) : ST7920_Write(LCD_DATA, 0x00); fill ? ST7920_Write(LCD_DATA, 0xFF) : ST7920_Write(LCD_DATA, 0x00); ST7920_Write(LCD_COMMAND, 0x80 | y + 15); ST7920_Write(LCD_COMMAND, x_addr | x); fill ? ST7920_Write(LCD_DATA, 0xFF) : ST7920_Write(LCD_DATA, 0x00); fill ? ST7920_Write(LCD_DATA, 0xFF) : ST7920_Write(LCD_DATA, 0x00); } for (byte y1 = y + 1; y1 < y + 15; y1++) { ST7920_Write(LCD_COMMAND, 0x80 | y1); ST7920_Write(LCD_COMMAND, x_addr); fill ? ST7920_Write(LCD_DATA, 0x80) : ST7920_Write(LCD_DATA, 0x00); ST7920_Write(LCD_DATA, 0x00); ST7920_Write(LCD_COMMAND, 0x80 | y1); ST7920_Write(LCD_COMMAND, x_addr + 7); ST7920_Write(LCD_DATA, 0x00); fill ? ST7920_Write(LCD_DATA, 0x01) : ST7920_Write(LCD_DATA, 0x00); } }I used a boolean variable to change the behavior of the draw function. Therefore this function can also clear a selected menu item. Therefore the following code can cycle through menu items at a push of a pulled-up button.
if (digitalRead(ENC_BTN) == LOW) { ST7920_setGraphicMode(true); ST7920_HighlightMenuItem(selectedItem, false); selectedItem += 1; if (selectedItem > 3) selectedItem = 1; ST7920_HighlightMenuItem(selectedItem, true); ST7920_setGraphicMode(false); delay(200); }Variable selectedItem holds current selection and it is an unsigned 8 bit number.
There's only one thing left to do. The previously written function for text display isn't too versatile. Now I need to change only the percent value, not any other text. So, I write a new function that writes the percent at the right address.
void displayValue(byte percent) { byte addr; switch (selectedItem) { case 1: addr = 0x90 + 4; break; case 2: addr = 0x88 + 4; break; case 3: addr = 0x98 + 4; break; } String p = String(percent * 100 / 256); p = p + "% "; ST7920_Write(LCD_COMMAND, addr); for (byte i = 0; i < 4; i++) { ST7920_Write(LCD_DATA, p.charAt(i)); } }It also uses global variable selectedItem to know on what row to change value.
Breadboard project of ST7920 "RGB Controller" with rotary encoder |
dear sir,do you have this code in avr(32,128,16).plz reply .
ReplyDeleteThis code is for ST7920 LCD connected on SPI bus only.
Deletecould you change this code in avr.thanks
DeleteHello, just a question. I am programming ST7920 with Raspberry Pi and I could get only 75 kHz communication, even if I used level shifter from 3.3V to 5V. This means it takes 670 ms to clear the screen, which is very long. What is your best time?
ReplyDeleteI have configured SPI clock to 200 kHz, which is low. Clearing the screen was slow for me too. The ST7920 is not able to process sent data if clock speed is too high.
DeleteAhh, I've just used better level shifter and now I can go up to 10 MHz, reducing screen refresh time to around 200ms. By the way, according to manual tSYSC = 400ns @5V, which suggests 2.5 MHz frequency. Things are just getting more mysterious.
DeleteYes, I know the manual. Yet for me even when configured for 400 kHz it was not reliable. I didn't have to use a level shifter on Arduino, maybe the quality of the hookup wires was poor.
Delete