ST7565 Menu
I recently completed an independent school project for which I designed and built a prototype of a hand-held sensor platform. It consisted of an ATmega328, a ST7565 LCD and a couple sensors. I ended up using the Arduino environment with Adufruit Industries' ST7565 Arduino library. Being tired of writing new code every time I wanted some sort of LCD user interaface, I set out to create my own API on top of Adafruit's library.
The basic structure I was after was a menu based UI, involving layers of menus and sub-menus, each consisting of different selectable items, whose functions were all user-defined. This ended up being an easier task than it sounds, and I ended up with a rather cool library which I've dubbed the exceedingly creative name of ST7565 Menu.
The library creates, in my opinion at least, a very easy and intuitive API for menu creation. The idea is that you would create a single menu object, then use a collection of methods to add items to it, with the ability to attach functions to each item that will be called when selected. Here's the code for one of the examples included with the library:
simple_menu.pde - 11/2011
Basic example of ST7565_Menu Arduino library.
Remember the ST7565 uses 3.3v, so a level shifter
is needed if using a standard Arduino board.
See Adafruit tutorial for more details:
http://www.ladyada.net/learn/lcd/st7565.html
*/
#include <ST7565.h> // Adafruit LCD library
#include <ST7565_Menu.h>
// Menu controls:
#define UP_PIN 3
#define DOWN_PIN 4
#define SELECT_PIN 2
// LCD pins:
#define BACKLIGHT_LED 10
#define LCD_SID 9
#define LCD_SCLK 8
#define LCD_A0 7
#define LCD_RST 6
#define LCD_CS 5
// Must create an ST7565 instance before Menu:
ST7565 glcd(LCD_SID, LCD_SCLK, LCD_A0, LCD_RST, LCD_CS);
// Create Menu with control pins and address of glcd:
Menu menu(UP_PIN, DOWN_PIN, SELECT_PIN, &glcd);
int backlight_state = 0; // 0-Off, 1-On
void setup() {
pinMode(BACKLIGHT_LED, OUTPUT);
glcd.begin(0x18); // Initialise LCD
delay(10); // Just to be safe
toggle_backlight();
show_menu(); // Create menu
}
void show_menu() {
// Title always shown on first line:
menu.set_title("==Simple Menu==");
// Create an item which will call toggle_backlight()
// when selected:
menu.add_item("Toggle Backlight", toggle_backlight);
// Add a couple items that do nothing when selected:
menu.add_item("Menu Item 2");
menu.add_item("Menu Item 3");
}
void toggle_backlight() {
backlight_state = backlight_state^1;
digitalWrite(BACKLIGHT_LED, backlight_state);
}
void loop() {
// Now we just need to update the menu each time
// through the main loop, it takes care of the
// rest for us!
menu.update();
}
If you ignore the comments, the function show_menu() that actually creates the menu is only four lines long, and consists of only two functions: set_title() and add_item(). The order of the menu is simply defined by the order in which items are added.
Here's a video of this sketch running:
And a slightly more complicated demo, which is also an included example sketch:
menu_test.pde - 11/2011
A more complicated example of ST7565_Menu Arduino library.
Remember the ST7565 uses 3.3v, so a level shifter
is needed if using a standard Arduino board.
See Adafruit tutorial for more details:
http://www.ladyada.net/learn/lcd/st7565.html
*/
#include <ST7565.h> // Adafruit LCD library
#include <ST7565_Menu.h>
// Menu controls:
#define UP_PIN 3
#define DOWN_PIN 4
#define SELECT_PIN 2
// LCD pins:
#define BACKLIGHT_LED 10 // Must be a PWM pin
#define LCD_SID 9
#define LCD_SCLK 8
#define LCD_A0 7
#define LCD_RST 6
#define LCD_CS 5
// Must create an ST7565 instance before Menu:
ST7565 glcd(LCD_SID, LCD_SCLK, LCD_A0, LCD_RST, LCD_CS);
// Create Menu with control pins and address of glcd:
Menu menu(UP_PIN, DOWN_PIN, SELECT_PIN, &glcd);
//Backlight PWM values:
uint8_t brightness_levels[7] = { 0, 20, 50, 90, 130, 190, 250 };
uint8_t brightness_index; // Index of brightness_levels
void setup() {
pinMode(BACKLIGHT_LED, OUTPUT);
glcd.begin(0x18); // Initialise LCD
delay(10); // Just to be safe
brightness_index = 1; // Initial brightness
set_brightness(0);
show_main(); // Draw main menu
}
// Draw main menu screen:
void show_main() {
menu.clear(); // Clear menu and display
// If menu.update() called 70 times, light_of() will be called:
menu.add_timeout_function(70, light_off);
// Title always shown on first line:
menu.set_title("Example Menu:");
// Add items with function to be called when selected:
menu.add_item("Read Analog", show_analog);
menu.add_item("Set Brightness", show_brightness);
menu.add_item("Scroll Test", scroll_test);
}
// Draw analog display menu:
void show_analog() {
menu.clear();
// draw_analog() will be called after menu items are
// drawn and before glcd.display() is called:
menu.add_draw_function(draw_analog);
menu.set_title("Analog Value:");
menu.add_item("Back", show_main);
}
// Draw brightness menu:
void show_brightness() {
menu.clear();
// Back to main menu after 40 a inactive loops:
menu.add_timeout_function(40, show_main);
menu.add_draw_function(draw_brightness);
menu.set_title("Brightness:");
// These items will pass their integer values
// (1 and -1) to set_brightness:
menu.add_item("Up", 1, set_brightness);
menu.add_item("Down", -1, set_brightness);
menu.add_item("Back", show_main);
}
// Fill a menu to demonstrate scrolling:
void scroll_test() {
uint8_t i;
char value[12]; // To hold counter value
char label[22]; // To hold item label
menu.clear();
menu.add_timeout_function(40, show_main);
menu.set_title("Scroll Test:");
menu.add_item("Back", show_main);
for (i=1; i<=MAX_ITEMS-2; i++) {
strcpy(label, "Menu Item "); // First part of label
itoa(i, value, 10); // Get counter value
strcat(label, value); // Append counter string
menu.add_item(label); // Add it
}
menu.add_item("Back", show_main);
}
// Convert and display ADC values from A0 and A1:
void draw_analog() {
char buffer[12];
int value;
value = analogRead(A0);
itoa(value, buffer, 10);
glcd.drawstring(20, 3, "A0:");
glcd.drawstring(44, 3, buffer);
value = analogRead(A1);
itoa(value, buffer, 10);
glcd.drawstring(20, 4, "A1:");
glcd.drawstring(44, 4, buffer);
}
// Lower brightness if dir<0, raise if dir>0
void set_brightness(int dir) {
if (dir < 0) {
if (brightness_index > 0) brightness_index--;
}
if (dir > 0) {
if (brightness_index < 6) brightness_index++;
}
analogWrite(BACKLIGHT_LED, brightness_levels[brightness_index]);
}
// Draw lower section of brightness screen:
void draw_brightness() {
char buf[12];
// Just display brightness_index as brightness level:
itoa(brightness_index, buf, 10);
glcd.drawstring(LEFT_MARGIN+10, 5, buf);
// Visual level display:
glcd.drawrect(LEFT_MARGIN+17, 40, 60, 8, BLACK);
glcd.fillrect(LEFT_MARGIN+17, 40, 10*brightness_index, 8, BLACK);
}
void light_off() {
uint8_t brightness = brightness_index; // Save current level
brightness_index = 0;
set_brightness(0);
// Hold until button press then reset brightness:
while (digitalRead(UP_PIN)&digitalRead(DOWN_PIN)&digitalRead(SELECT_PIN));
brightness_index = brightness; //
set_brightness(0);
}
void loop() {
// Now we just need to update the menu each time
// through the main loop, it takes care of the
// rest for us!
menu.update();
}
And menu_test.pde running: (I forgot to wait long enough here for the timeout function defined on line 51 to be called.)
Notice in both the analog read and brightness adjust windows I've simply created short menus, then used the Adafruit ST7565 library to draw the rest of the LCD using the add_draw_function() method.
Download library here: ST7565 Menu
And here's the complete API (also found in the README file included with the library):
Menu(uint8_t up_pin, uint8_t down_pin, uint8_t select_pin, ST7565 *lcd) Creates and returns an instance of the Menu object. up_, down_ and select_pins are the IO pins where the control buttons are connected. The buttons must be configured active-low with external pullups. lcd should be the address of an instance of the Adafruit ST7565 object (e.g. &ST7565_instance). Menu.set_title(char *title) Sets the title string that is displayed on the first line of the LCD. The title will remain on the first line during scrolling of the menu. Will be blank if not set. Menu.add_item(char *label) Adds a functionless item to the menu. Menu.add_item(char *label, void (*function)(void)) Adds an item to the menu. When selected the given function will be called. Menu.add_item(char *label, int value, void (*function)(int)) Adds an item to the menu. When selected the given function will be passed the given integer value. Menu.add_draw_function(void (*function)(void)) Adds a function that will be just before the menu is drawn. This is where other graphics should be drawn to the LCD. Menu can only have one draw function. Menu.add_timeout_function(int timeout, void (*function)(void)) Adds a function that will be called after Menu.update() has been called the given number of times without any of the menu controls being pressed. In order to have other controls or events restart the timeout count, one should set Menu.timeout_counter to 0. Menu.draw() Redraws the whole menu; does not update read controls or increment timeout counter. Shouldn't normally need to be called. Menu.update() This function should be called each time through the main loop to use the menu. Menu.clear() Completely resets the menu. This should be called before creating new menus.
Please what software did you use to simulate this circuit before implementing it physically on a board and please can you send me the schematic design of your project.. Thanks, you are doing great!
I did no simulations. The library was part of a school project I did, which you can take a look at here: https://cs.marlboro.edu/courses/fall2011/jims_tutorials/alex/home . The board includes a couple other things besides the LCD.
Hey!, great work!
im having a bit of an issue here,
every time i hit the up or the down button,
it scrolls 3-5 lines, and not 1 as required.
maybe there is an issue with the debouncing of the buttons?
please help.
thanks 🙂
You can increase the button debounce time by increasing the MAX_HOLD_COUNT value on line #40 of ST7565_Menu.h.
Worked :D, thanks
Hi!
well done!
that solution can be used with ST7920 LCD?
thanks!
Probably not as is, but you could swap out the LCD driver code and keep the same menu logic.
Hello Alexander,
Very nice work! Thanks for sharing.
Woud it be possible to make the menus work wit a touch scree instead of physical buttons?
I have the same display with a Nintendo DS touchscree, bu I’m having a hard time creating menus (only need two levels) and being able to adjust a variable inside a menu.
Do you have any suggestions?
Thnaks
Check out u8glib (LCD library which includes ST7565 support), which works with the m2tklib GUI library that already has touchscreen support.
Hello Ahiam,
Thanks for your suggestions.
I will definitly learn more than a couple of things in the next days….
Regards
i am having problems with using both your library and the adafruit library…i get a confilct of the ST7565 class, may you please help out , how i can avoid this but still use them both