Skip to content
Josh Smith edited this page Dec 22, 2025 · 6 revisions

Overview

ScpiLite is a lightweight SCPI (Standard Commands for Programmable Instruments) parser and command handler for the Raspberry Pi Pico. It provides a simple interface for implementing instrument like serial communication over UART, allowing devices to respond to SCPI commands with abbreviated command matching and built-in error handling.

Key Features

  • UART based SCPI command parsing
  • Flexible command registration system
  • SCPI abbreviation matching (commands can be shortened)
  • Built in commands: *IDN? and SYSTem:ERRor?
  • Error queue management with automatic error reporting
  • Callback based command handling
  • Low overhead circular error buffer

Core Components

ScpiLite Class

The main class that handles UART initialisation, command registration, parsing, and dispatch.

Constants

Constant Value Description
TOKEN_LEN 16 Maximum length of a command token or parameter
MAX_PARAMS 4 Maximum number of parameters per command
MAX_CMDS 16 Maximum number of registered commands
MAX_ERRS 16 Size of the circular error queue

Handler Callback

using Handler = bool (*)(ScpiLite*, bool is_query, const char params[][TOKEN_LEN], 
                         uint8_t param_count, char* resp, size_t resp_len, void* user_ctx);

Command handlers receive:

  • ScpiLite*: Pointer to the ScpiLite instance
  • bool is_query: True if the command ended with ?
  • const char params[][]: Array of parameter strings
  • uint8_t param_count: Number of parameters provided
  • char* resp: Response buffer to write to
  • size_t resp_len: Size of response buffer
  • void* user_ctx: User context provided during registration

Handlers return true if a response should be sent, false otherwise.

Public API

Initialisation

init_uart(uart_inst_t* uart, uint32_t baudrate, uint8_t tx_pin = 0, uint8_t rx_pin = 1)

Initializes the UART instance with specified baud rate and pins.

scpi.init_uart(uart0, 115200, 0, 1);

Configuration

set_idn(const char* idn)

Sets the identification string returned by *IDN?. Format is typically Manufacturer,Description,SerialNumber,FirmwareVersion.

scpi.set_idn("MyCompany,TestDevice,SN12345,v1.0");

Command Registration

register_command(const char* name, Handler h, void* user_ctx)

Registers a command with the SCPI parser. Command names use full SCPI format with uppercase letters indicating mandatory parts.

// MEASure:VOLTage means MEAS:VOLT are mandatory
// Users can send: MEAS:VOLT, MEAS:VOLTAGE, MEASure:VOLT, MEASure:VOLTAGE, etc.
scpi.register_command("MEASure:VOLTage", voltage_handler, nullptr);

Error Management

add_error(int code, const char* msg)

Adds an error to the queue (max 16 errors). Uses SCPI error code convention (negative codes). When the queue is full, the last error becomes -350 "Queue overflow".

scpi.add_error(-222, "Data out of range");

Main Loop

void poll()

Must be called regularly in the main loop. Reads characters from UART, buffers complete lines, and dispatches to command handlers.

while (true) 
{
    scpi.poll();
    sleep_ms(1);
}

void write_raw(const char* str)

Writes a raw string directly to UART.

scpi.write_raw("Hello\n");

Built-in Commands

*IDN? (Identification Query)

Returns the identification string set via set_idn().

*IDN?
< MyCompany,TestDevice,SN12345,v1.0

SYSTem:ERRor? (Query Error Queue)

Pops and returns the next error from the queue. Returns error code and message. If the queue is empty, returns 0,"No error".

Short form: SYS:ERR?

SYS:ERR?
< -222,"Data out of range"

Command Parsing

SCPI Abbreviation Matching

Commands use a specific format to support abbreviation:

  • Uppercase letters are mandatory minimum abbreviation
  • Lowercase letters are optional and can be omitted

Example: MEASure:VOLTage

  • Mandatory: MEAS:VOLT (4 chars + colon + 4 chars)
  • Valid inputs: MEAS:VOLT, MEASure:VOLT, MEAS:VOLTAGE, MEASure:VOLTAGE, etc.
  • Invalid: MEA:VOLT (too short), MEASURE:VOLT (extra characters not in definition)

Command Syntax

COMMAND[:SUBCOMMAND] [param1, param2, ...]?
  • Commands are case insensitive
  • Parameters are comma-separated
  • Query commands (ending with ?) cannot have parameters
  • Colons separate command hierarchy levels

Error Codes

Common SCPI error codes:

Code Meaning
-102 Illegal parameter value
-103 Input overflow
-104 Syntax error
-113 Undefined command
-222 Data out of range
-350 Queue overflow

Example Usage

#include "ScpiLite.hpp"

ScpiLite scpi;

// Command handler
bool handle_led(ScpiLite* s, bool is_query, const char params[][16], uint8_t param_count, char* resp, size_t resp_len, void* user_ctx)
{
    if (is_query) 
    {
        // LED? - query current state
        snprintf(resp, resp_len, "%d", led_state);
        return true;
    } else if (param_count == 1) 
    {
        // LED 1 - set LED on
        led_state = atoi(params[0]);
        return false; // no response needed
    }
    return false;
}

int main() 
{
    scpi.init_uart(uart0, 115200);
    scpi.set_idn("Lab,LED Controller,SN001,1.0");
    scpi.register_command("LED", handle_led, nullptr);
    
    while (true) 
    {
        scpi.poll();
    }
}

Limitations and Constraints

  • Maximum 16 registered commands
  • Maximum 4 parameters per command
  • Each token (command/parameter) limited to 15 characters
  • Input buffer 127 characters max
  • Error queue holds maximum 16 errors
  • Response buffer 255 characters max
  • Designed for Raspberry Pi Pico (uses pico/stdlib.h)

Notes

  • UART setup uses 8 data bits, 1 stop bit, no parity
  • Hardware flow control is disabled
  • FIFO is enabled for better throughput
  • The parser consumes complete lines (terminated by \n), carriage returns are ignored
  • All SCPI command matching is case-insensitive