32K-Schwellenwert für Daten im AVR-Mikrocontroller-ROM

Was könnte schlimmer sein als Krücken? Nur unvollständig dokumentierte Krücken.


Bild


Hier ist ein Screenshot der neuesten offiziellen integrierten Entwicklungsumgebung für 8-Bit-AVR-Mikrocontroller, Atmel Studio 7, der Programmiersprache C. Wie Sie in der Spalte Wert sehen können, enthält die Variable my_array die Nummer 0x8089. Mit anderen Worten, das Array my_array befindet sich ab der Adresse 0x8089 im Speicher.


Gleichzeitig gibt uns die Spalte Typ etwas andere Informationen: my_array ist ein Array von 4 Elementen vom Typ int16_t im ROM (dies wird im Gegensatz zu Daten für RAM durch das Wort prog angezeigt), beginnend mit der Adresse 0x18089. Hör auf, aber immerhin 0x8089! = 0x18089. Wie lautet die tatsächliche Adresse des Arrays?


C-Sprache und Harvard-Architektur


8-Bit-AVR-Mikrocontroller, die früher von Atmel und jetzt von Microchip hergestellt wurden, sind insbesondere deshalb beliebt, weil sie die Grundlage von Arduino bilden, das auf der Harvard-Architektur basiert, dh Code und Daten befinden sich in verschiedenen Adressräumen. Die offizielle Dokumentation enthält Codebeispiele in zwei Sprachen: Assembler und C. Zuvor bot der Hersteller eine kostenlose integrierte Entwicklungsumgebung an, die nur Assembler unterstützt. Aber was ist mit denen, die in C oder sogar C ++ programmieren möchten? Es gab kostenpflichtige Lösungen, zum Beispiel IAR AVR und CodeVisionAVR. Persönlich habe ich es nie benutzt, denn als ich 2008 mit der Programmierung von AVR begann, gab es bereits kostenloses WinAVR mit der Möglichkeit, es in AVR Studio 4 zu integrieren, und es ist einfach im aktuellen Atmel Studio 7 enthalten.


Das WinAVR-Projekt basiert auf dem GNU GCC-Compiler, der für die von Neumann-Architektur entwickelt wurde und einen einzigen Adressraum für Code und Daten impliziert. Bei der Anpassung von GCC an AVR wurde die folgende Krücke angewendet: Die Adressen 0 bis 0x007fffff werden für den Code (ROM, Flash) und 0x00800100 für 0x0080ffff für Daten (RAM, SRAM) zugewiesen. Es gab alle möglichen anderen Tricks, zum Beispiel Adressen von 0x00800000 bis 0x008000ff, die Register darstellten, auf die mit denselben Opcodes wie RAM zugegriffen werden kann. Wenn Sie ein einfacher Programmierer wie ein Arduino-Anfänger und kein Hacker sind, der Assembler und C / C ++ in derselben Firmware mischt, müssen Sie dies im Prinzip nicht wissen.


Neben dem eigentlichen Compiler enthält WinAVR verschiedene Bibliotheken (Teil der Standard-C-Bibliothek und AVR-spezifische Module) in Form des AVR Libc-Projekts. Die neueste Version 2.0.0 wurde vor fast drei Jahren veröffentlicht. Die Dokumentation ist nicht nur auf der Website des Projekts selbst, sondern auch auf der Website des Herstellers des Mikrocontrollers verfügbar. Es gibt auch inoffizielle russische Übersetzungen.


Daten im Adressraum des Codes


Manchmal müssen Sie in einem Mikrocontroller nicht nur viele, sondern auch viele Daten speichern: so viele, dass sie einfach nicht in den Arbeitsspeicher passen. Darüber hinaus sind diese Daten unveränderlich, zum Zeitpunkt der Firmware bekannt. Zum Beispiel ein Rasterbild, eine Melodie oder eine Art Tisch. Gleichzeitig nimmt der Code oft nur einen kleinen Teil des verfügbaren ROM ein. Warum also nicht den verbleibenden Speicherplatz für Daten nutzen? Einfach! Die Dokumentation zu avr-libc 2.0.0 umfasst ein ganzes Kapitel mit 5 Daten im Programmbereich. Wenn Sie den Teil über die Zeilen weglassen, ist alles sehr einfach. Betrachten Sie ein Beispiel. Für RAM schreiben wir wie folgt:


unsigned char array2d[2][3] = {...}; unsigned char element = array2d[i][j]; 

Und für ROM wie dieses:


 #include <avr/pgmspace.h> const unsigned char array2d[2][3] PROGMEM = {...}; unsigned char element = pgm_read_byte(&(array2d[i][j])); 

Es ist so einfach, dass diese Technologie auch in RuNet wiederholt behandelt wurde.


Was ist das Problem?


Erinnern Sie sich an die Aussage, dass 640 KB für alle ausreichen? Erinnern Sie sich, wie Sie von 16-Bit-Architektur auf 32-Bit und von 32-Bit auf 64-Bit umgestellt haben? Wie hat Windows 98 auf mehr als 512 MB RAM instabil funktioniert, während es für 2 GB ausgelegt war? Haben Sie jemals das BIOS aktualisiert, sodass das Motherboard mit Festplatten mit mehr als 8 GB funktioniert? Erinnern Sie sich an die Jumper auf 80 GB Festplatten, die ihre Lautstärke auf 32 GB reduzieren?


