Técnicas de delimitación/sincronización de protocolos en serie

Dado que la comunicación serial asíncrona está ampliamente extendida entre los dispositivos electrónicos incluso hoy en día, creo que muchos de nosotros nos hemos encontrado con esta pregunta de vez en cuando. Considere un dispositivo electrónico Dy una computadora PCconectada con línea serial (RS-232 o similar) y requerida para intercambiar información continuamente . Es decir PC, está enviando un cuadro de comando cada uno X msy Destá respondiendo con un informe de estado/cuadro de telemetría cada uno Y ms(el informe se puede enviar como respuesta a solicitudes o de forma independiente, realmente no importa aquí). Las tramas de comunicación pueden contener cualquier dato binario arbitrario . Suponiendo que las tramas de comunicación son paquetes de longitud fija.

El problema:

Como el protocolo es continuo, el lado receptor puede perder la sincronización o simplemente "unirse" en medio de un marco enviado en curso, por lo que simplemente no sabrá dónde está el inicio del marco (SOF). Si los datos tienen un significado diferente en función de su posición relativa a la SOF, los datos recibidos se corromperán, posiblemente para siempre.

La solución requerida

Esquema confiable de delimitación/sincronización para detectar la SOF con un tiempo de recuperación corto (es decir, no debería tomar más de, digamos, 1 cuadro para resincronizar).

Las técnicas existentes que conozco (y uso algunas) de:

1) Encabezado/suma de comprobación - SOF como valor de byte predefinido. Suma de comprobación al final del cuadro.

  • Ventajas: Sencillo.
  • Desventajas: No es confiable. Tiempo de recuperación desconocido.

2) Relleno de bytes:

  • Pros: recuperación rápida y confiable, se puede usar con cualquier hardware
  • Contras: no es adecuado para comunicaciones basadas en tramas de tamaño fijo

3) Marcado del noveno bit : anteponga cada byte con un bit adicional, mientras que SOF está marcado con 1y los bytes de datos están marcados con 0:

  • Pros: recuperación confiable y rápida
  • Contras: Requiere soporte de hardware. No es compatible directamente con la mayoría de PChardware y software.

4) Marcado de 8.º bit : una especie de emulación de lo anterior, mientras se usa el 8.º bit en lugar del 9.º, lo que deja solo 7 bits para cada palabra de datos.

  • Pros: Recuperación rápida y confiable, se puede usar con cualquier hardware.
  • Contras: Requiere un esquema de codificación/descodificación desde/hacia la representación convencional de 8 bits hacia/desde la representación de 7 bits. Algo derrochador.

5) Basado en el tiempo de espera : asume el SOF como el primer byte que viene después de un tiempo de inactividad definido.

  • Pros: sin sobrecarga de datos, simple.
  • Desventajas: No es tan fiable. No funcionará bien con sistemas de cronometraje deficientes como, por ejemplo, PC con Windows. Sobrecarga de rendimiento potencial.

Pregunta: ¿Cuáles son las otras técnicas/soluciones posibles que existen para abordar el problema? ¿Puede señalar los inconvenientes de la lista anterior que se pueden solucionar fácilmente y eliminarlos? ¿Cómo diseña (o diseñaría) el protocolo de sus sistemas?

4 es solo 1/8 más derrochador que 3.
@NickJohnson De acuerdo, pero solo sugiere que también agregue la cosa "Despilfarro" en (3) :)
No creo que haya explicado completamente sus suposiciones sobre los errores de comunicación. ¿Está asumiendo que la comunicación es 'perfecta', es decir, sin errores, o 'suficientemente perfecta' como para que el hardware de comunicación detecte e identifique todos los errores (por ejemplo, las comunicaciones usan paridad y son errores de un solo bit)?
Beceiver puede unirse en medio de un byte y puede interpretar el bit 8 como el bit 4, por ejemplo. Por lo tanto, el marcado del noveno bit no es fiable.
@gbulmer La suposición original es que el canal es perfecto y el problema puede surgir solo debido a una falta de sincronización inicial. Bajo estas suposiciones, la "confiabilidad" a la que me refería está relacionada solo con la resincronización. En la lista anterior, todas estas técnicas garantizan el 100% de éxito, excepto la primera. Pero probablemente el esquema de verificación de errores y el encuadre no deberían separarse de esta manera.
@TimothyBaldwin Supongamos que UART se ocupa del nivel físico.
@TimothyBaldwin, cualquier uart moderno, etc. sobremuestreará cada bit de cada byte y los bytes están rodeados por un bit de inicio, un bit de paridad y uno o más bits de parada. Por lo tanto, se generará un 'error de trama' si el formato no es correcto y se generará un 'error de paridad' si se encuentra cualquier error de bit 'único'. Todo esto se maneja en el hardware.

