¿Comunicándose a través de USART con un AVR - ¿Búfer de entrada?

Actualmente estoy construyendo un robot (como algunos de ustedes sabrán por mis preguntas anteriores). La tarea actual de la que me ocupo es la de la comunicación. Para simplificar las cosas, suponga que tengo 5 comandos para entregar desde mi estación terrestre a mi robot:

  1. Impulsar
  2. Conducir marcha atrás
  3. Activar servo 1
  4. Activar servo 2
  5. Velocidad del motor

Ahora, estoy enviando estos comandos desde mi computadora a un adaptador USB a serie y luego a través de un enlace de RF. Pero esto no es importante, que yo sepa, para mi pregunta.

Entonces mi pregunta es, ¿cómo funciona la comunicación en general? Mi idea es que tendré cinco paquetes de datos diferentes de 8 bits (uno para cada comando) que enviaré continuamente desde mi computadora. Así que mantendré un bucle infinito que comprobará, por ejemplo, la posición de un joystick analógico. Si está apuntando hacia arriba, el comando 1 llevará el mensaje de avance. Si está apuntando hacia abajo, el comando 2 llevará el mensaje, etc. Dependiendo de qué tan lejos esté el joystick en la posición arriba/abajo, dictará el contenido del comando 5. Y el estado de algunas teclas del teclado dictará si los comandos 4 y 5 contienen información para activar los servos.

Entonces, nuevamente, planeo tener un ciclo continuo que verificará cada uno de los 5 estados y enviará los comandos apropiados sobre el USART desde el lado de la computadora.

El problema que tengo, al menos conceptualmente, es que, ¿qué pasa si hay un retraso en el final del robot para procesar los datos que llegan a través del USART? Según tengo entendido, para garantizar que los datos no se pierdan, los datos se almacenan en un búfer en la MCU. Esencialmente, quiero un sistema de "último en entrar, primero en salir" en mi robot, por lo que incluso si pierdo los comandos anteriores por cualquier motivo, el robot está haciendo lo que quiero que haga ahora, no lo que quería hace 2 segundos. Pero con esta metodología, también me temo que saltaré los paquetes de datos de los comandos 1-4 porque seguirá leyendo el último paquete de datos (es decir, el comando 5).

Se me ocurrió esta "hoja de ruta" de comunicación por mi cuenta, así que estoy seguro de que hay formas mucho mejores de lograr lo que quiero. Pero espero que entiendas lo que quiero decir. Además, estoy usando un ATmega328 en mi robot, si importa.

Agradecería cualquier consejo relacionado con esta situación.

Respuestas (3)

Lo que suelo hacer es tratar de asegurarme de que el controlador no envíe comandos más rápido de lo que el robot puede procesar. Enviar instrucciones consistentemente más rápido de lo que puede procesar el robot eventualmente siempre resultará en una pérdida de información con un búfer FIFO o LIFO. Como respaldo al retraso intermitente en el robot, usaría un pequeño búfer FIFO. El tamaño exacto depende de cuál sea el equilibrio entre los comandos perdidos y la capacidad de respuesta.

un búfer LIFO realmente no tiene mucho sentido en cualquier caso porque terminará con una operación fuera de servicio en caso de que el robot responda a los comandos lentamente. considera lo siguiente:

  1. Enviar comando de avance
  2. Enviar Activar servo 1
  3. Enviar comando de retorno
  4. Enviar Activar servo 2

Con una estructura LIFO, digamos que todos los comandos se envían antes de que el robot tenga tiempo de procesar cualquier comando. ¡Los comandos ahora se ejecutarán en orden inverso! El robot activará el servo 2, retrocederá, activará el servo 1 y luego avanzará. Lo más probable es que esto nunca sea lo que quieres.

Si solo está procesando la última instrucción, entonces no se necesitará ningún búfer adicional.

A menos que un plan de "no enviar demasiado rápido" tenga una limitación estricta (como saber que puede mantenerse al día con todo lo que puede ofrecer la velocidad en baudios elegida) o un requisito definido para consultas de estado después de comandos más largos, corre el riesgo de ser roto por un programador diferente menos sintonizado con las limitaciones únicas, lo que en la práctica significa cualquiera menos su yo actual, ya que incluso usted puede pasarlo por alto si regresa al proyecto en seis meses.
cierto, pero es difícil garantizar que no se produzca un desbordamiento del búfer o una pérdida de mensajes. Un búfer circular FIFO sería la copia de seguridad para que las instrucciones más antiguas se sobrescriban en caso de desbordamiento en lugar de las instrucciones más nuevas, y todavía no se recomendaría LIFO debido a la ejecución en orden inverso.
En realidad, no, es bastante fácil asegurarlo: simplemente asegúrese de que su programa pueda analizar y ejecutar comandos tan rápido como la velocidad en baudios pueda entregarlos, y que su almacenamiento en búfer sea suficiente para la latencia máxima que podría ser causada por algún otro bloqueo. tarea. Los tipos de comandos que se analizan aquí son bastante rápidos de ejecutar. Lo que no se ha discutido es esperar a que el robot logre algo en el mundo físico antes de anular un comando; aparentemente, eso lo decide un humano con retroalimentación visual o aún no se ha abordado.
Sí, mi reclamo es que la tasa de transmisión de mensajes debe aplicarse/limitarse de alguna manera si no es importante la pérdida de mensajes. Esto puede ser fácil de aplicar utilizando velocidades de transmisión más lentas, pero sin ningún límite definido, eventualmente acumulará más mensajes de los que se pueden almacenar. Llámelo conservación de mensajes :P promedio de mensajes en tasa <= promedio de mensajes enviados/tasa procesada para garantizar que el búfer no se desborde.

