相机自动对焦项目-总方案.md 33 KB

相机自动对焦项目 · 总方案(唯一权威文档)

最后更新:2026-06-17(V5.0 结构化重构:整合协议手册/真机实测/调试界面搬迁清单) 用途: 相机自动对焦/自动拍摄校正项目的需求、技术方案、代码结构、协议、当前状态的唯一权威记录配套: 自动对焦流程图.html(最新全流程图解)。


文档导航

章节 看这里如果你要…
第一篇·项目 1 背景 / 2 硬件架构 了解设备、相机、运动机构、为什么做这个项目
第二篇·技术 3 对焦算法 / 4 踩坑教训 改算法、调参数、理解设计约束
第三篇·代码 5 代码结构 / 6 编译运行 / 7 GUI功能 / 8 业务闭环 上手改代码、跑程序
第四篇·协议 9 下位机串口协议手册 对接下位机、加新命令、写EEPROM
第五篇·演进 10 修复与演进史 / 11 真机实测记录 了解改了什么、真机实际数值
第六篇·待办 12 调试界面搬迁清单 / 13 现状与下一步 搬迁老功能、接续工作
附录 A 被删工具 / B 老代码考古 / C 诊断存图源码 备查


第一篇 · 项目

1. 项目背景

1.1 设备与场景

  • 设备: 10舱室时差培养箱(成都艾伟孚,川械注准20232180053)+ 1缓冲瓶(11号)
  • 用途: 相机在显微镜下对胚胎进行延时拍摄
  • 核心问题: 现有"自动选清晰层"算法效果差,7组测试全部选错
    • 根因背景(见9.x/附录B): 旧系统客户端不做选层,只上传图给服务器、向服务器索要最清晰层。 "选错"的根因大概率在服务器算法传给它的几何参数(Z起点/层间距/抠图偏移),不在原C#仓库。 本项目对策:把选层算法搬到本地、纯C#可解释,绕开服务器。

1.2 项目目标

开发独立C#小软件,实现"拍出一张可用图"的全自动校正流程:

曝光校正 → XY皿孔居中 → 空皿检测 → Z对焦(选层/搜索)

2. 硬件架构(真机确认)

2.1 设备组成

  • 10舱室整机 + 1缓冲瓶(11号);每舱独立:CCD相机 + 光源LED + Z轴电机 + 水平旋转电机
  • 缓冲瓶(11号)无相机、不对焦,且脉冲量级与培养舱不同(见11.2,千级 vs 万级)

2.2 相机

  • 型号: Microview MVC2000(型号串硬编码 "MVC2000",USB2.0,约200万像素,Bayer传感器)
  • 分辨率: 2592×1944(必须用这个,曾误用1600×1200导致well被裁出画面)
  • 枚举: 全局0-9台,通过序列号(CCDSN)桥接到舱室
  • 曝光单位: 100µs(CapInfoStruct.Exposure);增益 RGB 三通道各 0-255(Gain[3],非单一全局gain)
  • 数据: 上下颠倒需FlipY
  • 初始化顺序: Init()(MV_Usb2Init "MVC2000") → SetOpMode(0)(0=拍照单帧/1=实时)→ 循环 GrabRgb()

2.3 运动机构(关键)

  • 相机: 只做Z轴垂直运动拍不同焦平面
  • 培养皿: 水平电机旋转,让16个well圆形布局逐个对准相机
  • 只有2个步进电机(不是XYZ三轴):水平电机(皿孔定位) + 垂直电机(Z=对焦轴)。 方案里"XY两轴居中"在源码里只有单个水平轴——居中实际只能调Y偏移(见4.2)
  • 单位: 步进电机脉冲(步),非µm(全代码无µm换算)
  • ★ Z脉冲量级(真机实测,见11.2): 培养舱Z焦准零点 7.4万~8.4万、工作位最高 11万、软上限 125000。 每层步距各舱不同(实测96或128,默认128)。新代码 ZCoarseCenter=90000、区间60000~120000 与真机吻合。
  • Well间距: 约9000脉冲(实测各舱 well1≈7万 → well16≈20.5万)

2.4 下位机通信(详见第9章协议手册)

  • 串口: 每舱独立COM口,9600/8N1,超时基准3000ms(本项目按命令类型动态调)
  • 协议: 5E CMD 00 LTH 数据 累加和校验无独立帧尾,回复长度按命令固定
  • EEPROM: 每个well的"水平位置"+"Z焦准零点"存下位机
  • LED: 只能开关+读亮度,无写亮度命令(注:缓冲瓶程序里确有写亮度0x12到0x0534,见9.7)


第二篇 · 技术方案

3. 对焦算法(★ 按当前代码 06-17 校准)

