2026-08-09 学习笔记:背钻检测核心概念

2026-08-09 学习笔记:背钻检测核心概念


一、背钻在干什么

一块多层 PCB 板打了一个通孔(via),电镀后铜柱从板顶贯穿到板底,像一根吸管插穿蛋糕。

但高速信号只需要连到第 3 层。第 3 层以下那截铜柱没用,反而变成天线,反射信号。

解决办法:拿一把大一号的钻头,从板底反着往上钻,把多余铜掏掉。

复制代码
  板顶 ━━━━━━━━━━━━━━━
       │  ██ 信号层 ██ │  ← 信号走这里
       │  ██ 信号层 ██ │
  ─────┤──████████────├── 信号层
       │  ████████    │  ← 残桩(留着,安全距离)
  ─────┤  ████████    ├── 钻头停在这
       │              │
       │   多余铜柱    │  ← 这段被掏掉了
       │              │
  板底 ━━━━━━━━━━━━━━━  ← 钻头从这扎进去

铅笔比喻

复制代码
  ✏️ 笔尖 ━━━ 板顶
     ████    ← 保留的笔芯(信号层,有用)
     ████
  ───████─── ← 钻头停的位置
     ████    ← 残桩(留一点,安全)
  ───████─── ← 钻尖最深点
            ← 下半截笔芯被削掉了
     ════    ← 笔尾(板底)
术语 什么意思 比喻
信号层 信号走的那层铜 铅笔写字的那段
残桩 stub 信号层到钻头的安全距离 写字段和削掉段之间留的一点芯
钻深 钻进去多深 削掉多长
钻头最深点 钻头停在哪 削到哪停了

二、CT 扫描 Z 范围:只扫关键区域

CT 不需要扫整板,只扫钻头停的位置上下那一段。检测目的:确认钻深对不对、残桩长不长。

复制代码
板顶 ━━━━━━━━━━━━━━━━━━
     │    ████████    │  ← 保留铜(不在扫描范围里)
     │    ████████    │
─────┤────████████────├── ← topPos(扫描上边界)
     │ ①  ████████ ① │  ← 残桩,铜还在
─────┤    ████████    ├── ← 钻头最深点
     │ ②            ② │
     │ ② 被掏空     ② │  ← 铜被钻掉了
     │ ②            ② │
板底 ━━━━━━━━━━━━━━━━━━
     │ ③            ③ │  ← 冗余,板外空气
─────┴───────────────┴── ← bottomPos(扫描下边界)
区间 位置 有铜? 为什么
topPos → 钻头最深处 残桩,故意留的安全距离
钻头最深处 → 板底 没有 被钻头掏掉了,背钻的目的
板底 → bottomPos 板外空气 多扫一点冗余,防漏
CT 看到的情况 说明
钻太浅 残桩太长,残留铜变天线,信号烂
钻太深 伤到信号层的铜,断路

比喻:手机拍杯子 --- 不拍整个房间(整板),只拍杯子周围(Z 范围),稍微拉远一点(冗余量)。

语法 1:Math.Ceiling --- 向上取整

什么时候用:算"最少需要几个"的时候 --- 宁可多一个,不能少。

csharp 复制代码
// 场景:蛋糕切成 3cm 一块,蛋糕长 10cm,要几块?
double cakeLength = 10;
double pieceSize   = 3;

int pieces = (int)Math.Ceiling(cakeLength / pieceSize);
// 10 ÷ 3 = 3.33 → Ceiling → 4 块
// 3 块只能装 9cm,最后 1cm 不能扔 → 多加一块

Ceiling vs Floor vs Truncate

csharp 复制代码
double x = 3.3;
Math.Ceiling(x);   // 4  向上:往更大的整数靠
Math.Floor(x);     // 3  向下:往更小的整数靠
Math.Truncate(x);  // 3  向零:直接砍掉小数部分

double y = -3.3;
Math.Ceiling(y);   // -3  向上:数轴上 -3.3 往上走到 -3
Math.Floor(y);     // -4  向下:数轴上 -3.3 往下走到 -4
Math.Truncate(y);  // -3  向零:砍掉小数

