Short String overflows.text-Bereich auf ATtiny85, Arduino IDE

Ich verwende die Arduino IDE mit arduino-tiny( https://code.google.com/p/arduino-tiny/ ) auf einem ATTIny85 . Mein Code maximiert den RAM, oder so scheint es:

StringWenn ich meinem Code eine Single hinzufüge , selbst wenn ich nur ein Zeichen trage, wird ein Compiler-Fehler ausgegeben:

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

Die Zeile, die ich hinzufüge, um dorthin zu gelangen, ist einfach

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

Nur zum Vergleich: Die Codegröße der .hexDatei ohne den String beträgt 7.430 Bytes, mit dem definierten, aber nicht verwendeten String sind es 8092 Bytes, mit definiertem und verwendetem String ist es überlaufend. Das scheint ein bisschen viel zu sein, zumal es anscheinend keine Rolle spielt, ob mein String ist Aoder ABCDEFG- ich bekomme immer den Überlauf von 452 Bytes.

Irgendeine Idee, wie man das umgehen kann? Ich habe versucht, den String in einzufügen PROGMEM, aber das matrix.printfunktioniert nicht mit allen Methoden zum Abrufen, die ich ausprobiert habe (außer dem Kopieren in den RAM, aber dann bekomme ich natürlich wieder den Überlauf). .hexIch habe auch versucht, die Adafruit GFX-Bibliothek zu entfernen, aber es scheint, als ob ohnehin nur die benötigten Teile in die Kompilierung gezogen würden (da es keine Änderung der -Dateigröße gab ).

Der vollständige Code, nur um Ihnen eine Vorstellung davon zu geben, was ich tue (Zugriff auf eine Adafruit 8x8 LED matrix, Lesen eines Temperaturwerts von einem DS18S201-Wire-Digitalthermometer, Ausgeben eines ein- und ausblendenden Smileys, der Temperatur und des Namens meines Kindes an diese LED-Matrix ;), ist hier:

#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;
}
Reine Spekulation, weil ich diese Bibliotheken noch nie verwendet habe, aber ich frage mich, ob die Verwendung von matrix.print dazu führt, dass eine relativ große Schriftartentabelle verknüpft wird. Es klingt nach ungefähr der richtigen Größe für eine 96 x 5 x 8-Bit-Schrifttabelle. Vielleicht könnten Sie einige Zeichen weglassen, die Sie nicht verwenden.
Das tut es, aber später wird der Nachrichtentext variieren, also könnte ich es nur als allerletzten Ausweg versuchen. Danke aber für die Idee ;)

Antworten (3)

"Text" ist eigentlich die Code-Flash-Größe, nicht SRAM.

Hier gibt es mehrere Probleme:

1) Die "print"-Funktion zieht eine große Menge an Code zum Formatieren von Formatzeichenfolgen ein. Das ist dumm. Der 85er hat insgesamt nur 8 kB Flash, verglichen mit den 32 kB des Arduino Uno.

2) Die String-Klasse verwendet sowohl etwas Coderaum als auch viel SRAM. Der 85er hat nur 512 Byte SRAM für alle Ihre Variablen, Nicht-Programm-String-Konstanten und Stack. Die String-Klasse kann sogar Heap-Zuweisungen verwenden (malloc()/nw). Die String-Klasse ist aus Sicht der eingebetteten Programmierung ein Greuel. Verwenden Sie es nicht!

3) Für eine ordnungsgemäße Nutzung eingebetteter Ressourcen möchten Sie, dass Zeichenfolgenliterale in PROGMEM vorhanden sind. Sie möchten einen einzelnen Puffer im RAM, in den Sie die Zeichenfolgen kopieren, die Sie tatsächlich bearbeiten müssen, wenn Sie sie bearbeiten müssen. Suchen Sie nach strcpy_P() und Co. als viel besser geeigneten Satz von Funktionen.

