RFStimuli

RFStimuli

1. 任务背景

1.1 任务来源

分配任务:将 C# Raptor2.MS.RFStimuli 服务中 外部信号源请求处理 的逻辑理解清楚,并用 Python 在 test-interface-client 项目中实现对应的 gRPC 服务端。

1.2 业务场景

在 RX(接收端)校准过程中,DUT(被测设备)内部的 FAAP 需要外部信号源发射特定的 RF 信号进来,以测试 DUT 的接收功能。我们的 Python 服务就是那个"外部信号源控制端"——接收 DUT 的请求,控制仪器,给 DUT 打信号。

DUT (FAAP)                    我们的 Python 服务                    信号源仪表
    │                                │                                │
    │── gRPC: RequestExternalStimuli ──>│                                │
    │   "我要 3500MHz CW -30dBm"        │                                │
    │                                   │── SCPI: 设频率、功率、打开 ────>│
    │                                   │                                │── RF 信号 ──> DUT
    │                                   │<── 仪器确认 ──────────────────│
    │<── gRPC: ConfigurationResponse ──│                                │
    │   "配置好了,端口偏移如下"          │                                │
    │                                   │                                │
    │   ... DUT 做 RX 校准 ...           │                                │
    │                                   │                                │
    │── gRPC: UpdateConfiguration ────>│                                │
    │   "功率偏移 -0.5 dB"              │── SCPI: 调整功率 ──────────────>│
    │<── 更新结果 ────────────────────│                                │
    │                                   │                                │
    │── gRPC: TurnOffAndRelease ──────>│                                │
    │                                   │── SCPI: 关闭输出 ──────────────>│
    │<── 释放确认 ────────────────────│                                │

1.3 核心需求

  • 实现 gRPC 服务端,监听 DUT 发来的信号请求
  • 解析请求中的信号参数(频率、功率、载波类型、端口等)
  • 通过 GPIB/SCPI 控制信号发生器仪表
  • 返回配置结果(状态、端口偏移、多音信息)给 DUT

2. C# 源码分析

2.1 项目架构总览

C# 版本位于 PCSW/Raptor2.MS.RFStimuli/,是一个完整的 gRPC 服务端微服务。

Raptor2.MS.RFStimuli/Source/
├── Services/                          # gRPC 服务入口
│   ├── ExternalSignalStimuliService.cs      # gRPC service (proto V0)
│   └── ExternalSignalStimuliServiceV1.cs    # gRPC service (proto V1)
│
├── SupportClasses/                    # 核心业务逻辑
│   ├── ExternalSignalStimuliBase.cs         # ★ 基类:请求流程编排
│   ├── ExternalSignalStimuli.cs             # ★ 子类:具体硬件交互
│   ├── CarrierSignalConditions.cs           # 信号状态数据结构
│   ├── RFGeneratorCharacterization.cs       # RF 发生器特性化
│   ├── RFPowerMeasurement.cs                # 功率测量封装
│   ├── FrequencySweep.cs                    # 频谱扫描封装
│   └── ...
│
├── Container/                         # 缓存容器
│   ├── LevelingContainer.cs                 # Leveling 结果缓存
│   └── ArbCalibrationContainer.cs           # Arb 校准结果缓存
│
├── Calculations/
│   └── RFStimuliCalculate.cs                # Hilbert 变换等数学计算
│
└── Interfaces/                        # 依赖接口定义
    ├── IExternalSignalStimuli.cs
    ├── IRFPowerMeasurement.cs
    └── IFrequencySweep.cs

2.2 核心类关系

ExternalSignalStimuliBase (基类 — 流程编排)
    │
    ├── RequestExternalStimuliFlowAsync()    # 请求处理主流程
    ├── TurnOffAndReleaseConfigurationFlowAsync()
    ├── UpdateConfigurationFlowAsync()
    │
    ├── abstract CwResourceGeneratorAsync()          # 获取 CW 信号发生器
    ├── abstract ModulationResourceGeneratorAsync()   # 获取调制信号发生器
    ├── abstract GetWaveformAsync()                   # 加载 IQ 波形
    ├── abstract SignalLevelResponseAsync()            # 计算信号电平响应
    ├── abstract MultiToneLevelResponseAsync()         # 计算多音电平响应
    ├── abstract PerformLevelingAsync()                # 执行 Leveling
    └── abstract ArbCharacterizationAsync()            # 执行 Arb 特性化
         │
         ▼
