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.
int main(void) {
...
while (true) {}
}
ISR(ADC_vect) {
USARTSendByte(ADCL);
USARTSendByte(ADCH);
ADCSRA |= 1 << ADSC;
}
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.38ms
22,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?
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.
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.
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_t
Variable 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.
main()
wie:cli(); temp = adc_value; sei(); processAdcValue( temp );
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 temp
sie überschrieben werden. Das ist eigentlich der Grund, warum ich die Interrupts während der Datenübertragung deaktivieren wollte.
Mäusez