Adding an SD card to my OLED project

Hello. I am trying to make a digital notepad basically: I want to record the date, price for the fuel, how much fuel and then the amount of miles travelled on said fuel. I am going at this step by step and am at the second to last hurdle, SD card writing.

So my project uses a keypad interface to input a number which is displayed on the OLED. I am now trying to add saving this num to a file on the SD card so I can put it into a computer and log my cars MPG per tank of fuel on excel.

Problem is, the OLED doesn't work when I add the SD card code to the sketch. I can have the keypad work fine with the OLED but as soon as I add the SD card code it doesn't. I am seeing the SD card initialise on the serial monitor but the OLED doesn't turn on so I cant make any inputs to the keypad to be saved.

Please could you tell me if I am making a big mistake in terms of mixing an OLED with a SD card module, or if my code is set up wrong for it to work? Many Thanks.

I don't know if this is the issue in itself but this is the upload message for storage and mem:

Sketch uses 26918 bytes (87%) of program storage space. Maximum is 30720 bytes.
Global variables use 1510 bytes (73%) of dynamic memory, leaving 538 bytes for local variables. Maximum is 2048 bytes.

Code to come (5mins)...

///////////////////////////////////////////////////////////////
////////////////////////Included Libraries/////////////////////
///////////////////////////////////////////////////////////////

#include <Wire.h>
#include <Keypad.h>
#include <Adafruit_GFX.h>  // Include core graphics library for the display
#include <Adafruit_SSD1306.h>  // Include Adafruit_SSD1306 library to drive the display
#include <SPI.h>
#include <SD.h>

///////////////////////////////////////////////////////////////
//////////////////////////LCD Setup////////////////////////////
///////////////////////////////////////////////////////////////

Adafruit_SSD1306 display(128, 32);  // Create display
#include <Fonts/FreeMono9pt7b.h>  // Add a custom font

///////////////////////////////////////////////////////////////
////////////////////////Keypad Setup///////////////////////////
///////////////////////////////////////////////////////////////

const byte ROWS = 4; //four rows
const byte COLS = 3; //three columns
char keys[ROWS][COLS] =
{
  {'1', '2', '3'},
  {'4', '5', '6'},
  {'7', '8', '9'},
  {'*', '0', '#'}
};


//                    R  R  R  R
//                    1  2  3  4
byte rowPins[ROWS] = {5, 9, 3, 2}; 

//                    C  C  C
//                    1  2  3
byte colPins[COLS] = {8, 7, 6}; 

Keypad keypad = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS );

char num[20];

///////////////////////////////////////////////////////////////
///////////////////////////Functions/////////////////////////// These need to be declared before the function pointer.
///////////////////////////////////////////////////////////////

void Date()
{  
  GetNumber();
}

void Price()
{
  GetNumber();
  SaveSD();
}
void Litres()
{
  GetNumber();
}

void Miles()
{
  GetNumber();
}

///////////////////////////////////////////////////////////////
////////////////////////Button Variables///////////////////////
///////////////////////////////////////////////////////////////

bool ButtonFlag = LOW;                                        
                                                                
///////////////////////////////////////////////////////////////
/////////////////////////Menu Variables////////////////////////
///////////////////////////////////////////////////////////////

const int NoofMenus = 4;                                      //Instantiate NoOfMenus to be a constant integer of 4. This is how many menus I will have in have in my code. I have used constant int here for the same reason as before.

typedef void (* GenericFP)();                                 //Here I am creating an alias for void. I am saying that I want GenericFP to work as a void when I call 'GenericFP'. This enables me to use pointers in arrays to call functions, as shown below:
GenericFP ControlSystemFunctions[NoofMenus] = 
{&Date, &Price, &Litres, &Miles};
                                                              //Using GenericFP as described above in conjunction with this array, I can point to a location in my array and call a function as if I were typing 'void function();'.
                                                              //I can call ControlSystemFunctions[number between 0-3] in my code and it will perform the function assosiated with that memory location in the array.
/////////Menu Variables////////
int FunctionPointer = 0;                                      //This determines the Control System Function which is selected out of the GenericFP array. Initially this is set as 0.

char* MainMenuNames[NoofMenus] = 
{"Date:","Price:","Litres:","Miles:"};

