Interfaz de tres dispositivos UART al microcontrolador sin pérdida de datos

Tengo un microcontrolador TM4C123GXL Launchpad, que tiene 8 puertos seriales UART. Estoy usando Keil uVision5 con Tiva Ware para programar.

Quería conectar 3 dispositivos UART con el microcontrolador sin pérdida de bytes. La tasa de baudios se ha establecido en 115200 bits/s sin paridad, como se muestra en la figura a continuación. Todos los dispositivos enviaron tramas de datos periódicamente cada 5 ms.

Sistema de comunicación

El tiempo de TX y RX se calcula usando No_0f_bytes*10/BaudRate

He interconectado con éxito los dispositivos con el UART de µC. El problema en la comunicación es que estoy perdiendo los bytes. Si hago una comunicación con un solo dispositivo (Dispositivo 2) sigo perdiendo los bytes de todo el marco (20 bytes).

¿Se debe a la limitación FIFO de 16 bytes de Tm4c123 o algo más?

También he implementado la función µDMA de TM4C123. Pero aún así, se pierden bytes. ¿Qué puedo hacer para mejorar el sistema de transmisión y recepción sin pérdidas de tramas de datos?

Editar:

Aquí está la arquitectura de software:

Estoy usando Interrupción periódica del temporizador de 5 ms para recibir y transmitir la trama. Todos los marcos tienen los primeros 2 bytes de encabezado y un contador de bytes como último byte.

void Timer1IntHandler(void) //  Periodic Service Routine every 5ms
{

DisableIntrupts();

bool Correct=ReadJoystick(); //10 bytes  Device 1

if(Correct)
{
GenerateServoCardsFrame();

SendServo1Frame();   //20 bytes  Device 2
SendServo2Frame(); //17 bytes  Device 3
ReadServo1Frame();  //15 bytes Device 2
ReadServo2Frame();  //20 bytes Device 3

GenerateJoystickFrame();

SendJoystickFrame(); //10 bytes   Device 1

EnableIntrupts();
}

}

main()
{
SetupClock()  ;   //Setup 16 MHz Clock
SetupJoystick();  //Initalize uart1 port for Device1
SetupServoCard1(); //Initalize uart2 port for Device2
SetupServoCard2(); //Initalize uart3 port for Device3

InitalizePeriodicTimerHandler(5);   //Periodic Service Routine every 5ms  (Timer1IntHandler)

while(1)
{
}

}


bool ReadJoystick(void)
{
    int BytePos=0;
    int CountInvalid=0;
    int LoopoutTime=0;

    while(1)
    {
    if (ROM_UARTCharsAvail(UART1))                              
        { 
            ByteRX = ROM_UARTCharGetNonBlocking(UART1);            
            if (BytePos==0)
            {
                if (ByteRX== 0xA1)      //Header1 found                              
                {
                    KArray[0] = Bytebuf ;
                    BytePos ++;
                }
                else
                {
                    CountInvalid++;
                    if (CountInvalid>5) 
                        return 0;
                }           
            }            
            else if (BytePos ==1) 
            {           
                if (ByteRX == 0x66)      //Header2 found                                   
                {   
                    KArray[1] = ByteRX;
                    BytePos ++;
                }
                else                                                   
                    BytePos=0;                                                        
            }            
            else
            {
                KArray[BytePos++] = ByteRX;                
                if (BytePos==10)                                                      
                    return 1;      //Frame Recived                                                   
            }
        }
        else                                                          
        {
            SysCtlDelay(0.25*SysCtlClockGet()/3 / 1000);        //   0.25ms delay
            LoopoutTime++;            
            if (LoopoutTime > 10)                                      
                return 0;            
        }    
    }       

}

Según mi cálculo, 1 byte requiere 10/115200=0.08680ms y para leer un FIFO de 16 bytes requiere 1.38ms. La tabla de la figura muestra un tiempo Tx total de 4,08 ms y un tiempo Rx de 3,91 ms, lo que suma 8 ms. Esto es mayor que mi rutina de servicio de interrupción periódica.

