Arduino Uno SSD1306 Allocation Failed - Need help downsizing

I am trying to add a SSD1306 OLED display to my current project but I am running into space issues. The SSD1306 requires 1kb of memory to run and I am having trouble getting that much space. I have already cut out a few variables and moved strings to flash. I do not know what else I can do.

//Packages
#include <SPI.h>
#include <Wire.h>
//#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Encoder.h>
#include <RH_ASK.h>
#include <SPI.h> 


//Screen
//#define SCREEN_WIDTH 128 // OLED display width, in pixels
//#define SCREEN_HEIGHT 64 // 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(128, 64, &Wire, -1);

//Pins
//#define rotary1 2
//#define rotary2 3
//#define trans 6
//#define rotbutton 4

//Variables
int freq = 0;
String mode = "ON";
boolean radio = false;


Encoder myEnc(2, 3);
RH_ASK rf_driver;

void setup() {
  Serial.begin(9600);
  rf_driver.init();
  
  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { 
    Serial.println(F("SSD1306 allocation failed"));
    for(;;); 
  }
  
  display.setTextSize(2);
  display.setTextColor(WHITE);
  display.display();
  delay(1000);

  pinMode(6, OUTPUT);
  pinMode(4, INPUT_PULLUP);

}

void loop() {
  //Serial.println(mode);
  
  checkrotbutton();
  checkradio();
  updatedisplay();
  if (mode.equals("ON")){
  digitalWrite(6,HIGH);
  
  }
  else if (mode.equals("STRB")){
  updaterotary();
  
  digitalWrite(6,HIGH);
  delay(freq);
  digitalWrite(6,LOW);
  delay(freq);
  }
  else if (mode.equals("RADIO")){
    if(radio){
      digitalWrite(6,HIGH);
    }
    else{
      digitalWrite(6,LOW);
    }
  
  }



}

void updatedisplay(){
  display.clearDisplay();
  display.setCursor(0,0);
  display.println(F("Mode:"));
  display.setCursor(0,20);
  display.println(F("Freq:"));
  display.setCursor(0,40);
  display.println(F("Radio:"));
  display.setCursor(60,0);
  display.print(mode);
  display.setCursor(60,20);
  display.print(freq);
  display.setCursor(70,40);
  if (radio == true) {display.print(F("ON"));}
  else {display.print(F("OFF"));}
  display.display();
}

void updaterotary(){
  //int newPos = myEnc.read();
  if (myEnc.read() != freq && myEnc.read() > 0){
    freq = myEnc.read();
  }
}

void checkrotbutton(){
  if (digitalRead(4) == LOW){
    if (mode.equals("ON")){
      mode = "STRB";
      delay(300);
    }
    else if (mode.equals("STRB")){
      mode = "RADIO";
      delay(300);
    }
    else if (mode.equals("RADIO")){
      mode = "ON";
      delay(300);
    }
  }
}

void checkradio(){
    uint8_t buf[1];
    uint8_t buflen = sizeof(buf);
    // Check if received packet is correct size
    if (rf_driver.recv(buf, &buflen))
    {
      if (buf == 49){
        radio = false;
      }
      else if (buf == 50){
        radio = true;
      }
    }
}

Can we safely assume that "1kb of memory" means one kilobyte of RAM ?

Those libraries often have internal storage that uses RAM. Do you really need all of them?

You certainly don't need SPI.h twice, but if it is similar to other libraries it will have protection so that it does not get included more than once. Do you even need it once?

The String class tends to use a lot of RAM and is usually a poor choice to use with an Uno. Learn to use C strings (null terminated character strings) that use less RAM and cause less memory fragmentation.

Also, keeping track of states by using strings is wasteful. Using enum may be a better way to go.

I like that you have a state machine, but see above.

There is a way to print how much RAM you have available but I do not have the function readily available at the moment. Perhaps a search on the internet will provide it, or perhaps some other helper will provide it.

void checkradio(){
    uint8_t buf[1];
    uint8_t buflen = sizeof(buf);
    // Check if received packet is correct size
    if (rf_driver.recv(buf, &buflen)) {
      if (buf == 49){
        radio = false;
      } else if (buf == 50){
        radio = true;
      }
    }
}

You may have inadvertently changed it to try and save a bit of RAM, but I would think having buf as an array with 1 element is going to cause you problems. Also, comparing buf to a number probably isn’t what you want to do, buf is a pointer to the memory address where the array is located.

I'm not sure if a text only display is acceptable to you but i've been using text only OLED driver and it works great with less resources that the full graphics library.

