Pergunta

Então, estou tentando definir um protocolo de comunicação para comunicação serial, quero poder enviar números de 4 bytes para o dispositivo, mas não tenho certeza de como garantir que o dispositivo comece a captá-lo no byte certo.

Por exemplo, se eu quiser enviar

0x1234abcd 0xabcd3f56 ...

como posso garantir que o dispositivo não comece a ler no lugar errado e obtenha a primeira palavra como:

0xabcdabcd

Existe uma maneira inteligente de fazer isso?Pensei em usar um marcador para o início de uma mensagem, mas e se eu quiser enviar o número que escolhi como dado?

Foi útil?

Solução

Por que não enviar um start-of-message byte seguido por um length-of-data byte se você souber o tamanho dos dados?

Alternativamente, faça como outros protocolos binários e envie apenas tamanhos fixos de pacotes com cabeçalho fixo.Digamos que você enviará apenas 4 bytes e saberá que terá um ou mais bytes de cabeçalho antes do conteúdo real dos dados.

Editar: Acho que você está me entendendo mal.O que quero dizer é que o cliente deve sempre considerar os bytes como cabeçalho ou dados, não com base no valor, mas sim com base na posição no fluxo.Digamos que você esteja enviando quatro bytes de dados, então um byte seria o byte do cabeçalho.

+-+-+-+-+-+
|H|D|D|D|D|
+-+-+-+-+-+

O cliente seria então uma máquina de estado bastante básica, nos moldes de:

int state = READ_HEADER;
int nDataBytesRead = 0;
while (true) {
  byte read = readInput();
  if (state == READ_HEADER) {
    // process the byte as a header byte
    state = READ_DATA;
    nDataBytesRead = 0;
  } else {
    // Process the byte as incoming data
    ++nDataBytesRead;
    if (nDataBytesRead == 4) 
    {
      state = READ_HEADER;
    }
  }
} 

O problema dessa configuração é que o que determina se o byte é um byte de cabeçalho não é o conteúdo real de um byte, mas sim a posição no fluxo.Se você quiser ter um número variável de bytes de dados, adicione outro byte ao cabeçalho para indicar o número de bytes de dados que o seguem.Dessa forma, não importará se você está enviando o mesmo valor do cabeçalho no fluxo de dados, pois seu cliente nunca o interpretará como nada além de dados.

Outras dicas

cadeia de rede

Para esta aplicação, talvez o relativamente simples "cadeia de rede"o formato é adequado.

Por exemplo, o texto "Hello World!" codifica como:

12:hello world!,

A string vazia é codificada como os três caracteres:

0:,

que pode ser representado como a série de bytes

'0' ':' ','

A palavra 0x1234abcd em uma netstring (usando ordem de bytes da rede), seguido pela palavra 0xabcd3f56 em outra netstring, codifica como a série de bytes

'\n' '4' ':' 0x12 0x34 0xab 0xcd ',' '\n'
'\n' '4' ':' 0xab 0xcd 0x3f 0x56 ',' '\n'

(O caractere de nova linha ' ' antes e depois de cada netstring é opcional, mas facilita o teste e a depuração).

sincronização de quadros

como posso garantir que o dispositivo não comece a ler no local errado

A solução geral para o sincronização de quadros O problema é ler em um buffer temporário, esperando que tenhamos começado a ler no lugar certo.Posteriormente, executamos algumas verificações de consistência na mensagem no buffer.Se a mensagem falhar na verificação, algo deu errado, então jogamos fora os dados no buffer e comece de novo.(Se for uma mensagem importante, esperamos que o transmissor a reenvie).

Por exemplo, se o cabo serial estiver conectado na metade do primeiro NetString, o receptor verá a sequência de bytes:

0xab 0xcd ',' '\n' '\n'  '4' ':' 0xab 0xcd 0x3f 0x56 ',' '\n'

Como o receptor é inteligente o suficiente para esperar pelo ':' antes de esperar que o próximo byte seja um dado válido, o receptor é capaz de ignorar a primeira mensagem parcial e então receber a segunda mensagem corretamente.

Em alguns casos, você sabe antecipadamente qual será o(s) comprimento(s) válido(s) da mensagem;isso torna ainda mais fácil para o receptor detectar que começou a ler no local errado.

enviando marcador de início de mensagem como dados

Pensei em usar um marcador para o início de uma mensagem, mas e se eu quiser enviar o número que escolhi como dado?

Depois de enviar o cabeçalho netstring, o transmissor envia os dados brutos como estão - mesmo que pareçam com o marcador de início de mensagem.

No caso normal, o receptor já possui sincronização de quadros.O analisador de Netstring já leu o "comprimento" e o cabeçalho: ", de modo que o analisador de Netstring coloca os bytes de dados brutos diretamente no local correto no buffer - mesmo que esses dados se pareçam com o": "Cabeçalho Byte ou o "", byte de rodapé.

pseudo-código

// netstring parser for receiver
// WARNING: untested pseudocode
// 2012-06-23: David Cary releases this pseudocode as public domain.

const int max_message_length = 9;
char buffer[1 + max_message_length]; // do we need room for a trailing NULL ?
long int latest_commanded_speed = 0;
int data_bytes_read = 0;

int bytes_read = 0;
int state = WAITING_FOR_LENGTH;

reset_buffer()
    bytes_read = 0; // reset buffer index to start-of-buffer
    state = WAITING_FOR_LENGTH;