¿Tengo que aumentar el tiempo de interrupción periódica?

¿Intentaste esto en una tasa de baudios más baja? Tasas de baudios más altas significan más errores propensos.
Los dispositivos no se pueden configurar para una tasa de baudios más baja. Solo es compatible la tasa de baudios de 115200 bits/seg.
que aparato es ese
Uno es un Joy Stick y los otros dos son una tarjeta controladora de servo. Su firmware está configurado por otra persona.
Sí, 16 bytes fifo es pequeño. Esa sería la razón.
Los bytes entrantes a través de un UART se pierden cuando el software del controlador UART no presta servicio al UART lo suficientemente rápido. No ha proporcionado suficientes detalles sobre su software para que nadie pueda proporcionar sugerencias detalladas. En general, sugeriría que necesita un diseño de software más receptivo o un reloj de CPU más rápido.
Tendría que leer cada byte tan rápido (aproximadamente cada 64 microsegundos para una velocidad de transmisión de 115200)
@MasoodSalik Escribí controladores UART para el chip 16C554 hace mucho tiempo. Estos tienen FIFO de 16 bytes y son dispositivos cuádruples, por lo que hay cuatro puertos. También utilicé velocidades de datos exactamente iguales a las que mencionaste. Y todo esto funcionaba bien con un micro muy antiguo y lento (el 8088 de 4,77 MHz). Nunca perdí un solo byte... nunca. Todo simplemente funciona. Si tiene un problema, entonces está enterrado en algún código que no escribió (una rutina de biblioteca sobre la que está haciendo suposiciones incorrectas) o bien es un error en lo que está escribiendo. Si sus dispositivos externos funcionan, no hay otra forma de verlo.
@MasoodSalik La ÚNICA forma en que un marco de 20 bytes es un problema es si es MUY PEREZOSO en su codificación y hace que su software se quede allí sin hacer nada mientras se llevan a cabo las comunicaciones. Más bien, su software debe estar activo en la eliminación de bytes del FIFO (y hay varias formas de abordar esto: comenzar a drenar a "medio lleno" es solo un ejemplo) mientras se llevan a cabo las comunicaciones. No sé nada sobre su TM4C123GXL, pero los puertos respaldados por FIFO casi siempre tienen diferentes "niveles de activación" que puede seleccionar, según corresponda (primero en entrar, medio lleno o lleno, por nombrar tres). Use uno.
@MasoodSalik Consulte la sección "14.3.8 Operación FIFO" de la hoja de datos de TM4C123GXL . Nota: (1) " Fuera del reinicio, ambos FIFO están deshabilitados ", y (2) " Los FIFO se pueden configurar individualmente para activar interrupciones en diferentes niveles. Las configuraciones disponibles incluyen ⅛, ¼, ½, ¾ y ⅞. " Para recibir , probablemente iría con ½ o ¾, dependiendo. Realmente necesita leer la hoja de datos y controlar la codificación de su receptor. Sus dispositivos externos y TI MCU están bien. ¿Está utilizando este sitio como una forma de evitar tener que leer y pensar por sí mismo?
He actualizado la pregunta con Software Architecture. Un servicio de interrupción periódica lee y transmite la trama. @jonk He revisado la hoja de datos de TM4C123GXL. Si usamos Interrupción para recibir los bytes, entonces podemos definir el nivel de Interrupción Fifo. También configuré el uC usando la interrupción, pero el problema era que se llamaba a la rutina de servicio de interrupción continua, aunque no había datos en un bus serie.
@MasoodSalik Tiene que funcionar si tiene el código escrito correctamente. No tengo ninguna pregunta sobre eso. He hecho este tipo de cosas muchas veces en el pasado. Estoy seguro de que el dispositivo ASIC (MCU) está diseñado correctamente para que los programadores puedan hacerlo correctamente. Este no es el tipo de error que ves que cometen los diseñadores de circuitos integrados. Probablemente esté pensando que la interrupción que está recibiendo está relacionada, cuando llega por una razón diferente con la que no está tratando adecuadamente. Las tablas cuestan solo US$13 y pedí una ayer. Será fácil de probar. Pero tendrás que esperar unas semanas.
He estado en eso durante 4 meses. He probado diferentes posibilidades pero he fallado. La hoja de datos dice que UART es similar a un 16C550. La página en.wikipedia.org/wiki/16550_UART dice: "El 16550 original tenía un error que impedía que se usara este FIFO. NS luego lanzó el 16550A que corrigió este problema. Sin embargo, no todos los fabricantes adoptaron esta nomenclatura y continuaron refiriéndose a el chip fijo como 16550 Según otra fuente, el problema FIFO se corrigió solo en el modelo 16550AF, con el modelo A todavía con errores (los modelos C y CF también están bien, según esta fuente).
@MasoodSalik Claro, tuve que lidiar con una variedad de errores de silicio en el pasado, incluidos los de algunos de los 16550. Pero nada de eso aplica aquí. Este es un dispositivo moderno y basado en décadas de experiencia previa. Estoy bastante seguro de que solo funciona como está documentado. Pero lo sabré muy pronto. También puede buscar la errata de TI en el dispositivo. Vuelva a verificar que (como tendré que hacerlo pronto). TI generalmente NO corrige errores de silicio, sino que los documenta. (Microchip en realidad los arregla, a menudo).
@MasoodSalik Comience aquí: microcontroladores Tiva C Series TM4C123x, revisiones de silicio 6 y 7, erratas de silicio . Busque "Uart" y vea lo que encuentra allí. Tampoco le hará daño buscar la errata de ARM: ARM Cortex-M4F Errata (v3) . Pero no es probable que sea de mucha ayuda, ya que se trata del procesador.
@MasoodSalik Si aún no está claro, debe investigar en cualquier dispositivo. En este caso, significa estudiar las erratas de MCU (de ARM), las erratas de ASIC (de TI) y las erratas de LaunchPad (también de TI, pero que se encuentran por separado en el documento de la pizarra). También debe estudiar cuidadosamente todos los detalles sobre la configuración del puerto, los temporizadores y casi todo lo que se encuentra en la hoja de datos de casi 1500 páginas. Llevas 4 meses con esto, dices. Hubiera pasado la primera semana (o dos) antes de escribir UNA LÍNEA de código, estudiando estos documentos cuidadosamente. ¿Hiciste eso?
Sin embargo, en este caso parecería que la mala programación es la culpable. Esta no es la forma de escribir ISR: hay demasiado código, la latencia de interrupción será horrible. Y luego errores obvios de encontrar en 5 segundos, como que la interrupción se apague permanentemente. También puedo garantizar que te faltan volatileerrores por todas partes. La forma de mainuso es de la era de los dinosaurios, lo que sugiere que se utiliza un mal compilador. Etcétera.
@jonk He revisado los documentos que enumeraste. Hay un error de silicio que no está relacionado conmigo. No he revisado mucho la hoja de datos desde que estoy usando la biblioteca de periféricos del controlador Tiva Ware, que maneja automáticamente la programación de nivel de registro. Y trabajo una vez a la semana. Lundin: He entendido el punto de no escribir una subrutina larga en ISR.
@MasoodSalik Me alegra escuchar una actualización. Gracias. Estoy seguro de que he estado usando MCU desde mucho antes de que usted naciera (unos 40 años). En ese tiempo, a menudo he escrito código para manejar múltiples canales FIFO de 16 bytes, tanto externos como internos a MCU. . Estos han incluido errores de silicio, a veces. En todos los casos, he podido hacer que funcionen perfectamente. Pero escribí mi propio código, hice mi propia investigación y trabajo, y usando esa información desarrollé el código apropiado. Si usas una biblioteca, entonces dependes del trabajo de otros y si hay algún problema deberías contactarlos, supongo.
Sí. Seguro. TivaWare™ es de Texas Instrument y está diseñado para simplificar y acelerar el proceso de desarrollo, por eso lo estoy usando. Había errores en mi arquitectura de software. Ahora he establecido una comunicación exitosa entre los 2 dispositivos. Pronto experimentará con múltiples dispositivos.
@MasoodSalik Es bueno escucharlo. El software diseñado para ayudar a las personas a "comenzar rápidamente" no suele ser el mejor software, sino algo creado para ayudar a los representantes de campo que trabajan con los clientes a mostrar rápidamente al cliente que el problema está en el software del cliente y no en el hardware del fabricante. A menudo no resuelve nada más que las necesidades de uso final más triviales. Nunca utilizo dicho software, excepto para cotejarme a mí mismo, sabiendo que tendrá un valor real muy limitado. Y sí, suele ser cuestión de bugs en lo que escribes. Esa ha sido mi experiencia.
@MasoodSalik Por lo general, divido el código de mi controlador en cuatro partes con dos búferes para mediar. Está el controlador del receptor que consta de una interfaz de alto nivel para el resto de mi código y una interfaz de bajo nivel para el hardware, con un búfer entre ellos. Y el mismo concepto, repetido, para el lado de transmisión. Los búferes desacoplan el uso de nivel superior del lado de soporte de hardware de nivel inferior. Puede agregar la administración del grupo de búfer, si quiere complicarse. O bien suministre buffers asignados estáticamente de forma permanente.

