How to print a GUI to an OLED?

Hi there. So I'm using an Arduino Nano Every for a smart watch, and at the moment it's just a basic digital watch that prints the time and date to an SSD1320 flexible OLED (eventually I will switch over to an ESP32 TinyPICO). For now, I'd like to try and figure out if it's possible to program a basic GUI that prints to the display, which I can navigate using physical push buttons, to do things like set alarms, timers, etc. I couldn't seem to find much on this topic except for using the Processing IDE and the Control P5 library, but from what I could tell that GUI only exists in an IDE window? If anyone can shed some light on this or point me in the direction of some resources that I can use, that would be greatly appreciated. Thanks guys!

Don't get too tripped up on it being a GUI. It's just an image or some font you need to display and then program the nearby buttons to do their thing.

1 Like

What is possible:
320x132 White 3.83 inch SPI OLED Display Double SSD1320 16-level Grayscale - YouTube

But, your talk of a menu is essentially "character-based". Graphic menu can be done, but will consume more SRAM than text.

There is an Arduino menu library:
ArduinoMenu library - Arduino Reference

Ray

1 Like

the answer is yes, you can do all those things. to many different degrees. How complex does this thing need to be? how dynamic does it need to be?

I've done a few from scratch, its not a lot of fun...mostly just tedious work laying everything out. I'll attach a class I wrote for my last one, ifs very purpose built, so you wont be able to just use it. but it will give you an idea.

There are a bunch of libraries writing on the subject, I've not actually used any of them, so I cant recommend one specifically, just that you investigate them(i plan to the next time i need a menu)

processing is java code that runs on the computer, not part of arduino idea, but a different one called processing. Not what you'd use for the task at hand.

tmenu.cpp (44.3 KB)
tmenu.h (4.6 KB)

1 Like

Have a look at Nexion displays or these nice round ones on eBay have a lot of support ( google) and also allow a background picture to be uploaded

Put this in eBay -
“ round LCD Display Module 240×240 Resolution
GC9A01 Driver 4 Wire SPI Interface Board”

This display stuff is not hard! The first order of business is to understand your graphic display coordinates: example is the common ILI9341 and the associated Adafruit library:

/*************************** ILI9341 320x240 DISPLAY LAYOUT ****************************
0,0
----------------------------------------------------------------------------> 319
| nnnT                                                                 DOW
|              lcd.fillRect( 0,  0, 319, 59, 0);     // blank top
|
|
|<- 059 
|
|              lcd.fillRect( 0, 60, 319, 114, 0);     // blank middle
|                              HH:MM:SS A
|
|
|
|<- 174
|              lcd.fillRect( 0, 175, 319, 64, 0);     // blank lower
|
| MON DD YYYY
|
|<- 239
*/

Use a text editor or graph paper, mark the corner coordinates. Then in "overwrite mode", position major text features for captions or elements.

The rough sketch can be left in your Arduino code as a comment for reference!

As can be seen above, very little programming is needed to produce a nice display and informative!

void setup()  {
  lcd.begin();
  lcd.setRotation(3);                                             // landscape with SPI conn J2 to left side.
  lcd.fillScreen(ILI9341_BLACK);
  lcd.setCursor(0, 0);
  lcd.setTextColor(ILI9341_WHITE, ILI9341_BLACK);
  // lcd.setTextSize(1);                                          // Tiny   53 char / line
  // lcd.setTextSize(2);                                          // Small  26 char / line
  lcd.setTextSize(3);                                             // Medium 17 char / line  10Pixels Wide x 25 Pixels / line
  //lcd.setTextSize(4);                                           // Large  13 char / line
  lcd.setCursor(0, 0);
  lcd.print("Testing circuit:\n\r\n\r");

It is important to note how various size fonts will change character mapping per line.

Often, the whole screen is only drawn once in setup{} although one may wish to create a function for such an event if program logic requires the screen to be cleared & repainted.

  lcd.drawFastHLine(0, 23, 319, ILI9341_RED);                     // Hello Screen
  lcd.setTextColor(ILI9341_CYAN, ILI9341_BLACK);
  lcd.setCursor(0, 25);
  lcd.print("by: Ray Burnette");
  lcd.setCursor(0, 50);
  lcd.println("Barometric Pres.");

  lcd.setTextColor(ILI9341_GREEN, ILI9341_BLACK);
  lcd.setTextSize(1);
  lcd.setTextWrap(false);
  lcd.fillScreen(ILI9341_BLACK);
  lcd.setCursor(30, 100);
  lcd.print( "History updates every 36 minutes");                 // 48 hours for entire display
}

