背景故事#
大学時代はずっと遊んでいて、卒業した時にはほとんど何もできなかったが、大佬のこのライブラリが私を救ってくれた。
SCPI は文字列解析プロトコルの一種です。仕事を始めたばかりの頃、ライトを点けてシリアルポートを開いたら、SCPI プロトコルを実装するように言われました。その時はまだ未熟で、このプロトコルライブラリを立ち上げるのに 2 週間かかりました。
このライブラリの内容は本当に豊富で、非常に動的なポインタもなく、基本的にはバインディング性質の「静的」ポインタばかりなので、C 言語があまり得意でない私でも徐々に理解できるようになりました。
約 2 ヶ月間ゆっくり使っていくうちに、その大部分の内容を閲覧し、C 言語も正式に入門しました。
そして、後輩の新卒にもまずこのライブラリを見せるようにしています。
ソースコードのアドレスはこちらです。
j123b567/scpi-parser: Open Source SCPI device library (github.com)
このコードライブラリの優位性#
[!NOTE]
- これは純粋なプロトコル解析ライブラリで、ハードウェアとは強く関連しておらず、関心を持つべきポートはデータの流入と流出だけです。非常に迅速に使用を開始できます(たとえ CodeBlocks や DEV-C++ のような学習環境でも)。
- これはビジネス機能を十分に果たすことができるライブラリで、SCPI プロトコル解析に必要なすべての機能やメカニズムを備えており、学習後の応用シーンは非常に広く、無駄に学ぶことはありません。
- これは堅牢なライブラリで、現在のところこのライブラリ内で致命的なバグは発見していません。反例として、最近移植と修正を期待している freemodbus ライブラリには、致命的なバグや大小の設計欠陥が存在します。
- これは比較的シンプルなライブラリで、rtos カーネル、ネットワークライブラリ、ファイルシステムライブラリなどに比べて、非常に理解しやすいと言えます。時間をかければ、オブジェクト指向、マクロ翻訳、マクロ条件コンパイル、コールバックバインディング、文字処理など、C 言語の一般的な構文や設計を学ぶことができます。
何を見るべきか#
C 言語の入門者向けに書かれているため、このライブラリの構造が非常にシンプルであっても、紹介が必要です。ライブラリ全体は以下の 2 つのフォルダのみです。
-
examples
にはいくつかのサンプルが含まれており、私たちがライブラリを使う際に最初に見るべきものです。 -
libscpi
にはすべてのライブラリソースコードがあります。このフォルダ内にはtest
フォルダがあり、メインエントリを持つ単体テストが含まれています。実際のプロジェクトではlibscpi/test
フォルダのソースコードを含める必要はありません。
examples
内では、以下の 2 つのフォルダを重点的に確認することをお勧めします。
examples/common には例程がサポートする命令表と、それに対応する命令のコールバックが含まれており、ライブラリ API の使い方を学ぶことができます。
examples/test-parser には最もシンプルな例程のメインエントリと、SCPI_Write ()、SCPI_Error () などのインターフェース関数の定義、少量の命令のコールバックが含まれており、命令表の中でバインディングを見つけることができます。
大まかな使用フローは以下の通りです。
SCPI_Init()
関数を使用してオブジェクト、デバイス ID、さまざまなポート関数をバインドします。SCPI_Input()
関数を使用して、サポートされている完全な命令を入力します。ライブラリは自動的に命令コールバックをトリガーし、自動的にポート関数を使用して情報を送信します。
優れたライブラリは、使用がこれほど簡単です。
一部のライブラリ API の紹介#
公式ドキュメントにも API の紹介がありますが、ほとんど有効な情報はありません。結局、公式には一人しかいないので、参考にしてください。
About · SCPI parser (jaybee.cz)
Scpi-Def.c には、SCPI 処理プロセスで使用されるインターフェースパラメータ scpi interface が宣言されています。
命令パラメータ処理 API#
scpi_bool_t SCPI_ParamErrorOccurred(scpi_t* context);
処理関数内でエラーが発生したかどうかを検出し、エラーが存在する場合は処理関数を直ちに停止する必要があります。
scpi_bool_t SCPI_ParamInt32(
scpi_t* context,
int32_t* value,
scpi_bool_t mandatory);
contextから32ビット符号付きパラメータを取得し、valueに代入します。mandatoryがtrueでパラメータがない場合、-109エラー(無パラメータエラー)が生成されます。mandatoryがfalse(通常は使用しない)であれば、パラメータは式文字列である必要があり、そうでなければ-151エラー(無効文字列エラー)が発生します。
scpi_bool_t SCPI_ParamInt64(
scpi_t* context,
int64_t* value,
scpi_bool_t mandatory);
上記の32ビット符号付きパラメータ取得ロジックと同様に、64ビット符号付きパラメータを取得します。
上記の2つの関数と同様のロジックを持つAPI関数は以下の通りです。
SCPI_ParamUInt32() 無符号32ビットデータを取得します。
SCPI_ParamUInt64() 無符号64ビットデータを取得します。
SCPI_ParamDouble() double型データを取得します。
SCPI_ParamFloat() float型データを取得します。
SCPI_ParamBool() bool型データを取得します。
SCPI_ParamChoice(
scpi_t * context,
const scpi_choice_def_t * options,
int32_t * value,
scpi_bool_t mandatory)
オプションリストから値を取得します。*optionsはデータを抽出するためのパラメータで、オプションのインデックスをValueに代入します。
SCPI_ParamCopyText(
scpi_t * context,
char * buffer,
size_t buffer_len,
size_t * copy_len,
scpi_bool_t mandatory)
データを抽出してbufferに代入します。
SCPI_ParamCharacters(
scpi_t * context,
const char ** value,
size_t * len,
scpi_bool_t mandatory)
文字パラメータを抽出し、valueに代入します。lenは抽出に成功した文字の長さです。
SCPI_ParamArbitraryBlock(
scpi_t * context,
const char ** value,
size_t * len,
scpi_bool_t mandatory)
任意のブロックプログラムデータを取得し、valueに代入します。lenは抽出に成功した文字数です。
SCPI_ParamNumber(
scpi_t * context,
const scpi_choice_def_t * special,
scpi_number_t * value,
scpi_bool_t mandatory)
次のパラメータを数字または単位付き数字または特定のルールの数字として解析し、valueに代入します。特定のルールで解析する必要がある場合、specialが解析ルールを示します:MINimum、MAXimum、DEFaylt、UP、DOWNなど。詳細はscpi_choice_numbers_def[]の定義を確認してください。
生成された応答 API#
結果生成処理 API によって生成された応答パラメータは、インターフェースパラメータ scpi interface に格納され、最終的には SCPI_Write () 関数が呼び出されます。現在、この関数は UART4 ポートを介して応答パラメータを送信します。
size_t
SCPI_ResultArbitraryBlock(
scpi_t * context,
const char * data,
size_t len)
任意のブロックデータに(#1+バイトデータ長)ヘッダーを追加し——ヘッダーの変更はSCPI_ResultArbitraryBlockHeader()関数定義を確認し、\r\nの尾を追加し、SCPI_Write()関数を呼び出して送信します。
この関数は実際には以下の2つの関数を呼び出します。
size_t
SCPI_ResultArbitraryBlockHeader(
scpi_t * context,
size_t len)
運算データブロックに追加すべきヘッダーを追加して送信します。
size_t
SCPI_ResultArbitraryBlockData(
scpi_t * context,
const char * data,
size_t len)
エラーチェック付きのデータブロック送信関数で、contextのarbitrary_remindingパラメータが指定された長さlen未満である場合、システムエラーSCPI_ERROR_SYSTEM_ERRORが発生します。
size_t
SCPI_ResultText(
scpi_t * context,
const char * data)
「を含む文字列を検索し、「を含む文字列を結果に書き込みます(あまり役に立たないAPIです)。おそらくFUNCです。
size_t
SCPI_ResultBool(
scpi_t * context,
scpi_bool_t val)
bool値を結果に書き込みます。
size_t
SCPI_ResultCharacters(
scpi_t * context,
const char * data,
size_t len)
原始文字列結果を出力に書き込み、最初に応答区切り文字「,」を送信し、その後に文字列を送信します。
size_t
SCPI_ResultMnemonic(
scpi_t * context,
const char * data)
SCPI_ResultCharacters()文字列送信関数と同等ですが、この関数のlenはsizeofです。
size_t
SCPI_ResultArbitraryBlock(
scpi_t * context,
const char * data,
size_t len)
任意のブロックデータに(#1+バイトデータ長)ヘッダーを追加し——ヘッダーの変更はSCPI_ResultArbitraryBlockHeader()関数定義を確認し、\r\nの尾を追加し、SCPI_Write()関数を呼び出して送信します。
この関数は実際には以下の2つの関数を呼び出します。
size_t
SCPI_ResultArbitraryBlockHeader(
scpi_t * context,
size_t len)
運算データブロックに追加すべきヘッダーを追加して送信します。
size_t
SCPI_ResultArbitraryBlockData(
scpi_t * context,
const char * data,
size_t len)
エラーチェック付きのデータブロック送信関数で、contextのarbitrary_remindingパラメータが指定された長さlen未満である場合、システムエラーSCPI_ERROR_SYSTEM_ERRORが発生します。
size_t
SCPI_ResultText(
scpi_t * context,
const char * data)
「を含む文字列を検索し、「を含む文字列を結果に書き込みます(あまり役に立たないAPIです)。おそらくFUNCです。
size_t
SCPI_ResultBool(
scpi_t * context,
scpi_bool_t val)
bool値を結果に書き込みます。
size_t
SCPI_ResultCharacters(
scpi_t * context,
const char * data,
size_t len)
原始文字列結果を出力に書き込み、最初に応答区切り文字「,」を送信し、その後に文字列を送信します。
size_t
SCPI_ResultMnemonic(
scpi_t * context,
const char * data)
SCPI_ResultCharacters()文字列送信関数と同等ですが、この関数のlenはsizeofです。
以下の結果を書き込むAPIは、Baseパラメータがない場合、すべて10進数に変換して文字列として書き込みます。
size_t
SCPI_ResultDouble(
scpi_t * context,
double val)
双精度値を結果に書き込み、最初に結果区切り文字「,」を送信し、その後に値を送信します。
上記の関数と同様のロジックを持つAPI関数は以下の通りです。
SCPI_ResultFloat(scpi_t * context,float val) float値を書き込みます。
SCPI_ResultInt16(scpi_t * context,int16_t val) 符号付き16ビット値を書き込みます。
SCPI_ResultInt32(scpi_t * context,int32_t val)
SCPI_ResultInt64(scpi_t * context,int64_t val)
SCPI_ResultInt8(scpi_t * context,int8_t val)
SCPI_ResultUInt16(scpi_t * context,uint16_t val) 無符号16ビット値を書き込みます。
SCPI_ResultUInt32(scpi_t * context,uint32_t val)
SCPI_ResultUInt64(scpi_t * context,uint64_t val)
SCPI_ResultUInt8(scpi_t * context,uint8_t val)
SCPI_ResultUInt16Base(scpi_t * context,uint16_t val,int8_t base) 無符号16ビット値をBase進数に変換し、文字列として結果に書き込みます。
SCPI_ResultUInt32Base(scpi_t * context,uint32_t val,int8_t base)
SCPI_ResultUInt64Base(scpi_t * context,uint64_t val,int8_t base)
SCPI_ResultUInt8Base(scpi_t * context,uint8_t val,int8_t base)
配列形式での応答生成 API#
前の部分で生成された応答 API と似ており、この部分の API は配列をインターフェースパラメータ scpi interface の結果ストレージに格納し、SCPI_Write () 関数をトリガーします。
size_t
SCPI_ResultArrayDouble(
scpi_t * context,
const double * array,
size_t count,
scpi_array_format_t format);
arrayが指すcount個のdouble要素を配列に変換し、formatはシステムのエンディアンを選択します。
SCPI_ResultArrayFloat(scpi_t * context,const Float * array,
size_t count,scpi_array_format_t format);
SCPI_ResultArrayInt16(scpi_t * context,const int16_t * array,
size_t count,scpi_array_format_t format);
SCPI_ResultArrayInt32(scpi_t * context,const int32_t * array,
size_t count,scpi_array_format_t format);
SCPI_ResultArrayUInt64(scpi_t * context,const uint64_t * array,
size_t count,scpi_array_format_t format);
SCPI_ResultArrayUInt8(scpi_t * context,const uint8_t * array,
size_t count,scpi_array_format_t format);
データを文字列に変換する API#
デフォルトではインターフェースパラメータの応答値に対して操作を行いません。
size_t
SCPI_DoubleToStr(
double val,
char * str,
size_t len)
双精度値を文字列に変換し、strが指すアドレスに代入し、lenは許可される最大バッファサイズのバイト長です。
SCPI_FloatToStr(float val,char * str,size_t len)
SCPI_Int32ToStr(int32_t val,char * str,size_t len)
SCPI_Int64ToStr(int64_t val,char * str,size_t len)
SCPI_UInt32ToStrBase(uint32_t val,char * str,size_t len,int8_t base)変換された文字列はbase進数です。
SCPI_UInt64ToStrBase(uint64_t val,char * str,size_t len,int8_t base)
size_t
SCPI_NumberToStr(
scpi_t * context,
const scpi_choice_def_t * special,
scpi_number_t * value,
char * str,
size_t len)
特殊ルールの下での数字を単位付き文字列に変換します。SCPI_ParamNumberとは反対の関数です。
拡張されたパラメータ処理 API#
scpi_bool_t
SCPI_ChoiceToName(
const scpi_choice_def_t * options,
int32_t tag,
const char ** text)
optionsは{文字列、int_tデータ}の表構造であり、この関数はtagデータに基づいて表を参照し、対応する文字列のアドレスをtextが指すアドレスに代入します。
scpi_bool_t
SCPI_ParamIsNumber(
scpi_parameter_t * parameter,
scpi_bool_t suffixAllowed)
パラメータが数字型であるかどうかを確認します。通常はSCPI_Parameter(context, ¶m, mandatory)を使用してパラメータを取得した後に使用します。
scpi_bool_t
SCPI_ParamIsValid(
scpi_parameter_t * parameter)
この関数は、代入関数でエラーが発生したかどうかを確認します。
scpi_bool_t
SCPI_ParamToChoice(
scpi_t * context,
scpi_parameter_t * parameter,
const scpi_choice_def_t * options,
int32_t * value)
optionsは{文字列、int_tデータ}の表構造であり、この関数はcontext内のパラメータを参照し、パラメータを文字列に変換します。通常はSCPI_Parameter(context, ¶m, mandatory)を使用してパラメータを取得した後に使用します。
scpi_bool_t
SCPI_ParamToDouble(
scpi_t* context,
scpi_parameter_t* parameter,
double* value)
パラメータをdouble値に変換します。通常はSCPI_Parameter(context, ¶m, mandatory)を使用してパラメータを取得した後に使用します。
上記の関数と同様のロジックを持つ関数は以下の通りです。
SCPI_ParamToFloat(scpi_t* context,scpi_parameter_t* parameter,float* value);
SCPI_ParamToInt32(scpi_t* context,scpi_parameter_t* parameter,int32_t* value);
SCPI_ParamToInt64(scpi_t* context,scpi_parameter_t* parameter,int64_t* value);
SCPI_ParamToUInt32(scpi_t* context,scpi_parameter_t* parameter,uint32_t* value);
SCPI_ParamToUInt64(scpi_t* context,scpi_parameter_t* parameter,uint64_t* value);
scpi_bool_t
SCPI_Parameter(
scpi_t* context,
scpi_parameter_t* parameter,
scpi_bool_t mandatory)
コマンドラインから1つのパラメータを取得し、parameterに代入します。parameterにはデータの長さやデータ型も格納されます。
コマンド処理 API#
int32_t
SCPI_CmdTag(
scpi_t* context)
検出されたコマンドタグを返します;理解できません、テスト待ちです。
scpi_bool_t
SCPI_CommandNumbers(
scpi_t* context,
int32_t* numbers,
size_t len)
コマンドリスト内で、許可された数字の位置を指定し、処理関数内でこれらの位置の数字をnumbersに埋め込むことができます。lenは配列の長さです。例えば{.pattern = "TEST#:NUMbers#", .callback = TEST_Numbers,}のように、TEST3:NUMbers2を受け取った場合、SCPI_CommandNumbers(context,&data[0],2)を使用すると、data[0]に3が、data[1]に2が代入されます。