ExternalSignalStimuli (子类 — 具体实现)
    │
    ├── 依赖注入:
    │   ├── IResourceManagerV2          # 硬件资源管理器
    │   ├── ICompensationManager        # 路径补偿管理器
    │   ├── IRFPowerMeasurement         # 功率计接口
    │   ├── IFrequencySweep             # 频谱仪接口
    │   ├── IWaveformLibrary            # 波形库
    │   ├── ILevelingContainer          # Leveling 缓存
    │   └── IArbCalibrationContainer    # Arb 校准缓存
    │
    └── 实现所有 abstract 方法

2.3 RequestExternalStimuli 主流程

ExternalSignalStimuliBase.RequestExternalStimuliFlowAsync() 是核心入口:

1. 初始化响应对象 (Status = OK)
2. 获取信号发生器硬件能力列表
3. 检查 SessionId 有效性
4. 遍历每个 SignalConfiguration:
   │
   ├── 遍历每个 Signal:
   │   ├── 4a. 获取波形数据 (WaveformLibrary)
   │   ├── 4b. 分配信号发生器资源
   │   │       → CW: CwResourceGeneratorAsync()
   │   │       → 调制: ModulationResourceGeneratorAsync()
   │   ├── 4c. 加载波形到 ARB
   │   ├── 4d. 配置信号发生器 (频率、功率、调制)
   │   ├── 4e. ★ Leveling 校准 (功率计闭环迭代)
   │   ├── 4f. ★ Arb 特性化 (多音补偿)
   │   └── 4g. 计算响应 (端口偏移、多音电平/相位)
   │
   └── 5. 组装响应
       → ActiveConfigurations / UnhandledConfigurations / Errors

2.4 关键技术细节

Leveling(电平校准)

目的: 补偿信号发生器输出精度不足
方法: 闭环迭代调整

wanted_level = stimuli_level + path_compensation

while |measured - wanted| >= 0.1 dB:
    adjustment = (wanted - measured) * adjustment_factor
    rf_level += adjustment
    设置发生器电平 = rf_level
    measured = 功率计读数
    
    # 每 3 次迭代降低调整因子 (×0.75)
    # 超过 10 次迭代或累计调整 > 2 dB 则报错

路径补偿(Path Compensation)

目的: 补偿信号发生器到 DUT 端口之间的线缆/开关损耗

reference_attenuation = 参考路径的衰减值
port_attenuation = 当前端口路径的衰减值
target_offset = reference_attenuation - port_attenuation + signal_deviation

→ 每个端口返回不同的 target_offset
→ DUT 用这个偏移来校正接收电平

多音相位/电平补偿

目的: 补偿信号发生器和路径对不同频率音的非线性响应

对每个音 (tone):
    attenuation_tone = -20 * log10(|S21|)
    tone_level = rf_generator_level - attenuation_tone + ref_attenuation - port_offset
    phase = S21.phase + rf_generator_phase + multitone_phase
    
    # 相位相对于第一个音
    if tone_index == 0:
        relative_phase = -phase
    final_phase = phase + relative_phase

2.5 另一个参考:FaapProxyClient/RFStimuli.cs

PCSW/FaapProxyClient/Source/RFStimuli.cs 是一个测试/调试用的简易 客户端(~170 行),只负责构建 protobuf 请求发给服务端。它和我们的实现角色不同——我们做的是 服务端,对标 ExternalSignalStimuli.cs。但 FaapProxyClient 的代码有助于理解请求消息的构建方式。


3. gRPC 接口定义

3.1 ExternalSignalStimuliService

service ExternalSignalStimuliService {
    rpc RequestExternalStimuli (ConfigurationRequest) returns (ConfigurationResponse);
    rpc UpdateConfiguration (ConfigurationUpdateRequest) returns (ConfigurationUpdateResponse);
    rpc TurnOffAndReleaseConfiguration (ReleaseConfiguration) returns (ReleaseConfigurationAck);
    rpc GetRfInterface (GetRfInterfaceRequest) returns (GetRfInterfaceResponse);
}

3.2 请求消息结构

ConfigurationRequest
├── dut_id: DutIdentifier
│   ├── session_id: SessionId (device_id + start_time)
│   └── dut_pos: uint32
└── configurations[]: SignalConfiguration
    ├── configuration_id: uint32
    ├── polarization: Polarization enum
    ├── rfports[]: uint32
    └── signals[]: Signal
        ├── signal_id: uint32
        └── oneof stimuli:
            ├── carrier: CarrierSignal
            │   ├── carrier_freq_hz: double
            │   ├── requirements: SignalRequirements {stimuli_level_dbm}
            │   └── test_signal: TestSignal {carrier_type, model}
            └── mt: MultiToneSignal
                ├── start_hz: double
                ├── no_tones: uint32
                ├── sep_hz: uint32
                └── requirements: SignalRequirements