常见坑

  • Ceiling 返回 double,存 int 要强转 (int),否则类型不匹配
  • 负数时别搞混:Ceiling(-3.3)-3 不是 -4

为什么用 Ceiling 不用 Floor:Floor 会"丢一段"。10 个学生、每车坐 3 人 → 需要 4 辆车,不是 3 辆。


三、三种眼睛:CCD / FPD / CT

3.1 同一个光源,三种看法

复制代码
        X-Ray 光管(唯一光源)
              │
              │ X 光打下去
              ▼
     ┌───── 板子 ─────┐
     │                │
     ▼                ▼
  普通光相机        X光探测器
  (拍外观)         (拍透视/CT)
方式 像什么 怎么拍 看到什么
CCD 📷 手机拍蛋糕表面 普通光/LED 照板面,相机接收反射光 2D 外观
FPD 🏥 拍胸片 X 光穿透板子,探测器接收穿透光 所有层叠在一起的影子
CT 🏥 拍 CT X 光绕板子转多角度各拍一张 3D 模型,能一层一层切着看

通俗理解

  • CCD = 用眼看蛋糕,只看到表面
  • FPD = 手电筒从蛋糕底照,看到叠影(哪层有果酱?看不清)
  • CT = 把蛋糕切成薄片,一层一层看(果酱在第 3 层,清清楚楚)

3.2 物理平台:两个探头共用一套轨道

复制代码
  ┌────────────────────────────────┐
  │        X/Y/Z 三轴运动平台      │
  │                                │
  │   [CCD相机] ←── 偏移量 ──→ [FPD探测器]  │
  │                                │
  └────────────────────────────────┘

CCD 和 FPD 装在同一套轨道上,之间有固定物理偏移。

流程:轨道移到 CCD 位置拍外观照 → 平移固定偏移量 → FPD 对准同一位置拍 X 光。

比喻:同一辆滑板车上装两个摄像头,推到位置 A 用第一个拍,滑固定距离用第二个拍。

语法 2:switch 表达式 --- 比 if-else 更干净的多分支

为什么需要它:多个条件各返回不同值时,if-else 又长又啰嗦。

csharp 复制代码
// ❌ 老写法(switch 语句):需要 break,不返回值
string result;
switch (mode)
{
    case 0: result = "小焦斑"; break;
    case 2: result = "大焦斑"; break;
    default: result = "未知"; break;
}

// ✅ switch 表达式(C# 8+):一行赋值,自动返回
string result = mode switch
{
    0 => "小焦斑",
    2 => "大焦斑",
    _ => "未知"        // _ = discard 模式,兜底 default
};

语法拆解

  • value switch { ... } --- 表达式不是语句,直接产生值,可以赋给变量
  • pattern => result --- 匹配上了就返回 result,不需要 case 和 break
  • _ --- discard,匹配所有没列出来的情况,等价 default

什么时候用:条件 > 2 个、每个分支都是简单返回值 → switch 表达式。如果每个分支要做一堆事(调方法、改状态),用 if-else。

常见坑

  • 忘了加 _ 兜底 → 值不在列举范围内 → 运行时抛异常
  • 把 switch 语句的 default: 写成 switch 表达式 --- 语法不一样,编译不过

语法 3:Dictionary<K,V> --- 键值映射 O(1) 查找

为什么需要它:用"名字"找"东西"的场景 --- 不用遍历,直接拿。

csharp 复制代码
// 场景:学生成绩查询
var scores = new Dictionary<string, int>
{
    ["张三"] = 95,
    ["李四"] = 87,
    ["王五"] = 92,
};

// 查张三的成绩 → 一步到位,不用遍历
int zhangScore = scores["张三"];  // 95

// ❌ 但是!"赵六"不在字典里 → 抛 KeyNotFoundException
// ✅ 安全写法:
if (scores.TryGetValue("赵六", out int zhaoScore))
{
    Console.WriteLine($"成绩:{zhaoScore}");
}
else
{
    Console.WriteLine("没有这个人");
}

