banner
yono

yono

哈喽~欢迎光临
follow
github

24_7_12_C语言入门_SCPI库API介绍

背景故事#

上大学一直在划水,刚毕业时几乎啥也不会,是大佬的这个库拯救了我。

SCPI 是一种字符串解析协议。刚工作时,点完灯开完串口,就让我实现 SCPI 协议。那时还很菜,花了两周时间才把这个协议库开起来。

这个库中的内容真的特别丰富,也没有很动态的指针,基本上都是绑定性质的 "静态" 指针,哪怕 C 语言不怎么样的我也可以慢慢看得懂。

在慢慢使用大约两个月后,其中的大部分内容都浏览过,C 语言也正式入门了,

而且后续的应届生,我也是让他们先看这个库。

源码地址在这里

j123b567/scpi-parser: Open Source SCPI device library (github.com)

这个代码库的优势#

[!NOTE]

  1. 这是一个纯粹的协议解析库,与硬件并非强关联,需要关注的 port 也仅仅是数据流入和数据流出。非常迅速就可以使用起来(哪怕是 CodeBlocks 或 DEV-C++ 这样的学习环境)。
  2. 这是一个能胜任业务功能的库,SCPI 协议解析所需的所有功能或机制它都可以胜任,学习后的应用场景非常广,不会白白学习。
  3. 这是一个稳健的库,我目前还没有在这个库中发现致命 bug。举个反例,最近期望移植和修改的 freemodbus 库,其中就存在致命 bug 以及大大小小的设计缺陷。
  4. 这是一个相对简单的库,比起 rtos 内核、网络库、文件系统库等等,这个库几乎可以称得上非常易懂了,只要花时间,就能从其中学到 面向对象、宏翻译、宏条件编译、回调绑定、字符处理 等等 C 语言的常用语法及设计。

应该看什么#

由于是要写给 C 语言入门级选手的,哪怕这个库的结构如此简单,也依然需要介绍一下。整个库只有两个文件夹如下

  • examples 其中是一些示例,也是我们期望将库用起来首先要看的。

  • libscpi 其中就是所有的库源码。在这个文件夹下有test文件夹,是带有 main 入口的单元测试,实际工程中不必引入libscpi/test文件夹的源码。

    image-20241205131859389

examples 中建议重点查看如下两个文件夹

examples/common 下是例程所支持的指令表,以及对应指令的回调,其中可以学习一些库 API 的用法

examples/test-parser 下是最简单例程的 main 入口,以及如 SCPI_Write ()、SCPI_Error () 等接口函数的定义,还有少量指令的回调,在指令表中都可以找到绑定。

image-20241205132043166

大致使用流程如下

  1. 使用 SCPI_Init() 函数绑定对象、设备 ID、各种 port 函数
  2. 使用 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, &param, 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, &param, mandatory)取参数后使用
scpi_bool_t
SCPI_ParamToDouble(
    scpi_t* context,
    scpi_parameter_t* parameter,
    double* value)
将参数转换为 double 值,通常使用 SCPI_Parameter(context, &param, 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
加载中...
此文章数据所有权由区块链加密技术和智能合约保障仅归创作者所有。