Adafruit i2c OLED Display Inconsistencies

Hi! I'm experiencing some really weird inconsistencies with an Adafruit 128x64 SSD1306 monochrome display which I'm using through i2c. I'm trying to make the game snake on the display, using a joystick as an input, which is working great. However, when I change one unrelated line of code, the display fails to initialise. First, here's my code:

#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <ArduinoSTL.h>

int posx = 0; // position of head of snake
int posy = 0;

int joyx = 0; // joystick position
int joyy = 0;

int fx = 2; // fruit position
int fy = 0;

int facing = 2; // snake movement direction
int timer = 0; // snake movement timer

std::vector<int> trail{0, 0, 8, 0, 16, 0, 24, 0}; // list of coordinates of the snake's tail

Adafruit_SSD1306 display(128, 64, &Wire, 4);

void setup() {
  Serial.begin(9600);
  pinMode(A0, INPUT);
  pinMode(A1, INPUT);
  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // initialse display
    Serial.println("Fail");
    Serial.println(fx);
  }
  display.clearDisplay();
  display.display();
}

void loop() {
  joyx = analogRead(A0);
  joyy = analogRead(A1);
  if (joyx > 800 && abs(joyy - 512) < 200 && facing != 0) { // if right
    facing = 2;
  }
  if (joyy < 200 && abs(joyx - 512) < 200 && facing != 3) { // if down
    facing = 1;
  }
  if (joyx < 200 && abs(joyy - 512) < 200 && facing != 2) { // if left
    facing = 0;
  }
  if (joyy > 800 && abs(joyx - 512) < 200 && facing != 1) { // if up
    facing = 3;
  }
  timer++;
  if (timer == 20) { // every 200ms ish
    timer = 0;
    if (facing == 0 || facing == 2) { // change snake head position
      posx = posx + ((facing - 1) * 8);
    } else {
      posy = posy + ((facing - 2) * 8);
    }
    trail.push_back(posx); // move snake forward
    trail.push_back(posy);
    trail.erase(trail.begin()); // remove last square of snake
    trail.erase(trail.begin());
    display.clearDisplay();
    for (int i = 0; i < trail.size(); i = i + 2) { // display all snake segments
      display.fillRect(trail[i], trail[i + 1], 8, 8, SSD1306_WHITE);
    }
    display.drawCircle((fx * 8) + 4, (fy * 8) + 4, 4, SSD1306_WHITE); // draw the fruit
    display.drawRect(0, 0, 128, 64, SSD1306_WHITE); // draw the game bounding box
    display.display(); // refresh display
  }
  delay(10);
}

And this works perfectly, although the fruit position isn't randomised and is merely at grid reference (2, 0).
However, if I attempt to randomise the fruit position using the line int fx = random(0, 16) at the beginning of the code instead of hardcoding it to 2, the display fails to initialise and "Fail 7" is sent to the serial monitor. I simply don't understand why changing one line of code that is unrelated to the display should cause it to fail to start. (I'm using an Arduino UNO and am powering it with the USB cable.)

Please see my schematic below.

If you have any insights, it will be very helpful
Thank you!

Welcome to the forum

Where exactly did you put your attempt to give fx a random value ?

At the start. I replaced

int fx = 2;

with

int fx = random(0, 16);

What happens if you explicitly set fx to 7 instead of a random number which, by the way, is not really random ?

Well this is very odd - now it's not working for either 2 or 7 (And I didn't even change anything other than the number!) . And also, it's now spamming the serial monitor with "Fail 2", which doesn't make sense since it only runs once.

(Also yes I'm aware it's not random since I didn't seed it)

But yeah now it isn't working at all

Scratch that - I managed to fix it. By removing the line Serial.println(fx) it began working again, with fx set to 7 - this is incredibly strange.

Normally, should the display fail to initialise, the code would enter an endless loop to prevent it running in a failed state. However, your code it just carries on regardless writing to a display that does not exist and, I suspect, reboots the board, hence the "Fail" message being repeated

I see - so I should add like for(;;);?

Also how on earth did removing the Serial.print line fix it I genuinely don't understand

@tornato You could drop a serial print message in setup() to see if the board's restarting

The "Fail" message is doing a good job of it at the moment !

An endless for loop or while loop will stop the code when required

If you are still seeing the "Fail" message then it is not fixed

No, I'm not. It works fine now that I removed the line Serial.println(fx) which is very strange
However, if I change the line int fx = 7; to int fx = random(0, 16); it breaks again and prints Fail

It sounds like a timing issue

Try declaring fx as a global with n value (actually it will be zero) then initialise it with a random value in setup()

And now it doesn't work at all even if I set fx to a fixed value. I don't understand.

Also what do you mean by "timing issue"?

I mean that when the code tries to initialise the display if something else has not completed behind the scenes that might cause a problem. Global variables are created very early in the process of the sketch running and ideally local variables should be used

Before going any further I suggest that you write a simple test sketch that declares and initialises the value of fx, initialises the display and prints a message to it

fx is only used in the loop() function of your sketch so it could be declared and initialised there using the static keyword to ensure that its value is not changed every time loop() runs

I don't think static is what I want, since I will be wanting to move the fruit (fx + fy) later to a new random position once it's eaten

static does not mean what you think, confusion with const, perhaps. Declaring a variable as static in a function enables you to give it an initial value that can be changed later by code in the same scope (ie in the function). If the function is called again it will not be reset to its original value if it has been changed

The advantage is that the variable has only local scope so no code outside if that scope can change its value which can prevent mistakes in coding, particularly as programs get larger and more complicated

So it appears that I have some sort of strange character limit. I moved all my variables to the loop function, and made them all static (adding 48 chars to the file), and the display failed to initialise. However, if I removed the lines that drew the level bounding box and fruit, (removing 121 chars from the file), it worked again.

I'm am very baffled by C++ (I'm used to nice languages like python)