3.1 清晰度评价(核心算法)

  • 指标: Tenengrad(Sobel梯度幅值平方和),纯C#,无OpenCV(备用 Laplacian 方差)
  • ROI: 只在well圆内/中央区域计算,排除背景反光
  • 归一化: sumSq / 像素数 / mean除以均值一次方
    • ⚠️ 关键修正(06-16,提交 1216bbc): 原为除以 mean²。实测随Z增大画面亮度从~53升到~185(光学效应), mean² 把高Z清晰帧分数压死,导致选最暗最糊的低Z层。改÷mean后峰正确落在92000。这是"选错层"的真根因级修复。
    • 代码:Imaging/Sharpness.cs:105

3.2 标定流程(四步)

①粗对焦(中央40% ROI,固定大窗口 90000±30000)
  → ②以EEPROM位置为中心小范围微调Y居中
  → ③曝光二分(well内ROI)
  → ④精对焦(围绕粗峰 ±6000,0.95r ROI + 3点平滑 + 抛物线插值)

代码:Calib/CalibrationEngine.cs · CalibrateWell()

关键技术点:

  1. 粗对焦: 中央40% ROI避免被培养皿边缘带偏;固定大窗口(不依赖EEPROM零点), ZCoarseCenter=90000 ± ZCoarseHalf=30000,步距2000,约31层
  2. 开扫前停稳2秒: 逐层扫描前先把Z移到起点并 Thread.Sleep(CoarseSettleMs=2000) 停稳再开扫(见3.4)
  3. 居中: 以EEPROM水平位置为中心小范围微调(半幅 HFineRange=2000,9步), 绝不大范围扫描(well间距~9000,大扫会扫到相邻well);只优化Y偏移
  4. 曝光: SetExposure后等 max(200,e/5)ms 并丢第1帧用第2帧(见4.6)
  5. 精对焦: 围绕粗峰 FineZHalf=6000、步距 FineZStep=500(约25层),0.95r ROI保留边缘, 圆未检出降级中央40% ROI(绝不用全图);3点平滑 + 抛物线插值

