1

I suspect that I might be using too many resources, because of the strings, but I am new to Arduino, so not sure where I am going wrong. Any help would be appreciated.

What it should do when complete: "Birthday Reminder"

  • Read the SD card for a "birthday" file ("MM-DD.txt" ) that matches "today".
  • The file contains the Name of the person and year of birth.
  • Flash LED's and display the person's name and age on the OLED.
  • The unit will not displayed anything until someone's birthday.

I am using the SD card with a simple format, to easily add new birthday's.

Output:
⸮Initializing SD card...
card initialized.
25
111
opening: 04-25.txt
2020"Test Person"
bYearStr: 20

The sd card file:

Name: MM-DD.txt 
Example: 04-25.txt 

Contents: Birthyear, Name 
Example: 2020"Test Person"

The Sketch

#include <SPI.h>
#include <SD.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include "RTClib.h"

RTC_DS3231 rtc;

int age;
int led;
int dly;
String bName;
String bYearStr;
int bYear;
int currentDay;
int  tDy;
int  tMth;
int  tYr;

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
#define OLED_RESET -1
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

void setup() {
  Serial.begin(9600);
  delay(3000); // wait for console opening

  // Setup Candle pins
  pinMode(3, OUTPUT);
  pinMode(4, OUTPUT);
  pinMode(5, OUTPUT);
  if (! rtc.begin()) {
    Serial.println("Couldn't find RTC");
    while (1);
  }

  if (rtc.lostPower()) {
    Serial.println("RTC lost power, lets set the time!");
    // following line sets the RTC to the date & time this sketch was compiled
    rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
    // This line sets the RTC with an explicit date & time, for example to set
    // January 21, 2014 at 3am you would call:
    // rtc.adjust(DateTime(2014, 1, 21, 3, 0, 0));
  }

  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  display.clearDisplay();
  display.display(); // this command will display all the data which is in buffer
  display.setTextColor(WHITE, BLACK);

  const int SdCardPin = 10;
  Serial.println("Initializing SD card..."); // Verifying SD Card.
  //  if (!SD.begin(SdCardPin)) {
  if (!SD.begin(10)) {
    Serial.println("Card failed, or not present"); // don't do anything more:
    while (1);
  }
  Serial.println("card initialized.");

  currentDay = 111;
}


void loop() {
  age = 0;

  DateTime now = rtc.now();
  tDy = now.day();
  tMth = now.month();
  tYr = now.year();
  Serial.println(tDy);
  Serial.println(currentDay);

  if (currentDay != tDy) {
    String fileName;
    String m = String(tMth);
    String d = String(tDy);

    if (tMth < 10) {
      String mm = m;
      m = "0" + mm;
    }
    if (tDy < 10) {
      String dd = d;
      d =  "0" + dd;
    }
    fileName = m + "-" + d + ".txt";

    //String fileName = "01-01.txt";
    Serial.println("opening: " + fileName);
    File f = SD.open(fileName);
    delay(3000);

    if (!f) {
      Serial.println("open failed");
    } else {
      while (f.available()) {
        String line = f.readStringUntil('\n');
        Serial.println(line);
        bYearStr = line.substring(0, 4);
        bName = line.substring(4);
        delay(1000);
        currentDay = tDy;
        break;
      }
      f.close();
    }

    Serial.print("bYearStr: ");
    Serial.println(bYearStr);
    Serial.print("bName: ");
    Serial.println(bName);
    int bYear = bYearStr.toInt();
    age = tYr - bYear;
    Serial.print("Age: ");
    Serial.println(age);


  } else { // Start current day equal
    display.setTextSize(1);
    display.setCursor(38, 10);
    display.print("Birthday!");
    display.println();

    display.setTextSize(1);
    display.setCursor(0, 30);
    display.print("Name: ");
    display.print(bName);
    display.setCursor(0, 40);
    display.print("Age: ");
    display.print(age );
    display.println();

    led = random(3, 6);
    dly = random(80, 250);
    digitalWrite(led, HIGH);   // turn the LED on (HIGH is the voltage level)
    delay(dly);              // wait
    digitalWrite(led, LOW);    // turn the LED off by making the voltage LOW
  } // End current day

  display.display();

} // End void Loop
4
  • There is no more Serial output after the 2 first characters of the String "2020" ? Very strange. You might simplify your sketch to a test environment only, without all the display stuff, eventually even without RTC. – DataFiddler Apr 26 at 10:09
  • 1
    Don´t use String class. Replace it with char arrays. You are probably running out of heap memory. – SBF Apr 26 at 10:21
  • Thanks. @DataFiddler I have done that and it works to the serial print. The sketch also works when I don't us the SD card, but put the birthday's in an smaller test array (3 names). I run into problems with larger arrays (13 names). I broke it apart and have been building it back up. For info, I am not getting the display at all, it appears the script stops at the output. – E_Ja Apr 26 at 10:23
  • Thanks, @SBF I was afraid that is what was happening. – E_Ja Apr 26 at 10:26
