尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

基于Python的AD5522自动化测试平台搭建与数据可视化

基于Python的AD5522自动化测试平台搭建与数据可视化 1. 为什么我要给AD5522搭一套自动化测试平台AD5522这颗芯片做精密测量或者半导体测试设备的朋友应该不陌生。它是一颗四通道的PMU参数测量单元每通道都能独立做加压测流FVMI、加流测压FIMV、加压测压FVMV等操作内部有16位DAC和20位ADC配合一堆寄存器控制电压电流档位、钳位、比较器。手动调试它的寄存器用示波器和万用表一个通道一个通道去量效率低到让人怀疑人生——尤其是要跑几十个测试点、扫一遍电压电流曲线的时候手点寄存器点到手抽筋数据还得手动抄到Excel里画图。我最初接手这个项目的时候就是纯手动操作。上位机软件点一下改一个寄存器值读一次ADC记一个数。一个通道扫32个点四个通道就是128次操作中间还容易记错行。后来实在受不了了决定用Python搭一套自动化测试平台把寄存器配置、数据采集、曲线绘制全部串起来。这套平台跑通之后原本需要大半天的测试现在十几分钟就能出完整报告而且数据可追溯、可复现。这篇文章就是把这套平台的搭建过程完整拆开讲。从硬件连接、寄存器映射、Python驱动层封装到自动化测试脚本设计、数据可视化再到实际调试中踩过的坑我都会一一说清楚。适合有Python基础、正在做芯片测试或精密测量相关工作的朋友参考。如果你手头正好有AD5522或者类似的PMU芯片这套思路可以直接迁移。2. AD5522寄存器体系与通信链路的前期梳理2.1 先搞清楚这颗芯片的寄存器地图AD5522的寄存器不算特别复杂但如果不提前梳理清楚写代码的时候会非常混乱。它主要有几类寄存器系统控制寄存器、DAC数据寄存器、ADC配置寄存器、比较器/钳位寄存器、测量模式寄存器。每个通道有独立的DAC和ADC寄存器地址上通过通道位来区分。我建议在动手写代码之前先做一张寄存器映射表。这张表至少包含寄存器名称、地址或地址偏移、位宽、读写属性、功能说明、常用配置值。比如DAC寄存器是16位的高4位是通道选择低12位或者16位是数据取决于量程配置。ADC寄存器读回来的是20位数据需要做符号扩展和量程换算。这张表不用做得多漂亮Excel就行但一定要有。我一开始偷懒没做直接对着数据手册写代码结果写到一半发现某个寄存器的位定义记混了调了半天才发现是通道选择位搞错了。后来补了一张表代码里的常量定义直接对着表来效率高了很多。2.2 通信接口的选择SPI还是I2CAD5522支持SPI和I2C两种通信接口具体用哪个取决于你的硬件设计。我这边用的是SPI因为速度快而且AD5522的SPI时序比较标准用Python控制起来方便。SPI通信需要四根线SCLK、SDI、SDO、CS。如果你的主控是树莓派或者类似的Linux开发板可以直接用spidev库来操作。如果是通过USB转SPI的模块那就用对应的串口协议封装。我这边用的是FTDI的USB转SPI模块配合pyftdi库跨平台兼容性不错。这里有个细节要注意AD5522的SPI模式是CPOL0、CPHA0也就是时钟空闲低电平数据在时钟上升沿采样。这个在初始化的时候一定要设对否则读回来的数据全是乱的。我一开始就是CPHA设错了读出来的ADC值一直在跳以为是芯片坏了后来用逻辑分析仪抓了波形才发现时序不对。2.3 Python环境与依赖库的准备Python环境这块我建议用3.8以上的版本太老的版本有些库不支持。核心依赖库有这几个pyftdi或spidevSPI通信numpy数据处理和数值计算matplotlib或plotly数据可视化pandas测试数据管理和导出pyserial如果走串口通信的话需要安装命令很简单pip install pyftdi numpy matplotlib pandas pyserial如果你用的是树莓派spidev可能需要单独装pip install spidev虚拟环境建议用venv或者conda建一个避免和系统Python的库冲突。我这边用的是conda因为后面还要跑一些数据分析的脚本conda管理起来方便。提示pyftdi在Windows下需要先安装FTDI的驱动用Zadig工具把FTDI设备替换成WinUSB驱动否则pyftdi识别不到设备。这一步卡了我很久官方文档里写得不太明显。3. 用Python封装AD5522驱动层的完整思路3.1 寄存器读写的基础函数设计驱动层的第一件事是把SPI读写封装成两个基础函数write_register和read_register。AD5522的SPI帧格式是32位高8位是命令字中间8位是地址低16位是数据。读操作和写操作的命令字不同读的时候还要多发一个空帧来接收数据。import spidev class AD5522Driver: def __init__(self, bus0, device0, speed1000000): self.spi spidev.SpiDev() self.spi.open(bus, device) self.spi.max_speed_hz speed self.spi.mode 0 # CPOL0, CPHA0 def _build_frame(self, cmd, addr, data): frame (cmd 24) | (addr 16) | (data 0xFFFF) return [(frame 24) 0xFF, (frame 16) 0xFF, (frame 8) 0xFF, frame 0xFF] def write_register(self, addr, data): frame self._build_frame(0x01, addr, data) self.spi.xfer2(frame) def read_register(self, addr): frame self._build_frame(0x02, addr, 0x0000) resp self.spi.xfer2(frame [0x00, 0x00, 0x00, 0x00]) # 解析返回的32位数据 result (resp[4] 24) | (resp[5] 16) | (resp[6] 8) | resp[7] return result这段代码是简化版实际用的时候要根据AD5522的数据手册调整命令字和帧长度。关键是理解SPI帧的组装逻辑命令、地址、数据三段拼成一个整数再拆成字节数组发出去。3.2 通道配置的抽象把寄存器操作变成可读的API直接调write_register太底层了每次都要查地址和位定义容易出错。我在驱动层上面又包了一层把常用的操作抽象成方法比如set_dac_voltage(channel, voltage)设置某个通道的DAC输出电压set_current_range(channel, range_ua)设置电流量程enable_channel(channel)使能通道read_adc(channel)读取ADC值并换算成电压或电流这样上层测试脚本就不用关心寄存器细节了直接调这些方法就行。比如设置通道0输出1.5Vdriver.set_dac_voltage(0, 1.5)底层会自动计算DAC码值、选择正确的量程、写入对应的寄存器。这个抽象层是整套平台的核心写好了之后测试脚本的开发效率会高很多。3.3 量程切换与校准系数的处理AD5522的电压和电流量程是可配的不同量程下DAC码值和实际输出值的换算关系不同。比如电压量程有±5V、±10V、±20V几档电流量程有±5uA、±20uA、±200uA、±1mA、±5mA、±20mA等。每换一个量程换算系数就要跟着变。我的做法是在驱动层里维护一张量程表每个量程对应一个换算系数和寄存器配置值。切换量程的时候先查表拿到配置值写入寄存器然后更新内部的换算系数。这样上层调用的时候只需要指定目标量程不用关心底层细节。校准系数这块如果对精度要求高建议每个量程都做一次实测校准。用高精度万用表量实际输出和设定值对比算出一个修正系数。我这边因为测试精度要求不是特别高用的是数据手册的标称系数误差在0.1%以内够用了。4. 自动化测试脚本的设计与测试流程编排4.1 测试用例的参数化设计自动化测试的核心是测试用例。我把每个测试用例定义成一个字典或者JSON配置包含测试名称、通道号、量程、扫描起始值、扫描终止值、扫描步数、测量模式、判定条件。比如test_case { name: CH0_电压扫描, channel: 0, mode: FVMI, range: ±10V, start: -5.0, stop: 5.0, steps: 21, criteria: {max_error: 0.01} }这样测试脚本就是一个循环遍历测试用例列表对每个用例执行扫描采集数据判定结果保存记录。要加新的测试项只需要加一条配置不用改代码。4.2 扫描测试的执行流程一个典型的扫描测试流程是这样的初始化驱动复位芯片配置通道量程和测量模式循环设置DAC值每次设置后延时等待稳定读取ADC值记录设定值和测量值扫描完成后计算误差、线性度等指标保存原始数据和判定结果延时等待稳定这一步很关键。AD5522的建立时间跟量程和负载有关数据手册标称是几十微秒到几毫秒。我一般设10ms的延时保守一点确保读数稳定。如果追求速度可以先用示波器测一下实际建立时间再设一个合理的值。import time def run_sweep(driver, case): results [] step_size (case[stop] - case[start]) / (case[steps] - 1) for i in range(case[steps]): setpoint case[start] i * step_size driver.set_dac_voltage(case[channel], setpoint) time.sleep(0.01) measured driver.read_adc(case[channel]) results.append({setpoint: setpoint, measured: measured}) return results4.3 多通道并行测试的调度AD5522有四个通道如果每个通道串行测试时间会比较长。但实际上四个通道可以并行配置和采集因为芯片内部每个通道是独立的。我的做法是先一次性配置好四个通道的量程和模式然后在一个循环里同时设置四个通道的DAC值延时后同时读取四个通道的ADC值。这样测试时间可以缩短到原来的四分之一左右。不过要注意多通道同时工作时芯片的功耗和温升会增加如果对精度要求高可能需要适当增加延时等温度稳定后再读数。4.4 测试数据的结构化存储每次测试的数据我都会存成CSV和JSON两种格式。CSV方便用Excel打开看JSON方便后续程序读取和分析。文件名带上时间戳和测试用例名称比如20250115_CH0_电压扫描.csv。数据表的结构大概是这样的序号设定值(V)测量值(V)误差(mV)通道量程时间戳1-5.000-4.9982.00±10V...2-4.500-4.4973.00±10V...这样存的好处是后面要做数据分析或者画图直接读CSV就行不用再连硬件。5. 数据可视化从原始数据到可读的测试报告5.1 用Matplotlib画基本的扫描曲线最直观的可视化就是设定值vs测量值的曲线。用Matplotlib几行代码就能画出来import matplotlib.pyplot as plt import pandas as pd df pd.read_csv(20250115_CH0_电压扫描.csv) plt.figure(figsize(10, 6)) plt.plot(df[设定值(V)], df[测量值(V)], o-, label测量值) plt.plot(df[设定值(V)], df[设定值(V)], --, label理想值) plt.xlabel(设定电压 (V)) plt.ylabel(测量电压 (V)) plt.title(CH0 电压扫描曲线) plt.legend() plt.grid(True) plt.savefig(CH0_电压扫描.png, dpi150)这张图能直观看出芯片的输出是否线性、有没有明显的偏差。如果测量曲线和理想曲线几乎重合说明精度很好如果有明显的弯曲或者偏移就要检查量程配置或者校准系数。5.2 误差分布与线性度分析除了看曲线我还习惯画一张误差分布图。把每个测试点的误差测量值减设定值画成柱状图或者散点图能快速看出误差是否在允许范围内有没有异常点。df[误差(mV)] (df[测量值(V)] - df[设定值(V)]) * 1000 plt.figure(figsize(10, 4)) plt.bar(df[序号], df[误差(mV)]) plt.axhline(y10, colorr, linestyle--, label10mV限) plt.axhline(y-10, colorr, linestyle--, label-10mV限) plt.xlabel(测试点序号) plt.ylabel(误差 (mV)) plt.title(CH0 电压扫描误差分布) plt.legend() plt.savefig(CH0_误差分布.png, dpi150)线性度的话可以用numpy的polyfit做一次线性拟合然后算最大偏差。这个指标在测试报告里很有说服力。5.3 多通道对比图的绘制四个通道的测试结果放在一张图里对比能看出通道之间的一致性。用Matplotlib的subplot或者直接在一张图上画四条线都行。我一般用2x2的subplot每个通道一张小图看起来比较清晰。fig, axes plt.subplots(2, 2, figsize(12, 10)) for ch in range(4): ax axes[ch // 2][ch % 2] df_ch pd.read_csv(f20250115_CH{ch}_电压扫描.csv) ax.plot(df_ch[设定值(V)], df_ch[测量值(V)], o-) ax.plot(df_ch[设定值(V)], df_ch[设定值(V)], --) ax.set_title(fCH{ch}) ax.grid(True) plt.tight_layout() plt.savefig(四通道对比.png, dpi150)这张图在汇报的时候特别好用一眼就能看出哪个通道有问题。5.4 生成HTML测试报告如果每次都要手动跑脚本画图还是有点麻烦。我后来用Jinja2模板加Matplotlib做了一个自动生成HTML报告的功能。测试完成后脚本自动把图表嵌入HTML生成一份完整的测试报告直接用浏览器打开就能看。from jinja2 import Template template Template( html headtitleAD5522测试报告/title/head body h1AD5522自动化测试报告/h1 p测试时间: {{ timestamp }}/p h2CH0 扫描曲线/h2 img src{{ ch0_curve }} width800 h2误差分布/h2 img src{{ ch0_error }} width800 /body /html ) html template.render(timestamp2025-01-15 10:30, ch0_curveCH0_电压扫描.png, ch0_errorCH0_误差分布.png) with open(report.html, w) as f: f.write(html)这样每次测试完直接打开report.html就能看结果不用再手动整理。6. 实际调试中踩过的坑与排查经验6.1 SPI通信不稳定的问题最开始调SPI的时候读回来的数据经常跳变有时候正常有时候全是0xFF。排查了半天发现是SPI时钟速度设太高了。AD5522的SPI最高支持多少频率我忘了但实际用的时候1MHz以上就不太稳了。降到500kHz之后数据就稳定了。另外CS片选信号的处理也很关键。有些USB转SPI模块的CS是自动控制的但AD5522要求CS在整帧传输期间保持低电平。如果CS提前拉高数据就断了。我后来在代码里手动控制CS确保整帧传完再拉高。6.2 ADC读数跳变与滤波处理ADC读数偶尔会有几个LSB的跳变这是正常的噪声。但如果跳变太大就要检查是不是量程配置错了或者参考电压不稳。我的做法是在软件里做一次中值滤波连续读5次去掉最大最小值取中间3次的平均。这样读数会稳定很多。def read_adc_filtered(driver, channel, samples5): values [driver.read_adc(channel) for _ in range(samples)] values.sort() return sum(values[1:-1]) / (samples - 2)6.3 量程切换后的建立时间不足有一次做电流扫描发现切换量程后第一个点的误差特别大后面的点就正常了。后来意识到是量程切换后芯片内部需要时间稳定而我延时设得太短。把延时从1ms增加到20ms之后问题就解决了。这个建立时间跟量程跨度有关跨度越大需要的时间越长。如果从±5uA切到±20mA建议延时至少50ms。具体数值最好用示波器实测一下不同批次的芯片可能略有差异。6.4 多通道同时工作时的串扰四个通道同时输出的时候如果负载是共地的通道之间可能会有串扰。我实测下来串扰大概在几百微伏的量级对大多数应用来说可以接受。但如果你的测试精度要求很高建议还是分时复用一次只测一个通道其他通道输出0V或者高阻。7. 平台扩展与日常使用的一些体会这套平台跑通之后我又陆续加了一些功能。比如自动校准用一个高精度万用表作为参考自动扫描每个量程算出修正系数存到配置文件里下次测试自动加载。还有测试结果数据库把每次测试的数据存到SQLite里方便追溯和对比不同批次的数据。日常使用中我觉得最实用的还是配置文件驱动的设计。测试用例、量程配置、校准系数都放在JSON文件里改测试项不用动代码直接改配置就行。这样即使是不太懂Python的同事也能自己加测试用例。另外日志记录很重要。每次测试的详细过程包括每个寄存器的读写值、每个测试点的设定值和测量值都记到日志文件里。出了问题可以回溯看看是哪一步不对。我用的Python标准库logging配置成同时输出到控制台和文件很方便。如果你也在做类似的芯片测试平台我的建议是先把驱动层写扎实把寄存器操作封装好上层的东西都好说。驱动层不稳后面全是坑。另外别一开始就追求大而全先把一个通道的扫描测试跑通再逐步扩展。我一开始想一口气把四个通道、所有量程都支持结果代码写了一堆bug后来推倒重来从一个通道开始反而快了很多。
返回列表