3.3 居中判据(圆检测)

  • 目标: Y偏移 < ±12%(CenterTolPct)+ well完整
  • 检测: 下采样 + Otsu阈值化 + 连通域 + 圆度过滤(Imaging/WellDetector.cs
  • 阈值(与代码一致): aspect < 1.5 + boxFill > 0.6 + rConsist > 0.65 + rFrac ∈ [0.10, 0.28]
  • 延时: 居中细扫800ms;其余扫描小步用 ScanDelayMs=350

3.4 ⚠️ 已知未解决风险:粗对焦偶发 74000 伪峰(待现场复现)

  • 现象: 偶发某well粗对焦选中 ~74000 伪峰(真实焦面 ~86000-92000),精对焦被锁错误小窗口、输出弱峰(≈1.0)。 同批只坏1个、坏值固定74000、坏的well编号在批次间变化。
  • 已排除: 串口未到位、相机冷启动、清晰度算法(已修÷mean)、"第一个well"时序——多次实测无法复现。
  • 当前缓解: 开扫前停稳2秒(516f02a) + 逐层分数落盘(007d9f5);最可疑"某层电机未真正到位/运动中采样形成伪峰"。
  • ★ 注意(结合11.1真机数据): 真机实测6个培养舱Z焦准零点全在74000~84000!74000恰好落在这个真实焦准区。 这说明74000不一定是"伪峰",可能是EEPROM零点位置附近的真实次峰与90000主焦面竞争。下次复现时务必开DebugSave存图确认。
  • 详见 CalibrationEngine.cs · CoarseFocus() 长注释。

3.5 遗留死参数(别照着调)

CalibrationEngine 保留 ZHalf=1500 / ZLayers=9 / CoarseFocusLayers=7 / HScanRange / HScanSteps / FineScanSteps 等字段,在当前 CalibrateWell 流程中已不被使用。改对焦范围认准正在用的参数: ZCoarseCenter/ZCoarseHalf/ZCoarseStep(粗)、FineZHalf/FineZStep(精)、HFineRange/HFineSteps(居中)、CoarseSettleMs(停稳)。

4. 踩过的坑(长期有效)

# 教训/方案
4.1 硬编码1600×1200,相机原生2592×1944 → well被裁出画面 所有尺寸取自 cam.Width/Height,绝不硬编码
4.2 曾优化X偏移,但只有1个水平旋转轴只能控Y X的~5%固定偏移是硬件局限,只优化Y
4.3 well间斜纹反光被误判成圆 aspect<1.5 + boxFill>0.6 + rConsist>0.65 + rFrac∈[0.10,0.28]
4.4 曝光用全图均值被黑边拉低 → 过曝 只在well圆内ROI算亮度
4.5 焦不对→画面糊→检不出圆→无法居中 先粗对焦让well清晰,再居中
4.6 开环电机,回复≠停稳,移动后立刻抓图=运动拖影帧→伪峰 移动后等延时;大行程开扫前额外停稳2秒+丢残留帧
4.7 高Z层更亮(53→185),÷mean²把清晰亮帧分数压死→选错 归一化只÷mean一次方


第三篇 · 代码与运行

5. 代码结构(当前实际,仅主程序单工程)

C:\claudeFile\TL\AutoFocusTool\    (WPF/.NET 8/x64)
├── Camera/        相机封装(Camera/MVCAPI P/Invoke/CapInfoStruct)
├── Serial/        串口(Protocol协议 / SerialMotor底层 / HouseMotor语义层)
├── Imaging/       图像(Sharpness / WellDetector / ExposureMeter / ImageConverter)
├── Calib/         标定(CalibrationEngine / CalibrationFile / CalibrationManager)
├── Devices/       设备扫描(DeviceScanner / HouseDevice)
├── Logging/       FileLogger静态文件日志器(落盘 Logs/)
├── MainWindow.*.cs  主窗口分部类(xaml/Camera/Motor/Scan/Calib)
├── CalibWindow.*    标定独立窗口(4x4=16格实时显示)
├── calibration.json 标定结果
└── 自动对焦流程图.html  ★全流程图解(含每环节参数+异常分支)

⚠️ 旧文档列的 Survey/Calibrate/SelfTest/SmokeTest/HScan 已全删(提交1e36b67)。 价值评估见附录A。Survey目录从来不存在(备份亦无)。

关键类:

  • CalibrationEngine 标定引擎(四步+RetryMove+CenterRoi40+ClampH/ClampZ限位)
  • CalibrationManager 优先JSON(CircleFound && PeakRatio>1.2)否则降级EEPROM
  • CalibrationFile 档案JSON读写。⚠️ public字段Load 必须 IncludeFields=true(见10.2·P0-7)
  • WellDetector 圆检测;Sharpness 清晰度(÷mean);ExposureMeter 曝光二分
  • HouseMotor 电机+LED语义层(VerticalMoveTo(pulse, delayMs) 自定义延时重载)
  • Protocol 协议帧构造+校验(见第9章);SerialMotor 串口收发

6. 编译与运行

cd "C:\claudeFile\TL\AutoFocusTool"
dotnet build -c Debug
./bin/Debug/net8.0-windows/AutoFocusTool.exe   # 唯一可执行程序
  • 标定参数:calibration.json;终图:calib_result/;日志:Logs/;诊断数据:TestData/
  • ⚠️ 旧文档的 Calibrate.exe/SmokeTest.exe/HScan.exe 已失效。现所有标定/测试通过GUI。

7. GUI功能

  • 设备连接: 扫描设备(实测带相机舱2/4/6/7/8/9,11缓冲瓶无相机)→ 选舱 → 连接
  • 手动控制: 开关LED;抓帧预览(显示曝光/增益/Z/水平/well);调曝光/增益;Z上下移动; 水平转well(读EEPROM位置);Z扫描选层(清晰度曲线);按钮操作埋点落盘
  • 自动标定: 勾选well(全选/全不选)→「一键全自动初始化」→ 弹窗4x4=16格实时显示 → 合格绿框(偏移<12% && 检到圆 && 峰比>1.2) → 存 calibration.json + 终图 calib_result/,存档后 RefreshCache()

8. 业务闭环

  • GUI转well(MainWindow.Calib.cs · BtnGoWell_Click): 接入 CalibrationManager
  • 逻辑: 优先读JSON,合格(CircleFound && PeakRatio>1.2)才用JSON的水平/Z/曝光;否则降级EEPROM
  • 缓存刷新: 标定存档后 RefreshCache(),转well立即用最新结果
  • 日志: 转well打印来源 [标定结果][EEPROM(未标定/不合格)]
  • ⚠️ 当前最大缺口:标定结果只写JSON,从未回写下位机EEPROM(见第12章搬迁清单·P0)

标定文件格式(当前真实数据,house8):

{
  "tlSn": "house8", "date": "2026-06-16 23:32",
  "houses": [{
    "house": 8, "port": "COM5", "ccdIndex": 0, "ccdSn": "23120646",
    "wells": [{ "well": 1, "horizontalPulse": 71200, "exposure": 33, "focusZ": 88434,
      "centerOffsetPct": -2.181, "circleFound": true, "peakSharp": 4.284, "peakRatio": 1.239, "note": "" }]
  }]
}


第四篇 · 下位机串口协议手册(从老代码提取,对接下位机权威依据)

来源:ivf_tl_control_2.0 / ivf_tl_operate_2.0 的 Commander/ComBin/Analysiser/Channel。两版逻辑基本一致,operate 是超集。

9. 协议手册

9.1 帧格式总则

发送帧: [0]帧头0x5E [1]CMD命令码 [2]序号0x00 [3]整帧长LTH [4..N-2]辅助码+参数 [N-1]校验

  • 校验 = 除末字节外所有字节累加和取低8位(CreateORCfor i<len-1: sum+=b[i])。无帧尾。
  • 收帧 按预设固定长度截取(不靠帧尾同步)——长度表必须与固件一致,否则错位。
  • 结果位 = 回复帧倒数第二字节([lenght-2]),0x00=成功,非0=下位机失败。末字节是校验。

9.2 ⚠️ 字节序(混用,最易踩坑)

数据类型 字节序
电机脉冲(发0x05 / 收0x18) 大端(高字节在前)
EEPROM数值(发0x12 / 收0x11) 小端(低字节在前)
温度/压力(收0x06 / 0x20) 大端 int16

新代码 Protocol.MotorAbsolute 大端、ParseEepromInt 小端——已正确区分,务必沿用。

9.3 命令码总表

CMD 含义 回复长度
0x01 握手 6
0x02 自检 6
0x04 设目标温度 7
0x05 电机控制 6
0x06 读温度/压力 9
0x08 读传感器AD 9
0x09 设IO(LED/气阀/补气/换气) 6
0x10 获取IO(含舱门状态) 7
0x11 读EEPROM 10
0x12 写EEPROM control=6 / operate=12 ⚠️需真机确认
0x16 自动换气(仅operate) 6
0x18 读电机位置 10
0x19 缓冲瓶补气 6
0x20 读缓冲瓶数据(压力+双温度) 12

9.4 电机控制(CMD=0x05,11字节帧)

布局:5E 05 00 0B [辅助码] [脉冲4字节大端] 00 [校验] 辅助码 = 高半字节(轴:水平1/垂直2) | 低半字节(动作:正转0/反转1/脱机2/复位3/绝对4)

命令 辅助码
水平正转/反转 0x10 / 0x11 5E 05 00 0B 1x [4字节大端] 00 校验
水平绝对运动 0x14 同上
水平复位(硬编码,回退3000) 0x13 5E 05 00 0B 13 00 00 00 0B B8 44
垂直正转/反转 0x20 / 0x21 5E 05 00 0B 2x [4字节大端] 00 校验
垂直绝对运动 0x24 同上
垂直复位(硬编码,回退2000) 0x23 5E 05 00 0B 23 00 00 00 07 D0 68

脉冲是4字节int32大端(注释虽写"16位"但实现是32位,支持十几万,新旧一致,见11章)。

9.5 读电机位置(CMD=0x18,回复10字节)

  • 读水平:5E 18 00 06 01 00+校验;读垂直:5E 18 00 06 02 00+校验
  • 解析:位置在回复 [4..7]4字节大端 int32

9.6 IO/LED/气阀(CMD=0x09,回复6字节)

布局:5E 09 00 07 [子设备] [开01/关00] [校验] | 动作 | 子设备 | 帧 | |------|--------|-----| | 开/关 LED | 0x00 | 开5E 09 00 07 00 01 6F5E 09 00 07 00 00 6E硬编码不走校验函数) | | 进气阀 开/关 | 0x01 | 5E 09 00 07 01 01/00 校验 | | 排气阀 开/关 | 0x02 | 5E 09 00 07 02 01/00 校验 | | 换气标志 前/后 | 0x03 | 5E 09 00 07 03 01/00 校验 | | 舱室补气 | 0x04 | 5E 09 00 07 04 01 校验 | | 舱室排气(operate) | 0x05 | 5E 09 00 07 05 01 校验 |

