Backstory
I coasted through college and graduated knowing almost nothing. This library by a true master saved me.
SCPI is a string-parsing protocol. When I first started working, right after I had blinked an LED and opened a serial port, I was asked to implement the SCPI protocol. I was pretty green back then — it took me two weeks to get this protocol library up and running.
The library's content is really rich, and there are no highly dynamic pointers. They are basically all "static" pointers used for binding, so even someone with mediocre C skills like me could slowly understand it.
After slowly working through it for about two months, I had browsed through most of its content, and that was when I really got started with C. And for the new graduates who joined after me, I had them look at this library first as well.
The source code is here
Strengths of This Codebase
- It is a pure protocol-parsing library that is not strongly bound to hardware. The only ports you need to care about are data input and data output, so you can get it running very quickly (even in learning environments like CodeBlocks or DEV-C++).
- It is a library that can handle real business work. Every feature or mechanism required for SCPI protocol parsing is covered, so what you learn applies very broadly — you will not be learning it in vain.
- It is a robust library. I have not found a fatal bug in it so far. As a counterexample, the freemodbus library I recently wanted to port and modify has fatal bugs as well as design flaws big and small.
- It is a relatively simple library. Compared with RTOS kernels, network libraries, file system libraries, and so on, this library can almost be called very easy to understand. As long as you spend time on it, you can learn common C syntax and design patterns such as object-oriented programming, macro expansion, conditional compilation, callback binding, and string handling.
What You Should Look At
Since the target audience is C beginners, even a library with such a simple structure still needs an introduction. The entire library has only two folders:
examples— contains the examples, and it is the first thing you should look at when you want to put the library to use.libscpi— contains all of the library's source code. Under this folder there is atestfolder, which is unit tests with amainentry point. In real projects, you do not need to include the source code underlibscpi/test.

image-20241205131859389
In examples, I suggest focusing on the following two folders:
Under
examples/commonis the command table supported by the example programs and the callbacks for the corresponding commands, where you can learn how to use some library APIs.Under
examples/test-parseris themainentry point of the simplest example, the definitions of interface functions such asSCPI_Write()andSCPI_Error(), and a few command callbacks, all of which can be found bound in the command table.

