avr-gcc-Timer-Overflow-Interrupt-Handler in Inline-Assembly

Mein aktuelles Projekt beinhaltet die Erzeugung von 200-kHz-PWM-Wellenformen mit Timer 1 im schnellen PWM-Modus. Ich möchte einen 16-Bit-Zähler jedes Mal erhöhen, wenn der Timer überläuft (alle 5 μs).

volatile uint16_t count;
ISR(TIMER1_OVF_vect)
{
    ++count;
}

Der Mikrocontroller ist ein ATmega8, der mit 16 MHz läuft, sodass nur 80 Zyklen übrig bleiben, um den Interrupt zu bedienen und die Variable zu inkrementieren, bevor der nächste Interrupt ausgelöst wird. Wenn ich mir den kompilierten Code ansehe ...

00000890 <__vector_8>:
 890:   1f 92           push    r1
 892:   0f 92           push    r0
 894:   0f b6           in  r0, 0x3f    ; 63
 896:   0f 92           push    r0
 898:   11 24           eor r1, r1
 89a:   8f 93           push    r24
 89c:   9f 93           push    r25
 89e:   80 91 c9 00     lds r24, 0x00C9
 8a2:   90 91 ca 00     lds r25, 0x00CA
 8a6:   01 96           adiw    r24, 0x01   ; 1
 8a8:   90 93 ca 00     sts 0x00CA, r25
 8ac:   80 93 c9 00     sts 0x00C9, r24
 8b0:   9f 91           pop r25
 8b2:   8f 91           pop r24
 8b4:   0f 90           pop r0
 8b6:   0f be           out 0x3f, r0    ; 63
 8b8:   0f 90           pop r0
 8ba:   1f 90           pop r1
 8bc:   18 95           reti

...Ich fand heraus, dass die generierte Interrupt-Service-Routine leicht weiter optimiert werden könnte. Dies ist das erste Mal, dass ich versucht habe, die Inline-Assemblierung in ein C-Programm einzufügen, und ich habe festgestellt, dass es unnötig frustrierend ist, dies zu lernen, und es erfordert, eine ziemlich esoterische Syntax zu verstehen. Ich würde gerne wissen, wie ich auf die Inline-Assembly zugreifen kann uint8_t count(da die Variable statisch zugewiesen wird, anders als in jeder Antwort, die ich im Web gesehen habe). Ist der Code sonst in Ordnung, oder habe ich noch etwas übersehen?

ISR(TIMER1_OVF_vect, ISR_NAKED)
{
    asm volatile("push    r24"                                      "\n\t"
                 "in      r24, __SREG__"                            "\n\t"
                 "push    r24"                                      "\n\t"
                 "push    r25"                                      "\n\t"
                 "lds     r24, %A0"                                 "\n\t"
                 "lds     r25, %B0"                                 "\n\t"
                 "adiw    r24, 1"                                   "\n\t"
                 "sts     %B0, r25"                                 "\n\t"
                 "sts     %A0, r24"                                 "\n\t"
                 "pop     r25"                                      "\n\t"
                 "pop     r24"                                      "\n\t"
                 "out     __SREG__, r24"                            "\n\t"
                 "pop     r24"                                      "\n\t"
                 "reti"                                             "\n\t"
                 : "=r" (count)   /*this does*/
                 : "0" (count));  /*not work*/
}

Als Nebenbemerkung gibt es eine Möglichkeit, den Compiler dazu zu bringen, ein Registerpaar speziell für zu reservieren uint8_t count, da dies eine Reduzierung der ISR-Länge um mindestens 6 Anweisungen ermöglichen würde (durch Eliminieren der lds- und sts-Anweisungen, eines Push-to-Stack und eines pop vom Stapel)?

