Room to optimize code

Hi folks,

I have an ECG project, and my first Idea was just to have a simple datalogger with SD card. Done, its working.
Further, I wanted to add an OLED display (SSD1306 128x64), but when I included the library, and after the display init, the program has stucked in loop because after the memory allocation, there was not enough memory space left.
Currently I'm at 55% of program space, and 44% of dynamic memory space.
Can somebody check my code for available reducible dynamic memory space?

/*
 * ToDo: Update bootloader of Nano
 * Portable ECG
 * Author: yoman91
 * RTC setting command on UART: "$yymmdd_hhmmss\n"
 * RTC time checking command on UART: "#/n"
 * 
 * LEDs description: 
 * Green ON, Red OFF:   Normal operation
 * Green ON, Red ON:    SD Card problem
 * Green OFF, Red ON:   Leads Off detection
 * Green OFF, Red OFF:  Sleep mode/No power
 * 
 * Output: 
 * GreenLed
 * RedLed
 * Power switching output
 * Naming of SD file: 8.3 conversion restricted: hhmmss.csv
 */

extern "C"{
  #include "DS1337.h"
}
#include <SPI.h>
#include <SD.h>
#include <avr/sleep.h>
#include <avr/wdt.h>

File myFile;

uint8_t sensorPin = A0, triggerPin = 2, buttonPin = 3, greenLEDPin = 6, redLEDPin = 7;
uint8_t leadOff_neg = 8, leadOff_pos = 9, powerOutPin = 10, sensorValue = 0;
uint8_t second = 0, minute = 0, hour = 12, day = 1, month = 1, year = 21;
uint8_t lcdcursor = 0;

String bufferString  = "";
char buf[10];
char fileName[12]; //, fileName = "";

struct FLAG
{
  uint8_t serialInputFinished :1;
  uint8_t setsTime :1;
  uint8_t sendRTCTime :1;
  volatile uint8_t writeFile :1;
  volatile uint8_t trigger :1;
  volatile uint8_t sleep :1;  
}flag = {false, false, false, false, false, false};


ISR(TIMER1_COMPA_vect)
{
  /* 10ms timer interrupt */
  TCNT1 = 0;
  flag.writeFile = true;
}

ISR(INT1_vect)
{
  /* Button handling interrupt for wake up */
  /* Add a switch between D3 and GND. */

  /* If the switch is not in ON state */
  if(digitalRead(buttonPin))
  {
    // Wake up  
    flag.sleep = false;
  }
  else /* If the switch is not in OFF state */
  {
    flag.sleep = true;
  }
}

ISR(INT0_vect)
{
  /* Button handling interrupt for trigger */
  /* Add a push button between D2 and GND. If it pressed, then this interrupt fired */
  flag.trigger = true;
}

