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:
    1. 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.
    2. 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!
    3. 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
           
    4. 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