C语言入门_SCPI 库 API 介绍
背景故事
上大学一直在划水,刚毕业时几乎啥也不会,是大佬的这个库拯救了我。
SCPI 是一种字符串解析协议。刚工作时,点完灯开完串口,就让我实现 SCPI 协议。那时还很菜,花了两周时间才把这个协议库开起来。
这个库中的内容真的特别丰富,也没有很动态的指针,基本上都是绑定性质的"静态"指针,哪怕 C 语言不怎么样的我也可以慢慢看得懂。
在慢慢使用大约两个月后,其中的大部分内容都浏览过,C 语言也正式入门了,
而且后续的应届生,我也是让他们先看这个库。
源码地址在这里
这个代码库的优势
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