AVR-ADC-Leistung: Interrupts vs. manuelle Konvertierung

Ich habe ein Gerät auf einem ATMega16-Mikrocontroller, das ständig ADC-Messergebnisse über USART senden soll. Der Controller arbeitet mit einem externen Quarz bei 16 MHz und der ADC-Prescaler ist auf 128 eingestellt. Ich habe zwei Methoden ausprobiert, um die ADC-Konvertierung durchzuführen und die Ergebnisse zu senden.

  1. Die erste Methode basiert auf Interrupts
    int main(void) {
        ...
        while (true) {}
    }

    ISR(ADC_vect) {
        USARTSendByte(ADCL);
        USARTSendByte(ADCH);
        ADCSRA |= 1 << ADSC;
    }
  1. Die zweite Methode basiert auf dem manuellen Starten der Konvertierung
    int main(void) {
        ... 
        // main loop
        while (true) {
            if (adcEnabled) {
                ADMUX |= channel;
                ADCSRA |= (1 << ADSC);
                while (!(ADCSRA & (1 << ADIF))) {
                    // Do nothing
                }
                ADCSRA |= (1 << ADIF); // Clear ADIF            
                USARTSendByte(ADCL);
                USARTSendByte(ADCH);
            }
        }
    }

Ich habe eine Reihe von Tests durchgeführt, die darin bestanden, 32 Blöcke mit 512 Bytes (insgesamt 16384 Bytes) über USART zu senden und die Übertragungszeit zu messen. Im ersten Fall (Unterbrechungen) betrug die durchschnittliche Zeit 1623.13ms. Das kleinste Ergebnis war 1535 ms und das größte - 1712 ms. Im zweiten Fall war das Ergebnis 1600.38ms22,75 ms weniger als im vorherigen Fall, mit einem kleinsten Ergebnis von 1530 ms und dem größten Ergebnis von 1679 ms.

Die Frage ist also: Werden Interrupts tatsächlich die ADC-Leistung verringern und warum passiert das oder die Ergebnisse meiner Tests waren nicht schlüssig?

Beachten Sie, dass Sie in ADCSRA nach dem ADSC-Flag suchen können - das Mikro ändert es auf 0, nachdem die Konvertierung abgeschlossen ist. Auf diese Weise müssen Sie das Interrupt-Flag nicht löschen.

Antworten (3)

Was Sie wahrscheinlich messen, ist der Aufwand für das Betreten und Verlassen des ISR in der Software. Da Sie den ISR nicht dekoriert haben, speichert er den gesamten Registerstand und stellt ihn bei der Rückkehr wieder her. Ihr ISR kann als nackt deklariert werden und Sie können die Erhaltung der SREG und anderer kritischer Register selbst verwalten (z. B. in der Inline-Montage), und das bringt Sie dorthin, wo Sie sein möchten.

Sie möchten auch nicht wirklich Funktionen von der ISR aufrufen. Verschieben Sie einfach Bytes in einen globalen Puffer und machen Sie Dinge mit den gepufferten Daten aus Ihrer Hauptschleife (wie sie über den UART senden).

Aus dem avr-gcc- Handbuch:

ISR_NAKED definieren

ISR wird ohne Prolog- oder Epilogcode erstellt. Der Benutzercode ist verantwortlich für die Erhaltung des Maschinenzustands einschließlich des SREG-Registers sowie für die Platzierung eines reti() am Ende der Interrupt-Routine.

Danke, leider bin ich mit der Assemblierung von AVR unerfahren, also muss ich wohl weitere Informationen zur Erhaltung des Sreg-Registers finden.
Es ist einfach genug, ein minimales Beispiel in C zu schreiben, es zu kompilieren, sich das generierte Assembly-Listing anzusehen und es 'wörtlich' in Ihren Interrupt-Handler zu kopieren, ergänzt durch einen minimalistischen Prolog und Epilog

Planen Sie, etwas anderes im Mikroprozessor zu tun, als die ADC-Messwerte zu senden? Wenn nicht, vergessen Sie die Verwendung von Interrupts und fahren Sie einfach mit allem in der Hauptschleife fort. Interrupts sind wirklich nur nützlich, wenn Sie versuchen, etwas anderes im "Hintergrund" zu tun.

"Unterbrechungen sind wirklich [...] nützlich, wenn Sie versuchen, etwas anderes im "Hintergrund" zu tun ... oder wenn Sie Energiesparfunktionen verwenden möchten / müssen.

Kümmern Sie sich (noch) nicht darum, Assembler zu verwenden. Das brauchst du nicht.

Ein wichtiger (allgemeiner) Punkt ist jedoch: Rufen Sie keine Sperrfunktionen aus einem ISR heraus auf! Ein ISR sollte niemals auf irgendetwas warten müssen .

Vicatcu hat Recht: Verwenden Sie eine Art „Puffer“, der so einfach wie eine einzelne globale uint16_tVariable sein kann, in die die ISR ihr Ergebnis schreibt und die Hauptschleife es aufnimmt, wenn sie kann.

