¿Qué tan seguro es \ n \ r como stop bytes?

8

En mi comunicación UART, necesito saber el byte de inicio y el byte de detención del mensaje enviado. El byte de inicio es fácil, pero el byte de parada, no tanto. He implementado dos bytes de detención al final de mi mensaje, es decir, \ n y \ r (10 y 13 decimales). UART solo funciona en valores de bytes 0-255, entonces, ¿qué tan seguro es esto? Puedo imaginar, aunque con baja probabilidad, que mi mensaje podría contener los valores "10 y 13" uno tras otro cuando no son los bytes de detención.

¿Hay una mejor manera de implementar esto?

CK
fuente
77
Para enviar datos arbitrarios, debe utilizar paquetes o rellenar bytes. En su caso, la probabilidad de que el patrón aparezca en una ubicación determinada es 1/65536. Que llega a 1 si tiene un flujo de datos aleatorio lo suficientemente largo.
Oldfart
44
¿Puedes proporcionar contexto por favor? ¿Los bits de parada son parte de la comunicación UART pero detienen los bytes? Esto suena como un problema de software puro y depende de lo acordado por el remitente y el receptor.
Warren Hill
2
@MariusGulbrandsen si sus datos son verdaderamente arbitrarios y no son estrictamente de texto (piense en ASCII), entonces la terminación nula no funcionará; Tendrás que implementar un paquete.
RamblinRose
44
Por cierto: Esa práctica común es poner el retorno de carro antes del salto de línea: "\x0D\x0A".
Adrian McCarthy
3
@AdrianMcCarthy Creo que el punto de revertirlo es minimizar las probabilidades de que sea una secuencia válida. Dicho esto, dos finales de línea de Windows en una fila te darían \r\n\r\ncuál contiene la \n\rsecuencia en el medio ...
Mike Caron

Respuestas:

14

Hay diferentes formas de prevenir esto:

  • Asegúrese de no enviar nunca una combinación de 10/13 en sus mensajes regulares (solo como bytes de detención). Por ejemplo, para enviar 20 21 22 23 24 25:

20 21 22 23 24 25 10 13

  • Escape 10 y 13 (o todos los caracteres no ASCII con un carácter de escape, p. Ej., Para enviar 20 21 10 13 25 26 enviar: (ver comentario de / créditos para: DanW)

20 21 1b 10 1b 13 25 26

  • Definir un paquete al enviar mensajes. Por ejemplo, si desea enviar un mensaje 20 21 22 23 24 25 en lugar de agregar el número de bytes a enviar, el paquete es:

<nr_of_data_bytes> <datos>

Si sus mensajes tienen un máximo de 256 bytes, envíe:

06 20 21 22 23 24 25

Entonces sabes después de recibir 6 bytes de datos que es el final; no tienes que enviar un 10 13 después. Y puede enviar 10 13 dentro de un mensaje. Si sus mensajes pueden ser más largos, puede usar 2 bytes para el tamaño de los datos.

Actualización 1: otra forma de definir paquetes

Otra alternativa es enviar comandos que tienen una longitud específica y pueden tener muchas variaciones, p. Ej.

10 20 30 (Comando 10 que siempre tiene 2 bytes de datos)

11 30 40 50 (Comando 11 que siempre tiene 3 bytes de datos)

12 06 10 11 12 13 14 15 (Comando 12 + 1 byte para la cantidad de bytes de datos que siguen)

13 01 02 01 02 03 ... (Comando 13 + 2 bytes (01 02 para 256 + 2 = 258 bytes de datos que siguen)

14 80 90 10 13 (Comando 14 seguido de una cadena ASCII que termina con 10 13)

Actualización 2: Mala conexión / pérdida de bytes

Todo lo anterior solo funciona cuando la línea UART está enviando bytes correctamente. Si desea utilizar formas de envío más confiables, también hay muchas posibilidades. A continuación hay algunos:

  1. Envío de una suma de verificación dentro del paquete (consulte google para CRC: Cyclic Redundancy Check). Si el CRC está bien, el receptor sabe que el mensaje se ha enviado bien (con alta probabilidad).
  2. Si necesita reenviar un mensaje, entonces se debe utilizar un mecanismo de confirmación (ACK / respuesta) (por ejemplo, el remitente envía algo, el receptor recibe datos corruptos, envía un NACK (no reconocido), el remitente puede enviarlo de nuevo.
  3. Tiempo de espera: en caso de que el receptor no reciba un ACK o NACK a tiempo, es necesario reenviar un mensaje.

Tenga en cuenta que todo el mecanismo anterior puede ser simple o tan complicado como desee (o necesite). En caso de reenviar mensajes, también se necesita un mecanismo para identificar mensajes (por ejemplo, agregar un número de secuencia al paquete).

Michel Keijzers
fuente
1
"Asegúrese de no enviar nunca una combinación 10/13 en sus mensajes regulares (por lo tanto, solo como bytes de detención)". - usted no ha dicho cómo enviar datos, que no incluyen una combinación 10/13 - que necesita para escapar de ella. Por lo tanto, "20 10 13 23 10 13" podría enviarse como "20 1b 10 1b 13 23" con 1b como su personaje de escape.
Dan W
1
Tenga en cuenta que al usar un campo de longitud según lo propuesto, tendrá problemas cuando su enlace en serie sea malo y pierda un solo byte. Todo se desincronizará.
Jonas Schäfer
@DanW Si usa el primero o 2 bytes como número de bytes de datos, no importa si 10 o 13 son parte de esos datos ... Entonces 20 10 13 23 10 13 puede enviarse como 06 20 10 13 23 10 13 donde 06 es el número de bytes de datos que siguen.
Michel Keijzers
@MichelKeijzers: sí, pero esa es la segunda solución que mencionas. A su primera solución le falta una explicación de las secuencias de escape para evitar que se transmitan los bytes de detención.
Dan W
Ambos enfoques funcionan y se usan comúnmente, pero tienen diferentes ventajas y desventajas, que podría agregar si lo desea, aunque está más allá de lo que solicitó el OP.
Dan W
13

¿Qué tan seguro es \ n \ r como stop bytes?

Si envía, envíe datos arbitrarios -> probablemente no sea lo suficientemente seguro.

Una solución común es usar escape:

Definamos que los caracteres 0x02 (STX - inicio de trama) y 0x03 (ETX - final de trama) deben ser únicos dentro del flujo de datos transmitido. De esta manera, el inicio y el final de un mensaje se pueden detectar de forma segura.

Si uno de estos caracteres debe enviarse dentro del marco del mensaje, se reemplaza con el prefijo de un carácter de escape (ESC = 0x1b) y agregando 0x20 al carácter original.

Carácter original reemplazado por

0x02 -> 0x1b 0x22  
0x03 -> 0x1b 0x23  
0x1b -> 0x1b 0x3b  

El receptor invierte este proceso: cada vez que recibe un carácter de escape, este carácter se descarta y el siguiente carácter se resta 0x20.

Esto solo agrega un poco de sobrecarga de procesamiento, pero es 100% confiable (suponiendo que no ocurran errores de transmisión, lo que podría / debería verificar implementando adicionalmente un mecanismo de suma de verificación).

Rev1.0
fuente
1
Buena respuesta. El carácter de escape común utilizado para los protocolos ASCII fue '\x10'DLE (Data Link Escape). Algunas de las páginas de Wikipedia sugieren que DLE a menudo se usaba de manera opuesta: para decir que el siguiente byte era un carácter de control en lugar de un byte de datos. En mi experiencia, ese es generalmente el significado opuesto para un escape.
Adrian McCarthy
2
Una cosa a tener en cuenta aquí es que el tamaño del búfer de su peor caso se duplica. Si la memoria es muy limitada, esa podría no ser la mejor solución.
TechnoSam
1
@Rev ¿Cuál es la razón para agregar 0x20 al personaje original? ¿No funcionaría el esquema de escape sin eso también?
Nick Alexeev
1
@NickAlexeev: es más fácil / más rápido identificar los límites de trama reales si elimina cualquier otra ocurrencia de los caracteres reservados de la secuencia. De esa manera, puede separar la recepción de cuadros y el análisis de cuadros (incluido el no escape). Esto puede ser especialmente relevante si tiene un controlador muy lento sin FIFO y / o altas velocidades de datos. Por lo tanto, puede copiar los bytes entrantes (entre STX / ETX) en el búfer de trama a medida que llegan, marcar el fotograma como completo y realizar el procesamiento con menor prioridad.
Rev1.0
@TechnoSam: Buen punto.
Rev1.0
5

Ya sabes, ASCII ya tiene bytes para estas funciones.

  • 0x01: inicio del encabezado - inicio byte
  • 0x02: inicio de texto - encabezados finales, comenzar carga útil
  • 0x03: fin del texto - fin de la carga útil
  • 0x04: fin de transmisión - detener byte
  • 0x17: fin del bloque de transmisión - el mensaje continúa en el siguiente bloque

También tiene códigos para varios usos dentro de la carga útil.

  • 0x1b: escape (escapa del siguiente carácter; utilícelo en la carga útil para indicar que el siguiente carácter no es una de las estructuras que describen los códigos utilizados en su protocolo)
  • 0x1c, 0x1d, 0x1e, 0x1f: archivo, grupo, registro y separador de unidades, respectivamente - se utiliza como byte de inicio y parada simultáneo para partes de datos jerárquicos

Su protocolo debe especificar la granularidad más fina de ACK (0x06) y NAK (0x15), de modo que los datos negativos reconocidos puedan retransmitirse. Hasta esta granularidad más fina, es aconsejable tener un campo de longitud inmediatamente después de cualquier indicador de inicio (sin escape) y (como se explica en otras respuestas) es aconsejable seguir cualquier indicador de parada (sin escape) con un CRC.

Eric Towers
fuente
Enviaré datos arbitrarios, supongo que podría haber sido confuso usar "\ n \ r" en mi pregunta cuando no estoy enviando datos ASCII. Aunque me gusta esta respuesta, es muy informativo sobre el envío de ASCII a través de UART
CK
@MariusGulbrandsen: siempre que su protocolo establezca dónde se encuentra la carga útil y qué códigos se deben escapar en cada sección de carga útil, puede enviar cualquier cosa, no solo datos de texto.
Eric Towers el
4

UART no es a prueba de fallas por su propia naturaleza: aquí estamos hablando de la tecnología de la década de 1960.

La raíz del problema es que UART solo se sincroniza una vez cada 10 bits, lo que permite que pase un montón de galimatías entre esos períodos de sincronización. A diferencia de, por ejemplo, CAN, que muestrea cada bit individual varias veces.

Cualquier error de doble bit que ocurra dentro de los datos dañará una trama UART y pasará sin ser detectado. Los errores de bit en los bits de inicio / parada pueden o no detectarse en forma de errores de desbordamiento.

Por lo tanto, no importa si usa paquetes o datos sin procesar, siempre existe la probabilidad de que los cambios de bits causados ​​por EMI den como resultado datos inesperados.

Existen numerosas formas de "charlatanería tradicional UART" para mejorar la situación muy ligeramente. Puede agregar bytes de sincronización, bits de sincronización, paridad, bits de doble parada. Podría agregar sumas de verificación que cuenten la suma de todos los bytes (y luego invertirla, porque por qué no) o podría contar la cantidad de binarios como una suma de verificación. Todo esto es ampliamente utilizado, muy poco científico y con una alta probabilidad de errores faltantes. Pero esto fue lo que hizo la gente desde 1960 hasta 1990 y muchas cosas extrañas como estas viven en la actualidad.

La forma más profesional de lidiar con una transmisión segura a través de UART es tener una suma de verificación CRC de 16 bits al final del paquete. Todo lo demás no es muy seguro y tiene una alta probabilidad de faltar errores.

Luego, en el nivel de hardware, puede usar el diferencial RS-422 / RS-485 para mejorar drásticamente la robustez de la transmisión. Esto es imprescindible para una transmisión segura a largas distancias. El nivel TTL UART solo debe usarse para la comunicación a bordo. RS-232 no debe usarse para ningún otro propósito, sino compatibilidad con versiones anteriores.

En general, cuanto más cerca del hardware esté su mecanismo de detección de errores, más efectivo será. En términos de efectividad, las señales diferenciales son las que más agregan, seguidas de la verificación de errores de encuadre / desbordamiento, etc. CRC16 agrega algo, y luego la "charlatanería tradicional UART" agrega un poco.

Lundin
fuente
77
Este consejo es bastante tangencial: en realidad no ha abordado la pregunta formulada. En particular, sus soluciones propuestas pueden resolver otros problemas, pero no resuelven el problema básico de la pregunta en esta página , que es la confusión entre la estructura de los adiós y la carga útil. A lo sumo, su propuesta rechazaría datos válidos que incrustan un byte de trama debido a CRC o falla similar, sin forma de comunicarlo.
Chris Stratton
3
De hecho, esta respuesta lo empeora. El original tenía solo bytes de datos y bytes de parada. Esto agrega una tercera categoría, bytes CRC. Y como se presenta aquí, estos pueden tener cualquier valor, incluido {10,13}.
MSalters
1
@MSalters: El CRC puede ser hexadecimal codificado en ASCII para evitar este problema. Otro truco que he visto en RS485 es establecer el bit 7 en el byte de inicio / dirección.
Transistor
Re "CAN que muestrea cada bit individual varias veces". : El muestreo real del valor de bit es solo una vez por bit. ¿A qué te refieres aquí? ¿Algún tipo de comprobación de errores, como por parte del remitente? Sincronización de reloj?
Peter Mortensen el
La inversión de la suma de verificación se realizó de modo que sumar todo el bloque de datos resultaría en un cero, que es un poco más fácil de codificar y un poco más rápido de ejecutar. Además, CRC es mucho mejor de lo que parece, búsquelo en Wikipedia.
toolforger
0

... Puedo imaginar, aunque con baja probabilidad, que mi mensaje podría contener los valores "10 y 13" uno tras otro cuando no son los bytes de detención.

Se debe considerar una situación en la que una porción de datos es igual a la secuencia de terminación al diseñar el formato de un paquete de datos en serie. Otra cosa a considerar es que cualquier personaje puede corromperse o perderse durante la transmisión. Un carácter de inicio, un carácter de detención, un byte de carga útil de datos, una suma de verificación o un byte CRC, un byte de corrección de error directo no son inmunes a la corrupción. El mecanismo de trama debe poder detectar cuándo un paquete tiene datos corruptos.

Hay varias formas de abordar todo esto.

Estoy asumiendo que los paquetes están enmarcados solo con los bytes seriales. Las líneas de apretón de manos no se usan para enmarcar. Los retrasos de tiempo no se usan para enmarcar.

Enviar longitud del paquete

Envíe la longitud del paquete al principio, en lugar de [o además de] el carácter final al final.

Pros: la carga útil se envía en un formato binario eficiente.

Contras: necesita saber la longitud del paquete al comienzo de la transmisión.

Escapar de los personajes especiales

Escapar de los caracteres especiales al enviar los datos de carga. Esto ya se explicó en una respuesta anterior .

Pros: el remitente no necesita saber la longitud del paquete al comienzo de la transmisión.

Contras: Ligeramente menos eficiente, dependiendo de cuántos bytes de carga útil se deban escapar.

Datos de carga útil codificados de modo que no puedan contener caracteres de inicio y detención

La carga útil del paquete está codificada de modo que no puede contener los caracteres de inicio o detención. Por lo general, esto se hace enviando números como su representación ASCII o Hex-ASCII.

Pros: legible para humanos con programas terminales comunes. No hay necesidad de código para manejar el escape. No es necesario saber la longitud del paquete al comienzo de la transmisión.

Contras: menor eficiencia. Para un byte de datos de carga útil, se envían varios bytes.

Nick Alexeev
fuente