Arduino NANO / SH1106 OLED randomly freezes using U8g2 library

Hello,
I’d be very grateful for any tips etc for solving my problem as stated. My project is a clock which displays the time on an OLED SH1106 1.3” 128x64 screen using any of three randomly selected number scales. This has worked perfectly but recently the display has become faulty - probably due to my over-tightening the screen mounting bolts during assembly. I have bought two replacement of the same type i.e. SH1106 but for the life of me I cannot get these to work for any length of time without them “freezing up”. The length of time before “freezing” varies from a few seconds to several minutes.
I was using the latest version of U8g2 because my laptop had changed but I have backed this version out (see later).
I am using the following: Arduino IDE version 1.6.13, Arduino Nano V3, DS3231 and SH1106 pre-assembled boards.

It is wired up as follows: Nano 5V -> DS3231 Vcc and OLED Vcc, Nano Gnd -> DS3231 Gnd and OLED Gnd, Nano A4 -> DS3231 SDA and OLED SDA and finally Nano A5 -> DS3231 SCL and OLED SCK. The project is powered by my PC’s USB.
Here is my code:

/* this version uses two char arrays for building the OLED display characters */ 

#include <U8g2lib.h>
#include <Wire.h>

#define DS3231_ADDRESS 0x68

//U8G2_SH1106_128X64_NONAME_1_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE); This one for OLED without a reset pin
U8G2_SH1106_128X64_NONAME_1_HW_I2C u8g2(U8G2_R0); /* and this one for OLED with a reset pin connected */

int hours = 0;                    // DS3231 clock variables
int minutes = 0;
int seconds = 0;
const byte zero = 0x00;           // For the Wire reset
const char hex_chars[6] = {'A','B','C','D','E','F'};
  
int base=8;                       // Base variables
int base_randomiser=1;

int first_digit=0;                // Base conversion variables
int second_digit=0;
int third_digit=0;
int fourth_digit=0;
int in_hours=0;
int in_minutes=0;
const int modulo_minutes=0;

unsigned long previous_millis = 0;// Blink variables
const long interval=1000;
boolean blink_on = true;
  
void setup(void) {
  Wire.begin();
  u8g2.begin(); 
  randomSeed(analogRead(A0));
}

void loop(void) {

  set_blinking();     //  Toggles bit switch to aid blinking in OLED
  get_time();
  if ((modulo_minutes==minutes%3) and (seconds==0)) {   // Change the base every three minutes to keep people awake :-)
    base_randomiser = random(1,4);
    switch(base_randomiser){
      case 1:
        base = 8;
        break;
      case 2:
        base = 10;
        break;
      case 3:
        base = 16;
        break;
    }
  }
  
  convert_time(hours,minutes,base);   // Convert time into base display variables
  show_time();      // Show the time depending on the base selected.
  delay(500);       // Keep below interval (1000) else blink won't work.
}

void set_blinking() {
   
   unsigned long current_millis = millis();
   if ((current_millis - previous_millis) >= interval) {
        previous_millis = current_millis;
        blink_on = true;
   }
   else blink_on = false;   
}

void get_time() {

   // Reset the register pointer
   Wire.beginTransmission(DS3231_ADDRESS);
   Wire.write(zero);
   Wire.endTransmission();

   Wire.requestFrom(DS3231_ADDRESS, 3);
  
   seconds = bcdToDec(Wire.read());
   minutes = bcdToDec(Wire.read());
   hours = bcdToDec(Wire.read() & 0b111111); //24 hour time
 }

byte bcdToDec(byte val)  {
// Convert binary coded decimal to normal decimal numbers
  return ( (val/16*10) + (val%16) );
}

void convert_time(int in_hours,int in_minutes, int base){
  
  // Do the hours first...
  if (in_hours >= base) {
     first_digit = in_hours / base;
     second_digit = in_hours - (first_digit * base);
  }
  else {
  first_digit = 0;
  second_digit = in_hours;
  }
  
  // Now do the minutes...
  if (in_minutes >= base) {
     third_digit = in_minutes / base;
     fourth_digit = in_minutes - (third_digit * base); 
  }
  else {
  third_digit = 0;
  fourth_digit = in_minutes;
  }
}