Respuestas (6)

¿Cómo diseña (o diseñaría) el protocolo de sus sistemas?

En mi experiencia, todos pasan mucho más tiempo depurando los sistemas de comunicación de lo que esperaban. Por lo tanto, sugiero enfáticamente que cada vez que necesite elegir un protocolo de comunicación, elija la opción que haga que el sistema sea más fácil de depurar, si es posible.

Los animo a que jueguen con el diseño de algunos protocolos personalizados, es divertido y muy educativo. Sin embargo, también le animo a que consulte los protocolos preexistentes. Si tuviera que comunicar datos de un lugar a otro, me esforzaría mucho en usar algún protocolo preexistente que alguien más ya haya pasado mucho tiempo depurando.

Escribir su propio protocolo de comunicación desde cero es muy probable que se enfrente a muchos de los mismos problemas comunes que todos tienen cuando escriben un nuevo protocolo.

Hay una docena de protocolos de sistemas integrados enumerados en Buenos protocolos basados ​​en RS232 para la comunicación integrada con la computadora . ¿Cuál es el más cercano a sus requisitos?

Incluso si alguna circunstancia hizo imposible usar exactamente cualquier protocolo preexistente, es más probable que haga que algo funcione más rápido al comenzar con algún protocolo que casi se ajuste a los requisitos y luego ajustarlo.

malas noticias

Como he dicho antes :

Desafortunadamente, es imposible que cualquier protocolo de comunicación tenga todas estas características agradables:

  • transparencia: la comunicación de datos es transparente y "limpia en 8 bits": (a) se puede transmitir cualquier archivo de datos posible, (b) las secuencias de bytes en el archivo siempre se manejan como datos y nunca se malinterpretan como otra cosa, y (c ) el destino recibe todo el archivo de datos sin errores, sin adiciones ni eliminaciones.
  • copia simple: la formación de paquetes es más fácil si simplemente copiamos a ciegas los datos de la fuente al campo de datos del paquete sin cambios.
  • inicio único: el símbolo de inicio de paquete es fácil de reconocer porque es un byte constante conocido que nunca aparece en ningún otro lugar de los encabezados, CRC de encabezado, carga útil de datos o CRC de datos.
  • 8 bits: solo utiliza bytes de 8 bits.

Estaría sorprendido y encantado si hubiera alguna forma de que un protocolo de comunicación tuviera todas estas características.

buenas noticias

¿Cuáles son las otras técnicas/soluciones posibles que existen para abordar el problema?

A menudo, hace que la depuración sea mucho, mucho más fácil si un humano en una terminal de texto puede reemplazar cualquiera de los dispositivos de comunicación. Esto requiere que el protocolo esté diseñado para ser relativamente independiente del tiempo (no se agota el tiempo de espera durante las pausas relativamente largas entre las pulsaciones de teclas escritas por un humano). Además, dichos protocolos están limitados a los tipos de bytes que son fáciles de escribir para un ser humano y luego de leer en la pantalla.

Algunos protocolos permiten enviar mensajes en modo "texto" o "binario" (y requieren que todos los mensajes binarios posibles tengan algún mensaje de texto "equivalente" que signifique lo mismo). Esto puede ayudar a que la depuración sea mucho más fácil.

Algunas personas parecen pensar que limitar un protocolo para usar solo los caracteres imprimibles es un "despilfarro", pero el ahorro en el tiempo de depuración a menudo hace que valga la pena.