image-20241205132043166
The general usage flow is as follows:
- Use the
SCPI_Init()function to bind the object, device ID, and various port functions. - Use the
SCPI_Input()function to input a complete supported command. The library automatically triggers the command callback and automatically uses the port functions to send information.
A great library is just that simple to use.
Some Library APIs Explained
The official documentation does have API introductions, but there is almost no useful information in them. After all, there is only one person behind the official project. It can still be used for reference.
Scpi-Def.c declares the scpi interface — the set of interface parameters used throughout the entire SCPI handling process.
Command Parameter Handling APIs
scpi_bool_t SCPI_ParamErrorOccurred(scpi_t* context);
Use this in a handler to check whether an error occurred while handling the command. If there is an error, the handler should stop immediately.
scpi_bool_t SCPI_ParamInt32(
scpi_t* context,
int32_t* value,
scpi_bool_t mandatory);
Retrieves a 32-bit signed parameter from context and assigns it to value. If mandatory is true and there is no parameter, error -109 is generated (no parameter error); if mandatory is false (usually not used), the parameter should be an expression string, otherwise error -151 is generated (invalid string error).
scpi_bool_t SCPI_ParamInt64(
scpi_t* context,
int64_t* value,
scpi_bool_t mandatory);
Same logic as the 32-bit signed parameter retrieval above, retrieving a 64-bit signed parameter.
The following API functions follow the same logic as the two functions above:
SCPI_ParamUInt32() retrieves unsigned 32-bit data
SCPI_ParamUInt64() retrieves unsigned 64-bit data
SCPI_ParamDouble() retrieves double data
SCPI_ParamFloat() retrieves float data
SCPI_ParamBool() retrieves bool data
SCPI_ParamChoice(
scpi_t * context,
const scpi_choice_def_t * options,
int32_t * value,
scpi_bool_t mandatory)
Retrieves a value from the option list. *options is the parameter from which the data is extracted. The index of the selected option is assigned to value.
SCPI_ParamCopyText(
scpi_t * context,
char * buffer,
size_t buffer_len,
size_t * copy_len,
scpi_bool_t mandatory)
Extracts the data and assigns it to buffer.
SCPI_ParamCharacters(
scpi_t * context,
const char ** value,
size_t * len,
scpi_bool_t mandatory)
Extracts a character parameter into value; len is the length of the characters successfully extracted.
SCPI_ParamArbitraryBlock(
scpi_t * context,
const char ** value,
size_t * len,
scpi_bool_t mandatory)
Retrieves arbitrary block program data into value; len is the number of characters successfully extracted.
SCPI_ParamNumber(
scpi_t * context,
const scpi_choice_def_t * special,
scpi_number_t * value,
scpi_bool_t mandatory)
Parses the next parameter as a number, a number with a unit, or a number under special rules, and assigns it to value. If parsing under special rules is needed, special identifies the parsing rules: MINimum, MAXimum, DEFaylt,
UP, DOWN, etc. For details, see the scpi_choice_numbers_def[] definition.
Result Generation APIs
The response parameters produced by the result-generation processing APIs are stored in the scpi interface (the interface parameter), and in the end they all go through the SCPI_Write() function. Currently, that function sends the response parameters out through UART4.
size_t
SCPI_ResultArbitraryBlock(
scpi_t * context,
const char * data,
size_t len)
Adds a (#1 + byte length) header to the arbitrary block data — to change the header, see the SCPI_ResultArbitraryBlockHeader() function definition — appends a \r\n tail, and calls SCPI_Write() to send it.
This function actually calls the following two functions:
size_t
SCPI_ResultArbitraryBlockHeader(
scpi_t * context,
size_t len)
Computes the header that should be added to the data block and sends it.
size_t
SCPI_ResultArbitraryBlockData(
scpi_t * context,
const char * data,
size_t len)
Sends the data block with error detection. If the arbitrary_reminding parameter in context is less than the specified length len, it generates a system error SCPI_ERROR_SYSTEM_ERROR.
size_t
SCPI_ResultText(
scpi_t * context,
const char * data)
Looks for data wrapped in quotes and writes the quoted string into the result (a not very useful API; possibly a FUNC).
size_t
SCPI_ResultBool(
scpi_t * context
scpi_bool_t val)
Writes the bool value into the result.
size_t
SCPI_ResultCharacters(
scpi_t * context,
const char * data,
size_t len)
Writes the raw string result into the output. It first sends a response separator "," and then sends the string.
size_t
SCPI_ResultMnemonic(
scpi_t * context,
const char * data)
Equivalent to the string-sending function SCPI_ResultCharacters(), except that this function uses sizeof for len.
size_t
SCPI_ResultArbitraryBlock(
scpi_t * context,
const char * data,
size_t len)
Adds a (#1 + byte length) header to the arbitrary block data — to change the header, see the SCPI_ResultArbitraryBlockHeader() function definition — appends a \r\n tail, and calls SCPI_Write() to send it.
This function actually calls the following two functions:
size_t
SCPI_ResultArbitraryBlockHeader(
scpi_t * context,
size_t len)
Computes the header that should be added to the data block and sends it.
size_t
SCPI_ResultArbitraryBlockData(
scpi_t * context,
const char * data,
size_t len)
Sends the data block with error detection. If the arbitrary_reminding parameter in context is less than the specified length len, it generates a system error SCPI_ERROR_SYSTEM_ERROR.
size_t
SCPI_ResultText(
scpi_t * context,
const char * data)
Looks for data wrapped in quotes and writes the quoted string into the result (a not very useful API; possibly a FUNC).
size_t
SCPI_ResultBool(
scpi_t * context
scpi_bool_t val)
Writes the bool value into the result.
size_t
SCPI_ResultCharacters(
scpi_t * context,
const char * data,
size_t len)
Writes the raw string result into the output. It first sends a response separator "," and then sends the string.
size_t
SCPI_ResultMnemonic(
scpi_t * context,
const char * data)
Equivalent to the string-sending function SCPI_ResultCharacters(), except that this function uses sizeof for len.
The result-writing APIs below all convert values to strings in decimal unless a Base parameter is passed.
size_t
SCPI_ResultDouble(
scpi_t * context,
double val)
Writes a double value into the result. It first sends a result separator "," and then sends the value.
The following API functions follow the same logic as the above function:
SCPI_ResultFloat(scpi_t * context,float val) writes a float value
SCPI_ResultInt16(scpi_t * context,int16_t val) writes a signed 16-bit value
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) writes an unsigned 16-bit value
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) converts the unsigned 16-bit value to the given base and writes it into the result as a string
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)
Array Result Generation APIs
Similar to the result-generation APIs in the previous section, the APIs in this section generate an array, store it in the result storage of the scpi interface, and trigger the SCPI_Write() function.
size_t
SCPI_ResultArrayDouble(
scpi_t * context,
const double * array,
size_t count,
scpi_array_format_t format);
Converts the count double elements pointed to by array into an array; format selects the system endianness.
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);
Data-to-String Conversion APIs
By default, these APIs do not operate on the response values in the interface parameter.
size_t
SCPI_DoubleToStr(
double val,
char * str,
size_t len)
Converts a double value to a string, assigning it to the address pointed to by str; len is the maximum allowed buffer length in bytes.
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) The converted string is in the given 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)
Converts a number under special rules into a string with a unit. It is the inverse function of SCPI_ParamNumber.
Extended Parameter Handling APIs
scpi_bool_t
SCPI_ChoiceToName(
const scpi_choice_def_t * options,
int32_t tag,
const char ** text)
options is a table structure of {string, int_t data}. This function looks up the table according to the tag data and assigns the address of the corresponding string to the address pointed to by text.
scpi_bool_t
SCPI_ParamIsNumber(
scpi_parameter_t * parameter,
scpi_bool_t suffixAllowed)
Checks whether the parameter is of numeric type. It is usually used after retrieving the parameter with SCPI_Parameter(context, ¶m, mandatory).
scpi_bool_t
SCPI_ParamIsValid(
scpi_parameter_t * parameter)
This function checks whether an error occurred while assigning the parameter.
scpi_bool_t
SCPI_ParamToChoice(
scpi_t * context,
scpi_parameter_t * parameter,
const scpi_choice_def_t * options,
int32_t * value)
options is a table structure of {string, int_t data}. This function looks up the table according to the parameter in context and converts the parameter to a string. It is usually used after retrieving the parameter with SCPI_Parameter(context, ¶m, mandatory).
scpi_bool_t
SCPI_ParamToDouble(
scpi_t* context,
scpi_parameter_t* parameter,
double* value)
Converts the parameter to a double value. It is usually used after retrieving the parameter with SCPI_Parameter(context, ¶m, mandatory).
The following functions have similar logic to the above function:
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)
Takes one parameter from the command line and assigns it to parameter. The data length and data type are also stored in parameter.
Command Handling APIs
int32_t
SCPI_CmdTag(
scpi_t* context)
Returns the detected command tag. I don't understand it; needs testing.
scpi_bool_t
SCPI_CommandNumbers(
scpi_t* context,
int32_t* numbers,
size_t len)
Specifies the positions where numbers are allowed in the command list. In a handler, you can fill the numbers at these positions into numbers; len is the length of the array. For example, {.pattern = "TEST#:NUMbers#", .callback = TEST_Numbers,}. If TEST3:NUMbers2 is received, you can use SCPI_CommandNumbers(context, &data[0], 2), and data[0] will be assigned 3 and data[1] will be assigned 2.