Quão seguro é \ n \ r como bytes de parada?

8

Na minha comunicação UART, preciso conhecer o byte inicial e o byte final da mensagem enviada. O byte inicial é fácil, mas o byte final, nem tanto. Eu implementei dois bytes de parada no final da minha mensagem, ou seja, \ n e \ r (10 e 13 decimais). O UART funciona apenas com valores de bytes de 0 a 255, então, qual é a segurança contra falhas? Posso imaginar, embora com baixa probabilidade, que minha mensagem possa conter os valores "10 e 13" um após o outro, quando não forem os bytes de parada.

Existe uma maneira melhor de implementar isso?

CK
fonte
7
Para enviar dados arbitrários, você deve usar pacotes ou byte stuffing. No seu caso, a probabilidade do padrão aparecer em um determinado local é 1/65536. Que chega a 1 se você tiver um fluxo de dados aleatórios por tempo suficiente.
Oldfart
4
Você pode fornecer contexto, por favor. Os bits de parada fazem parte da comunicação UART, mas os bytes de parada? Parece um problema de software puro e depende do que foi acordado pelo remetente e pelo destinatário.
Warren Hill
2
@MariusGulbrandsen se seus dados forem verdadeiramente arbitrários e não estritamente texto (pense em ASCII), o encerramento nulo não funcionará; você terá que implementar um pacote.
RamblinRose
4
BTW: É prática comum é colocar o retorno de carro antes do avanço de linha: "\x0D\x0A".
Adrian McCarthy
3
@AdrianMcCarthy Acho que o objetivo de revertê-lo é minimizar as chances de ser uma sequência válida. Dito isto, duas quebras de linha do Windows em uma fileira lhe daria \r\n\r\nque contém a \n\rsequência no meio ...
Mike Caron

Respostas:

14

Existem diferentes maneiras de evitar isso:

  • Certifique-se de nunca enviar uma combinação 10/13 em suas mensagens regulares (apenas como bytes de parada). Por exemplo, para enviar 20 21 22 23 24 25:

20 21 22 23 24 25 10 13

  • Escape 10 e 13 (ou todos os caracteres não ASCII com um caractere de escape, por exemplo. Para enviar 20 21 10 13 25 26, envie: (consulte o comentário de / créditos para: DanW)

20 21 1b 10 1b 13 25 26

  • Defina um pacote ao enviar mensagens. Por exemplo, se você deseja enviar a mensagem 20 21 22 23 24 25 do que adicionar o número de bytes a serem enviados, o pacote é:

<nr_of_data_bytes> <dados>

Se suas mensagens tiverem no máximo 256 bytes, envie:

06 20 21 22 23 24 25

Então você sabe depois de receber 6 bytes de dados que é o fim; você não precisa enviar um 10 13 depois. E você pode enviar 10 13 dentro de uma mensagem. Se suas mensagens puderem ser maiores, use 2 bytes para o tamanho dos dados.

Atualização 1: Outra maneira de definir pacotes

Outra alternativa é enviar comandos que tenham um comprimento específico e possam ter muitas variações, por exemplo

10 20 30 (comando 10, que sempre possui 2 bytes de dados)

11 30 40 50 (comando 11, que sempre possui 3 bytes de dados)

12 06 10 11 12 13 14 15 (comando 12 + 1 byte para o número de bytes de dados a seguir)

13 01 02 01 02 03 ... (Comando 13 + 2 bytes (01 02 para 256 + 2 = 258 bytes de dados a seguir)

14 80 90 10 13 (comando 14 que é seguido por uma sequência ASCII que termina com 10 13)

Atualização 2: conexão ruim / perdas de bytes

Todas as opções acima funcionam apenas quando a linha UART está enviando bytes corretamente. Se você deseja usar formas mais confiáveis ​​de envio, também existem muitas possibilidades. Abaixo estão alguns:

  1. Enviando uma soma de verificação dentro do pacote (verifique no google se há CRC: verificação de redundância cíclica). Se o CRC estiver ok, o destinatário sabe que a mensagem foi enviada ok (com alta probabilidade).
  2. Se você precisar reenviar uma mensagem, será necessário usar um mecanismo de reconhecimento (ACK / resposta) (por exemplo, remetente envia algo, receptor recebe dados corrompidos, envia um NACK (não reconhecido), o remetente pode enviar novamente.
  3. Tempo limite: caso o destinatário não receba ACK ou NACK a tempo, é necessário reenviar uma mensagem.

Observe que todo o mecanismo acima pode ser simples ou tão complicado quanto você deseja (ou precisa). No caso de reenvio de mensagem, também é necessário um mecanismo para identificar as mensagens (por exemplo, adicionar um número de sequência ao pacote).

Michel Keijzers
fonte
1
"Certifique-se de nunca enviar uma combinação 10/13 em suas mensagens regulares (apenas como bytes de parada)." - você não disse como enviar dados que não incluem uma combinação 10/13 - você precisa escapar. Portanto, "20 10 13 23 10 13" pode ser enviado como "20 1b 10 1b 13 23" com 1b como seu caractere de escape.
21719 Dan W
1
Observe que, ao usar um campo de tamanho proposto, você terá problemas quando o link serial estiver ruim e perder um único byte. Tudo ficará fora de sincronia.
Jonas Schäfer
@ DanW Se você usar o primeiro ou 2 bytes como o número de bytes de dados, não importa se 10 ou 13 fazem parte desses dados ... Então 20 10 13 23 10 13 pode ser enviado como 06 20 10 13 23 10 13 em que 06 é o número de bytes de dados a seguir.
Michel Keijzers
@MichelKeijzers - sim, mas essa é a segunda solução que você mencionou. Sua primeira solução está faltando uma explicação das seqüências de escape para impedir que os bytes de parada sejam transmitidos.
21719 Dan W
Ambas as abordagens funcionam e são comumente usadas, mas têm vantagens e desvantagens diferentes, que você pode adicionar se desejado, embora esteja além do que o OP solicitou.
21719 Dan W
13

Quão seguro é \ n \ r como bytes de parada?

Se você enviar enviar dados arbitrários -> provavelmente não será suficientemente seguro.

Uma solução comum é usar escape:

Vamos definir que os caracteres 0x02 (STX - início do quadro) e 0x03 (ETX - final do quadro) precisam ser exclusivos no fluxo de dados transmitidos. Dessa forma, o início e o final de uma mensagem podem ser detectados com segurança.

Se um desses caracteres for enviado dentro do quadro da mensagem, ele será substituído pelo prefixo de um caractere de escape (ESC = 0x1b) e pela adição de 0x20 ao caractere original.

Caráter original substituído por

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

O receptor reverte esse processo: sempre que ele recebe um caractere de escape, esse caractere é descartado e o próximo caractere é subtraído por 0x20.

Isso adiciona apenas alguma sobrecarga de processamento, mas é 100% confiável (supondo que não ocorram erros de transmissão, que você pode / deve verificar implementando adicionalmente um mecanismo de soma de verificação).

Rev1.0
fonte
1
Boa resposta. O caractere de escape comum usado para protocolos ASCII era '\x10'DLE (Data Link Escape). Algumas páginas da Wikipedia sugerem que o DLE era frequentemente usado de maneira oposta: dizer que o próximo byte era um caractere de controle e não um byte de dados. Na minha experiência, esse geralmente é o significado oposto para uma fuga.
Adrian McCarthy
2
Uma coisa a ser observada aqui é que o tamanho do buffer de pior caso dobra. Se a memória estiver realmente fraca, talvez essa não seja a melhor solução.
TechnoSam 18/04/19
1
@Rev Qual é a justificativa para adicionar 0x20 ao personagem original? O esquema de fuga não funcionaria sem isso tão bem?
Nick Alexeev
1
@NickAlexeev: É mais fácil / rápido identificar os limites reais do quadro se você remover qualquer outra ocorrência dos caracteres reservados do fluxo. Dessa forma, você pode separar a recepção e a análise de quadros (incluindo os que não escapam). Isso pode ser especialmente relevante se você tiver um controlador muito lento sem FIFO e / ou altas taxas de dados. Assim, você pode simplesmente copiar os bytes recebidos (entre STX / ETX) no buffer do quadro à medida que eles chegam, marcar o quadro como completo e fazer o processamento com menor prioridade.
Rev1.0
@ TechnoSam: Bom ponto.
Rev1.0
5

Você sabe, o ASCII já possui bytes para essas funções.

  • 0x01: início do cabeçalho - iniciar byte
  • 0x02: início do texto - cabeçalhos finais, carga útil inicial
  • 0x03: fim do texto - carga útil final
  • 0x04: fim da transmissão - parar byte
  • 0x17: fim do bloco de transmissão - a mensagem continua no próximo bloco

Ele também possui códigos para vários usos dentro da carga útil.

  • 0x1b: escape (escape do próximo caractere - use na carga útil para indicar que o próximo caractere não é uma das estruturas que descrevem os códigos usados ​​no seu protocolo)
  • 0x1c, 0x1d, 0x1e, 0x1f: separador de arquivo, grupo, registro e unidade, respectivamente - usado como byte de parada e início simultâneo para partes de dados hierárquicos

Seu protocolo deve especificar a granularidade mais fina de ACK (0x06) e NAK (0x15), para que dados negativos reconhecidos possam ser retransmitidos. Até essa granularidade mais fina, é aconselhável ter um campo de comprimento imediatamente após qualquer indicador de início (sem escape) e (conforme explicado em outras respostas) é aconselhável seguir qualquer indicador de parada (sem escape) com um CRC.

Eric Towers
fonte
Enviarei dados arbitrários, acho que pode ter sido confuso usar "\ n \ r" na minha pergunta quando não estiver enviando dados ASCII. Mesmo assim, eu gosto desta resposta, é muito informativo sobre o envio de ASCII através de UART
CK
@MariusGulbrandsen: Desde que o seu protocolo estabeleça onde está a carga útil e quais códigos devem ser escapados em cada seção da carga, você pode enviar qualquer coisa, não apenas dados de texto.
Eric Towers
4

O UART não é à prova de falhas por sua própria natureza - estamos falando da tecnologia da década de 1960 aqui.

A raiz do problema é que o UART sincroniza apenas uma vez a cada 10 bits, permitindo que muita bobagem passe entre esses períodos de sincronização. Ao contrário, por exemplo, CAN, que mostra cada bit individual várias vezes.

Qualquer erro de dois bits que ocorra dentro dos dados corromperá um quadro UART e passará despercebido. Erros de bits nos bits de início / parada podem ou não ser detectados na forma de erros de saturação.

Portanto, não importa se você usa dados ou pacotes brutos, sempre há uma probabilidade de que inversões de bits causadas por EMI resultem em dados inesperados.

Existem inúmeras maneiras de "charlatanismo tradicional do UART" para melhorar a situação um pouco. Você pode adicionar bytes de sincronização, bits de sincronização, paridade e bits de parada dupla. Você pode adicionar somas de verificação que contam a soma de todos os bytes (e depois invertê-lo - porque por que não) ou pode contar o número de binários como soma de verificação. Tudo isso é amplamente utilizado, amplamente não científico e com uma alta probabilidade de erros ausentes. Mas foi o que as pessoas fizeram das décadas de 1960 a 1990 e muitas coisas estranhas como essas vidas hoje.

A maneira mais profissional de lidar com a transmissão segura pelo UART é ter uma soma de verificação CRC de 16 bits no final do pacote. Tudo o resto não é muito seguro e tem uma alta probabilidade de erros ausentes.

Em seguida, no nível do hardware, você pode usar o diferencial RS-422 / RS-485 para melhorar drasticamente a robustez da transmissão. Esta é uma obrigação para uma transmissão segura em distâncias maiores. O UART de nível TTL deve ser usado apenas para comunicação a bordo. O RS-232 não deve ser usado para nenhum outro propósito, mas para compatibilidade retroativa com coisas antigas.

No geral, quanto mais próximo do hardware estiver o mecanismo de detecção de erros, mais eficaz ele será. Em termos de eficácia, os sinais diferenciais são os que mais agregam, seguidos pela verificação de erros de enquadramento / saturação, etc. O CRC16 adiciona um pouco e, em seguida, o "charlatanismo tradicional do UART" adiciona um pouco.

Lundin
fonte
7
Esse conselho é bastante tangencial - você realmente não respondeu à pergunta. Em particular, suas soluções propostas podem resolver outros problemas, mas elas não solucionam o problema básico da pergunta nesta página , que é a confusão entre a definição de bytes e a carga útil. No máximo, sua proposta rejeitaria dados válidos incorporando um byte de enquadramento devido a CRC ou falha semelhante, sem nenhuma maneira de comunicar isso.
18719 Chris Stratton
3
De fato, essa resposta piora. O original tinha apenas bytes de dados e bytes de parada. Isso adiciona uma terceira categoria, bytes CRC. E, como apresentado aqui, esses podem assumir qualquer valor, incluindo {10,13}.
precisa saber é o seguinte
1
@MSalters: O CRC pode ser hexadecimal codificado em ASCII para evitar esse problema. Outro truque que eu já vi no RS485 é definir o bit 7 no byte de início / endereço.
Transistor #
Re "CAN, que mostra cada bit individual várias vezes". : A amostragem real do valor do bit é apenas uma vez por bit. A que você está se referindo aqui? Algum tipo de verificação de erro, como pelo remetente? Sincronização de relógio?
22419 Peter Mortensen
A inversão da soma de verificação foi feita para que a soma de todo o bloco de dados resultasse em zero, o que é um pouco mais fácil de codificar e um pouco mais rápido de executar. Além disso, o CRC é muito melhor do que você imagina, procure na Wikipedia.
toolforger
0

... Posso imaginar, embora com baixa probabilidade, que minha mensagem possa conter os valores "10 e 13" um após o outro quando não forem os bytes de parada.

Uma situação em que uma parte dos dados é igual à sequência de finalização deve ser considerada ao projetar o formato de um pacote de dados serial. Outra coisa a considerar é que qualquer personagem pode ser corrompido ou perdido durante a transmissão. Um caractere inicial, um caractere de parada, um byte de carga útil de dados, uma soma de verificação ou CRC, um byte de correção de erro de encaminhamento não está imune a corrupção. O mecanismo de enquadramento deve ser capaz de detectar quando um pacote possui dados corrompidos.

Existem várias maneiras de abordar tudo isso.

Estou assumindo que os pacotes são enquadrados apenas com os bytes seriais. Linhas de aperto de mão não são usadas para enquadrar. Atrasos de tempo não são usados ​​para enquadrar.

Enviar tamanho do pacote

Envie o comprimento do pacote no início, em vez de [ou além] do caractere final no final.

profissionais: a carga útil é enviada em um formato binário eficiente.

contras: Precisa saber o tamanho do pacote no início da transmissão.

Escapar dos caracteres especiais

Escape dos caracteres especiais ao enviar os dados da carga útil. Isso já foi explicado em uma resposta anterior .

prós: o remetente não precisa saber o tamanho do pacote no início da transmissão.

contras: um pouco menos eficiente, dependendo de quantos bytes de carga útil precisam ser escapados.

Dados de carga útil codificados de forma que não possam conter caracteres de início e parada

A carga útil do pacote é codificada de forma que não possa conter os caracteres de início ou parada. Geralmente, isso é feito enviando números como sua representação ASCII ou Hex-ASCII.

prós: legível por humanos com programas terminais comuns. Não há necessidade de código para lidar com escape. Não é necessário saber o tamanho do pacote no início da transmissão

contras: menor eficiência. Para um byte de dados de carga útil, vários bytes são enviados.

Nick Alexeev
fonte