Como ya mencionó, si permite que el campo de datos contenga cualquier byte arbitrario, incluidos los bytes de inicio y final del encabezado, cuando se enciende un receptor por primera vez, es probable que el receptor no se sincronice correctamente. lo que parece un byte de inicio de encabezado (SOH) en el campo de datos en medio de un paquete. Por lo general, el receptor obtendrá una suma de verificación no coincidente al final de ese pseudopaquete (que generalmente se encuentra a la mitad de un segundo paquete real). Es muy tentador simplemente descartar todo el pseudomensaje (incluida la primera mitad de ese segundo paquete) antes de buscar el siguiente SOH, con la consecuencia de que el receptor podría quedarse sin sincronizar durante muchos mensajes.

Como señaló alex.forencich, un enfoque mucho mejor es que el receptor descarte bytes al comienzo del búfer hasta el siguiente SOH. Esto permite que el receptor (después de posiblemente trabajar con varios bytes SOH en ese paquete de datos) se sincronice inmediatamente con el segundo paquete.

¿Puede señalar los inconvenientes de la lista anterior que se pueden solucionar fácilmente y eliminarlos?

Como señaló Nicholas Clark, el relleno de bytes de sobrecarga constante (COBS) tiene una sobrecarga fija que funciona bien con marcos de tamaño fijo.

Una técnica que a menudo se pasa por alto es un byte de marcador de fin de cuadro dedicado. Cuando el receptor se enciende en medio de una transmisión, un byte de marcador de fin de cuadro dedicado ayuda al receptor a sincronizar más rápido.

Cuando se enciende un receptor en medio de un paquete, y el campo de datos de un paquete contiene bytes que parecen ser un comienzo de paquete (el comienzo de un pseudopaquete), el transmisor puede insertar una serie de bytes de marcador de fin de trama después de ese paquete, de modo que dichos bytes de pseudo-inicio de paquete en el campo de datos no interfieran con la sincronización inmediata y la decodificación correcta del siguiente paquete, incluso cuando tenga mucha mala suerte y la suma de verificación de ese pseudo-paquete parece correcto.

Buena suerte.

Esta respuesta vale la pena reconsiderar la respuesta previamente aceptada (lo siento, @DaveTweed), y el artículo vinculado es sin duda una lectura obligada sobre el tema. Gracias por tomarte el tiempo y escribirlo.
es bueno que señale COBS, así que no tengo que escribir una respuesta :-)

Los esquemas de relleno de bytes me han funcionado muy bien a lo largo de los años. Son agradables porque son fáciles de implementar en software o en hardware, puede usar un cable USB a UART estándar para enviar paquetes de datos y tiene la garantía de obtener un encuadre de buena calidad sin tener que preocuparse por tiempos de espera, intercambio en caliente o cualquier otra cosa por el estilo.

Recomendaría un método de relleno de bytes combinado con un byte de longitud (módulo de longitud de paquete 256) y un CRC de nivel de paquete, y luego usaría UART con un bit de paridad. El byte de longitud garantiza la detección de bytes descartados, lo que funciona bien con el bit de paridad (porque la mayoría de los UART eliminarán cualquier byte que falle en la paridad). Luego, el CRC a nivel de paquete le brinda seguridad adicional.

En cuanto a la sobrecarga del relleno de bytes, ¿ha mirado el protocolo COBS? Es una forma genial de rellenar bytes con una sobrecarga fija de 1 byte por cada 254 transmitidos (más su estructura, CRC, LEN, etc.).

https://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing

Esta es una excelente manera de evitar que el relleno de bytes explote en el doble de datos en el peor de los casos. He usado esquemas similares pero más específicos de la aplicación, pero es genial ver esto descrito de una manera estándar. Voy a usar COBS de ahora en adelante...
Gracias de mi parte también por señalar COBS, un pequeño algoritmo muy bueno.

Su opción n. ° 1, SOH más suma de verificación, ES confiable y se recupera en el siguiente marco no dañado.