Now, things on-screen like the graph and numeric data can be output in loop{}

void loop()  {

  barometer.setControl(BMP085_MODE_TEMPERATURE);                  // request temperature
  lastMicros         = micros();
  while (micros() - lastMicros < barometer.getMeasureDelayMicroseconds()); // wait for conversion (4.5ms delay)

  if (Fahrenheit) {                                               // read calibrated temperature value in degrees Fahrenheit
    temperature      = barometer.getTemperatureF();
  } else {
    temperature      = barometer.getTemperatureC();
  }

  temperature = temperature - 4.0;                                 // individual unit correction by observation (Anagela's unit)

  lastMicros         = micros();
  barometer.setControl(BMP085_MODE_PRESSURE_3);                   // request pressure (3x oversampling mode, high detail, 23.5ms delay)
  while (micros() - lastMicros < barometer.getMeasureDelayMicroseconds());

  pressure           = barometer.getPressure();                   // read calibrated pressure value in Pascals (Pa)
  pressure          += localCorrect;                              // rough adjustment ... as we are plotting, non-adjusted pressure is just as appropriate
  inHg               = double((pressure) * mmhg_conversion * 10.0);
  M                  = pressure / 100L;                           // scale to hPa
  // graph values are typically between a low of 985 and a high of 1040
  // clipping high/low forces the dynamic range to be limited to a maximum change of 45 hPa
  if (M > 1040L)  M  = 1040L;                                     // clip (flatten) ceiling
  if (M < 985L)   M  =  985L;                                     // clip (flatten) floor

  if ((TimeMarker + GraphResponsemS) < millis() )
  { // machine clock has advanced beyond trigger, so store current pressure
    TimeMarker     = millis();                                  // reset trigger for future (next) event
    StoreHistory();                                             // update pHistory matrix
    lcd.fillRect(xRect, yRect, wRect, hRect, 0) ;               // erase history graph area with transparancy
    displayHistory();                                           // Intent is to only display on command
  }
  displayCurrent();                                               // Header and current sensor data
  drawLegends();                                                  // Grid and right-hand graph legends
}


void StoreHistory ( void ) {                                      // Creates a boxed border using lines 0 & 1
  int  n;                                                         // avoid compiler complaints in for(;;) loop
  // array [0] is reserved for newest value                       // Therefore [80] = [79], [79] = [78],..., [1]==[0] after shift
  for (n = 80; n > 0; n--)                                        // shift history array to the right, oldest value [80] is lost
  {
    pHistory[n]      = pHistory[(n - 1)];                         // shift data right, oldest value lost, [1] becomes [0] the current value
  }
  pHistory[0]        = M;                                         // save current "adjusted" pressure hPa [0]
}


void displayCurrent( void ) {
  int temp1, temp2;                                               // working vars for type conversion

  lcd.setTextSize(3);                                             // Print hPa and inHg legends and readings in large font
  lcd.setCursor(0, 1);  lcd.print("hPa   inHg   temp");
  lcd.setCursor(0, 35); lcd.print( M );

  if ( M < 1000 ) {
    lcd.print("   ");
  } else {
    lcd.print("  ");
  }

  temp1              = inHg / 10;                                 // Integer component
  temp2              = ((inHg / 10) - (float) temp1 ) * 100;      // 2 decimal places

  lcd.print(temp1); lcd.print(".");
  lcd.print(temp2);

  lcd.setCursor(235, 35);
  lcd.print( temperature, 0 );

  if (Fahrenheit) {
    lcd.print(" F");
  } else {
    lcd.print(" C");
  }
}