Haben Sie den Originalcode mit aktivierter Optimierung in GCC kompiliert?
@David Ursprünglich wurde es mit der auf O2 eingestellten Optimierungsstufe kompiliert. Ich habe seitdem versucht, mit Optimierungen zu kompilieren, die auf O, O1, O2, O3 und Os eingestellt sind. Anscheinend kompiliert dieser ISR-Code unabhängig von der Optimierungsstufe immer zur gleichen Ausgabe. Der Rest der kompilierten Ausgabe zeigt große Unterschiede.
Das ist interessant. Ich würde mich fragen, ob es sehr gute Gründe gibt, warum GCC dies nicht weiter optimiert. Die Tatsache, dass r0/r1 gespeichert und wiederhergestellt werden, ist interessant und könnte eher ein Architekturmerkmal als ein Mangel an Optimierung sein. Ich weiß nicht genug über AVR, ohne weiter zu lesen.

Antworten (2)

Ich habe Ihren Beitrag gefunden, als ich nach einer Optimierung der ISR-Routine gesucht habe. Endlich habe ich eine Lösung, die Sie (und ich) haben wollten.

Ich verwende Atmel Studio 6.1 (GCC 3.4.2.1002)

ISR(TIM0_OVF_vect,ISR_NAKED)
{
    asm volatile(
        "push   r24"            "\n"
        "in     r24, __SREG__"  "\n"
        "push   r24"            "\n"
        "push   r25"            "\n"

        "lds    r24, %A[_ts]"   "\n"
        "lds    r25, %B[_ts]"   "\n"
        "adiw   r24,1"          "\n"
        "sts    %B[_ts], r25"   "\n"
        "sts    %A[_ts], r24"   "\n"

        "pop    r25"            "\n"
        "pop    r24"            "\n"
        "out    __SREG__,r24"   "\n"
        "pop    r24"            "\n"
        "reti"                  "\n"
        : 
        : [_ts] "m" (ts)
        : "r24", "r25"
    );
}

Hier tswird deklariert, wie ich benannte Operanden ( ) anstelle von Standard volatile unsigned int ts = 0;
verwende[_ts] "m" (ts)%0

Das Ergebnis ist:


    0000005a <__vector_11>:
    5a: 8f 93           push    r24
    5c: 8f b7           in  r24, 0x3f   ; 63
    5e: 8f 93           push    r24
    60: 9f 93           push    r25
    62: 80 91 60 00     lds r24, 0x0060
    66: 90 91 61 00     lds r25, 0x0061
    6a: 01 96           adiw    r24, 0x01   ; 1
    6c: 90 93 61 00     sts 0x0061, r25
    70: 80 93 60 00     sts 0x0060, r24
    74: 9f 91           pop r25
    76: 8f 91           pop r24
    78: 8f bf           out 0x3f, r24   ; 63
    7a: 8f 91           pop r24
    7c: 18 95           reti

Lassen Sie den Compiler die Routinearbeit für Sie erledigen.

asm volatile("adiw %0,1\n\t"
             : "=w" (count)
             : "0" (count)
            );
reti();

Auch , registeraber der Compiler nimmt es nur als Vorschlag.

Okay, das funktioniert, aber ich müsste die Werte von r23, r24 und SREG sichern, um sie vorher in einer separaten asm-Anweisung zu stapeln (da die Register geändert werden könnten), und sie anschließend in einer dritten asm-Anweisung wiederherstellen. Und meines Wissens habe ich keine Möglichkeit zu wissen, ob der Wert von counttatsächlich in den Registern r23 und r24 zum Inkrementieren oder woanders gespeichert ist.
Das zweite und dritte Argument sollten dem Compiler mitteilen, dass er die Register pushen und poppen muss. Finden Sie, dass dies nicht der Fall ist?
Ja, der Compiler könnte sich um die Sicherung dieser Register kümmern, wenn ich einfach eine normale ISR() ohne ISR_NAKED schreiben würde. Dies wäre jedoch ISR(TIMER1_OVF_vect){ ++count; }in Bezug auf die Leistung nicht besser als der ursprüngliche C-Code, da beide in genau denselben Assemblercode kompiliert werden.