Protocolo de streaming de pacotes para microcontroladores PSP1N

Declaração do problema


Ao desenvolver o próximo dispositivo no microcontrolador, deparei-me com uma situação em que era necessário o registro contínuo de uma grande quantidade de dados. O dispositivo teve que salvar um conjunto de dados que consistia em um registro de data e hora e seis medições ADC de 100 vezes por segundo em um cartão SD (vamos chamar esse conjunto de dados de pacote) e depois analisar esses dados em um computador na forma de gráficos. Também foi necessário em paralelo com a gravação de dados em um cartão SD, transferindo-os via UART. Esses dados deveriam ocupar o mínimo de espaço possível, pois existem muitos dados. Ao mesmo tempo, era necessário separar de alguma forma esses pacotes, porque os dados estavam em um fluxo contínuo. Tendo vasculhado a Internet nada de bom, não encontrei, por isso foi decidido criar meu próprio protocolo e bibliotecas para ele.


E então ele apareceu - Protocolo de streaming de pacotes (PSP1N)


Como resultado de algumas considerações, foi decidido o seguinte: no protocolo, os dados serão transmitidos em pacotes constituídos por N bytes, em que o primeiro bit de cada byte é alocado ao sinal de bit inicial para sincronização de pacotes (daí o nome do protocolo), os sete bits restantes são alocados aos dados. A sequência e o tamanho dos dados são conhecidos antecipadamente.


Um exemplo:


Alocamos 32 bits para o registro de data e hora, 60 bits para as medições ADC (6 canais de 10 bits cada), totalizando 92 bits. Como você pode transferir 7 bits de dados úteis em um byte, o pacote será composto por 14 bytes (92 bits / 7 bits = 13,14 ... arredondado para 14). Existem 112 bits de informação em 14 bytes, dos quais 14 bits são ocupados pelo atributo start bit de 92 bits de dados úteis, existem 6 bits não utilizados (nos quais podemos escrever mais algumas informações, mas por uma questão de simplicidade, não as usaremos).



Onde o 7º bit é o sinal do bit inicial (indica o início do pacote), 6,5,4,3,2,1,0 são bits de dados.


O lado receptor também sabe que recebe um pacote de 14 bytes no qual o primeiro bit do primeiro byte é o bit inicial (1) (nos bytes restantes, os bits iniciais são 0); em seguida, nos bits de dados, na ordem 32 bits do carimbo de data / hora, 10 bits da medição ADC. 1, depois 10 bits do ADC # 2 e assim por diante ...


Da mesma forma, ocorre a gravação no cartão SD e a leitura do mesmo de acordo com o protocolo. No total, para um dia de gravação em um cartão SD, obtemos 115,4 MB de informações (14 bytes x 100 medições por segundo x 3600 seg x 24 horas).


Essa estrutura de dados ainda é conveniente, porque no futuro podemos selecionar blocos de dados de qualquer lugar do arquivo e exibi-los na forma de gráficos, não carregando o arquivo inteiro na RAM (que pode atingir várias dezenas de gigabytes). E também podemos implementar a rolagem conveniente desses gráficos carregando os pacotes necessários.



É hora de iniciar a implementação de software para o microcontrolador


Nós escrevemos a biblioteca para o microcontrolador em C ++.


Por conveniência, crie uma classe:


class PSP { public: /*   init: startBit -   0  1 *arrayByte -      sizeArrayByte -     */ void init(byte startBit, byte* arrayByte, byte sizeArrayByte); /*      pushData: sizeBit -     value -   (       ) */ void pushData(byte sizeBit, uint32_t value); /*       popData: return     . */ byte* popData(); protected: byte startBit; //  byte* arrayByte; //   byte sizeArrayByte; //   byte position = 0; //    bool clearFlag = false; //   void setStartBit(byte &value); //     void clearStartBit(byte &value); //     }; 

Com o método de inicialização, acho que tudo está claro:


 void PSP::init(byte startBit, byte* arrayByte, byte sizeArrayByte) { this->startBit = startBit; this->arrayByte = arrayByte; this->sizeArrayByte = sizeArrayByte; } 

O método de adicionar dados é mais complicado: aqui, por manipulações astutas, colocamos os dados em nossa matriz de bytes.


 void PSP::pushData(byte sizeBit, uint32_t value) { byte free; byte y; int remBit = 0; //      //   ,     if (!clearFlag) { for (byte i = 0; i < sizeArrayByte; i++) { arrayByte[i] = 0; } clearFlag = true; } //        7      while (remBit > -1) { free = 7 - (position) % 7; //        y = (position) / 7; //     //       remBit = sizeBit - free; //      if (remBit < 0) { arrayByte[y] |= value << ~remBit + 1; //   ,    position += sizeBit; //        remBit = -1; //      } //      else if (remBit > 0) { arrayByte[y] |= value >> remBit; //     ,    position += sizeBit - remBit; sizeBit = remBit; //        } //         else if (remBit == 0) { arrayByte[y] |= value; //    position += sizeBit; remBit = -1; //      } clearStartBit(arrayByte[y]); //   } setStartBit(arrayByte[0]); //   } 

Método para obter uma matriz de bytes de um pacote:


 byte* PSP::popData() { position = 0; //   clearFlag = false; //    return arrayByte; //   } 

E, finalmente, algumas funções auxiliares:


 //      void PSP::setStartBit(byte &value) { if (startBit == 0) value &= ~(1 << 7); else value |= 1 << 7; } //      void PSP::clearStartBit(byte &value) { if (startBit == 1) value &= ~(1 << 7); else value |= 1 << 7; } 

Resumir


Como resultado do trabalho realizado, o protocolo compacto para streaming de dados PSP1N e bibliotecas prontas que podem ser baixadas do GitHub aqui "nasceu". Neste repositório você encontrará:


  1. Exemplo de uso da biblioteca ExampleColsolePSP1N / C #
  2. PSP1N_CPP / contém a biblioteca PSP para trabalhar com o protocolo e um exemplo de uso no Arduino
  3. Biblioteca PSP1N_CSHARP / protocolo para .NET

Para demonstrar a operação do protocolo, você pode exibir o esboço no Arduino e, no exemplo ExampleSerialPortRead no computador, receber dados do microcontrolador através da porta COM. Lá, esses dados são descriptografados e exibidos em um aplicativo de console. Falarei sobre a biblioteca escrita em C # para o lado receptor outra vez.


TestingConsole:



UPDATE (31/03/19): Alterado o algoritmo de codificação e decodificação

Source: https://habr.com/ru/post/pt441506/


All Articles