和 List 对比

List 查找 Dictionary 查找
怎么找 从头一个个比对 算哈希码,直接定位
100 项 最多比 100 次 1 次
复杂度 O(n) O(1)

什么时候用 Dictionary:知道 key 要取 value 的场景。key 是唯一的。

常见坑

  • dict[key] --- key 不存在直接炸,用 TryGetValue 兜底
  • key 是引用类型 → 注意别用 null 当 key
  • 同一个 key 存两次 → 第二次覆盖第一次,不报错

四、BGA 检测核心概念

4.1 桥接(Bridge)= 球挨太近了?

复制代码
焊球 A    焊球 B
  ●         ●        ← 正常:两个球,中间有间隙
  ●~~~~~~~●          ← NG:中间有锡连着,短路了

检测方法:膨胀法

复制代码
原始焊球框:           膨胀后(×1.5):
  ┌──┐  ┌──┐          ┌────┐┌────┐
  │ A│  │ B│    →     │  A ││ B  │  ← 扩大后重叠 = 桥接!
  └──┘  └──┘          └────┘└────┘

比喻:两个人站在地铁里,距离刚好不碰。但给他们各套一件羽绒服 --- 羽绒服碰上了,说明离得太近,有危险。

4.2 空洞率(Void)= 球里面有多少气泡

复制代码
  完美焊球        有空洞的焊球
   ┌─────┐        ┌─────┐
   │█████│        │██ ██│
   │█████│        │█ ○ █│  ← 中间有气泡
   │█████│        │██ ██│
   └─────┘        └─────┘
   空洞率 0%      空洞率 20% → 超过 30% NG

比喻:掰开一个馒头,里面气孔太多 → 不瓷实 → 容易裂。

4.3 枕头效应(HIP)= 看起来粘住了其实没粘

复制代码
  焊球  ██
        ██  ← 挨上了
  ─ ─ ─ ─ ─ ← 接触面
        ██  ← 锡膏
  焊盘  ██

两个表面挨上了但没有真正融合,像两块糖放在热锅里挨上了但没化在一起 --- 看着粘住了,一掰就开,时通时断。

产生原因:焊球熔了但锡膏没熔透 / 温度不够。回流焊 = BGA 贴 PCB 后进高温炉(预热→回流区 220-250°C 熔化→冷却凝固)。

4.4 五层切片

焊球在 CT 里不是只看一层,从上到下切 5 层:

复制代码
  Top      ─── ○ ───  顶层(贴芯片端)
  TopComp  ─── ○ ───  顶层补偿
  Center   ─── ◎ ───  中心层(最宽截面)
  BotComp  ─── ○ ───  底层补偿
  Bottom   ─── ○ ───  底层(贴 PCB 端)

每层各跑一套检测参数(空洞率、圆度、焊锡率),不合格就报。

语法 4:foreach vs for --- 遍历的选择

csharp 复制代码
// 场景:打印班级花名册
string[] students = { "张三", "李四", "王五", "赵六" };

// ✅ foreach --- 不用管索引,直接拿元素
foreach (var name in students)
{
    Console.WriteLine(name);
}

// for --- 需要知道"第几个",用索引
for (int i = 0; i < students.Length; i++)
{
    Console.WriteLine($"第{i + 1}名:{students[i]}");
}

选择规则

用 foreach 用 for
每个元素都要处理 需要知道序号
不需要修改集合 需要跳过/只处理一部分
代码更短更安全 需要反向遍历

常见坑

  • foreach 遍历期间不能修改集合 → 抛异常。方案:先 ToList() 复制一份再遍历
  • foreach 变量是只读的,不能给元素重新赋值(值类型)

五、焦斑与硬件控制

5.1 焦斑 = 手电筒调焦

电子束打在阳极靶上的点大小,决定光源大小:

