在这个项目中,await Task.Delay(500, ct); 出现在指南第96行,用于替代 Thread.Sleep(500)。下面详细解释:
Task 是什么?
Task 是 .NET 中表示"一个正在进行或将来完成的异步操作"的类,位于 System.Threading.Tasks 命名空间。你可以把它理解为:
- 传统的
Thread(线程)比较底层,创建成本高,难以管理 Task是对异步操作的高级抽象,它不一定对应一个专用线程,而是由线程池调度- Task 可以有返回值
Task<TResult>,也可以没有返回值(就是Task),类似"一个将来会完成的承诺"
在本项目中:
- 每个槽位的 Worker 运行在一个独立 Task 上(指南第34行)
Task.Run()在线程池中启动一个新任务(指南第108行)Task.WhenAll()等待多个任务全部完成(指南第117行)
Delay 是什么意思?
Task.Delay(毫秒数, 取消令牌) 是一个异步等待方法,它的作用是:
"等待指定的毫秒数,但不阻塞当前线程,并且可以随时被取消。"
这是它和 Thread.Sleep() 的核心区别:
| 对比项 | Thread.Sleep(500) |
await Task.Delay(500, ct) |
|---|---|---|
| 是否阻塞线程 | ✅ 阻塞,线程干等着什么也不干 | ❌ 不阻塞,等待期间线程可以去干别的活 |
| 能否取消 | ❌ 不能,只能等时间到 | ✅ 可以通过 CancellationToken 随时取消 |
| 使用场景 | 同步代码中简单暂停 | async/await 异步代码中等待 |
| 对UI的影响 | 会卡UI界面 | 不会卡UI |
在本项目中的实际意义
await Task.Delay(500, ct) 通常用于轮询等待 场景。比如在 WaitForModuleInserted() 中,典型写法是:
csharp
while (!ct.IsCancellationRequested)
{
// 检查模块是否插入
if (IsModuleInserted()) break;
await Task.Delay(500, ct); // 等500毫秒再检查,期间不占着线程
// 如果用户按了取消(ct),立即退出
}
- 500 = 每500毫秒检查一次(轮询间隔)
- ct = CancellationToken,当外部请求取消时(比如用户按Ctrl+C),
Task.Delay会立即抛出OperationCanceledException,不会傻等500毫秒
简单总结:Task 是异步任务,Delay 是异步等待,组合起来就是"非阻塞地等一会儿,而且随时能叫停"。
CheckTestTypeInfo() 函数逐行讲解
这个函数的作用是:通过 I2C 总线读取光模块 Flash 中存储的"模块身份信息",解析出模块的速率等级、芯片方案型号、光传输距离类型、软件版本、芯片工作状态,并存入 TestResult 供后续测试流程使用。
一、函数整体流程图
选择Flash表6 → 从0xFC地址读4个字节 → 解析字节0(速率+方案号)
→ 解析字节1(光类型+版本号) → 解析字节2(芯片状态) → 返回成功
这4个字节是模块厂商预先烧录在Flash里的"身份证",每一位都有特定含义。
二、逐段讲解
1. 准备工作 --- 选择表、读取数据
csharp
byte[] readbuffer = new byte[4];
String str, strRate;
SelectTable(6); // 切换到Flash的第6页(QSFP模块有多页寄存器/表,表6存的是厂商自定义信息)
uint readCount = i2c.TWI_ReadPage(0xa0, 0xFC, readbuffer, 4);
// ↑ I2C设备地址 ↑起始地址 ↑存数据的数组 ↑读4字节
SelectTable(6) 很关键!QSFP模块的I2C地址空间是分页的,就像一本书翻到第6页才能看到这页的内容。注释说"定时器结束时需选择表06",说明其他操作可能切到别的页了,这里必须切回表6才能读到正确数据。
i2c.TWI_ReadPage(0xa0, 0xFC, readbuffer, 4) 通过I2C总线读取:
0xA0:I2C从设备地址(QSFP模块的A0页地址)0xFC:起始寄存器地址readbuffer:读出的数据存到这个数组4:读4个字节
csharp
if (readCount != 4)
{
return false; // 没读到4个字节说明通信失败(模块没插好/没上电/I2C总线异常)
}
2. 解析第1个字节(readbuffer0)--- 速率 + 设计方案号
readbuffer[0] 的二进制位分布:
┌─b7─┬─b6─┬─b5─┬─b4─┬─b3─┬─b2─┬─b1─┬─b0─┐
│ 速率等级 (高4位) │ 设计方案编号 (低4位) │
└─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
(1) 取低4位 --- 设计方案编号:
csharp
str = string.Format("设计方案{0} ", (readbuffer[0] & 0x0F).ToString("D"));
0x0F= 二进制00001111&是按位与运算,和00001111做与操作,相当于只保留低4位,高4位清零.ToString("D")以十进制输出,比如值为3就输出"3"- 结果举例:
"设计方案3 "
(2) 取高4位 --- 速率等级:
csharp
strRate = "000";
switch (readbuffer[0] & 0xF0) // 0xF0 = 11110000,只保留高4位
{
case 0x10: // 高4位 = 0001 → 40G
str += "40G"; strRate = "40G"; break;
case 0x20: // 高4位 = 0010 → 100G
str += "100G"; strRate = "100G"; break;
case 0x30: // 高4位 = 0011 → 双速率
str += "100G/112G 双速率 "; strRate = "100G"; break;
default:
str += " "; break; // 其他值 = 未知速率
}
TestResult.bitRate = str; // 把速率信息存入测试结果
💡 为什么用
& 0xF0而不是>> 4? 因为这里直接用高4位的原始值(0x10/0x20/0x30)做switch的case,不需要右移。如果右移4位,0x10→0x01、0x20→0x02,也能做,但写法不同。
3. 根据速率+方案号,确定芯片型号
csharp
if (strRate == "40G")
{
switch (readbuffer[0] & 0x0F) // 取低4位的方案编号
{
case 0x01: str += "MAX24040"; break; // 单芯片方案
case 0x02: str += "4xGN1157"; break; // 4通道GN1157芯片
case 0x03: str += "PHXT8104+PHXR8104"; break; // 发射+接收芯片组合
case 0x05: str += "37045+37044"; break;
case 0x06: str += "24025+37046"; break;
case 0x07: str += "24025+2110S"; break;
case 0x08: str += "037057+37046"; break;
default: str += "Reserved"; break; // 保留值/未定义
}
}
else if (strRate == "100G")
{
switch (readbuffer[0] & 0x0F)
{
case 0x01: str += "37049+37046+011039+002304"; break; // 4颗芯片组合
case 0x02: str += "24028+37046"; break;
case 0x03: str += "37049+37046+1185"; break;
case 0x04: str += "37059+37244"; break;
case 0x05: str += "37045+37044"; break;
case 0x06: str += "24025+37046"; break;
case 0x07: str += "24025+2110S"; break;
case 0x08: str += "037057+37046"; break;
default: str += "Reserved"; break;
}
}
else
{
str += "未定义"; // 既不是40G也不是100G
}
这些编号(37049、MAX24040、GN1157等)是光通信芯片的型号,不同芯片的调试参数、寄存器地址都不同,后续测试流程会根据这里识别出的方案选择不同的调试算法。比如"37049+37046"就是用Semtech的GN37049(TIA)+ GN37046(Driver)芯片组合。
4. 解析第2个字节(readbuffer1)--- 光模块类型 + 软件版本
readbuffer[1] 的二进制位分布:
┌─b7─┬─b6─┬─b5─┬─b4─┬─b3─┬─b2─┬─b1─┬─b0─┐
│ 光类型(高3位) │ 保留(b4) │ 软件版本号(低4位) │
└─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
(1) 取高3位 --- 光传输距离类型:
csharp
switch (readbuffer[1] & 0xE0) // 0xE0 = 11100000,只保留最高3位
{
case 0x20: str += "SR4"; break; // 001 → SR4(短距,多模光纤,100m)
case 0x40: str += "CW4"; break; // 010 → CWDM4(粗波分,2km)
case 0x60: str += "LR4"; break; // 011 → LR4(长距单模,10km)
case 0x80: str += "ER4"; break; // 100 → ER4(超长距,40km)
case 0xA0: str += "ZR4"; break; // 101 → ZR4(80km)
case 0xC0: str += "PAM4"; break; // 110 → PAM4调制(更高速率)
default: str += "未知"; break;
}
这些是光模块行业标准名称:
- SR4 = Short Range 4通道,多模光纤短距离
- LR4 = Long Range 4通道,单模光纤10km
- ER4 = Extended Range,40km
- ZR4 = 80km超长距
- CW4 = CWDM4,粗波分复用
- PAM4 = 4级脉冲幅度调制,用于更高速率(200G/400G)
(2) 取低4位 --- 软件版本号:
csharp
str += string.Format(" 软件版本:V{0} ", (readbuffer[1] & 0x0F).ToString("D"));
// 例:低4位=2 → "软件版本:V2"
TestResult.chipType = str; // 把完整的芯片方案信息(含类型+版本)存入结果
5. 解析第3个字节(readbuffer2)--- 芯片工作状态
csharp
if ((readbuffer[2] & 0x0F) == 0x0F)
{
TestResult.chipIsOK = false; // 低4位全1(=15) → 芯片异常
}
else
{
TestResult.chipIsOK = true; // 其他值 → 芯片正常
}
这是一个"故障标志":低4位全为1(二进制 1111 = 0x0F = 15)表示芯片自检报错,可能是上电初始化失败、芯片损坏等。正常工作时不会出现全1的情况。
6. 第4个字节(readbuffer3)
注意:代码中读了4个字节但只用了前3个(readbuffer0、1、2),第4个字节 readbuffer3 在这个函数里没有使用,可能是保留字段或其他用途(比如生产日期/批次)。
三、输出结果示例
假设读到的4个字节是:0x26, 0x61, 0x00, 0x00
readbuffer[0] = 0x26= 二进制0010 0110- 高4位
0010(即0x20)→ 100G - 低4位
0110(即0x06)→ 方案6 → 24025+37046
- 高4位
readbuffer[1] = 0x61= 二进制0110 0001- 高3位
011(即0x60)→ LR4 - 低4位
0001(即0x01)→ 软件版本V1
- 高3位
readbuffer[2] = 0x00→ 低4位=0,不是0x0F → 芯片正常
最终拼接的字符串:
"设计方案6 100G 24025+37046 LR4 软件版本:V1 "
存入:
TestResult.bitRate="设计方案6 100G"TestResult.chipType="设计方案6 100G 24025+37046 LR4 软件版本:V1 "TestResult.chipIsOK=true
四、关键C#语法补充(新员工需掌握)
| 语法 | 含义 | 举例 |
|---|---|---|
& |
按位与(AND),两个位都是1结果才是1 | 0x26 & 0x0F = 0x06 |
0x0F |
十六进制数,= 十进制15 = 二进制00001111 | 用于掩码(mask) |
0xF0 |
= 240 = 11110000 | 取高4位的掩码 |
0xE0 |
= 224 = 11100000 | 取高3位的掩码 |
switch/case |
多分支选择 | 根据不同值走不同分支 |
+= |
字符串追加拼接 | str += "40G" 等价于 str = str + "40G" |
💡 掩码(Mask)技巧 :这是嵌入式/硬件编程中最常用的技巧。想取一个字节的哪几位,就用那几位为1、其余为0的数做
&运算。比如想取低4位,掩码就是00001111(0x0F)。
RunFirstTest() 函数逐段详细讲解
这是整个初测流程的核心编排方法,负责按顺序执行光模块的接收端调试和发射端调试。你可以把它理解为一条"测试流水线",按步骤走完整个初测。
整体流程图
初测开始
│
├─ 步骤1: 进入调试模式(写密码解锁芯片寄存器)
│
├─ 步骤2: TEC温控使能(如果需要控温的模块)
│
├─ 步骤3: 接收端(Rx) 4通道循环测试
│ ├─ 切换光开关到模块
│ ├─ APD高压调试(突变检测法)
│ ├─ DDM校准(多衰减点多项式拟合)
│ ├─ LOS告警阈值调试(二分逼近法)
│ └─ 保存接收端校准数据
│
├─ 步骤4: 发射端(Tx) 4通道循环测试
│ ├─ 使能发射通道
│ ├─ VON负压调试
│ ├─ 光功率调试(APC/MOD寄存器)
│ ├─ 消光比ER调试
│ └─ 保存发射端校准数据到Flash
│
├─ 步骤5: 读取Flash信息(SN/PN序列号)
│
└─ 初测完成(进度100%)
函数签名
csharp
private async Task RunFirstTest(TestData data, CancellationToken ct)
private:只有这个类(ModuleTestWorker)内部能调用async Task:异步方法,内部有 await 调用(等待硬件操作),不阻塞调用线程TestData data:测试数据对象,用来传递/存储测试结果和错误信息CancellationToken ct:取消令牌,用户按Ctrl+C或停止测试时,能随时中断流程
第一段:初始化
csharp
_errorMessage = "";
ReportProgress(5);
ReportLog("========== 初测流程开始 ==========");
_errorMessage = "":清空上次测试的错误信息(这个字段在调试失败时会被填充)ReportProgress(5):上报进度条到5%,UI(控制台)会显示进度ReportLog(...):打印日志,方便车间测试人员看到当前在做什么
💡 进度条设计 :整个函数用
ReportProgress(百分比)来更新进度:
- 进入调试模式 → 10%
- 接收端4通道 → 10%→40%(每通道+15%:10→25→40,但4通道只有3个间隔,最后到40%)
- 发射端4通道 → 45%→90%(每通道+10%)
- 读Flash → 95%
- 完成 → 100%
第二段:步骤1 --- 进入调试模式
csharp
ReportLog("[初测-步骤1] 进入调试模式(写4字节密码到0x7B)...");
if (!_module.SetDebugPWD())
{
ReportLog("[初测-步骤1] 失败: 调试密码写入失败!(可能是模块不在调试状态或I2C通信异常)");
data.ErrorMessage = "进入调试模式失败";
return; // ★ 提前退出,后面的步骤都不执行了
}
ReportLog("[初测-步骤1] 调试模式进入成功");
ReportProgress(10);
什么是"调试模式"?
光模块芯片在正常工作状态下,关键控制寄存器(偏置电流、调制电流、APD高压等)是锁定的 ,防止误操作。要调试这些参数,必须先写一个特定的4字节密码(A9 46 50 54,即"AFPT"的ASCII码)到寄存器地址 0x7B,芯片才会"解锁"进入调试模式。
_module.SetDebugPWD():通过I2C向模块写入调试密码- 如果返回
false,说明I2C通信失败(模块没插好、没上电、芯片不响应),直接return终止测试 data.ErrorMessage设置错误信息,这个信息最终会显示在测试结果里- 成功后进度推进到10%
⚠️ 关键模式:你会发现整个函数反复使用这种模式:
csharpif (!某个操作()) { data.ErrorMessage = "具体错误"; return; // 失败就立刻终止 }这叫"快速失败"(Fail Fast),任何一步失败都不继续后面的步骤,避免在异常状态下做无意义的操作。
第三段:步骤2 --- TEC温控(可选)
csharp
if (GlobalConfig.TxTecTest)
{
ReportLog("[初测-TEC] 使能TEC温控,等待3秒稳定...");
_module.SetTx_EN(); // 使能发射端供电(TEC需要电才能工作)
await Task.Delay(3000, ct); // 异步等待3秒让温度稳定
ReportLog("[初测-TEC] TEC已稳定");
}
else
{
ReportLog("[初测-TEC] 跳过(TEC测试未启用)");
}
什么是TEC?
TEC = Thermoelectric Cooler(半导体制冷器),是某些光模块(特别是EML激光器类型的长距模块LR4/ER4/ZR4)内部的温度控制元件。激光器对温度极其敏感,波长会随温度漂移,必须用TEC把芯片温度稳定在一个精确值(比如45°C±0.1°C)。
GlobalConfig.TxTecTest:全局配置开关,在 Program.cs 中由测试人员选择是否启用TEC测试SetTx_EN():打开发射端的使能信号(给TEC和激光器供电)await Task.Delay(3000, ct):等待3000毫秒(3秒),让TEC把温度稳定下来。用await+ct是异步等待,不阻塞线程,而且可以被取消- 不是所有模块都带TEC(比如SR4短距模块用DFB激光器就不需要),所以这个步骤是可选的
第四段:步骤3 --- 接收端测试(4通道循环)
csharp
if (GlobalConfig.RxDdmTest)
{
ReportLog($"[初测-Rx] 开始接收端4通道测试 (APD调试={GlobalConfig.ApdEnabled}, LOS调试={GlobalConfig.RxLosTest}, RX类型={GlobalConfig.RxIsApd})");
for (int ch = 0; ch < 4; ch++) // ch=0,1,2,3 对应4个通道
{
QSFP光模块有4个通道(4路光信号并行传输),每个通道独立调试。
csharp
ct.ThrowIfCancellationRequested();
取消检查点 :这一行非常重要!它的作用是:如果用户在这4通道循环过程中按了"取消"(Ctrl+C),这里会立即抛出 OperationCanceledException,终止整个测试。如果不加这个检查,即使取消了也要等当前通道跑完才能退出,体验不好。
💡 最佳实践 :在长时间循环的每次迭代开始时调用
ct.ThrowIfCancellationRequested(),保证取消响应及时。
csharp
TestSet.ch = ch; // 记录当前正在测试的通道号(全局/静态变量,其他地方会用到)
ReportLog($"[初测-Rx] ===== 通道{ch} 开始 =====");
4a. 切换光开关
csharp
_hw.SwitchToModule(); // 控制光开关,把测试仪器的光纤切换到当前模块
await Task.Delay(200, ct); // 等待200ms让光开关机械动作完成
什么是光开关? 测试系统有多台仪器(光功率计、误码仪、衰减器等),通过一个光开关(光学的继电器)来切换哪路光信号通到哪个仪器。切换后需要短暂等待机械结构稳定。
4b. APD高压调试
csharp
if (GlobalConfig.ApdEnabled)
{
ReportLog($"[初测-Rx-CH{ch}] APD调试开始(突变检测法)...");
_module.TxTempLookupTableCtrl(false); // 关闭温度查找表(调试时要固定条件)
什么是APD?
APD = Avalanche Photodiode(雪崩光电二极管),是光模块接收端的核心器件,把光信号转换成电信号。APD需要一个反向高压(通常20~40V),电压高低直接影响接收灵敏度。电压太低信号弱,太高会产生雪崩噪声。每个芯片最佳电压不同,需要逐个调试。
csharp
if (!RxApdDebug()) // 执行APD高压搜索算法
{
ReportLog($"[初测-Rx-CH{ch}] APD调试失败!");
data.ErrorMessage = $"CH{ch} APD调试失败";
return;
}
ReportLog($"[初测-Rx-CH{ch}] APD调试完成, APD值={TestResult.rxapdVal}");
_module.SaveRxDataAfterDebug(); // 保存APD调试结果
}
"突变检测法":逐步增加APD高压,当电流出现突变(雪崩倍增因子快速增大)时,那个点就是最佳APD电压。这是RxApdDebug()方法内部的算法。
TxTempLookupTableCtrl(false) --- 关闭发射端的温度补偿查找表。为什么调Rx时要关Tx的温度补偿?因为温度补偿会动态调整参数,影响调试条件的稳定性。调试时要固定所有可变条件,得到可重复的结果。
4c. DDM校准
csharp
ReportLog($"[初测-Rx-CH{ch}] DDM校准开始(多衰减点多项式拟合)...");
await RxDdmCalibration(ct); // 注意这里是await,因为内部有异步操作(控制衰减器+读光功率)
ReportLog($"[初测-Rx-CH{ch}] DDM校准完成");
什么是DDM?
DDM = Digital Diagnostics Monitoring(数字诊断监控),光模块会实时上报接收光功率值。但芯片内部的ADC读数和实际光功率之间有偏差,需要校准。
校准方法 :用可调光衰减器改变输入光功率(比如 -3dBm、-7dBm、-11dBm...),记录模块内部ADC的读数,然后用多项式拟合(通常是线性或二次曲线)算出校准系数,写入模块寄存器。之后模块上报的功率值就准确了。
这个方法前面有
await,说明内部有异步操作(控制衰减器需要通过TCP/GPIB通信,等待光功率计读数等)。
4d. LOS告警调试
csharp
if (GlobalConfig.RxLosTest)
{
ReportLog($"[初测-Rx-CH{ch}] LOS调试开始(二分逼近法)...");
if (!RxLosDebug())
{
ReportLog($"[初测-Rx-CH{ch}] LOS调试失败!");
data.ErrorMessage = $"CH{ch} LOS调试失败";
return;
}
ReportLog($"[初测-Rx-CH{ch}] LOS调试完成");
}
什么是LOS?
LOS = Loss of Signal(信号丢失告警)。当接收光功率太低(信号太弱)时,模块应该触发LOS告警,告诉设备端"我收不到信号了"。当光功率恢复时,告警应该解除。行业标准规定了LOS断言(Assert)和解除(Deassert)的阈值范围。
"二分逼近法":通过二分搜索快速找到LOS触发和解除的精确光功率阈值,比线性搜索快很多。
4e. 保存 & 进度更新
csharp
_module.SaveRxDataAfterDebug(); // 把该通道所有接收端校准数据保存到模块Flash
ReportLog($"[初测-Rx-CH{ch}] ===== 通道{ch} 完成 =====");
ReportProgress(10 + ch * 15); // ch=0→10%, ch=1→25%, ch=2→40%, ch=3→55%...
}
ReportProgress(40); // 接收端全部完成,进度到40%
}
注意:4个通道的进度计算是 10 + ch*15,ch=0→10、ch=1→25、ch=2→40、ch=3→55,但循环结束后直接 ReportProgress(40) 把进度拉到40%。ch=3时55%会被后续的40覆盖(这个可能是代码写得不太精确的地方,但不影响功能)。
第五段:步骤4 --- 发射端测试(4通道循环)
csharp
if (GlobalConfig.TxTest)
{
ReportLog($"[初测-Tx] 开始发射端4通道调试 (VON调试={GlobalConfig.VonEnabled}, 功率方法={GlobalConfig.TxPwrDebugMethod}, ER方法={GlobalConfig.TxErDebugMethod})");
for (int ch = 0; ch < 4; ch++)
{
ct.ThrowIfCancellationRequested();
TestSet.ch = ch;
ReportLog($"[初测-Tx] ===== 通道{ch} 开始 =====");
5a. 使能发射通道
csharp
_hw.SwitchToModule(); // 光开关切到模块
_hw.SetTxDisable(false); // 取消Tx Disable(允许发射端发光)
_module.SoftTxCHEn(ch); // 通过I2C软件使能当前通道的发射
await Task.Delay(200, ct); // 等待200ms让激光器启动稳定
ReportLog($"[初测-Tx-CH{ch}] 发射通道使能完成");
_module.TxTempLookupTableCtrl(false); // 关闭温度补偿表(调试时固定条件)
SetTxDisable(false):硬件层面取消TxDisable引脚的低电平禁止。QSFP模块有一个TxDisable硬件引脚,拉低时激光器关断。SoftTxCHEn(ch):软件层面(通过I2C寄存器)单独使能某一个通道的发射。因为是逐通道调试,每次只开一个通道。- 等待200ms让激光器出光稳定。
5b. 波长调试(占位,暂未实现)
csharp
if (GlobalConfig.TosaTempEnabled)
{
ReportLog($"[初测-Tx-CH{ch}] 波长调试(TOSA温度)...");
// 这里目前只有日志,没有实际代码
}
这是TEC温度调试的预留功能。对于WDM(波分复用)模块,每个通道的波长必须精确(比如LAN-WDM的4个波长分别是1295.56/1300.05/1304.58/1309.14nm),通过调节TEC温度来微调波长。目前代码中这个功能还没实现,只是个框架。
5c. VON负压调试
csharp
if (GlobalConfig.VonEnabled)
{
ReportLog($"[初测-Tx-CH{ch}] VON负压调试开始...");
if (!VonDebug())
{
ReportLog($"[初测-Tx-CH{ch}] VON调试失败!");
data.ErrorMessage = $"CH{ch} VON调试失败";
return;
}
ReportLog($"[初测-Tx-CH{ch}] VON调试完成");
}
什么是VON?
VON是EML激光器(Electro-absorption Modulated Laser,电吸收调制激光器)的偏置负压。EML芯片需要一个负压来控制电吸收调制器的工作点,这个电压直接影响消光比和光功率。VON不对会导致ER不够或波形失真。
注意:只有EML类型的激光器(LR4/ER4等长距模块)需要VON调试,DFB激光器(SR4短距)不需要。所以有
GlobalConfig.VonEnabled开关控制。
5d. 光功率调试
csharp
ReportLog($"[初测-Tx-CH{ch}] 光功率调试开始(方法={GlobalConfig.TxPwrDebugMethod})...");
if (!TxPowerDebug())
{
ReportLog($"[初测-Tx-CH{ch}] 光功率调试失败: {_errorMessage}");
data.ErrorMessage = $"CH{ch} 光功率调试失败: {_errorMessage}";
return;
}
ReportLog($"[初测-Tx-CH{ch}] 光功率调试完成 (APC={TestResult.txapcVal}, MOD={TestResult.txmodVal})");
光功率调试 :调节激光器的APC (Automatic Power Control,自动功率控制)寄存器和MOD(Modulation Current,调制电流)寄存器,使发射光功率达到目标值(比如 0~3dBm,根据模块类型不同)。
- APC:控制激光器的偏置电流(Bias Current),决定平均光功率
- MOD:控制调制电流幅度,决定"1"电平和"0"电平之间的差距(和消光比相关)
TxPwrDebugMethod可能有不同的调试算法(比如固定MOD调APC、或者APC和MOD交替调)
调试方法是:设置APC/MOD值 → 用光功率计测光功率 → 根据偏差调整寄存器 → 反复迭代直到功率达标。
5e. 消光比(ER)调试
csharp
ReportLog($"[初测-Tx-CH{ch}] 消光比调试开始...");
if (!TxErDebug())
{
ReportLog($"[初测-Tx-CH{ch}] 消光比调试失败: {_errorMessage}");
data.ErrorMessage = $"CH{ch} 消光比调试失败: {_errorMessage}";
return;
}
ReportLog($"[初测-Tx-CH{ch}] 消光比调试完成 (ER={TestResult.txErbuf[ch]:F1}dB, Pwr={TestResult.txPower:F1}dBm)");
什么是消光比(ER)?
ER = Extinction Ratio,是光信号"1"电平功率和"0"电平功率的比值,用dB表示(比如 ER = 10log(P1/P0))。ER太低(比如<4dB)会导致接收端无法区分0和1,误码率升高。行业标准通常要求ER≥4dB或≥6dB。
消光比主要通过调节MOD(调制电流)来控制。但MOD和光功率是互相影响的(调MOD会影响平均功率,调APC也会影响ER),所以功率调试和ER调试通常需要交替迭代:调功率→调ER→再调功率→再调ER,直到两者都达标。
调试结果记录了 txErbuf[ch](该通道的ER值,保留1位小数)和 txPower(最终光功率,dBm为单位)。
5f. 写入校准数据 & 保存
csharp
_module.WriteTxCalData(); // 把校准系数写入模块的Flash/寄存器
_module.SaveTxDataAfterDebug(); // 保存调试结果到TestResult
ReportLog($"[初测-Tx-CH{ch}] 校准数据已保存到Flash");
ReportLog($"[初测-Tx] ===== 通道{ch} 完成 =====");
ReportProgress(45 + ch * 10); // ch=0→45%, ch=1→55%, ch=2→65%, ch=3→75%
}
ReportProgress(90); // 发射端全部完成,进度到90%
}
WriteTxCalData() 会把调试好的APC、MOD、VON等值正式写入模块Flash(断电不丢失),这样模块在客户现场使用时就能直接加载这些校准参数。
第六段:步骤5 --- 读取Flash信息
csharp
ReportLog("[初测-步骤5] 读取Flash调试信息...");
_module.GetFlashInfoDebug();
ReportLog($"[初测-步骤5] Flash信息读取完成 SN={TestResult.sn} PN={TestResult.pn}");
ReportProgress(95);
初测完成后,从模块Flash中读取:
- SN(Serial Number):序列号,每个模块唯一,用于追溯
- PN(Part Number):料号,标识模块型号
这些信息会被记录到测试结果中,后续上传到MES系统(制造执行系统)进行生产追溯。
第七段:完成
csharp
ReportLog("========== 初测流程全部完成!==========");
ReportProgress(100);
打印完成日志,进度推到100%。函数执行完毕,回到调用者(RunTestFlow),后续会等待模块拔出再进行下一模块测试。
总结:测试涉及的关键概念速查表
| 术语 | 全称 | 含义 |
|---|---|---|
| Rx | Receive | 接收端,把光信号转成电信号 |
| Tx | Transmit | 发射端,把电信号转成光信号 |
| APD | Avalanche Photodiode | 雪崩光电二极管,需要调试最佳高压 |
| DDM | Digital Diagnostics Monitoring | 数字诊断监控,需要校准光功率读数精度 |
| LOS | Loss of Signal | 信号丢失告警,需要调试触发阈值 |
| APC | Automatic Power Control | 自动功率控制,控制激光器偏置电流 |
| MOD | Modulation Current | 调制电流,控制1/0电平差 |
| ER | Extinction Ratio | 消光比,P1/P0的比值(dB) |
| VON | Voltage Negative (for EAM) | EML电吸收调制器负压偏置 |
| TEC | Thermoelectric Cooler | 半导体制冷器,控制激光器温度 |
| TOSA | Transmitter Optical Sub-Assembly | 发射光组件(含激光器) |
| ch | Channel | 通道(0~3共4通道) |
| ct | CancellationToken | 取消令牌,支持随时中止测试 |
整个调试流程的本质就是:逐个通道,调节光模块芯片内部的模拟参数(电压/电流寄存器),让各项光学指标(功率、灵敏度、消光比、告警阈值)达到行业标准规定的范围内,然后把最优参数写入Flash永久保存。