In this post I will begin with a short discussing on different paradigms for user interfaces, and give you an example for a GUI (graphical user interface) I find most useful:

User interfaces

Many projects that include a microcontroller require some kind of a user interface, either to interact with the operation of the device or to change internal configurations easily.

The most straight forward way to make a UI for your project is to designate a knob or a button for each functionality. This interfaces are great! anyone that handled an oscilloscope or a soundboard would agree, you can manipulate each and every parameter by rotating or sliding the right knob.

These are not only easy to use, they are also easy to implement in software, however, the mechanical design is much more demanding and expansive, as the panel must be designed, drilled and assembled, and there a limit to the functionality you can fit in a given panel size.

Another popular approach is to control the device via a PC or a smartphone. The interface can be a GUI that wraps a serial interface transferred via USB2Serial, bluetooth or WiFi. Sometimes, if the microcontroller is strong enough, a web-server can be installed on the device, which can be very handy.

The flaw of these UI implementation is in the dependability on an external machine to communicate with the device, as the GUI that wraps the underlying interface may not be compatible with all operational systems, or just be a pain to install on a new machine. Even more so, ten years from now, the GUI may be totally deprecated to any OS.

Communication and especially wireless communication with the device may not always be simple, as it is not always transparent on how to initiate the communication – The user may fail to remember the correct baudrate, or have problems configuring the IP of the device.

The UI I found most useful for the kind of projects that I make, is the prusa LCD screen + rotary encoder (combined with an underlying serial interface for remote control). It is extremely cheap, and easy to assemble, and allows to modify a vast number of configuration with only one finger!

This approach may be clunky if the user wishes to change multiple parameters quickly, also, it is also much more complicated to implement in code. Luckily, the second disadvantage I named is greatly reduced by the help the open source community, as I found a great library called ‘menusystem‘ that creates menus very easily.

One great thing about the implementation of the menu system, is that it separates the rendering the the menu objects, making the menu portable for different display devices, as the menusystem is more of virtual construct, and the rendering part handles how the menusystem will be displayed.

Our setup

The setup includes an Arduino nano, a rotary encoder for scrolling the menu alongside an integrated push button, an RGB LED, an LDR (light dependent resistor) , a servo motor, and an OLED display (128×64).

The servo motor is controlled by the menu to go to any desirable angle, the RGB LED color is controlled by three PWM output pins, all configurable in the menu, and lastly an LDR that measures the light intensity of the led, and returns a value which is constantly updated in the menu system.

The code (available for download here) is composed of several independent entities:

Menusystem – handles the menu tree, where each node can be a a leaf or a subtree, it includes the textual data, and control values of these items.

MyRenderer – realizes the menusystem to a display, it falls on the designer to define how he wants the menu to appear on the screen.

input interface – handles the input hardware, and how it effects the menu.

application – the code that makes up for the purpose of the device, and how it reacts to user input to the menu.

This separation allows for the user to reuse the same code for different displays or user input hardware, only by changing the behavior of the relevant part.

Display initialization

Before we define the hierarchy of our menu, we must define the display hardware:

//#############OLED CONFIG################# #define SCREEN_WIDTH 128 // OLED display width, in pixels #define SCREEN_HEIGHT 32 // OLED display height, in pixels // Declaration for an SSD1306 display connected to I2C (SDA, SCL pins) #define OLED_RESET 4 // Reset pin # (or -1 if sharing Arduino reset pin) Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); 1 2 3 4 5 6 7 //#############OLED CONFIG################# #define SCREEN_WIDTH 128 // OLED display width, in pixels #define SCREEN_HEIGHT 32 // OLED display height, in pixels // Declaration for an SSD1306 display connected to I2C (SDA, SCL pins) #define OLED_RESET 4 // Reset pin # (or -1 if sharing Arduino reset pin) Adafruit_SSD1306 display ( SCREEN_WIDTH , SCREEN_HEIGHT , &Wire , OLED_RESET ) ;

As you can see I use the adafruit driver for the OLED screen(Adafruit_SSD1306), alongside the adafruit graphic engine(Adafruit_GFX). To link the display driver to the menu system, we write the following lines:

MyRenderer my_renderer(&display, textRows); MenuSystem ms(my_renderer); 1 2 MyRenderer my_renderer ( & display , textRows ) ; MenuSystem ms ( my_renderer ) ;

Menu items declarations

The menu items are declared in the following snippet:

Menu ledMenu("LED MENU", nullptr); BackMenuItem ledBackItem("Back", &on_ledBackItemSelected, &ms); ToggleMenuItem ledPowerItem("LED Power - ", nullptr, "ON", "OFF", false); NumericMenuItem ledRedItem("Red", nullptr,0,0,255); NumericMenuItem ledGreenItem("Green", nullptr, 0, 0, 255); NumericMenuItem ledBlueItem("Blue", nullptr, 0, 0, 255); NumericDisplayMenuItem photoTransistorItem("Sensor", nullptr, 0); Menu servoMenu("", nullptr); BackMenuItem servoBackItem("Back", on_servoBackItemSelected, &ms); ToggleMenuItem servoPowerItem("Servo Power - ", &on_servoPowerItemSelected, "ON", "OFF", false); NumericMenuItem servoAngleItem("Set Angle", nullptr, 0, 0, 180); 1 2 3 4 5 6 7 8 9 10 11 Menu ledMenu ( "LED MENU" , nullptr ) ; BackMenuItem ledBackItem ( "Back" , & on_ledBackItemSelected , & ms ) ; ToggleMenuItem ledPowerItem ( "LED Power - " , nullptr , "ON" , "OFF" , false ) ; NumericMenuItem ledRedItem ( "Red" , nullptr , 0 , 0 , 255 ) ; NumericMenuItem ledGreenItem ( "Green" , nullptr , 0 , 0 , 255 ) ; NumericMenuItem ledBlueItem ( "Blue" , nullptr , 0 , 0 , 255 ) ; NumericDisplayMenuItem photoTransistorItem ( "Sensor" , nullptr , 0 ) ; Menu servoMenu ( "" , nullptr ) ; BackMenuItem servoBackItem ( "Back" , on_servoBackItemSelected , & ms ) ; ToggleMenuItem servoPowerItem ( "Servo Power - " , & on_servoPowerItemSelected , "ON" , "OFF" , false ) ; NumericMenuItem servoAngleItem ( "Set Angle" , nullptr , 0 , 0 , 180 ) ;

The menu declarations above are made of the following item types:

Menusystem – the root node of the tree

Menu – contains any son items, when triggered the menu enters its subtree.

back – lets the user to go back one level on the menu tree by triggering it.

ToggleMenuItem – switches between two states, like on/off states. When triggered it toggles the sate.

NumericMenuItem – contains a numeric value, when triggered, it is available for scrolling it up and down for editing the number.

NumericDisplayMenuItem -shows a numeric value, unlike the former, it is not configurable. It is used here to show the value of the LDR sensor.

The input parameters vary according to the menu item type, but all have the same first two parameters, a display string for the menu item, and a callback function that is called whenever a menu is pressed (or a ‘nullptr’ of you don’t need the it).

All the menu items above are part of the original Menusystem library, all but the ToggleMenuItem and NumericDisplayMenuItem , which were custom made by me. You can also add any kind of menu you’d like by taking any of the item classes as a template (the original ones or the on i made) and add a realization in the MyRenderer library. I must say that it is not as easy to do as I would have liked…

Menu Hierarchy

The definition of the menu is done after the setup() function is called, as it has to be used in the runtime of the uC:

ms.get_root_menu().add_menu(&ledMenu); ledMenu.add_item(&ledBackItem); ledMenu.add_item(&ledPowerItem); ledMenu.add_item(&ledRedItem); ledMenu.add_item(&ledGreenItem); ledMenu.add_item(&ledBlueItem); ledMenu.add_item(&photoTransistorItem); ms.get_root_menu().add_menu(&servoMenu); servoMenu.add_item(&servoBackItem); servoMenu.add_item(&servoPowerItem); servoMenu.add_item(&servoAngleItem); 1 2 3 4 5 6 7 8 9 10 11 12 ms . get_root_menu ( ) . add_menu ( & ledMenu ) ; ledMenu . add_item ( & ledBackItem ) ; ledMenu . add_item ( & ledPowerItem ) ; ledMenu . add_item ( & ledRedItem ) ; ledMenu . add_item ( & ledGreenItem ) ; ledMenu . add_item ( & ledBlueItem ) ; ledMenu . add_item ( & photoTransistorItem ) ; ms . get_root_menu ( ) . add_menu ( & servoMenu ) ; servoMenu . add_item ( & servoBackItem ) ; servoMenu . add_item ( & servoPowerItem ) ; servoMenu . add_item ( & servoAngleItem ) ;

Encoder user interface

The physical interface of this device is a rotary encoder (EC11) which encodes four different states along its rotation using two pins:

To detect whether the encoder was rotated one step CW or CCW we use the following code to scroll the menu up and down:

void knob_isr() { if (digitalRead(knobAPin)) { if (digitalRead(knobBPin)) //knob moved CW ms.next(); else if (!digitalRead(knobBPin)) //knob moved CCW ms.prev(); } } 1 2 3 4 5 6 7 8 9 10 void knob_isr ( ) { if ( digitalRead ( knobAPin ) ) { if ( digitalRead ( knobBPin ) ) //knob moved CW ms . next ( ) ; else if ( ! digitalRead ( knobBPin ) ) //knob moved CCW ms . prev ( ) ; } }

Where knobAPIN and knobBPIN are the two state pins of the encoder

The snippet is embedded inside an interrupt function that is called whenever knobAPin changes, giving us fast response to the user.

whenever the user wants to select a menu, he presses the rotary encoder, and the following function is called by an interrupt service:

void pbPressed() { ms.select(); } 1 2 3 4 void pbPressed ( ) { ms . select ( ) ; }

This triggers the menu callback function (if there is one) and triggers an action on the menu depending on its type (e.g. toggle the value, enter number editing mode, entering a submenu, etc..).

Summary

I have shown you an example on how you can make a GUI very easily using an encoder and an OLED screen. I hope that this example can be helpful as skeleton for adding GUI into your project. If I will find that this post is of interest to the community, then I will make a guide on how you can make your own GUI items and how to create a renderer for different displays (LCD, LED matrtix, etc…)