Hallo, für das Nachfolgeprojekt von meinem ChipBasic wollte ich die Möglichkeit schaffen, dass man auch Anwenderprogramme für den AVR in C schreiben kann. Allerdings bin ich mir nicht sicher, ob das überhaupt möglich ist. Denn dazu müsste man dem Compiler bestimmte Dinge vorgeben bzw. vorgaukeln. Das (Betriebs-) System selbst ist in ASM geschrieben und muss die Programme (mit derzeit maximal 9K Größe) an 8 verfügbare Programmplätze laden können. Ich habe zwar einige Erfahrung in C (GCC), aber halt nur auf dem PC. 1. Der Code muss relozierbar sein. Dazu habe ich bereits einen Relocator, der beim Speichern des Programmes in das Flash die Befehle CALL,JMP und LPM der entsprechenden Startadresse anpasst. Bei Tabellenzugriffen über das Z Register sehe ich dabei aber keine Chance. Es gibt zwar eine API-Funktion, die die Startadresse des Programmes ermittelt und im Z-Register zurückgibt aber ich weiss nicht ob sich die so nutzen lässt. 2. Der nutzbare RAM-Bereich muss begrenzbar sein, außerdem darf das Programm keinen eigenen Stack benutzen. Letzteres könne ich vielleicht realisieren, indem ich vom Relocator Direktzugriffe auf den Stackpointer z.B. in NOP umwandle. 3. Es gibt mind. 6 Register (im Bereich R0-R15), die nicht genutzt werden dürfen da die Video-Engine sie benötigt. 4. Man müsste zumindest eine neue STDIO schreiben, damit die Ausgaben auch auf dem Bildschirm landen. Ist so etwas überhaupt realisierbar ohne den Compiler zu modifizieren? Jörg
Joerg Wolfram schrieb: > Ist so etwas überhaupt realisierbar ohne den Compiler zu modifizieren? Nein. Ein Beispiel: Der Compiler bringt lokale Variable im Stack unter. Dazu ist es nötig, dass er in den Stackpointer schreibt. Aber auch andere Bedingungen sind nicht erfüllbar.
Hallo Joerg, so ganz will mir nicht einleuchten, was das ganze bringen soll. Wäre es nicht einfacher, die Videoausgabe mit einer C-Schnittstelle zu versehen und eine Library zu erstellen, die der Anwender zu seinem Code linken kann? Ansonsten ruft deine Anwendung nach einem RTOS-System, das u.a. einzelnen Prozessen eigene Stacks zuordnet. Das Relozieren könnte man dem gcc-Linker überlassen. Da musst du einmal schauen, welche Möglichkeiten einem Skripte ermöglichen und das Ganze in eine entsprechende toolchain integrieren. Vielleicht kann man z.B. den Linker dazu bringen, den Code für alle 8 Programmplätze zu erzeugen (und auch das RAM entsprechend aufzuteilen) und dein Loader pickt sich dann das passende Teil-Stück heraus. Um das Sichern der Register für deine Videofunktion kommst du aber nicht herum, wenn du den Compiler nicht komplett neu schreiben willst. Gruß, DetlevT
Danke erstmal für die schnelle Antwort. Den Stack kann das Programm schon benutzen, aber eben halt "blind", nur mit PUSH/POP/CALL/RET. Aber letztendlich muss es auch nicht gehen, es war nur so eine Idee... Gruß Jörg
Hallo Joerg, mal so eine Idee: Es muss ja nicht C und avr-gcc sein. Wenn du eine Art Programmier-Lern-System machen willst, kannst du mit deiner Erfahrung ja vielleicht einen Compiler für eine (einfache) Sprache selbst schreiben, der am Ende Assembler ausspuckt. Das wäre so eine Art Preprozessor, wo du die Umsetzung selbst unter Kontrolle hast. RJMP und RCALL muss man nicht relozieren und an die aktuelle Adresse kommt man z.B. via rcall und POP der Rücksprungadresse vom Stack. Oder es kommt so eine Art p-Code heraus, den dein AVR interpretiert. Dann kann das Programm auch im RAM stehen. Gruß, DetlevT
Hallo Detlev, wenn ich aus dem Code eine dazulinkbare Library machen würde, würde ich vielleicht anderen einen Gefallen tun, aber mir selbst nur Steine in den Weg legen da ich selbst das System nicht nochmal in C nachprogrammieren möchte und auch nicht vorhabe die darauf laufenden Anwendungen in C zu programmieren. Also müssen sich die Anwendungen nach dem System und seinen Schnittstellen richten und nicht umgekehrt. Es ging mir einfach darum, ob neben BASIC und ASM auch C zur Erstellung von Anwenderprogrammen möglich ist. Und das scheint halt nicht zu gehen. Gruß Jörg
Nein, ein Programmierlern-System wird es nicht, eher eine Art "AVR-PAD". Aber die Idee mit dem P-Code ist gut, vielleich kann man ja aus C-Code mit einer Art "Compiler" den Bytecode erstellen, mit dem auch die Runtime des BASIC-"Interpreters" läuft. Gruß Jörg
Joerg Wolfram schrieb: > Den Stack kann das Programm schon benutzen, aber eben halt "blind", nur > mit PUSH/POP/CALL/RET. Genügt nicht, größere Datenmengen auf dem Stack benötigen eine Stackpointermanipulation. Alles, was du willst ist doch, dass die Applikation sich den initialen Stackpointerwert nicht setzen darf, das sollte möglich sein (durch Modifizieren des Startup- Codes). Im Prinzip müsstest du deine Applikation zur dynamischen Bibliothek linken und dann einen Lader schreiben, der die verbliebenen Dinge zur Laufzeit aktualisiert. Das wird aber nicht ganz trivial sein, außerdem hat für den AVR-GCC ganz sicher noch keiner die Optionen -fpic und -fPIC tatsächlich implementiert. Du bist dir aber auch im Klaren, dass du für die Applikationen jedesmal aus dem Bootloaderbereich heraus den Flash-Inhalt beschreiben musst, ja?
Joerg Wolfram schrieb: > 1. Der Code muss relozierbar sein. Möglicherweise ist das ELF-File relozierbar, indem die nötigen Relocations Record da noch drin sind - oder es lässt sich vielleicht beim Linken so einrichten. In diesem Fall würde es reichen, einen relozierenden Loader mit dem ELF-File an Stelle des Hex/Bin-Files zu füttern.
A. K. schrieb: > oder es lässt sich vielleicht > beim Linken so einrichten. Genau das wäre der Sinn einer dynamischen Bibliothek (shared object). Aber wie schon geschrieben, der Compiler müsste dafür position independent code generieren können, und das wird für den AVR keiner wirklich bis zu Ende implementiert haben. Die Optionen -fpic und -fPIC existieren zwar, aber es wird niemand garantieren können, dass der sich ergebende Code tatsächlich zur Laufzeit relozierbar ist noch dass der Compiler nicht mit einem internal compiler error aussteigt (weil halt notwendige Teile vielleicht gar nicht implementiert sind).
Jörg Wunsch schrieb: > Aber wie schon geschrieben, der Compiler müsste dafür position > independent code generieren können Nicht unbedingt. Solcher -fPIC-Code ist ohne Modifikation verschiebbar. Es gibt aber auch EXE-Formate, die weiterhin Relokationsinformation enthalten. Damit wird es dem OS-Loader ermöglicht, den Code an jede beliebige Adresse zu laden, indem er in anhand dieser Relokationen Code und Daten anpasst. Solcher Code wird für eine präferierte Adresse gelinkt, kann aber auch mit leichtem Zusatzaufwand zum Ladezeitpunkt an jede andere geladen werden. Eine spezielle Codeerzeugung ist dabei nicht erforderlich. Ich glaube Windows arbeitet so, jedenfalls aber tat dies OS/2.
Jörg Wunsch schrieb: > Aber wie schon geschrieben, der Compiler müsste dafür position > independent code generieren können, und das wird für den AVR keiner > wirklich bis zu Ende implementiert haben. Nein, muss er für diese Anwendung nicht. Position Independent Code ist bei gängigen "grossen" Betriebssystemen nötig, damit alle inneren Referenzen in der Library (also z.B. Jumps bei Verzweigungen, aber noch vieles, vieles mehr) bereits beim Linken der Shared Library aufgelöst werden können (bzw. schon beim Assemblieren eine Konstante ergeben), und beim Laden der Lib nur noch Referenzen auf andere (externe) Shared Libraries reloziert werden müssen. Dass man das so macht hat aber nichts damit zu tun, dass es anders garnicht funktionieren würde. Natürlich kann man grundsätzlich auch alle Relokationen auf den Zeitpunkt des Ladens der Lib verschieben. Der Grund warum man stattdessen Position Independent Code verwendet (trotzdem er auf manchen Architekturen mit leichten Performanceeinbußen verbunden ist) ist, dass innere Referenzen so zahlreich und dicht im Code vorkommen, dass so gut wie keine Pages in der Lib mehr ohne Relokationen auskämen. Letzteres würde dazu führen, dass von dem "Shared" zumindest im Arbeitsspeicher nicht mehr viel übrigbleiben würde (da der Code durch die Relokation geändert wird, muss von der Page eine Kopie für den einzelnen Prozess angelegt werden), was natürlich dem ganzen Zweck von Shared Libaries zuwiderliefe. Für die oben genannten Anforderungen ist das aber alles irrelevant, und deshalb wird kein PIC benötigt. Es müssen dann eben alle Relokationen beim Laden statt beim Linken endgültig durchgeführt werden. Evtl. kann man das noch dahingehend verfeinern (damit der Loader auf dem AVR kleiner wird), dass die eigentliche Relokation (z.B. mit Basisadresse 0) bereits vom Linker durchgeführt wird, aber (durch ein selbst zu schreibendes Tool) zusätzlich noch eine Tabelle erstellt wird, in der festgehalten wird, an welchen Stellen noch die Basisadresse zu addieren ist. Dann braucht sich der Loader zumindest nicht mehr mit dem Auflösen von Symbolen rumschlagen, sondern muss nur noch die Tabelle einmal durchgehen und überall den Offset addieren. Die relative Lage der Symbole untereinander kann sich ja nicht mehr ändern, es wird nur alles en bloc im Adressraum verschoben. BTW kamen Shared Libraries im a.out-Format damals unter Linux auch ohne PIC aus. Stattdessen wurde dort die Ladeadresse der Lib bereits beim Linken der Lib endgültig festgelegt. Da der von der Lib verwendete Adressraum nicht mit anderen Libraries überlappen durfte, gab es eine zentrale Registry, über die man Adressraum für seine Library registrieren musste. Letzteres würde quasi dem Vorschlag entsprechen, für jede mögliche Position eine eigene Kopie des Codes zu linken, und dann im Loader das passende Stück herauszupicken. Andreas
Andreas Ferber schrieb: > (da der Code durch die Relokation geändert wird, muss von der Page eine > Kopie für den einzelnen Prozess angelegt werden), was natürlich dem > ganzen Zweck von Shared Libaries zuwiderliefe. Alternativ wird der Adressraum einer bestimmten DLL/SharedLib bei ersten Laden global vergeben. Dann ist Sharing problemlos, aber dieser Teil des Adressraum ist dann auch bei Prozessen blockiert, die sie garnicht verwenden.
Andreas Ferber schrieb: > BTW kamen Shared Libraries im a.out-Format damals unter Linux auch ohne > PIC aus. Ja, aber das war ein ziemlicher Horror. SunOS 4.x hat mit a.out vorgemacht, dass man das auch besser machen kann. ;-) Aber das geht jetzt hier vom Thema weg. Ich denke, dass das Ganze für einen AVR ohnehin etwas, naja, oversized ist.
Joerg Wolfram schrieb: > Hallo, > > für das Nachfolgeprojekt von meinem ChipBasic wollte ich die Möglichkeit > schaffen, dass man auch Anwenderprogramme für den AVR in C schreiben > kann. Allerdings bin ich mir nicht sicher, ob das überhaupt möglich ist. Was ist ein "Programm"? EIn fertig erzeugtes etwas? Routinen, gegeb die gelinkt wird und die innerhalb eines bestimmten Kontextes ausführbar sein sollen? ...? > Denn dazu müsste man dem Compiler bestimmte Dinge vorgeben bzw. > vorgaukeln. Das (Betriebs-) System selbst ist in ASM geschrieben und > muss die Programme (mit derzeit maximal 9K Größe) an 8 verfügbare > Programmplätze laden können. Ich habe zwar einige Erfahrung in C (GCC), > aber halt nur auf dem PC. > > 1. Der Code muss relozierbar sein. Dazu habe ich bereits einen > Relocator, der beim Speichern des Programmes in das Flash die Befehle > CALL,JMP und LPM der entsprechenden Startadresse anpasst. Das ist zu kurz gedacht, weil du so niemals alle Informationen hast, um die Ersetzungen zu machen. Bei einem LDI/SUBI/SBCI zB musst du wissen, ob er eine Zahl lädt/subtrahiert oder eine Adresse, und ob die Adresse eine RAM-, eine Flash- oder eine EEPROM-Adresse ist. Diese Informationen stehen im Objekt. Du kannst sie zB mit
1 | > foo-objdump -rR file |
anzeigen lassen. Im Endeffekt musst du die Aufgabe des Lokators übernehmen, und je nach RELOC (R_AVR_16, R_AVR_LO8_LDI, R_AVR_HI8_LDI, R_AVR_LO8_LDI_NEG, R_AVR_HI8_LDI_NEG, R_AVR_CALL, R_AVR_16_PM, ...) die richtige Ersetzung vornehmen. Weiters ist zu beachten, daß das Symbol, auf den sich der RELOC bezieht, zu unterschiedlichen Sections gehören kann, zb .text, .data, .progmem.data etc. Falls das Programm explizite Casts enthält, etwa weil es auf fest codierte EEPROM oder Flash-Adressen zugreift, ist das nicht erkennbar, weil dafür keine RELOCs erzeugt werden. > Bei > Tabellenzugriffen über das Z Register sehe ich dabei aber keine Chance. > Es gibt zwar eine API-Funktion, die die Startadresse des Programmes > ermittelt und im Z-Register zurückgibt aber ich weiss nicht ob sich die > so nutzen lässt. > 2. Der nutzbare RAM-Bereich muss begrenzbar sein, außerdem darf das > Programm keinen eigenen Stack benutzen. Letzteres könne ich vielleicht > realisieren, indem ich vom Relocator Direktzugriffe auf den Stackpointer > z.B. in NOP umwandle. Das Programm wird Stack benutzen; einfach schon deshalb, weil es Befehle wie CALL unr RET enthält. Falls es darum geht, daß keine Daten auf dem Stack angelegt werden bzw. über den Stack übergeben werden wird's schwierig. Weil du unter die stdio erwähnst: Funktionen mit variabler Argumentzahl übergeben ihre Parameter immer auf dem Stack. Zumindest im avr-gcc. Sobald die Adresse einer lokalen Variablen genommen wird, zu viele lokale Variablen gebraucht werden, deren Größe ungünstig ist (zB char[3]), solche "ungünstigen" Objekte als Funktions- oder Rückgabeparameter auftauchen oder Argumente bzw. Rückgabepparameter eine gewisse Anzahl übersteigen, wird ebenfalls Stack fällig. Stack wird auf jeden Fall gebraucht, wenn eine Funktion call-saved GPRs verwendet. Zwar lassen sich auch recht komplexe Anwendungen "ohne Stack" schreiben (d.h. ohne daß ein Framepointer gebraucht wird) schreiben, aber dazu ist einiges an Erfahrung bzw. Arbeitsweise von avr-gcc nötig. > 3. Es gibt mind. 6 Register (im Bereich R0-R15), die nicht genutzt > werden dürfen da die Video-Engine sie benötigt. Das kommt sich u.U mit dem avr-gcc ABI quer. R0 und R1 können bzw. dürfen nicht ausgeschlossen werden. Für Rn, n>=2 gilt: je kleiner die Register-Nummer, desto besser. Beim Übersetzen kann das GPR zwar mit -ffixed-n ausgeschlossen werden, allerdings ist das nicht ABI-kompatibel mit den Bibliotheken bzw. die Libs müssen mit ebendiesen Schaltern übersetzt werden. Zudem ist zu bedenken, daß, gcc um so mehr Stack verwenden wird, je mehr GPRs im entzogen werden.
Vielen Dank für die Antworten und die interessante Diskussion. Ich versuche mal, ein bisschen Resümee für mich zu ziehen: Kurz noch zum Ziel. Das Ganze soll eine recht universelle mobile Plattform werden und einige meiner nicht veröffentlichten Projekte zu einem Open Source Projekt zusammenführen. Ausgabe ist ein 320x240 LCD (mit Graustufen) aber auch VGA und TV (RGB/FBAS). Controller ist ein ATMega1284P, der auch später durch einen S12X ersetzt werden kann. Als "Programme" bezeichne ich das, was man neuerdings "Apps" nennt. Dazu gibt es 8 Programmplätze im System, die jeweils 8K oder 9K (steht noch nicht ganz fest) Flash belegen. Wenn dann jemand dich eine "App" in C programmieren will, sollte er es zumindest theoretisch können. Zuallererst, das "Stack-Problem" sollte wahrscheinlich keines darstellen, solange nicht über andere Dinge als den Stackzeiger darauf zugegriffen werden muss. Mir geht es darum, dass beim Start des Programms ja schon ein Stack existiert und der auch am Programmende noch so existieren muss. Für die Reloziererei ist es wahrscheinlich der einfachste Weg, das Programm genau für den Adressbereich zu kompilieren in dem es später laufen soll. Im Programmkopf der bei der HEX->BIN Konvertierung noch dazugefügt wird steht dann in einem Byte, an welche der 8 Programmplätze das Programm geladen werden kann. Bei der Register-Thematik werde isch schauen, dass ich die reservierten Register (R2,R3 und R8-R11) zusammenlege. Gruß Jörg
Da meine ursprüngliche Idee wohl nicht funktioniert, habe ich mir noch ein anderes Konzept überlegt. Da ich ja einen Mega1284P verwende, habe jetzt ich Dinge wie z.B. den Bildspeicher an das Ende des RAM gerückt und nutze die Vektortabelle im Bootloaderbereich. - Das Programm kann an Adresse 0 beginnen - Das Programm darf R2 - R5 nicht verwenden - Port A und C dürfen nicht verwendet werden - Speicherbereich wie ATMega 328, bei Bedarf auch 4K RAM - Interrupts können nicht genutzt und dürfen nicht abgeschalten werden - Das Programm kann einen eigenen Stack nutzen, die ISR braucht aber 10 Bytes auf dem Stack Der Zugriff auf das System müsste dann über entsprechende Bibliotheken erfolgen, die im Wesentlichen nur aus CALL-Aufrufen in den oberen Bereich bestehen. Auf die gleiche Weise kann ich dann auch den Rücksprung ins System realisieren. Da ich mir nicht mehr sicher bin, ob ich das Ganze als Open Source veröffentlichen will, sollte es auch möglich sein, das System als "Black Box" mit definierter Schnittstelle zu betrachten. Jörg
So, es gibt einen neuen "Zwischenstand". Ich werde das Ganze so halten, dass man die Einstellungen für den Mega644 verwenden kann: - Die unteren 64K Flash sind frei, ebenso die unteren 2K EEPROM - Die ersten 4K RAM sind frei - Keyboard habe ich jetzt auf USART1 gelegt, USART0 ist frei Es sind aber noch ein paar Fragen offen 1. macht der Assembler es mit, dass Unterprogrammaufrufe in den für den Mega644 nicht vorhandenenen Flash-Bereich erfolgen? 2. kann ich mit static FILE mystdout ... die Ausgabe einfach auf den Bildschirm umlenken? 3. gibt es eine ausführliche Doku, wie beim GCC die Parameterübergabe auf Register-/Stackebene erfolgt? 4. Besteht überhaupt Interesse an einem derartigen Projekt? Jörg
Joerg Wolfram schrieb: > 1. macht der Assembler es mit, dass Unterprogrammaufrufe in den für den > Mega644 nicht vorhandenenen Flash-Bereich erfolgen? Sollte dem Assembler egal sein. Ist halt nur die Frage, wie du ihm die Aufrufe dahin vermittelst. > 2. kann ich mit static FILE mystdout ... die Ausgabe einfach auf den > Bildschirm umlenken? Ich kenne keinen AVR mit eingebautem Bildschirm. ;-) stdio existiert, aber du musst dir schon die Doku dazu mal durchlesen, und dann musst du dich drum kümmern, eine Backend- Funktion zu schreiben, die die Zeichen aus dem AVR irgendwie zu deinem Bildschirm bekommt. Es existiert ein Beispielprogramm, das für UART und HD44780-LCD zeigt, wie's geht. > 3. gibt es eine ausführliche Doku, wie beim GCC die Parameterübergabe > auf Register-/Stackebene erfolgt? Ja, in der avr-libc-FAQ. > 4. Besteht überhaupt Interesse an einem derartigen Projekt? Keine Ahnung. Auch Jörg. ;-)
Hallo Jörg, danke für die Info. Wichtig ist erstmal, dass es prinzipiell gehen sollte. Die Aufrufe müssten dann wohl als CALL in Inline-Assembler erfolgen, ebenso ließe sich dort auch die Parameter- und Resultatübergabe unterbringen, also eine Art "Wrapper" um die eigentlichen ASM-API Funktionen. Ob das jemand jemals realisiert, ist noch eine ganz andere Frage da ich selbst nur in ASM programmiere. Jörg (TO)
Joerg Wolfram schrieb: > Die Aufrufe müssten dann wohl als CALL in Inline-Assembler > erfolgen, Nein, C kennt Funktionszeiger.
Gut, dann müsste halt der Wrapper im Assemblerbereich stehen. Da die "Schnittstellen" des compilierten C-Codes und die meiner ASM-Funktionen nicht miteinander kompatibel sind, wird so etwas auf jeden Fall notwendig sein. Um zum Beispiel einen Punkt zu setzen (PLOT), liegen bei mir die X-Koordinate im X-Register und die Y-Koordinate im Y Register und die Farbe in R20. Diese ganzen "internen Konventionen" will und muss ich auch weiter beibehalten, damit meine ASM-Module miteinander weitestgehend kompatibel bleiben. Jörg
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.