背景故事#
上大學一直在划水,剛畢業時幾乎啥也不會,是大佬的這個庫拯救了我。
SCPI 是一種字符串解析協議。剛工作時,點完燈開完串口,就讓我實現 SCPI 協議。那時還很菜,花了兩週時間才把這個協議庫開起來。
這個庫中的內容真的特別豐富,也沒有很動態的指針,基本上都是綁定性質的 "靜態" 指針,哪怕 C 語言不怎麼樣的我也可以慢慢看得懂。
在慢慢使用大約兩個月後,其中的大部分內容都瀏覽過,C 語言也正式入門了,
而且後續的應屆生,我也是讓他們先看這個庫。
源碼地址在這裡
j123b567/scpi-parser: Open Source SCPI device library (github.com)
這個代碼庫的優勢#
[!NOTE]
- 這是一個純粹的協議解析庫,與硬件並非強關聯,需要關注的 port 也僅僅是數據流入和數據流出。非常迅速就可以使用起來(哪怕是 CodeBlocks 或 DEV-C++ 這樣的學習環境)。
- 這是一個能勝任業務功能的庫,SCPI 協議解析所需的所有功能或機制它都可以勝任,學習後的應用場景非常廣,不會白白學習。
- 這是一個穩健的庫,我目前還沒有在這個庫中發現致命 bug。舉個反例,最近期望移植和修改的 freemodbus 庫,其中就存在致命 bug 以及大大小小的設計缺陷。
- 這是一個相對簡單的庫,比起 rtos 內核、網絡庫、文件系統庫等等,這個庫幾乎可以稱得上非常易懂了,只要花時間,就能從其中學到 面向對象、宏翻譯、宏條件編譯、回調綁定、字符處理 等等 C 語言的常用語法及設計。
應該看什麼#
由於是要寫給 C 語言入門級選手的,哪怕這個庫的結構如此簡單,也依然需要介紹一下。整個庫只有兩個文件夾如下
-
examples
其中是一些示例,也是我們期望將庫用起來首先要看的。 -
libscpi
其中就是所有的庫源碼。在這個文件夾下有test
文件夾,是帶有 main 入口的單元測試,實際工程中不必引入libscpi/test
文件夾的源碼。
在examples
中建議重點查看如下兩個文件夾
examples/common 下是例程所支持的指令表,以及對應指令的回調,其中可以學習一些庫 API 的用法
examples/test-parser 下是最簡單例程的 main 入口,以及如 SCPI_Write ()、SCPI_Error () 等接口函數的定義,還有少量指令的回調,在指令表中都可以找到綁定。
大致使用流程如下
- 使用
SCPI_Init()
函數綁定對象、設備 ID、各種 port 函數 - 使用
SCPI_Input()
函數輸入一條所支持的完整指令,庫自動觸發指令回調,自動使用 port 函數發送信息
優秀的庫,使用就是如此簡單。
一些庫 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 為 ture 且沒有參數,則生成-109錯誤(無參數錯誤);若 mandatory 為 false(通常不使用),則參數應為表達式字符串,否則產生-151錯誤(無效字符串錯誤)
scpi_bool_t SCPI_ParamInt64(
scpi_t* context,
int64_t* value,
scpi_bool_t mandatory);
同上述取32位有符號參數邏輯,取出64位有符號參數
與上述兩個函數相同邏輯的還有如下 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)
將下個參數解析為數字 or 帶單位數字 or 特定規則的數字賦值給 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()函數發出
該函數實際上調用下列兩個函數
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()函數發出
該函數實際上調用下列兩個函數
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 傳參則均以十進制轉換為字符串
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 數據}的表結構,該函數根據 contexet 中的參數查表,
並將參數轉換為字符串,通常使用 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)
從命令行取一個參數並賦值給 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