問題の声明
マイクロコントローラで次のデバイスを開発するときに、大量のデータの継続的な登録が必要な状況に陥りました。 デバイスは、タイムスタンプと1秒あたり100回から6つのADCの測定値で構成されるデータセットをSDカードに保存し(このデータセットをパケットと呼びましょう)、コンピューター上でこのデータをグラフの形式で分析します。 また、データをSDカードに書き込み、UARTを介してデータを転送するのと並行して必要でした。 大量のデータがあるため、このデータはできるだけ少ないスペースを使用する必要があります。 同時に、データが連続ストリームで送信されるため、これらのパケットを何らかの方法で分離する必要がありました。 インターネットを介して大騒ぎしたので、私は良いものを見つけられなかったので、独自のプロトコルとライブラリを作成することにしました。
そして彼が登場-パケットストリーミングプロトコル(PSP1N)
いくつかの考慮事項の結果、以下が決定されました:プロトコルでは、データはNバイトで構成されるパケットで送信されます。各バイトの最初のビットはパケット同期の開始ビット記号に割り当てられ(したがってプロトコル名)、残りの7ビットはデータに割り当てられます。 データの順序とサイズは事前にわかっています。
例:
タイムスタンプに32ビット、ADC測定に60ビット(各10ビットの6チャネル)、合計92ビットを割り当てます。 1バイトで7ビットの有用なデータを転送できるため、パケットは14バイトで構成されます(92ビット/ 7ビット= 13.14 ... 14に切り上げ)。 14バイトに112ビットの情報があり、そのうち14ビットは92ビットの有用なデータの開始ビット属性で占められており、6つの未使用ビットがあります(さらに情報を書き込むことができますが、簡単にするために使用しません)。
7番目のビットが開始ビット(パケットの開始を示す)の符号である場合、6,5,4,3,2,1,0はデータビットです。
また、受信側は、最初のバイトの最初のビットがスタートビット(1)(残りのバイトではスタートビットが0)である14バイトのパケットを受信することを認識し、次にデータビットではタイムスタンプの32ビット、ADC測定値の10ビットです。 1、次に10ビットのADC#2など...
同様に、SDカードへの書き込みとプロトコルによる読み取りが行われます。 合計で、SDカードへの1日間の記録では、115.4 MBの情報(14バイトx 1秒あたり100回の測定x 3600秒x 24時間)が得られます。
将来的にはファイル内の任意の場所からデータブロックを選択してグラフ形式で表示できるため、このデータ構造は引き続き便利です。これにより、ファイル全体をRAM(数十ギガバイトに達する可能性があります)にロードしません。 また、必要なパッケージをロードすることにより、これらのグラフの便利なスクロールを実装できます。
マイクロコントローラのソフトウェア実装を開始する時が来ました
マイクロコントローラ用のライブラリを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からダウンロードできる既製のライブラリが 「誕生」しました。 このリポジトリには次のものがあります。
- ExampleColsolePSP1N / C#ライブラリの使用例
- PSP1N_CPP /プロトコルを操作するためのPSPライブラリとArduinoでの使用例が含まれています
- PSP1N_CSHARP / .NETのプロトコルライブラリ
プロトコルの動作を示すために、Arduinoにスケッチをフラッシュし、コンピューターのExampleSerialPortReadサンプルで、COMポートを介してマイクロコントローラーからデータを受信します。 そこで、このデータは復号化され、コンソールアプリケーションに表示されます。 C#で記述されたライブラリについては、受信側でもう一度説明します。
TestingConsole:
UPDATE(03/31/19):エンコードとデコードのアルゴリズムを変更