Tipps und Tricks zum C-Compiler (gcc) für den AVR
english page is missing - you can help the project with a translation!
Optimierungen
Die Prozessoren für Decoder sollten natürlich möglichst billig sein und deswegen versucht man,
mit möglichst wenig Speicher auszukommen. Um nun den Compiler zu besonders kompakten Code zu
veranlassen, sollte man ein paar Dinge beachten:
- Einstellungen des Compilers: der WinAVR (gcc) Compiler wird mit
-Os auf "optimize for size" eingestellt.
Weiters wird mit -fshort_enums eine kompakte Darstellung von Aufzähltypen erreicht.
- Der Compiler führt Berechnungen und logische Operationen generell als 16-Bit Integer durch,
erst das Endergebnis wird dann der Zielvariablen oder dem if-then-else-Statement zugeführt.
Man kann den Compiler zu kleinerem Code zwingen,
wenn man Zwischenergebnisse der Berechnung auf eine lokale Variable vom Typ
unsigned char casted.
Dadurch kann der Optimizer unnötige 16-Bit Zwischenschritte erkennen und wegoptimieren.
Ein Beispiel: das bisher empfangene Byte soll um eins nach links geschoben
werden und das neue Bit hinten angefügt werden:
- normaler Code:
if (DCC_INPUT == 1)
{
dccrec.accubyte = (dccrec.accubyte << 1) | 1;
}
else
{
dccrec.accubyte = dccrec.accubyte << 1;
}
- optimierter Code:
unsigned char my_accubyte; // lokale Zwischenvariable
my_accubyte = dccrec.accubyte << 1;
if (DCC_INPUT == 1)
{
my_accubyte |= 1; // alle Operationen auf der lokalen Variablen
}
dccrec.accubyte = my_accubyte; // wieder zurückspeichern
Das sieht auf den ersten Blick umständlicher aus, ist aber kleiner und schneller!
- Oft benutzte globale Variablen kann man in unbenutzten IO-Registern des AVR ablegen. Diese sind
sowohl im Zugriff schneller, als auch per Bitbefehle manipulierbar; die Bits sind auch direkt abfragbar.
Speziell GPIOR0 ... 2 sind für diese Zwecke gedacht.
Beispiel:
#define Recstate GPIOR1 // use GPIOR1 as state variable
#define RECSTAT_WF_PREAMBLE 0 // define bit positions for states
#define RECSTAT_WF_LEAD0 1
#define RECSTAT_WF_BYTE 2
#define RECSTAT_WF_TRAILER 3
Die Zuweisung des Status erfolgt so:
Recstate = 1<<RECSTAT_WF_LEAD0;
Und die entsprechende Abfrage lautet:
if (Recstate & (1<<RECSTAT_WF_PREAMBLE)) // wait for preamble
{
}
Der Compiler generiert daraus folgenden, äußerst effektiven Code:
sbis 0x14, 0
rjmp .+20
- Ganz "harte Nüsse" kann man mit inline-Assembler knacken. Hier ein Beispiel, wie man in
einer Interruptroutine einen Wert auf ein IO-Register schreiben kann:
#define ISR_INT0_OPTIMIZED
#ifdef ISR_INT0_OPTIMIZED
#define ISR_NAKED(vector) \
void vector (void) __attribute__ ((signal, naked)); \
void vector (void)
ISR_NAKED(INT0_vect)
{
__asm__ __volatile
(
"push r16" "\n\t"
"ldi r16, %1" "\n\t"
"out %0, r16" "\n\t"
"pop r16" "\n\t"
: // no output section
: "M" (_SFR_IO_ADDR (TCCR1B)),
"M" ((0 << ICNC1) // start timer1
| (0 << ICES1)
| (0 << WGM13)
| (1 << WGM12) // Mode 4: CTC
| (0 << CS12) // clk 1:1
| (0 << CS11)
| (1 << CS10))
);
asm volatile ( "reti" );
}
#else
ISR(INT0_vect)
{
TCCR1B = (0 << ICNC1) // start timer1
| (0 << ICES1)
| (0 << WGM13)
| (1 << WGM12) // Mode 4: CTC
| (0 << CS12) // clk 1:1
| (0 << CS11)
| (1 << CS10);
}
#endif
Mit dem Attribute "naked" zwingt man gcc, den Funktionsprolog und -epilog wegzulassen.
Man muß dann allerdings *alles* selbst machen, inkl. des RETI am Ende der ISR. Sinnvollerweise
sollte inline-Assembler immer parallel zu einer C-Implementierung abgelegt werden, damit der
Code möglichst kompatibel bleibt.
Besondere Probleme
Sprung nach 0x0000 (Restart)
An diversen Stellen im Internet wird folgendes Konstrukt empfohlen, um einen Softwarereset auszulösen:
void (*funcptr)( void ) = 0x0000;
...
funcptr(); // Jump to Reset vector 0x0000
Das hat bei mir nicht funktioniert, wie auch am Assemblerlisting (.lss) abzulesen war. Meine Lösung sieht
so aus:
__asm__ __volatile
(
"ldi r30,0" "\n\t"
"ldi r31,0" "\n\t"
"icall" "\n\t"
);
Links