├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── errorgen.php ├── example ├── Makefile └── example.c ├── include ├── scpi.h ├── scpi_builtins.h ├── scpi_errors.h ├── scpi_parser.h └── scpi_regs.h ├── lib └── .gitkeep ├── scpi.pro ├── source ├── scpi_builtins.c ├── scpi_errors.c ├── scpi_parser.c └── scpi_regs.c └── style.astylerc /.gitignore: -------------------------------------------------------------------------------- 1 | # Object files 2 | *.o 3 | *.ko 4 | *.obj 5 | *.elf 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Libraries 12 | *.lib 13 | *.a 14 | *.la 15 | *.lo 16 | 17 | # Shared objects (inc. Windows DLLs) 18 | *.dll 19 | *.so 20 | *.so.* 21 | *.dylib 22 | 23 | # Executables 24 | *.exe 25 | *.out 26 | *.app 27 | *.i*86 28 | *.x86_64 29 | *.hex 30 | 31 | # Debug files 32 | *.dSYM/ 33 | 34 | *.autosave 35 | *.pro.user 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Ondřej Hruška 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | 3 | # Makefile to build a library for ARM CortexM3 4 | 5 | FP_FLAGS = -mfloat-abi=hard -mfpu=fpv4-sp-d16 6 | ARCH_FLAGS = -mthumb -mcpu=cortex-m4 $(FP_FLAGS) 7 | 8 | INCL_DIR = include 9 | SRC_DIR = source 10 | 11 | LIBNAME = arm_cortexM4_scpi 12 | 13 | OBJS = $(SRC_DIR)/scpi_parser.o 14 | OBJS += $(SRC_DIR)/scpi_regs.o 15 | OBJS += $(SRC_DIR)/scpi_errors.o 16 | OBJS += $(SRC_DIR)/scpi_builtins.o 17 | 18 | JUNK = *.o *.d *.elf *.bin *.hex *.srec *.list *.map *.dis *.disasm *.a 19 | 20 | ############################################################################### 21 | 22 | PREFIX ?= arm-none-eabi 23 | 24 | CC := $(PREFIX)-gcc 25 | AR := $(PREFIX)-ar 26 | 27 | ############################################################################### 28 | 29 | CFLAGS += -Os -ggdb -std=gnu99 -Wfatal-errors 30 | CFLAGS += -Wall -Wextra -Wshadow 31 | CFLAGS += -Wwrite-strings -Wold-style-definition -Winline -Wmissing-noreturn -Wstrict-prototypes 32 | CFLAGS += -Wredundant-decls -Wfloat-equal -Wsign-compare 33 | CFLAGS += -fno-common -ffunction-sections -fdata-sections -Wunused-function 34 | CFLAGS += -I$(INCL_DIR) 35 | 36 | CFLAGS += -DSCPI_FINE_ERRORS 37 | #CFLAGS += -DSCPI_WEIRD_ERRORS 38 | 39 | ############################################################################### 40 | 41 | all: lib 42 | 43 | %.o: %.c 44 | $(Q)$(CC) $(CFLAGS) $(ARCH_FLAGS) -o $(*).o -c $(*).c 45 | 46 | lib: lib/lib$(LIBNAME).a 47 | 48 | lib/lib$(LIBNAME).a: $(OBJS) 49 | $(Q)$(AR) rcs $@ $(OBJS) 50 | 51 | clean: 52 | $(Q)$(RM) $(JUNK) 53 | $(Q)cd source && $(RM) $(JUNK) 54 | $(Q)cd lib && $(RM) $(JUNK) 55 | $(Q)cd example && $(RM) $(JUNK) 56 | 57 | .PHONY: clean all lib 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SCPI parser 2 | 3 | This library provides a simple ("KISS") SCPI implementation for embedded devices (instruments). 4 | 5 | The implementation is not 100% complete, but it's sufficient for basic SCPI communication. 6 | 7 | - Easy configuration with one const array and callbacks 8 | - Implements SCPI99 (minimal requirements) - including common commands and status registers 9 | - Mandatory parts of the SYST and STAT subsystems are built in (ie. error handling) 10 | - Easy, straightforward API 11 | 12 | ## Feature overview 13 | 14 | ### What's supported 15 | 16 | - Commands with colon (hierarchical header model) 17 | - Semicolon for chaining commands on the same level 18 | - Long and short command variants (eg. `SYSTem?`) 19 | - String, Int, Float, Bool, CharData arguments 20 | - **Block data argument** with callback each N received bytes - allows virtually unlimite binary data length 21 | - Status Registers 22 | - Error queue with error numbers and messages (and the required SYST:ERR subsystem) 23 | 24 | Built-in commands can be overriden by matching user commands. 25 | 26 | ### Limitations 27 | 28 | - Binary block must be the last argument of a command (callback is run after reading the binary block preamble) 29 | 30 | ### What's missing 31 | 32 | - Number units (mV,V,kW,A,Ohm) and metric suffixes (k,M,G,m,u,n) 33 | - DEF, MIN, MAX, INF, NINF argument values for numbers 34 | - Numbered virtual instruments (DAC1:OUT, DAC2:OUT) - currently you need to define the commands twice 35 | 36 | Feel free to propose a pull request implementing any missing features. 37 | 38 | 39 | ## How to use it 40 | 41 | To test it with regular gcc, run the Makefile in the `example` directory. 42 | 43 | The main Makefile builds a library for ARM Cortex M4 (can be easily adjusted for others). 44 | 45 | ### Stubs to implement 46 | 47 | Here's an overview of stubs you have to implement (at the time of writing this readme): 48 | 49 | ```c 50 | #include "scpi.h" 51 | 52 | // Receive byte - call: 53 | // scpi_handle_byte() 54 | // scpi_handle_string() 55 | 56 | 57 | // ---- DEVICE IMPLEMENTATION ---- 58 | 59 | const SCPI_error_desc scpi_user_errors[] = { 60 | {10, "Custom error"}, // add your custom errors here (positive numbers) 61 | {/*END*/} // <-- end marker 62 | }; 63 | 64 | 65 | void scpi_send_byte_impl(uint8_t b) 66 | { 67 | // send the byte to master (over UART?) 68 | } 69 | 70 | 71 | const char *scpi_device_identifier(void) 72 | { 73 | // fill in your device info 74 | // possible to eg. read a serial # from EEPROM 75 | return ",,,"; 76 | } 77 | 78 | 79 | /* Custom commands */ 80 | const SCPI_command_t scpi_commands[] = { 81 | // see the struct definition for more details. Examples: 82 | { 83 | .levels = {"APPLy", "SINe"}, 84 | .params = {SCPI_DT_INT, SCPI_DT_FLOAT, SCPI_DT_FLOAT}, 85 | .callback = cmd_APPL_SIN_cb 86 | }, 87 | { 88 | .levels = {"DATA", "BLOB"}, 89 | .params = {SCPI_DT_BLOB}, 90 | .callback = cmd_DATA_BLOB_cb, // <-- command callback 91 | .blob_chunk = 4, 92 | .blob_callback = cmd_DATA_BLOB_data // <-- data callback 93 | }, 94 | {/*END*/} // <-- important! Marks end of the array 95 | }; 96 | 97 | // ---- OPTIONAL CALLBACKS ---- 98 | 99 | void scpi_service_request_impl(void) 100 | { 101 | // Called when the SRQ flag in Status Byte is set. 102 | // Device should somehow send the request to master. 103 | } 104 | 105 | // Device specific implementation of common commands 106 | // (status registers etc are handled internally) 107 | void scpi_user_CLS(void) { /*...*/ } 108 | void scpi_user_RST(void) { /*...*/ } 109 | void scpi_user_TSTq(void) { /*...*/ } 110 | 111 | ``` 112 | 113 | See the header files for more info. 114 | -------------------------------------------------------------------------------- /errorgen.php: -------------------------------------------------------------------------------- 1 | 'CMD', 160 | -200 => 'EXE', 161 | -300 => 'DEV', 162 | ]; 163 | 164 | foreach($lines as $a) 165 | { 166 | $a = trim($a); 167 | if($a == '') continue; 168 | 169 | list($num, $name) = explode("\t", $a); 170 | 171 | $pfx = ''; $ii=($num - $num%100); 172 | if (isset($prefixes[$ii]) && $num%100!=0) { 173 | $pfx = $prefixes[$ii].'_'; 174 | } 175 | 176 | $enum = str_replace(' ', '_', strtoupper($name)); 177 | $enum = 'E_' . $pfx . preg_replace("/[^A-Z0-9_]/", "_", $enum); 178 | 179 | //echo "\t$enum = $num,\n"; 180 | 181 | echo "\t{"."$num, \"$name\"},\n"; 182 | } 183 | -------------------------------------------------------------------------------- /example/Makefile: -------------------------------------------------------------------------------- 1 | SRC = example.c 2 | SRC += ../source/scpi_parser.c 3 | SRC += ../source/scpi_regs.c 4 | SRC += ../source/scpi_builtins.c 5 | SRC += ../source/scpi_errors.c 6 | 7 | INCL_DIR = ../include 8 | 9 | CFLAGS = -std=gnu99 10 | CFLAGS += -Wall -Wextra -Wshadow 11 | CFLAGS += -Wwrite-strings -Wold-style-definition -Winline 12 | CFLAGS += -Wredundant-decls -Wfloat-equal -Wsign-compare -Wunused-function 13 | 14 | CFLAGS += -DSCPI_FINE_ERRORS 15 | 16 | CC = gcc 17 | 18 | %.o: %.c 19 | 20 | 21 | all: example.elf 22 | 23 | example.elf: $(SRC) 24 | $(Q)$(CC) $(CFLAGS) -I$(INCL_DIR) -o example.elf $(SRC) 25 | 26 | run: example.elf 27 | ./example.elf 28 | 29 | clean: 30 | rm -f *.o *.d *.so *.elf *.bin *.hex 31 | cd ../source 32 | rm -f *.o *.d *.so *.elf *.bin *.hex 33 | -------------------------------------------------------------------------------- /example/example.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "scpi.h" 6 | 7 | // ------- TESTING ---------- 8 | 9 | static void send_cmd(const char *cmd) 10 | { 11 | printf("\n> %s\n", cmd); 12 | scpi_handle_string(cmd); 13 | } 14 | 15 | int main(void) 16 | { 17 | send_cmd("*IDN?\n"); // builtin commands.. 18 | send_cmd("*SRE 4\n"); // enable SRQ on error 19 | send_cmd("FOO:BAR:BAZ\n"); // invalid command causes error 20 | send_cmd("SYST:ERR:COUNT?\n"); // error subsystem 21 | send_cmd("SYST:ERR:NEXT?; COUNT?; NEXT?\n"); // semicolon works according to spec 22 | send_cmd("DATA:BLOB #216abcdefghijklmnop\n"); // binary data block 23 | 24 | send_cmd("APPLY:SINE 50, 1.0, 2.17\n"); // floats 25 | send_cmd("DISP:TEXT \"Hello world\"\n"); // string 26 | 27 | // test user error 28 | send_cmd("*CLS\n"); // string 29 | send_cmd("USERERROR\n"); // string 30 | send_cmd("SYST:ERR:COUNT?\n"); 31 | send_cmd("SYST:ERR:NEXT?\n"); 32 | 33 | send_cmd("ERROR_FALLBACK\n"); // test fallback to closest related error 34 | 35 | // test chardata 36 | send_cmd("CHARD FOOBAR123_MOO_abcdef_HELLO, 12\n"); 37 | 38 | send_cmd("SINGLE_STR_ARG \n"); 39 | 40 | send_cmd("SYST:ERR:ALL?\n"); 41 | } 42 | 43 | 44 | // ---- Test device impl ---- 45 | 46 | // Newline sequence for responses. 47 | // Device accepts botn \r\n and \n in incomming messages. 48 | const char *scpi_eol = "\r\n"; 49 | 50 | const SCPI_error_desc scpi_user_errors[] = { 51 | {10, "Custom error"}, 52 | {0} // terminator 53 | }; 54 | 55 | 56 | void scpi_send_byte_impl(uint8_t b) 57 | { 58 | putchar(b); // device sends a byte 59 | } 60 | 61 | 62 | const char *scpi_user_IDN(void) 63 | { 64 | return "MightyPork,Test SCPI device,0,0.1"; 65 | } 66 | 67 | 68 | /** Error callback */ 69 | void scpi_user_error(int16_t errno, const char * msg) 70 | { 71 | printf("### ERROR ADDED: %d, %s ###\n", errno, msg); 72 | } 73 | 74 | 75 | /** Service request impl */ 76 | void scpi_user_SRQ(void) 77 | { 78 | // NOTE: Actual instrument should send SRQ event somehow 79 | 80 | printf("[Service Request]\n"); 81 | } 82 | 83 | 84 | // ---- INSTRUMENT COMMANDS ---- 85 | 86 | void cmd_APPL_SIN_cb(const SCPI_argval_t *args) 87 | { 88 | printf("cb APPLy:SINe %d, %f, %f\n", args[0].INT, args[1].FLOAT, args[2].FLOAT); 89 | } 90 | 91 | 92 | 93 | void cmd_DISP_TEXT_cb(const SCPI_argval_t *args) 94 | { 95 | printf("cb DISPlay:TEXT %s\n", args[0].STRING); 96 | } 97 | 98 | 99 | void cmd_DATA_BLOB_cb(const SCPI_argval_t *args) 100 | { 101 | printf("cb DATA:BLOB <%d>\n", args[0].BLOB_LEN); 102 | } 103 | 104 | 105 | void cmd_DATA_BLOB_data(const uint8_t *bytes) 106 | { 107 | printf("binary data: \"%s\"\n", bytes); 108 | } 109 | 110 | 111 | void cmd_USRERR_cb(const SCPI_argval_t *args) 112 | { 113 | (void) args; 114 | 115 | printf("cb USRERR - raising user error 10.\n"); 116 | scpi_add_error(10, "Custom error message..."); 117 | } 118 | 119 | 120 | void cmd_CHARD_cb(const SCPI_argval_t *args) 121 | { 122 | printf("CHARData cb: %s, arg2 = %d\n", args[0].CHARDATA, args[1].INT); 123 | } 124 | 125 | 126 | void cmd_ERROR_FALLBACK_cb(const SCPI_argval_t *args) 127 | { 128 | (void) args; 129 | 130 | printf("Testing the error fallback feature...\n"); 131 | 132 | scpi_add_error(-427, NULL); 133 | } 134 | 135 | 136 | void cmd_SINGLE_STR_ARG_cb(const SCPI_argval_t *args) 137 | { 138 | (void) args; 139 | 140 | printf("Single str arg = %s", args[0].STRING); 141 | } 142 | 143 | // Command definition (mandatory commands are built-in) 144 | const SCPI_command_t scpi_commands[] = { 145 | { 146 | .levels = {"APPLy", "SINe"}, 147 | .params = {SCPI_DT_INT, SCPI_DT_FLOAT, SCPI_DT_FLOAT}, 148 | .callback = cmd_APPL_SIN_cb 149 | }, 150 | { 151 | .levels = {"DISPlay", "TEXT"}, 152 | .params = {SCPI_DT_STRING}, 153 | .callback = cmd_DISP_TEXT_cb 154 | }, 155 | { 156 | .levels = {"DATA", "BLOB"}, 157 | .params = {SCPI_DT_BLOB}, 158 | .callback = cmd_DATA_BLOB_cb, 159 | .blob_chunk = 4, 160 | .blob_callback = cmd_DATA_BLOB_data 161 | }, 162 | { 163 | .levels = {"USeRERRor"}, 164 | .callback = cmd_USRERR_cb 165 | }, 166 | { 167 | .levels = {"ERROR_FALLBACK"}, 168 | .callback = cmd_ERROR_FALLBACK_cb 169 | }, 170 | { 171 | .levels = {"SINGLE_STR_ARG"}, 172 | .params = {SCPI_DT_STRING}, 173 | .callback = cmd_SINGLE_STR_ARG_cb 174 | }, 175 | { 176 | .levels = {"CHARData"}, 177 | .params = {SCPI_DT_CHARDATA, SCPI_DT_INT}, 178 | .callback = cmd_CHARD_cb 179 | }, 180 | {/*END*/} 181 | }; 182 | -------------------------------------------------------------------------------- /include/scpi.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Include this file when using the library 4 | 5 | #include "scpi_regs.h" 6 | #include "scpi_errors.h" 7 | #include "scpi_builtins.h" 8 | #include "scpi_parser.h" 9 | -------------------------------------------------------------------------------- /include/scpi_builtins.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | /** Optional *CLS command callback - clear non-SCPI device state */ 6 | extern __attribute__((weak)) void scpi_user_CLS(void); 7 | 8 | /** Optional *RST command callback - reset non-SCPI device state */ 9 | extern __attribute__((weak)) void scpi_user_RST(void); 10 | 11 | /** Optional *TST? command callback - perform self test and send response back. */ 12 | extern __attribute__((weak)) void scpi_user_TST(void); 13 | 14 | /** MANDATORY callback to get the device *IDN? string. */ 15 | extern const char *scpi_user_IDN(void); 16 | 17 | // Provides: 18 | // const SCPI_command_t scpi_commands_builtin[]; 19 | -------------------------------------------------------------------------------- /include/scpi_errors.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | typedef struct { 6 | const int16_t errno; 7 | const char *msg; 8 | } SCPI_error_desc; 9 | 10 | /** User error definitions */ 11 | extern const SCPI_error_desc scpi_user_errors[]; 12 | 13 | 14 | /** 15 | * Callback when error is added to the queue 16 | * 17 | * @param errno error code 18 | * @param msg error string in the canonical format , 19 | */ 20 | extern __attribute__((weak)) 21 | void scpi_user_error(int16_t errno, const char * error_string); 22 | 23 | 24 | // SCPI error constants 25 | enum { 26 | E_NO_ERROR = 0, 27 | E_COMMAND_ERROR = -100, 28 | E_CMD_INVALID_CHARACTER = -101, 29 | E_CMD_SYNTAX_ERROR = -102, 30 | E_CMD_INVALID_SEPARATOR = -103, 31 | E_CMD_DATA_TYPE_ERROR = -104, 32 | E_CMD_GET_NOT_ALLOWED = -105, 33 | E_CMD_PARAMETER_NOT_ALLOWED = -108, 34 | E_CMD_MISSING_PARAMETER = -109, 35 | E_CMD_COMMAND_HEADER_ERROR = -110, 36 | E_CMD_HEADER_SEPARATOR_ERROR = -111, 37 | E_CMD_PROGRAM_MNEMONIC_TOO_LONG = -112, 38 | E_CMD_UNDEFINED_HEADER = -113, 39 | E_CMD_HEADER_SUFFIX_OUT_OF_RANGE = -114, 40 | E_CMD_UNEXPECTED_NUMBER_OF_PARAMETERS = -115, 41 | E_CMD_NUMERIC_DATA_ERROR = -120, 42 | E_CMD_INVALID_CHARACTER_IN_NUMBER = -121, 43 | E_CMD_EXPONENT_TOO_LARGE = -123, 44 | E_CMD_TOO_MANY_DIGITS = -124, 45 | E_CMD_NUMERIC_DATA_NOT_ALLOWED = -128, 46 | E_CMD_SUFFIX_ERROR = -130, 47 | E_CMD_INVALID_SUFFIX = -131, 48 | E_CMD_SUFFIX_TOO_LONG = -134, 49 | E_CMD_SUFFIX_NOT_ALLOWED = -138, 50 | E_CMD_CHARACTER_DATA_ERROR = -140, 51 | E_CMD_INVALID_CHARACTER_DATA = -141, 52 | E_CMD_CHARACTER_DATA_TOO_LONG = -144, 53 | E_CMD_CHARACTER_DATA_NOT_ALLOWED = -148, 54 | E_CMD_STRING_DATA_ERROR = -150, 55 | E_CMD_INVALID_STRING_DATA = -151, 56 | E_CMD_STRING_DATA_NOT_ALLOWED = -158, 57 | E_CMD_BLOCK_DATA_ERROR = -160, 58 | E_CMD_INVALID_BLOCK_DATA = -161, 59 | E_CMD_BLOCK_DATA_NOT_ALLOWED = -168, 60 | E_CMD_EXPRESSION_ERROR = -170, 61 | E_CMD_INVALID_EXPRESSION = -171, 62 | E_CMD_EXPRESSION_DATA_NOT_ALLOWED = -178, 63 | E_CMD_MACRO_ERROR = -180, 64 | E_CMD_INVALID_OUTSIDE_MACRO_DEFINITION = -181, 65 | E_CMD_INVALID_INSIDE_MACRO_DEFINITION = -183, 66 | E_CMD_MACRO_PARAMETER_ERROR = -184, 67 | E_EXECUTION_ERROR = -200, 68 | E_EXE_INVALID_WHILE_IN_LOCAL = -201, 69 | E_EXE_SETTINGS_LOST_DUE_TO_RTL = -202, 70 | E_EXE_COMMAND_PROTECTED = -203, 71 | E_EXE_TRIGGER_ERROR = -210, 72 | E_EXE_TRIGGER_IGNORED = -211, 73 | E_EXE_ARM_IGNORED = -212, 74 | E_EXE_INIT_IGNORED = -213, 75 | E_EXE_TRIGGER_DEADLOCK = -214, 76 | E_EXE_ARM_DEADLOCK = -215, 77 | E_EXE_PARAMETER_ERROR = -220, 78 | E_EXE_SETTINGS_CONFLICT = -221, 79 | E_EXE_DATA_OUT_OF_RANGE = -222, 80 | E_EXE_TOO_MUCH_DATA = -223, 81 | E_EXE_ILLEGAL_PARAMETER_VALUE = -224, 82 | E_EXE_OUT_OF_MEMORY = -225, 83 | E_EXE_LISTS_NOT_SAME_LENGTH = -226, 84 | E_EXE_DATA_CORRUPT_OR_STALE = -230, 85 | E_EXE_DATA_QUESTIONABLE = -231, 86 | E_EXE_INVALID_FORMAT = -232, 87 | E_EXE_INVALID_VERSION = -233, 88 | E_EXE_HARDWARE_ERROR = -240, 89 | E_EXE_HARDWARE_MISSING = -241, 90 | E_EXE_MASS_STORAGE_ERROR = -250, 91 | E_EXE_MISSING_MASS_STORAGE = -251, 92 | E_EXE_MISSING_MEDIA = -252, 93 | E_EXE_CORRUPT_MEDIA = -253, 94 | E_EXE_MEDIA_FULL = -254, 95 | E_EXE_DIRECTORY_FULL = -255, 96 | E_EXE_FILE_NAME_NOT_FOUND = -256, 97 | E_EXE_FILE_NAME_ERROR = -257, 98 | E_EXE_MEDIA_PROTECTED = -258, 99 | E_EXE_EXPRESSION_ERROR = -260, 100 | E_EXE_MATH_ERROR_IN_EXPRESSION = -261, 101 | E_EXE_MACRO_ERROR = -270, 102 | E_EXE_MACRO_SYNTAX_ERROR = -271, 103 | E_EXE_MACRO_EXECUTION_ERROR = -272, 104 | E_EXE_ILLEGAL_MACRO_LABEL = -273, 105 | E_EXE_MACRO_PARAMETER_ERROR = -274, 106 | E_EXE_MACRO_DEFINITION_TOO_LONG = -275, 107 | E_EXE_MACRO_RECURSION_ERROR = -276, 108 | E_EXE_MACRO_REDEFINITION_NOT_ALLOWED = -277, 109 | E_EXE_MACRO_HEADER_NOT_FOUND = -278, 110 | E_EXE_PROGRAM_ERROR = -280, 111 | E_EXE_CANNOT_CREATE_PROGRAM = -281, 112 | E_EXE_ILLEGAL_PROGRAM_NAME = -282, 113 | E_EXE_ILLEGAL_VARIABLE_NAME = -283, 114 | E_EXE_PROGRAM_CURRENTLY_RUNNING = -284, 115 | E_EXE_PROGRAM_SYNTAX_ERROR = -285, 116 | E_EXE_PROGRAM_RUNTIME_ERROR = -286, 117 | E_EXE_MEMORY_USE_ERROR = -290, 118 | E_EXE_OUT_OF_MEMORY_291 = -291, 119 | E_EXE_REFERENCED_NAME_DOES_NOT_EXIST = -292, 120 | E_EXE_REFERENCED_NAME_ALREADY_EXISTS = -293, 121 | E_EXE_INCOMPATIBLE_TYPE = -294, 122 | E_DEVICE_SPECIFIC_ERROR = -300, 123 | E_DEV_SYSTEM_ERROR = -310, 124 | E_DEV_MEMORY_ERROR = -311, 125 | E_DEV_PUD_MEMORY_LOST = -312, 126 | E_DEV_CALIBRATION_MEMORY_LOST = -313, 127 | E_DEV_SAVE_RECALL_MEMORY_LOST = -314, 128 | E_DEV_CONFIGURATION_MEMORY_LOST = -315, 129 | E_DEV_STORAGE_FAULT = -320, 130 | E_DEV_OUT_OF_MEMORY = -321, 131 | E_DEV_SELF_TEST_FAILED = -330, 132 | E_DEV_CALIBRATION_FAILED = -340, 133 | E_DEV_QUEUE_OVERFLOW = -350, 134 | E_DEV_COMMUNICATION_ERROR = -360, 135 | E_DEV_PARITY_ERROR_IN_PROGRAM_MESSAGE = -361, 136 | E_DEV_FRAMING_ERROR_IN_PROGRAM_MESSAGE = -362, 137 | E_DEV_INPUT_BUFFER_OVERRUN = -363, 138 | E_DEV_TIME_OUT_ERROR = -365, 139 | E_QUERY_ERROR = -400, 140 | E_QUERY_INTERRUPTED = -410, 141 | E_QUERY_UNTERMINATED = -420, 142 | E_QUERY_DEADLOCKED = -430, 143 | E_QUERY_UNTERMINATED_AFTER_INDEFINITE_RESPONSE = -440, 144 | E_POWER_ON = -500, 145 | E_USER_REQUEST = -600, 146 | E_REQUEST_CONTROL = -700, 147 | E_OPERATION_COMPLETE = -800, 148 | }; 149 | 150 | 151 | 152 | /** 153 | * Get SCPI error string: 154 | * ,"[; ]" 155 | * 156 | * @param buffer Buffer for storing the final string. Make sure it's big enough. 157 | * @param errno Error number 158 | * @param extra Extra information, appended after the generic message. Can be NULL. 159 | * 160 | * @returns actual error code. Code may be coerced to closest defined code (categories: tens, hundreds) 161 | */ 162 | int16_t scpi_error_string(char *buffer, int16_t errno, const char *extra); 163 | 164 | 165 | /** Add error to the error queue */ 166 | void scpi_add_error(int16_t errno, const char *extra); 167 | 168 | 169 | /** Get number of errors in the error queue */ 170 | uint8_t scpi_error_count(void); 171 | 172 | 173 | /** 174 | * Read and remove one entry from the error queue. 175 | * Returns 0,"No error" if the queue is empty. 176 | * 177 | * The entry is copied to the provided buffer, which must be 256 chars long. 178 | */ 179 | void scpi_read_error(char *buf); 180 | 181 | 182 | /** Read error, do not remove from queue */ 183 | void scpi_read_error_noremove(char *buf); 184 | -------------------------------------------------------------------------------- /include/scpi_parser.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | #define SCPI_MAX_CMD_LEN 16 // 12 according to spec 6 | #define SCPI_MAX_STRING_LEN 64 // 12 according to spec 7 | #define SCPI_MAX_LEVEL_COUNT 4 8 | #define SCPI_MAX_PARAM_COUNT 4 9 | 10 | /** Argument data types */ 11 | typedef enum { 12 | SCPI_DT_NONE = 0, 13 | SCPI_DT_FLOAT, // float with support for scientific notation 14 | SCPI_DT_INT, // integer (may be signed) 15 | SCPI_DT_BOOL, // 0, 1, ON, OFF 16 | SCPI_DT_CHARDATA, // string without quotes - [A-Za-z0-9_] 17 | SCPI_DT_STRING, // quoted string, max 12 chars; no escapes. 18 | SCPI_DT_BLOB, // binary block, callback: uint32_t holding number of bytes 19 | } SCPI_datatype_t; 20 | 21 | 22 | /** Arguemnt value (union) */ 23 | typedef union { 24 | float FLOAT; 25 | 26 | int32_t INT; 27 | uint32_t BLOB_LEN; 28 | 29 | bool BOOL; 30 | 31 | char STRING[SCPI_MAX_STRING_LEN + 1]; 32 | char CHARDATA[SCPI_MAX_STRING_LEN + 1]; 33 | } SCPI_argval_t; 34 | 35 | 36 | // ------ CONFIGURATION -------- 37 | 38 | /** 39 | * SCPI command preset 40 | * NOTE: command array is terminated by {0} - zero in levels[0][0] 41 | */ 42 | typedef struct { 43 | // levels MUST BE FIRST! 44 | const char levels[SCPI_MAX_LEVEL_COUNT][SCPI_MAX_CMD_LEN + 2]; // up to 4 parts (+? and \0) 45 | 46 | // called when the command is completed. BLOB arg must be last in the argument list, 47 | // and only the first part is collected. 48 | void (*callback)(const SCPI_argval_t *args); 49 | 50 | // Param types - optional (defaults to zeros) 51 | const SCPI_datatype_t params[SCPI_MAX_PARAM_COUNT]; // parameter types (0 for unused) 52 | 53 | // --- OPTIONAL (only for blob) --- 54 | 55 | // Number of bytes in a blob callback 56 | const uint8_t blob_chunk; 57 | // Blob chunk callback (every blob_chunk bytes) 58 | void (*blob_callback)(const uint8_t *bytes); 59 | } SCPI_command_t; 60 | 61 | 62 | // ---------------- USER CONFIG ---------------- 63 | 64 | /** Zero terminated command struct array - must be defined. */ 65 | extern const SCPI_command_t scpi_commands[]; 66 | 67 | /** Built-in SCPI commands, provided by scpi_builtins.h */ 68 | extern const SCPI_command_t scpi_commands_builtin[]; 69 | 70 | /** Send a byte to master (may be buffered) */ 71 | extern void scpi_send_byte_impl(uint8_t b); 72 | 73 | /** Character sequence used as a newline in responses. */ 74 | extern const char *scpi_eol; 75 | 76 | 77 | // --------------- functions -------------------- 78 | 79 | /** 80 | * SCPI parser entry point. 81 | * All incoming bytes should be sent to this function. 82 | */ 83 | void scpi_handle_byte(const uint8_t b); 84 | 85 | /** 86 | * SCPI parser - handle a string (multiple chars) at once. 87 | * String is interpreted as is, nothing is added. Must be terminated with \0. 88 | */ 89 | void scpi_handle_string(const char* str); 90 | 91 | 92 | /** Discard the rest of the currently processed blob */ 93 | void scpi_discard_blob(void); 94 | 95 | /** Send a string to master. \r\n is added. */ 96 | void scpi_send_string(const char *message); 97 | 98 | /** Send a string without a line terminator */ 99 | void scpi_send_string_raw(const char *message); 100 | 101 | /** Clear the error queue */ 102 | void scpi_clear_errors(void); 103 | 104 | -------------------------------------------------------------------------------- /include/scpi_regs.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | typedef union { 6 | struct __attribute__((packed)) { 7 | bool VOLT: 1; 8 | bool CURR: 1; 9 | bool TIME: 1; 10 | bool POWER: 1; 11 | bool TEMP: 1; 12 | bool FREQ: 1; 13 | bool PHASE: 1; 14 | bool MODUL: 1; 15 | bool CALIB: 1; 16 | bool BIT_9: 1; // user defined 17 | bool BIT_10: 1; 18 | bool BIT_11: 1; 19 | bool BIT_12: 1; 20 | bool INSTR_SUM: 1; // instrument summary 21 | bool COMMAND_WARNING: 1; // command warning 22 | bool RESERVED: 1; 23 | }; 24 | 25 | uint16_t u16; 26 | } SCPI_REG_QUES_t; 27 | 28 | 29 | typedef union { 30 | struct __attribute__((packed)) { 31 | bool CALIB: 1; 32 | bool SETTING: 1; 33 | bool RANGING: 1; 34 | bool SWEEP: 1; 35 | bool MEAS: 1; 36 | bool WAIT_TRIG: 1; // waiting for trigger 37 | bool WAIT_ARM: 1; // waiting for ARM 38 | bool CORRECTING: 1; 39 | bool BIT_8: 1; // user defined 40 | bool BIT_9: 1; 41 | bool BIT_10: 1; 42 | bool BIT_11: 1; 43 | bool BIT_12: 1; 44 | bool INSTR_SUM: 1; // instrument summary 45 | bool PROG_RUN: 1; // program running 46 | bool RESERVED: 1; 47 | }; 48 | 49 | uint16_t u16; 50 | } SCPI_REG_OPER_t; 51 | 52 | 53 | typedef union { 54 | struct __attribute__((packed)) { 55 | bool OPC: 1; // operation complete - only for instruments with overlapping commands 56 | bool REQ_CONTROL: 1; // GPIB-only 57 | bool QUERY_ERROR: 1; 58 | bool DEV_ERROR: 1; 59 | bool EXE_ERROR: 1; 60 | bool CMD_ERROR: 1; 61 | bool USER_REQUEST: 1; 62 | bool POWER_ON: 1; 63 | }; 64 | 65 | uint8_t u8; 66 | } SCPI_REG_SESR_t; 67 | 68 | 69 | typedef union { 70 | struct __attribute__((packed)) { 71 | bool BIT_0: 1; 72 | bool BIT_1: 1; 73 | bool ERRQ: 1; // error queue 74 | bool QUES: 1; 75 | bool MAV: 1; // message available 76 | bool SESR: 1; 77 | bool RQS: 1; // request service 78 | bool OPER: 1; 79 | }; 80 | 81 | uint8_t u8; 82 | } SCPI_REG_STB_t; 83 | 84 | 85 | // QUESTionable register 86 | extern SCPI_REG_QUES_t SCPI_REG_QUES; 87 | extern SCPI_REG_QUES_t SCPI_REG_QUES_EN; // picks what to use for the STB bit 88 | 89 | // OPERation status register 90 | extern SCPI_REG_OPER_t SCPI_REG_OPER; 91 | extern SCPI_REG_OPER_t SCPI_REG_OPER_EN; // picks what to use for the STB bit 92 | 93 | // Standard Event Status register 94 | extern SCPI_REG_SESR_t SCPI_REG_SESR; 95 | extern SCPI_REG_SESR_t SCPI_REG_SESR_EN; // ESE 96 | 97 | // Status byte 98 | extern SCPI_REG_STB_t SCPI_REG_STB; 99 | extern SCPI_REG_STB_t SCPI_REG_SRE; // SRE 100 | 101 | 102 | /** Update the status registers (perform propagation) */ 103 | void scpi_status_update(void); 104 | 105 | 106 | /** 107 | * Service Request callback. 108 | * User may choose to implement it (eg. send request to master), 109 | * or leave unimplemented. 110 | * 111 | * SRQ is issued when an event enabled in the status registers (namely SRE) occurs. 112 | * See the SCPI spec for details. 113 | */ 114 | extern __attribute__((weak)) void scpi_user_SRQ(void); 115 | -------------------------------------------------------------------------------- /lib/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MightyPork/scpi_parser/14c7ec1c041fa3c12d99eeb21ed69f7a303ba3d1/lib/.gitkeep -------------------------------------------------------------------------------- /scpi.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = app 2 | CONFIG += console 3 | CONFIG -= app_bundle 4 | CONFIG -= qt 5 | 6 | DEFINES += __null=0 7 | 8 | INCLUDEPATH += source/ \ 9 | include/ \ 10 | /usr/arm-none-eabi/include \ 11 | /usr/lib/gcc/x86_64-unknown-linux-gnu/5.3.0/plugin/include 12 | 13 | SOURCES += \ 14 | main.c \ 15 | source/scpi_parser.c \ 16 | source/scpi_errors.c \ 17 | source/scpi_regs.c \ 18 | source/scpi_builtins.c \ 19 | example/example.c 20 | 21 | DISTFILES += \ 22 | style.astylerc \ 23 | .gitignore \ 24 | LICENSE \ 25 | README.md \ 26 | Makefile \ 27 | example/Makefile 28 | 29 | HEADERS += \ 30 | source/scpi_parser.h \ 31 | source/scpi_errors.h \ 32 | source/scpi_regs.h \ 33 | source/scpi_builtins.h \ 34 | include/scpi_builtins.h \ 35 | include/scpi_errors.h \ 36 | include/scpi_parser.h \ 37 | include/scpi_regs.h \ 38 | include/scpi.h 39 | -------------------------------------------------------------------------------- /source/scpi_builtins.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "scpi_builtins.h" 7 | #include "scpi_parser.h" 8 | #include "scpi_errors.h" 9 | #include "scpi_regs.h" 10 | 11 | // response buffer 12 | static char sbuf[256]; // must be long enough to contain an error message 13 | 14 | 15 | // ---------------- BUILTIN SCPI COMMANDS ------------------ 16 | 17 | static void builtin_CLS(const SCPI_argval_t *args) 18 | { 19 | (void)args; 20 | 21 | // clear the registers 22 | SCPI_REG_SESR.u8 = 0; 23 | SCPI_REG_OPER.u16 = 0; 24 | SCPI_REG_QUES.u16 = 0; 25 | scpi_clear_errors(); 26 | 27 | if (scpi_user_CLS) { 28 | scpi_user_CLS(); 29 | } 30 | 31 | scpi_status_update(); // flags 32 | } 33 | 34 | 35 | static void builtin_RST(const SCPI_argval_t *args) 36 | { 37 | (void)args; 38 | 39 | if (scpi_user_RST) { 40 | scpi_user_RST(); 41 | } 42 | } 43 | 44 | 45 | static void builtin_TSTq(const SCPI_argval_t *args) 46 | { 47 | (void)args; 48 | 49 | if (scpi_user_TST) { 50 | scpi_user_TST(); 51 | } 52 | } 53 | 54 | 55 | static void builtin_IDNq(const SCPI_argval_t *args) 56 | { 57 | (void)args; 58 | 59 | scpi_send_string(scpi_user_IDN()); 60 | } 61 | 62 | 63 | static void builtin_ESE(const SCPI_argval_t *args) 64 | { 65 | SCPI_REG_SESR_EN.u8 = (uint8_t) args[0].INT; 66 | scpi_status_update(); 67 | } 68 | 69 | 70 | static void builtin_ESEq(const SCPI_argval_t *args) 71 | { 72 | (void)args; 73 | 74 | sprintf(sbuf, "%d", SCPI_REG_SESR_EN.u8); 75 | scpi_send_string(sbuf); 76 | } 77 | 78 | 79 | static void builtin_ESRq(const SCPI_argval_t *args) 80 | { 81 | (void)args; 82 | 83 | sprintf(sbuf, "%d", SCPI_REG_SESR.u8); 84 | scpi_send_string(sbuf); 85 | 86 | SCPI_REG_SESR.u8 = 0; // register cleared 87 | scpi_status_update(); 88 | } 89 | 90 | 91 | static void builtin_OPC(const SCPI_argval_t *args) 92 | { 93 | (void)args; 94 | 95 | // implementation for instruments with no overlapping commands. 96 | // Can be overridden in the user commands. 97 | SCPI_REG_SESR.OPC = 1; 98 | scpi_status_update(); 99 | } 100 | 101 | 102 | static void builtin_OPCq(const SCPI_argval_t *args) 103 | { 104 | (void)args; 105 | 106 | // implementation for instruments with no overlapping commands. 107 | // Can be overridden in the user commands. 108 | // (would be): sprintf(sbuf, "%d", SCPI_REG_SESR.OPC); 109 | 110 | scpi_send_string("1"); 111 | } 112 | 113 | 114 | static void builtin_SRE(const SCPI_argval_t *args) 115 | { 116 | SCPI_REG_SRE.u8 = (uint8_t) args[0].INT; 117 | scpi_status_update(); 118 | } 119 | 120 | 121 | static void builtin_SREq(const SCPI_argval_t *args) 122 | { 123 | (void)args; 124 | 125 | sprintf(sbuf, "%d", SCPI_REG_SRE.u8); 126 | scpi_send_string(sbuf); 127 | } 128 | 129 | 130 | static void builtin_STBq(const SCPI_argval_t *args) 131 | { 132 | (void)args; 133 | 134 | sprintf(sbuf, "%d", SCPI_REG_STB.u8); 135 | scpi_send_string(sbuf); 136 | } 137 | 138 | 139 | static void builtin_WAI(const SCPI_argval_t *args) 140 | { 141 | (void)args; 142 | 143 | // no-op 144 | } 145 | 146 | 147 | static void builtin_SYST_ERR_NEXTq(const SCPI_argval_t *args) 148 | { 149 | (void)args; 150 | 151 | scpi_read_error(sbuf); 152 | scpi_send_string(sbuf); 153 | } 154 | 155 | 156 | // optional 157 | static void builtin_SYST_ERR_ALLq(const SCPI_argval_t *args) 158 | { 159 | (void)args; 160 | 161 | if (scpi_error_count()) { 162 | int cnt = 0; 163 | while (scpi_error_count()) { 164 | scpi_read_error(sbuf); 165 | if (cnt++ > 0) scpi_send_string_raw(","); 166 | scpi_send_string_raw(sbuf); 167 | scpi_send_string_raw(scpi_eol); 168 | } 169 | } else { 170 | scpi_read_error(sbuf); // O,"No error" 171 | scpi_send_string(sbuf); 172 | } 173 | } 174 | 175 | 176 | static void builtin_SYST_ERR_CODE_NEXTq(const SCPI_argval_t *args) 177 | { 178 | (void)args; 179 | 180 | scpi_read_error(sbuf); 181 | 182 | // end at comma 183 | for (int i = 0; i < 256; i++) { 184 | if (sbuf[i] == ',') { 185 | sbuf[i] = 0; 186 | break; 187 | } 188 | } 189 | 190 | scpi_send_string(sbuf); 191 | } 192 | 193 | 194 | // optional 195 | static void builtin_SYST_ERR_CODE_ALLq(const SCPI_argval_t *args) 196 | { 197 | (void)args; 198 | 199 | int cnt = 0; 200 | while (scpi_error_count()) { 201 | scpi_read_error(sbuf); 202 | if (cnt++ > 0) scpi_send_string_raw(","); 203 | 204 | // end at comma 205 | for (int i = 0; i < 256; i++) { 206 | if (sbuf[i] == ',') { 207 | sbuf[i] = 0; 208 | break; 209 | } 210 | } 211 | 212 | scpi_send_string_raw(sbuf); 213 | } 214 | 215 | scpi_send_string_raw(scpi_eol); 216 | } 217 | 218 | 219 | // optional 220 | static void builtin_SYST_ERR_COUNq(const SCPI_argval_t *args) 221 | { 222 | (void)args; 223 | 224 | sprintf(sbuf, "%d", scpi_error_count()); 225 | scpi_send_string(sbuf); 226 | } 227 | 228 | 229 | // optional, custom 230 | static void builtin_SYST_ERR_CLEAR(const SCPI_argval_t *args) 231 | { 232 | (void)args; 233 | 234 | scpi_clear_errors(); 235 | } 236 | 237 | 238 | static void builtin_SYST_VERSq(const SCPI_argval_t *args) 239 | { 240 | (void)args; 241 | 242 | scpi_send_string("1999.0"); // implemented SCPI version 243 | } 244 | 245 | 246 | static void builtin_STAT_OPER_EVENq(const SCPI_argval_t *args) 247 | { 248 | (void)args; 249 | 250 | // read and clear 251 | sprintf(sbuf, "%d", SCPI_REG_OPER.u16); 252 | SCPI_REG_OPER.u16 = 0x0000; 253 | scpi_send_string(sbuf); 254 | scpi_status_update(); 255 | } 256 | 257 | 258 | static void builtin_STAT_OPER_CONDq(const SCPI_argval_t *args) 259 | { 260 | (void)args; 261 | 262 | // read and keep 263 | sprintf(sbuf, "%d", SCPI_REG_OPER.u16); 264 | scpi_send_string(sbuf); 265 | } 266 | 267 | 268 | static void builtin_STAT_OPER_ENAB(const SCPI_argval_t *args) 269 | { 270 | SCPI_REG_OPER.u16 = (uint16_t) args[0].INT; // set enable flags 271 | scpi_status_update(); 272 | } 273 | 274 | 275 | static void builtin_STAT_OPER_ENABq(const SCPI_argval_t *args) 276 | { 277 | (void)args; 278 | 279 | sprintf(sbuf, "%d", SCPI_REG_OPER_EN.u16); 280 | scpi_send_string(sbuf); 281 | } 282 | 283 | 284 | static void builtin_STAT_QUES_EVENq(const SCPI_argval_t *args) 285 | { 286 | (void)args; 287 | 288 | // read and clear 289 | sprintf(sbuf, "%d", SCPI_REG_QUES.u16); 290 | SCPI_REG_QUES.u16 = 0x0000; 291 | scpi_send_string(sbuf); 292 | scpi_status_update(); 293 | } 294 | 295 | 296 | static void builtin_STAT_QUES_CONDq(const SCPI_argval_t *args) 297 | { 298 | (void)args; 299 | 300 | // read and keep 301 | sprintf(sbuf, "%d", SCPI_REG_QUES.u16); 302 | scpi_send_string(sbuf); 303 | } 304 | 305 | 306 | static void builtin_STAT_QUES_ENAB(const SCPI_argval_t *args) 307 | { 308 | SCPI_REG_QUES.u16 = (uint16_t) args[0].INT; // set enable flags 309 | scpi_status_update(); 310 | } 311 | 312 | 313 | static void builtin_STAT_QUES_ENABq(const SCPI_argval_t *args) 314 | { 315 | (void)args; 316 | 317 | sprintf(sbuf, "%d", SCPI_REG_QUES_EN.u16); 318 | scpi_send_string(sbuf); 319 | } 320 | 321 | 322 | static void builtin_STAT_PRES(const SCPI_argval_t *args) 323 | { 324 | (void)args; 325 | 326 | // Command required by SCPI spec, useless for this SCPI implementation. 327 | // This is meant to preset transition and filter registers, which are not used here. 328 | 329 | // Do not use this command, only defined to satisfy the spec. 330 | 331 | SCPI_REG_QUES_EN.u16 = 0; 332 | SCPI_REG_OPER_EN.u16 = 0; 333 | scpi_status_update(); 334 | } 335 | 336 | 337 | const SCPI_command_t scpi_commands_builtin[] = { 338 | // ---- COMMON COMMANDS ---- 339 | { 340 | .levels = {"*CLS"}, 341 | .callback = builtin_CLS 342 | }, 343 | { 344 | .levels = {"*ESE"}, 345 | .params = {SCPI_DT_INT}, 346 | .callback = builtin_ESE 347 | }, 348 | { 349 | .levels = {"*ESE?"}, 350 | .callback = builtin_ESEq 351 | }, 352 | { 353 | .levels = {"*ESR?"}, 354 | .callback = builtin_ESRq 355 | }, 356 | { 357 | .levels = {"*IDN?"}, 358 | .callback = builtin_IDNq 359 | }, 360 | { 361 | .levels = {"*OPC"}, 362 | .callback = builtin_OPC 363 | }, 364 | { 365 | .levels = {"*OPC?"}, 366 | .callback = builtin_OPCq 367 | }, 368 | { 369 | .levels = {"*RST"}, 370 | .callback = builtin_RST 371 | }, 372 | { 373 | .levels = {"*SRE"}, 374 | .params = {SCPI_DT_INT}, 375 | .callback = builtin_SRE 376 | }, 377 | { 378 | .levels = {"*SRE?"}, 379 | .callback = builtin_SREq 380 | }, 381 | { 382 | .levels = {"*STB?"}, 383 | .callback = builtin_STBq 384 | }, 385 | { 386 | .levels = {"*WAI"}, 387 | .callback = builtin_WAI 388 | }, 389 | { 390 | .levels = {"*TST?"}, 391 | .callback = builtin_TSTq 392 | }, 393 | 394 | // ---- REQUIRED SUBSYSTEM COMMANDS ---- 395 | 396 | // SYSTem 397 | { 398 | .levels = {"SYSTem", "ERRor?"}, 399 | .callback = builtin_SYST_ERR_NEXTq 400 | }, 401 | { 402 | .levels = {"SYSTem", "ERRor", "NEXT?"}, 403 | .callback = builtin_SYST_ERR_NEXTq 404 | }, 405 | { 406 | .levels = {"SYSTem", "ERRor", "ALL?"}, // optional 407 | .callback = builtin_SYST_ERR_ALLq 408 | }, 409 | { 410 | .levels = {"SYSTem", "ERRor", "CLEAR"}, // custom, added for convenience 411 | .callback = builtin_SYST_ERR_CLEAR 412 | }, 413 | { 414 | .levels = {"SYSTem", "ERRor", "COUNt?"}, // optional 415 | .callback = builtin_SYST_ERR_COUNq 416 | }, 417 | { 418 | .levels = {"SYSTem", "ERRor", "CODE?"}, // optional 419 | .callback = builtin_SYST_ERR_CODE_NEXTq 420 | }, 421 | { 422 | .levels = {"SYSTem", "ERRor", "CODE", "NEXT?"}, // optional 423 | .callback = builtin_SYST_ERR_CODE_NEXTq 424 | }, 425 | { 426 | .levels = {"SYSTem", "ERRor", "CODE", "ALL?"}, // optional 427 | .callback = builtin_SYST_ERR_CODE_ALLq 428 | }, 429 | { 430 | .levels = {"SYSTem", "VERSion?"}, 431 | .callback = builtin_SYST_VERSq 432 | }, 433 | // STATus:OPERation 434 | { 435 | .levels = {"STATus", "OPERation?"}, 436 | .callback = builtin_STAT_OPER_EVENq 437 | }, 438 | { 439 | .levels = {"STATus", "OPERation", "EVENt?"}, 440 | .callback = builtin_STAT_OPER_EVENq 441 | }, 442 | { 443 | .levels = {"STATus", "OPERation", "CONDition?"}, 444 | .callback = builtin_STAT_OPER_CONDq 445 | }, 446 | { 447 | .levels = {"STATus", "OPERation", "ENABle"}, 448 | .params = {SCPI_DT_BOOL}, 449 | .callback = builtin_STAT_OPER_ENAB 450 | }, 451 | { 452 | .levels = {"STATus", "OPERation", "ENABle?"}, 453 | .callback = builtin_STAT_OPER_ENABq 454 | }, 455 | // STATus:QUEStionable 456 | { 457 | .levels = {"STATus", "QUEStionable?"}, 458 | .callback = builtin_STAT_QUES_EVENq 459 | }, 460 | { 461 | .levels = {"STATus", "QUEStionable", "EVENt?"}, 462 | .callback = builtin_STAT_QUES_EVENq 463 | }, 464 | { 465 | .levels = {"STATus", "QUEStionable", "CONDition?"}, 466 | .callback = builtin_STAT_QUES_CONDq 467 | }, 468 | { 469 | .levels = {"STATus", "QUEStionable", "ENABle"}, 470 | .params = {SCPI_DT_BOOL}, 471 | .callback = builtin_STAT_QUES_ENAB 472 | }, 473 | { 474 | .levels = {"STATus", "QUEStionable", "ENABle?"}, 475 | .callback = builtin_STAT_QUES_ENABq 476 | }, 477 | // STATus:PRESet 478 | { 479 | .levels = {"STATus", "PRESet"}, 480 | .callback = builtin_STAT_PRES 481 | }, 482 | 483 | {/*END*/} 484 | }; 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | -------------------------------------------------------------------------------- /source/scpi_errors.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "scpi_errors.h" 8 | #include "scpi_regs.h" 9 | 10 | #define ERR_QUEUE_LEN 4 11 | #define MAX_ERROR_LEN 150 12 | 13 | // --- queue impl --- 14 | 15 | 16 | static struct ErrorQueueStruct { 17 | char queue[ERR_QUEUE_LEN][MAX_ERROR_LEN + 1]; 18 | int8_t r_pos; 19 | int8_t w_pos; 20 | int8_t count; // signed for backtracking 21 | } erq; 22 | 23 | 24 | void scpi_add_error(int16_t errno, const char *extra) 25 | { 26 | if (erq.count >= ERR_QUEUE_LEN) { 27 | errno = E_DEV_QUEUE_OVERFLOW; 28 | extra = NULL; 29 | 30 | // backtrack 31 | erq.w_pos--; 32 | erq.count--; 33 | if (erq.w_pos < 0) { 34 | erq.w_pos = ERR_QUEUE_LEN - 1; 35 | } 36 | } 37 | 38 | // get string & coerce errno to valid value 39 | errno = scpi_error_string(erq.queue[erq.w_pos], errno, extra); 40 | 41 | // run optional user error callback 42 | if (scpi_user_error) { 43 | scpi_user_error(errno, erq.queue[erq.w_pos]); 44 | } 45 | 46 | erq.w_pos++; 47 | erq.count++; 48 | if (erq.w_pos >= ERR_QUEUE_LEN) { 49 | erq.w_pos = 0; 50 | } 51 | 52 | // error type status flags 53 | if (errno >= -499 && errno <= -400) { 54 | SCPI_REG_SESR.QUERY_ERROR = true; 55 | } else if ((errno >= -399 && errno <= -300) || errno > 0) { 56 | SCPI_REG_SESR.DEV_ERROR = true; 57 | } else if (errno >= -299 && errno <= -200) { 58 | SCPI_REG_SESR.EXE_ERROR = true; 59 | } else if (errno >= -199 && errno <= -100) { 60 | SCPI_REG_SESR.CMD_ERROR = true; 61 | } 62 | 63 | // update the error queue bit and propagate the above flags 64 | scpi_status_update(); 65 | } 66 | 67 | 68 | void scpi_read_error_noremove(char *buf) 69 | { 70 | if (erq.count == 0) { 71 | scpi_error_string(buf, E_NO_ERROR, NULL); 72 | return; 73 | } 74 | 75 | strcpy(buf, erq.queue[erq.r_pos]); 76 | } 77 | 78 | 79 | void scpi_read_error(char *buf) 80 | { 81 | if (erq.count == 0) { 82 | scpi_error_string(buf, E_NO_ERROR, NULL); 83 | return; 84 | } 85 | 86 | strcpy(buf, erq.queue[erq.r_pos++]); 87 | erq.count--; 88 | 89 | if (erq.r_pos >= ERR_QUEUE_LEN) { 90 | erq.r_pos = 0; 91 | } 92 | 93 | scpi_status_update(); 94 | } 95 | 96 | 97 | void scpi_clear_errors(void) 98 | { 99 | erq.r_pos = 0; 100 | erq.w_pos = 0; 101 | erq.count = 0; 102 | 103 | scpi_status_update(); 104 | } 105 | 106 | 107 | uint8_t scpi_error_count(void) 108 | { 109 | return erq.count; 110 | } 111 | 112 | 113 | // ---- table ---- 114 | 115 | static const SCPI_error_desc no_error_desc = {0, "No error"}; 116 | 117 | static const SCPI_error_desc scpi_std_errors[] = { 118 | { -100, "Command error"}, 119 | { -110, "Command header error"}, 120 | { -120, "Numeric data error"}, 121 | { -130, "Suffix error"}, 122 | { -140, "Character data error"}, 123 | { -150, "String data error"}, 124 | { -160, "Block data error"}, 125 | { -170, "Expression error"}, 126 | { -180, "Macro error"}, 127 | { -200, "Execution error"}, 128 | 129 | { -210, "Trigger error"}, 130 | { -220, "Parameter error"}, 131 | { -230, "Data corrupt or stale"}, 132 | { -240, "Hardware error"}, 133 | { -250, "Mass storage error"}, 134 | { -260, "Expression error"}, 135 | { -270, "Macro error"}, 136 | { -280, "Program error"}, 137 | { -290, "Memory use error"}, 138 | 139 | { -300, "Device-specific error"}, 140 | { -310, "System error"}, 141 | { -320, "Storage fault"}, 142 | { -330, "Self-test failed"}, 143 | { -340, "Calibration failed"}, 144 | { -350, "Queue overflow"}, 145 | { -360, "Communication error"}, 146 | 147 | { -400, "Query error"}, 148 | 149 | // Error codes that don't make much sense 150 | #ifdef SCPI_WEIRD_ERRORS 151 | { -410, "Query INTERRUPTED"}, 152 | { -420, "Query UNTERMINATED"}, 153 | { -430, "Query DEADLOCKED"}, 154 | { -440, "Query UNTERMINATED after indefinite response"}, 155 | { -500, "Power on"}, 156 | { -600, "User request"}, 157 | { -700, "Request control"}, 158 | { -800, "Operation complete"}, 159 | #endif 160 | 161 | // Fine error codes. 162 | // Turn off to save space 163 | #ifdef SCPI_FINE_ERRORS 164 | { -101, "Invalid character"}, 165 | { -102, "Syntax error"}, 166 | { -103, "Invalid separator"}, 167 | { -104, "Data type error"}, 168 | { -105, "GET not allowed"}, 169 | { -108, "Parameter not allowed"}, 170 | { -109, "Missing parameter"}, 171 | 172 | { -111, "Header separator error"}, 173 | { -112, "Program mnemonic too long"}, 174 | { -113, "Undefined header"}, 175 | { -114, "Header suffix out of range"}, 176 | { -115, "Unexpected number of parameters"}, 177 | 178 | { -121, "Invalid character in number"}, 179 | { -123, "Exponent too large"}, 180 | { -124, "Too many digits"}, 181 | { -128, "Numeric data not allowed"}, 182 | 183 | { -131, "Invalid suffix"}, 184 | { -134, "Suffix too long"}, 185 | { -138, "Suffix not allowed"}, 186 | 187 | { -141, "Invalid character data"}, 188 | { -144, "Character data too long"}, 189 | { -148, "Character data not allowed"}, 190 | 191 | { -151, "Invalid string data"}, 192 | { -158, "String data not allowed"}, 193 | 194 | { -161, "Invalid block data"}, 195 | { -168, "Block data not allowed"}, 196 | 197 | { -171, "Invalid expression"}, 198 | { -178, "Expression data not allowed"}, 199 | 200 | { -181, "Invalid outside macro definition"}, 201 | { -183, "Invalid inside macro definition"}, 202 | { -184, "Macro parameter error"}, 203 | 204 | { -201, "Invalid while in local"}, 205 | { -202, "Settings lost due to rtl"}, 206 | { -203, "Command protected"}, 207 | 208 | { -211, "Trigger ignored"}, 209 | { -212, "Arm ignored"}, 210 | { -213, "Init ignored"}, 211 | { -214, "Trigger deadlock"}, 212 | { -215, "Arm deadlock"}, 213 | 214 | { -221, "Settings conflict"}, 215 | { -222, "Data out of range"}, 216 | { -223, "Too much data"}, 217 | { -224, "Illegal parameter value"}, 218 | { -225, "Out of memory"}, 219 | { -226, "Lists not same length"}, 220 | 221 | { -231, "Data questionable"}, 222 | { -232, "Invalid format"}, 223 | { -233, "Invalid version"}, 224 | 225 | { -241, "Hardware missing"}, 226 | 227 | { -251, "Missing mass storage"}, 228 | { -252, "Missing media"}, 229 | { -253, "Corrupt media"}, 230 | { -254, "Media full"}, 231 | { -255, "Directory full"}, 232 | { -256, "File name not found"}, 233 | { -257, "File name error"}, 234 | { -258, "Media protected"}, 235 | 236 | { -261, "Math error in expression"}, 237 | 238 | { -271, "Macro syntax error"}, 239 | { -272, "Macro execution error"}, 240 | { -273, "Illegal macro label"}, 241 | { -274, "Macro parameter error"}, 242 | { -275, "Macro definition too long"}, 243 | { -276, "Macro recursion error"}, 244 | { -277, "Macro redefinition not allowed"}, 245 | { -278, "Macro header not found"}, 246 | 247 | { -281, "Cannot create program"}, 248 | { -282, "Illegal program name"}, 249 | { -283, "Illegal variable name"}, 250 | { -284, "Program currently running"}, 251 | { -285, "Program syntax error"}, 252 | { -286, "Program runtime error"}, 253 | 254 | { -291, "Out of memory"}, 255 | { -292, "Referenced name does not exist"}, 256 | { -293, "Referenced name already exists"}, 257 | { -294, "Incompatible type"}, 258 | 259 | { -311, "Memory error"}, 260 | { -312, "PUD memory lost"}, 261 | { -313, "Calibration memory lost"}, 262 | { -314, "Save/recall memory lost"}, 263 | { -315, "Configuration memory lost"}, 264 | 265 | { -321, "Out of memory"}, 266 | { -361, "Parity error in program message"}, 267 | { -362, "Framing error in program message"}, 268 | { -363, "Input buffer overrun"}, 269 | { -365, "Time out error"}, 270 | #endif 271 | 272 | {0} // end mark 273 | }; 274 | 275 | 276 | static const SCPI_error_desc * find_error_desc(const SCPI_error_desc *table, int16_t errno) 277 | { 278 | for (int i = 0; (i == 0 || table[i].errno != 0); i++) { 279 | if (table[i].errno == errno) { 280 | return &table[i]; 281 | } 282 | } 283 | 284 | return NULL; 285 | } 286 | 287 | 288 | static const SCPI_error_desc * resolve_error_desc(int16_t errno) 289 | { 290 | const SCPI_error_desc *desc; 291 | 292 | if (errno == 0) { 293 | // ok state 294 | return &no_error_desc; 295 | 296 | } else if (errno < 0) { 297 | // standard errors 298 | 299 | desc = find_error_desc(scpi_std_errors, errno); 300 | if (desc != NULL) return desc; 301 | 302 | // not found in table, use group-common error 303 | errno += -errno % 10; // round to ten 304 | 305 | desc = find_error_desc(scpi_std_errors, errno); 306 | if (desc != NULL) return desc; 307 | 308 | errno += -errno % 100; // round to hundred 309 | 310 | desc = find_error_desc(scpi_std_errors, errno); 311 | if (desc != NULL) return desc; 312 | 313 | } else { 314 | // user error 315 | 316 | desc = find_error_desc(scpi_user_errors, errno); 317 | if (desc != NULL) return desc; 318 | } 319 | 320 | return NULL; 321 | } 322 | 323 | 324 | /** 325 | * Get error string. 326 | * 327 | * @param buffer Buffer for storing the final string. Make sure it's big enough. 328 | * @param errno Error number 329 | * @param extra Extra information, appended after the generic message. 330 | * 331 | * @returns actual error code. Code may be coerced to closest defined code (categories: tens, hundreds) 332 | */ 333 | int16_t scpi_error_string(char *buffer, int16_t errno, const char *extra) 334 | { 335 | const SCPI_error_desc *desc = resolve_error_desc(errno); 336 | const char *msg; 337 | 338 | if (desc != NULL) { 339 | errno = desc->errno; 340 | msg = desc->msg; 341 | } else { 342 | // bad error code 343 | msg = "Unknown error"; 344 | } 345 | 346 | // Print. 347 | if (extra == NULL) { 348 | sprintf(buffer, "%d,\"%s\"", errno, msg); 349 | } else { 350 | sprintf(buffer, "%d,\"%s; %s\"", errno, msg, extra); 351 | } 352 | 353 | return errno; 354 | } 355 | 356 | -------------------------------------------------------------------------------- /source/scpi_parser.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include "scpi_parser.h" 10 | #include "scpi_errors.h" 11 | #include "scpi_builtins.h" 12 | #include "scpi_regs.h" 13 | 14 | // Config 15 | #define MAX_CHARBUF_LEN 64 16 | 17 | 18 | // Char matching 19 | #define INRANGE(c, a, b) ((c) >= (a) && (c) <= (b)) 20 | #define IS_WHITESPACE(c) ((c) <= 9 || INRANGE((c), 11, 32)) 21 | 22 | #define IS_LCASE_CHAR(c) INRANGE((c), 'a', 'z') 23 | #define IS_UCASE_CHAR(c) INRANGE((c), 'A', 'Z') 24 | #define IS_NUMBER_CHAR(c) INRANGE((c), '0', '9') 25 | //#define IS_MULTIPLIER_CHAR(c) ((c) == 'k' || (c) == 'M' || (c) == 'G' || (c) == 'm' || (c) == 'u' || (c) == 'n' || (c) == 'p') 26 | 27 | // A-Z a-z 0-9 _ 28 | #define IS_CHARDATA_CHAR(c) (IS_LCASE_CHAR((c)) || IS_UCASE_CHAR((c)) || IS_NUMBER_CHAR((c)) || (c) == '_') 29 | // A-Z a-z 0-9 _ * ? 30 | #define IS_IDENT_CHAR(c) (IS_CHARDATA_CHAR((c)) || (c) == '*' || (c) == '?') 31 | // 0-9 - + 32 | #define IS_INT_CHAR(c) (IS_NUMBER_CHAR((c)) || (c) == '-' || (c) == '+') 33 | // 0-9 . + - eE 34 | #define IS_FLOAT_CHAR(c) (IS_NUMBER_CHAR((c)) || (c) == '.' || (c) == 'e' || (c) == 'E' || (c) == '+' || (c) == '-') 35 | 36 | #define CHAR_TO_LOWER(ucase) ((ucase) + 32) 37 | #define CHAR_TO_UPPER(lcase) ((lcase) - 32) 38 | 39 | 40 | 41 | /** Parser internal state enum */ 42 | typedef enum ParserStateEnum { 43 | PARS_COMMAND = 0, 44 | // collect generic arg, terminated with comma or newline. Leading and trailing whitespace ignored. 45 | PARS_ARG, // generic argument (bool, float...) 46 | PARS_ARG_STRING, // collect arg - string (special treatment for quotes) 47 | PARS_ARG_BLOB_PREAMBLE, // #nDDD 48 | PARS_ARG_BLOB_DISCARD, // discard blob - same as BLOB_BODY, but no callback or buffering 49 | PARS_ARG_BLOB_BODY, // blob body, callback for each group 50 | PARS_TRAILING_WHITE, // command ready to run, waiting for end, only whitespace allowed 51 | PARS_TRAILING_WHITE_NOCB, // discard whitespace until end of line, don't run callback on success 52 | PARS_DISCARD_LINE, // used after detecting error - drop all chars until \n 53 | } parser_state_t; 54 | 55 | 56 | 57 | /** Parser internal state struct */ 58 | static struct ParserInternalStateStruct { 59 | parser_state_t state; // current parser internal state 60 | 61 | // string buffer, chars collected here until recognized 62 | char charbuf[MAX_CHARBUF_LEN + 1]; 63 | uint16_t charbuf_i; 64 | 65 | uint32_t blob_cnt; // preamble counter, if 0, was just #, must read count. Used also for blob body. 66 | uint32_t blob_len; // total blob length to read 67 | 68 | char string_quote; // symbol used to quote string 69 | bool string_escape; // last char was backslash, next quote is literal 70 | 71 | // recognized complete command level strings (FUNCtion) - exact copy from command struct 72 | char cur_levels[SCPI_MAX_LEVEL_COUNT][SCPI_MAX_CMD_LEN]; 73 | uint8_t cur_level_i; // next free level slot index 74 | 75 | bool cmdbuf_kept; // set to 1 after semicolon - cur_levels is kept (removed last part) 76 | 77 | const SCPI_command_t * matched_cmd; // command is put here after recognition, used as reference for args 78 | 79 | SCPI_argval_t args[SCPI_MAX_PARAM_COUNT]; 80 | uint8_t arg_i; // next free argument slot index 81 | 82 | } pst = {/*EMPTY*/}; // initialized by all zeros 83 | 84 | 85 | // buffer for error messages 86 | static char ebuf[100]; 87 | 88 | // ---------------- PRIVATE PROTOTYPES ------------------ 89 | 90 | // Command parsing 91 | static void pars_cmd_colon(void); // colon starting a command sub-segment 92 | static void pars_cmd_space(void); // space ending a command 93 | static void pars_cmd_newline(void); // LF 94 | static void pars_cmd_semicolon(void); // semicolon right after a command 95 | 96 | // Command properties (find length of array) 97 | static uint8_t cmd_param_count(const SCPI_command_t *cmd); 98 | static uint8_t cmd_level_count(const SCPI_command_t *cmd); 99 | 100 | static bool match_cmd(bool partial); 101 | static bool match_any_cmd_from_array(const SCPI_command_t arr[], bool partial); 102 | static bool match_cmd_do(const SCPI_command_t *cmd, bool partial); 103 | static void run_command_callback(void); 104 | 105 | // Argument parsing 106 | static void pars_arg_char(char c); 107 | static void pars_arg_comma(void); 108 | static void pars_arg_newline(void); 109 | static void pars_arg_semicolon(void); 110 | static void pars_blob_preamble_char(uint8_t c); 111 | static void arg_convert_value(void); 112 | 113 | static void charbuf_terminate(void); 114 | static void charbuf_append(char c); 115 | 116 | // Reset 117 | static void pars_reset_cmd(void); 118 | static void pars_reset_cmd_keeplevel(void); 119 | 120 | 121 | // ------------------- MESSAGE SEND ------------------ 122 | 123 | /** Send string, no \r\n */ 124 | void scpi_send_string_raw(const char *message) 125 | { 126 | char c; 127 | while ((c = *message++) != 0) { 128 | scpi_send_byte_impl(c); 129 | } 130 | } 131 | 132 | 133 | /** Send a message to master. Trailing newline is added. */ 134 | void scpi_send_string(const char *message) 135 | { 136 | scpi_send_string_raw(message); 137 | scpi_send_string_raw(scpi_eol); 138 | } 139 | 140 | // ------- Error shortcuts ---------- 141 | 142 | static void err_no_such_command(void) 143 | { 144 | char *b = ebuf; 145 | for (int i = 0; i < pst.cur_level_i; i++) { 146 | if (i > 0) b += sprintf(b, ":"); 147 | b += sprintf(b, "%s", pst.cur_levels[i]); 148 | } 149 | 150 | scpi_add_error(E_CMD_UNDEFINED_HEADER, ebuf); 151 | } 152 | 153 | 154 | static void err_no_such_command_partial(void) 155 | { 156 | char *b = ebuf; 157 | for (int i = 0; i < pst.cur_level_i; i++) { 158 | b += sprintf(b, "%s:", pst.cur_levels[i]); 159 | } 160 | 161 | scpi_add_error(E_CMD_UNDEFINED_HEADER, ebuf); 162 | } 163 | 164 | 165 | // ----------------- INPUT PARSING ---------------- 166 | 167 | void scpi_handle_string(const char* str) 168 | { 169 | while (*str != 0) { 170 | scpi_handle_byte(*str); 171 | str++; 172 | } 173 | } 174 | 175 | void scpi_handle_byte(const uint8_t b) 176 | { 177 | const char c = (char) b; 178 | 179 | switch (pst.state) { 180 | case PARS_COMMAND: 181 | // Collecting command 182 | 183 | if (IS_IDENT_CHAR(c)) { 184 | // valid command char 185 | 186 | if (pst.charbuf_i < SCPI_MAX_CMD_LEN) { 187 | charbuf_append(c); 188 | } else { 189 | scpi_add_error(E_CMD_PROGRAM_MNEMONIC_TOO_LONG, NULL); 190 | pst.state = PARS_DISCARD_LINE; 191 | } 192 | 193 | } else { 194 | // invalid or delimiter 195 | 196 | if (IS_WHITESPACE(c)) { 197 | pars_cmd_space(); // whitespace in command - end of command, start of args (?) 198 | break; 199 | } 200 | 201 | switch (c) { 202 | case ':': 203 | pars_cmd_colon(); // end of a section 204 | break; 205 | 206 | case '\n': // line terminator 207 | pars_cmd_newline(); 208 | break; 209 | 210 | case ';': // ends a command, does not reset cmd path. 211 | pars_cmd_semicolon(); 212 | break; 213 | 214 | default: 215 | sprintf(ebuf, "Unexpected '%c' in command.", c); 216 | scpi_add_error(E_CMD_INVALID_CHARACTER, ebuf); 217 | pst.state = PARS_DISCARD_LINE; 218 | } 219 | } 220 | break; 221 | 222 | case PARS_DISCARD_LINE: 223 | // drop it. Clear state on newline. 224 | if (c == '\r' || c == '\n') { 225 | pars_reset_cmd(); 226 | } 227 | 228 | break; 229 | 230 | case PARS_TRAILING_WHITE: 231 | case PARS_TRAILING_WHITE_NOCB: 232 | if (IS_WHITESPACE(c)) break; 233 | 234 | if (c == '\n') { 235 | if (pst.state != PARS_TRAILING_WHITE_NOCB) { 236 | run_command_callback(); 237 | } 238 | 239 | pars_reset_cmd(); 240 | } else { 241 | sprintf(ebuf, "Unexpected '%c' in trailing whitespace.", c); 242 | scpi_add_error(E_CMD_INVALID_CHARACTER, ebuf); 243 | pst.state = PARS_DISCARD_LINE; 244 | } 245 | 246 | break; // whitespace discarded 247 | 248 | case PARS_ARG: 249 | if (IS_WHITESPACE(c)) break; // discard 250 | 251 | switch (c) { 252 | case ',': 253 | pars_arg_comma(); 254 | break; 255 | 256 | case '\n': 257 | pars_arg_newline(); 258 | break; 259 | 260 | case ';': 261 | pars_arg_semicolon(); 262 | break; 263 | 264 | default: 265 | pars_arg_char(c); 266 | } 267 | break; 268 | 269 | case PARS_ARG_STRING: 270 | // string 271 | 272 | if (c == pst.string_quote && !pst.string_escape) { 273 | // end of string 274 | pst.state = PARS_ARG; // next will be newline or comma (or ignored spaces) 275 | } else if (c == '\n') { 276 | scpi_add_error(E_CMD_STRING_DATA_ERROR, "String not terminated (unexpected newline)."); 277 | 278 | pst.state = PARS_DISCARD_LINE; 279 | } else { 280 | if (pst.string_escape) { 281 | charbuf_append(c); 282 | pst.string_escape = false; 283 | } else { 284 | if (c == '\\') { 285 | pst.string_escape = true; 286 | } else { 287 | charbuf_append(c); 288 | } 289 | } 290 | } 291 | break; 292 | 293 | case PARS_ARG_BLOB_PREAMBLE: 294 | // # 295 | pars_blob_preamble_char(c); 296 | break; 297 | 298 | case PARS_ARG_BLOB_BODY: 299 | // binary blob body with callback on buffer full 300 | 301 | charbuf_append(c); 302 | pst.blob_cnt++; 303 | 304 | if (pst.charbuf_i >= pst.matched_cmd->blob_chunk) { 305 | charbuf_terminate(); 306 | 307 | if (pst.matched_cmd->blob_callback != NULL) { 308 | pst.matched_cmd->blob_callback((uint8_t *)pst.charbuf); 309 | } 310 | } 311 | 312 | if (pst.blob_cnt == pst.blob_len) { 313 | pst.state = PARS_TRAILING_WHITE_NOCB; // discard trailing whitespace until newline 314 | } 315 | 316 | break; 317 | 318 | case PARS_ARG_BLOB_DISCARD: 319 | // binary blob, discard incoming data 320 | 321 | pst.blob_cnt++; 322 | 323 | if (pst.blob_cnt == pst.blob_len) { 324 | pst.state = PARS_DISCARD_LINE; 325 | } 326 | 327 | break; 328 | } 329 | } 330 | 331 | 332 | 333 | // ------------------- RESET INTERNAL STATE ------------------ 334 | 335 | 336 | // public // 337 | /** Discard the rest of the currently processed blob */ 338 | void scpi_discard_blob(void) 339 | { 340 | if (pst.state == PARS_ARG_BLOB_BODY) { 341 | pst.state = PARS_ARG_BLOB_DISCARD; 342 | } 343 | } 344 | 345 | 346 | /** Reset parser state. */ 347 | static void pars_reset_cmd(void) 348 | { 349 | pst.state = PARS_COMMAND; 350 | pst.charbuf_i = 0; 351 | pst.cur_level_i = 0; 352 | pst.cmdbuf_kept = false; 353 | pst.matched_cmd = NULL; 354 | pst.arg_i = 0; 355 | pst.string_escape = false; 356 | } 357 | 358 | 359 | /** Reset parser state, keep level (semicolon) */ 360 | static void pars_reset_cmd_keeplevel(void) 361 | { 362 | pst.state = PARS_COMMAND; 363 | pst.charbuf_i = 0; 364 | 365 | // rewind to last colon 366 | if (pst.cur_level_i > 0) { 367 | pst.cur_level_i--; // keep prev levels 368 | } 369 | 370 | pst.cmdbuf_kept = true; 371 | pst.matched_cmd = NULL; 372 | pst.arg_i = 0; 373 | pst.string_escape = false; 374 | } 375 | 376 | 377 | // ------------------- COMMAND HELPERS ------------------- 378 | 379 | /** Get param count from command struct */ 380 | static uint8_t cmd_param_count(const SCPI_command_t *cmd) 381 | { 382 | for (uint8_t i = 0; i < SCPI_MAX_PARAM_COUNT; i++) { 383 | if (cmd->params[i] == SCPI_DT_NONE) { 384 | return i; 385 | } 386 | } 387 | 388 | return SCPI_MAX_PARAM_COUNT; 389 | } 390 | 391 | 392 | /** Get level count from command struct */ 393 | static uint8_t cmd_level_count(const SCPI_command_t *cmd) 394 | { 395 | for (uint8_t i = 0; i < SCPI_MAX_LEVEL_COUNT; i++) { 396 | if (cmd->levels[i][0] == 0) { 397 | return i; 398 | } 399 | } 400 | 401 | return SCPI_MAX_LEVEL_COUNT; 402 | } 403 | 404 | 405 | 406 | // ----------------- CHAR BUFFER HELPERS ------------------- 407 | 408 | /** Add a byte to charbuf, error on overflow */ 409 | static void charbuf_append(char c) 410 | { 411 | if (pst.charbuf_i >= MAX_CHARBUF_LEN) { 412 | scpi_add_error(E_DEV_INPUT_BUFFER_OVERRUN, NULL); 413 | pst.state = PARS_DISCARD_LINE; 414 | } 415 | 416 | pst.charbuf[pst.charbuf_i++] = c; 417 | } 418 | 419 | 420 | /** Terminate charbuf and rewind the pointer to start */ 421 | static void charbuf_terminate(void) 422 | { 423 | pst.charbuf[pst.charbuf_i] = '\0'; 424 | pst.charbuf_i = 0; 425 | } 426 | 427 | 428 | 429 | // ----------------- PARSING COMMANDS --------------- 430 | 431 | /** Colon received when collecting command parts */ 432 | static void pars_cmd_colon(void) 433 | { 434 | if (pst.charbuf_i == 0) { 435 | // No command text before colon 436 | 437 | if (pst.cur_level_i == 0 || pst.cmdbuf_kept) { 438 | // top level command starts with colon (or after semicolon - reset level) 439 | pars_reset_cmd(); // clears keep flag 440 | } else { 441 | // colon after nothing - error 442 | scpi_add_error(E_CMD_SYNTAX_ERROR, "Unexpected colon."); 443 | 444 | pst.state = PARS_DISCARD_LINE; 445 | } 446 | 447 | } else { 448 | // internal colon - partial match 449 | if (match_cmd(true)) { 450 | // ok 451 | pst.cmdbuf_kept = false; // drop the flag (needed for rejecting whitespace) 452 | } else { 453 | // error 454 | err_no_such_command_partial(); 455 | 456 | pst.state = PARS_DISCARD_LINE; 457 | } 458 | } 459 | } 460 | 461 | 462 | /** Semiolon received when collecting command parts */ 463 | static void pars_cmd_semicolon(void) 464 | { 465 | if (pst.cur_level_i == 0 && pst.charbuf_i == 0) { 466 | // nothing before semicolon 467 | scpi_add_error(E_CMD_SYNTAX_ERROR, "Semicolon not preceded by command."); 468 | pars_reset_cmd(); 469 | return; 470 | } 471 | 472 | if (match_cmd(false)) { 473 | int req_cnt = cmd_param_count(pst.matched_cmd); 474 | 475 | if (req_cnt == 0) { 476 | // no param command - OK 477 | run_command_callback(); 478 | pars_reset_cmd_keeplevel(); // keep level - that's what semicolon does 479 | } else { 480 | sprintf(ebuf, "Required %d, got 0.", req_cnt); 481 | scpi_add_error(E_CMD_MISSING_PARAMETER, ebuf); 482 | pars_reset_cmd(); 483 | } 484 | } else { 485 | err_no_such_command(); 486 | pst.state = PARS_DISCARD_LINE; 487 | } 488 | } 489 | 490 | 491 | /** Newline received when collecting command - end command and execute. */ 492 | static void pars_cmd_newline(void) 493 | { 494 | if (pst.cur_level_i == 0 && pst.charbuf_i == 0) { 495 | // nothing before newline 496 | pars_reset_cmd(); 497 | return; 498 | } 499 | 500 | // complete match 501 | if (match_cmd(false)) { 502 | int req_cnt = cmd_param_count(pst.matched_cmd); 503 | 504 | if (req_cnt == 0) { 505 | // no param command - OK 506 | run_command_callback(); 507 | pars_reset_cmd(); 508 | } else { 509 | // error 510 | sprintf(ebuf, "Required %d, got 0.", req_cnt); 511 | scpi_add_error(E_CMD_MISSING_PARAMETER, ebuf); 512 | 513 | pars_reset_cmd(); 514 | } 515 | 516 | } else { 517 | err_no_such_command(); 518 | pars_reset_cmd(); 519 | } 520 | } 521 | 522 | 523 | /** Whitespace received when collecting command parts */ 524 | static void pars_cmd_space(void) 525 | { 526 | if ((pst.cmdbuf_kept || pst.cur_level_i == 0) && pst.charbuf_i == 0) { 527 | // leading whitespace, ignore 528 | return; 529 | } 530 | 531 | if (match_cmd(false)) { 532 | if (cmd_param_count(pst.matched_cmd) == 0) { 533 | // no commands 534 | pst.state = PARS_TRAILING_WHITE; 535 | } else { 536 | pst.state = PARS_ARG; 537 | } 538 | } else { 539 | // error 540 | err_no_such_command(); 541 | pst.state = PARS_DISCARD_LINE; 542 | } 543 | } 544 | 545 | 546 | /** Check if chars equal, ignore case */ 547 | static bool char_equals_ci(char a, char b) 548 | { 549 | if (IS_LCASE_CHAR(a)) { 550 | 551 | if (IS_LCASE_CHAR(b)) { 552 | return a == b; 553 | } else if (IS_UCASE_CHAR(b)) { 554 | return a == CHAR_TO_LOWER(b); 555 | } else { 556 | return false; 557 | } 558 | 559 | } else if (IS_UCASE_CHAR(a)) { 560 | 561 | if (IS_UCASE_CHAR(b)) { 562 | return a == b; 563 | } else if (IS_LCASE_CHAR(b)) { 564 | return a == CHAR_TO_UPPER(b); 565 | } else { 566 | return false; 567 | } 568 | 569 | } else { 570 | return a == b; // exact match, not letters 571 | } 572 | } 573 | 574 | 575 | /** Check if command matches a pattern */ 576 | static bool level_str_matches(const char *test, const char *pattern) 577 | { 578 | const uint8_t testlen = strlen(test); 579 | uint8_t pat_i, tst_i; 580 | bool long_started = false; 581 | for (pat_i = 0, tst_i = 0; pat_i < strlen(pattern); pat_i++) { 582 | if (tst_i > testlen) return false; // not match 583 | 584 | const char pat_c = pattern[pat_i]; 585 | const char tst_c = test[tst_i]; // may be at the \0 terminator 586 | 587 | if (IS_LCASE_CHAR(pat_c)) { 588 | // optional char 589 | if (char_equals_ci(pat_c, tst_c)) { 590 | tst_i++; // advance test string 591 | long_started = true; 592 | } else { 593 | if (long_started) return false; // once long variant started, it must be completed. 594 | } 595 | 596 | continue; // next pi - tc stays in place 597 | } else { 598 | // require exact match (case insensitive) 599 | if (char_equals_ci(pat_c, tst_c)) { 600 | tst_i++; 601 | } else { 602 | return false; 603 | } 604 | } 605 | } 606 | 607 | return (tst_i >= testlen); 608 | } 609 | 610 | 611 | /** 612 | * Match content of the charbuf to a command. 613 | * @param partial - match also parts of a command (until a colon) 614 | */ 615 | static bool match_cmd(bool partial) 616 | { 617 | charbuf_terminate(); // zero-end and rewind index 618 | 619 | // copy to level table 620 | char *dest = pst.cur_levels[pst.cur_level_i++]; 621 | strcpy(dest, pst.charbuf); 622 | 623 | 624 | // User commands are checked first, can override builtin commands 625 | if (match_any_cmd_from_array(scpi_commands, partial)) { 626 | return true; 627 | } 628 | 629 | // Try the built-in commands 630 | return match_any_cmd_from_array(scpi_commands_builtin, partial); 631 | } 632 | 633 | 634 | static bool match_any_cmd_from_array(const SCPI_command_t arr[], bool partial) 635 | { 636 | for (uint16_t i = 0; i < 0xFFFF; i++) { 637 | 638 | const SCPI_command_t *cmd = &arr[i]; 639 | if (cmd->levels[0][0] == 0) break; // end marker 640 | 641 | if (cmd_level_count(cmd) > SCPI_MAX_LEVEL_COUNT) { 642 | // FAIL, too deep. Bad config 643 | continue; 644 | } 645 | 646 | if (match_cmd_do(cmd, partial)) { 647 | if (partial) { 648 | // match found, OK 649 | return true; 650 | } else { 651 | // exact match found 652 | pst.matched_cmd = cmd; 653 | return true; 654 | } 655 | } 656 | } 657 | 658 | return false; 659 | } 660 | 661 | 662 | /** Try to match current state to a given command */ 663 | static bool match_cmd_do(const SCPI_command_t *cmd, bool partial) 664 | { 665 | const uint8_t level_cnt = cmd_level_count(cmd); 666 | if (pst.cur_level_i > level_cnt) return false; // command too short 667 | if (pst.cur_level_i == 0) return false; // nothing to match 668 | 669 | if (partial) { 670 | if (pst.cur_level_i == level_cnt) { 671 | return false; // would be exact match 672 | } 673 | } else { 674 | if (pst.cur_level_i != level_cnt) { 675 | return false; // can be only partial match 676 | } 677 | } 678 | 679 | // check for match up to current index 680 | for (uint8_t j = 0; j < pst.cur_level_i; j++) { 681 | if (!level_str_matches(pst.cur_levels[j], cmd->levels[j])) { 682 | return false; 683 | } 684 | } 685 | 686 | return true; 687 | } 688 | 689 | 690 | /** Run the matched command's callback with the arguments */ 691 | static void run_command_callback(void) 692 | { 693 | if (pst.matched_cmd != NULL) { 694 | pst.matched_cmd->callback(pst.args); // run 695 | } 696 | } 697 | 698 | 699 | 700 | // ---------------------- PARSING ARGS -------------------------- 701 | 702 | /** Non-whitespace and non-comma char received in arg. */ 703 | static void pars_arg_char(char c) 704 | { 705 | switch (pst.matched_cmd->params[pst.arg_i]) { 706 | case SCPI_DT_FLOAT: 707 | if (!IS_FLOAT_CHAR(c)) { 708 | sprintf(ebuf, "'%c' not allowed in FLOAT.", c); 709 | scpi_add_error(E_CMD_INVALID_CHARACTER_IN_NUMBER, ebuf); 710 | 711 | pst.state = PARS_DISCARD_LINE; 712 | } else { 713 | charbuf_append(c); 714 | } 715 | break; 716 | 717 | case SCPI_DT_INT: 718 | if (!IS_INT_CHAR(c)) { 719 | sprintf(ebuf, "'%c' not allowed in INT.", c); 720 | scpi_add_error(E_CMD_INVALID_CHARACTER_IN_NUMBER, ebuf); 721 | 722 | pst.state = PARS_DISCARD_LINE; 723 | } else { 724 | charbuf_append(c); 725 | } 726 | break; 727 | 728 | case SCPI_DT_CHARDATA: 729 | if (!IS_CHARDATA_CHAR(c)) { 730 | sprintf(ebuf, "'%c' not allowed in CHARDATA.", c); 731 | scpi_add_error(E_CMD_INVALID_CHARACTER_DATA, ebuf); 732 | 733 | pst.state = PARS_DISCARD_LINE; 734 | } else { 735 | charbuf_append(c); 736 | } 737 | break; 738 | 739 | case SCPI_DT_STRING: 740 | if (c == '\'' || c == '"') { 741 | pst.state = PARS_ARG_STRING; 742 | pst.string_quote = c; 743 | pst.string_escape = false; 744 | } else { 745 | scpi_add_error(E_CMD_INVALID_STRING_DATA, "Invalid quote, or chars after string."); 746 | pst.state = PARS_DISCARD_LINE; 747 | } 748 | break; 749 | 750 | case SCPI_DT_BLOB: 751 | if (c == '#') { 752 | pst.state = PARS_ARG_BLOB_PREAMBLE; 753 | pst.blob_cnt = 0; 754 | } else { 755 | scpi_add_error(E_CMD_INVALID_BLOCK_DATA, "Block data must start with #"); 756 | pst.state = PARS_DISCARD_LINE; 757 | } 758 | break; 759 | 760 | default: 761 | charbuf_append(c); 762 | break; 763 | } 764 | } 765 | 766 | 767 | /** Received a comma while collecting an arg */ 768 | static void pars_arg_comma(void) 769 | { 770 | if (pst.arg_i == cmd_param_count(pst.matched_cmd) - 1) { 771 | // it was the last argument 772 | scpi_add_error(E_CMD_UNEXPECTED_NUMBER_OF_PARAMETERS, "Comma after last argument."); 773 | pst.state = PARS_DISCARD_LINE; 774 | return; 775 | } 776 | 777 | if (pst.charbuf_i == 0) { 778 | scpi_add_error(E_CMD_SYNTAX_ERROR, "Missing command before comma."); 779 | pst.state = PARS_DISCARD_LINE; 780 | return; 781 | } 782 | 783 | // Convert to the right type 784 | 785 | arg_convert_value(); 786 | } 787 | 788 | 789 | // line ended with \n or ; 790 | static void pars_arg_eol_do(bool keep_levels) 791 | { 792 | int req_cnt = cmd_param_count(pst.matched_cmd); 793 | 794 | if (pst.arg_i + (pst.charbuf_i ? 1 : 0) < req_cnt) { 795 | // not the last arg yet - fail 796 | 797 | if (pst.charbuf_i > 0) pst.arg_i++; // acknowledge the last arg 798 | 799 | sprintf(ebuf, "Required %d arg, got %d.", req_cnt, pst.arg_i); 800 | scpi_add_error(E_CMD_MISSING_PARAMETER, ebuf); 801 | 802 | pst.state = PARS_DISCARD_LINE; 803 | return; 804 | } 805 | 806 | arg_convert_value(); 807 | run_command_callback(); 808 | 809 | if (keep_levels) { 810 | pars_reset_cmd_keeplevel(); 811 | } else { 812 | pars_reset_cmd(); // start a new command 813 | } 814 | } 815 | 816 | 817 | static void pars_arg_newline(void) 818 | { 819 | pars_arg_eol_do(false); 820 | } 821 | 822 | 823 | static void pars_arg_semicolon(void) 824 | { 825 | pars_arg_eol_do(true); 826 | } 827 | 828 | 829 | /** Convert BOOL, FLOAT or INT char to arg type and advance to next */ 830 | static void arg_convert_value(void) 831 | { 832 | charbuf_terminate(); 833 | 834 | SCPI_argval_t *dest = &pst.args[pst.arg_i]; 835 | int j; 836 | 837 | switch (pst.matched_cmd->params[pst.arg_i]) { 838 | case SCPI_DT_BOOL: 839 | if (strcasecmp(pst.charbuf, "1") == 0) { 840 | dest->BOOL = 1; 841 | } else if (strcasecmp(pst.charbuf, "0") == 0) { 842 | dest->BOOL = 0; 843 | } else if (strcasecmp(pst.charbuf, "ON") == 0) { 844 | dest->BOOL = 1; 845 | } else if (strcasecmp(pst.charbuf, "OFF") == 0) { 846 | dest->BOOL = 0; 847 | } else { 848 | sprintf(ebuf, "Invalid BOOL value: '%s'", pst.charbuf); 849 | scpi_add_error(E_CMD_NUMERIC_DATA_ERROR, ebuf); 850 | 851 | pst.state = PARS_DISCARD_LINE; 852 | } 853 | break; 854 | 855 | case SCPI_DT_FLOAT: 856 | j = sscanf(pst.charbuf, "%f", &dest->FLOAT); 857 | if (j == 0 || pst.charbuf[0] == '\0') { //fail or empty buffer 858 | sprintf(ebuf, "Invalid FLOAT value: '%s'", pst.charbuf); 859 | scpi_add_error(E_CMD_NUMERIC_DATA_ERROR, ebuf); 860 | 861 | pst.state = PARS_DISCARD_LINE; 862 | } 863 | break; 864 | 865 | case SCPI_DT_INT: 866 | j = sscanf(pst.charbuf, "%" SCNu32, &dest->INT); 867 | 868 | if (j == 0 || pst.charbuf[0] == '\0') { //fail or empty buffer 869 | sprintf(ebuf, "Invalid INT value: '%s'", pst.charbuf); 870 | scpi_add_error(E_CMD_NUMERIC_DATA_ERROR, ebuf); 871 | 872 | pst.state = PARS_DISCARD_LINE; 873 | } 874 | break; 875 | 876 | case SCPI_DT_STRING: 877 | if (strlen(pst.charbuf) > SCPI_MAX_STRING_LEN) { 878 | scpi_add_error(E_CMD_STRING_DATA_ERROR, "String too long."); 879 | 880 | pst.state = PARS_DISCARD_LINE; 881 | } else { 882 | strcpy(dest->STRING, pst.charbuf); // copy the string 883 | } 884 | 885 | break; 886 | 887 | case SCPI_DT_CHARDATA: 888 | if (strlen(pst.charbuf) > SCPI_MAX_STRING_LEN) { // using common buffer 889 | scpi_add_error(E_CMD_CHARACTER_DATA_TOO_LONG, NULL); 890 | 891 | pst.state = PARS_DISCARD_LINE; 892 | } else { 893 | strcpy(dest->CHARDATA, pst.charbuf); // copy the character data text 894 | } 895 | 896 | break; 897 | 898 | default: 899 | // impossible 900 | scpi_add_error(E_DEV_SYSTEM_ERROR, "Unexpected argument data type."); 901 | pst.state = PARS_DISCARD_LINE; 902 | } 903 | 904 | // proceed to next argument 905 | pst.arg_i++; 906 | } 907 | 908 | 909 | static void pars_blob_preamble_char(uint8_t c) 910 | { 911 | if (pst.blob_cnt == 0) { 912 | if (!INRANGE(c, '1', '9')) { 913 | sprintf(ebuf, "Unexpected '%c' in binary data preamble.", c); 914 | scpi_add_error(E_CMD_BLOCK_DATA_ERROR, ebuf); 915 | 916 | pst.state = PARS_DISCARD_LINE;// (but not enough to remove the blob containing \n) 917 | return; 918 | } 919 | 920 | pst.blob_cnt = c - '0'; // 1-9 921 | } else { 922 | if (c == '\n') { 923 | scpi_add_error(E_CMD_BLOCK_DATA_ERROR, "Unexpected newline in binary data preamble."); 924 | 925 | pars_reset_cmd(); 926 | return; 927 | } 928 | 929 | if (!IS_NUMBER_CHAR(c)) { 930 | sprintf(ebuf, "Unexpected '%c' in binary data preamble.", c); 931 | scpi_add_error(E_CMD_BLOCK_DATA_ERROR, ebuf); 932 | 933 | pst.state = PARS_DISCARD_LINE; 934 | return; 935 | } 936 | 937 | charbuf_append(c); 938 | if (--pst.blob_cnt == 0) { 939 | // end of preamble sequence 940 | charbuf_terminate(); 941 | 942 | sscanf(pst.charbuf, "%" SCNu32, &pst.blob_len); 943 | 944 | pst.args[pst.arg_i].BLOB_LEN = pst.blob_len; 945 | run_command_callback(); 946 | 947 | // Call handler, enter special blob mode 948 | pst.state = PARS_ARG_BLOB_BODY; 949 | pst.blob_cnt = 0; 950 | } 951 | } 952 | } 953 | -------------------------------------------------------------------------------- /source/scpi_regs.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "scpi_regs.h" 7 | #include "scpi_errors.h" 8 | #include "scpi_parser.h" 9 | 10 | SCPI_REG_QUES_t SCPI_REG_QUES; 11 | SCPI_REG_QUES_t SCPI_REG_QUES_EN = {.u16 = 0xFFFF}; 12 | 13 | SCPI_REG_OPER_t SCPI_REG_OPER; 14 | SCPI_REG_OPER_t SCPI_REG_OPER_EN = {.u16 = 0xFFFF}; 15 | 16 | SCPI_REG_SESR_t SCPI_REG_SESR = {.POWER_ON = 1}; // indicates the startup condition 17 | SCPI_REG_SESR_t SCPI_REG_SESR_EN; 18 | 19 | SCPI_REG_STB_t SCPI_REG_STB; 20 | SCPI_REG_STB_t SCPI_REG_SRE; 21 | 22 | /** Update status registers (propagate using enable registers) */ 23 | void scpi_status_update(void) 24 | { 25 | // propagate to STB 26 | SCPI_REG_STB.ERRQ = scpi_error_count() > 0; 27 | SCPI_REG_STB.QUES = SCPI_REG_QUES.u16 & SCPI_REG_QUES_EN.u16; 28 | SCPI_REG_STB.OPER = SCPI_REG_OPER.u16 & SCPI_REG_OPER_EN.u16; 29 | SCPI_REG_STB.SESR = SCPI_REG_SESR.u8 & SCPI_REG_SESR_EN.u8; 30 | SCPI_REG_STB.MAV = false; // TODO!!! 31 | 32 | // Request Service 33 | SCPI_REG_STB.RQS = SCPI_REG_STB.u8 & SCPI_REG_SRE.u8; 34 | 35 | 36 | // Run service request callback 37 | if (SCPI_REG_STB.RQS) { 38 | if (scpi_user_SRQ) { 39 | scpi_user_SRQ(); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /style.astylerc: -------------------------------------------------------------------------------- 1 | style=kr 2 | indent=tab 3 | max-instatement-indent=60 4 | 5 | convert-tabs 6 | 7 | indent-switches 8 | 9 | pad-oper 10 | unpad-paren 11 | pad-header 12 | 13 | verbose 14 | --------------------------------------------------------------------------------