PSP1N微控制器的数据包流协议

问题陈述


在微控制器上开发其他设备时,我遇到了需要连续记录大量数据的情况。 该设备必须将包含时间戳和每秒100次的六个ADC测量值的数据集保存到SD卡中(我们将此数据集称为数据包),然后以图形形式在计算机上分析此数据。 同时还需要将数据写入SD卡,并通过UART传输数据。 由于存在大量数据,因此该数据应占用的空间尽可能少。 同时,有必要以某种方式分离这些数据包,因为数据以连续流的形式进入。 通过互联网翻遍没什么好,我没有找到,所以决定为它创建我自己的协议和库。


然后他出现了-分组流协议(PSP1N)


由于某些考虑,决定了以下内容:在协议中,将以N个字节组成的数据包传输数据,其中每个字节的第一位分配给数据包同步的起始位的符号(因此称为协议名称),其余的七个位分配给数据。 数据的顺序和大小是事先已知的。


一个例子:


我们为时间戳分配32位,为ADC测量分配60位(6个通道,每个通道10位),总计92位。 由于您可以在一个字节中传输7位有用数据,因此数据包将由14个字节组成(92位/ 7位= 13.14 ...向上舍入为14)。 14字节中有112位信息,其中92位有用数据的起始位属性占用了14位,还有6位未使用的位(我们可以在其中写入更多信息,但为简单起见,我们将不使用它们)。



第7位是起始位的符号(表示数据包的开始),其中6,5,4,3,2,1,0是数据位。


接收方还知道它接收到一个14字节的数据包,其中第一个字节的第一位是起始位(1)(在其余字节中,起始位是0),然后在数据位中依次是时间戳的32位,然后是ADC测量编号的10位。 1,然后是ADC#2的10位,依此类推...


同样,也会根据协议写入SD卡并从中进行读取。 总共,对于一天在SD卡上记录的信息,我们获得115.4 MB的信息(14字节x每秒100次测量x 3600秒x 24小时)。


这样的数据结构仍然很方便,因为将来我们可以从文件中的任何位置选择数据块,然后以图形的形式显示它们,从而不会将整个文件加载到RAM(可能达到数十GB)中。 通过加载必要的程序包,我们还可以方便地滚动显示这些图形。



是时候开始微控制器的软件实现了


我们用C ++编写用于微控制器的库。


为了方便起见,请创建一个类:


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); //     }; 

使用初始化方法,我认为一切都很清楚:


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

添加数据的方法更加复杂,这里,通过狡猾的按位操作,我们将数据放置在字节数组中。


 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]); //   } 

获取数据包字节数组的方法:


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

最后,还有一些辅助功能:


 //      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; } 

总结一下


完成工作的结果是,用于流数据PSP1N的紧凑协议和可从GitHub下载的现成库在这里诞生了。 在此存储库中,您将找到:


  1. ExampleColsolePSP1N / C#库的使用示例
  2. PSP1N_CPP /包含用于处理协议的PSP库及其在Arduino上使用的示例
  3. PSP1N_CSHARP / .NET的协议库

为了演示协议的操作,您可以将草图刷入Arduino,然后在计算机上的ExampleSerialPortRead示例中,通过COM端口从微控制器接收数据。 在那里,这些数据被解密并显示在控制台应用程序中。 我将再说一次用C#编写的用于接收方的库。


测试控制台:



更新(03/31/19):更改了编码和解码算法

Source: https://habr.com/ru/post/zh-CN441506/


All Articles