void setup()
{
  /* Data direction definition */
  pinMode(sensorPin,   INPUT);  pinMode(leadOff_pos, INPUT);  pinMode(leadOff_neg, INPUT);  pinMode(buttonPin,  INPUT_PULLUP);
  pinMode(greenLEDPin, OUTPUT); pinMode(redLEDPin,   OUTPUT); pinMode(powerOutPin, OUTPUT); pinMode(triggerPin, INPUT_PULLUP);
  
  /* Set Timer to 10 ms */
  TCCR1B |= (1 << CS11);      //Set CS11 to 1 so we get prescalar 8  
  TIMSK1 |= (1 << OCIE1A);    //Set OCIE1A to 1 so we enable compare match A 
  OCR1A   = 20000;            //10ms - 20000; 1ms = 2000; 0.5ms = 1000

  /* Set up interrupt INT1 */
  EICRA   = (1 << ISC10) | (1 << ISC01);    //Any logical change INT1, falling INT0
  EIMSK   = (1 << INT1) | (1 << INT0);      //Enable external interrupts

  /* Init UART */
  Serial.begin(9600);
  while (!Serial) { ; } // wait for serial port to connect. Needed for native USB port only

  bufferString.reserve(20); // reserve 20 bytes for the bufferString:

  /* Read out current time from RTC and save to eeprom */
  year = read_year();     // eeprom_update_byte(&year, read_year_byte()); 
  month = read_month();   // eeprom_update_byte(&month, read_month()); 
  day = read_date();      // eeprom_update_byte(&day, read_date()); 
  hour = read_hour();     // eeprom_update_byte(&hour, read_hour()); 
  minute = read_minute(); // eeprom_update_byte(&minute, read_minute()); 
  second = read_second(); // eeprom_update_byte(&second, read_second()); 

  /* Create filename */
//  if(hour < 10)   {fileName  = "0" + String(hour);  }
//  else            {fileName  = String(hour);        }
//  if(minute < 10) {fileName += "0" + String(minute);}
//  else            {fileName += String(minute);      }  
//  if(second < 10) {fileName += "0" + String(second);}
//  else            {fileName += String(second);      }
//                   fileName += ".csv";
  sprintf(fileName, "%u%u%u%u%u%u.csv",
  ((hour < 10) ? 0 : hour / 10),       // ((eeprom_read_byte(&hour) < 10) ? 0 : eeprom_read_byte(&hour) / 10), 
  ( hour % 10),                        // ( eeprom_read_byte(&hour) % 10),
  ((minute < 10) ? 0 : minute / 10),   // ((eeprom_read_byte(&minute) < 10) ? 0 : eeprom_read_byte(&minute) / 10), 
  ( minute % 10),                      // ( eeprom_read_byte(&minute) % 10),
  ((second < 10) ? 0 : second / 10),   // ((eeprom_read_byte(&second) < 10) ? 0 : eeprom_read_byte(&second) / 10), 
  ( second % 10));                     //  (eeprom_read_byte(&second) % 10));  
   
  
  /* Define watchdog */
  wdt_enable(WDTO_2S);
  
  /* Init SD card */
  SdFile::dateTimeCallback(dateTime);

  if (!SD.begin(4)) // chip select = D4 
  {
    //initialization failed
    digitalWrite(redLEDPin,   HIGH);
    digitalWrite(greenLEDPin, HIGH);
    wdt_disable();
    while (1);
  }
  
  /* Write header to SDcard */
  myFile = SD.open(fileName, FILE_WRITE);
  if (myFile) 
  {
    myFile.println(F("SensorValue\tTrigger"));
    myFile.close();
  } 
  
  digitalWrite(powerOutPin, HIGH);
  
  sei();
}

void loop()
{
  wdt_reset();
  
  digitalWrite(redLEDPin,   LOW);
  digitalWrite(greenLEDPin, LOW);

  if(flag.sleep)
  {
    Sleep();
  }

  /* Set RTC time from UART */
  if(flag.serialInputFinished && flag.setsTime)
  {
    /* parse bufferString, which is like "$210314_205612\n" */
    year = bufferString.substring(1, 3).toInt();       // eeprom_update_byte(&year, bufferString.substring(1, 3).toInt());
    month = bufferString.substring(3, 5).toInt();      // eeprom_update_byte(&month, bufferString.substring(3, 5).toInt()); 
    day = bufferString.substring(5, 7).toInt();        // eeprom_update_byte(&day, bufferString.substring(5, 7).toInt()); 
    hour = bufferString.substring(8, 10).toInt();      // eeprom_update_byte(&hour, bufferString.substring(8, 10).toInt()); 
    minute = bufferString.substring(10, 12).toInt();   // eeprom_update_byte(&minute, bufferString.substring(10, 12).toInt()); 
    second = bufferString.substring(12, 14).toInt();   // eeprom_update_byte(&second, bufferString.substring(12, 14).toInt()); 

    //set_time_and_date(eeprom_read_byte(&year), eeprom_read_byte(&month), eeprom_read_byte(&day), eeprom_read_byte(&hour), eeprom_read_byte(&minute), eeprom_read_byte(&second));
    set_time_and_date(year, month, day, hour, minute, second);
    
    bufferString = "";  

    flag.serialInputFinished = false;
    flag.setsTime = false;
  }

  /* Send RTC time to UART */
  if(flag.sendRTCTime && flag.serialInputFinished)
  {
    sendRTCTime();
    bufferString = "";
    flag.sendRTCTime = false;
    flag.serialInputFinished = false;
  }

  /* Leads OFF detection */
//  if( (digitalRead(leadOff_neg) == 1) || (digitalRead(leadOff_pos) == 1) )
//  {
//    digitalWrite(redLEDPin, HIGH);
//    wdt_disable();    
//    while(1);
//  }

  /* Write data to SD card */
  if(flag.writeFile)
  {
    digitalWrite(greenLEDPin, HIGH);
    sensorValue = analogRead(sensorPin);
    
    /* Write data to SDcard */
    myFile = SD.open(fileName, FILE_WRITE);
    if (myFile) 
    {
      sprintf(buf, "%u\t%u", sensorValue, flag.trigger);
      myFile.println(buf);
      myFile.close();
      
      flag.trigger = false;
    }
    else 
    {
      // failed to open
      digitalWrite(redLEDPin,   HIGH);
      digitalWrite(greenLEDPin, HIGH);
    }
    flag.writeFile = false;
  }  
}