复制代码
小焦斑(mode=0):           大焦斑(mode=2):
    ┌──┐                    ┌──────┐
    │很│ ← 光源小             │ 很亮 │ ← 光源大
    │清│   边缘锐利           │ 但糊 │   边缘模糊
    │楚│   但暗              │      │   但亮
    └──┘                    └──────┘

比喻:手电筒聚光模式(光斑小,照得远但范围窄)vs 泛光模式(光斑大,照得近但范围广)。工厂检测优先亮 → 默认大焦斑。

5.2 开光顺序:先设参再开灯

复制代码
SetFocus → SetVoltage → SetCurrent → 停上一轮 → 开光 → 等稳定
   ①          ②           ③           ④         ⑤       ⑥

比喻:开煤气灶 --- 先调火大小,再打火。不能先打着火再调大小。

语法 5:async/await --- 排队做事,不走神

csharp 复制代码
// 场景:早上出门流程
async Task MorningRoutine()
{
    BrushTeeth();          // 刷牙(同步,瞬间)
    WashFace();            // 洗脸(同步,瞬间)

    await BoilWater();     // 烧水(异步,要等5分钟)
                           // ↑ await = 站在这等着,水开了才往下走

    await MakeCoffee();    // 冲咖啡(异步,要等1分钟)
                           // ↑ 必须水开了才能冲,顺序不能乱

    Console.WriteLine("出门!");
}

// ❌ 错误:没有 await → BoilWater 还没开,MakeCoffee 就开始冲了
BoilWater();    // 发了指令就跑了
MakeCoffee();   // 水还凉着呢就开始冲 --- 崩

语法拆解

  • async Task --- 声明"这个方法里有 await"
  • await --- "这一步没做完之前,不准往下走"
  • 不加 await → 方法发了个指令就跑,不等结果

async ≠ 快。async 是"等的时候不卡住 UI",不是"跑的更快"。

常见坑

  • 写了 async 但方法里没有 await → 编译器警告
  • await 忘了写 → 异步变同步的假象,时序错乱
  • Task.Result 死锁 --- UI 线程上 await,别用 .Result

5.3 状态机:上楼不能跳级

复制代码
Offline → OnlineLocal → OnlineRemote
(没上线)   (本地控制)    (远程遥控)

不能跳级。像进公司大楼:先刷卡进大门,再进办公室。

语法 6:守卫子句(Guard Clause)

为什么需要它:把"不合法的情况"拦在门口,后续代码只处理合法的。

csharp 复制代码
// ❌ 层层嵌套,越读越深
void Process(User user)
{
    if (user != null)
    {
        if (user.IsActive)
        {
            if (user.HasPermission)
            {
                DoWork();  // 三层嵌套才到正事
            }
        }
    }
}

// ✅ 守卫子句:不合法就滚,合法的继续
void Process(User user)
{
    if (user == null) return;          // null 直接滚
    if (!user.IsActive) return;        // 不活跃滚
    if (!user.HasPermission) return;   // 没权限滚

    DoWork();  // 走到这的一定是合法用户,放心做事
}

语法点

  • if (不合法) return/throw --- 守卫子句的核心句式
  • 前面拦完,后面代码不用再判 null、不用再判状态

什么时候用:方法开头检查参数合法性、检查前置条件。

常见坑 :守卫子句处理完 null 了,后面别再用 ?. 画蛇添足。

5.4 轮询等稳定:隔一会看一眼

开光后电压不是瞬间到位 → 隔 100ms 看一次 → 到了继续、没到等、超时报错。

比喻:微波炉热牛奶 --- 隔一会打开看热了没,没热继续,超时还没热就是微波炉坏了。

语法 7:while 轮询 + Stopwatch 超时

csharp 复制代码
// 场景:等水烧到 100°C,最多等 30 秒
async Task<bool> WaitForBoiling(int timeoutMs = 30000)
{
    var timer = Stopwatch.StartNew();  // 秒表开始计时

    while (timer.ElapsedMilliseconds < timeoutMs)
    {
        double temp = ReadTemperature();

        if (temp >= 100)
        {
            return true;  // 水开了!
        }

        await Task.Delay(500);  // 没开,等半秒再测
    }

    return false;  // 超时,水还是没开 → 可能壶坏了
}

