从芯片介绍、STM32下位机驱动到Python上位机,一站式搞定高性能参数测量单元(PMU)

大家好!在之前的项目中,我需要构建一个高精度的自动化测试平台,核心是电压电流输出与采集。ADI的AD5522(参数测量单元PMU) 负责输出精确的可编程电压/电流并测量响应,而AD7606(同步采样ADC) 则负责高速、高精度地采集多路信号。这对“黄金搭档”在半导体测试、传感器标定和精密仪器中非常常见。

本次项目使用的硬件板卡并非由我本人设计(淘宝购买),因此硬件部分仅作简要介绍和关键点提示,重点会放在软件调试上。

在这里插入图片描述

第一次拿到它的时候,看着厚厚的Datasheet和复杂的寄存器,确实有点头皮发麻。网上成体系的、能跑通的中文资料又比较少。经过一番摸索,终于成功地驱动了起来。今天我就把自己调试和使用AD5522的全过程分享给大家,包括芯片简介、硬件连接注意事项、STM32下位机程序编写、以及Python上位机控制软件的实现,希望能帮各位小伙伴少走弯路!

一、 AD5522是什么?为什么是它?

ADI的AD5522是一个集成了源(Source)、测(Measure)、序(Sequence)三大功能的参数测量单元(PMU),常见于半导体测试(ATE)、芯片验证等高端场景。简单来说,AD5522是一个四通道的PMU芯片。每个通道都能独立工作,并且非常“全能”:

  • 源(Source): 可以输出精确的电压或电流,force模式。
  • 测(Measure): 可以测量通道上的电压或电流,sense模式。
  • 序(Sequence): 其内部有一个序列发生器,可以预先编程一系列操作(如 Force Voltage -> Measure Current -> 下一步…),然后自动执行,极大减轻主控MCU的负担。

在这里插入图片描述
正是因为这些特性,它在给待测器件(DUT)施加激励并精确测量其响应方面表现得非常出色。

二、 硬件连接简析(虽然不是“亲生”的硬件)

硬件板卡是现成的,下面对其进行简要分析,如图所示:

在这里插入图片描述
整个板卡由电源、STM32、AD5522、AD7606组成。AD5522每个通道可独立输出(Force) 精确的电压/电流,并测量(Sense/Measure) 负载上的电压或电流。完美用于激励待测器件(DUT)。AD7606负责同步采集DUT的多路响应信号,或者同时监测系统关键节点的电压,8通道同步采样、16位ADC。STM32F103通过SPI配置和控制AD5522与AD7606,处理数据,执行逻辑,并通过串口与上位机通信,其强大的性能足以处理两个外设的中断和实时数据流。

整板需要多种不同规格的电源,这是最复杂也是最关键的部分!除了数字电源,如2.5V,3.3V,5V外还需要模拟正负电源,如±15V。我在调试过程中,12V转±15V电源芯片发烫极其严重,最终选择不再使用该电源芯片,直接用其他电源供电,这个问题最终没找到原因。不过商家其他板卡没有这个现象,晕。

AD5522、AD7606与STM32的通信方式都是SPI,AD5522有SPI和LVDS两种接口模式

在这里插入图片描述
一定要检查硬件,看该引脚是接的高电平还是低电平,我当时在调试时使用的是SPI,但是没发现硬件是选用的LVDS,导致通信极其不稳定,最后还是在同事帮助下找到该问题。

AD7606要注意硬件上这两个引脚是否正确连接

在这里插入图片描述

三、 下位机驱动(STM32篇)之双设备驱动

驱动的核心是让STM32正确地与两个设备“对话”。
完整的工程目录如下:

在这里插入图片描述

打开工程文件:

在这里插入图片描述

完整的工程打开如下:

在这里插入图片描述
AD5522核心程序AD5522_WriteRegister和AD5522_ReadRegister如下