Das erste Problem überkam mich, als ich versuchte, ein Array von mindestens 32 KB im ROM zu erstellen. Warum im ROM und nicht im RAM? Denn derzeit existieren 8-Bit-AVRs mit mehr als 32 KB RAM einfach nicht. Und mit mehr als 256 B - existieren. Dies ist wahrscheinlich der Grund, warum die Ersteller des Compilers 16 b (2 B) für Zeiger im RAM (und gleichzeitig für den Int-Typ) ausgewählt haben. Dies finden Sie im Abschnitt "Datentypen" in Kapitel 11.14. Welche Register werden vom C-Compiler verwendet? AVR Libc-Dokumentation. Oh, und wir wollten nicht hacken, aber hier sind die Register ... Aber zurück zum Array. Es stellte sich heraus, dass Sie kein Objekt erstellen können, das größer als 32.767 B (2 ^ (16 - 1) - 1 B) ist. Ich weiß nicht, warum es notwendig war, die Länge des Objekts signifikant zu machen, aber das ist eine Tatsache: Kein Objekt, selbst ein mehrdimensionales Array, kann eine Länge von 32.768 B oder mehr haben. Ein bisschen wie eine Einschränkung des Adressraums von 32-Bit-Anwendungen (4 GB) in einem 64-Bit-Betriebssystem, nicht wahr?


Soweit ich weiß, hat dieses Problem keine Lösung. Wenn Sie ein Objekt mit einer Länge von 32.768 im ROM platzieren möchten, teilen Sie es in kleinere Objekte auf.


Wir wenden uns wieder dem Absatz Datentypen zu: Zeiger sind 16 Bit. Wir wenden dieses Wissen auf Kapitel 5 von Daten im Programmraum an. Nein, Theorie ist unverzichtbar, Praxis ist erforderlich. Ich habe ein Testprogramm geschrieben, einen Debugger gestartet (leider Software, keine Hardware) und pgm_read_byte dass die Funktion pgm_read_byte nur Daten zurückgeben kann, deren Adressen in 16 Bit passen (64 KB; danke, nicht 15). Dann tritt ein Überlauf auf, der ältere Teil wird verworfen. Es ist logisch, wenn Zeiger 16-Bit sind. Es stellen sich jedoch zwei Fragen: Warum ist dies nicht in Kapitel 5 geschrieben (eine rhetorische Frage, aber er hat mich dazu veranlasst, diesen Artikel zu schreiben) und wie man das 64-KB-ROM-Limit überwindet, ohne zum Assembler zu wechseln.


Glücklicherweise gibt es zusätzlich zu Kapitel 5 eine weitere 25.18 pgmspace.h-Dateireferenz, aus der wir erfahren, dass die Funktionsfamilie pgm_read_* nur eine pgm_read_*_near für pgm_read_*_near , die 16-Bit-Adressen akzeptiert, und es gibt auch pgm_read_*_far , und Sie können senden 32-Bit-Adresse Eureka!


Wir schreiben den Code:


 unsigned char element = pgm_read_byte_far(&(array2d[i][j])); 

Es wird kompiliert, funktioniert aber nicht wie gewünscht (wenn sich Array2d nach 32 KB befindet). Warum? Ja, da die & -Operation eine vorzeichenbehaftete 16-Bit-Nummer zurückgibt! Es ist lustig, dass die pgm_read_*_near Familie vorzeichenlose 16-Bit-Adressen akzeptiert, pgm_read_*_near sie kann mit 64 KB Daten arbeiten, und die Operation & ist nur für 32 KB nützlich.


Lass uns weitermachen. Was haben wir in pgmspace.h außer pgm_read_* ? Die Funktion pgm_get_far_address(var) , die bereits eine halbe Seite Beschreibung enthält und die Operation & ersetzt.


Wahrscheinlich richtig:


 unsigned char element = pgm_read_byte_far(pgm_get_far_address(array2d[i][j])); 

Kompilierungsfehler. Wir lesen die Beschreibung: 'var' muss zum Zeitpunkt der Verknüpfung als vorhandenes Symbol aufgelöst werden, dh als einfacher Typvariablenname, als Arrayname (kein indiziertes Element des Arrays, wenn der Index eine Konstante ist, beschwert sich der Compiler nicht, aber kann die Adresse nicht erhalten, wenn die Optimierung aktiviert ist), einen Strukturnamen oder einen Strukturfeldnamen, eine Funktionskennung, eine vom Linker definierte Kennung, ...


Wir setzen eine weitere Krücke: Wir wechseln von Array-Indizes zur Zeigerarithmetik:


 unsigned char element = pgm_read_byte_far(pgm_get_far_address(array2d) + i*3*sizeof(unsigned char) + j*sizeof(unsigned char)); 

Jetzt funktioniert alles.


Schlussfolgerungen


Wenn Sie mit dem GCC-Compiler in C / C ++ für 8-Bit-AVR-Mikrocontroller schreiben und die Daten im ROM speichern, dann:


  • Bei einer ROM-Größe von nicht mehr als 32 KB treten keine Probleme auf, wenn Sie nur Kapitel 5 Daten im Programmbereich lesen.
  • Für ROMs, die größer als 32 KB sind, sollten Sie die Funktionsfamilie pgm_read_*_far , die Funktion pgm_get_far_address anstelle von & , die Zeigerarithmetik anstelle von Array-Indizes verwenden und die Größe eines Objekts darf 32.767 B nicht überschreiten.

Referenzen


Source: https://habr.com/ru/post/de436904/


All Articles