3

It is quite likely you're out of memory. To know, really you need to keep track of the stack pointer and heap structure. There's enough going on in your code, that I'm not reasonably going to be able to set up an identical scenario to prove it.

Display Library

The greatest use of SRAM in your project comes from the Adafruit SSD1306 library. It's allocating a buffer for the entire frame, which is 128 * 64 == 8192 bits, or 1024 bytes, half your Nano's SRAM. It will use more than that for other data, though probably not a lot more by percentage.

You may be able to mitigate this usage with a library like trades some SRAM usage for extra execution time or lower frame rates. One is the U8g2 library. You can read about how it can use half-buffer in some documentation to the older version of the library; the concept carries over to the newer library.

SD Library

The next largest usage is the SD card libraries sector buffer, which is 512 bytes.

So between the two of them, there go 3/4 of your SRAM in just the two buffers, nevermind anything else.

You may be able to avoid this buffer by using a different library. You could try PetitFS. The library it wraps says it uses something like 44 bytes plus whatever call stack is needed.

String Literals

You have at least 206 bytes of string literals in your sketch alone, assuming the compiler can't determine that any of them are in fact unused. There may be others in the libraries, though probably not many. So now we're down to at least 306 bytes or less SRAM.

Size String
35 "RTC lost power, lets set the time!"
28 "Card failed, or not present"
24 "Initializing SD card..."
18 "Couldn't find RTC"
18 "card initialized."
12 "open failed"
11 "bYearStr: "
10 "opening: "
10 "Birthday!"
10 "01-01.txt"
8 "bName: "
7 "Name: "
6 "Age: "
5 ".txt"
2 "-"
2 "0"
206 Total

The "0" is only counted once in the above, even though you have two of them. Fun fact: the C and C++ languages allow identical string literals to be colocated or overlap. I don't know whether or not avr-gcc will combine "Name: " into "bName: ", since you have bigger problems to worry about, I'm just pretending it won't without looking.

Mitigation can include use of the F() macro to move some of these string literals into PROGMEM (flash). You can't do this to every string literal, but the ones where you're involving them in a String(F("we're trying to avoid these right?")) constructor or in Serial (or other Stream/Print instances)'s .print(F("Hello World"));

The guts of the F() macro is another called PSTR(), which does the actual work of putting the string literal into PROGMEM. F() tags the resulting pointer with a dummy type that is used in function overloading in Arduino libraries. The only reason why I mention this is because below, you'll see me use PSTR to do the same thing, only with a function that belongs to avr-libc rather than Arduino.

Note when you're initializing a String object from a regular string literal, you're paying the SRAM cost twice for the lifetime of the String object. One copy is in SRAM for the literal used to call the String constructor, the other copy is in the dynamic memory allocated to the String object.

String class

This was pointed out in the comments; and it's true that it's better to avoid them. Although, I think not actually the greatest problem in your case.

I see you've avoided unnecessarily adding them together for Serial output and instead are using separate .print() calls, so you're ahead of the game there.

For your filename, mitigation could be using fixed size local char array and snprintf_P() to cobble together your filename, something like:

char filename[sizeof "MM-DD.txt"];
snprintf_P(
    filename, sizeof filename,
    PSTR("%02u-%02u.txt"),
    tMth, tDy
);

or if you wanted to avoid pulling the beast of printf into your sketch, assuming it's not already there, you could do something like:

const char filename[] = {
    '0' + tMth / 10,
    '0' + tMth % 10,
    '-',
    '0' + tDy  / 10,
    '0' + tDy  % 10,
    '.',
    't',
    'x',
    't',
    '\0'
};

Serial

What Serial? Yup, I mean, there's not a lot you can do about it, but I bring it up because the last conservative figure above was 306 bytes, and we didn't try to account for the what String is doing in the heap at all. Well Serial takes 157 bytes according to a simple test here, which makes sense, because on the UNO it has two ring buffers for transmit and receive that are 64 bytes in size each, half a dozen pointers for configuring hardware registers to use, and head and tail indices for those buffers. There's also another 12 bytes that are used for HardWareSerial's vtable.

So that puts you down to conservatively 137 bytes of SRAM for well, everything else.

Everything Else

There's a lot not accounted for here. Your globals, the libraries globals, other vtables, the String data on the heap, and the stack. Those are just the ones coming to mind. I mean just the millis()/micros() counter variables and fract take 9 bytes. If you really want to dig into the details here, besides looking at the stack pointer and heap structure, the avr-nm command run on your sketch's .elf file with its -D, -S, and --size-sort is helpful for locating global variable usage and similar things.

1
  • Actually you could modify the core files (or possibly the board definition) to get HardwareSerial to use a smaller buffers. So, it's not completely out of your control. Not sure if it's worth editing in; it's kind of lower on te list. – timemage Apr 27 at 16:38

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.