ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
scpi.h
Go to the documentation of this file.
1// ProtoCore v1.0.16 - Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file scpi.h
6 * @brief SCPI / IEEE 488.2 instrument-control codec (PROTOCORE_ENABLE_SCPI) - a zero-heap codec for the
7 * text command language nearly every modern bench instrument speaks (DMMs, oscilloscopes,
8 * power supplies, function/arbitrary generators, SMUs, spectrum/network analyzers, loads)
9 * over a raw TCP socket on port 5025 (also USBTMC / VXI-11 / HiSLIP / serial).
10 *
11 * The codec is symmetric - it serves both roles:
12 * - **Controller** (device drives an instrument): build command lines with a `:`-hierarchy header
13 * and comma-separated parameters (@ref protocore_scpi_build), and parse the instrument's replies -
14 * numeric NR1/NR2/NR3 (@ref protocore_scpi_parse_number), boolean (@ref protocore_scpi_parse_bool), quoted
15 * string (@ref protocore_scpi_parse_string), and the IEEE 488.2 arbitrary block `#<n><len><data>` used
16 * for waveform captures (@ref protocore_scpi_parse_block).
17 * - **Instrument** (device answers a controller): the IEEE 488.2 status model - the Status Byte,
18 * Standard Event Status Register + its enable mask, the Service Request Enable mask, and the SCPI
19 * error/event queue (@ref ScpiStatus) - plus a SCPI short/long-form header matcher
20 * (@ref protocore_scpi_match) to dispatch an incoming command against a pattern like `"SYSTem:ERRor?"`.
21 *
22 * Common commands (@ref protocore_scpi_common) return the IEEE 488.2 mnemonics (`*IDN?`, `*RST`, `*CLS`,
23 * `*ESR?`, `*STB?`, ...). Pure codec, host-tested; the TCP/USB/serial transport is the app's.
24 *
25 * References:
26 * - IEEE Std 488.2-1992 (Codes, Formats, Protocols and Common Commands).
27 * - SCPI Consortium, "Standard Commands for Programmable Instruments", Vol. 1 Syntax & Style (1999).
28 *
29 * @author Douglas Quigg (dstroy0)
30 * @date 2026
31 */
32
33#ifndef PROTOCORE_SCPI_H
34#define PROTOCORE_SCPI_H
35
36#include "protocore_config.h" // the entry point: protocore_types.h for the widths
37
38#if PROTOCORE_ENABLE_SCPI
39
41
42/** @brief The default raw-socket ("SCPI-RAW") TCP port instruments listen on. */
43#define PROTOCORE_SCPI_PORT 5025
44
45// ── Standard Event Status Register (ESR) bits - read/cleared by *ESR? ──────────────────────────
46#define SCPI_ESR_OPC 0x01 ///< bit 0: Operation Complete
47#define SCPI_ESR_RQC 0x02 ///< bit 1: Request Control
48#define SCPI_ESR_QYE 0x04 ///< bit 2: Query Error
49#define SCPI_ESR_DDE 0x08 ///< bit 3: Device-Dependent Error
50#define SCPI_ESR_EXE 0x10 ///< bit 4: Execution Error
51#define SCPI_ESR_CME 0x20 ///< bit 5: Command Error
52#define SCPI_ESR_URQ 0x40 ///< bit 6: User Request
53#define SCPI_ESR_PON 0x80 ///< bit 7: Power On
54
55// ── Status Byte Register (STB) bits - read by *STB? / serial poll (SCPI model) ─────────────────
56#define SCPI_STB_EAV 0x04 ///< bit 2: Error/event queue not empty
57#define SCPI_STB_QSB 0x08 ///< bit 3: QUEStionable status summary
58#define SCPI_STB_MAV 0x10 ///< bit 4: Message Available (output queue not empty)
59#define SCPI_STB_ESB 0x20 ///< bit 5: Standard Event status Summary (ESR & ESE)
60#define SCPI_STB_MSS 0x40 ///< bit 6: Master Summary Status (RQS in a serial poll)
61#define SCPI_STB_OSB 0x80 ///< bit 7: OPERation status summary
62
63/** @brief The mandatory IEEE 488.2 common commands (`protocore_scpi_common` renders each). */
64typedef enum PROTO_ENUM_PACKED
65{
66 SCPI_CLS, ///< "*CLS" clear status
67 SCPI_ESE, ///< "*ESE" set event status enable
68 SCPI_ESE_Q, ///< "*ESE?" query event status enable
69 SCPI_ESR_Q, ///< "*ESR?" query + clear event status register
70 SCPI_IDN_Q, ///< "*IDN?" identification query
71 SCPI_OPC, ///< "*OPC" set operation-complete bit when done
72 SCPI_OPC_Q, ///< "*OPC?" query operation complete (returns 1)
73 SCPI_RST, ///< "*RST" reset
74 SCPI_SRE, ///< "*SRE" set service request enable
75 SCPI_SRE_Q, ///< "*SRE?" query service request enable
76 SCPI_STB_Q, ///< "*STB?" query status byte
77 SCPI_TST_Q, ///< "*TST?" self-test query
78 SCPI_WAI, ///< "*WAI" wait to continue
79} ScpiCommon;
80
81/** @brief Render an IEEE 488.2 common command mnemonic (a static string, never null). */
82const char *protocore_scpi_common(ScpiCommon c);
83
84/**
85 * @brief Build a SCPI command line: `header` + ' ' + comma-joined `args` + '\n'.
86 *
87 * With @p argc 0 the line is just `header` + '\n' (e.g. `"*RST\n"`, `"MEAS:VOLT:DC?\n"`).
88 * @param header a full `:`-hierarchy header or a common command, e.g. `"SOURce:VOLTage"`, `"*IDN?"`.
89 * @param args already-formatted parameter strings (see @ref protocore_scpi_fmt_real to format a number).
90 * @return characters written (excluding the NUL terminator), or 0 on overflow / bad input.
91 * @note The line is NUL-terminated so callers may treat @p buf as a C-string.
92 */
93size_t protocore_scpi_build(char *buf, size_t cap, const char *header, const char *const *args, size_t argc);
94
95/**
96 * @brief Format a real number as a SCPI parameter (NR2 fixed, or NR3 scientific for large/small
97 * magnitudes), trailing zeros trimmed. Handy for @ref protocore_scpi_build args.
98 * @return characters written (excluding NUL), or 0 on overflow.
99 */
100size_t protocore_scpi_fmt_real(char *buf, size_t cap, double v);
101
102/**
103 * @brief Parse a SCPI numeric response (NR1 integer / NR2 fixed / NR3 scientific), hand-rolled
104 * (no stdlib). Recognizes the special values `9.9E37` (INFinity), `-9.9E37`, `9.91E37` (NaN).
105 * @return true on a fully-consumed numeric field; false on malformed input.
106 */
107proto_bool protocore_scpi_parse_number(const char *s, size_t len, double *out);
108
109/**
110 * @brief Parse a SCPI boolean response: `1`/`0` or `ON`/`OFF` (case-insensitive).
111 * @return true on a recognized boolean; false otherwise.
112 */
113proto_bool protocore_scpi_parse_bool(const char *s, size_t len, proto_bool *out);
114
115/**
116 * @brief Parse a SCPI string response: strip the surrounding single or double quotes and collapse a
117 * doubled quote (`""` or `''`) to one. Copies into @p out (NUL-terminated).
118 * @return the unquoted length (excluding NUL), or 0 on a missing/mismatched quote or overflow.
119 */
120size_t protocore_scpi_parse_string(const char *s, size_t len, char *out, size_t cap);
121
122/**
123 * @brief Parse an IEEE 488.2 arbitrary block: definite `#<n><n-length-digits><data>` or indefinite
124 * `#0<data>` (runs to the trailing newline). On success @p data points INTO @p buf.
125 * @param data receives a pointer to the first data byte.
126 * @param data_len receives the data byte count.
127 * @param consumed receives the total bytes the block occupied (header + data [+ the indefinite NL]).
128 * @return true on a complete block; false if truncated or malformed.
129 */
130proto_bool protocore_scpi_parse_block(const uint8_t *buf, size_t len, const uint8_t **data, size_t *data_len,
131 size_t *consumed);
132
133// ── IEEE 488.2 / SCPI status model (instrument side) ───────────────────────────────────────────
134
135/** @brief One error/event queue entry. @ref msg points at a static or app-owned string. */
136typedef struct
137{
138 int16_t number; ///< SCPI error number (negative = standard, positive = device-specific, 0 = no error)
139 const char *msg; ///< the description text (without the surrounding quotes)
140} ScpiError;
141
142/** @brief The IEEE 488.2 status registers + the SCPI error/event queue (one owned block). */
143typedef struct
144{
145 uint8_t esr; ///< Standard Event Status Register (latched events)
146 uint8_t ese; ///< Standard Event Status Enable mask (ESR -> ESB)
147 uint8_t sre; ///< Service Request Enable mask (STB -> MSS)
148 uint8_t summary; ///< app-set STB summary bits (QSB/MAV/OSB); merged into *STB?
149 ScpiError queue[PROTOCORE_SCPI_ERR_QUEUE]; ///< the error/event queue (FIFO ring)
150 uint8_t head; ///< ring read index
151 uint8_t count; ///< entries currently queued
152} ScpiStatus;
153
154/** @brief Clear every register + the queue (power-on state). */
155void protocore_scpi_status_init(ScpiStatus *s);
156
157/** @brief Latch one or more ESR event bits (e.g. @ref SCPI_ESR_OPC). */
158void protocore_scpi_event(ScpiStatus *s, uint8_t esr_bits);
159
160/**
161 * @brief Enqueue an error/event and latch its ESR class bit (CME/EXE/DDE/QYE from the -1xx/-2xx/
162 * -3xx/-4xx number range). On overflow the tail entry becomes -350 "Queue overflow".
163 * @param msg the description; pass nullptr to use the standard text for a standard number.
164 */
165void protocore_scpi_push_error(ScpiStatus *s, int16_t number, const char *msg);
166
167/**
168 * @brief Pop the head error (the `SYSTem:ERRor?` action). When the queue is empty @p out becomes
169 * `0,"No error"`.
170 * @return true if an error was dequeued; false if the queue was empty (out = the no-error entry).
171 */
172proto_bool protocore_scpi_pop_error(ScpiStatus *s, ScpiError *out);
173
174/** @brief Compute the Status Byte: EAV (queue), ESB (esr & ese), the app summary bits, and MSS. */
175uint8_t protocore_scpi_stb(const ScpiStatus *s);
176
177/** @brief The `*CLS` action: clear the ESR and empty the error/event queue (enables untouched). */
178void protocore_scpi_cls(ScpiStatus *s);
179
180/** @brief The standard SCPI message for a standard (negative) error number, or "" if unknown. */
181const char *protocore_scpi_std_error(int16_t number);
182
183/**
184 * @brief Match an incoming command header against a SCPI short/long-form pattern.
185 *
186 * Case-insensitive. Each `:`-separated node in @p pattern has an uppercase run (the required short
187 * form) followed by an optional lowercase tail (the long form); the input node must equal EITHER the
188 * short form OR the whole long form (no other truncation). An optional numeric suffix is compared
189 * with an omitted suffix defaulting to 1 (`OUTP` == `OUTPut1`). A trailing `?` in the pattern must
190 * be matched by a `?` in the input. A `*`-prefixed pattern (common command) is matched whole,
191 * case-insensitively. A leading `:` on the input (absolute path) is ignored.
192 *
193 * @return true if @p input (its header, up to the first space) matches @p pattern.
194 */
195proto_bool protocore_scpi_match(const char *input, size_t input_len, const char *pattern);
196
198
199#endif // PROTOCORE_ENABLE_SCPI
200
201#endif // PROTOCORE_SCPI_H
#define PROTOCORE_SCPI_ERR_QUEUE
SCPI error/event queue depth (entries). The SCPI status model requires a queue; when it overflows the...
PROTO_ENUM_PACKED
Application protocol spoken on a listener port or connection slot.
#define PROTOCORE_BEGIN_DECLS
Give a header's declarations C linkage, so their symbol names carry no parameter types.
Definition types.h:96
_Bool proto_bool
The truth value.
Definition types.h:64
#define PROTOCORE_END_DECLS
Definition types.h:97