///////////////////////////////////////////////////////////////
//////////////////////////////Setep////////////////////////////
///////////////////////////////////////////////////////////////

void setup()
{
  delay(500);  // This delay is needed to let the display to initialize

  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);  // Initialize display with the I2C address of 0x3C

  display.clearDisplay();  // Clear the buffer

  display.setTextColor(WHITE);  // Set color of the text

  display.setRotation(0);  // Set orientation. Goes from 0, 1, 2 or 3

  display.setTextWrap(false);  // By default, long lines of text are set to automatically "wrap" back to the leftmost column.
  // To override this behavior (so text will run off the right side of the display - useful for
  // scrolling marquee effects), use setTextWrap(false). The normal wrapping behavior is restored
  // with setTextWrap(true).

  display.dim(0);  //Set brightness (0 is maximun and 1 is a little dim)

  Serial.begin(9600);

  Serial.print("Initializing SD card...");

  if (!SD.begin(4)) 
  {
    Serial.println("initialization failed!");
    while (1);
  }
  Serial.println("initialization done.");
}

///////////////////////////////////////////////////////////////
////////////////////////////Main Loop//////////////////////////
///////////////////////////////////////////////////////////////

void loop()                                                   
{
  //Serial.println(FunctionPointer);
  ControlSystemFunctions[FunctionPointer]();                  //Run the code determined by the function pointer.   
  ReadFlag(); 
  MenuCode();                                                                                           
  ResetFlag();                                                //After every loop I want to ensure the flag is reset to ensure that on the next loop any change in state of the flags can be seen again.
}

///////////////////////////////////////////////////////////////
///////////////////////////Menu System/////////////////////////
///////////////////////////////////////////////////////////////

void MenuCode()
{
  display.clearDisplay();  // Clear the display so we can refresh

  //display.drawRect(0, 0, 128, 32, WHITE);  // Draw rectangle (x,y,width,height,color
  
  display.setFont(&FreeMono9pt7b);  // Set a custom font
  
  display.setTextSize(0);  // Set text size. We are using a custom font so you should always use the text size of 0
  
  display.setCursor(0, 19); // (x,y) (5,11) = MIN
  
  display.println(MainMenuNames[FunctionPointer]);  // Text or value to print

  if (FunctionPointer == 0)
  {
    display.setCursor (60,19);
  }

  if (FunctionPointer == 1 || FunctionPointer == 3)
  {
    display.setCursor (70,19);
  }

  if (FunctionPointer == 2)
  {
    display.setCursor (75,19);
  }
  
  display.println(num);

  display.display();
}

void ReadFlag()                                               //This function essentially tells my control system what to do when a button is pressed.
{
  if (ButtonFlag == HIGH)                                     //This 'if' structure acts as the functionality behind my scroll button.                                 
  {
      FunctionPointer = (FunctionPointer + 1) % NoofMenus;    //Assign FunctionPointer a new, incrimented value so long as it is not bigger than the number of menus. 
                                                                //'% NoofMenus' ensures that when the FunctionPointer reaches 8, the next scroll puts the counter back to 0. This is due to the remainder of 8 % 8 being 0.
  }
}

void ResetFlag()                                             //This resets the flags on both programmable buttons in both system states, normal and alternate (or extra).
{
  ButtonFlag = LOW;
}

///////////////////////////////////////////////////////////////
///////////////////////////Functions///////////////////////////
///////////////////////////////////////////////////////////////

void GetNumber()
{
  static byte offset = 0;
  char key = keypad.getKey();
  if (key != NO_KEY)
  {
    switch (key)
    {
      case '0': case '1': case '2': case '3': case '4':
      case '5': case '6': case '7': case '8': case '9':

        num[offset] = key;
        offset++;
        //add a null terminator for the currently entered ASCII number
        num[offset] = 0;
        float temp;
        temp = atof(num);
        display.display();
        break;

      case '*':
        num[offset] = '.';
        offset++;
        display.display();
        break;

      case '#':
        offset = 0;
        memset(num, 0, sizeof num);
        ButtonFlag = HIGH;
        break;

    } //END of switch/case

  } //END of   if (key != NO_KEY)

} //END of    GetNumber()