Respuestas (1)

El diseño de su software no es bueno y es probablemente la razón por la que se pierden los bytes entrantes.

Primero, no estoy seguro de si esto es un error o un error tipográfico. Deshabilita las interrupciones al principio Timer1IntHandler(), pero luego solo las vuelve a habilitar si Correctes cierto. ¿No desea volver a habilitar las interrupciones antes de regresar, independientemente del condicional? Parece extraño que las interrupciones puedan quedar deshabilitadas cuando la función regresa.

Parece que su código lee caracteres de UART1 solo dentro de la ReadJoystick()función. Y sospecho que UART1 no se lee mientras GenerateServoCardsFrame()se SendJoystickFrame()llaman todas esas funciones. ¿Cuánto tiempo tardan en ejecutarse esas funciones? ¿Esas funciones podrían tomar el tiempo suficiente para que el UART1 FIFO se llene y se desborde? Esto podría ser cuando se descartan los bytes entrantes.

Si estuviera diseñando este software, lo implementaría de manera completamente diferente a como lo ha hecho usted. Habilitaría la solicitud de interrupción de UART y crearía una rutina rápida de manejo de interrupciones de UART. Lo único que haría UART ISR es copiar bytes hacia/desde los registros UART TX/RX. Crearía dos búferes circulares (también conocidos como anillos) para contener los bytes. El UART ISR copiaría un byte recibido del registro UART RX al búfer circular RX. Y el UART ISR copiaría un byte para transmitir desde el búfer circular TX al registro UART TX. El UART ISR no intentaría interpretar el significado de ninguno de los bytes. Todo lo que hace es mover bytes entre los búferes de RAM y el periférico UART. Esto mantiene corto el UART ISR, lo que permite que el programa general responda mejor a otras interrupciones.

