¿Cuál puede ser la causa de una latencia excepcionalmente grande para la interrupción de recepción de UART?

Recibo datos en el UART, usando un atmega de 8 bits, generalmente alrededor de 5 bytes conectados, luego una pausa larga. El tiempo total para un byte (con bits de inicio y parada, no uso paridad) es de 160 us. Sin embargo, la interrupción de recepción se activa entre 60 y 100 segundos después del bit de parada, ¡y casi la mitad del tiempo no se activa en absoluto! (comprobado con alcance)

Hubo algunas interrupciones bastante largas, así que las culpé, pero después de deshabilitar todas las interrupciones además de la UART, la situación sigue siendo la misma. El UART interrumpe termina en menos de 10 us (típicamente 7 us) todo el tiempo. La intensidad de la señal está bien, es de 5 V, al igual que el voltaje de suministro.

Primero, después de darme cuenta de que se perdieron muchos bytes, pensé que la frecuencia de la señal era la causa, pero lo verifiqué dos veces: la velocidad en baudios es perfecta, la calidad de la señal se ve bien, el error en la velocidad en baudios es cercano a cero. Si ese fuera el problema, obtendría algunas interrupciones perdidas (porque se perdieron algunos bits), pero el resto debería suceder en el momento adecuado, ¿no? En mi caso, la interrupción llega muy tarde, si es que llega, e incluso cuando llega, a veces contiene basura. La señal en el pin está bien, puedo leerla y evaluarla correctamente en el osciloscopio.

Busqué la latencia típica de la interrupción UART, pero no pude encontrar nada. Sospecho firmemente que una variación salvaje entre 60 y 100 us no debería ser normal.

¿Puedes publicar tu código?
@OliGlaser: dudo que publicar 3000 líneas de código pueda ser de alguna ayuda. Si está interesado en la configuración, el reloj es de 10MHz UCSR0A=0x01; UCSR0B=0xDC; UCSR0C=0x06; UBRR0H=0x00; UBRR0L=0x09;y leo UDR0en la interrupción.
¡Sí, por favor no publiques el código! :-)
No estaba pensando en todo el código, solo en la interrupción y cualquier otra parte relevante. Es probable que el problema esté en el código y, de ser así, no se puede encontrar sin mirarlo. Simplificar todo (o hacer un programa de prueba) ayudaría a encontrar la causa.
¿Es esto una especie de tablero de evaluación? ¿Tiene un traductor de nivel lógico en línea entre su atmega y la PC? ¿En qué línea de código está configurando su punto de interrupción? ¿El compilador lo está optimizando inteligentemente? También podría ser un problema de velocidad en baudios. Intente ir en sentido contrario y enviar caracteres desde el atmega a la PC y ver si se reciben correctamente.
¿Puede pegar su código en su lugar y proporcionarnos un enlace? ( pastebin.com )
No estoy familiarizado con la parte, pero ¿se supone que el uart debe darte una interrupción para cada carácter, o tiene un fifo? el objetivo de tener un fifo es reducir la frecuencia de interrupción, pero para hacer eso, el uart tiene que diferir la interrupción usando alguna estrategia. Una forma es esperar hasta que se alcance una 'marca de agua alta'. Otra sería esperar hasta que se detecte un espacio adecuado entre los bytes recibidos. Una especie de olores como la última situación. Es curioso cómo "60 a 100" tiene una media muy cercana a medio byte, ¿no? ¿Por casualidad las brechas entre sus grupos de 5 son cercanas o más pequeñas a esto?
Debe reducir su código a solo un bucle principal que no hace nada y el ISR. Si aún tiene el problema, entonces puede obtener ayuda. De lo contrario (y esto es mi suposición), tiene algo más en juego que aún no está tomando en cuenta.

Respuestas (4)

Supongo que está utilizando el mismo proceso de depuración que haría en este caso: una de las primeras instrucciones en la rutina de interrupción enciende un LED y una de las últimas instrucciones en la rutina de interrupción apaga ese LED.

Luego usó un osciloscopio de doble trazo con una sonda conectada al pin apropiado para observar los bytes que ingresan al UART, y la otra sonda conectada al pin que activa el LED.

Supongo que su rutina de interrupción del controlador UART finaliza con la instrucción de retorno de la interrupción (en lugar de usar la instrucción de retorno de la subrutina utilizada por las instrucciones normales).