void SaveSD()
{
  File myFile = SD.open("test1.txt", FILE_WRITE);
  if (myFile) {
    Serial.print("Writing to test.txt...");
    myFile.print(num);
    myFile.print(",");
    // close the file:
    myFile.close();
    Serial.println("done.");
  } else {
    // if the file didn't open, print an error:
    Serial.println("error opening test.txt");
  }
}

Does your display use SPI? If so, what is it's CS pin on the Arduino? (It appears the SD card module uses D4.)

If both use SPI, it's possible the SD card module design does not work well with others. Could you post a picture of that module, or a link to it?

As ShermanP says, some SD card modules will not play well with other devices on the SPI bus. The problem is with the way the level shifter on the SD module is wired. The ones that do not work well have the MOSI MISO signal running through the level shifter. That causes the MOSI MISO signal to not be released so that the other devices can control it. The modules made by Adafruit (and some others) have the MOSI MISO signal going from the SD card straight to the MOSI MISO, bypassing the level shifter. Look at the schematic for the Adafruit module to see what to look for. DO (data out) is the MISO signal.

Edit: corrected the MOSI references to read the correct MISO. Sorry for any confusion.

Thanks for the help:

kwmobile 2x Micro SD Card Reader Module 5V Card Adapter for Arduino and other Microcontrollers kwmobile 2x Micro SD Card Reader Module 5V Card Adapter for Arduino and other Microcontrollers : Amazon.co.uk: Computers & Accessories

MakerHawk 2pcs I2C OLED Display Module 0.91 Inch I2C SSD1306 OLED Display Module White I2C OLED Screen Driver DC 3.3V5V for Arduino Seamuig I2C OLED Display Module I2C SSD1306 Screen Tiny Module 0.91 Inch White 128X32 I2C OLED Driver DC 3.3V to 5V for Arduino (2 Pack) : Amazon.co.uk: Electronics & Photo

groundFungus, I think you meant the MISO line, not MOSI.

DrDaveyG, your display is I2C, not SPI, so in theory there should be no conflict even if the SD module behaves badly - because the two modules use different Arduino pins. So I don’t know what to suggest. Maybe someone who has these two working together will offer some advice.

In case it might help, I’ll attach what I believe is the schematic for your SD module (although the pinout may be different) so you can compare it to the Adafruit version. One solution to the problem groundFungus described would be to disconnect the output enable pin of the MISO LVC125 gate from ground, and connect it instead to the LVC CS output. But as I said, if the display is on I2C, this really shouldn’t matter. I don’t think you said which Arduino you’re using, but can you confirm that aside from power and ground, there are no lines from the two modules connected to the same pin on the Arduino?

Arduino Nano is what I am using.

groundFungus, I think you meant the MISO line, not MOSI.

Yes I did. Corrected the post, sorry for the mistake. And, ShermanP, thank you for letting me know.

So if I take out the SaveSD(); line in my price function it works. The code shouldnt even get to this stage until I press # once, but even so with it in the code it causes the fault. With this line commented "//SaveSD();" the code runs and the OLED displays as per normal.

But as soon as I un-comment and re-upload it the OLED displays whatever was last on the screen and doesnt change.

For instance I just commented the SaveSD(); line, it worked fine and I input 322 as a date, then I un-commented the SaveSD(); and re-uploaded it and the 322 and date remains on the OLED. I have got the SD module itself disconnected so I am thinking its just an issue with the code somehow?

For instance, could the I2C address of the OLED change somehow? :

display.begin(SSD1306_SWITCHCAPVCC, 0x3C); // Initialize display with the I2C address of 0x3C

I have read around and some people have said that the OLED and the SD module both use RAM so might I have run out of usable RAM to use the OLED properly? Would an SPI OLED be better?

Also, on amazon it states the SD module connections are:
D13-> SCK, D12-> MISO, D11-> MOSI, D8-> CS

But I have connected the SD module using this image:

Your code appears to set the CS pin for the SDI as D4, so your wiring should be correct. But I have no idea about the ram or other stuff.

If you get the problem including SaveSD() even when the SD module isn’t connected, then it could be a software conflict, or possibly not enough ram.

We still need to find someone who has done this combination successfully.