Supongo que ya conoce la longitud de un mensaje o que la longitud está codificada en los bytes inmediatamente después de SOH. Los bytes de verificación aparecen al final del mensaje. También necesita un búfer del lado de recepción para los datos que sea al menos tan largo como su mensaje más largo.

Cada vez que ve un byte SOH en la cabecera del búfer, es potencialmente el comienzo de un mensaje. Escanea a través del búfer para calcular el valor de verificación para ese mensaje y ver si coincide con los bytes de verificación en el búfer. Si es así, ya está; de lo contrario, descarta los datos del búfer hasta que llegue al siguiente byte SOH.

Tenga en cuenta que si un mensaje TIENE realmente errores de datos, este algoritmo lo descartará, pero probablemente lo haría de todos modos. Si su algoritmo de verificación incluye la corrección de errores de reenvío, puede verificar la alineación de cada mensaje potencial en busca de errores corregibles.

Si los mensajes tienen una longitud fija, puede prescindir del byte SOH por completo; simplemente pruebe CADA posición de inicio posible para obtener un valor de verificación válido.

También puede prescindir del algoritmo de verificación y mantener solo el byte SOH, pero esto hace que el algoritmo sea menos determinista. La idea es que para alineaciones de mensajes válidas, el SOH siempre aparecerá al comienzo de un mensaje. Si tiene una alineación incorrecta, es poco probable que el siguiente byte en el flujo de datos sea otro SOH (depende de la frecuencia con la que aparezca SOH en los datos del mensaje). Puede seleccionar los bytes SOH válidos solo sobre esta base. (Así es básicamente cómo funciona el marco en los servicios de telecomunicaciones síncronos como T1 y E1).

¿Supongo que la confiabilidad es algo probabilística? Dependiendo de la fuerza del código de verificación/corrección de errores, podemos encontrar tramas que parecen correctas en un flujo de bytes aleatorio/arbitrario.
Claro, eso es posible. Pero en la práctica, es relativamente fácil elegir un algoritmo de verificación que sea lo suficientemente fuerte.
Si tiene una tasa de errores de datos distinta de cero, siempre hay una posibilidad distinta de cero de que acepte un mensaje no válido de todos modos.
@NickJohnson Suponiendo un canal perfectamente limpio, todavía habrá (teóricamente) discrepancias con este enfoque. Por supuesto, su probabilidad puede ser despreciable.
Bueno, la pregunta parece demasiado abierta para tener una sola respuesta correcta, así que solo marcaré esto como aceptado como el primero para señalar lo mismo que los demás señalan.
Sé que ya sabe esto, y ya lo mencionó de pasada, pero la versión en la que no almacena en búfer un mensaje completo, o simplemente es perezoso acerca de cómo decodificar, es menos confiable. Si vuelve a sincronizar en el siguiente byte de SOH después de la suma de verificación no coincidente, en lugar del siguiente byte de SOH después del SOH "falso", tiene muchas posibilidades de descartar el inicio del mensaje real y quedarse sin sincronizar para muchos mensajes o, en el peor de los casos, para siempre.
@EugeneSh. Sí. Pero muéstrame un canal perfectamente confiable en cualquier lugar y me sorprenderé mucho.

Una opción que no se menciona pero que se usa ampliamente (especialmente en Internet) es la codificación de texto/ASCII (en realidad, la mayoría de las implementaciones modernas asumen UTF-8). En mi experiencia, los chicos de hardware odian hacer esto, pero la gente de software tiende a preferir esto sobre casi cualquier otra cosa (principalmente viene por la tradición de Unix de hacer que todo esté basado en texto).

La ventaja de la codificación de texto es que puede usar caracteres no imprimibles para enmarcar. Por ejemplo, lo más simple sería usar algo como 0x00para indicar el inicio del cuadro y 0xffel final del cuadro.