Luego, crearía una main()función con un bucle infinito y, dentro del bucle infinito, llamaría a una función llamada SerialReceive()para leer mensajes del búfer RX. SerialReceive()se implementaría como una máquina de estado. Si hay bytes disponibles en el búfer RX, procesará un número finito de bytes a través de la máquina de estado. La máquina de estado tendría estados para el encabezado del cuadro, el cuerpo y el tráiler similar a lo que ha hecho. SerialReceive()regresa inmediatamente cuando se completa un mensaje o no hay más bytes disponibles. Cuando un mensaje está incompleto porque no hay más bytes disponibles de inmediato, SerialReceive()no los esperará, sino que recordará el estado y el mensaje actuales para que pueda continuar con el mismo mensaje cuando se lo llame nuevamente desde main().

Si necesita hacer algo periódicamente, configure un temporizador como lo ha hecho, pero en lugar de hacer todo el trabajo dentro del ISR del temporizador, simplemente configure una bandera. El bucle infinito principal debe verificar repetidamente la bandera y hacer lo que sea apropiado cuando el temporizador ISR ha establecido la bandera. Hacer el trabajo desde el contexto de main()significa que el sistema puede responder a otras interrupciones mientras está haciendo el trabajo.

Mantener los ISR breves permite que el sistema en general responda mejor a otras solicitudes de interrupción. Si pasa demasiado tiempo en un ISR, como creo que está haciendo en su temporizador ISR, entonces el sistema no responderá.