void show_time() {
   
  char hrs_display[3];              // OLED variables 
  char min_display[3];
  /* Build the individual characters */
  hrs_display[0] = 48 + first_digit;
  if (base == 16) {
     if (second_digit < 10)
        hrs_display[1] = 48 + second_digit;
     else 
       hrs_display[1] = hex_chars[second_digit - 10];
  }      
  else
     hrs_display[1] = 48 + second_digit;
  
  hrs_display[2] = '\0';  // Terminate the hours array
  
  min_display[0] = 48 + third_digit;  
  if (base == 16) {
     if (fourth_digit < 10)
        min_display[1] = 48 + fourth_digit;
     else 
       min_display[1] = hex_chars[fourth_digit - 10];  
  }   
  else
     min_display[1] = 48 + fourth_digit;
    
  min_display[2] = '\0';  // Terminate the minutes array

  u8g2.firstPage();
   do {
      u8g2.setFont(u8g2_font_logisoso38_tr); //u8g2_font_logisoso42_tr
      u8g2.drawStr(00,42,hrs_display);
      if (blink_on)
         u8g2.drawStr(52,42,":");
      else 
         u8g2.drawStr(52,42," ");
      u8g2.drawStr(68,42,min_display);
      u8g2.setFont(u8g2_font_helvR10_tr);
      if (base == 8)
         u8g2.drawStr(25,63,"BASE: OCT");
      else if (base == 10)
              u8g2.drawStr(25,63,"BASE: DEC");
           else u8g2.drawStr(25,63,"BASE: HEX");
      } 
   while ( u8g2.nextPage() );
}

And here are the things I have tried from various forum sources:

  1. Change the code to move some variables from global to local,
  2. Change the OLED supply voltage from 5V to 3.3V with and without additional logic level converters for the SDA and SCL lines,
  3. Added external pull-up resistors of 2.2K between the SDA and SCL and 5V,
  4. Changed the main font from u8g2_font_logisoso42_tr to u8g2_font_logisoso38_tr/tf and other smaller font sizes,
  5. Monitored the memory usage by adding freeMem() and freeRam() – no change at all during execution prior to stoppage,
  6. Added I2C trace code to identify I2C addresses in use to see if there were any conflicts,
  7. Modified the u8g2 construct to remove then re-add U8X8_PIN_NONE option,
  8. Changed the version of U8G2 software to match the version (2.24.3) as used in the initial build,
  9. Replicated the build on a breadboard in an attempt to cure any possible stray capacitance issues(?), as the project is housed in a very confined space.
  10. Changed the Wire read request from 7 bytes to 3 bytes,
  11. Changed the Arduino Nano and DS3231 modules,
  12. Reverted the code back to use the u8glib library using the u8g_font_fur30r (I tried u8g_font_osr35 but the compiled code was too big and u8g_font_gdr30 came too close to the limit).

I am currently at option 12 which does work perfectly and in all fairness, one of the OLED vendors states you should use the U8g library however I really would like to use U8G2 for its enhanced fonts if possible please. Thank you in advance for any help you can provide.

Addendum: Option 12 does not work having finally stopped. Currently looking at alternative constructs.

@prd4wc,

Your Arduino Nano may be short of RAM for your display. Search for SH1106 in this forum.

Hello ZinggIM and thanks for your reply.

Yes I am currently grinding through the forum search results for SH1106.

As mentioned in my previous post I'm looking at the other constructs available for the SH1106, since as my OLED device has no discernable name I assumed the U8G2_SH1106_128X64_NONAME_1_HW_I2C construct would be suitable and indeed this did work in the initial build. However having attempted to use all 18 available constructs the problem remains (9 of these containing the 2ND_HW string did not work at all)

Regarding your suggestion for running short of RAM,
if I use the 128 byte page buffer size constructs( NO_NAME and VCOMH0)
the following compilation messages appear:

Sketch uses 14,110 bytes (45%) of program storage space. Maximum is 30,720 bytes.
Global variables use 659 bytes (32%) of dynamic memory, leaving 1,389 bytes for local variables. Maximum is 2,048 bytes.

and for the WINSTAR 128 page buffer size construct:
Sketch uses 14,102 bytes (45%) of program storage space. Maximum is 30,720 bytes.
Global variables use 651 bytes (31%) of dynamic memory, leaving 1,397 bytes for local variables. Maximum is 2,048 bytes.

Using the 256 byte page buffer size constructs( NO_NAME and VCOMH0) gives:
Sketch uses 14,112 bytes (45%) of program storage space. Maximum is 30,720 bytes.
Global variables use 787 bytes (38%) of dynamic memory, leaving 1,261 bytes for local variables. Maximum is 2,048 bytes.

and for the WINSTAR construct 256 page buffer construct:
Sketch uses 14,104 bytes (45%) of program storage space. Maximum is 30,720 bytes.
Global variables use 779 bytes (38%) of dynamic memory, leaving 1,269 bytes for local variables. Maximum is 2,048 bytes.

Finally for the full framebuffer, size = 1024 bytes constructs( NO_NAME and VCOMH0) :

Sketch uses 14,112 bytes (45%) of program storage space. Maximum is 30,720 bytes.
Global variables use 1,555 bytes (75%) of dynamic memory, leaving 493 bytes for local variables. Maximum is 2,048 bytes.
Low memory available, stability problems may occur.

and for the WINSTAR construct :
Sketch uses 14,104 bytes (45%) of program storage space. Maximum is 30,720 bytes.
Global variables use 1,547 bytes (75%) of dynamic memory, leaving 501 bytes for local variables. Maximum is 2,048 bytes.
Low memory available, stability problems may occur.

With the exception of the full framebuffer examples the ram usage seems to be ok?.
As mentioned I did use the U8G2_SH1106_128X64_NONAME_1_HW_I2C construct in the initial build without issue so I would not have used the full framebuffersize option (knowingly) Also the freeMem and freeRam routines I used (see code below) did not report excessive RAM/memory usage. These routines were placed at the top of the main loop to report on each iteration (with the setup and definition code in the correct place)

FreeMem

void setup() {
Serial.begin(9600);
}

void loop() {
  Serial.print("FreeMemory value: ");
  Serial.println(freeMemory());
  delay(1000);
}

  #ifdef __arm__
// should use uinstd.h to define sbrk but Due causes a conflict
extern "C" char* sbrk(int incr);
#else  // __ARM__
extern char *__brkval;
#endif  // __arm__
 
int freeMemory() {
  char top;
#ifdef __arm__
  return &top - reinterpret_cast<char*>(sbrk(0));
#elif defined(CORE_TEENSY) || (ARDUINO > 103 && ARDUINO != 151)
  return &top - __brkval;
#else  // __arm__
  return __brkval ? &top - __brkval : &top - __malloc_heap_start;
#endif  // __arm__
}

FreeRam:

void setup() {
    Serial.begin(115200);
}

void loop() {
    Serial.println(freeRam());
    delay(1000);
}
int freeRam () {
  extern int __heap_start, *__brkval;
  int v;
  return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval);
}

@prd4wc,

Hi, I just often read the actual posts of this forum section. That's why I answered, as I came across posts about SH1106 occasionally. But I am not really familiar with it. The RAM issue is important with the use of the Adafruit SH1106 library, as it dynamically allocates buffer RAM.

A quick second look at your first post made it unclear to me what version works and what doesn't.

Looks like you need to wait for answers of experts for your display, sorry.

Jean-Marc

Hello Jean-Marc,

Thank you for your reply.
Just to clarify my posts, I am using the Olikraus U8g2 library and have previously used the U8g version but this is no longer supported hence my wish to move to the U8g2 version. None of the 12 suggestions I have documented have worked.

Thanks again
Paul