Customer Check-In
Despite all the fiddling and stuff like running out of CO2, fixing the pumps…I’m pleased with how my customers are doing in the leaf spa. Here are pictures from yesterday:
I’ve been using the Kale and A LOT of basil.
Today’s Post
I thought I had fixed the challenge I was having writing records out to the log file. Well…I haven’t. I just looked at the most recent log file and see that it only has 28 rows. Hmmm….In this post I find and fix a memory leak but first I take a step back to understand how memory is used by the Arduino. I am glad I did. I now have a much better conceptual model with where my code is going versus my variables…even though I “sort of” knew this, I did not have a strong enough conceptual model in my head…I guess I lost my memory about (Arduino) memory.
The memory leak turned out to be repeated calls to openFile() in writeSensorDataToLogFile() that created and left hanging around instances of File objects:
void writeSensorDataToLogFile() { File logFile = openFile();
if (!logFile) {
return;
} dtostrf(sensorData.temperatureValue, 4, 1, additionalInfo); char *idx = additionalInfo + strlen(additionalInfo); *idx++ = ','; dtostrf(sensorData.humidityValue, 5, 1, idx); idx = additionalInfo + strlen(additionalInfo); *idx++ = ','; itoa(sensorData.CO2Value, idx, 10); writeEventHappened(SensorData, additionalInfo); }
Thanks to Those That Went Before
Adafruit: Thank you for your excellent tutorial on “Memories of an Arduino.” I am an extremely happy customer of Adafruit. I feel great about buying from Adafruit because I feel that a lot of the margins made are given back to us through these great tutorials, youtube videos, friendliness, etc. Adafruit is NOT a company in which a few (usually white men) are making a ton of money while they lack the compassion shown by Adafruit. Again THANK YOU.
Open Source
The version of TheLeafSpa.ino that evolved from this post is located at this GitHub location.
Debugging Memory Use
How Memory is Used by the Arduino
I decided to take a step back and appreciate the memory architect used by the Arduino. It’s like going into a place that does wine sampling. A moment to reflect on why something is the way it is, and appreciate it for what it is…OK..enough with the analogies.
Adafruit’s excellent section on memory architecture, notes on the Arduino, Flash = Program Memory and SRAM = Data Memory.
“The Arduino UNO has only 32K bytes of Flash memory and 2K bytes of SRAM.” Note: for completeness there is also EEPROM. However, theLeafSpa.ino challenge would be on how much Flash and/or SRAM the code is using.
Looking at this table,
I might consider using a Mega instead of the Uno. Or perhaps the Adafruit Feather M0 WiFi – ATSAMD21 + ATWINC1500. If I switch to the Feather (which is tempting…), I lose the Grove Connector base shield..of course, I could have a Frankenstein way of tying altogether for now…
How Memory is Used by TheLeafSpa.ino
From my previous post, I noted the current amount of program storage (flash) and SRAM (dynamic memory) the compiler and boot loader let me know about:
Sketch uses 24,970 bytes (77%) of program storage space. Maximum is 32,256 bytes.
Global variables use 1,416 bytes (69%) of dynamic memory, leaving 632 bytes for local variables. Maximum is 2,048 bytes.
Hmm…these numbers made me think I don’t have this type of issue:
but…I underemphasized the term “dynamic” in my thinking. The amount of SRAM changes.
Using free_ram()
I had been calling free_ram() at the beginning of the code (see the debug library)…but I wasn’t doing what Bill recommended in the Adafruit article: “SRAM utilization is dynamic and will change over time. So It is important to call free_ram() at various times and from various places in your sketch to see how it changes over time.”
I added a function to add an amount SRAM record to the log file:
/* Write the amount of SRAM available after the record has been written. If there is a change, it's of interest to know the length of stringBuffer and additionalInfo. Are they what I expect them to be? */ void writeFreeRamtoStringBuffer() { int sram = freeRam(); itoa(AmtSRAM, stringBuffer, 10); char *idx = stringBuffer + strlen(stringBuffer); *idx++ = ','; itoa(sram, idx, 10); idx = stringBuffer + strlen(stringBuffer); *idx++ = ','; itoa(strlen(stringBuffer), idx, 10); idx = stringBuffer + strlen(stringBuffer); *idx++ = ','; itoa(strlen(additionalInfo), idx, 10); }
/* free_ram() */ int freeRam () { extern int __heap_start, *__brkval; int v; return (int)&v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); }
The added AmtSRAM records are added within the writeEventHappened function:
void writeEventHappened(logRow_t event, char * additionalInfo) { if (event == CardInserted || event == CardRemoved) { return; } File logFile = openFile(); if (!logFile) { return; } writeFreeRamtoStringBuffer(); logFile.println(stringBuffer); //////////////////////////////////////////////////////////////////////////// // // an event has a log row type followed by date/time followed by additonal info. itoa(event, stringBuffer, 10); char *idx = stringBuffer + strlen(stringBuffer); *idx++ = ','; makeDateTimeString(idx); idx = stringBuffer + strlen(stringBuffer); *idx++ = ','; strcpy(idx, additionalInfo); logFile.println(stringBuffer); writeFreeRamtoStringBuffer(); logFile.println(stringBuffer); //////////////////////////////////////////////////////////////////////////// logFile.flush(); logFile.close(); }
Looking at the log file:
13,548
53,3/29/2017,10:0:18,120,1200,60,900,0,8
13,548,40,19
13,548
9,3/29/2017,10:0:18,
13,548,20,0
13,526
10,3/29/2017,10:3:18,
13,526,21,0
13,526
3,3/29/2017,10:3:18,
13,526,20,0
13,459 (67 bytes)
0,3/29/2017,10:4:18,22.1, 84.0,764
13,459,34,14
13,490
5,3/29/2017,10:4:18,
13,490,20,0
13,495
6,3/29/2017,10:4:26,
13,495,20,0
13,428 (67 bytes)
0,3/29/2017,10:6:18,22.0, 82.7,1343
13,428,35,15
13,397 (31 bytes)
0,3/29/2017,10:8:18,21.9, 83.0,967
13,397,34,14
13,428
5,3/29/2017,10:8:18,
13,428,20,0
13,433
6,3/29/2017,10:8:23,
13,433,20,0
13,366 (67 bytes)
0,3/29/2017,10:10:18,21.9, 83.5,1168
13,366,36,15
13,397
5,3/29/2017,10:10:18,
13,397,21,0
13,402
6,3/29/2017,10:10:23,
13,402,21,0
13,335 (67 bytes)
0,3/29/2017,10:12:18,21.9, 83.7,1301
13,335,36,15
13,304 (31 bytes)
0,3/29/2017,10:14:18,21.8, 84.0,973
13,304,35,14
13,335
5,3/29/2017,10:14:18,
13,335,21,0
13,340
6,3/29/2017,10:14:23,
13,340,21,0
13,337
1,3/29/2017,10:15:18,
13,337,21,0
13,330
2,3/29/2017,10:16:18,168.4
13,330,26,5
13,273
0,3/29/2017,10:16:18,21.8, 84.2,1191
13,273,36,15
13,304
5,3/29/2017,10:16:18,
13,304,21,0
13,309
6,3/29/2017,10:16:23,
13,309,21,0
13,247
6,3/29/2017,10:20:23,
13,247,21,0
The most glaring thing I notice is SRAM is lost after writing a row that has a record type that is not a sensor reading (i.e.: record type is not 0 and before writing a sensor reading. I highlighted these occurrences above.
This is how I tracked down the overzealous / needless creation of File Instances:
void writeSensorDataToLogFile() { File logFile = openFile();
if (!logFile) {
return;
}
The log file now looks like:
11,545,7,19
53,3/30/2017,0:58:27,120,1200,60,900,0,8
11,545,40,19
11,545,12,19
9,3/30/2017,0:58:27,
11,545,20,19
11,523,12,19
10,3/30/2017,1:1:27,
11,523,20,19
11,523,12,19
4,3/30/2017,1:1:27,
11,523,19,19
11,523,12,14
0,3/30/2017,1:2:27,18.8, 79.5,523
11,523,33,14
11,523,12,14
0,3/30/2017,1:4:27,18.8, 79.6,514
11,523,33,14
11,523,12,14
0,3/30/2017,1:6:27,18.8, 79.7,515
11,523,33,14
11,523,12,14
0,3/30/2017,1:8:28,18.7, 79.9,514
11,523,33,14
11,523,12,14
0,3/30/2017,1:10:28,18.7, 79.7,515
11,523,34,14
11,523,12,14
0,3/30/2017,1:12:28,18.7, 79.8,522
11,523,34,14
11,520,12,14
1,3/30/2017,1:13:27,
11,520,20,14
11,513,12,14
2,3/30/2017,1:14:27,163.9
11,513,25,14
11,523,12,14
I changed the log file row types for the amount of SRAM. AmtSRAM = record type 11. The amount of SRAM starts out as 545 bytes prior to writing out the first record (53). It goes down to 523 before writing out record type 10 (warmup is over). This is a loss of 22 bytes which remains consistent. So at this point, I’m not researching deeper. When a relay gets switched there are some bytes that get lost. However, these return. The SRAM stays around 523 bytes. This will work.
I enjoyed working on this. It is always fun to learn and seeing positive changes based on applying what I have learned.