void check_for_incoming_byte()
    if( inWaiting() ) // Has a new byte has come into the UART?
        // If so, then deal with this new byte.
        if( NEW_VALID_MESSAGE == state )
            // oh dear. We had an unhandled valid message,
            // and now another byte has come in.
            reset_buffer();
        char newbyte = read_serial(1); // pull out 1 new byte.
        buffer[ bytes_read++ ] = newbyte; // and store it in the buffer.
        if( max_message_length < bytes_read )
            reset_buffer(); // reset: avoid buffer overflow

        switch state:
            WAITING_FOR_LENGTH:
                // FIXME: currently only handles messages of 4 data bytes
                if( '4' != newbyte )
                    reset_buffer(); // doesn't look like a valid header.
                else
                    // otherwise, it looks good -- move to next state
                    state = WAITING_FOR_COLON;
            WAITING_FOR_COLON:
                if( ':' != newbyte )
                    reset_buffer(); // doesn't look like a valid header.
                else
                    // otherwise, it looks good -- move to next state
                    state = WAITING_FOR_DATA;
                    data_bytes_read = 0;
            WAITING_FOR_DATA:
                // FIXME: currently only handles messages of 4 data bytes
                data_bytes_read++;
                if( 4 >= data_bytes_read )
                    state = WAITING_FOR_COMMA;
            WAITING_FOR_COMMA:
                if( ',' != newbyte )
                    reset_buffer(); // doesn't look like a valid message.
                else
                    // otherwise, it looks good -- move to next state
                    state = NEW_VALID_MESSAGE;

void handle_message()
    // FIXME: currently only handles messages of 4 data bytes
    long int temp = 0;
    temp = (temp << 8) | buffer[2];
    temp = (temp << 8) | buffer[3];
    temp = (temp << 8) | buffer[4];
    temp = (temp << 8) | buffer[5];
    reset_buffer();
    latest_commanded_speed = temp;
    print( "commanded speed has been set to: " & latest_commanded_speed );
}

void loop () # main loop, repeated forever
    # then check to see if a byte has arrived yet
    check_for_incoming_byte();
    if( NEW_VALID_MESSAGE == state ) handle_message();
    # While we're waiting for bytes to come in, do other main loop stuff.
    do_other_main_loop_stuff();

mais dicas

Ao definir um protocolo de comunicação serial, Acho que faz testes e depuração muito mais fácil se o protocolo sempre usar caracteres de texto ASCII legíveis por humanos, em vez de quaisquer valores binários arbitrários.

sincronização de quadros (novamente)

Pensei em usar um marcador para o início de uma mensagem, mas e se eu quiser enviar o número que escolhi como dado?

Já abordamos o caso em que o receptor já possui sincronização de quadros.O caso em que o receptor ainda não possui sincronização de quadros é bastante complicado.

A solução mais simples é o transmissor enviar uma série de bytes inofensivos (talvez novas linhas ou caracteres de espaço), o comprimento da mensagem máxima válida possível, como um preâmbulo logo antes de cada netstring.Não importa em que estado o receptor está quando o cabo serial está conectado, Esses bytes inofensivos eventualmente conduzem o receptor para o Estado "WAITING_FOR_LENGTH".E então, quando o tranmitter envia o cabeçalho do pacote (comprimento seguido de ":"), O receptor o reconhece corretamente como um cabeçalho de pacote e recuperou a sincronização de quadros.

(Não é realmente necessário que o transmissor envie esse preâmbulo antes todo pacote.Talvez o transmissor pudesse enviá-lo em 1 de 20 pacotes;então é garantido que o receptor recupere a sincronização de quadros em 20 pacotes (geralmente menos) após o cabo serial ser conectado).

outros protocolos

Outros sistemas usam uma soma de verificação Fletcher-32 simples ou algo mais complicado para detectar muitos tipos de erros que o formato netstring não consegue detectar ( a, b ), e pode sincronizar mesmo sem um preâmbulo.

Muitos protocolos usam um marcador especial de "início do pacote" e usam uma variedade de técnicas de "escape" para evitar o envio de um byte literal de "início do pacote" nos dados transmitidos, mesmo que os dados reais que queremos enviar tenham esse valor.( Recheio Consistente de Bytes Overhead, pouco recheio, cotado para impressão e outros tipos de codificação binária para texto, etc.).

Esses protocolos têm a vantagem de que o receptor pode ter certeza de que quando vemos o marcador de “início do pacote”, é o início real do pacote (e não algum byte de dados que coincidentemente tem o mesmo valor).Isso torna muito mais fácil lidar com a perda de sincronização - simplesmente descarte os bytes até o próximo marcador de "início do pacote".

Muitos outros formatos, incluindo o formato netstring, permitem que qualquer valor de byte possível seja transmitido como dados.Portanto, os receptores precisam ser mais espertos ao lidar com o byte de início do cabeçalho que poder ser um início de cabeçalho real, ou poder ser um byte de dados - mas pelo menos eles não precisam lidar com o "escapamento" ou com o buffer surpreendentemente grande necessário, na pior das hipóteses, para manter uma "mensagem de dados fixa de 64 bytes" após o escape.

Escolher uma abordagem não é realmente mais simples que a outra - apenas empurra a complexidade para outro lugar, como previsto por teoria do colchão d'água.

Você se importaria de dar uma olhada na discussão sobre as várias maneiras de lidar com o byte de início do cabeçalho, incluindo essas duas maneiras, no Wikilivro de programação serial, e editar esse livro para torná-lo melhor?

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top