3.3 响应消息结构

ConfigurationResponse
├── status: Status {code, message}
├── errors[]: ConfigurationError
├── active_configurations[]: SignalConfigurationResponse
│   ├── configuration_id
│   └── signals[]: SignalResponse
│       ├── signal_id
│       └── oneof:
│           ├── signal: SignalRequestResponse
│           │   └── rfports[]: RfPortTargetOffset {rfport, target_offset_db}
│           └── mt: MultiToneSignalResponse
│               └── tones[]: MultiTone
│                   ├── rfport: RfPortTargetOffset
│                   ├── tones[]: Tone {frequency_hz, tone_level_dbm, phase_deg}
│                   └── par_db: double
└── unhandled_configurations[]: uint32

4. Python 实现

4.1 实现范围

我们实现的是 gRPC 服务端,对应 C# Raptor2.MS.RFStimuli 的角色:

  • 接收 DUT (FAAP) 发来的信号请求
  • 通过 GPIB/SCPI 控制信号发生器仪表
  • 返回配置结果给 DUT

4.2 新增文件

Proto 文件(protos/

文件 内容
radio.proto DutIdentifierRfPortTestSignal
stimuli.proto 信号请求/响应全部消息
service_dut_rx_stimuli.proto ExternalSignalStimuliService 服务定义

生成的 Python 代码(generated/

radio_pb2stimuli_pb2service_dut_rx_stimuli_pb2 及对应的 _grpc.pyi 文件。

核心实现(service_client/rf_stimuli.py

4.3 代码架构

service_client/rf_stimuli.py
│
├── SignalGeneratorController            # 仪器控制层 (GPIB/SCPI)
│   ├── configure_cw()                  # 配置 CW 信号
│   ├── configure_modulated()           # 配置调制信号
│   ├── set_frequency() / set_level()   # 设置频率 / 功率
│   ├── set_output() / set_modulation() # 开关 RF 输出 / IQ 调制
│   └── turn_off()                      # 关闭输出
│
├── RFStimuliServicer                    # gRPC 服务实现
│   ├── RequestExternalStimuli()        # 处理信号请求 → 配置仪器 → 返回结果
│   ├── UpdateConfiguration()           # 处理更新请求 → 调整仪器参数
│   ├── TurnOffAndReleaseConfiguration()# 处理释放请求 → 关闭仪器
│   └── GetRfInterface()               # 返回 RF 接口类型
│
├── ActiveSignal / ActiveConfiguration   # 运行时状态
│
└── create_server() / start_server()     # 服务启动入口

4.4 C# → Python 对照

C# 类 Python 类 角色
ExternalSignalStimuliService.cs RFStimuliServicer gRPC 入口,请求验证和分发
ExternalSignalStimuliBase.cs RFStimuliServicer._process_signal() 请求流程编排
ExternalSignalStimuli.cs SignalGeneratorController 硬件交互
CarrierSignalConditions.cs ActiveSignal / ActiveConfiguration 运行时状态
IRFSignalGeneration (driver) GPIB_Client (pyvisa) 仪器通信
ConcurrentDictionary<> Dict[str, Dict[int, ActiveConfiguration]] 活跃配置缓存

4.5 设计决策

决策点 选择 理由
角色 gRPC 服务端 DUT 主动发请求给我们,我们控制仪器给 DUT 打信号
仪器控制 SignalGeneratorController 封装 SCPI 复用项目已有的 GPIB_Client,可替换不同仪器型号
状态管理 Dict[device_id][config_id] 对应 C# 的 ConcurrentDictionary,跟踪活跃配置
简化范围 暂不实现 Leveling / Arb 特性化 / 路径补偿 第一版先跑通基本流程,后续迭代补充
响应偏移 返回 target_offset_db = 0.0 无补偿数据时的安全默认值

5. 使用示例

5.1 启动服务(无仪器 — dry-run 模式)

from service_client.rf_stimuli import start_server

# 不传 signal_generator,SCPI 命令只打日志不实际发送
start_server(port=50052)

5.2 启动服务(连接真实信号发生器)

from util.gpib_client import GPIB_Client
from service_client.rf_stimuli import SignalGeneratorController, start_server

# 连接信号发生器
gpib = GPIB_Client("TCPIP0::192.168.1.50::hislip0::INSTR")
gpib.connect()

# 创建控制器
sg = SignalGeneratorController(gpib_client=gpib)
print(sg.identify())  # 确认仪器连接

# 启动 gRPC 服务,DUT 连接 50052 端口发送请求
start_server(port=50052, signal_generator=sg)

5.3 DUT 请求处理流程(自动)

服务启动后,DUT 的 FAAP 自动通过 gRPC 发送请求,全流程自动处理:

DUT → RequestExternalStimuli(freq=3500MHz, CW, -30dBm, ports=[1,2,3,4])
    → Python 服务收到请求
    → SCPI: :FREQ 3500000000
    → SCPI: :POW -30.00 dBm
    → SCPI: :OUTP ON
    → 返回 ConfigurationResponse (status=OK, port offsets)

DUT → UpdateConfiguration(config_id=1, power_offset=-0.5dB)
    → SCPI: :POW -30.50 dBm
    → 返回 ConfigurationUpdateResponse

DUT → TurnOffAndReleaseConfiguration(config_id=1)
    → SCPI: :OUTP OFF
    → 返回 ReleaseConfigurationAck (status=OK)

6. 产出文件清单

文件路径 类型 说明
protos/radio.proto 新增 Radio 消息定义
protos/stimuli.proto 新增 Stimuli 消息定义
protos/service_dut_rx_stimuli.proto 新增 gRPC 服务定义
generated/radio_pb2.py + _grpc.py + .pyi 生成 Radio protobuf
generated/stimuli_pb2.py + _grpc.py + .pyi 生成 Stimuli protobuf
generated/service_dut_rx_stimuli_pb2.py + _grpc.py + .pyi 生成 服务 stub + servicer 基类
service_client/rf_stimuli.py 新增(核心) gRPC 服务端 + 仪器控制

7. 后续待办

ExternalSignalStimuli.cs

1. 类定位

ExternalSignalStimuli 继承自 ExternalSignalStimuliBase,实现 IExternalSignalStimuli 接口。
它是 RF Stimuli 微服务的核心业务实现——负责与真实硬件(信号发生器、功率计、频谱仪)交互。

基类 ExternalSignalStimuliBase 负责流程编排(遍历请求、组装响应),本类负责每个步骤的具体硬件操作。

ExternalSignalStimuliBase (流程编排)
    └── ExternalSignalStimuli (硬件交互) ← 本文件

2. 构造函数与依赖

public ExternalSignalStimuli(
    ILoggerFactory loggerFactory,
    IConfiguration configuration,
    IResourceManagerV2 resourceManager,        // 硬件资源分配(信号发生器、开关矩阵)
    ICompensationManager compensationManager,  // 路径补偿数据(线缆/开关衰减)
    IWaveformLibrary waveformLibrary,          // IQ 波形文件库
    IRFPowerMeasurement rFPowerMeasurement,    // 功率计接口
    IFrequencySweep frequencySweep,            // 频谱仪接口
    ILevelingContainer levelingContainer,      // Leveling 结果缓存
    IArbCalibrationContainer arbCalibrationContainer,  // Arb 校准结果缓存
    HashGenerator hashGenerator)               // 波形文件 hash 生成

关键常量

常量 含义
_maximumNumberOfIterations 10 Leveling 最大迭代次数
_maximumLevelAdjustment 2.0 dB Leveling 最大累计调整量
_expirationDate 14 天 Leveling/Arb 缓存有效期
_wantedAccuracy 0.1 dB Leveling 精度目标

构造函数做了什么

  1. 读取配置:TestStationInformation:Id(测试站 ID)和 TestInformation:FactoryCode(工厂代码)
  2. 创建 RFGeneratorCharacterization 实例(用于 Arb 特性化)
  3. 从磁盘加载历史 Leveling 和 Arb 校准缓存数据

3. 三个公开方法(gRPC 入口)

这三个方法是 gRPC service 层调用的入口,它们直接委托给基类的 Flow 方法:

// 请求外部信号
public async Task<...> RequestExternalStimuliAsync(request)
    → return await RequestExternalStimuliFlowAsync(request);  // 基类方法

// 释放配置
public async Task<...> TurnOffAndReleaseConfigurationAsync(request)
    → return await TurnOffAndReleaseConfigurationFlowAsync(request);

// 更新配置
public async Task<...> UpdateConfigurationAsync(request)
    → return await UpdateConfigurationFlowAsync(request);

真正的业务逻辑在基类的 Flow 方法中编排,但 Flow 方法会回调本类的 override 方法来执行具体操作。


4. 方法一:CwResourceGeneratorAsync — 获取 CW 信号发生器

业务目的

为一个 CW(连续波)信号请求分配一台信号发生器硬件。

详细流程

输入: 信号配置(频率、功率、端口)、开关路径、资源能力要求

1. 查询可用的信号发生器列表
   → rfSGCapabilities: 所有信号发生器的能力(是否支持 IQ 调制)
   → 统计: CW 专用发生器数量、调制发生器数量

2. 尝试分配 CW 专用发生器
   │
   ├── 如果 CW 发生器都在用 → 尝试分配调制发生器(降级使用)
   │   "None available CW generator. Try to get a generator with modulation capabilities."
   │
   ├── 如果所有发生器都在用 → 抛出 TimeoutException
   │   (基类会把这个配置标记为 unhandled)
   │
   └── 分配成功 → 得到 resourceData(包含驱动接口 + 开关连接信息)

3. 更新计数器
   → 调制发生器: NumberOfUsedModulationGenerators++
   → CW 发生器: NumberOfUsedCwGenerators++

4. 填充 CarrierSignalConditions(信号状态对象)
   ├── RFSignalGeneration = 信号发生器驱动
   ├── ArbGeneration = null(CW 不需要 ARB)
   ├── ConnectionDetails = 开关连接路径信息
   ├── LevelingSupport = 开关网络是否支持 Leveling
   ├── ConnectionPortsShared = 共享连接能力
   └── ConnectionPort = DUT 端口信息

5. 检查信号电平是否可达
   → differenceFromRequirement = 路径补偿后的电平差
   → 如果 < 0: 记录错误 "Can not meet signal level requirement"

6. 如果不支持 Leveling → 立即连接 RF 开关
   (支持 Leveling 的情况下,开关连接在 Leveling 之后进行)

关键设计:CW 发生器优先策略

优先级:
1. CW 专用发生器(便宜、简单、timeout=1s 快速尝试)
2. 调制发生器降级当 CW 用(timeout=10s)
3. 全部占用 → TimeoutException → unhandled

5. 方法二:ModulationResourceGeneratorAsync — 获取调制信号发生器

业务目的

为调制信号(LTE/NR/多音)分配一台带 IQ 调制 + ARB 的信号发生器。

与 CW 的区别

维度 CW 调制
资源类型 IRFSignalGeneration IRFSignalGeneration + IArbGeneration
ARB 不需要 需要(播放 IQ 波形)
波形元数据 解析 PAR、多音信息、采样率
Arb 特性化 不需要 需要(如果开关网络支持)

详细流程

1. 检查调制发生器是否还有空闲的
   → 全部占用 → TimeoutException

2. 分配资源: GetResourceAsync<IRFSignalGeneration, IArbGeneration>
   → 同时获取信号发生器 + ARB 发生器

3. 填充 CarrierSignalConditions
   ├── RFSignalGeneration = 信号发生器驱动
   ├── ArbGeneration = ARB 驱动
   ├── ArbCharacterizationSupport = 是否支持 Arb 特性化
   ├── PeakToAverageRatio = 波形的 PAR 值
   └── MultiToneSettings = 多音信息(频率、相位、电平、采样数)
       ├── Samples = 波形采样点数
       └── MultiToneInformation[] = 每个音的元数据

4. 如果是多音信号 && 波形元数据标记 DisableLinearization
   → 关闭 Arb 特性化

5. 检查信号电平是否可达(同 CW)

6. 如果不支持 Arb 特性化也不支持 Leveling → 立即连接开关

6. 方法三:GetWaveformAsync — 加载 IQ 波形

业务目的

从波形库下载的 IQ 文件中读取数据,准备上传到 ARB 发生器。

详细流程

1. 验证: 只支持单个 IQ 文件
   → waveFormData.IqWaveform.Count() != 1 → 抛异常

2. 生成波形唯一 key
   → WaveformKey() = hash(IQ文件hash + 元数据文件hash) 的后 8 位
   → waveformName = "{key}.wf"

3. 检查 ARB 发生器是否已存储该波形
   → ArbGeneration.IsWaveformStored(waveformKey)
   │
   ├── 已存储 → 跳过加载,返回空 IQ 数据
   │
   └── 未存储 → 读取 IQ 文件
       → 逐行解析 Tab 分隔的 I/Q 值
       → 转为 Complex 数组
       → 返回 (waveformLoad=true, waveformName, iqData)

IQ 文件格式:
    0.0312\t0.0156
    0.0625\t0.0234
    ...
    每行: I值<Tab>Q值

7. 方法四:SignalLevelResponseAsync — 单载波电平响应

业务目的

计算单载波信号到达每个 DUT 端口时的电平偏移,返回给 DUT 用于校准。

详细流程

前置检查:
  - 如果是多音 && 调制已开启 → 返回空(多音走另一个方法)
  - 如果输出未开启 → 返回空
  - 如果是双极化连接 → 走特殊处理 BothPolarizationConnectionResponse()

核心计算:

1. 获取参考路径的衰减值
   referenceAttenuation = CompensationManager.GetAttenuation(
       参考路径, 当前频率)

2. 对每个连接端口:
   portAttenuation = CompensationManager.GetAttenuation(
       该端口路径, 当前频率)

   target_offset = referenceAttenuation - portAttenuation + signalDeviation
                   ─────────────────   ───────────────   ───────────────
                   参考路径衰减          该端口衰减         Leveling 偏差

3. 组装响应:
   RfPortTargetOffset {
       Port = 端口信息(端口号 + 极化方向),
       TargetOffset = target_offset
   }

偏移的含义

target_offset 告诉 DUT:这个端口收到的信号比参考端口强/弱多少 dB。DUT 用这个值来补偿各端口之间的差异。


8. 方法五:MultiToneLevelResponseAsync — 多音电平/相位响应

业务目的

计算多音信号中每个音到达每个 DUT 端口时的电平和相位,返回给 DUT。

详细流程

前置检查:
  - 无多音频率 → 返回空
  - 输出未开启 → 返回空
  - 调制未开启 → 返回空
  - 双极化连接 → 抛异常(多音不支持双极化)

设置:
  - ArbCharacterization 标记写入响应

核心计算:

1. 获取参考路径衰减(同单载波)

2. 对每个连接端口:
   │
   ├── 获取该端口的衰减值
   │
   ├── 计算端口级偏移 (RfPortTargetOffset)
   │   offset = referenceAttenuation - portAttenuation + signalDeviation
   │
   ├── 获取该端口路径的 S 参数(所有音频率上的 S21)
   │   twoPort = CompensationManager.GetTwoPort(端口路径, 多音频率列表)
   │   → 返回每个频率上的 S21 复数值
   │
   └── 对每个音 (toneIndex = 0 ... N-1):
       │
       ├── 路径损耗:
       │   attenuationTone = -20 × log10(|S21.Magnitude|)
       │
       ├── 电平计算:
       │   rfGeneratorLevel = 发生器上这个音的电平(来自 Arb 特性化)
       │   toneLevel = rfGeneratorLevel
       │             - attenuationTone        ← 减去这个音的路径损耗
       │             + referenceAttenuation    ← 加回参考衰减
       │             - portAttenuation         ← 减去端口衰减
       │
       ├── 相位计算:
       │   phase = S21.Phase                  ← 路径相移
       │         + rfGeneratorPhase           ← 发生器相位(Arb 特性化得到)
       │         + multiTonePhase             ← 波形元数据相位
       │
       ├── 相位归一化(相对于第一个音):
       │   if toneIndex == 0:
       │       relativePhase = -phase
       │   finalPhase = phase + relativePhase
       │
       └── 写入响应:
           RfTone { Frequency, ToneLevel, Phase }

3. 组装 MultiTone 响应:
   MultiTone {
       Par = PAR 值,
       RfPortTargetOffset = 端口偏移,
       RfTones[] = 每个音的信息
   }

9. 方法六:PerformLevelingAsync — 电平校准(核心)

业务目的

通过功率计实测信号电平,迭代调整信号发生器输出,使 DUT 端口上的信号电平精确达到目标值。

为什么需要

信号发生器设置 -30 dBm,实际输出可能是 -30.3 dBm 或 -29.7 dBm。经过线缆衰减后偏差更大。Leveling 通过闭环反馈消除这个误差。

详细流程

前置检查:
  - 不支持 Leveling → 返回 0.0
  - 输出未开启 → 返回 0.0

1. 计算目标电平
   pathCompensation = 路径衰减值
   wantedLevel = stimuliLevel + pathCompensation
   (stimuliLevel 是 DUT 端口上的目标电平,加上路径损耗 = 发生器端需要的电平)

2. 查询缓存
   (levelingUpdate, levelingRFlevel, signalDeviation) =
       LevelingContainer.GetPerformedLeveling(
           仪器序列号, 频率, 目标电平, 波形名)
   │
   ├── 缓存命中(14 天内做过相同条件的 Leveling)
   │   → 直接使用缓存的 RF 电平值
   │   → 设置发生器电平 = levelingRFlevel - pathCompensation
   │   → 跳到步骤 6
   │
   └── 缓存未命中 → 执行实际 Leveling(步骤 3-5)

3. 准备 Leveling
   ├── 关闭同一连接点上的其他信号发生器(避免干扰测量)
   ├── 配置功率计测量参数
   │   → CW: 配置带宽滤波
   │   → 多音: 只配置中心频率
   └── 打开功率计流式测量

4. ★ 迭代校准循环
   ┌─────────────────────────────────────────────────┐
   │ 首次测量: measured = 功率计读数                     │
   │ signalDeviation = measured - wantedLevel           │
   │                                                    │
   │ while |measured - wantedLevel| >= 0.1 dB:          │
   │   │                                                │
   │   ├── iterations++                                 │
   │   │   → 超过 10 次 → 恢复原始电平,抛异常            │
   │   │                                                │
   │   ├── 每 3 次迭代: adjustmentFactor *= 0.75         │
   │   │   (逐步降低调整步长,提高精度)                   │
   │   │                                                │
   │   ├── levelAdjustment = (wanted - measured)         │
   │   │                     × adjustmentFactor          │
   │   │                                                │
   │   ├── totalAdjustment += levelAdjustment            │
   │   │   → 累计 > 2.0 dB → 恢复原始电平,抛异常         │
   │   │                                                │
   │   ├── rfLevel += levelAdjustment                    │
   │   ├── 设置发生器电平 = rfLevel                       │
   │   ├── 等待发生器稳定                                 │
   │   ├── measured = 功率计再次读数                       │
   │   └── signalDeviation = measured - wantedLevel      │
   │                                                    │
   │ 循环结束: 精度达标                                   │
   └─────────────────────────────────────────────────┘

5. 保存缓存
   LevelingContainer.SetLeveling(
       仪器序列号, 频率, 目标电平, 实际RF电平,
       signalDeviation, 波形名, 过期时间=14天后)

   重新打开之前关闭的其他信号发生器

6. 连接 RF 开关到 DUT
   (Leveling 期间开关连到功率计,现在切回 DUT)
   ├── 无 ARB → GetResourceAsync<IRFSignalGeneration>
   ├── CW 或不支持 Arb 特性化 → GetResourceAsync<SG, ARB>
   └── 支持 Arb 特性化 → 开关连接在 ArbCharacterization 中进行

7. 返回 signalDeviation

迭代示例

目标: wantedLevel = -28.0 dBm

迭代 1: measured = -28.5 dBm, adjustment = +0.5 dB → rfLevel = -29.5 dBm
迭代 2: measured = -28.15 dBm, adjustment = +0.15 dB → rfLevel = -29.35 dBm
迭代 3: measured = -28.03 dBm → |差值| = 0.03 < 0.1 → 完成!

signalDeviation = -28.03 - (-28.0) = -0.03 dB

10. 方法七:ArbCharacterizationAsync — ARB 特性化(多音专用)

业务目的

补偿信号发生器 ARB 模块对不同频率音的非线性响应。即使 Leveling 校准了总功率,各个音之间的电平差异仍然存在,需要单独测量和补偿。

前置条件

  • 仅多音信号需要
  • 输出已开启
  • 调制已开启

详细流程

1. 获取路径补偿
   pathCompensation = 路径衰减值

2. 如果不支持 Arb 特性化
   → RFGeneratorLevelDefault(): 使用波形元数据中的默认值
   → 返回

3. 查询缓存
   arbCalibrationData = ArbCalibrationContainer.GetPerformedArbCalibration(
       仪器序列号, 频率, 目标电平, 波形名)
   │
   ├── 缓存命中
   │   → 使用缓存的多音电平数据
   │   → 应用电平修正: levelCorrection = wantedLevel - 缓存的wantedLevel
   │   → 每个音电平 += levelCorrection
   │   → 跳到步骤 7
   │
   └── 缓存未命中 → 执行实际特性化(步骤 4-6)

4. 关闭同一连接点上的其他信号发生器

5. 选择特性化方式
   │
   ├── 方式 A: 频谱仪 (Spectrum Analyzer)
   │   ├── 5a-1. 切换到 CW 模式(关闭 IQ 调制)
   │   ├── 5a-2. 用功率计测量 CW 下各音频率的电平
   │   │         → rfSignalGeneratorLevel[]
   │   ├── 5a-3. 用频谱仪测量 CW 下各音频率的电平
   │   │         → spectrumAnalyzerLevel[]
   │   ├── 5a-4. 切换回多音模式(开启 IQ 调制)
   │   ├── 5a-5. 用频谱仪测量多音下各音的电平
   │   │         → frequencySweepMultiTone[]
   │   └── 5a-6. 计算补偿:
   │             rfLevel[tone] = frequencySweepMultiTone[tone]
   │                           + rfSignalGeneratorLevel[tone]
   │                           - spectrumAnalyzerLevel[tone]
   │
   └── 方式 B: 功率计 (Power Meter)
       ├── 5b-1. ALC 功率搜索: 在中心频率上校准
       ├── 5b-2. 逐音测量: 每个音单独设置频率,用功率计测电平
       │         → measuredToneLevels[]
       └── 5b-3. 计算补偿:
                 rfLevel[tone] = measuredToneLevels[tone]
                               - 10×log10(音数量+1)

6. 后处理
   ├── 验证音间差异
   │   → 相邻音差 > 0.5 dB → 抛异常
   │   → 最大最小差 > 1.5 dB → 抛异常
   │
   ├── Hilbert 变换: 从电平推算相位
   │   phase[] = RFStimuliCalculate.HilbertTransform(rfLevel)
   │
   ├── 处理中心音(奇数个音时,中心音频率 = 发生器频率,无法测量)
   │   → 取相邻两个音的平均值
   │
   └── 保存缓存
       ArbCalibrationContainer.SetArbCalibration(...)

7. 连接 RF 开关到 DUT

两种方式的选择逻辑

PossibleToPerformArbCharacterizationWithSpectrumAnalyzer()
│
├── True → 频谱仪方式(更精确,能直接测每个音的频谱电平)
│
└── False → 功率计方式(频谱仪不可用时的备选)

11. 辅助方法

RFGeneratorLevelDefault

当不支持 Arb 特性化时,使用波形元数据中的默认值填充多音电平:

foreach (var tone in multiToneInformation)
{
    RFGeneratorLevel.Add(new MultiToneInformation() {
        Frequency = centerFrequency + tone.Frequency,
        Phase = 0.0,                                    // 无相位数据
        Level = stimuliLevel + tone.Level + pathCompensation,  // 元数据电平 + 补偿
        Index = tone.Index
    });
}

WaveformKey

生成波形唯一标识,用于判断 ARB 是否已加载该波形:

return hashGenerator.GenerateHash(
    $"{iqWaveform.Hash}{metaWaveform.Hash}"
);
// 取后 8 位作为 key

EnabelUpConversion

空实现(return Task.CompletedTask),为 IUDC 变体预留的扩展点。


12. 完整业务流程总图

DUT 发送 RequestExternalStimuli
    │
    ▼
基类: RequestExternalStimuliFlowAsync()
    │
    ├── 遍历每个 SignalConfiguration
    │   ├── 遍历每个 Signal
    │   │   │
    │   │   ├── [本类] CwResourceGeneratorAsync() 或 ModulationResourceGeneratorAsync()
    │   │   │   → 分配信号发生器硬件
    │   │   │   → 建立开关路径
    │   │   │
    │   │   ├── [本类] GetWaveformAsync()
    │   │   │   → 读取 IQ 波形文件(调制信号才需要)
    │   │   │
    │   │   ├── [基类] SetupSignalGenerator()
    │   │   │   → 设置频率、功率、加载波形、打开输出
    │   │   │
    │   │   ├── [本类] PerformLevelingAsync()  ★
    │   │   │   → 功率计闭环校准
    │   │   │   → 迭代调整发生器电平直到精度 < 0.1 dB
    │   │   │
    │   │   ├── [本类] ArbCharacterizationAsync()  ★ (仅多音)
    │   │   │   → 频谱仪/功率计测量每个音
    │   │   │   → Hilbert 变换算相位
    │   │   │   → 验证音间一致性
    │   │   │
    │   │   ├── [本类] SignalLevelResponseAsync()
    │   │   │   → 计算每个端口的电平偏移
    │   │   │
    │   │   └── [本类] MultiToneLevelResponseAsync()
    │   │       → 计算每个音在每个端口的电平和相位
    │   │
    │   └── 组装 SignalConfigurationResponse
    │
    └── 返回 ConfigurationResponse 给 DUT
            │
            DUT 用这些数据校准 RX 通道
            │
            校准完成后 DUT 发送 TurnOffAndReleaseConfiguration
            │
            ▼
        基类: TurnOffAndReleaseConfigurationFlowAsync()
            → 关闭信号发生器
            → 释放硬件资源
            → 断开开关连接
posted @ 2026-04-17 16:04  mo686  阅读(1)  评论(0)    收藏  举报