第四章:测试软件框架
做ToF模组量产,硬件只是骨架,软件才是灵魂。我见过太多团队,硬件设计得漂漂亮亮,一到测试软件就翻车。说白了,测试软件框架决定了你的产线能跑多快、多稳。
这一章,我重点聊聊上位机软件架构、固件烧录与校准流程,还有自动化测试脚本怎么设计。嗯,都是我在产线上摸爬滚打总结出来的经验。
4.1 上位机软件架构:C# vs Python
先说说上位机。我个人习惯把上位机分成三层:通信层、业务逻辑层、UI层。别觉得复杂,这层架构能帮你省掉后期80%的维护成本。
核心原则:通信层只管收发数据,业务逻辑层只管处理数据,UI层只管展示数据。各干各的,互不干扰。
4.1.1 C#方案(推荐产线使用)
如果你问我产线用什么,我首选C#。为什么?因为C#的Windows Forms或WPF做UI太成熟了,产线工人操作起来直观。我在项目中遇到过,用Python写的界面,工人点错一个按钮就卡死,换成C#后稳定多了。
C#架构示例:
// 通信层 - 串口通信类
public class SerialPortManager
{
private SerialPort _port;
public bool Connect(string portName, int baudRate = 115200)
{
// 我习惯用115200,产线稳定
_port = new SerialPort(portName, baudRate);
_port.DataReceived += OnDataReceived;
return _port.IsOpen;
}
private void OnDataReceived(object sender, SerialDataReceivedEventArgs e)
{
// 异步处理,别阻塞UI
byte[] buffer = new byte[_port.BytesToRead];
_port.Read(buffer, 0, buffer.Length);
// 触发业务层事件
OnRawDataReceived?.Invoke(buffer);
}
}
// 业务逻辑层 - 测试流程控制
public class TestController
{
public TestResult RunCalibration(SerialPortManager com)
{
// 发送校准指令
com.Send("CAL_START");
// 等待响应
var response = WaitForResponse(5000); // 5秒超时
return ParseResult(response);
}
}
我的小技巧:产线环境复杂,串口经常被占用。我习惯在连接前先枚举所有可用串口,让工人自己选,而不是写死COM口。
4.1.2 Python方案(适合研发验证)
Python呢?我一般在研发阶段用。快速验证算法、调试参数,Python的灵活性无可替代。但产线我不推荐,GIL锁和打包问题太头疼。
Python架构示例:
# 通信层 - 使用pyserial
import serial
import threading
class ToFComm:
def __init__(self, port, baudrate=115200):
self.ser = serial.Serial(port, baudrate, timeout=1)
self._lock = threading.Lock() # 线程安全
def send_command(self, cmd):
with self._lock:
self.ser.write(cmd.encode())
return self.ser.readline()
def read_depth_data(self):
"""读取深度数据,返回16位数组"""
raw = self.ser.read(1024)
# 解析协议头
if raw[0:2] != b'\xAA\x55':
return None
# 提取有效数据
return [int.from_bytes(raw[i:i+2], 'little')
for i in range(4, len(raw), 2)]
# 业务逻辑层
class CalibrationFlow:
def __init__(self, comm):
self.comm = comm
def run_offset_cal(self):
"""偏移校准 - 我曾经踩过坑,忘了清空缓存"""
self.comm.ser.reset_input_buffer() # 清空缓存,很重要!
resp = self.comm.send_command("CAL_OFFSET")
return resp == "OK"
注意:Python做产线测试时,记得用pyinstaller打包成exe。但打包后串口权限可能丢失,我建议用管理员权限运行。
4.2 固件烧录与校准流程
固件烧录,说白了就是把编译好的二进制文件写进ToF模组的Flash里。校准呢?就是让模组知道「什么是0,什么是1」。
我画了一张流程图,帮你理清整个流程:
4.2.1 固件烧录细节
烧录方式我常用两种:
| 烧录方式 | 适用场景 | 速度 | 可靠性 |
|---|---|---|---|
| SWD/JTAG | 研发调试、小批量 | 快(~1MB/s) | 极高 |
| UART Bootloader | 产线量产 | 中(~115200bps) | 高 |
| USB DFU | 支持USB的模组 | 快(~12Mbps) | 高 |
避坑指南:我曾经在产线上遇到批量烧录失败,查了三天发现是烧录器供电不稳。后来我强制要求:烧录时模组必须独立供电,不能靠烧录器供电。
4.2.2 校准流程详解
校准分两步:偏移校准和增益校准。你想想看,ToF模组出厂时每个像素的响应都不一样,不校准的话测出来的距离全是歪的。
偏移校准:让模组对着标准白板(距离固定,比如1米),记录每个像素的原始值,算出偏移量。
增益校准:换个距离(比如2米),再测一次,算出增益系数。
代码示例(Python):
def calibrate_tof_module(comm, distance_mm):
"""
校准ToF模组
:param comm: 通信对象
:param distance_mm: 标准距离(毫米)
"""
# 步骤1:设置模组为校准模式
comm.send_command("ENTER_CAL_MODE")
time.sleep(0.1)
# 步骤2:采集100帧数据取平均
frames = []
for i in range(100):
frame = comm.read_depth_data()
if frame:
frames.append(frame)
avg_frame = np.mean(frames, axis=0)
# 步骤3:计算偏移量
# 理想值 = 实际距离 / 像素分辨率
ideal_value = distance_mm / 0.1 # 假设分辨率0.1mm
offset = ideal_value - avg_frame
# 步骤4:写入校准参数
comm.send_command(f"SET_OFFSET:{','.join(map(str, offset))}")
# 步骤5:验证
verify = comm.send_command("GET_OFFSET")
if verify == "OK":
print("校准成功")
else:
print("校准失败,请检查")
4.3 自动化测试脚本设计
产线测试,核心就一个字:快。你想想看,一条产线一天要测几千个模组,每个模组多花1秒,一天就多花1小时。所以自动化脚本的设计原则是:能并行就别串行,能异步就别同步。
4.3.1 测试脚本架构
我习惯把测试脚本分成三层:
- 测试用例层:每个用例测一个功能点(比如测距精度、帧率、温度漂移)
- 测试流程层:组合多个用例,形成完整的测试序列
- 报告生成层:把测试结果汇总成报表,支持MES系统对接
代码示例(C#自动化测试框架):
// 测试用例基类
public abstract class TestCase
{
public string Name { get; set; }
public bool IsPass { get; protected set; }
public string ErrorMessage { get; protected set; }
public abstract void Execute(ToFModule module);
}
// 具体测试用例:测距精度测试
public class DistanceAccuracyTest : TestCase
{
private int _targetDistance; // 标准距离
public DistanceAccuracyTest(int targetDistance)
{
Name = $"测距精度测试@{targetDistance}mm";
_targetDistance = targetDistance;
}
public override void Execute(ToFModule module)
{
// 设置模组到目标距离
module.SetTargetDistance(_targetDistance);
// 采集10次取平均
double sum = 0;
for (int i = 0; i < 10; i++)
{
sum += module.ReadDistance();
}
double avg = sum / 10;
// 判断误差是否在±1%以内
double error = Math.Abs(avg - _targetDistance) / _targetDistance;
IsPass = error <= 0.01;
if (!IsPass)
ErrorMessage = $"误差{error:P2},超过1%";
}
}
// 测试流程管理
public class TestRunner
{
private List<TestCase> _cases = new List<TestCase>();
public void AddCase(TestCase testCase)
{
_cases.Add(testCase);
}
public TestReport RunAll(ToFModule module)
{
var report = new TestReport();
foreach (var testCase in _cases)
{
try
{
testCase.Execute(module);
report.AddResult(testCase.Name, testCase.IsPass);
}
catch (Exception ex)
{
report.AddResult(testCase.Name, false, ex.Message);
}
}
return report;
}
}
我的经验:产线测试脚本一定要加「看门狗」机制。如果某个测试用例卡住超过30秒,自动重启模组并重试。我曾经遇到过模组死机导致整条产线停摆,加了看门狗后再也没出过问题。
4.3.2 测试报告输出
测试报告我建议输出两种格式:
- CSV格式:给MES系统导入用,每行一个模组,每列一个测试项
- JSON格式:给研发分析用,包含详细的原始数据
CSV输出示例:
模组ID,测距精度@1m,测距精度@2m,帧率,温度漂移,总结果
TOF-001, PASS, PASS, PASS, FAIL, FAIL
TOF-002, PASS, PASS, PASS, PASS, PASS
TOF-003, PASS, FAIL, PASS, PASS, FAIL
嗯,说到这,测试软件框架的核心内容基本都覆盖了。上位机选C#还是Python,看你场景;固件烧录和校准,流程要标准化;自动化脚本,追求稳定和速度。这些都是在产线上真金白银换来的经验,希望能帮你少走弯路。
公众号:蓝海资料掘金营,微信deep3321