void serialEvent()
{
  while (Serial.available()) 
  {
    char inChar = (char)Serial.read(); // get the new byte: 
    bufferString += inChar; // add it to the bufferString

    if(inChar == '$')
    {
      // request to set the rtc time
      flag.setsTime = true;
    }
    else if(inChar == '#')
    {
      // send out the rtc time on uart      
      flag.sendRTCTime = true;
    }
    else if (inChar == '\n') 
    {
      flag.serialInputFinished = true;
    }
    else
    {
      // Do nothing
    }
  }
}

void sendRTCTime(void)
{
  Serial.println(String(read_year()) + '.' + String(read_month()) + '.'+ String(read_date()) + '_' + String(read_hour()) + ':' + String(read_minute()) + ':' + String(read_second()));
}

void Sleep(void)
{
  digitalWrite(greenLEDPin, LOW);
  digitalWrite(redLEDPin,   LOW);
  digitalWrite(powerOutPin, LOW);

  /* Define the sleep mode - max power consumption */
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);
  cli();   
  sleep_enable();
  wdt_disable();
  sei();
  sleep_cpu();
  sleep_disable();
  
  wdt_enable(WDTO_1S);
  delay(1500); //Force watchdog reset 
  
  /* When exiting sleep mode, first the modules has to be powered on */
  digitalWrite(powerOutPin, HIGH);
}

void dateTime(uint16_t* date, uint16_t* time) 
{
 /* return date using FAT_DATE macro to format fields */
 *date = FAT_DATE(year, month, day);

 /* return time using FAT_TIME macro to format fields */
 *time = FAT_TIME(hour, minute, second);
}

Thanks for trying to post your code correctly, but you used Blockquote tags (the '' icon) rather than code tags (the </> icon)

See How to get the best out of this forum for advice on using code tags

Look at the u8g2 library and the use of a page buffer instead of a full buffer for the display. Depending on what you want to display, the u8g2 library includes the u8x8 library that needs no display buffer, but is limited to fixed-pitch fonts and no graphics.

The 128x64 display full buffer needs 1024 bytes of ram, and the SD library uses a 512 byte buffer, leaving 512 bytes for everything else.

Also look at sprintf_P, that will store the format string in program memory, saving some ram.

Can't see anything like that in the code you posted!

Yes, I deleted everything because it stopped the program. I used Adafruit_SSD1306.h, and it stopped at

  while(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3D for 128x64
    Serial.println(F("SSD1306 allocation failed"));
  }

Thanks, I fixed it.

Thanks for the tip. Unfortunately, I would need full graphic, because I'd like to show an ECG curve.
I tried the sprintf_P, but nothing changed regarding the space.

Slowly I need to admit that the display is a big ram-consuming device, that I cannot use in this project - at least while I'm not swapping the Arduino Nano to a more serious device.

Yup, just buy your way out of trouble with a Feather or Teensy or whatever your favorite vendor offers with more RAM.

1 Like

The u8g2 library using a page buffer does allow full graphics, but is slightly slower that a full buffer.

1 Like

So, you have a program that works and a program that doesn't work. You want help with the program that doesn't work, so you post the program that does work? Your logic escapes me!

But anyway, you are using 2 devices that both need quite a lot of ram memory, SD card and graphic display.

As others mentioned, something with more ram, and something that runs at 3.3V perhaps, because SD card writers and most oled displays also run at 3.3V I think. I would also go for an oled with SPI interface, they are faster to update than i2c and should be able to share most pins with the SD card. Maybe something based on samd21 chip so that it Arduino zero compatible.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.