Lo que he hecho en algunos proyectos es usar un búfer circular que se llena con un ISR activado por un UART.

Entonces, cuando el UART recibe un byte, se activa una interrupción y el controlador carga ese byte hasta el final del búfer.

Luego, en el ciclo principal, puede verificar si hay algún byte en el búfer y ejecutar cada comando que se ha puesto en cola.

Mucha información sobre buffers circulares en Internet.

Salud.

Esto es bastante similar a un proyecto mío que usa un ATmega16 que se comunica a través de un Lantronix XPort . El uso de la configuración PuTTY con respuesta de caracteres ACK en el código ENQ se ve así (todavía no he resuelto las cosas del temporizador):

#define F_CPU 8000000UL                         // 8MHz - prevents default 1MHz
#define USART_BAUDRATE 38400UL                  //300, 600, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200, 230400, 460800, 921600
#define BAUD_PRESCALE (((F_CPU / (USART_BAUDRATE * 16UL))) - 1)
#define BUFFER_SIZE 255

#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif
#define pinstate(sfr,bit) ((sfr) & (1<<(bit)))

int coms = 0;                                   // Do we have communication, reset by timer every x milliseconds
int BufferI = 0;                                // Buffer position
char Buffer[BUFFER_SIZE];                       // Receive buffer
char cmd[BUFFER_SIZE];                          // Command variable
int sendheartbeat = 0;
char clearandhome[7] = {'\e', '[', '2', 'J', '\e', '[', 'H'};
#define PROX_SW_EXPANDED            PINB1       // PIN2 POSITION WHEN EXPANDED, READY FOR START
#define MTR_RADIAL_CLOCKWISE        PINC3

void USARTWriteChar(char data) {
    while(!(UCSRA & (1<<UDRE))) { }             //Do nothing until the transmitter is ready
    UDR=data;                                   //Now write the data to USART buffer
}

void USARTWriteLine ( const char *str ) {
    while (*str) {
        USARTWriteChar(*str);
        str++;
    }
    USARTWriteChar(0x0D);                       //Carriage return, 13dec, ^M
    USARTWriteChar(0x0A);                       //Line feed      , 10dec, ^J
}
ISR(USART_RXC_vect) {
    char ReceivedByte;
    ReceivedByte = UDR;                         // Fetch the received byte value into the variable "ReceivedByte"
    coms = 1;                                   // We just received something
    TCNT1 = 0;                                  // reset counter
    if (ReceivedByte == 0x0A) {                 // Use linefeed as command separator
        Buffer[BufferI] = '\0';                 // String null terminator
        memcpy(cmd, Buffer, BufferI + 1);       // Copy complete command to command variable    
        BufferI = 0;                            // Reset buffer position
    } else if (ReceivedByte > 0x1F)             // Ignore Control Characters
        Buffer[BufferI++] = ReceivedByte;       // Add received byte to buffer
    #ifdef DEBUG
    switch (ReceivedByte) {
        case 'q': USARTWriteLine(Buffer); break;
        case 'w': USARTWriteLine(cmd); break;
    }    
    #endif  
}
ISR(TIMER1_OVF_vect) {
    coms = 0;                                       // Lost communication
}

ISR(TIMER0_OVF_vect) {
    if (sendheartbeat) USARTWriteChar(0x05);        // 15 times / second, 66,67ms
}

int main(void) {
    TIMSK=(1<<TOIE0) | (1<<TOIE1);      
    TCNT0=0x00;         
    TCCR0 = (1<<CS02) | (1<<CS00);  
    TCCR1B |= (1 << CS10) | (1 << CS12);

    UBRRL = BAUD_PRESCALE;                      // Load lower 8-bits of the baud rate value into the low byte of the UBRR register
    UBRRH = (BAUD_PRESCALE >> 8);               // Load upper 8-bits of the baud rate value into the high byte of the UBRR register
    UCSRB = (1<<RXEN)|(1<<TXEN | 1<< RXCIE);    // Turn on the transmission and USART Receive Complete interrupt (USART_RXC)
    UCSRC |= (1<<URSEL)|(1<<UCSZ0)|(1<<UCSZ1);  // Set frame format: 8data, 1stop bit

    DDRB = 0x00;                                // Set all pins on PORTB for output
    DDRC = 0xff;                                // Set all pins on PORTC for output
    sei();                                      // Enable the Global Interrupt Enable flag so that interrupts can be processed

    while(1) {
        if (!coms) PORTC = 0x00;                // If not coms turn of everything   
        else if (cmd[0] != '\0') {              // We have coms, check for new command
            if (strcmp("help", cmd) == 0) USARTWriteLine("liberate tuteme ex inferis");
            else if (strcmp("sync", cmd) == 0) sendheartbeat = !sendheartbeat;
            else if (strcmp("clear", cmd) == 0) USARTWriteLine(clearandhome);
            else if (strcmp("expand", cmd) == 0) {
                if (!pinstate(PINB, PROX_SW_EXPANDED))
                    sbi(PORTC, MTR_RADIAL_CLOCKWISE);
            } else if (!(strcmp("", cmd) == 0)) USARTWriteLine("Unknown command");
            cmd[0] = '\0';                      // Reset command string with null terminator.
        }
    }
}

Espera que esto sea útil.