He visto dos formas principales en que los datos se codifican como texto:

  1. Cuando se le pide a un tipo de hardware/ensamblaje que haga esto, lo más probable es que se implemente como codificación hexadecimal. Esto es simplemente convertir los bytes a sus valores hexadecimales en ASCII. La sobrecarga es grande. Básicamente, transmitirá dos bytes por cada byte de datos real.

  2. Cuando se le pide a un tipo de software que haga esto, probablemente se implementará como codificación base64. Esta es la codificación de facto de Internet. Se utiliza para todo, desde archivos adjuntos MIME de correo electrónico hasta codificación de datos de URL. Los gastos generales son exactamente del 33%. Mucho mejor que la simple codificación hexadecimal.

Alternativamente, puede abandonar por completo los datos binarios y transmitir texto. En este caso, la técnica más común es delimitar los datos con nueva línea (ya sea solo "\n"o "\r\n"). Los comandos NMEA (GPS), Modem AT y los sensores Adventech ADAM son algunos de los ejemplos más comunes de esto.

Todos estos protocolos/encuadres basados ​​en texto tienen los siguientes pros y contras:

Pro:

  • Fácil de depurar
  • Fácil de implementar en un lenguaje de secuencias de comandos
  • El hardware se puede probar simplemente usando Hyperterminal/minicom
  • Fácil de implementar en el hardware (a menos que sea un micro realmente pequeño como un PIC)
  • Puede ser un marco de tamaño fijo o un tamaño variable.
  • Encuadre predecible y rápido tiempo de recuperación de sincronización (se recupera al final del cuadro actual)

Estafa:

  • Sobrecarga muy grande en comparación con la transmisión binaria pura (por otra parte, la E/S de texto también puede "comprimir" números como enviar un byte "0"(0x30) en lugar de cuatro bytes 0x00000000)
  • No tan limpio para implementar en micros muy pequeños como el PIC (a menos que su biblioteca incluya una sprintf()función)

Personalmente, para mí, los pros superan con creces a los contras. La facilidad de depuración por sí sola cuenta como 5 puntos (por lo que ese único punto por sí solo ya supera ambas desventajas).


Luego, existen soluciones no tan cuidadosamente pensadas que a menudo provienen de los desarrolladores de software: envíe datos codificados sin pensar en enmarcar.

Tuve que interactuar con hardware que enviaba XML sin procesar en el pasado. El XML era todo el marco que había. Afortunadamente, es bastante fácil descifrar los límites de los marcos por medio de las <xml></xml>etiquetas. La gran desventaja para mí es que usa más de un byte para enmarcar. Además, es posible que el encuadre en sí no se corrija, ya que la etiqueta puede contener atributos: <tag foo="bar"></tag>por lo que tendría que almacenar en el búfer en el peor de los casos para encontrar el inicio del marco.

Recientemente, he visto a personas comenzar a enviar JSON desde puertos serie. Con JSON, el encuadre es, en el mejor de los casos, una suposición. Solo tiene el carácter "{"(o "[") para detectar el marco, pero también están contenidos en los datos. Entonces terminas necesitando un analizador de descenso recursivo (o al menos un contador de llaves) para descubrir el marco. Al menos es trivial saber si el marco actual finaliza prematuramente: "}{"o "]["son ilegales en JSON y, por lo tanto, indican que el marco anterior ha finalizado y ha comenzado uno nuevo.

Para codificaciones de texto, también existe base85 , que solo tiene un 25 % de sobrecarga en lugar de un 33 %.
Lo consideraría un subconjunto/variación del cuarto método.
@EugeneSh.: Técnicamente es un subconjunto de bytestuffing. Por otra parte, dado que lo considera un subconjunto de marcado de bits, puede comprender por qué esta ambigüedad lo convierte en una categoría por derecho propio. Además, no puede considerar la mayoría de las implementaciones de codificación de texto como un subconjunto de marcado de bits porque los bits de marcado nunca se usan (por ejemplo, generalmente uso <y >como delimitadores y creo que el correo electrónico usa saltos de línea. Nota: sí, el correo electrónico es un formato enmarcado correctamente que se puede transmitir a través de RS232. Un amigo mío solía ejecutar un servidor de distribución de correo para su casa usando RS232)

