¿Cómo leer datos completos enviados a USART con interrupción?

Actualmente puedo leer byte por byte de USART con este código

ISR(USART_RX_vect)
{
    cli();
    while(!(UCSR0A&(1<<RXC0))){};
    // clear the USART interrupt  
    received = UDR0;

    if(pinState == 0)
    {
        OCR2A = received;
        pinState = 1;
    }
    else if(pinState == 1)
    {
        OCR2B = received;
        pinState = 2;
    }
    else
    {
        OCR0A = received;
        pinState = 0;
    } 
    sei();
}

Pero ahora enviaré 4 bytes de datos que son únicamente necesarios para mi aplicación. No pude averiguar cómo leer 4 bytes a la vez ya que la interrupción se activa para cada byte. Gracias por cualquier esfuerzo de antemano.

Para que te quede más claro te explicaré brevemente en qué pinStateconsiste. Estoy enviando 3 bytes de datos y quiero que el 1. byte vaya al pin 3 pwm2. byte pin 11 pwmy el 3. byte a pin6 pwm. Como puede ver en cada interrupción, no obtengo 3 bytes, sino 1 byte. Entonces, esa cosa pinState solo sirve para ese propósito.

Respuestas (3)

No mencionó qué microcontrolador está usando, pero probablemente no importe. Los periféricos USART generalmente funcionan exactamente como lo ha descubierto: un byte a la vez. Esto, sin embargo, no es una limitación.

Según el fragmento de código que publicó en su pregunta, está intentando ejecutar alguna funcionalidad con cada byte recibido. Eso lo limita a operaciones de un byte. ¿Qué pasaría si, en cambio, usara el ISR solo para completar una matriz de bytes? Luego, después de que haya llegado una cierta cantidad de bytes, solo entonces lee los bytes e interpreta su significado, preferiblemente en el ciclo principal de su código, no en el ISR.

Tenga cuidado de no hacer demasiado trabajo en un ISR. Especialmente funciones de llamada desde dentro de la función ISR. Cada fabricante y compilador de silicio es diferente, por supuesto, pero las funciones de llamada dentro de los ISR a menudo pueden conducir a un código extremadamente lento e ineficiente. La mayoría de sus cálculos y manejo de datos deberían ocurrir en su bucle principal. Los ISR deben usarse para establecer banderas y operaciones rápidas. Por lo general, desea salir de la ISR lo más rápido que pueda.

Además, no creo que lo necesite cli();en su ISR, ya que la MCU lo hace automáticamente.
No estoy escribiendo el código para un procesador específico. Por lo tanto, quería ser cauteloso con las diferencias. Es como plantearse si int es de 32bits o de 16bits en cualquier procesador, por así decirlo.
@Zgrkpnr__ Los controladores de interrupción son un área en la que no puede escribir código portátil. Los detalles importan, y borrar explícitamente una bandera que debe permitir que el hardware borre para usted puede tener efectos secundarios inesperados. Del mismo modo, no borrar una bandera que el hardware espera que usted maneje también causará problemas.
Es claramente AVR de todos modos. Este código ya está lejos de ser portátil :)

El método convencional para manejar recepciones de múltiples bytes en una rutina de interrupción es configurar una cola circular. A medida que se recibe cada byte, se coloca en la siguiente ranura disponible en la cola. Esto reemplazaría su código de guardado de un solo byte que tiene ahora.

El lado de salida de la cola circular es sondeado por el contenido disponible por su código de línea principal que quiere usar los datos recibidos. Cuando comienza a manejar flujos de múltiples bytes en una interfaz serial, a menudo es deseable crear un "protocolo" que permita que el receptor pueda rastrear dónde comienza el inicio de cada lote de datos. Hay muchas maneras de hacer esto. Una forma es seleccionar un valor de byte único para el inicio del lote. Otra forma es configurar el MSB del primer byte del lote y luego continuar con el resto de los bytes del lote con el MSB borrado.

**Algunos comentarios sobre tu ISR**

  1. Por lo general, es una mala práctica colocar bucles de sondeo dentro de una rutina de servicio de interrupción. Desea mantener el tiempo de ejecución de ISR lo más corto posible. No sé cuál es la intención del ciclo de sondeo, pero debería estudiar cómo eliminarlo.

  2. Por lo general, no es necesario poner la desactivación y reactivación de interrupciones dentro de un ISR. La mayoría de las MCU deshabilitarán automáticamente las interrupciones cuando se ingrese el ISR y luego restaurarán el estado anterior cuando se ejecute el retorno de la interrupción.

  3. No está claro qué está haciendo la lógica del estado del pin dentro del ISR. Eso no parece en absoluto aplicable al servicio de una interrupción USART.

Actualicé mi pregunta con un poco más de detalle.
Ambas respuestas me llevaron a la misma solución que fue útil y resolvió mi problema. Gracias. También voté tu respuesta

Justo hoy estaba exactamente en la misma posición, y escribí un programa en la línea de lo que sugiere Michael Karas en su respuesta, usando un búfer circular. Utilicé un PIC18, por lo que es posible que algunos códigos no se compilen, pero muestra la idea claramente y debería ser fácil transferir este código a AVR, ...

Declaré algunas variables globales:

#define EUSART_BUFFER_SIZE 2048
char eusart_rx_buffer[EUSART_BUFFER_SIZE];   // the actual buffer, now 2048 bytes long
uint16_t eusart_rx_buffer_rd = 0;            // the current read position
uint16_t eusart_rx_buffer_wr = 0;            // the current write position

Esto supone stdint.hque está incluido para el uint16_ttipo.

La idea es:

  • En el ISR, cuando recibimos un byte, lo almacenamos eusart_rx_buffer[eusart_rx_buffer_wr]e incrementamos la posición de escritura.
  • Cuando queremos leer los datos, puede leer desde eusart_rx_buffer_rdhasta eusart_rx_buffer_wr.

Por supuesto, cuando se almacenan más de 2048 bytes al mismo tiempo, el búfer se sobrescribirá y perderá datos. Sin embargo, hay algunos trucos que puedes usar para evitar eso. Puedes cambiar EUSART_BUFFER_SIZEsegún tus necesidades. Un valor más bajo, por supuesto, ocupa menos memoria de datos.

Ahora, en mi ISR, tengo:

if (PIR1bits.RCIF) {                                  // EUSART data received
    eusart_rx_buffer[eusart_rx_buffer_wr++] = RCREG;  // Store the received data
    if (eusart_rx_buffer_wr >= EUSART_BUFFER_SIZE)    // Increment write pointer
        eusart_rx_buffer_wr = 0;
    PIR1bits.RCIF = 0;                                // Clear interrupt flag
}

Por supuesto, en un AVR este código se verá un poco diferente, pero la idea es la misma.

Luego, donde desea leer los datos, puede hacer algo como:

while (eusart_rx_buffer_rd != eusart_rx_buffer_wr) {   // While there's data in the buffer
    do_sth(eusart_rx_buffer[eusart_rx_buffer_rd++]);   // Do something with it
    if (eusart_rx_buffer_rd >= EUSART_BUFFER_SIZE)     // Increase read pointer
        eusart_rx_buffer_rd = 0;
}

Este código fue escrito para PIC18 usando el compilador XC8, pero la mayor parte es C estándar y se puede copiar directamente o portar fácilmente.