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 |
DutIdentifier、RfPort、TestSignal |
stimuli.proto |
信号请求/响应全部消息 |
service_dut_rx_stimuli.proto |
ExternalSignalStimuliService 服务定义 |
生成的 Python 代码(generated/)
radio_pb2、stimuli_pb2、service_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 精度目标 |
构造函数做了什么
- 读取配置:
TestStationInformation:Id(测试站 ID)和TestInformation:FactoryCode(工厂代码) - 创建
RFGeneratorCharacterization实例(用于 Arb 特性化) - 从磁盘加载历史 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()
→ 关闭信号发生器
→ 释放硬件资源
→ 断开开关连接

浙公网安备 33010602011771号