void AD5522_WriteRegister( uint32_t Code)
{
	uint8_t i;
	delay_us(1);
	AD5522_SYNC_0; 
	delay_us(1);
	for (i=0;i<32;i++) 
	{
		if((Code&0x80000000)==0x80000000)  
			AD5522_SDI_1;  
		else 
			AD5522_SDI_0;
		delay_ns(5);
		AD5522_SCLK_1;
		delay_us(1);
		AD5522_SCLK_0;
		delay_us(1);
		Code<<=1; 
	}
	delay_us(1);
	AD5522_SYNC_1;
	delay_us(1);
}
uint8_t AD5522_ReadByte(void)
{
	uint8_t i;
	uint8_t result;
	AD5522_SYNC_0;
	for(i=0;i<8;i++) 
	{
		AD5522_SCLK_1;
		delay_us(1);	
		AD5522_SCLK_0; 
		result<<=1;  
	  if(AD5522_SDO)  
			result |= 0x01; 
		delay_us(1);
	}
	AD5522_SYNC_1;
	return result;
}
uint32_t AD5522_ReadRegister(uint32_t Code)
{
	uint32_t temp,L,M,H;
	AD5522_WriteRegister(Code);  
	delay_us(5);
	AD5522_SYNC_0;  
	AD5522_SDI_1;
	delay_us(1);
  	H=AD5522_ReadByte();  
	M=AD5522_ReadByte();  
	L=AD5522_ReadByte(); 
	delay_us(1);
	AD5522_SYNC_1;    
	temp=H<<16|M<<8|L; 
	return temp;
}

在这里插入图片描述
注意AD5522读寄存器时,不要将已经写的数据篡改。因此SDI一定要拉高,置1。

AD7606核心程序ad7606_ReadData如下

uint16_t SPI_ReceiveData(void)	  
{ 	 
	uint8_t count=0; 	  
	uint16_t Num=0; 
	AD7606_SCLK_1;
	for(count=0;count<16;count++)
	{
		Num<<=1;
		AD7606_SCLK_0;
		if(AD7606_SDO) Num |= 0x01;
		AD7606_SCLK_1;
	}
	return Num;
}	 
uint16_t ad7606_ReadBytes(void)
{
  uint16_t usData = 0;

	usData = SPI_ReceiveData();
  return usData;
}
void ad7606_ReadData(int16_t *ad1,int16_t *ad2,int16_t *ad3,int16_t *ad4,
												 int16_t *ad5,int16_t *ad6,int16_t *ad7,int16_t *ad8)
{
		AD7606_CS_0;
		*ad1=ad7606_ReadBytes();
		*ad2=ad7606_ReadBytes();
		*ad3=ad7606_ReadBytes();
		*ad4=ad7606_ReadBytes();
		*ad5=ad7606_ReadBytes();
  		*ad6=ad7606_ReadBytes();
	  	*ad7=ad7606_ReadBytes();
	  	*ad8=ad7606_ReadBytes();
		AD7606_CS_1;
}

由于AD5522寄存器非常复杂,为了更方便地控制和调试,设计了简单的上位机代替普通的串口调试助手。

四、 上位机软件(Python篇)与联动测试

上位机软件可以做的更强大,比如加入智能校准,数据可视化等等,下面是个简单的版本,如图所示:

在这里插入图片描述

使用pyqt开发的Python上位机:

在这里插入图片描述
核心程序AD5522.py如下:

from enum import Enum
import re

# On power-up, the default code loaded to the offset DAC is 0xA492
# ±5 µA (200 kΩ)、±20 μA (50 kΩ)、±200 μA(5 kΩ)和±2 mA(500 Ω)
# FI = 4.5 × VREF × ((DAC_CODE − 32,768)/2^16)/(RSENSE × MI_放大器增益); MI_放大器增益:5或10, VREF = 5V
# FV = 4.5 × VREF × (DAC_CODE/2^16) − (3.5 × VREF × (OFFSET_DAC_CODE/2^16)) + DUTGND; VREF = 5V, OFFSET_DAC_CODE = 0xA492

# GAIN1 = 0, MEASOUT增益 = 1
# MV : ±VDUT
# MI : GAIN0 = 0, IDUT × RSENSE × 10
# MI : GAIN0 = 1, IDUT × RSENSE × 5
# GAIN1 = 1, MEASOUT增益 = 0.2
# MV : 0.2 × (VDUT - DUTGND + VMIN),VMIN = 3.5 × VREF × (OFFSET_DAC_CODE/2^16)
# MI : GAIN0 = 0, (IDUT × RSENSE × 10 × 0.2) + (0.45 × VREF)
# MI : GAIN0 = 1, (IDUT × RSENSE × 5 × 0.2) + (0.45 × VREF)