Hay 4 cosas que pueden causar una latencia prolongada entre el final del último byte de un mensaje y el inicio del controlador UART:

  • Algún byte anterior en el mensaje que activa el controlador UART y, de alguna manera, lleva mucho tiempo antes de que se vuelvan a habilitar las interrupciones. Algunas personas estructuran sus rutinas de interrupción para que después de que el controlador UART termine de almacenar un byte en el búfer apropiado, verifique muchas otras cosas antes de ejecutar la instrucción de retorno de la interrupción; aumenta la fluctuación y la latencia, pero a veces esas personas lo hacen. de todos modos porque mejora el rendimiento.

  • Alguna otra interrupción que tarda mucho tiempo en ejecutarse antes de volver a habilitar las interrupciones ejecutando la instrucción de retorno de interrupción. (Si puede hacer que todas y cada una de las interrupciones enciendan y apaguen algún otro LED, es bastante fácil ver en el osciloscopio si este es el problema o descartarlo).

  • Algún código de no interrupción "temporalmente" desactiva las interrupciones. (Esto aumenta la fluctuación y la latencia, pero la gente lo hace de todos modos, porque a menudo es la forma más fácil de evitar la corrupción de datos cuando tanto una interrupción como una tarea en segundo plano del bucle principal funcionan con la misma pieza de datos). (Si puede hacer que cada bit de código que hace esto encienda y apague algún otro LED, es bastante fácil ver en el osciloscopio si este es el problema o descartarlo).

  • Instrucciones que tardan mucho tiempo en ejecutarse.

La forma tradicional de descubrir exactamente qué está causando el problema es guardar la versión actual de su código (está usando TortoiseHg o algún otro sistema de control de versiones, ¿verdad?), y luego piratear y cortar deliberadamente una copia temporal de su código. código, eliminando y eliminando completamente el código unas pocas subrutinas a la vez, volviendo a probar después de cada ronda de eliminaciones, hasta que tenga un programa pequeño, pero técnicamente "completo" y ejecutable, que presenta el mismo problema.

Con demasiada frecuencia, las personas nos muestran fragmentos de un programa completo, las partes que esas personas creen que son relevantes, y no podemos ayudarlos porque una de las piezas que omitieron está causando el problema.

El proceso de reducir un programa a un pequeño caso de prueba es una habilidad muy útil porque, a menudo, al pasar por ese proceso, descubres rápidamente cuál es el problema real.

Una vez que tenga un programa tan pequeño, pero ejecutable, publíquelo aquí. Si descubre cuál es el problema durante ese proceso, díganoslo también, para que el resto de nosotros podamos evitar ese problema.

Generalmente, los UART con código que no es en tiempo real no son confiables. Es decir, si tiene subrutinas reentrantes y longitudes de ejecución de código indeterminadas, ¿puede garantizar que el firmware responderá a tiempo a una interrupción no enmascarable? Pero los deshabilitó todos, entonces, ¿la duración de IRQ cumple con los requisitos mínimos de tiempo en el peor de los casos?

Si esto se convierte en un problema difícil, es posible que deba usar datos de búfer con UART de 16 niveles de profundidad y detección de sobreejecución y subejecución de búfer y usar una estrategia de sondeo para que los datos detecten si los datos se reciben en el búfer. Mi primer diseño de UART en 1976 tenía este problema al principio. Luego pasé al diseño DMA con una respuesta encuestada menor que la longitud del búfer con FIFO casi completo que permitía una interrupción.

Usted dice que la tasa de baudios es correcta, pero la calculo como 62500bps (160us / 10 bits = 16us por bit). Esto parece un poco extraño para una conexión en serie, y una tasa de baudios incorrecta causaría problemas similares a los que ve. Es decir, el UART bien puede estar interrumpiendo lo que cree que es el bit de parada, pero que en realidad es parte del siguiente octeto. Naturalmente, los datos también aparecerán corruptos :)

Cuando recibe datos, ¿también puede leer los indicadores de error del UART? Eso le dirá por qué a la UART no le gustan los datos.

No estoy familiarizado con ATMega, pero algunos controladores usan interrupciones activadas por borde en lugar de interrupciones sensibles al nivel; en tales controladores, una rutina de servicio de interrupción debe verificar, antes de salir, si todas sus causas asociadas están "satisfechas"; si alguno no lo es, la rutina debe regresar y manejarlos en lugar de salir. Si una rutina de interrupción finaliza sin haber satisfecho simultáneamente todas sus causas, la interrupción puede terminar inhabilitada a menos que o hasta que algún otro código haga que se satisfagan todas las causas de la interrupción.

Por ejemplo, si un controlador usa la misma rutina de servicio de interrupción para manejar los datos entrantes y salientes de un UART, y la rutina de servicio comienza manejando los datos entrantes y luego procesando los datos salientes. Si llega un byte de datos entrantes justo antes de que la rutina de servicio de interrupción envíe un byte de datos salientes al UART, es posible que el controlador de interrupciones vea un nivel constante en la señal de interrupción del UART. Antes de que se cargue el byte saliente, querría interrumpir porque necesitaba datos salientes. En el momento en que se cargó, el UART querría interrumpir porque tenía datos entrantes. Sin embargo, desde el punto de vista del controlador de interrupciones, parecería que la rutina de servicio de interrupciones del UART fue simplemente ineficaz para resolver la condición de interrupción.