关注+星标公众号,不错过精彩内容来源 | 嵌入式情报局
实验室里的示波器、万用表、电源是如何被计算机精确控制的?答案藏在一种名为SCPI的“普通话”中。
本文不仅揭秘SCPI协议,还教你用STM32单片机通过串口控制万用表,并手写代码解析数据——无需复杂库函数,嵌入式小白也能轻松上手!
一、什么是SCPI协议?仪器的“普通话”1. 为什么需要SCPI?SCPI(Standard Commands for Programmable Instruments)是一种标准化仪器控制语言,解决了不同品牌设备之间的“语言障碍”。
?跨厂商兼容:Keysight、Tektronix、Fluke等品牌设备均可通过相同语法控制。
?层级化命令:类似文件路径的树状结构(如:MEAS:VOLT:DC?查询直流电压)。
?简单高效:通过ASCII字符串通信,无需专用驱动。
2. SCPI vs. 其他协议协议特点适用场景SCPI标准化高、通用性强实验室仪器、测试设备Modbus轻量级、适合工业环境PLC、传感器LXI基于以太网、支持Web控制高端仪器、分布式系统二、台式万用表的通用SCPI指令(以Keysight 34401A为例)1. 基础配置指令?选择测量功能
:CONF:VOLT:DC ; 直流电压测量
:CONF:CURR:AC ; 交流电流测量
:CONF:FRES ; 4线电阻测量(高精度)
?量程与分辨率设置
:VOLT:DC:RANG 10 ; 手动量程10V(若超量程自动切换)
:VOLT:DC:RES 0.001 ; 分辨率设为1mV
2. 触发与数据读取?单次触发
:TRIG:SOUR IMM ; 触发源设为“立即触发”
:READ? ; 返回当前电压(如“+5.325E+0”)
?连续采样
:SAMP:COUN 100 ; 采样100次
:INIT ; 启动采样
:FETC? ; 读取数据(逗号分隔字符串)
3. 错误查询:SYST:ERR? ; 返回错误信息(如“0, No error”)
三、实战:STM32单片机控制万用表(含自定义数据解析)1. 硬件连接?STM32:USART1(TX-PA9,RX-PA10)
?万用表:RS-232串口(波特率9600,8N1)
?电平转换:MAX3232芯片(TTL转RS-232)
2. 代码实现(HAL库)步骤1:初始化串口UART_HandleTypeDef huart1;
huart1.Instance = USART1;
huart1.Init.BaudRate = 9600;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
HAL_UART_Init(&huart1);
步骤2:发送SCPI命令// 设置直流电压测量
char cmd[] = ":CONF:VOLT:DC\r
"; // SCPI命令需以\r
结尾
HAL_UART_Transmit(&huart1, (uint8_t*)cmd, strlen(cmd), 1000);
步骤3:接收数据并解析(自定义函数替代atof)// 自定义ASCII转浮点函数(支持科学计数法)
float string_to_float(const char *str) {
float result = 0.0, fraction = 0.1;
int exponent = 0, sign = 1, exp_sign = 1;
char *p = (char*)str;
// 处理符号位
if (*p == '-') { sign = -1; p++; }
elseif (*p == '+') { p++; }
// 解析整数和小数部分
while (*p >= '0' && *p '9') { result = result * 10 + (*p - '0'); p++; }
if (*p == '.') {
p++;
while (*p >= '0' && *p '9') {
result += (*p - '0') * fraction;
fraction *= 0.1;
p++;
}
}
// 处理指数部分(E/e)
if (*p == 'E' || *p == 'e') {
p++;
if (*p == '-') { exp_sign = -1; p++; }
elseif (*p == '+') { p++; }
while (*p >= '0' && *p '9') { exponent = exponent * 10 + (*p - '0'); p++; }
exponent *= exp_sign;
}
return sign * result * powf(10, exponent);
}
// 接收数据并转换
uint8_t rx_buf[32];
HAL_UART_Receive(&huart1, rx_buf, sizeof(rx_buf), 1000);
float voltage = string_to_float((char*)rx_buf);
printf("电压值: %.3f V", voltage);
步骤4:错误处理// 查询错误信息
char err_cmd[] = ":SYST:ERR?\r
";
HAL_UART_Transmit(&huart1, (uint8_t*)err_cmd, strlen(err_cmd), 1000);
HAL_UART_Receive(&huart1, rx_buf, sizeof(rx_buf), 1000);
printf("错误信息: %s", rx_buf);
四、为什么不用标准库函数?自定义函数的优势特性自定义string_to_float标准库atof代码体积~200字节(无依赖)依赖完整库文件执行速度更快(无冗余检查)较慢(支持复杂错误处理)内存占用极低(无动态内存)可能依赖系统内存管理适用场景:嵌入式设备、资源受限的单片机项目。
更多学习参考如下资料:
? 《IEEE 488.2标准协议》
? GitHub开源库:PyVISA、SCPI-parser
------------ END ------------
wvmbr0nzynt6404450605.gif
●专栏《嵌入式工具》●专栏《嵌入式开发》●专栏《Keil教程》●嵌入式专栏精选教程
关注公众号回复“加群”按规则加入技术交流群,回复“1024”查看更多内容。
点击“阅读原文”查看更多分享。 |