class CurrentRange(Enum):
    Range5uA = 0  # ±5 μA (200 kΩ)
    Range20uA = 1  # ±20 μA (50 kΩ)
    Range200uA = 2 # ±200 μA (5 kΩ)
    Range2mA = 3   # ±2 mA (500 Ω)

class MiGain(Enum):
    Gain5 = 5
    Gain10 = 10

class VrefValue(Enum):
    V2_5 = 2    # 2.5V
    V5 = 5      # 5V

class DUTGND(Enum):
    GND_0V = 0

class OffsetDacCode(Enum):
    OffsetCode_0 = 0
    OffsetCode_32768 = 32768
    OffsetCode_42130 = 42130
    OffsetCode_60855 = 60855
    OffsetCode_65535 = 65535

def get_rsense_for_range(current_range):
    if current_range == CurrentRange.Range5uA:
        return 200000   # 200 kΩ
    elif current_range == CurrentRange.Range20uA:
        return 50000    # 50 kΩ
    elif current_range == CurrentRange.Range200uA:
        return 5000     # 5 kΩ
    elif current_range == CurrentRange.Range2mA:
        return 500      # 500 Ω
    else:
        raise ValueError(f"Invalid CurrentRange: {current_range}")

def get_vref_value(vref):
    if vref == VrefValue.V2_5:
        return 2.5
    elif vref == VrefValue.V5:
        return 5.0
    else:
        raise ValueError(f"Invalid VrefValue: {vref}")

def binary_to_hex(binary_str):
    # 确保长度是4的倍数
    padding = (4 - len(binary_str) % 4) % 4
    if padding != 0:
        binary_str = '0' * padding + binary_str
    
    hex_str = ""
    for i in range(0, len(binary_str), 4):
        nibble = binary_str[i:i+4]
        hex_digit = hex(int(nibble, 2))[2:].upper()
        hex_str += hex_digit
    return hex_str

def is_binary_string(input_str):
    return all(char in '01' for char in input_str)

def is_hex_string(input_str):
    return all(char in '0123456789ABCDEFabcdef' for char in input_str)

def dac_register_address(force_mode):
    mapping = {
        "±5uA": "001000",
        "±20uA": "001001",
        "±200uA": "001010",
        "±2mA": "001011",
        "外部电流": "001100",
        "电压": "001101"
    }
    if force_mode in mapping:
        return mapping[force_mode]
    else:
        raise ValueError(f"无效的force_mode: {force_mode}")

def write_system_control_register(clamp_enable, comparator_output_enable, cpbias_enable, ini10k, measoutx_output_range):
    bits = ['0'] * 29  # 29位寄存器
    
    # 设置控制位
    if clamp_enable:
        bits[21] = '1'  # CLAMP_EN[3]
        bits[20] = '1'  # CLAMP_EN[2]
        bits[19] = '1'  # CLAMP_EN[1]
        bits[18] = '1'  # CLAMP_EN[0]
    
    if comparator_output_enable:
        bits[17] = '1'  # COMP_EN[3]
        bits[16] = '1'  # COMP_EN[2]
        bits[15] = '1'  # COMP_EN[1]
        bits[14] = '1'  # COMP_EN[0]
    
    if cpbias_enable:
        bits[13] = '1'

    if ini10k:
        bits[9] = '1'   # INI10K
    
    # MEASOUTx输出范围
    meas_mapping = {
        "GAIN00": (),
        "GAIN01": (('6', '1'),),
        "GAIN10": (('7', '1'),),
        "GAIN11": (('7', '1'), ('6', '1'))
    }
    if measoutx_output_range.upper() in meas_mapping:
        for pos, val in meas_mapping[measoutx_output_range.upper()]:
            bits[int(pos)] = val
    else:
        raise ValueError(f"无效的MEASOUTx_output_range: {measoutx_output_range}")
    
    bits[10] = '1'
    bits[5] = '1'
    
    # 反转并转换为16进制
    bits.reverse()
    binary_str = ''.join(bits)
    return binary_to_hex(binary_str)