阀类操作发完应 Thread.Sleep(valueDelay) 等气路稳定。

9.7 读EEPROM(CMD=0x11,回复10字节)

布局:5E 11 00 09 [地址高] [地址中] [地址低] 04 00 [校验](倒数第二字节0x04=读取字节数) 解析:值在回复 [4..7]4字节小端 int32

EEPROM地址全表(高 中 低): | 项 | 地址 | |----|------| | 仪器编号 TLNum | 00 00 08 | | CCDSN | 00 00 10 | | 下加热板目标温度(读出÷100) | 00 02 38 | | 舱室排气阀时间 | 00 03 08 | | 舱室进气阀时间 | 00 03 0C | | 垂直焦准零点(用,⚠️与读不一致) | 00 03 1C | | 缓冲瓶进气阀时间 | 00 05 0C | | 灯光亮度 | 00 05 34 | | Z扫描间隔脉冲 | 00 04 48 | | 16个well水平位置 | well1=00 04 00,well2~15=00 04 50起步进0x04,well16=00 04 04(特例) | | 16个well Z焦准零点(读用) | well1=00 04 08,步进0x04,well16=00 04 44(连续) |

⚠️ 读写地址错配: Z焦准零点 00 04 08(按well表), 00 03 1C(单地址)。 新项目逐well写零点应用well表 00 04 [08..44],但必须真机确认下位机真实写地址