void displayHistory( void ) {
  long     M;
  long     minimum   = 1040L;
  long     maximum   = 985L;

  uint8_t  h, z;
  uint16_t x, y;

  z = 0;                                                          // z will span 0 to 319, full screen landscape width in pixels

  for ( x = 1; x < 240; x++ )                                     // 80 elements @1 element per 18 minutes = 10 elements per 3 hours
  {
    if ( x % 3 == 0 ) {                                           // this provides for skipping every 3th position for bar spacing
      z++;
    } else if (pHistory[z] != 0) {                                // array is initially init to zero value, no need to plot this
      if (pHistory[z] < minimum) minimum = pHistory[z];           // capture the lowest  24 hour pressure reading
      if (pHistory[z] > maximum) maximum = pHistory[z];           // capture the highest 24 hour pressure reading
      h = (pHistory[z] - 985) * 2;                                // h is for height and x2 is for scaling factor
      y = 200 - h;                                                // Adafruit's GFX y coordinates plots upside down, flip reference
      lcd.drawFastVLine(x,  y,   h, ILI9341_WHITE);               // 2 bars are drawn per array element and 1 blank space for separation
    }
  }

  y = 200 - ((minimum - 985) * 2);
  
  for (x = y ; x < 200; x++) {                                    // Alternate display mode
    lcd.drawFastHLine(1, x, 245, ILI9341_BLACK);
  }

//    drawFastHLine(uin86_t x0, uin86_t y0, uint8_t length, uint16_t color);
  lcd.drawFastHLine(1, y, 245, ILI9341_WHITE);                    // draw baseline   24 hour lowest reading
  y = 200 - ((maximum - 985) * 2);
  lcd.drawFastHLine(239, y, 6, ILI9341_YELLOW);                   // draw marker tic 24 hour highest reading
}


void drawLegends( void )
{
  // Legends and axis for display
  lcd.setTextSize(1);
  // Atlanta: High = 30.79 on 1/6/1924	Low = 29.08 on 1/11/1918
  // 2X scaling: 1040 - 1026 = 14 x 2 = 28 pixels
  lcd.setCursor(240,  84); lcd.print("_ 1040= 30.71");            // setup Y legends
  lcd.setCursor(240, 112); lcd.print("_ 1026= 30.30");
  lcd.setCursor(240, 140); lcd.print("_ 1012= 29.88");
  lcd.setCursor(240, 168); lcd.print("_  998= 29.47");
  lcd.setCursor(240, 194); lcd.print("_  985= 29.09");
  // tiny fonts for timeline legend @53 char/line (6px/char)
  lcd.setCursor(0, 205); lcd.print("N        ^         ^         ^         ^  Historical ");
  lcd.setCursor(0, 213); lcd.print("O        1         2         3         4  Timeline   ");
  lcd.setCursor(0, 221); lcd.print("W        2         4         6         8  In Hours   ");
  lcd.drawFastVLine(0,  88, 112, ILI9341_RED);                    // setup Y axis
  lcd.drawFastHLine(0, 200, 238, ILI9341_RED);                    // setup X axis
  lcd.drawFastHLine(0, 201, 238, ILI9341_RED);                    // setup X axis double-width X axis for emphasis
}

BP180_GLCD_48.zip (33.3 KB)

does your do any other than print to the display? Thats what the op asked for, and that DOES take a bit of programming. Not really difficult or anything, but lots of code.

These seem very convenient, if your willing to spend the money on their screens...which are pricey. But they built a whole computer GUI to help you build your uC GUI. Do you remember "frontpage", it basically wrote the html for a webpage for you. Nexion is similar in concept.

1 Like

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.