By the way, how is the display wired to the Nano?

Have you tried commenting out the display related lines to see if the SD card works then?

The display has GND, VCC which are connected as you would expect from 5V and GND (could this be an issue in terms of two different GNDs? do I need to link the SD module and display GNDS?).

And the display has SCL and SDA which are connected to A4 and A5 correctly so thats not an issue.

I have made an interesting discovery however. From the code below you can see that that I have been using the basic examples to try and figure out the problem. It seems that when the display line:

display.begin(SSD1306_SWITCHCAPVCC, 0x3C);

is in the code, the SD card does not save “it works”.

Could this be a clue as to whats going wrong here?

So to re-iterate, the SD card saves “it works” when the display line above is commented out and then the SD card does NOT save when it is un-commented.

#include <SPI.h>
#include <SD.h>
#include <Wire.h>
#include <Adafruit_GFX.h>  // Include core graphics library for the display
#include <Adafruit_SSD1306.h>  // Include Adafruit_SSD1306 library to drive the display

File myFile;

Adafruit_SSD1306 display(128, 32);  // Create display
#include <Fonts/FreeMono9pt7b.h>  // Add a custom font

void setup() {
  // Open serial communications and wait for port to open:
  Serial.begin(9600);

  delay(500);  // This delay is needed to let the display to initialize

  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);  // Initialize display with the I2C address of 0x3C <------------------------------

  //display.clearDisplay();  // Clear the buffer

  //display.setTextColor(WHITE);  // Set color of the text

  //display.setRotation(0);  // Set orientation. Goes from 0, 1, 2 or 3

  //display.setTextWrap(false);  // By default, long lines of text are set to automatically "wrap" back to the leftmost column.
  // To override this behavior (so text will run off the right side of the display - useful for
  // scrolling marquee effects), use setTextWrap(false). The normal wrapping behavior is restored
  // with setTextWrap(true).

  //display.dim(0);  //Set brightness (0 is maximun and 1 is a little dim)

  //display.clearDisplay();  // Clear the display so we can refresh

  //display.drawRect(0, 0, 50, 32, WHITE);  // Draw rectangle (x,y,width,height,color

  //display.display();

  Serial.print("Initializing SD card...");
  
  SD.begin(4);
  
  // open the file. note that only one file can be open at a time,
  // so you have to close this one before opening another.
  myFile = SD.open("test6.txt", FILE_WRITE);
  myFile.println("it works");
  myFile.close();
  Serial.println(".....");
}

void loop() {

}

I ran into this exact issue a few years back. Once you add a screen to your Arduino your pretty much going to be out of room. So your going to be really pushing it to do both OLED & Anything else. Grab something with some more space. (I use Teensy 3.2s) You run your same Arduino code on them, they go way faster and have 64k or RAM as opposed to 2k. Suddenly a whole new world of possibilities will open up for you.

-jim lee

Hi jimlee, I will give it a go thank you.

So do the same libraries work on the Teensy 3.2s?

And will the same capability be there for the OLED and SD module pins, like SDA, SCL etc.

Any advice on using the Teensy will be greatly appreciated as I have never even hear about it. I did look at similar platforms but didn't come across the Teensy.

Ok, so it appears that both modules work as long as you aren't running the other one. And since they aren't using the same pins on the Arduino, there's really no circuitry explanation for why they don't work together. Based on jimLee's post, it seems that insufficient ram may be the explanation. I don't know anything about Teensy except that it uses an Arm ship. The Arduino Mega 2560 R3 would be another option. It has 8K of ram, but is still an AVR. But you probably should search for other threads on using this combination with the Mega.

I have never run into much difference between the Teensy 3.2s that I use and the Arduino. I use all the same libraries.. Well, it seems so, but I notice that sometimes the Teensy will slip in versions of the same libraries that work better for Teensys. All the code I've written runs fine on both. Just I can run a LOT more on the Teensy.

For example : Teensy 3.2 & Feather FONA cell phone.

This was a LOT of code. Not all that difficult, just a LOT of it.

-jim lee

To complete this post…

The Teensy works great and after some more programming it works exactly how I wanted it to.

I am just working on battery supplies now so its all working, THANKS!

You're welcome!

-jim lee