def write_pmu_register(ch_en, pmu_channels, force_mode, measure_current_range, measure_mode, sf_ss, cl_enable):
    if not pmu_channels:
        raise ValueError("必须至少选择一个PMU通道")
    
    bits = ['0'] * 29
    
    # CH_EN
    if ch_en:
        bits[21] = '1'
    
    # PMU通道选择
    for channel in pmu_channels:
        ch_upper = channel.upper()
        if ch_upper == "PMU0":
            bits[24] = '1'
        elif ch_upper == "PMU1":
            bits[25] = '1'
        elif ch_upper == "PMU2":
            bits[26] = '1'
        elif ch_upper == "PMU3":
            bits[27] = '1'
        else:
            raise ValueError(f"无效的PMU通道: {channel}")
    
    # Force模式
    force_mapping = {
        "FV": (),
        "FI": (('19', '1'),),
        "FOHX电压": (('20', '1'),),
        "FOHX电流": (('19', '1'), ('20', '1'))
    }
    fm_upper = force_mode.upper()
    if fm_upper in force_mapping:
        for pos, val in force_mapping[fm_upper]:
            bits[int(pos)] = val
    else:
        raise ValueError(f"无效的Force模式: {force_mode}")
    
    # 电流范围
    current_range_mapping = {
        "±5uA": (),
        "±20uA": (('15', '1'),),
        "±200uA": (('16', '1'),),
        "±2mA": (('15', '1'), ('16', '1')),
        "±外部电流范围": (('17', '1'),)
    }
    if measure_current_range in current_range_mapping:
        for pos, val in current_range_mapping[measure_current_range]:
            bits[int(pos)] = val
    else:
        raise ValueError(f"无效的电流范围: {measure_current_range}")
    
    # Measure模式
    measure_mapping = {
        "ISENSE": (),
        "VSENSE": (('13', '1'),),
        "TEMP": (('14', '1'),),
        "HIGHZ": (('13', '1'), ('14', '1'))
    }
    mm_upper = measure_mode.upper()
    if mm_upper in measure_mapping:
        for pos, val in measure_mapping[mm_upper]:
            bits[int(pos)] = val
    else:
        raise ValueError(f"无效的Measure模式: {measure_mode}")
    
    # SF/SS
    sf_ss_mapping = {
        "00": (),
        "01": (('10', '1'),),
        "10": (('11', '1'),),
        "11": (('10', '1'), ('11', '1'))
    }
    if sf_ss in sf_ss_mapping:
        for pos, val in sf_ss_mapping[sf_ss]:
            bits[int(pos)] = val
    else:
        raise ValueError(f"无效的SF/SS选择: {sf_ss}")
    
    if cl_enable:
        bits[9] = '1'
        
    # 固定控制位
    bits[12] = '1'
    bits[8] = '1'
    
    bits.reverse()
    binary_str = ''.join(bits)
    return binary_to_hex(binary_str)

def write_dac_register(pmu_channels, dac_register, dac_address, dac_value):
    if len(dac_address) != 6 or not is_binary_string(dac_address):
        raise ValueError("地址必须是6位二进制字符串")
    
    if not pmu_channels:
        raise ValueError("必须至少选择一个PMU通道")
    
    if len(dac_value) != 4 or not is_hex_string(dac_value):
        raise ValueError("必须提供4位十六进制字符串")
    
    bits = ['0'] * 29
    
    # PMU通道选择
    for channel in pmu_channels:
        ch_upper = channel.upper()
        if ch_upper == "PMU0":
            bits[24] = '1'
        elif ch_upper == "PMU1":
            bits[25] = '1'
        elif ch_upper == "PMU2":
            bits[26] = '1'
        elif ch_upper == "PMU3":
            bits[27] = '1'
        else:
            raise ValueError(f"无效的PMU通道: {channel}")
    
    # DAC寄存器选择
    dac_reg_mapping = {
        "DACM": (('22', '1'),),
        "DACC": (('23', '1'),),
        "DACX1": (('22', '1'), ('23', '1'))
    }
    dr_upper = dac_register.upper()
    if dr_upper in dac_reg_mapping:
        for pos, val in dac_reg_mapping[dr_upper]:
            bits[int(pos)] = val
    else:
        raise ValueError(f"无效的DAC寄存器: {dac_register}")
    
    # DAC地址 (6位)
    for i in range(6):
        bits[16 + i] = dac_address[5 - i]
    
    # DAC值 (16位)
    dac_int = int(dac_value, 16)
    dac_bin = bin(dac_int)[2:].zfill(16)
    for i in range(16):
        bits[i] = dac_bin[15 - i]
    
    bits.reverse()
    binary_str = ''.join(bits)
    return binary_to_hex(binary_str)