9.8 写EEPROM(CMD=0x12,12字节帧)

布局:5E 12 00 0C [地址高] [地址中] [地址低] [数值4字节小端] [校验]

数值小端(与电机脉冲大端相反!)。0x12 回复长度两版本不同(6/12),新项目须真机实测

9.9 传感器/温压/舱门/缓冲瓶

命令 回复 解析
读温度(0x06) 5E 06 00 06 [通道] 00,通道0下盖板/1上盖板/2玻璃片下 9字节 [4..5]大端int16 ÷100
读压力(0x06) 5E 06 00 06 03 00 9字节 [4..5]大端int16 不除
读舱门(0x10) 5E 10 00 06 02 00 7字节 [4]==0x01为开
读缓冲瓶(0x20) 5E 20 00 05 00 12字节 压力[4..5]/温度1[6..7]÷100/温度2[8..9]÷100
缓冲瓶补气(0x19) 5E 19 00 05 00 6字节
握手(0x01) 5E 01 00 05 00 6字节 houseSn=[2]

缓冲瓶是 houseSn=11 的独立串口。



第五篇 · 演进史与真机记录

10. 修复与演进史

10.1 第一轮 P0 修复(2026-06-13)

曝光丢帧(7倍偏差→0)、居中阈值放宽+细扫800ms(62.5%→100%)、业务闭环接CalibrationManager、 异常处理、相机旧帧缓冲、粗对焦改中央40% ROI。

10.2 第二轮 P0 修复(2026-06-13,针对同事审查报告)

编号 问题 修复
P0-1 标定"只存不用" BtnGoWell接CalibrationManager,存档后RefreshCache
P0-2/3 手动路径运动模糊/旧帧 Z扫描每层丢第1帧;设曝光后等max(200,e/5)ms并丢帧
P0-4 精焦圆未检出用全图 降级中央40% ROI,绝不用全图
P0-6 电机移动无重试 RetryMove 重试3次
P0-5 误导性死代码 GrabWithRollingExposure/FullMean
P0-7 JSON反序列化静默失效 public字段→System.Text.Json默认不反序列化→Load空对象。加 IncludeFields=true否则标定永远读不到、闭环形同虚设

10.3 ★ 对焦算法重写与扫描范围扩大(06-14~06-17)

提交 改动 意义
ac9867c 扫描范围参数 + 电机限位钳位 防越界
144f3db Z固定大窗口粗对焦 + 扩精焦半幅 不依赖EEPROM零点
8e6508b 水平定位改小范围微调,废弃全行程扫描 防扫到相邻well
1216bbc 清晰度归一化 ÷mean²→÷mean + 峰落边界报警 修高Z清晰帧被压低
adea570 串口按命令类型动态读超时 修大行程误判超时
f92a71d/6446adb GetSourceBuffer加锁+空保护 修段错误/NPE
007d9f5 粗对焦逐层分数落盘 排查74000伪峰
516f02a 粗对焦开扫前停稳2秒 修运动帧伪峰
a354b79~18ef633 FileLogger + 按钮埋点 操作可追溯
1e36b67 清理测试子工程 见附录A

10.4 审查报告未采纳条目

仿真模式/单元测试/状态机重构等架构升级,经用户确认本轮不做("改动太大,非必要")。

11. 真机实测记录(2026-06-17 · ReadEepromProbe 只读探针)

工具:C:\claudeFile\TL\测试代码\ReadEepromProbe(严格只读,只发握手/读EEPROM/读位置,不驱动任何电机)。

11.1 ★ Z脉冲量级:一锤定音(澄清"10000 vs 100000")

结论:培养舱Z脉冲是"万级(7~11万)",新代码参数完全正确,不存在10倍单位错误。

