Rendimiento de AVR ADC: interrupciones frente a conversión manual

4

Tengo un dispositivo en el microcontrolador ATMega16 que se supone que envía constantemente los resultados de las mediciones de ADC a través de USART. El controlador funciona a 16MHz con un cristal externo y el prescaler ADC está configurado a 128. He intentado dos métodos para realizar la conversión de ADC y enviar los resultados.

  1. El primer método se basa en interrupciones
    int main(void) {
        ...
        while (true) {}
    }

    ISR(ADC_vect) {
        USARTSendByte(ADCL);
        USARTSendByte(ADCH);
        ADCSRA |= 1 << ADSC;
    }
  1. El segundo método se basa en iniciar manualmente la conversión
    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);
            }
        }
    }

He realizado varias pruebas que consistieron en enviar 32 bloques de 512 bytes (16384 bytes en total) sobre USART y medir el tiempo de transmisión. En el primer caso (interrumpe) el tiempo promedio fue 1623.13ms . El resultado más pequeño fue 1535 ms y el más grande: 1712 ms. En el segundo caso, el resultado fue 1600.38ms , que es 22.75ms menos que en el caso anterior, con un resultado mínimo de 1530ms y el mayor resultado de 1679ms.

Entonces, la pregunta es: ¿las interrupciones realmente disminuirán el rendimiento del ADC y por qué sucede eso o los resultados de mis pruebas no fueron concluyentes?

    
pregunta Ashton H.

3 respuestas

6

Lo que probablemente esté midiendo es la sobrecarga de ingresar y salir del ISR en el software. Debido a que no ha decorado el ISR, está guardando todo el estado de registro y restaurándolo en la devolución. Se puede declarar que su ISR está desnudo y usted puede administrar la conservación del SREG y otros registros críticos usted mismo (por ejemplo, en un ensamblaje en línea) y eso lo llevará a donde desea estar.

En realidad, tampoco quieres llamar a funciones desde el ISR. Simplemente mueva los bytes a un búfer global y haga cosas con los datos almacenados en el búfer de su ciclo principal (como enviarlos a través del UART).

Desde el avr-gcc :

  

define ISR_NAKED

     

ISR se crea sin prólogo ni código de epílogo. El código de usuario es responsable de la preservación del estado de la máquina.   incluido el registro SREG, así como colocar un reti () al final de   la rutina de interrupción.

    
respondido por el vicatcu
3

¿Está planeando hacer otra cosa en el microprocesador que no sea enviar las lecturas de ADC? Si no, olvídate de las interrupciones y ve con todo en el bucle principal. Las interrupciones son realmente útiles solo si intentas hacer otra cosa en el "fondo".

    
respondido por el DoxyLover
1

No te molestes (todavía) en usar el ensamblador. No necesitas eso.

Sin embargo, un punto importante (general) es: ¡No llame a las funciones de bloqueo desde un ISR! Un ISR no debería nunca esperar a nada .

Vicatcu tiene razón: usa algún tipo de 'búfer', que puede ser tan simple como una única variable global uint16_t , en la que el ISR escribe su resultado y el bucle principal lo recoge cuando puede.

Otra nota: en su código, adquiere un resultado de ADC, envíelo a través del USART, y luego comienza la siguiente conversión. Debería poder obtener el resultado del ADC, comenzar la siguiente conversión y solo luego transferir los datos, mientras que el ADC ya está procesando la siguiente muestra al mismo tiempo. O bien, es posible que desee ver el modo de "ejecución libre" del ADC, donde inicia automáticamente la siguiente conversión tan pronto como finaliza la anterior.

Para ilustrar cómo se podría implementar un enfoque de almacenamiento en búfer genérico, esto puede servir como ejemplo:

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));

  }

}

Si / cuando el ADC toma muestras constantemente más rápido que el USART puede transmitir los datos, no no hay forma de perder muestras. En ese caso, deberá sincronizar ambos procesos (muestrear y transmitir) como ya lo hizo, comenzando el siguiente muestreo solo después de la operación de transmisión (más lenta).

Aparte de eso, colas o buffers circulares (también conocidos como ring buffers ) se utilizan a menudo para intercambiar datos a velocidades variables entre componentes asíncronos de el programa: el ISR agrega datos al búfer y el ciclo principal toma (y elimina) tantos elementos como estén disponibles en el búfer cuando tenga tiempo.

Estos búferes pueden implementarse en dos tipos de condiciones de desbordamiento: un tipo descarta cualquier información nueva cuando el búfer está lleno, el otro simplemente sobrescribe los datos más antiguos del búfer.

Si eso es lo que necesita, simplemente busque "avr circular buffer"; hay un montón de implementaciones por ahí.

    
respondido por el JimmyB

Lea otras preguntas en las etiquetas