Unless you post your latest code I am afraid that it will be impossible to provide further help

Super sorry for the late reply, haven't had much time to work on this. Anyway, I'll just show you my working code and my non-working code.

Working:

#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <ArduinoSTL.h>

int posx = 0; // position of head of snake
int posy = 0;

int joyx = 0; // joystick position
int joyy = 0;

int fx = 2; // fruit position
int fy = 0;

int facing = 2; // snake movement direction
int timer = 0; // snake movement timer

std::vector<int> trail{0, 0, 8, 0, 16, 0, 24, 0}; // list of coordinates of the snake's tail

Adafruit_SSD1306 display(128, 64, &Wire, 4);

void setup() {
  Serial.begin(9600);
  pinMode(A0, INPUT);
  pinMode(A1, INPUT);
  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // initialse display
    Serial.println("Fail");
    for(;;);
  }
  display.clearDisplay();
  display.display();
}

void loop() {
  joyx = analogRead(A0);
  joyy = analogRead(A1);
  if (joyx > 800 && abs(joyy - 512) < 200 && facing != 0) { // if right
    facing = 2;
  }
  if (joyy < 200 && abs(joyx - 512) < 200 && facing != 3) { // if down
    facing = 1;
  }
  if (joyx < 200 && abs(joyy - 512) < 200 && facing != 2) { // if left
    facing = 0;
  }
  if (joyy > 800 && abs(joyx - 512) < 200 && facing != 1) { // if up
    facing = 3;
  }
  timer++;
  if (timer == 20) { // every 200ms ish
    timer = 0;
    if (facing == 0 || facing == 2) { // change snake head position
      posx = posx + ((facing - 1) * 8);
    } else {
      posy = posy + ((facing - 2) * 8);
    }
    trail.push_back(posx); // move snake forward
    trail.push_back(posy);
    trail.erase(trail.begin()); // remove last square of snake
    trail.erase(trail.begin());
    display.clearDisplay();
    for (int i = 0; i < trail.size(); i = i + 2) { // display all snake segments
      display.fillRect(trail[i], trail[i + 1], 8, 8, SSD1306_WHITE);
    }
    display.drawCircle((fx * 8) + 4, (fy * 8) + 4, 4, SSD1306_WHITE); // draw the fruit
    display.drawRect(0, 0, 128, 64, SSD1306_WHITE); // draw the game bounding box
    display.display(); // refresh display
  }
  delay(10);
}

Not Working:

#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <ArduinoSTL.h>

int posx = 0; // position of head of snake
int posy = 0;

int joyx = 0; // joystick position
int joyy = 0;

int fx = random(0, 17); // fruit position
int fy = 0;

int facing = 2; // snake movement direction
int timer = 0; // snake movement timer

std::vector<int> trail{0, 0, 8, 0, 16, 0, 24, 0}; // list of coordinates of the snake's tail

Adafruit_SSD1306 display(128, 64, &Wire, 4);

void setup() {
  Serial.begin(9600);
  pinMode(A0, INPUT);
  pinMode(A1, INPUT);
  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // initialse display
    Serial.println("Fail");
    for(;;);
  }
  display.clearDisplay();
  display.display();
}

void loop() {
  joyx = analogRead(A0);
  joyy = analogRead(A1);
  if (joyx > 800 && abs(joyy - 512) < 200 && facing != 0) { // if right
    facing = 2;
  }
  if (joyy < 200 && abs(joyx - 512) < 200 && facing != 3) { // if down
    facing = 1;
  }
  if (joyx < 200 && abs(joyy - 512) < 200 && facing != 2) { // if left
    facing = 0;
  }
  if (joyy > 800 && abs(joyx - 512) < 200 && facing != 1) { // if up
    facing = 3;
  }
  timer++;
  if (timer == 20) { // every 200ms ish
    timer = 0;
    if (facing == 0 || facing == 2) { // change snake head position
      posx = posx + ((facing - 1) * 8);
    } else {
      posy = posy + ((facing - 2) * 8);
    }
    trail.push_back(posx); // move snake forward
    trail.push_back(posy);
    trail.erase(trail.begin()); // remove last square of snake
    trail.erase(trail.begin());
    display.clearDisplay();
    for (int i = 0; i < trail.size(); i = i + 2) { // display all snake segments
      display.fillRect(trail[i], trail[i + 1], 8, 8, SSD1306_WHITE);
    }
    display.drawCircle((fx * 8) + 4, (fy * 8) + 4, 4, SSD1306_WHITE); // draw the fruit
    display.drawRect(0, 0, 128, 64, SSD1306_WHITE); // draw the game bounding box
    display.display(); // refresh display
  }
  delay(10);
}

The only difference between the two snippets is I replace the line

int fx = 2;

with

int fx = random(0, 17);

which causes the display to fail to initialise

(Making the variables static and moving them to the loop as you suggested also makes it not work)

If you have any ideas, please do suggest them as I am baffled.

You already got one that you appear to have ignored.

I know you said

But you never posted the code of that attempt. And you never said what didn't work. That should have done the trick.

You see the random function is not built into the core, it is a ready included function written in C, and as the program hasn't even started yet it can't run that function.

Python is an interpreted language and C is a compiled language.
Python is slow maybe several hundreds of times slower. But being an interpretive langue it can spot run time errors like an array being indexed outside its defined size. C can't possibly do something like that because it is compiled so it doesn't know what values are going to be derived from calculations as it runs,

Yes Python is a beginners language in that respect, but it doesn't have the speed.