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

5

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 por adelantado.

Para que quede más claro, explicaré brevemente qué es ese pinState . Estoy enviando 3 bytes de datos y quiero que el 1. byte vaya pin 3 pwm 2. byte pin 11 pwm y 3. byte a pin6 pwm . Como se ve en cada interrupción, no obtengo 3 bytes, sino 1 byte. Así que esa cosa pinState solo está defendiendo ese propósito.

    
pregunta Zgrkpnr__

3 respuestas

10

No mencionaste qué microcontrolador estás usando, pero probablemente no importa. 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 te limita a operaciones de un byte. ¿Qué pasaría si, en cambio, utilizara el ISR solo para rellenar una matriz de bytes? Luego, después de haber llegado un cierto número de bytes, solo entonces lees los bytes e interpretas su significado, preferiblemente en el bucle principal de tu código, no en el ISR.

Tenga cuidado de hacer demasiado trabajo en un ISR. Especialmente llamando a funciones desde dentro de la función ISR. Por supuesto, cada fabricante y compilador de silicio es diferente, pero las funciones de llamada dentro de los ISR pueden llevar a un código extremadamente lento e ineficiente. La mayoría de sus cálculos y manejo de datos deben ocurrir en su ciclo principal. Los ISR se deben utilizar para establecer indicadores y operaciones rápidas. Por lo general, desea salir del ISR lo más rápido que pueda.

    
respondido por el Dan Laks
11

El método convencional para tratar con recibos 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 la salida de la cola circular es interrogado en busca de contenido disponible por el código de la línea principal que desea usar los datos recibidos. Cuando comienza a tratar con flujos de múltiples bytes en una interfaz en serie, a menudo es deseable hacer un "protocolo" que permita al extremo del receptor poder rastrear dónde comienza el inicio de cada lote de datos. Hay muchas maneras de hacer esto. Una forma de seleccionar un valor de byte único para el inicio del lote. Otra forma es configurar el MSB del primer byte del lote y luego seguir con el resto de los bytes del lote con el MSB borrado.

** Algunos comentarios sobre su ISR **

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

  2. Por lo general, no es necesario poner inhabilitar y volver a habilitar las 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 de estado pin dentro del ISR. Eso no se ve en absoluto aplicable al servicio de una interrupción de USART.

respondido por el Michael Karas
6

Hoy mismo estaba en la misma posición, y escribí un programa siguiendo las líneas de lo que sugiere Michael Karas en su respuesta, utilizando 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 portar 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 que se incluye stdint.h para el tipo uint16_t .

La idea es:

  • En el ISR, cuando recibimos un byte, almacenémoslo en eusart_rx_buffer[eusart_rx_buffer_wr] e incrementemos la posición de escritura.
  • Cuando queremos leer los datos, puedes leer desde eusart_rx_buffer_rd hasta 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. Hay algunos trucos que puedes usar para evitar eso. Puede cambiar EUSART_BUFFER_SIZE para satisfacer sus necesidades. Un valor más bajo, por supuesto, requiere 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 tendrá un aspecto ligeramente diferente, pero la idea es la misma.

Luego, cuando desee 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ía es estándar C y se puede copiar directamente o portar fácilmente.

    
respondido por el Keelan

Lea otras preguntas en las etiquetas