A few things:

  1. Those #defines that you have commented out won't save memory. When the code is compiled, the compiler essentially goes around and literally replaces them with whatever they represent. So you can keep those.
  2. You should be using an enum for the mode. Read Enumeration declaration - cppreference.com
  3. The buffer checkRadio() seems like it is unintentionally being used wrong. Why do you have an array of only 1 element?

Thanks for all of the help! I have been able to get the size down to where the OLED can allocate the storage needed but now I am running into a few issues.

  1. I setup the enum and it seems to work but i need a way to print out what is active. I tried to remedy this with returnMode() but it does not seem to work (it returns a blank).
  2. For the radio control part of the flashlight i have the transmitter sending a 1 or 2 to activate/deactivate the light. But, I cannot get the main controller to properly interpret the incoming 1 or 2 (it adds symbols after or simply does nothing.
  3. I tried to use ASCII library mentioned by one person but it flashes the screen when I refresh the display. I do not have a solution to that so I continued work on my original library.
  4. For some odd reason the oled has some fuzzyness to the bottom right of the screen when it is on. This does not happen when I remove the radiohead library.
  5. When at around a 40-50ms delay in the transistor it starts to behave oddly by flashing irregularly. This did not happen before.
//Packages
#include <SPI.h>
//#include <Wire.h>
//#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Encoder.h>
#include <RH_ASK.h>


//Screen
//#define SCREEN_WIDTH 128 // OLED display width, in pixels
//#define SCREEN_HEIGHT 64 // 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(128, 64, &Wire, -1);

//Pins
#define rotary1 2
#define rotary2 3
#define trans 6
#define rotbutton 4

//Variables
int freq = 0;
//String mode = "ON";
enum modeselect {
  OFF, ON, STRB, RADIO
};
modeselect mode = OFF;
boolean radio = false;


Encoder myEnc(rotary1, rotary2);
RH_ASK rf_driver;

void setup() {
  Serial.begin(9600);
  rf_driver.init();
  //Wire.begin();
  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { 
    Serial.println(F("SSD1306 allocation failed"));
    for(;;); 
  }
  
  display.setTextSize(2);
  display.setTextColor(WHITE);
  display.display();
  delay(1000);

  pinMode(trans, OUTPUT);
  pinMode(rotbutton, INPUT_PULLUP);

}

void loop() {
  //Serial.println(mode);
  
  checkrotbutton();
  checkradio();
  updatedisplay();
  if (mode == OFF){
  digitalWrite(trans,LOW);
  
  }
  else if (mode == ON){
  digitalWrite(trans,HIGH);
  
  }
  else if (mode == STRB){
  updaterotary();
  
  digitalWrite(trans,HIGH);
  delay(freq);
  digitalWrite(trans,LOW);
  delay(freq);
  }
  else if (mode == RADIO){
    if(radio){
      digitalWrite(trans,HIGH);
    }
    else{
      digitalWrite(trans,LOW);
    }
  
  }



}

void updatedisplay(){
  display.clearDisplay();
  display.setCursor(0,0);
  display.println(F("Mode:"));
  display.setCursor(0,20);
  display.println(F("Freq:"));
  display.setCursor(0,40);
  display.println(F("Radio:"));
  display.setCursor(60,0);
  Serial.println(returnMode());
  Serial.println(F("FAIL"));
  display.print(returnMode());
  display.setCursor(60,20);
  display.print(freq);
  display.setCursor(70,40);
  if (radio == true) {display.print(F("ON"));}
  else {display.print(F("OFF"));}
  display.display();
}

void updaterotary(){
  //int newPos = myEnc.read();
  if (myEnc.read() != freq && myEnc.read() > 0){
    freq = myEnc.read();
  }
}

void checkrotbutton(){
  if (digitalRead(rotbutton) == LOW){
    if (mode == OFF){
      mode = ON;
      delay(300);
    }
    else if (mode == ON){
      mode = STRB;
      delay(300);
    }
    else if (mode == STRB){
      mode = RADIO;
      delay(300);
    }
    else if (mode == RADIO){
      mode = OFF;
      delay(300);
    }
  }
}

void checkradio(){
    uint8_t buf[1];
    uint8_t buflen = sizeof(buf);
    //uint8_t buf;
    // Check if received packet is correct size
    if (rf_driver.recv(buf, &buflen))
    {
      //Serial.println(F((char*)buf));
      if (buf == 49){
        radio = false;
        Serial.println(F("RadioFalse"));
      }
      else if (buf == 50){
        radio = true;
        Serial.println(F("RadioTrue"));
      }
    }
}

String returnMode(){
  if (mode == OFF){
      return F("OFF");
    }
    else if (mode == ON){
      return F("ON");
    }
    else if (mode == STRB){
      return F("STRB");
    }
    else if (mode == RADIO){
      return F("RADIO");
    }
    else {return F("FAIL");}
}

Thanks for all of the help!

  1. I setup the enum and it seems to work but i need a way to print out what is active. I tried to remedy this with returnMode() but it does not seem to work (it returns a blank).

Can't really think of a way to write a function that returns the text for the enum without needing a few bytes of ram in which to do it, so you could have the printing done in the function itself:

void displayMode() { //prints value of mode to the OLED display
  switch (mode) {
    case OFF:
      display.print(F("OFF"));
      break;
    case ON:
      display.print(F("ON"));
      break;
    case STRB:
      display.print(F("STRB"));
      break;
    case RADIO:
      display.print(F("RADIO"));
      break;
    default:
      display.print(F("FAIL"));
      break;
  }
}

void printMode() { //prints value of mode to Serial monitor
  switch (mode) {
    case OFF:
      Serial.print(F("OFF"));
      break;
    case ON:
      Serial.print(F("ON"));
      break;
    case STRB:
      Serial.print(F("STRB"));
      break;
    case RADIO:
      Serial.print(F("RADIO"));
      break;
    default:
      Serial.print(F("FAIL"));
      break;
  }
  Serial.println();
}
  1. For the radio control part of the flashlight i have the transmitter sending a 1 or 2 to activate/deactivate the light. But, I cannot get the main controller to properly interpret the incoming 1 or 2 (it adds symbols after or simply does nothing.

buf is an array (even though it only has 1 element), so you have to address it as buf[0] in the comparison statements.

  1. For some odd reason the oled has some fuzzyness to the bottom right of the screen when it is on. This does not happen when I remove the radiohead library.
  2. When at around a 40-50ms delay in the transistor it starts to behave oddly by flashing irregularly. This did not happen before.

I would suspect both of those may be caused by a lack of ram, when you are calling functions the return address and a bit of other information has to be temporarily stored in ram, it is probably overwriting the end of your display buffer.

Thanks for the help david_2018, your solution for displaying the mode was brilliant. Just two (hopefully) final things. I am still having trouble with interpreting the radio signal. With the receiver code below, the print statement is returning 1⸮⸮ for 1 and 2⸮⸮ for 2. I tried swapping in (char*)buf[0] but that returned something even more gibberish. I really do not know what I am doing wrong here. My transmitter code will be linked below the main in case there is an issue I am not seeing there. Lastly, are there any final ways to reduce the RAM usage? I can live with the fuzziness on the screen but the irregular flashing is an issue. Thanks so much for all of your help!

Main Code:

//Packages
#include <SPI.h>
//#include <Wire.h>
//#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Encoder.h>
#include <RH_ASK.h>


//Screen
//#define SCREEN_WIDTH 128 // OLED display width, in pixels
//#define SCREEN_HEIGHT 64 // 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(128, 64, &Wire, -1);

//Pins
#define rotary1 2
#define rotary2 3
#define trans 6
#define rotbutton 4

//Variables
int freq = 10;
//String mode = "ON";
enum modeselect {
  OFF, ON, STRB, RADIO
};
modeselect mode = OFF;
boolean radio = false;


Encoder myEnc(rotary1, rotary2);
RH_ASK rf_driver;

void setup() {
  Serial.begin(9600);
  rf_driver.init();
  //Wire.begin();
  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { 
    Serial.println(F("SSD1306 allocation failed"));
    for(;;); 
  }
  
  display.setTextSize(2);
  display.setTextColor(WHITE);
  display.display();
  delay(1000);

  pinMode(trans, OUTPUT);
  pinMode(rotbutton, INPUT_PULLUP);

}

void loop() {
  //Serial.println(mode);
  
  checkrotbutton();
  checkradio();
  updatedisplay();
  if (mode == OFF){
  digitalWrite(trans,LOW);
  
  }
  else if (mode == ON){
  digitalWrite(trans,HIGH);
  
  }
  else if (mode == STRB){
  updaterotary();
  
  digitalWrite(trans,HIGH);
  delay(freq);
  digitalWrite(trans,LOW);
  delay(freq);
  }
  else if (mode == RADIO){
    if(radio){
      digitalWrite(trans,HIGH);
    }
    else{
      digitalWrite(trans,LOW);
    }
  
  }



}

void updatedisplay(){
  display.clearDisplay();
  display.setCursor(0,0);
  display.println(F("Mode:"));
  display.setCursor(0,20);
  display.println(F("Freq:"));
  display.setCursor(0,40);
  display.println(F("Radio:"));
  display.setCursor(60,0);
  //Serial.println(returnMode());
  //Serial.println(F("FAIL"));
  //display.print(returnMode());
  displayMode();
  display.setCursor(60,20);
  display.print((1/((double)freq/1000)));
  display.setCursor(70,40);
  if (radio == true) {display.print(F("ON"));}
  else {display.print(F("OFF"));}
  display.display();
}

void updaterotary(){
  //int newPos = myEnc.read();
  if (myEnc.read() != freq && myEnc.read() > 0){
    freq = myEnc.read();
  }
}

void checkrotbutton(){
  if (digitalRead(rotbutton) == LOW){
    if (mode == OFF){
      mode = ON;
      delay(300);
    }
    else if (mode == ON){
      mode = STRB;
      delay(300);
    }
    else if (mode == STRB){
      mode = RADIO;
      delay(300);
    }
    else if (mode == RADIO){
      mode = OFF;
      delay(300);
    }
  }
}

void checkradio(){
    uint8_t buf[1];
    uint8_t buflen = sizeof(buf);
    //uint8_t buf;
    // Check if received packet is correct size
    if (rf_driver.recv(buf, &buflen))
    {
      Serial.println((char*)buf);
      if (buf == 49){
        radio = false;
        Serial.println(F("RadioFalse"));
      }
      else if (buf == 50){
        radio = true;
        Serial.println(F("RadioTrue"));
      }
    }
}
/**
String returnMode(){
  if (mode == OFF){
      return F("OFF");
    }
    else if (mode == ON){
      return F("ON");
    }
    else if (mode == STRB){
      return F("STRB");
    }
    else if (mode == RADIO){
      return F("RADIO");
    }
    else {return F("FAIL");}
}
**/
void displayMode() { //prints value of mode to the OLED display
  switch (mode) {
    case OFF:
      display.print(F("OFF"));
      break;
    case ON:
      display.print(F("ON"));
      break;
    case STRB:
      display.print(F("STRB"));
      break;
    case RADIO:
      display.print(F("RADIO"));
      break;
    default:
      display.print(F("FAIL"));
      break;
  }
}
/**
void printMode() { //prints value of mode to Serial monitor
  switch (mode) {
    case OFF:
      display.print(F("OFF"));
      break;
    case ON:
      display.print(F("ON"));
      break;
    case STRB:
      display.print(F("STRB"));
      break;
    case RADIO:
      display.print(F("RADIO"));
      break;
    default:
      display.print(F("FAIL"));
      break;
  }
}
**/

Transmitter Code (Running on an arduino nano with button and transmitter connected):

/*
  433 MHz RF Module Transmitter Demonstration 1
  RF-Xmit-Demo-1.ino
  Demonstrates 433 MHz RF Transmitter Module
  Use with Receiver Demonstration 1

  DroneBot Workshop 2018
  https://dronebotworkshop.com
*/

// Include RadioHead Amplitude Shift Keying Library
#include <RH_ASK.h>
// Include dependant SPI Library 
#include <SPI.h> 

#define button 5
boolean on = true;

// Create Amplitude Shift Keying Object
RH_ASK rf_driver;

void setup()
{
  Serial.begin(9600);
  pinMode(button,INPUT);
    // Initialize ASK Object
    rf_driver.init();
    Serial.println("Turning on");
}

void loop()
{
  //Serial.println(digitalRead(button));
  if (digitalRead(button) == HIGH)
  {
    if(on){
      Serial.println("on");
      const char *msg = "1";
      rf_driver.send((uint8_t *)msg, strlen(msg));
      on = false;
      rf_driver.waitPacketSent();
      delay(300);
    }
    else
    {
      Serial.println("off");
      const char *msg = "2";
      rf_driver.send((uint8_t *)msg, strlen(msg));
      on = true;
      rf_driver.waitPacketSent();
      delay(300);
    }
  }

}

buf is an array, you need to specify which element of the array you want to print.

Serial.println(buf[0]); //prints the decimal value of buf[0]

Serial.println((char)buf[0]); //prints the ASCII character representation of buf[0]

You will have a similar problem with the if statements, it should be written as:

if (buf[0] == 49){

That worked!

Update on the project: The code is now almost entirely done. I decided to switch to the ASCII library because the ram situation was really bugging me. I got around the display flashing on update by making the display only update when some information on the display changes. This seems like the much more elegant solution and it allows me to potentially add more things to the project in the future. All of the help was much appreciated!

//Packages
#include <SPI.h>
#include <Wire.h>
//#include <Adafruit_GFX.h>
//#include <Adafruit_SSD1306.h>
#include <Encoder.h>
#include <RH_ASK.h>

#include "SSD1306Ascii.h"
#include "SSD1306AsciiWire.h"



//Screen
//#define SCREEN_WIDTH 128 // OLED display width, in pixels
//#define SCREEN_HEIGHT 64 // 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(128, 64, &Wire, -1);
// 0X3C+SA0 - 0x3C or 0x3D
#define I2C_ADDRESS 0x3C

// Define proper RST_PIN if required.
#define RST_PIN -1

SSD1306AsciiWire oled;


//Pins
#define rotary1 2
#define rotary2 3
#define trans 6
#define rotbutton 4

//Variables
int freq = 1;
int lastfreq;
//String mode = "ON";
enum modeselect {
  OFF, ON, STRB, RADIO
};
modeselect mode = OFF;
String lastmode;
boolean radio = false;
boolean lastradio = true;

Encoder myEnc(rotary1, rotary2);
RH_ASK rf_driver;

void setup() {
  Wire.begin();
  Wire.setClock(400000L);
  Serial.begin(9600);
  rf_driver.init();
  /**
  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { 
    Serial.println(F("SSD1306 allocation failed"));
    for(;;); 
  }
  **/
  oled.begin(&Adafruit128x64, I2C_ADDRESS);
  oled.setFont(Adafruit5x7);
  uint32_t m = micros();
  oled.set2X();

  
  //display.setTextSize(2);
  //display.setTextColor(WHITE);
  //display.display();
  delay(1000);

  pinMode(trans, OUTPUT);
  pinMode(rotbutton, INPUT_PULLUP);

}

void loop() {
  //Serial.println(mode);
  
  checkrotbutton();
  checkradio();
  updatedisplay();
  if (mode == OFF){
  digitalWrite(trans,LOW);
  
  }
  else if (mode == ON){
  digitalWrite(trans,HIGH);
  
  }
  else if (mode == STRB){
  updaterotary();
  
  digitalWrite(trans,HIGH);
  delay(freq);
  digitalWrite(trans,LOW);
  delay(freq);
  }
  else if (mode == RADIO){
    if(radio){
      digitalWrite(trans,HIGH);
    }
    else{
      digitalWrite(trans,LOW);
    }
  
  }



}

void updatedisplay(){
  
  if (freq != lastfreq || lastmode != returnMode() || radio != lastradio){
  oled.clear();
  oled.print("Mode: ");
  oled.println(returnMode());
  oled.print("Freq: ");
  oled.println((1/((double)freq/1000.0)));
  oled.print("Radio: ");
  if (radio == true) {oled.println("ON");}
  else {oled.println("OFF");}
  lastfreq = freq;
  lastmode = returnMode();
  lastradio = radio;
  }
}

void updaterotary(){
  //int newPos = myEnc.read();
  if (myEnc.read() != freq && myEnc.read() > 0){
    freq = myEnc.read();
  }
}

void checkrotbutton(){
  if (digitalRead(rotbutton) == LOW){
    if (mode == OFF){
      mode = ON;
      delay(300);
    }
    else if (mode == ON){
      mode = STRB;
      delay(300);
    }
    else if (mode == STRB){
      mode = RADIO;
      delay(300);
    }
    else if (mode == RADIO){
      mode = OFF;
      delay(300);
    }
  }
}

void checkradio(){
    uint8_t buf[1];
    uint8_t buflen = sizeof(buf);
    //uint8_t buf;
    // Check if received packet is correct size
    if (rf_driver.recv(buf, &buflen))
    {
      //Serial.println(buf);
      //Serial.println(buf[0]);
      if (buf[0] == 49){
        radio = false;
        Serial.println(F("RadioFalse"));
      }
      else if (buf[0] == 50){
        radio = true;
        Serial.println(F("RadioTrue"));
      }
    }
}
/**
String returnMode(){
  if (mode == OFF){
      return F("OFF");
    }
    else if (mode == ON){
      return F("ON");
    }
    else if (mode == STRB){
      return F("STRB");
    }
    else if (mode == RADIO){
      return F("RADIO");
    }
    else {return F("FAIL");}
}
**/

String returnMode() { //prints value of mode to the OLED display
  switch (mode) {
    case OFF:
      return("OFF");
      break;
    case ON:
      return("ON");
      break;
    case STRB:
      return("STRB");
      break;
    case RADIO:
      return("RADIO");
      break;
    default:
      return("FAIL");
      break;
  }
}