舱(houseSn) COM Z焦准零点范围 当前Z位置 每层步距 水平位置范围
8 COM5 74000~74600 88434 128 70700~205650
7 COM18 79700~80500 60000 128 70700~205700
9 COM19 79300~80200 78456 128 70200~205300
6 COM11 75760~76400 95740 96 70800~205800
4 COM9 75372~76012 110000 96 71500~206650
2 COM4 83272~84128 0 96 71000~205850
11(缓冲瓶) COM3 7025~7325 48 7464~22088

三点定论:

  1. 培养舱(2/4/6/7/8/9): Z焦准零点 7.4万~8.4万,工作位最高11万,软上限125000。 新代码 ZCoarseCenter=90000、区间60000~120000、ZMaxPulse=125000 与真机吻合,无需改
  2. "1万"印象的来源 = 缓冲瓶舱(11号): 其脉冲仅千级(Z焦准7000、水平7千~2.2万),比培养舱小~10倍。 很可能是看了缓冲瓶数据误以为整机Z是1万级。缓冲瓶无相机不对焦,不影响对焦标定。
  3. 每层步距真机各舱不同(96或128),不是固定128。 文档/老代码的128只是默认值。

11.2 与74000伪峰的关联(重要线索)

真机6个培养舱Z焦准零点全部落在74000~84000,而74000伪峰恰在此区间下沿。 → 74000可能不是纯"运动拖影伪峰",而是EEPROM零点附近的真实次清晰峰与90000主焦面竞争。 下次复现74000问题时,应对比该well的EEPROM零点值,并开DebugSave存粗对焦每层图(附录C)。

11.3 旧选层"7组选错"的新佐证

真机显示同一舱16个well的Z焦准零点几乎相同(如house8全是74000~74600),但新代码精对焦实测最清晰层在88434(差~14000脉冲≈110层)。 → 印证:EEPROM零点严重偏离真实焦面,老系统从零点出发只扫40层×128≈5120脉冲的小窗口,焦面落在窗口外必然选错。新代码大窗口(±30000)正是对症。



第六篇 · 待办与现状

12. 调试界面搬迁清单(老程序有、新程序缺,要搬过来)

老调试界面:ivf_tl_operate_2.0 · HouseDebugPageViewModel / BufferDebugViewModel。 底层协议新程序已具备(第9章),多数缺失功能只需"加帧构造 + 加封装方法 + 上层循环"。

12.1 缺失功能总览(按优先级)

优先级 缺失功能 协议 备注
P0 写EEPROM框架(新程序完全没有0x12写命令) 0x12 是下面所有"写"的基础
P0 标定结果回写下位机(well水平位置+Z焦准零点) 0x12 现只写JSON,换文件即丢
P0 读温度/压力/舱门 0x06/0x10 调试台基本监控
P1 进/排气阀、舱室补气/排气、缓冲瓶补气与状态 0x09/0x19/0x20 固定帧,注意valueDelay
P1 写灯光亮度/扫描间隔/阀门时间 0x12 复用写框架换地址
P2 逐well水平抓拍(ShuiPingZhuaPai) 已有底层 上层循环1-16
P2 多轮重复对焦(AutoFocusPic的xun轮次) 已有底层 对焦稳定性复检
P2 电机正转/反转/脱机、一键归位(MototReady) 已有底层(0x05) 组合命令

12.2 写EEPROM框架(P0,所有"写"的基础)

新程序 Protocol.cs 加(数值小端,与电机脉冲大端相反):

public static byte[] WriteEeprom(byte addrHi, byte addrMid, byte addrLo, int value)
{
    byte[] p = BitConverter.GetBytes(value); // 小端: p[0]最低
    return WithChecksum(new byte[] { ST, 0x12, 0x00, 0x0C, addrHi, addrMid, addrLo,
                                     p[0], p[1], p[2], p[3], 0x00 });
}

注意事项: ① 数值小端(电机大端,别搞反);② 0x12回复长度老代码自相矛盾(6/12),先真机实测确认; ③ 写后回读校验;④ well地址表照抄第9.7(well16=0x04、well1=0x00非线性)。

12.3 标定结果回写(P0,解决"只存JSON不落硬件")

MainWindow.Calib.cs:205 存JSON后,对每个合格well:

motor.WriteWellHorizontalPos(well, wc.HorizontalPulse); // 地址 00 04 [well表]
motor.WriteWellFocusZero(well, wc.FocusZ);              // 地址 00 04 [08..44],⚠️写地址需真机确认

注意: Z焦准零点读写地址错配(9.7),写之前务必真机验证"写哪个地址 → 读回一致"。

12.4 读温度/压力/舱门(P0)

