Electronic – arduino – Short String overflows.text area on ATtiny85, Arduino IDE

arduinoattinyavr-gcclinker

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;
}

Best Answer

"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.