3
\$\begingroup\$

I'm using the Arduino IDE with arduino-tiny (https://code.google.com/p/arduino-tiny/) on an ATTIny85. My code is maxing out the RAM, or so it seems:

Adding a single String to my code, even just carrying one character, throws a compiler error:

(...)region `text' overflowed by 452 bytes

The line I add to get to this is a simple

String name2 = "A";
(...)
matrix.print(temp2 + name2);

Just for comparison: Code size of the .hex file without the String is 7.430 Bytes, with the String defined but not used it's 8092 bytes, with it being defined and used it's overflowing. This seems to be a bit much, especially as it does not seem to matter if my String is A or ABCDEFG - I always get the overflow by 452 bytes.

Any idea how to get around this? I tried putting the String into PROGMEM, but the matrix.print doesn't work with any methods of retrieval I have tried (besides copying into RAM, but then of course I get the overflow again). I have also tried stripping the Adafruit GFX library, but it appears as though only the needed parts are pulled into the compilation anyways (as there was no change in .hex-file size).

The full code, just to give you an idea of what I am doing (accessing an Adafruit 8x8 LED matrix, reading a temperature value from a DS18S20 1-Wire digital thermometer, outputting a fading in-and-out smiley, the temperature and the name of my child to that LED matrix ;), is here:

#include <TinyWireM.h>
#include <Tiny_LEDBackpack.h>
#include "Adafruit_GFX.h"
#include <avr/pgmspace.h>
#include <OneWire.h>

#define ONE_WIRE_BUS 4
OneWire  ds(4);  // on ATTiny85 pin 1

int buttonState = 0;
byte addr[8];
float temp;

static uint8_t PROGMEM
  smile_bmp[] =
  { B00111100,
    B01000010,
    B10100101,
    B10000001,
    B10100101,
    B10011001,
    B01000010,
    B00111100 };

Tiny_8x8matrix matrix = Tiny_8x8matrix();

void setup() {
  matrix.begin(0x70);  // pass in the address
  matrix.setBrightness(1);
}

void loop() {
  byte addr[] = { 0x28, 0xad, 0x3f, 0x51, 0x4, 0x0, 0x0, 0x2a };
  temp = readTemperatureFromSensor(addr);

  matrix.begin(0x70);  // pass in the address
  matrix.setBrightness(1);
for (int8_t c=1; c<=2; c++) {
  for (int8_t x=1; x<=15; x++) {
    matrix.setBrightness(x);
    matrix.clear();
    matrix.drawBitmap(0, 0, smile_bmp, 8, 8, LED_ON);
    matrix.writeDisplay();
    delay(50);
  }
  for (int8_t x=15; x>=1; x--) {
    matrix.setBrightness(x);
    matrix.clear();
    matrix.drawBitmap(0, 0, smile_bmp, 8, 8, LED_ON);
    matrix.writeDisplay();
    delay(50);
  }
}

  matrix.clear();
  matrix.writeDisplay();
  delay(50);
  String name2 = "A";
  int temp2 = int(temp);

  matrix.setBrightness(10);
  matrix.setTextSize(1);
  matrix.setTextWrap(false);
  matrix.setTextColor(1);
  for (int8_t x=0; x>=-24; x--) {
    matrix.clear();
    matrix.setCursor(x,0);
    matrix.print(temp2 + name2);
    matrix.writeDisplay();
    delay(200);
  }


}

float readTemperatureFromSensor(byte addr[])
{
  byte i;
  byte present = 0;
  byte type_s;
  byte data[12];
  float celsius;  
  ds.reset();
  ds.select(addr);
  ds.write(0x44,1);         // start conversion, with parasite power on at the end
  delay(1000);     // maybe 750ms is enough, maybe not
  present = ds.reset();
  ds.select(addr);    
  ds.write(0xBE);         // Read Scratchpad
  // reading the data from the sensor
  for ( i = 0; i < 9; i++) {           // we need 9 bytes
    data[i] = ds.read();
  }

  // convert the data to actual temperature
  unsigned int raw = (data[1] << 8) | data[0];
  if (type_s) {
    raw = raw << 3; // 9 bit resolution default
    if (data[7] == 0x10) {
      // count remain gives full 12 bit resolution
      raw = (raw & 0xFFF0) + 12 - data[6];
    }
  } else {
    byte cfg = (data[4] & 0x60);
    if (cfg == 0x00) raw = raw << 3;  // 9 bit resolution, 93.75 ms
    else if (cfg == 0x20) raw = raw << 2; // 10 bit res, 187.5 ms
    else if (cfg == 0x40) raw = raw << 1; // 11 bit res, 375 ms
    // default is 12 bit resolution, 750 ms conversion time
  }
  celsius = (float)raw / 16.0;
  return celsius;
}
\$\endgroup\$
2
  • 3
    \$\begingroup\$ Pure speculation because I've never used those libraries but I wonder if using matrix.print causes a relatively large font table to get linked in? It sounds like about the right size for a 96 x 5 x 8-bit font table. Maybe you could drop out some characters you're not using. \$\endgroup\$
    – PeterJ
    May 29, 2013 at 12:42
  • \$\begingroup\$ It does, but later on the message text will vary, so I just might try this a a very last resort. Thanks for the idea though ;) \$\endgroup\$
    – Christian
    May 29, 2013 at 18:46

3 Answers 3

8
\$\begingroup\$

"text" is actually code flash size, not SRAM.

There are several problems here:

1) The "print" function pulls in a large amount of code for formatting format strings. This is dumb. The '85 only has 8 kB of flash total, compared to the 32 kB of the Arduino Uno.

2) The String class uses both some amount of code space, AND a lot of SRAM. The '85 only has 512 bytes of SRAM, for all your variables, non-progmem string constants, and stack. The String class may even use heap allocations (malloc()/nw). The String class is an abomination from the point of view of embedded programming. Don't use it!

3) For proper embedded resource usage, you want string literals to live in PROGMEM. You want a single buffer in RAM into which you copy the strings you actually need to operate on, when you need to operate on them. Look up strcpy_P() and friends, as a much better suited set of functions.

4) You don't want to use malloc() or new, ever, in embedded systems like these, for two reasons. The first reason is that the heap will make inefficient use of RAM for control overhead. The second is that you can't prove that your program will actually be correct as easily. More RAM isn't coming from anywhere, so you should be able to account for all RAM that you actually need. The way to do that is with static or global allocations. Note that this is contrary to code structure advice you'd use on a large system running on a large CPU -- the rules are different, because the CPU/system is different.

Finally, if you need to, you can use placement new to initialize object instances; it's the allocating version of new that's bad.

\$\endgroup\$
1
  • \$\begingroup\$ Thanks Jon, that is quite a bit od info to digest (for me it is). I'm especially thankful for being direct in terms of what not to use on these little pieces of wonder ;) \$\endgroup\$
    – Christian
    May 30, 2013 at 6:24
2
\$\begingroup\$

Off hand I believe your problem is that the print method, places the constant text strings into RAM. Please see my answer to arduino flash memory limit, where the F() macro compiles the code to use the const string directly from Flash and not RAM.

Then try

matrix.print(temp2);
matrix.print(F("Hello World"));
PROGMEM const char name2[] = "A"; // replaces: String name2 = "A";
matrix.print(name2);
\$\endgroup\$
3
  • \$\begingroup\$ Great idea, but doing just the second line I get only initialized variables can be placed into program memory area and error: initializer fails to determine size of '__c'... Err? \$\endgroup\$
    – Christian
    May 29, 2013 at 18:44
  • \$\begingroup\$ I missed name2 being a type of String, the F() function won't know what to do with the String Object, change it to a char array e.i. char name2[2]; \$\endgroup\$
    – mpflaga
    May 29, 2013 at 19:44
  • \$\begingroup\$ @mpflaga: In my testing, "Serial.print(F(name2));" gives me the same error that Christian reported. I hope you don't mind me editing your code to use F() with literal strings, and PROGMEM with "C string" variables -- that way my test code at least compiles and runs. \$\endgroup\$
    – davidcary
    May 30, 2013 at 0:59
2
\$\begingroup\$

Basically, the answer is to write your program in C, and not use malloc or any other dynamic memory allocation.

While I understand why the arduino people want to make programming easier, but as @JohnWatte has said, the String class on an embedded microcontroller has never been anything but a terrible idea. If you really have to have string manipulation, do it like you would in C. E.g. char arrays, and all the old C string manipulation routines (strstr, strcat, strcpy, etc...).

Statically allocate EVERYTHING. This isn't a problem in this case, but it's a good thing to remember.

I don't know what the matrix library is doing, but I would be surprised if a simple replacing of

matrix.print(temp2 + name2);

with

matrix.print(temp2);
matrix.print("A"); 

Wouldn't work, thorugh it may still drag in all the giant print machinery from the arduino library. You may need to roll your own int to string converter (it's really quite easy to do).


As an aside:

float readTemperatureFromSensor(byte addr[])
{

    /* Snip */
    celsius = (float)raw / 16.0;
    return celsius;
}

....

void loop() {
    byte addr[] = { 0x28, 0xad, 0x3f, 0x51, 0x4, 0x0, 0x0, 0x2a };
    temp = readTemperatureFromSensor(addr);

        /* Snip */

    int temp2 = int(temp);

        /* Snip */
    }


}

The temperature is being cast to a float, divided by 16, and then cast back to an int? Why? Any additional precision you get from the operation on a float is being thrown away, and floats are slow, particularly on an 8-bit MCU.

\$\endgroup\$
1
  • \$\begingroup\$ Thanks for the added insight, I really just copied the readTemperatureFromSensor from somewhere else. I'll definitely look into making this function easier, especially as you are right: I don't need anything behind the decimal point. \$\endgroup\$
    – Christian
    May 30, 2013 at 6:27

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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