1. error.h中的SCPI错误码定义表
在LIBSCPI中的error.h,SCPI错误码定义表,采用了 X-Macro 技术进行统一管理。
c
/* Using X-Macro technique to define everything once
* http://en.wikipedia.org/wiki/X_Macro
*
* X macro is for minimal set of errors for library itself
* XE macro is for full set of SCPI errors available to user application
*/
#define LIST_OF_ERRORS \
X(SCPI_ERROR_NO_ERROR, 0, "No error") \
XE(SCPI_ERROR_COMMAND, -100, "Command error") \
X(SCPI_ERROR_INVALID_CHARACTER, -101, "Invalid character") \
XE(SCPI_ERROR_SYNTAX, -102, "Syntax error") \
X(SCPI_ERROR_INVALID_SEPARATOR, -103, "Invalid separator") \
X(SCPI_ERROR_DATA_TYPE_ERROR, -104, "Data type error") \
XE(SCPI_ERROR_GET_NOT_ALLOWED, -105, "GET not allowed") \
X(SCPI_ERROR_PARAMETER_NOT_ALLOWED, -108, "Parameter not allowed") \
X(SCPI_ERROR_MISSING_PARAMETER, -109, "Missing parameter") \
XE(SCPI_ERROR_UNSUPPORTED_COMMAND, -110, "Command not supported") \
XE(SCPI_ERROR_HEADER_SEPARATOR, -111, "Header separator error") \
XE(SCPI_ERROR_PRG_MNEMONIC_TOO_LONG, -112, "Program mnemonic too long") \
X(SCPI_ERROR_UNDEFINED_HEADER, -113, "Undefined header") \
XE(SCPI_ERROR_HEADER_SUFFIX_OUTOFRANGE, -114, "Header suffix out of range") \
XE(SCPI_ERROR_UNEXP_NUM_OF_PARAMETER, -115, "Unexpected number of parameters") \
XE(SCPI_ERROR_NUMERIC_DATA_ERROR, -120, "Numeric data error") \
XE(SCPI_ERROR_INVAL_CHAR_IN_NUMBER, -121, "Invalid character in number") \
XE(SCPI_ERROR_EXPONENT_TOO_LONG, -123, "Exponent too large") \
XE(SCPI_ERROR_TOO_MANY_DIGITS, -124, "Too many digits") \
XE(SCPI_ERROR_NUMERIC_DATA_NOT_ALLOWED, -128, "Numeric data not allowed") \
XE(SCPI_ERROR_SUFFIX_ERROR, -130, "Suffix error") \
X(SCPI_ERROR_INVALID_SUFFIX, -131, "Invalid suffix") \
XE(SCPI_ERROR_SUFFIX_TOO_LONG, -134, "Suffix too long") \
X(SCPI_ERROR_SUFFIX_NOT_ALLOWED, -138, "Suffix not allowed") \
XE(SCPI_ERROR_CHARACTER_DATA_ERROR, -140, "Character data error") \
XE(SCPI_ERROR_INVAL_CHARACTER_DATA, -141, "Invalid character data") \
XE(SCPI_ERROR_CHARACTER_DATA_TOO_LONG, -144, "Character data too long") \
XE(SCPI_ERROR_CHARACTER_DATA_NOT_ALLOWED, -148, "Character data not allowed") \
XE(SCPI_ERROR_STRING_DATA_ERROR, -150, "String data error") \
X(SCPI_ERROR_INVALID_STRING_DATA, -151, "Invalid string data") \
XE(SCPI_ERROR_STRING_DATA_NOT_ALLOWED, -158, "String data not allowed") \
XE(SCPI_ERROR_BLOCK_DATA_ERROR, -160, "Block data error") \
XE(SCPI_ERROR_INVALID_BLOCK_DATA, -161, "Invalid block data") \
XE(SCPI_ERROR_BLOCK_DATA_NOT_ALLOWED, -168, "Block data not allowed") \
X(SCPI_ERROR_EXPRESSION_PARSING_ERROR, -170, "Expression error") \
XE(SCPI_ERROR_INVAL_EXPRESSION, -171, "Invalid expression") \
XE(SCPI_ERROR_EXPRESSION_DATA_NOT_ALLOWED, -178, "Expression data not allowed") \
XE(SCPI_ERROR_MACRO_DEFINITION_ERROR, -180, "Macro error") \
XE(SCPI_ERROR_INVAL_OUTSIDE_MACRO_DEF, -181, "Invalid outside macro definition") \
XE(SCPI_ERROR_INVAL_INSIDE_MACRO_DEF, -183, "Invalid inside macro definition") \
XE(SCPI_ERROR_MACRO_PARAMETER_ERROR, -184, "Macro parameter error") \
X(SCPI_ERROR_EXECUTION_ERROR, -200, "Execution error") \
XE(SCPI_ERROR_INVAL_WHILE_IN_LOCAL, -201, "Invalid while in local") \
XE(SCPI_ERROR_SETTINGS_LOST_DUE_TO_RTL, -202, "Settings lost due to rtl") \
XE(SCPI_ERROR_COMMAND_PROTECTED, -203, "Command protected") \
XE(SCPI_ERROR_TRIGGER_ERROR, -210, "Trigger error") \
XE(SCPI_ERROR_TRIGGER_IGNORED, -211, "Trigger ignored") \
XE(SCPI_ERROR_ARM_IGNORED, -212, "Arm ignored") \
XE(SCPI_ERROR_INIT_IGNORED, -213, "Init ignored") \
XE(SCPI_ERROR_TRIGGER_DEADLOCK, -214, "Trigger deadlock") \
XE(SCPI_ERROR_ARM_DEADLOCK, -215, "Arm deadlock") \
XE(SCPI_ERROR_PARAMETER_ERROR, -220, "Parameter error") \
XE(SCPI_ERROR_SETTINGS_CONFLICT, -221, "Settings conflict") \
XE(SCPI_ERROR_PARAM_OUT_OF_RANGE, -222, "Parameter out of range") \
XE(SCPI_ERROR_TOO_MUCH_DATA, -223, "Too much data") \
X(SCPI_ERROR_ILLEGAL_PARAMETER_VALUE, -224, "Illegal parameter value") \
XE(SCPI_ERROR_OUT_OF_MEMORY_FOR_REQ_OP, -225, "Out of memory") \
XE(SCPI_ERROR_LISTS_NOT_SAME_LENGTH, -226, "Lists not same length") \
XE(SCPI_ERROR_DATA_CORRUPT, -230, "Data corrupt or stale") \
XE(SCPI_ERROR_DATA_QUESTIONABLE, -231, "Data questionable") \
XE(SCPI_ERROR_INVAL_VERSION, -233, "Invalid version") \
XE(SCPI_ERROR_HARDWARE_ERROR, -240, "Hardware error") \
XE(SCPI_ERROR_HARDWARE_MISSING, -241, "Hardware missing") \
XE(SCPI_ERROR_MASS_STORAGE_ERROR, -250, "Mass storage error") \
XE(SCPI_ERROR_MISSING_MASS_STORAGE, -251, "Missing mass storage") \
XE(SCPI_ERROR_MISSING_MASS_MEDIA, -252, "Missing media") \
XE(SCPI_ERROR_CORRUPT_MEDIA, -253, "Corrupt media") \
XE(SCPI_ERROR_MEDIA_FULL, -254, "Media full") \
XE(SCPI_ERROR_DIRECTORY_FULL, -255, "Directory full") \
XE(SCPI_ERROR_FILE_NAME_NOT_FOUND, -256, "File name not found") \
XE(SCPI_ERROR_FILE_NAME_ERROR, -257, "File name error") \
XE(SCPI_ERROR_MEDIA_PROTECTED, -258, "Media protected") \
XE(SCPI_ERROR_EXPRESSION_EXECUTING_ERROR, -260, "Expression error") \
XE(SCPI_ERROR_MATH_ERROR_IN_EXPRESSION, -261, "Math error in expression") \
XE(SCPI_ERROR_MACRO_UNDEF_EXEC_ERROR, -270, "Macro error") \
XE(SCPI_ERROR_MACRO_SYNTAX_ERROR, -271, "Macro syntax error") \
XE(SCPI_ERROR_MACRO_EXECUTION_ERROR, -272, "Macro execution error") \
XE(SCPI_ERROR_ILLEGAL_MACRO_LABEL, -273, "Illegal macro label") \
XE(SCPI_ERROR_IMPROPER_USED_MACRO_PARAM, -274, "Macro parameter error") \
XE(SCPI_ERROR_MACRO_DEFINITION_TOO_LONG, -275, "Macro definition too long") \
XE(SCPI_ERROR_MACRO_RECURSION_ERROR, -276, "Macro recursion error") \
XE(SCPI_ERROR_MACRO_REDEF_NOT_ALLOWED, -277, "Macro redefinition not allowed") \
XE(SCPI_ERROR_MACRO_HEADER_NOT_FOUND, -278, "Macro header not found") \
XE(SCPI_ERROR_PROGRAM_ERROR, -280, "Program error") \
XE(SCPI_ERROR_CANNOT_CREATE_PROGRAM, -281, "Cannot create program") \
XE(SCPI_ERROR_ILLEGAL_PROGRAM_NAME, -282, "Illegal program name") \
XE(SCPI_ERROR_ILLEGAL_VARIABLE_NAME, -283, "Illegal variable name") \
XE(SCPI_ERROR_PROGRAM_CURRENTLY_RUNNING, -284, "Program currently running") \
XE(SCPI_ERROR_PROGRAM_SYNTAX_ERROR, -285, "Program syntax error") \
XE(SCPI_ERROR_PROGRAM_RUNTIME_ERROR, -286, "Program runtime error") \
XE(SCPI_ERROR_MEMORY_USE_ERROR, -290, "Memory use error") \
XE(SCPI_ERROR_OUT_OF_MEMORY, -291, "Out of memory") \
XE(SCPI_ERROR_REF_NAME_DOES_NOT_EXIST, -292, "Referenced name does not exist") \
XE(SCPI_ERROR_REF_NAME_ALREADY_EXISTS, -293, "Referenced name already exists") \
XE(SCPI_ERROR_INCOMPATIBLE_TYPE, -294, "Incompatible type") \
XE(SCPI_ERROR_DEVICE_ERROR, -300, "Device specific error") \
X(SCPI_ERROR_SYSTEM_ERROR, -310, "System error") \
XE(SCPI_ERROR_MEMORY_ERROR, -311, "Memory error") \
XE(SCPI_ERROR_PUD_MEMORY_LOST, -312, "PUD memory lost") \
XE(SCPI_ERROR_CALIBRATION_MEMORY_LOST, -313, "Calibration memory lost") \
XE(SCPI_ERROR_SAVE_RECALL_MEMORY_LOST, -314, "Save/recall memory lost") \
XE(SCPI_ERROR_CONFIGURATION_MEMORY_LOST, -315, "Configuration memory lost") \
XE(SCPI_ERROR_STORAGE_FAULT, -320, "Storage fault") \
XE(SCPI_ERROR_OUT_OF_DEVICE_MEMORY, -321, "Out of memory") \
XE(SCPI_ERROR_SELF_TEST_FAILED, -330, "Self-test failed") \
XE(SCPI_ERROR_CALIBRATION_FAILED, -340, "Calibration failed") \
X(SCPI_ERROR_QUEUE_OVERFLOW, -350, "Queue overflow") \
XE(SCPI_ERROR_COMMUNICATION_ERROR, -360, "Communication error") \
XE(SCPI_ERROR_PARITY_ERROR_IN_CMD_MSG, -361, "Parity error in program message") \
XE(SCPI_ERROR_FRAMING_ERROR_IN_CMD_MSG, -362, "Framing error in program message") \
X(SCPI_ERROR_INPUT_BUFFER_OVERRUN, -363, "Input buffer overrun") \
XE(SCPI_ERROR_TIME_OUT, -365, "Time out error") \
XE(SCPI_ERROR_QUERY_ERROR, -400, "Query error") \
XE(SCPI_ERROR_QUERY_INTERRUPTED, -410, "Query INTERRUPTED") \
XE(SCPI_ERROR_QUERY_UNTERMINATED, -420, "Query UNTERMINATED") \
XE(SCPI_ERROR_QUERY_DEADLOCKED, -430, "Query DEADLOCKED") \
XE(SCPI_ERROR_QUERY_UNTERM_INDEF_RESP, -440, "Query UNTERMINATED after indefinite response") \
XE(SCPI_ERROR_POWER_ON, -500, "Power on") \
XE(SCPI_ERROR_USER_REQUEST, -600, "User request") \
XE(SCPI_ERROR_REQUEST_CONTROL, -700, "Request control") \
XE(SCPI_ERROR_OPERATION_COMPLETE, -800, "Operation complete") \
2. X-Macro
X-Macro(X 宏) 是一种 C/C++ 预处理器编程技术,用于避免代码重复和维护同步问题。它的核心思想是:将数据列表定义在一个地方,然后用不同的宏来"展开"这个列表,生成不同的代码。
基本原理
传统方式的问题
假设你有一个错误码列表,需要同时生成:
- 枚举类型(给程序用)
- 错误码数值数组(给查找用)
- 错误描述字符串数组(给人看)
传统做法:在三个地方分别维护相同的数据 → 极易出错且难以维护
X-Macro 解决方案
第一步:定义一个宏列表
c
#define LIST_OF_ERRORS \
X(ERR_NONE, 0, "No error") \
X(ERR_SYNTAX, -102, "Syntax error") \
X(ERR_RANGE, -222, "Parameter out of range")
第二步 :用不同的 X 定义来"展开"这个列表
c
// 生成枚举类型
#define X(name, code, desc) name = code,
typedef enum { LIST_OF_ERRORS } error_code_t;
#undef X
// 生成错误描述字符串数组
#define X(name, code, desc) desc,
const char* error_desc[] = { LIST_OF_ERRORS };
#undef X
// 生成错误码查找表
#define X(name, code, desc) code,
const int error_values[] = { LIST_OF_ERRORS };
#undef X
预处理后展开为:
c
// 枚举
typedef enum {
ERR_NONE = 0,
ERR_SYNTAX = -102,
ERR_RANGE = -222
} error_code_t;
// 字符串数组
const char* error_desc[] = {
"No error",
"Syntax error",
"Parameter out of range"
};
// 数值数组
const int error_values[] = { 0, -102, -222 };
优点
| 优点 | 说明 |
|---|---|
| 单一事实来源 | 数据只定义一次,永远不会不一致 |
| 易于维护 | 添加/删除/修改条目只需改一个地方 |
| 编译时生成 | 零运行时开销 |
| 灵活过滤 | 可以只生成列表的子集(如你的 X/XE 机制) |
局限性
- 宏内容复杂时调试困难
- 预处理展开后的代码不易阅读
- 过度使用会让代码晦涩难懂
这是一种预处理时代的代码生成技巧 ,在现代 C++ 中可能用 constexpr、模板或代码生成工具替代,但在 C 语言和嵌入式领域仍然非常流行和实用。
3.原来的X-Macro使用
c
enum {
#define X(def, val, str) def = val,
#if USE_FULL_ERROR_LIST
#define XE X
#else
#define XE(def, val, str)
#endif
LIST_OF_ERRORS
#if USE_USER_ERROR_LIST
LIST_OF_USER_ERRORS
#endif
#undef X
#undef XE
};
通过条件编译控制错误列表的范围。
代码解析
整体结构
c
enum {
// 宏定义阶段
#define X(def, val, str) def = val,
#if USE_FULL_ERROR_LIST
#define XE X
#else
#define XE(def, val, str)
#endif
// 展开阶段
LIST_OF_ERRORS // 使用 X 和 XE 宏
#if USE_USER_ERROR_LIST
LIST_OF_USER_ERRORS // 用户自定义错误
#endif
// 清理阶段
#undef X
#undef XE
};
关键技巧:#define XE X
当 USE_FULL_ERROR_LIST 为真时:
c
#define XE X // XE 变成 X 的别名
这意味着 XE 宏会像 X 一样展开,把带 XE 标记的错误也生成枚举值。
实际展开示例
情况1:基础模式(USE_FULL_ERROR_LIST = 0)
c
#define X(def, val, str) def = val,
#define XE(def, val, str) // 空定义,忽略 XE 条目
LIST_OF_ERRORS // 展开...
假设 LIST_OF_ERRORS 定义为:
c
#define LIST_OF_ERRORS \
X(ERR_NONE, 0, "No error") \
XE(ERR_SYNTAX, -102, "Syntax error") \
X(ERR_RANGE, -222, "Parameter out of range")
展开结果:
c
enum {
ERR_NONE = 0,
// XE(ERR_SYNTAX, -102, ...) 被忽略
ERR_RANGE = -222,
};
情况2:完整模式(USE_FULL_ERROR_LIST = 1)
c
#define X(def, val, str) def = val,
#define XE X // XE 变成 X 的别名
LIST_OF_ERRORS // 展开...
展开结果:
c
enum {
ERR_NONE = 0,
ERR_SYNTAX = -102, // 现在 XE 也被展开了!
ERR_RANGE = -222,
};
设计模式总结
| 模式 | X 宏 |
XE 宏 |
结果 |
|---|---|---|---|
| 库内部模式 | def = val |
空 | 只生成基础错误 |
| 用户完整模式 | def = val |
X |
生成所有错误 |
4. 添加错误码
将 USE_USER_ERROR_LIST 置1
c
// 在 scpi/config.h 中
#define USE_FULL_ERROR_LIST 1
#define USE_USER_ERROR_LIST 1
并且在error.h中添加
c
/* User defined errors - add your custom errors here */
#define LIST_OF_USER_ERRORS \
XE(SCPI_ERROR_VOLTAGE_EXCEED_OVP, 351, "Voltage exceeds OVP setting") \
XE(SCPI_ERROR_OVP_BELOW_VOLTAGE, 352, "OVP below voltage setting") \
XE(SCPI_ERROR_VOLTAGE_BELOW_UVL, 353, "Voltage below UVL setting") \
XE(SCPI_ERROR_UVL_ABOVE_VOLTAGE, 354, "UVL above voltage setting") \
这样就可以成功添加了
5. 格式注意事项
X-Macro 格式注意事项
1. 续行符 \ 后面绝对不能有空格或制表符
c
// ❌ 错误:反斜杠后面有空格
#define LIST_OF_ERRORS \
X(ERR_ONE, 1, "Error one") \␣
// ✅ 正确:反斜杠后直接换行
#define LIST_OF_ERRORS \
X(ERR_ONE, 1, "Error one") \
X(ERR_TWO, 2, "Error two")
2. 每行末尾的 \ 除了最后一行
c
// ❌ 错误:最后一行也有续行符
#define LIST_OF_ERRORS \
X(ERR_ONE, 1, "Error one") \
X(ERR_TWO, 2, "Error two") \ // 多余
// ✅ 正确:只有中间行需要
#define LIST_OF_ERRORS \
X(ERR_ONE, 1, "Error one") \
X(ERR_TWO, 2, "Error two")
3. 多行宏顶格写或统一缩进
c
// 方式1:顶格 + 续行符后缩进
#define LIST_OF_ERRORS \
X(ERR_ONE, 1, "Error one") \
X(ERR_TWO, 2, "Error two")
// 方式2:统一缩进(推荐)
#define LIST_OF_ERRORS \
X(ERR_ONE, 1, "Error one") \
X(ERR_TWO, 2, "Error two")
4. 宏名称与参数之间可以有空格(风格问题)
c
// 三种写法都合法
#define X(def,val,str) def=val,
#define X(def, val, str) def = val,
#define X( def , val , str ) def = val ,
5. 字符串中的反斜杠需要转义
c
// ❌ 错误:字符串内的 \n 会被预处理器混淆?
// 实际上没问题,但小心与续行符混淆
XE(ERR_PATH, 100, "C:\test\file.txt") // 危险
// ✅ 正确:转义反斜杠
XE(ERR_PATH, 100, "C:\\test\\file.txt")
6. 字符串内不能有未转义的双引号
c
// ❌ 错误
XE(ERR_MSG, 100, "He said "Hello"")
// ✅ 正确
XE(ERR_MSG, 100, "He said \"Hello\"")
7. 逗号和缩进保持对齐(可读性)
c
// 推荐:列对齐
#define LIST_OF_USER_ERRORS \
XE(SCPI_ERROR_VOLTAGE_EXCEED_OVP, 351, "Voltage exceeds OVP setting") \
XE(SCPI_ERROR_OVP_BELOW_VOLTAGE, 352, "OVP below voltage setting") \
XE(SCPI_ERROR_VOLTAGE_BELOW_UVL, 353, "Voltage below UVL setting") \
XE(SCPI_ERROR_UVL_ABOVE_VOLTAGE, 354, "UVL above voltage setting")
8. 宏定义不能跨多个独立 #define
c
// ❌ 错误:不能这样拆分
#define LIST_OF_ERRORS \
X(ERR_ONE, 1, "One")
#define LIST_OF_ERRORS \
X(ERR_TWO, 2, "Two") // 这是重新定义,会覆盖
// ✅ 正确:必须在同一个 #define 内
#define LIST_OF_ERRORS \
X(ERR_ONE, 1, "One") \
X(ERR_TWO, 2, "Two")
9. 注意预处理器符号冲突
c
// ❌ 可能冲突:如果其他地方也定义了 X
#define X(a,b,c) something
#include "error_list.h" // 展开会出错
// ✅ 使用前确保 X/XE 未定义,或用后立即 #undef
#undef X
#undef XE
#define X(a,b,c) a = b,
#include "error_list.h"
#undef X
10. 错误码数值的选择建议
c
// 推荐:使用用户错误范围,避免与标准 SCPI 冲突
// 标准 SCPI 使用 -100 到 -899
// 用户自定义通常用正数或 > 900
XE(ERR_USER_BASE, 1000, "User error base")
XE(ERR_VOLTAGE_EXCEED_OVP, 351, "Voltage exceeds OVP setting") // ❌ 351 已被使用?
// 标准 SCPI 中 -351 是 "Queue overflow",但正数 351 没问题
代码中 351、352 等是正数 ,而标准 SCPI 错误码都是负数,所以不会冲突。
快速检查清单
- 每行末尾的
\后无空格 - 最后一行无
\ - 字符串内引号已转义
- 字符串内容无裸反斜杠
- 列对齐使代码易读
- 错误码数值有明确范围规划
- 使用前确保
X/XE定义正确