功能 解析
下盖板温度 5E 06 00 06 00 00+校验 [4..5]大端int16 ÷100
上盖板温度 5E 06 00 06 01 00+校验 同上
玻璃片下温度 5E 06 00 06 02 00+校验 同上
压力 5E 06 00 06 03 00+校验 [4..5]大端int16 不除
舱门 5E 10 00 06 02 00+校验 [4]==0x01为开
下加热板目标温度 5E 11 00 09 00 02 38 04 00+校验 [4..7]小端int32 ÷100

12.5 逐well水平抓拍 ShuiPingZhuaPai(P2)

老代码 HouseDebugPageViewModel.ShuiPingZhuaPai:615:先水平复位 → for well=1..16:读该well水平位置→HorizontalAbsolute转过去→抓帧存盘。 注意: 每步用 motorDelay 延时;循环内检查中止标志;必须先打开实时图像否则抓不到。底层新程序全有。

12.6 多轮重复对焦 AutoFocusPic(P2)

老代码 AutoFocusPic(focalCount, xun):672:for k=0..xun(轮):每轮先垂直复位 → for i=0..focalCount:目标=起点+i×间隔脉冲→VerticalAbsolute→抓帧。 注意: 间隔脉冲默认128(真机实测96/128各异,应读EEPROM);超 verticalMotorPulseMax 立即终止;用于对焦稳定性复检。

12.7 老代码可借鉴的健壮性(搬功能时一并吸收,详见附录B)

  • 相机native调用挂 [HandleProcessCorruptedStateExceptions][SecurityCritical] + static锁(防dll崩溃)
  • 抓帧失败=重建相机而非重抓(最多3次)
  • 串口命令队列+单发送线程+双信号量,异步包成同步 XxxWait()
  • finally强制关LED+电机复位+释放忙标志;循环内查急停/门开

13. 现状与下一步

13.1 当前状态(2026-06-17)

  • 代码: 两轮P0清零 + 对焦算法重写(÷mean、固定大窗口、小范围居中、停稳2秒)。分支 feature/autofocus-scan-range
  • 真机: ReadEepromProbe已确认Z脉冲量级(万级,参数正确);6个培养舱EEPROM参数已读取。
  • 开放问题:
    1. ⚠️ 粗对焦偶发74000伪峰未根除(见3.4;真机线索见11.2)
    2. ⚠️ 标定结果未回写下位机EEPROM(见12.3)
    3. ⏳ 真胚胎验证待做(峰比预期>1.5)
    4. 调试界面功能搬迁(第12章清单)

13.2 真胚胎验证步骤

舱室约定: 有胚胎→9号舱;空皿/功能测试→1号舱(现场无1号则用2号等空舱)。

  1. GUI「一键全自动初始化」勾选有胚胎well重标
  2. 看峰比是否>1.5(空皿~1.1)
  3. 人工核对最清晰层Z(对比 calib_result/ 终图)
  4. 验证闭环:转该well日志应显示 [标定结果] 且曝光自动设标定值
  5. 若现74000伪峰:开DebugSave存粗对焦每层图(附录C)+ 对比该well EEPROM零点

13.3 新会话衔接

开场:"继续相机自动对焦项目,读总方案文档第13章现状,代码在 C:\claudeFile\TL\AutoFocusTool"
进度:两轮P0清零+算法重写,真机已确认Z万级参数正确(第11章)。
  开放:74000伪峰、标定回写EEPROM、真胚胎验证、调试界面搬迁(第12章)。
改算法:Calib/CalibrationEngine.cs(认准3.2参数,别动3.5死参数)
改协议:Serial/Protocol.cs(第9章手册)
测试代码:C:\claudeFile\TL\测试代码\(只读探针等,不混入主工程)


附录

附录A:被删测试工具的价值评估

提交1e36b67删除了所有子工程。备份在 C:\Users\AIVFO\Desktop\AutoFocusTool-master\autofocustool。 | 工具 | 价值 | 建议 | |------|------|------| | SelfTest/Diag.cs | :纯C#裸写BMP+亮度统计,抓74000伪峰要靠它存图 | 建议恢复(源码见附录C) | | SmokeTest/SmokeTest.cs | 中:无电机验证扫描/相机/曝光/JSON | 需快速回归时恢复 | | Calibrate/Calibrate.cs | 中:命令行单well标定 | 需命令行调试时恢复,注意对齐当前参数 | | ToPng/CalibTest/topng.csx | 低 | 不必恢复 |

附录B:老代码考古(可借鉴逻辑 + 必坑点)

B.1 脉冲单位核实(与第11章真机互证)

新旧脉冲单位完全一致,无10倍差异。铁证:垂直复位命令帧两边逐字节相同(5E 05 00 0B 23 00 00 00 07 D0 68)。 老代码 verticalMotorPulseMax=125000、绝对运动用32位脉冲,与新代码、与真机(万级)全部吻合。 "1万"印象源于缓冲瓶舱(11号,千级,见11.1)。

