3. 硬件接口基础:串口通信原理、GPIB/LAN控制、SCPI命令基础、与DUT建立连接
做嵌入式测试,说白了就是跟硬件打交道。你写的脚本再漂亮,最后也得落到跟设备通信上。这一章我们聊聊最基础、也最绕不开的几个接口——串口、GPIB、LAN,以及它们通用的语言SCPI。
我个人习惯,拿到一个新项目,第一件事就是先把通信链路打通。链路不通,后面全是白搭。咱们一个一个来看。
3.1 串口通信:最朴素的连接方式
串口,也叫UART,是嵌入式世界里的“通用语”。几乎所有的MCU、传感器模块、老式仪器都带它。为什么?因为它简单,三根线(TX、RX、GND)就能干活。
3.1.1 串口参数:对不上就白搭
串口通信就像两个人打电话,你得先约定好“语言”和“语速”。这些参数必须两边一致:
| 参数 | 常见值 | 说明 |
|---|---|---|
| 波特率 | 9600, 115200, 921600 | 每秒传输的符号数。越高越快,但容易丢包 |
| 数据位 | 7, 8 | 一帧里有效数据的位数。现在基本都用8 |
| 停止位 | 1, 2 | 帧结束的标志位。1位最常见 |
| 校验位 | None, Even, Odd | 简单的错误检测。很多设备直接关掉 |
| 流控制 | None, RTS/CTS | 硬件握手。嵌入式设备基本不用 |
3.1.2 Python操作串口:pyserial实战
Python里操作串口,几乎清一色用 pyserial 库。安装很简单:pip install pyserial。
下面是一个最基础的打开、发送、接收的例子:
import serial
import time
# 打开串口
ser = serial.Serial(
port='COM3', # Windows下是COM口,Linux下是/dev/ttyUSB0
baudrate=115200,
bytesize=8,
parity='N',
stopbits=1,
timeout=1 # 读取超时,单位秒
)
# 检查是否打开成功
if ser.is_open:
print(f"串口 {ser.port} 已打开")
# 发送数据(记得加换行符,很多设备靠这个判断命令结束)
ser.write(b"AT\r\n")
# 读取响应
response = ser.readline()
print(f"收到: {response}")
# 用完记得关
ser.close()
config = {'port':'COM3', 'baudrate':115200, 'timeout':1},然后 ser = serial.Serial(**config)。这样换设备时改一个字典就行。
3.2 GPIB与LAN控制:仪器通信的两大支柱
串口虽然通用,但速度慢,而且不支持多设备级联。到了实验室级别的仪器(示波器、万用表、频谱仪),GPIB和LAN才是主流。
3.2.1 GPIB:老当益壮的总线
GPIB(IEEE-488)是上个世纪70年代的标准,但现在很多高端仪器还在用。它支持最多15台设备挂在同一条总线上,用“控制器-听者-讲者”的模式通信。
Python控制GPIB,通常用 pyvisa 库配合NI-VISA驱动。我个人建议,如果条件允许,尽量用LAN口替代GPIB。为什么?GPIB线又粗又贵,而且需要专门的PCIe卡或USB-GPIB转接头。LAN线几块钱一根,随处可得。
3.2.2 LAN控制:现代仪器的标配
现在的仪器,十个里有九个带网口。通过LAN控制,本质上就是Socket通信。仪器监听一个端口(通常是5025或5555),你连上去发命令就行。
举个例子,控制一台是德科技的示波器:
import socket
# 建立TCP连接
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(5)
# 连接仪器(IP地址和端口)
instrument_ip = "192.168.1.100"
port = 5025
sock.connect((instrument_ip, port))
# 发送SCPI命令
sock.send(b"*IDN?\n")
# 读取响应
response = sock.recv(1024)
print(f"仪器标识: {response.decode().strip()}")
# 关闭连接
sock.close()
pyvisa 把它们统一成了“资源”。你只需要指定资源地址,比如 TCPIP0::192.168.1.100::5025::SOCKET,剩下的读写操作几乎一样。这就是VISA层的魅力。
3.3 SCPI命令基础:仪器的通用语言
SCPI(Standard Commands for Programmable Instruments)是一种标准化的仪器控制语言。你想想看,如果没有SCPI,每家仪器厂商都用自己的命令集,那测试脚本得写成什么样?
SCPI命令的结构很清晰,遵循“树形”层级。比如:
MEASure:VOLTage:DC?—— 测量直流电压SOURce:VOLTage:LEVel:IMMediate:AMPLitude 5.0—— 设置电源输出电压为5VSYSTem:ERRor?—— 查询仪器错误
注意看,冒号 : 表示路径分隔符,问号 ? 表示查询,没有问号就是设置。大小写不敏感,但习惯上把关键字大写。
MEASure 和 READ 的区别坑过。MEASure 是仪器自己触发一次测量然后返回结果,而 READ 是从内部缓冲区读上次测量的结果。如果你连续发 READ,拿到的可能是旧数据。所以,需要新数据时,记得先发 INITiate 或直接用 MEASure。
3.4 与DUT建立连接:从脚本到硬件的最后一公里
好了,前面讲了串口、LAN、SCPI,现在把它们串起来。与DUT(Device Under Test,被测设备)建立连接,通常分三步:
- 发现设备: 确定DUT的通信地址(串口号、IP地址、GPIB地址)。
- 建立会话: 打开串口或Socket,设置超时和缓冲区。
- 验证通信: 发送一个简单的查询命令(比如
*IDN?),确认DUT在线且响应正常。
我习惯把这部分封装成一个类,方便复用。下面是一个简化版的例子:
import serial
import socket
class DUTConnection:
def __init__(self, conn_type, **params):
self.conn_type = conn_type
self.params = params
self.handle = None
def connect(self):
if self.conn_type == 'serial':
self.handle = serial.Serial(**self.params)
elif self.conn_type == 'lan':
self.handle = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.handle.settimeout(self.params.get('timeout', 5))
self.handle.connect((self.params['ip'], self.params['port']))
else:
raise ValueError(f"不支持的连接类型: {self.conn_type}")
def send(self, cmd):
if self.conn_type == 'serial':
self.handle.write(cmd.encode() + b'\n')
elif self.conn_type == 'lan':
self.handle.send(cmd.encode() + b'\n')
def recv(self, size=1024):
if self.conn_type == 'serial':
return self.handle.readline()
elif self.conn_type == 'lan':
return self.handle.recv(size)
def close(self):
if self.handle:
self.handle.close()
# 使用示例
dut = DUTConnection('lan', ip='192.168.1.100', port=5025, timeout=3)
dut.connect()
dut.send("*IDN?")
print(dut.recv())
dut.close()
connect() 方法里,我会加一个重试机制。比如尝试3次,每次间隔1秒。因为有些仪器上电后需要几秒才能响应网络请求。脚本里加个 time.sleep(1) 和 try...except,能省去很多手动重启的麻烦。
3.5 本章知识体系
下面这张图,把本章的核心知识点串了起来。你可以把它当作一个快速索引:
嗯,这一章的内容就这些。串口、GPIB、LAN,加上SCPI,构成了嵌入式测试中最底层的通信骨架。把这些基础打牢,后面写自动化脚本时,你才会觉得“手里有粮,心里不慌”。