4) Sie wollen malloc() oder new niemals in eingebetteten Systemen wie diesen verwenden, und zwar aus zwei Gründen. Der erste Grund ist, dass der Heap RAM für den Steuerungsaufwand ineffizient nutzt. Zweitens können Sie nicht so einfach beweisen, dass Ihr Programm tatsächlich korrekt sein wird. Mehr RAM kommt nirgendwo her, also sollten Sie in der Lage sein, den gesamten RAM zu berücksichtigen, den Sie tatsächlich benötigen. Der Weg dazu ist mit statischen oder globalen Allokationen. Beachten Sie, dass dies den Ratschlägen zur Codestruktur widerspricht, die Sie auf einem großen System verwenden würden, das auf einer großen CPU läuft - die Regeln sind anders, weil die CPU/das System anders ist.

Schließlich können Sie bei Bedarf die Platzierung new verwenden, um Objektinstanzen zu initialisieren; es ist die zuordnende Version von new, die schlecht ist.

Danke Jon, das ist ziemlich viel Information zu verdauen (für mich ist es das). Ich bin besonders dankbar dafür, dass ich direkt bin, was ich bei diesen kleinen Wunderwerken nicht verwenden soll ;)

Ich glaube, Ihr Problem ist, dass die Druckmethode die konstanten Textzeichenfolgen in den Arbeitsspeicher legt. Bitte sehen Sie sich meine Antwort auf arduino flash memory limit an , wo das F()-Makro den Code kompiliert, um die const-Zeichenfolge direkt aus dem Flash und nicht aus dem RAM zu verwenden.

Dann versuche es

matrix.print(temp2);
matrix.print(F("Hello World"));
PROGMEM const char name2[] = "A"; // replaces: String name2 = "A";
matrix.print(name2);
Tolle Idee, aber wenn ich nur die zweite Zeile mache, bekomme ich only initialized variables can be placed into program memory areaund error: initializer fails to determine size of '__c'... Ähm?
Ich habe verpasst, dass name2 eine Art String ist, die F()-Funktion weiß nicht, was sie mit dem String-Objekt machen soll, ändere es in ein char-Array ei char name2[2];
@mpflaga: In meinen Tests "Serial.print(F(name2));" gibt mir den gleichen Fehler, den Christian gemeldet hat. Ich hoffe, Sie haben nichts dagegen, dass ich Ihren Code so bearbeite, dass er F() mit wörtlichen Zeichenfolgen und PROGMEM mit "C-String"-Variablen verwendet - auf diese Weise wird zumindest mein Testcode kompiliert und ausgeführt.

Grundsätzlich lautet die Antwort, Ihr Programm in C zu schreiben und keine mallocandere dynamische Speicherzuweisung zu verwenden.

Ich verstehe zwar, warum die Arduino-Leute das Programmieren vereinfachen wollen, aber wie @JohnWatte sagte, Stringwar die Klasse auf einem eingebetteten Mikrocontroller nie etwas anderes als eine schreckliche Idee. Wenn Sie wirklich eine String-Manipulation haben müssen, tun Sie es wie in CEg- charArrays und all den alten C-Routinen zur String-Manipulation ( strstr, strcat, strcpy, etc ...).

ALLES statisch zuweisen . Dies ist in diesem Fall kein Problem, aber es ist gut, sich daran zu erinnern.

Ich weiß nicht, was die matrixBibliothek tut, aber ich wäre überrascht, wenn ein einfaches Ersetzen von

matrix.print(temp2 + name2);

mit

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

Würde nicht funktionieren, obwohl es möglicherweise immer noch alle riesigen Druckmaschinen aus der Arduino-Bibliothek einzieht. Möglicherweise müssen Sie Ihren eigenen int-to-string-Konverter rollen (es ist wirklich ganz einfach).


Nebenbei:

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


}

Die Temperatur wird auf a geworfen float, durch 16 geteilt und dann auf ein int? Warum? Jede zusätzliche Genauigkeit, die Sie durch die Operation auf einem Float erhalten, wird weggeworfen, und Floats sind langsam , insbesondere auf einer 8-Bit-MCU.

Danke für den zusätzlichen Einblick, ich habe das wirklich nur readTemperatureFromSensorvon woanders kopiert. Ich werde auf jeden Fall versuchen, diese Funktion zu vereinfachen, zumal Sie Recht haben: Ich brauche nichts hinter dem Komma.