B.2 可借鉴的成熟逻辑

  1. 相机native崩溃防护(最该借鉴): 所有进 mvcapi.dll 的方法加 [HandleProcessCorruptedStateExceptions][SecurityCritical] + static锁。dll抛AccessViolation时默认catch不住、进程挂。新代码近期 f92a71d/6446adb 修段错误属同域。
  2. 抓帧失败=重建相机GetRgbDataFun 关相机+重开,最多3次),不重试单帧。
  3. 串口模型: 命令队列+单发送线程+双AutoResetEvent;超时30s/重发3次/失败ReopenPort(丢缓冲+Close+Dispose+重建+重订阅);扫描类快速失败。
  4. 流程finally强制收尾: 关LED+电机复位+释放忙标志;循环内查急停/门开。
  5. 开机一次性读全EEPROM序列SerialBin.GetHouseInfo):是"读设备全部标定值"现成模板。

B.3 老代码自己的坑(要避开)

  1. SourceBuffer 每次new数组 → 高频抓图GC压力;应复用缓冲。
  2. 校验和失败只记日志不重发 → 标定应丢帧重发。
  3. 电机位置回读不一致只告警不纠偏 → 标定精度敏感,应重等/重发/废弃该层。
  4. 环形缓冲Clear()全清、一问一答容不得粘包 → 若下位机主动上报需改按长度清。
  5. LED开后无稳定延时 → 标定应显式补LED亮起延时。
  6. 异常路径未在finally关LED → 务必补上。
  7. App.config明文密码、autoFocus键名词不符义、返回码语义不统一 → 别学。

附录C:诊断存图工具源码(Diag.cs,备查·不单独建文件)

来自备份 SelfTest/Diag.cs。纯C#不依赖WPF,把相机24bpp BGR裸写BMP(含FlipY翻正)+统计亮度。 用途: 排查3.4的74000伪峰时挂到 CalibrationEngine.DebugSave 逐层存图。需要时贴进项目临时用、用完即删。

挂载:

> engine.DebugSave = (buf, name) =>
>     Diag.SaveBmp(buf, cam.Width, cam.Height, $@"C:\claudeFile\TL\AutoFocusTool\TestData\{name}.bmp");
> ```
> 并在 `CoarseFocus` 逐层处加 `DebugSave?.Invoke(b, $"coarse_w{well}_z{z}")`。

csharp using System; using System.IO;

namespace AutoFocusTool {

/// <summary>自检用轻量图像诊断(不依赖 WPF)。统计像素亮度;裸写 24bpp BMP 存盘。</summary>
internal static class Diag
{
    /// <summary>返回 (最小, 最大, 平均) 灰度。</summary>
    public static (int min, int max, double mean) Stats(byte[] bgr24)
    {
        int min = 255, max = 0; long sum = 0; int n = 0;
        for (int i = 0; i + 2 < bgr24.Length; i += 27)
        {
            int g = (bgr24[i] * 29 + bgr24[i + 1] * 150 + bgr24[i + 2] * 77) >> 8;
            if (g < min) min = g;
            if (g > max) max = g;
            sum += g; n++;
        }
        return (min, max, n > 0 ? (double)sum / n : 0);
    }

    /// <summary>裸写 24bpp BMP(含 FlipY 翻正)。</summary>
    public static void SaveBmp(byte[] bgr24, int w, int h, string path)
    {
        int rowSize = ((w * 3 + 3) / 4) * 4;
        int imgSize = rowSize * h;
        int fileSize = 54 + imgSize;
        using var fs = new FileStream(path, FileMode.Create);
        using var bw = new BinaryWriter(fs);
        bw.Write((byte)'B'); bw.Write((byte)'M');
        bw.Write(fileSize); bw.Write(0); bw.Write(54);
        bw.Write(40); bw.Write(w); bw.Write(h); // h>0 自底向上=翻正相机倒像
        bw.Write((short)1); bw.Write((short)24);
        bw.Write(0); bw.Write(imgSize);
        bw.Write(2835); bw.Write(2835); bw.Write(0); bw.Write(0);
        byte[] pad = new byte[rowSize - w * 3];
        for (int y = 0; y < h; y++)
        {
            int row = y * w * 3;
            bw.Write(bgr24, row, w * 3);
            if (pad.Length > 0) bw.Write(pad);
        }
    }
}

} ```


文档版本: V5.0(2026-06-17 结构化重构:六篇+附录;整合协议手册/真机实测/调试界面搬迁清单) 状态: 唯一权威。配套 自动对焦流程图.html维护: 重大变更更新第10章演进史 + 第13章现状。