Noch ein Hinweis: In Ihrem Code erfassen Sie ein ADC-Ergebnis, senden es über den USART und starten dann die nächste Konvertierung. Sie sollten in der Lage sein, das ADC-Ergebnis zu erhalten, die nächste Konvertierung zu starten und erst dann die Daten zu übertragen, während der ADC gleichzeitig bereits die nächste Probe verarbeitet. Oder schauen Sie sich den "freilaufenden" Modus des ADC an, wo er automatisch die nächste Konvertierung startet, sobald die vorherige beendet ist.

Um zu veranschaulichen, wie ein generischer Pufferungsansatz implementiert werden könnte, kann dies als Beispiel dienen:

volatile uint16_t adcValueBuffer;
volatile uint8_t adcValueBufferValidFlag;

ISR(ADC_vect) {

    adcValueBuffer = ADC;

    adcValueBufferValidFlag = 1; // This signals that the ADC provided a new value for the code outside the ISR.

    ADCSRA |= 1 << ADSC;

}


int main() {
  uint16_t adcValueCache; // Local variable which will hold the ADC value until it is completely transmitted.

  while( true ) {

    // Wait for the ISR to signal that a new value is available:
    while ( adcValueBufferValidFlag == 0 ) {
    }

    adcValueBufferValidFlag = 0; // Re-set flag. Will be set again by the ISR when a new ADC value becomes available.

    // Make sure that we read the buffered value atomically:
    cli();

    adcValueCache = adcValueBuffer;

    sei();

    USARTSendByte( (uint8_t)adcValueCache );
    USARTSendByte( (uint8_t)(adcValueCache >> 8));

  }

}

Wenn der ADC ständig schneller abtastet, als der USART die Daten übertragen kann, gibt es keine Möglichkeit, keine Abtastungen zu verlieren. In diesem Fall müssen Sie beide Prozesse (Sampling und Übertragung) synchronisieren, wie Sie es bereits getan haben, und das nächste Sampling erst starten, nachdem der (langsamere) Übertragungsvorgang abgeschlossen ist.

Abgesehen davon werden häufig Warteschlangen oder Ringpuffer (auch Ringpuffer genannt ) verwendet, um Daten mit unterschiedlichen Raten zwischen asynchronen Komponenten des Programms auszutauschen: Die ISR hängt Daten an den Puffer an und die Hauptschleife nimmt (und entfernt) dann so viele Elemente wie im Puffer verfügbar sind, wenn es Zeit hat.

Diese Puffer können in Bezug auf Überlaufbedingungen in zwei Arten implementiert werden: Ein Typ verwirft alle neuen Daten, wenn der Puffer voll ist, der andere überschreibt einfach die ältesten Daten im Puffer.

Wenn Sie das brauchen, suchen Sie einfach nach "avr kreisförmiger Puffer"; Es gibt viele Implementierungen da draußen.

Der Aufruf einer Funktion von einem ISR ist an sich nicht schlecht, aber die Funktion sollte nicht länger als sagen wir einige µs laufen. Die Regel lautet einfach: Halten Sie den Code, der in einer ISR ausgeführt wird, so kurz wie möglich. -- Sie sollten Interrupts während der Übertragung über USART nicht deaktivieren müssen, aber, und ich denke, das ist Ihr Punkt, Sie sollten Interrupts deaktivieren, während Sie die von der ISR bereitgestellten Daten lesen. Sie können diese Daten einfach zwischenspeichern main()wie:cli(); temp = adc_value; sei(); processAdcValue( temp );
Meine USART-Übertragungsroutine sieht so aus: while ((UCSRA & (1 << UDRE)) == 0) {/*Do nothing*/} UDR = temp;. Wenn also ein Interrupt auftritt, bevor die temporären Daten gesendet werden, sende ich am Ende einen anderen Wert, da tempsie überschrieben werden. Das ist eigentlich der Grund, warum ich die Interrupts während der Datenübertragung deaktivieren wollte.
Bitte schau dir meine Bearbeitung an.
Danke für das Beispiel. Und auch +1 für die Verwendung von Java-Namenskonventionen :) Aber ich habe noch eine Frage: Im Allgemeinen läuft ADC schneller als USART (z. B. ADC mit 9,615 ksps vs. USART mit 115200 b / s). Bedeutet das, dass ich wahrscheinlich eine Liste (dh ein Array) von ADC-Puffervariablen verwenden sollte, um nicht einige der ADC-Samples zu verlieren?
Eine weitere Bearbeitung hinzugefügt :)
vielen Dank für die sehr ausführliche und informative Antwort! Es hat mir wirklich geholfen, die Dinge zu sortieren.
Das ist großartig :) Sie können dann meine Antwort akzeptieren .
Ich würde das gerne tun, aber meine anfängliche Frage war "wird Interrupts die ADC-Leistung verringern", also denke ich, dass das Akzeptieren der Antwort gegenüber @vicatcu unfair wäre :) Ich hätte wahrscheinlich eine andere Frage zum Puffern von ADC-Werten stellen sollen. Aber noch einmal vielen Dank für Ihre Zeit und Mühe, ich weiß es wirklich zu schätzen.