def read_register(register_name, pmu_channel=None, dac_register=None, dac_address=None):
    bits = ['0'] * 29
    
    reg_upper = register_name.upper()
    if reg_upper == "SYSTEMCONTROL":
        bits[28] = '1'
    elif reg_upper == "PMU":
        if not pmu_channel:
            raise ValueError("必须提供PMU通道")
        bits[28] = '1'
        ch_upper = pmu_channel.upper()
        if ch_upper == "PMU0":
            bits[24] = '1'
        elif ch_upper == "PMU1":
            bits[25] = '1'
        elif ch_upper == "PMU2":
            bits[26] = '1'
        elif ch_upper == "PMU3":
            bits[27] = '1'
        else:
            raise ValueError(f"无效的PMU通道: {pmu_channel}")
    elif reg_upper == "DAC":
        if not pmu_channel or not dac_register or not dac_address:
            raise ValueError("读取DAC寄存器需要所有参数")
        if len(dac_address) != 6 or not is_binary_string(dac_address):
            raise ValueError("地址必须是6位二进制字符串")
        
        bits[28] = '1'
        ch_upper = pmu_channel.upper()
        if ch_upper == "PMU0":
            bits[24] = '1'
        elif ch_upper == "PMU1":
            bits[25] = '1'
        elif ch_upper == "PMU2":
            bits[26] = '1'
        elif ch_upper == "PMU3":
            bits[27] = '1'
        else:
            raise ValueError(f"无效的PMU通道: {pmu_channel}")
        
        dr_upper = dac_register.upper()
        if dr_upper == "DACM":
            bits[22] = '1'
        elif dr_upper == "DACC":
            bits[23] = '1'
        elif dr_upper == "DACX1":
            bits[22] = '1'
            bits[23] = '1'
        else:
            raise ValueError(f"无效的DAC寄存器: {dac_register}")
        
        for i in range(6):
            bits[16 + i] = dac_address[5 - i]
    else:
        raise ValueError(f"无效的寄存器名称: {register_name}")
    
    bits.reverse()
    binary_str = ''.join(bits)
    return binary_to_hex(binary_str)

def voltage_to_dac_value_hex(voltage, vref, offset_dac_code, dut_gnd):
    vref_val = get_vref_value(vref)
    offset_val = offset_dac_code.value / 65536.0
    numerator = voltage - dut_gnd.value + 3.5 * vref_val * offset_val
    denominator = 4.5 * vref_val / 65536.0
    dac_code = int(round(numerator / denominator))
    dac_code = max(0, min(65535, dac_code))
    return format(dac_code, '04X')

def dac_value_hex_to_voltage(dac_hex, vref, offset_dac_code, dut_gnd):
    if len(dac_hex) != 4 or not is_hex_string(dac_hex):
        raise ValueError("必须提供4位十六进制字符串")
    
    dac_code = int(dac_hex, 16)
    vref_val = get_vref_value(vref)
    offset_val = offset_dac_code.value / 65536.0
    voltage = (4.5 * vref_val * (dac_code / 65536.0) - 
              3.5 * vref_val * offset_val + 
              dut_gnd.value)
    return voltage

def current_to_dac_value_hex(current, vref, current_range, mi_gain):
    rsense = get_rsense_for_range(current_range)
    vref_val = get_vref_value(vref)
    gain_val = mi_gain.value
    
    numerator = current * rsense * gain_val
    denominator = 4.5 * vref_val / 65536.0
    dac_value = numerator / denominator + 32768
    dac_code = int(round(dac_value))
    dac_code = max(0, min(65535, dac_code))
    return format(dac_code, '04X')