Lo que usted describe como "marcado de bit X" se puede generalizar a otros códigos que tienen la propiedad de expandir los datos en una fracción constante, dejando algunas palabras de código libres para usar como delimitadores. A menudo, estos códigos también brindan otros beneficios; Los CD usan ocho a catorce modulaciones , lo que garantiza una longitud máxima de ejecución de 0 bits entre cada 1. Otros ejemplos incluyen códigos de bloque de corrección de errores de reenvío , que también usan bits adicionales para codificar información de detección y corrección de errores.

Otro sistema que no ha mencionado es el uso de información fuera de banda, como una línea de selección de chip, para delimitar transacciones o paquetes.

Los códigos de corrección de errores están un poco al margen de la cuestión. Deben agregarse a cualquiera de estos esquemas de todos modos. Supongo que la "información fuera de banda" a la que se refiere es lo mismo que "control de flujo de hardware".
@EugeneSh. -- En realidad, el uso de bits de verificación de errores para el encuadre es perfectamente válido, aunque computacionalmente costoso en el lado de la recepción. Simplemente haga el cálculo de error para cada alineación de datos posible, y la que tenga éxito es una alineación válida en un marco no dañado. Por supuesto, si el marco está dañado, no lo encontrará.
@DaveTweed Bueno, es más o menos lo que quise decir con la primera técnica. ¿O te estoy malinterpretando?
No, no estás malinterpretando; Eso es de lo que estaba hablando. Sin embargo, su "estafa" es incorrecta: ES confiable y también puede hacerse robusto con respecto a los errores de transmisión reales.
@DaveTweed ¿Qué pasa con el tiempo de recuperación? ¿Tiene algún ejemplo de cómo se puede hacer robusto?
Ver mi respuesta separada.
@EugeneSh. No creo que sean irrelevantes, porque puede combinar el encuadre y la corrección de errores (o la codificación de longitud limitada) en un solo paso. Si va a introducir una sobrecarga para enmarcar, también puede darle un buen uso a esa sobrecarga.

Otra opción es lo que se conoce como codificación de líneas . La codificación de línea le da a la señal ciertas características eléctricas que la hacen más fácil de transmitir (CC balanceada y garantías de longitud máxima de recorrido) y admiten caracteres de control para tramas y sincronización de reloj. Los códigos de línea se utilizan en todos los protocolos seriales modernos de alta velocidad: 10M, 100M, 1G y 10G Ethernet, serial ATA, FireWire, USB 3, PCIe, etc. Los códigos de línea comunes son 8b/10b , 64b/66b y 128b/130b. También hay códigos de línea más simples que no brindan información de trama, solo balance de CC y sincronización de reloj. Ejemplos de estos son Machester y NRZ. Probablemente quieras usar 8b/10b si quieres sincronizar rápidamente; los otros códigos de línea no están diseñados para sincronizarse rápidamente. El uso de un código de línea como el que ofrece el anterior requerirá el uso de hardware personalizado para transmitir y recibir.

En cuanto a su opción 5, se supone que la serie RS232 estándar admite el envío y la recepción de pausas donde la línea está inactiva durante un par de bytes. Sin embargo, es posible que esto no sea compatible con todos los sistemas.

Generalmente, el método de encuadre más simple y confiable es su opción 1, en combinación con un campo de longitud y una rutina de suma de verificación o CRC simple. La rutina de decodificación es simple: descarte bytes hasta que obtenga un byte de inicio, lea el campo de longitud, espere el marco completo, verifique la suma de verificación, conserve si está bien. Si la suma de verificación es incorrecta, comience a descartar bytes del búfer hasta que obtenga un byte de inicio y repita. El problema principal con esta técnica es encontrar un byte de inicio de cuadro que en realidad no sea un byte de inicio de cuadro. Para aliviar este problema, una técnica es escapar bytes con el mismo valor que el byte de inicio de trama con otro carácter de control y luego cambiar el byte escapado para que tenga un valor diferente. En este caso, también tendrás que hacer lo mismo con el nuevo byte de control.

Esta es la misma que la respuesta de Nick Johnson.