SCPI添加错误码

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++ 预处理器编程技术,用于避免代码重复和维护同步问题。它的核心思想是:将数据列表定义在一个地方,然后用不同的宏来"展开"这个列表,生成不同的代码

基本原理

传统方式的问题

假设你有一个错误码列表,需要同时生成:

  1. 枚举类型(给程序用)
  2. 错误码数值数组(给查找用)
  3. 错误描述字符串数组(给人看)

传统做法:在三个地方分别维护相同的数据 → 极易出错且难以维护

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 没问题

代码中 351352 等是正数 ,而标准 SCPI 错误码都是负数,所以不会冲突。

快速检查清单

  • 每行末尾的 \ 后无空格
  • 最后一行无 \
  • 字符串内引号已转义
  • 字符串内容无裸反斜杠
  • 列对齐使代码易读
  • 错误码数值有明确范围规划
  • 使用前确保 X/XE 定义正确
相关推荐
秀秀更健康1 小时前
stm32: 系统时钟如何配置为72Mhz
stm32·单片机·嵌入式硬件
归零鸟6 小时前
WD Elements移动硬盘能识别出盘但不能出盘的修复记录
stm32·单片机·嵌入式硬件
追兮兮7 小时前
MCUQuickStart v1.1.0发布,一键生成Keil工程+RTOS模板
stm32·单片机·嵌入式硬件·freertos·gd32·keil5
rit84324998 小时前
STM32移植NES模拟器指南
stm32·单片机·嵌入式硬件
都在酒里8 小时前
STM32 I2C通信协议详解——标准库函数实现(通讯协议总结一)
stm32·嵌入式硬件·i2c
fengfuyao9858 小时前
STM32 HAL库实现串口DMA接收不定长数据
stm32·单片机·嵌入式硬件
yuan199978 小时前
STM32直流无刷电机六拍方波控制器程序
stm32·单片机·嵌入式硬件
番茄灭世神9 小时前
PN学堂GD32教程第21篇——WiFiIOT
c语言·stm32·单片机·嵌入式·gd32
2zcode11 小时前
基于STM32的直流电机串级PID伺服控制系统设计与实现
stm32·单片机·嵌入式硬件·直流电机
都在酒里11 小时前
STM32低功耗休眠详解——睡眠、停止与待机模式实战,综合应用(三)
stm32·单片机·嵌入式硬件