def dac_value_hex_to_current(dac_hex, vref, current_range, mi_gain):
    if len(dac_hex) != 4 or not is_hex_string(dac_hex):
        raise ValueError("必须提供4位十六进制字符串")
    
    dac_code = int(dac_hex, 16)
    rsense = get_rsense_for_range(current_range)
    vref_val = get_vref_value(vref)
    gain_val = mi_gain.value
    
    current = (4.5 * vref_val * ((dac_code - 32768) / 65536.0) / 
              (rsense * gain_val))
    return current

def ad7606_value_hex_to_voltage(hex_str):
    if len(hex_str) != 4 or not is_hex_string(hex_str):
        raise ValueError("必须提供4位十六进制字符串")
    
    adc_value = int(hex_str, 16)
    if adc_value & 0x8000:  # 负数
        complement = (~adc_value + 1) & 0xFFFF
        return -complement / 32768 * 10
    else:  # 正数
        return adc_value / 32768 * 10

def ad7606_voltage_to_true_voltage(voltage, vref, offset_dac_code, dut_gnd, measoutx_output_range):
    vref_val = get_vref_value(vref)
    vmin = 3.5 * vref_val * (offset_dac_code.value / 65536.0)
    range_upper = measoutx_output_range.upper()
    
    if range_upper in ["GAIN00", "GAIN01"]:
        return voltage
    elif range_upper in ["GAIN10", "GAIN11"]:
        return voltage / 0.2 + dut_gnd.value - vmin
    else:
        raise ValueError(f"无效的MEASOUTx_output_range: {measoutx_output_range}")

def ad7606_voltage_to_true_current(voltage, vref, current_range, measoutx_output_range):
    rsense = get_rsense_for_range(current_range)
    vref_val = get_vref_value(vref)
    range_upper = measoutx_output_range.upper()
    
    if range_upper == "GAIN00":
        return voltage / (rsense * 10)
    elif range_upper == "GAIN01":
        return voltage / (rsense * 5)
    elif range_upper == "GAIN10":
        return (voltage - 0.45 * vref_val) / (rsense * 10 * 0.2)
    elif range_upper == "GAIN11":
        return (voltage - 0.45 * vref_val) / (rsense * 5 * 0.2)
    else:
        raise ValueError(f"无效的MEASOUTx_output_range: {measoutx_output_range}")  

上位机软件使用步骤:1、选择设备,设置波特率等参数;2、设备连接;3、刷新设备;4、设置系统参数;5、选择PMU通道;6、设置PMU参数;7、Force或Measure功能测试

在这里插入图片描述

使用单帧命令控制AD5522系统,如下图中发送指定命令即可

在这里插入图片描述

具体操作 单帧指令
刷新设备:00
写系统寄存器 01 00 3F E4 A0
写PMU寄存器并回读 02 0F 21 B3 00 11 00 00 00
写DAC寄存器并回读 03 0F CD FF 55 11 CD 00 00
四个PMU通道加压测压 05 BF FF
四个PMU通道加压测流 06 9F FF
四个PMU通道加流测流 07 8F FF
四个PMU通道加流测压 08 9F FF
读取CH1 ADC值 09
读取CH2 ADC值 0A
读取CH3 ADC值 0B
读取CH4 ADC值 0C

六、 总结与展望

通过这次项目,我们成功驯服了AD5522这头“猛兽”。总结一下关键步骤:

  • 硬件是基础: 确保供电和连接正确。
  • SPI是桥梁: 写好底层的读写函数,并用逻辑分析仪验证。
  • 寄存器是地图: 仔细阅读Datasheet,正确配置每一位。
  • 上位机是遥控器: 做一个方便的工具来快速验证和调试。

完整的下位机程序、数据手册和上位机软件手把手一起玩转AD5522:避坑指南与软硬件实战全解析资源包

在这里插入图片描述

未来还可以探索AD5522更强大的功能,比如利用其内部的序列发生器(Sequencer)实现自动化的测试流程,或者实现更复杂的四线制测量以提升精度。

希望这篇“手把手一起玩转AD5522”的指南能对大家有所帮助!如果在调试中也遇到了有趣的问题或坑,欢迎在评论区一起交流讨论。


希望本文对大家有帮助,上文若有不妥之处,欢迎指正

分享决定高度,学习拉开差距

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