语法拆解

  • Stopwatch.StartNew() --- 启动秒表并返回,一行 = new + Start
  • while (条件) --- 条件成立就继续循环
  • Stopwatch.ElapsedMilliseconds --- 从 Start 到现在经过了多少毫秒
  • Task.Delay(500) --- 等 500ms,不卡 UI 。和 Thread.Sleep 完全不同
  • 两个出口:① return true 成功 ② return false 超时失败

Stopwatch vs DateTime.Now

  • Stopwatch 用硬件高精度计时器,误差微秒级
  • DateTime.Now 用系统时钟,误差可能十几毫秒,而且可能被用户改时间
  • 测性能、做超时 → 一律用 Stopwatch

常见坑

  • Task.Delay 不精确 --- 可能 501ms、510ms,看系统调度
  • while 忘了 await Task.Delay → 死循环吃满 CPU,一个核 100%
  • while 条件只写 true 不写超时 → 永远出不来

六、数据组织

6.1 超大元件切格子

元件比探测器视野大 → 一张拍不全 → 切成 N×M 个格子逐格拍:

复制代码
┌────┬────┬────┐
│1,1 │1,2 │1,3 │  ← 每个格子是一个"虚拟元件"
├────┼────┼────┤     标记 IsVirtual = true
│2,1 │2,2 │2,3 │
├────┼────┼────┤
│3,1 │3,2 │3,3 │
└────┴────┴────┘

格子数 = Math.Ceiling(元件宽 ÷ 视野宽) --- 向上取整,2.3 → 拍 3 格,不能漏。

比喻:A3 纸放不进扫描仪 → 折 4 张 A4 → 逐张扫 → 拼回去。

6.2 不同元件 → 不同处理器

复制代码
元件类型 ──→ 字典查找 ──→ 对应的检测处理器
  "A类"                    A处理器(检空洞、桥接)
  "B类"                    B处理器(检分段、虚焊)
  "C类"                    C处理器(2D单张即可,不用CT)

比喻:医院分诊台 --- 挂号时告诉护士症状,护士分到对应科室(内科/外科/骨科)。


附录:语法速查表

# 语法点 一句话 章节
1 Math.Ceiling 向上取整,2.3→3,宁可多不能少
2 Math.Floor / Math.Truncate 向下取整 / 向零截断
3 switch 表达式 value switch { pattern => result } 一行赋值
4 Dictionary<K,V> O(1) 键值查找,用 TryGetValue 安全取值
5 foreach vs for 遍历选择:不管索引用 foreach,要序号用 for
6 async/await 排队执行,await 不等完不走
7 守卫子句 if(非法) return 把不合法的拦门口
8 Stopwatch 高精度计时,ElapsedMilliseconds 拿耗时
9 while 轮询 + Task.Delay 循环检查 + 超时保护
相关推荐
yanaiding13 小时前
C# WPF 独立开发看图工具 PixSight 经验介绍(二)—— 水印模块开发实录
图像处理·c#·wpf·个人开发
淡海水14 小时前
15-02-YooAsset面试篇-Unity架构设计与源码
java·unity·面试·c#·游戏引擎·yooasset
-银雾鸢尾-16 小时前
C#中的多线程
开发语言·c#
在世修行18 小时前
从零打造 C# 工业视觉检测系统(九):工厂模式实现 SQL Server / MySQL / SQLite 三库切换
mysql·sqlite·c#·工厂模式
LccKyI20 小时前
C#学习day04
经验分享·笔记·学习·c#
LccKyI21 小时前
C#学习day05(开发福彩双色球系统附思维导图)
开发语言·学习·c#
-银雾鸢尾-1 天前
C#中的协变和逆变
开发语言·c#
Chen—LSN1 天前
C语言——深度理解指针(6)
c语言·开发语言·算法·c#
在世修行2 天前
从零打造 C# 工业视觉检测系统(八):Modbus RTU 协议解析与 PLC 剔除控制实战
开发语言·c#