Weg von Keil, hin zu SDCC
Migrationshinweise.
In englisch: Von Infineon, von 2007.
Im allgemeinen geht es um das Problem,
dass der Keil-Linker BL51.exe bzw. XL51.exe
ein Kode-Limit von 2 KiByte
(im Bundle mit Cypress 4 KiByte) hat.
Sicherlich findet man irgendwo einen Crack,
aber damit verbaut man sich die Möglichkeit, Kode zu veröffentlichen.
Als Ersatz muss SDCC herhalten.
Leider optimiert SDCC noch einiges schlechter als Keil-C,
sodass man im Projekt mehr Speicher einplanen muss.
Von Vorteil ist bei SDCC der zuverlässigere Compiler,
der IMHO besser Warnungen und Fehler findet,
und nicht bei Konstrukten wie if (0<i)
abstürzt.
Im folgenden geht es darum, einen vorhandenen umfangreichen Quelltext
von Keil-C auf SDCC umzusetzen und dabei die Übersetzbarkeit
mit beiden Compilern aufrechtzuerhalten.
Beide Compiler sind kode-bank-fähig; 8051-Programme mit mehr als
64 KiByte Umfang lassen sich damit erstellen.
Schlüsselwörter
Beide Compiler unterstützen „benannte“ Speicherblöcke und -zeiger,
die schließlich von verschiedenen Assemblerbefehlen angesprochen werden.
Man behalte stets im Hinterkopf, dass es beim 8051 3× die
Adresse 0 gibt.
Für den Linker sogar 4×, die Bitadressen.
Schlüsselwörter
Keil-C | SDCC | Erklärung | Kompatibilitätsheader |
code__codeROM- bzw. Flash-Speicher.
Für Programmkode und Konstanten: (Tabellen, USB-Deskriptoren und Strings).
Wird ausschließlich indirekt mit movc a,@a+dptr angesprochen.
Von Vorteil ist die leichtere Adressierung von Strukturkomponenten mit a,
wird aber (zumindest) von SDCC verschmäht.
Objekte und Zeiger sind automatisch const .
#define code __code
data__data
Je nach Speichermodell
wie idata (256 Byte indirekt adressierbarer RAM)
oder xdata (64 KiByte „externer“ RAM).
Für Variablen aller Art.
Beim SDCC ist __data nicht ganz dasselbe
wie __idata !
Sondern (beim Speichermodell small) nur die direkt adressierbaren
Variablen von 0x30 bis 0x7F, das sind 80 Bytes.
Achtung! Zeiger ohne Typspezifikation sind generische Zeiger,
wobei ein drittes Byte den Speicherbereich kodiert!
Die Verwendung solcher Zeiger bläht den Kode auf!
Datenstrukturen ohne Typspezifikation landen in data .
Man sollte immer eine geeignete Typspezifikation verwenden.
| #define data __data
idata__idata
Der indirekt adressierbare Speicher von 0x80 bis 0xFF, 128 Byte.
Der Zugriff erfolgt ausschließlich indirekt mov r/m,@r[0:1]; mov @r[0:1],r/m .
Wer echten idata hat, platziert den Stapelzeiger am besten
da hinein, hinter allen anderen idata -Variablen.
Denn der Stapel wächst beim 8051 „nach oben“.
Belässt man den Stapelzeiger bei seinem Reset-Wert 0x07,
überschreibt der Stapel die wertvollen, direkt und indirekt adressierbaren
Bank-Register ab Adresse 0x08.
| #define idata __idata
xdata__xdata
Der „externe“ RAM, häufig eingebaut, bietet bis zu 64 KiByte Daten.
Früher war er wie der Programmspeicher über den Busanschluss angebunden.
Kann nur mit movx @dptr,a
bzw. movx a,@dptr angesprochen werden.
Bei einigen Controllern ist der Datenspeicher „von-neumannisiert“,
und Kode und Daten können/müssen sich den RAM teilen.
Dann und nur dann ist ein Typecast zwischen xdata
und code folgenlos.
Und der Lesezugriff auf Strukturkomponenten ist mit
movc a,@a+dptr günstiger.
Beim Ablegen von (konstanten) Objekten ist jedoch ein großer
Unterschied anzumerken: xdata -Objekte
müssen vom Startupkode vom Flash (meistens hinter dem Kode) in den XRAM
geschaufelt werden oder können gar nicht vorinitialisiert werden.
| #define xdata __xdata
pdata__pdata
Eine Seite von 256 Byte im „externen“ RAM, Vorgabe 0..0xFF.
Wird etwas günstiger mit movx @r[1:0],a
bzw. movx a,@r[1:0] angesprochen.
Damit stehen 2 Datenzeiger zur Verfügung, die 8 Bit umfassen.
Früher wurde der externe RAM durch Laden von PortC page-adressiert,
heutzutage tut das ein SFR, beim EZUSB MPAGE.
Da beim FX2 viele Register im xdata -Bereich ab 0xE600
liegen, ist es zweckmäßig, diese Register als pdata zu deklarieren.
Nicht vergessen, in main()
am Anfang MPAGE = 0xE6; hinzuschreiben!
pdata -Zeiger können in xdata -Zeiger
überführt werden, indem MPAGE als High-Teil hinzugefügt wird.
| #define pdata __pdata
bdatakeine Entsprechung
| Bit-adressierbare Daten im Bereich 0x20 - 0x2F (16 Byte).
Diese sind direkt, indirekt und bitweise-direkt ansprechbar
und nach den Registern der wertvollste Datenbereich.
Beim SDCC muss man sich mit __at() -Zuweisungen behelfen.
Dass dieser Speicherbereich nicht nebenher von automatischen Zuweisungen
überschrieben wird, darum muss man sich mit Linkerschaltern kümmern:
-Wl -bBSEG=0x21.
| Muss aufgelöst werden:
__data unsigned char __at(0x20) bits;
__bit __at(0x00) bit0;
/* usw. */
__bit __at(0x07) bit7;
| bit__bit
Losgelöste bit-adressierbare Daten im Bereich 0x20 - 0x2F (16 Byte = 128 Bit)
Wird vom Compiler / Linker auf die (noch) freien Bits verteilt.
Als Funktionsargumente und Rückgabetypen ???
Als lokale Variablen werden diese, wenn nicht rekursiv,
wie static behandelt.
| #define bit __bit
sfr__at()
(Nur) Direkt adressierbare Special-Function-Register im Bereich 0x80..0xFF.
Können nur gemeinsam mit = bzw. __at()
verwendet werden. Defaultwertzuweisung nicht möglich.
| #ifdef SDCC
# define SFRBYTE(name,addr) \
volatile data BYTE __at(addr) name
#else
# define SFRBYTE(name,addr) \
sfr name = addr
#endif
| sbit__sbit __at()
Bits im Bereich der Special-Function-Register definieren.
Können nur gemeinsam mit = bzw. __at()
verwendet werden.
Defaultwertzuweisung nicht möglich.
| #ifdef SDCC
# define SFRBIT(name,addr) \
__sbit __at(addr) name
#else
# define SFRBIT(name,addr) \
sbit name = addr
#endif
| | | | | | | | | | | | | | | | | | | | | | | | |
Automatische Variablen
Keiner der 8051-Compiler legt die automatischen Variablen standardmäßig
auf den Stack! Das heißt, sie sind effektiv static
und die Funktionen sind nicht reentrant.
Während Keil-C eine Liste hält, welche Funktionen sich wie gegenseitig aufrufen
und bei entsprechender Eignung automatische Variablen „übereinander“ legt,
tut der SDCC alle nebeneinander, sodass der Platz bei __data
sehr schnell knapp wird.
Beim SDCC wird nur der erste Parameter in (DPH:)DPL übergeben,
alles andere über solche static
-Speicherplätze.
Man tut also gut, die Länge der Parameterliste klein zu halten
und weitere Parameter über globale Variablen, wie beim Uralt-BASIC,
zu übergeben.
Ein guter Kandidat dafür ist (DPH2:)DPL2 (Zweiter Datapointer)
der 8052-Architekturen sowie die beiden Autopointer beim CY7C68013A.
Bei umfangreichen Datenstrukturen innerhalb der Funktion
legt man diese mittels idata
oder xdata
„zu Fuß“ in entsprechende Speicherbereiche.
Inline-Assembler
Inline-Assembler (?)
Keil-C | SDCC |
#pragma ASM
djnz r7,$
#pragma ENDASM
| __asm
djnz r7,$
__endasm
|
Externer Assembler
Die Art der Parameterübergabe unterscheidet sich stark zwischen
Keil-C und SDCC. Leider.
Übergabe an nicht-rekursive Funktionen
Parameter | Keil-C | SDCC | Erklärung |
1. | 8,16bit: R6:R7 32bit: R4:R5:R6:R7 *: R3:R2:R1 | A:B:DPH:DPL | Jedes SFR v.r.n.l. je nach Bitbreite
|
2. | 8,16bit: R4:R5 (wenn verfügbar) *: R3:R2:R1 | In funktionsspezifischen,
direkt adressierbaren Speicherzellen, Segment UDATA?
|
3. | 8,16bit: R2:R3 (wenn verfügbar) *: R3:R2:R1
|
weitere | In direkt adressierbaren Speicherzellen
|
Returnwert | 8-32bit:R4:R5:R6:R7 *: R3:R2:R1 | A:B:DPH:DPL
|
Das bedeutet, dass es für SDCC zweckmäßig ist, Zeigerargumente
als ersten Parameter anzuordnen,
dann ist dieser sogleich in DPTR.
Weiterhin sind Funktionen mit mehr als 1 Parameter per se nicht
rekursiv verwendbar, sonst muss umständlicher über den Stack gegangen werden.
Structs
IMHO können im aktuellen SDCC struct
s kopiert werden.
Besser ist es jedoch, mit spezialisierten memcpy()
-Routinen
(von code
nach idata
,
von xdata
nach xdata
,
von idata
nach xdata
usw.)
das Problem ohne generische Zeiger zu lösen.