Short String overflows.text area en ATtiny85, Arduino IDE

Estoy usando Arduino IDE con arduino-tiny( https://code.google.com/p/arduino-tiny/ ) en un ATTIny85 . Mi código está maximizando la RAM, o eso parece:

Agregar un solo Stringa mi código, incluso solo con un carácter, genera un error de compilación:

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

La línea que agrego para llegar a esto es un simple

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

Solo para comparar: el tamaño del código del .hexarchivo sin la cadena es de 7.430 bytes, con la cadena definida pero no utilizada es de 8092 bytes, con la definición y el uso se desborda. Esto parece ser demasiado, especialmente porque no parece importar si mi Cadena es Ao ABCDEFG: siempre obtengo el desbordamiento de 452 bytes.

¿Alguna idea de como solucionar esto? Intenté poner String en PROGMEM, pero matrix.printno funciona con ningún método de recuperación que haya probado (además de copiar en RAM, pero luego, por supuesto, obtengo el desbordamiento nuevamente). También intenté eliminar la biblioteca Adafruit GFX, pero parece que solo las partes necesarias se incluyen en la compilación de todos modos (ya que no hubo cambios en el .hextamaño del archivo).

El código completo, solo para darle una idea de lo que estoy haciendo (acceder a un Adafruit 8x8 LED matrix, leer un valor de temperatura de un DS18S20termómetro digital de 1 cable, mostrar un emoticón que se desvanece de entrada y salida, la temperatura y el nombre de mi hijo para esa matriz LED ;), está aquí:

#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;
}
Pura especulación porque nunca he usado esas bibliotecas, pero me pregunto si el uso de matrix.print hace que se vincule una tabla de fuentes relativamente grande. Suena como el tamaño adecuado para una tabla de fuentes de 96 x 5 x 8 bits. Tal vez podría eliminar algunos caracteres que no está usando.
Sí, pero más adelante el texto del mensaje variará, por lo que podría intentarlo como último recurso. Aunque gracias por la idea ;)

Respuestas (3)

"texto" es en realidad el tamaño del flash del código, no SRAM.

Hay varios problemas aquí:

1) La función "imprimir" extrae una gran cantidad de código para formatear cadenas de formato. Esto es tonto. El '85 solo tiene 8 kB de flash total, en comparación con los 32 kB del Arduino Uno.

2) La clase String usa tanto una cierta cantidad de espacio de código como una gran cantidad de SRAM. El '85 solo tiene 512 bytes de SRAM, para todas sus variables, constantes de cadena que no son de programa y pila. La clase String puede incluso usar asignaciones de montón (malloc()/nw). La clase String es una abominación desde el punto de vista de la programación integrada. ¡No lo uses!

3) Para un uso adecuado de los recursos incrustados, desea que los literales de cadena vivan en PROGMEM. Desea un solo búfer en la RAM en el que copie las cadenas en las que realmente necesita operar, cuando necesite operar en ellas. Busque strcpy_P() y amigos, como un conjunto de funciones mucho más adecuado.

4) No desea usar malloc() o new, nunca, en sistemas integrados como estos, por dos razones. La primera razón es que el montón hará un uso ineficiente de la RAM para controlar la sobrecarga. La segunda es que no puede probar que su programa será realmente correcto tan fácilmente. Más RAM no proviene de ninguna parte, por lo que debería poder contabilizar toda la RAM que realmente necesita. La forma de hacerlo es con asignaciones estáticas o globales. Tenga en cuenta que esto es contrario a los consejos de estructura de código que usaría en un sistema grande que se ejecuta en una CPU grande: las reglas son diferentes, porque la CPU/sistema es diferente.

Finalmente, si lo necesita, puede usar la ubicación nueva para inicializar instancias de objetos; es la versión de asignación de new la que es mala.

Gracias Jon, esa es una información bastante extraña para digerir (para mí lo es). Estoy especialmente agradecido por ser directo en términos de qué no usar en estas pequeñas maravillas ;)

De antemano, creo que su problema es que el método de impresión coloca las cadenas de texto constante en la RAM. Consulte mi respuesta al límite de memoria flash de arduino , donde la macro F () compila el código para usar la cadena const directamente desde Flash y no RAM.

Entonces intenta

matrix.print(temp2);
matrix.print(F("Hello World"));
PROGMEM const char name2[] = "A"; // replaces: String name2 = "A";
matrix.print(name2);
Gran idea, pero haciendo solo la segunda línea me sale only initialized variables can be placed into program memory areay error: initializer fails to determine size of '__c'... Err?
Me perdí que name2 sea un tipo de String, la función F() no sabrá qué hacer con el objeto String, cámbielo a una matriz de caracteres ei char name2[2];
@mpflaga: en mis pruebas, "Serial.print(F(name2));" me da el mismo error que Christian informó. Espero que no le moleste que edite su código para usar F() con cadenas literales y PROGMEM con variables de "cadena C", de esa manera mi código de prueba al menos compila y se ejecuta.

Básicamente, la respuesta es escribir su programa en C y no usar mallocninguna otra asignación de memoria dinámica.

Si bien entiendo por qué la gente de arduino quiere facilitar la programación, pero como ha dicho @JohnWatte, la Stringclase en un microcontrolador integrado nunca ha sido más que una idea terrible. Si realmente tiene que tener manipulación de cadenas, hágalo como lo haría en charlas matrices CEg y todas las antiguas rutinas de manipulación de cadenas C ( strstr, strcat, strcpy, etc...).

Asignar estáticamente TODO . Esto no es un problema en este caso, pero es bueno recordarlo.

No sé qué matrixestá haciendo la biblioteca, pero me sorprendería si un simple reemplazo de

matrix.print(temp2 + name2);

con

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

No funcionaría, aunque todavía puede arrastrar toda la maquinaria de impresión gigante de la biblioteca de arduino. Es posible que deba crear su propio convertidor de int a cadena (realmente es bastante fácil de hacer).


Como un aparte:

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 */
    }


}

La temperatura se convierte en float, se divide por 16 y luego se vuelve a convertir en int? ¿Por qué? Cualquier precisión adicional que obtenga de la operación en un flotador se descarta, y los flotadores son lentos , particularmente en una MCU de 8 bits.

Gracias por la información adicional, realmente solo copié el readTemperatureFromSensorde otro lugar. Definitivamente buscaré hacer esta función más fácil, especialmente porque tienes razón: no necesito nada detrás del punto decimal.