Actualización: en su comentario, dice que esas funciones se repiten hasta que se completan las transmisiones y toman más de 7 milisegundos. Es tiempo suficiente para que lleguen 80 bytes al UART y su código no lee esos bytes durante este tiempo, por lo que, por supuesto, se perderán bytes.

Sus funciones de transmisión deben copiar bytes en el búfer TX y regresar sin esperar a que se transmita el mensaje completo. El UART ISR debe transmitir un byte cada invocación mientras que el búfer TX contiene bytes.

El búfer RX y TX debe ser más grande que cualquier mensaje. Por lo general, los búferes tienen un tamaño de potencia de dos porque eso hace que sea más fácil rodear los punteros del búfer hasta el principio. Así que hazlos de 256 bytes (o 128 o 64, pero ¿por qué no más grandes?).

Debe tener un conjunto independiente de búferes RX/TX para cada UART.

Cambiar el período de su temporizador periódico ISR no afectará el problema con su código original. Dentro de su ISR periódico, su código está gastando 7 milisegundos NO leyendo el UART. Su código perderá bytes independientemente del período del temporizador.

The Ending Brace fue un error tipográfico al escribir el pseudocódigo. Las funciones de transmisión de mi UART se realizan de tal manera que hasta que se envíen todos los bytes, permanecerá en el bucle. Entonces, para enviar 20 bytes, cuesta 1,74 ms. Toda la función en un bucle "if" toma 7.11 ms. ¿Tengo que aumentar el tiempo de ISR periódico? Implementé con éxito un búfer circular para almacenar los bytes recibidos de UART usando una interrupción. Y verificando la bandera en el ciclo while en main(). Funciona bien con un solo dispositivo. Pronto experimentará con 2 dispositivos. ¿Qué tamaño de búfer circular debo configurar? 2x bytes de datos?
¿Debería el tamaño del búfer circular ser el doble que el marco real?
@MasoodSalik, actualicé mi respuesta para abordar sus comentarios.
¿Existe algún concepto de que una vez que llamo a una función, la función se llama a sí misma después de un tiempo (calculado) hasta que se alcanza alguna condición; sin dejar el sistema inactivo. Quería implementarlo para la función de transmisión. Una vez que llene el Tx Fifo, la función vuelve a llamarse después de un tiempo y vuelve a llenar el FIFO hasta que se envía todo el marco.
@MasoodSalik, lo estás pensando mal. En lugar de calcular cuándo puede recargar, deje que la UART le informe cuándo puede recargar. Más específicamente, habilite la IRQ de UART adecuada para que se llame a la ISR de UART cuando se puedan agregar bytes a la FIFO. Honestamente, no creo que debas intentar usar el UART FIFO hasta que lo hagas funcionar sin el FIFO. Y le recomiendo encarecidamente que busque un código de ejemplo en línea. Mi respuesta es una descripción general y omite detalles importantes que lo harán tropezar.