Ich habe einige Schwierigkeiten, die Speicherverwaltung zu verstehen.
In der Arduino-Dokumentation heißt es, dass es möglich ist, Konstanten wie Strings oder was auch immer ich zur Laufzeit nicht ändern möchte, im Programmspeicher zu behalten. Ich halte es für irgendwo im Codesegment eingebettet, was in einer von-Neumann-Architektur durchaus möglich sein muss. Ich möchte das nutzen, um mein UI-Menü auf einem LCD zu ermöglichen.
Aber ich bin verwirrt über diese Anweisungen, nur Daten aus dem Programmspeicher zu lesen und zu drucken:
strcpy_P(buffer, (char*)pgm_read_word(&(string_table[i]))); // Necessary casts and dereferencing, just copy.
Serial.println( buffer );
Warum um alles in der Welt muss ich den verdammten Inhalt in den Arbeitsspeicher kopieren, bevor ich darauf zugreifen kann? Und wenn dies wahr ist, was passiert dann mit dem gesamten Code? Wird es vor der Ausführung auch in den RAM geladen? Wie wird der Code (32kiB) dann mit nur 2kiB RAM behandelt? Wo tragen diese kleinen Kobolde Disketten?
Und noch interessanter: Was passiert mit wörtlichen Konstanten wie in diesem Ausdruck:
a = 5*(10+7)
Werden 5, 10 und 7 wirklich in den Arbeitsspeicher kopiert, bevor sie in Register geladen werden? Das kann ich einfach nicht glauben.
quelle
string_table
Array liest . Dieses Array könnte 20 KB groß sein und würde niemals in den Speicher passen (auch nicht vorübergehend). Mit der obigen Methode können Sie jedoch nur einen Index laden.Antworten:
AVR ist eine modifizierte Harvard-Architekturfamilie , sodass Code nur in Flash gespeichert wird, während Daten bei der Bearbeitung hauptsächlich im RAM vorhanden sind.
Lassen Sie uns in diesem Sinne Ihre Fragen beantworten.
Sie müssen dies nicht per se tun, aber standardmäßig setzt der Code voraus, dass sich die Daten im RAM befinden, es sei denn, der Code wird so geändert, dass er speziell in Flash danach sucht (z. B. mit
strcpy_P()
).Nee. Harvard-Architektur. Weitere Informationen finden Sie auf der Wikipedia-Seite.
Die vom Compiler generierte Präambel kopiert die Daten, die geändert werden sollen, in SRAM, bevor das eigentliche Programm ausgeführt wird.
Keine Ahnung. Aber wenn Sie sie zufällig sehen, kann ich nichts tun, um zu helfen.
Nein, nein. Der Compiler wertet den Ausdruck zur Kompilierungszeit aus. Was auch immer sonst passiert, hängt von den Codezeilen ab.
quelle
const uint8_t test1[5]= { 0x54, 0x65, 0x73, 0x74, 0x31 }; const uint8_t bla[9]= { 0x62, 0x6c, 0x61, 0x62, 0x6c, 0x61, 0x62, 0x6c, 0x62 }; const uint8_t Menu[4]= { 0x3d, 0x65, 0x6e, 0x75};
Wie bringe ich diese Daten zum Flashen und später in die Funktion SPI.transfer (), die pro Aufruf einen uint8_t benötigt?So wird
Print::print
aus dem Programmspeicher in der Arduino-Bibliothek gedruckt:__FlashStringHelper*
ist eine leere Klasse, die überladene Funktionen wie print ermöglicht, um einen Zeiger auf den Programmspeicher von einem auf den normalen Speicher zu unterscheiden, da beideconst char*
vom Compiler gesehen werden (siehe /programming/16597437/arduino-f- was-macht-es-tatsächlich-tun )Sie können also die
print
Funktion für Ihr LCD-Display überladen , sodass es ein__FlashStringHelper*
Argument aufnimmt , es aufruftLCD::print
und dannlcd.print(F("this is a string in progmem"));' to call it.
F () verwendet. `Ist ein Makro, das sicherstellt, dass sich die Zeichenfolge im Programmspeicher befindet.Um die Zeichenfolge vorab zu definieren (um mit dem integrierten Arduino-Druck kompatibel zu sein), habe ich Folgendes verwendet:
Ich denke, eine Alternative wäre so etwas wie
das würde die
__FlashStringHelper
Besetzung vermeiden .quelle
Alle Konstanten befinden sich zunächst im Programmspeicher. Wo sonst wären sie, wenn der Strom ausgeschaltet ist?
Es ist eigentlich Harvard-Architektur .
Das tust du nicht. Tatsächlich gibt es einen Hardware-Befehl (LPM - Load Program Memory), der Daten direkt aus dem Programmspeicher in ein Register verschiebt.
Ich habe ein Beispiel für diese Technik in Arduino Uno Ausgabe auf VGA-Monitor . In diesem Code ist eine Bitmap-Schriftart im Programmspeicher gespeichert. Es wird im laufenden Betrieb gelesen und wie folgt in die Ausgabe kopiert:
Eine Demontage dieser Leitungen zeigt (teilweise):
Sie können sehen, dass ein Byte Programmspeicher in R30 kopiert und dann sofort im USART-Register UDR0 gespeichert wurde. Kein RAM beteiligt.
Es gibt jedoch eine Komplexität. Für normale Zeichenfolgen erwartet der Compiler, dass Daten im RAM und nicht in PROGMEM gefunden werden. Sie sind unterschiedliche Adressräume, und daher unterscheidet sich 0x200 im RAM von 0x200 im PROGMEM. Daher macht sich der Compiler die Mühe, Konstanten (wie Zeichenfolgen) beim Programmstart in den Arbeitsspeicher zu kopieren, sodass er sich später keine Gedanken mehr über den Unterschied machen muss.
Gute Frage. Sie werden nicht mit mehr als 2 KB konstanten Zeichenfolgen davonkommen, da nicht genügend Platz zum Kopieren vorhanden ist.
Aus diesem Grund unternehmen Leute, die Dinge wie Menüs und andere wortreiche Dinge schreiben, zusätzliche Schritte, um den Zeichenfolgen das PROGMEM-Attribut zu geben, das das Kopieren in den RAM verhindert.
Wenn Sie das PROGMEM-Attribut hinzufügen, müssen Sie Schritte ausführen, um den Compiler darüber zu informieren, dass sich diese Zeichenfolgen in einem anderen Adressraum befinden. Das Erstellen einer vollständigen (temporären) Kopie ist eine Möglichkeit. Oder drucken Sie einfach byteweise direkt aus PROGMEM. Ein Beispiel dafür ist:
Wenn Sie dieser Funktion einen Zeiger auf eine Zeichenfolge in PROGMEM übergeben, führt sie das "spezielle Lesen" (pgm_read_byte) durch, um die Daten aus PROGMEM und nicht aus dem RAM abzurufen, und druckt sie aus. Beachten Sie, dass dies einen zusätzlichen Taktzyklus pro Byte erfordert.
Nein, weil sie nicht sein müssen. Das würde sich zu einer Anweisung "Literal in Register laden" kompilieren. Diese Anweisung befindet sich bereits in PROGMEM, daher wird das Literal jetzt behandelt. Sie müssen es nicht in den Arbeitsspeicher kopieren und dann zurücklesen.
Ich habe eine ausführliche Beschreibung dieser Dinge auf der Seite Konstante Daten in den Programmspeicher (PROGMEM) einfügen . Das hat Beispielcode zum relativ einfachen Einrichten von Strings und Arrays von Strings.
Außerdem wird das Makro F () erwähnt, mit dem Sie einfach aus PROGMEM drucken können:
Ein bisschen Präprozessor-Komplexität ermöglicht das Kompilieren in eine Hilfsfunktion, die die Bytes in der Zeichenfolge byteweise aus PROGMEM zieht. Es ist keine Zwischenverwendung von RAM erforderlich.
Es ist einfach genug, diese Technik für andere Dinge als seriell (z. B. Ihr LCD) zu verwenden, indem Sie den LCD-Druck aus der Druckklasse ableiten.
Als Beispiel habe ich in einer der LCD-Bibliotheken, die ich geschrieben habe, genau das getan:
Der entscheidende Punkt hierbei ist, vom Drucken abzuleiten und die "Schreib" -Funktion zu überschreiben. Jetzt macht Ihre überschriebene Funktion alles, was sie zur Ausgabe eines Zeichens benötigt. Da es von Print abgeleitet ist, können Sie jetzt das Makro F () verwenden. z.B.
quelle