返回文章列表

把示波器上的李萨如图形变成一个可控系统:我的电赛自动锁图之路

把示波器上的李萨如图形变成一个可控系统:我的电赛自动锁图之路

这次电赛项目最开始看起来很像一道“把两个正弦波接到示波器上”的题:给定目标图形,让示波器显示直线、圆和无穷大。

真正做起来以后,我才发现题目考察的并不是某一个波形公式,而是一整套闭环系统:FPGA 产生两路稳定信号,STM32 负责交互与状态显示,树莓派通过摄像头观察示波器,再把测得的频率反馈回来。

我把这篇文章当成一次项目复盘,记录硬件、通信、视觉测频和自动锁图是怎样一步步连起来的,也记录那些“示波器上只闪了一下,程序却以为成功了”的调试时刻。

50 kHz 测试过程中的示波器画面,图中可以看到探测波形逐渐切换到李萨如轨迹

赛题的前四问:先把基本图形做准

第五问的自动控制建立在前四问之上。赛题规定,信号源输出的正弦信号一路直接接示波器 X 轴,另一路进入装置;装置处理后输出到示波器 Y 轴。信号源频率范围为 1 kHz~100 kHz,步进 100 Hz,示波器 X、Y 两个通道的灵敏度均为 0.5 V/div。

这意味着前四问首先要解决的是“波形关系和幅度标定”,而不是摄像头识别。

第一问:输入、输出直通,得到 8×8 div 的对角线

装置需要提供输入、输出直通模式。调节信号源输出幅度,使示波器 X-Y 模式中显示一个约 8×8 div 矩形的对角线。调好以后,信号源幅度和示波器两个通道的灵敏度都不能再改变,后续各问都以这组状态为基准。

在理想情况下,若

X(t) = A sin(ωt)
Y(t) = A sin(ωt)

Y = X,示波器上就是一条 45° 对角线。因为每个通道是 0.5 V/div,8 div 对应 4 V 峰峰值;这一步实际上完成了整个系统的基准幅度标定。

第二问:同频、等幅、正交,显示直径 8 div 的圆

第二问要求装置输出信号与输入信号同频、幅度相等、相位正交。可写成:

X(t) = A sin(ωt)
Y(t) = A sin(ωt + π/2)

消去时间变量后得到 X² + Y² = A²,所以示波器上应显示直径约 8 div 的圆。若示波器水平方向和垂直方向的实际 1 div 长度不完全相同,视觉上会变成椭圆;这不一定是电路相位错误,需要先区分示波器几何比例和幅度误差。

赛题对幅度误差的绝对值要求不超过 0.2 div。我的实现中,FPGA DDS 负责保持频率和相位关系,幅度参数由 STM32/FPGA 共同维护,最后再通过摄像头测量图形的外接宽高进行复核。

第三问:二倍频、等幅,显示水平“∞”形

第三问要求装置输出信号频率为输入信号的 2 倍,幅度相等,显示水平的“∞”形,并且图形上下左右对称。理想表达式可以写成:

X(t) = A sin(ωt)
Y(t) = A sin(2ωt + φcomp)

其中 φcomp 是实测后保留的相位补偿量。二倍频关系决定图形出现两个环,补偿相位则用来修正 FPGA、DAC 和模拟链路带来的固定偏差。这个项目中第三问也是最容易暴露低频和高频相位误差的部分:频率越高,同样的时间延迟会对应更大的相位偏移。

除了频率比必须正确,赛题还要求图形上下、左右对称,幅度误差绝对值不超过 0.2 div。因此不能只看“像不像无穷大”,还要检查两个方向的跨度和交点位置。

第四问:Y 轴峰峰值四档可调

第四问要求设置装置输出信号的峰峰值,使示波器 Y 轴方向图形峰峰幅度分别为 2、4、6 和 8 div,幅度误差绝对值同样不超过 0.2 div。由于 Y 轴灵敏度为 0.5 V/div,对应的输出峰峰值基准为:

屏幕 Y 轴峰峰幅度对应电压峰峰值
2 div1 Vpp
4 div2 Vpp
6 div3 Vpp
8 div4 Vpp

这四档是幅度控制的独立验收项,不能用改变示波器灵敏度来“补偿”。实际调试时,我把幅度码限制在 FPGA DDS 的有效范围内,并对 DAC 满量程、模拟偏置和示波器读数分别做了标定。

前四问和第五问的关系

可以把整个赛题理解成两层:

第一问:确定输入幅度和系统基准
第二问:验证同频正交与等幅
第三问:验证二倍频和相位补偿
第四问:验证输出幅度档位
第五问:断开信号源与装置连接后,用摄像头自动完成前面三种图形

第五问并不是重新发明三种波形,而是要在信号源频率任意变化、装置不能手动调节的条件下,自动找到正确频率、幅度和相位参数,并在正确图形出现后保持至少 5 s。也正因为前四问的基准不能漂移,自动模式中才必须保留原有的幅度映射、二倍频关系和相位补偿参数。

先把系统拆成三端

为了让问题可以定位,我没有把所有功能都塞进一块板子,而是让每一端只做自己擅长的事。

flowchart LR
    A[STM32 + LCD\n按键/页面/状态] <-->|UART| B[树莓派\nOV5647 + OpenCV]
    A <-->|UART| C[FPGA\nDDS/锯齿探测/时序]
    C --> D[两路模拟输出]
    D --> E[示波器 XY 模式]
    E -->|摄像头画面| B

FPGA:时间和波形的“硬实时”部分

FPGA 使用 50 MHz 时钟,负责产生两路 DAC 波形。系统空闲时输出探测锯齿,树莓派可以先通过锯齿轨迹判断当前频率是否适合视觉识别;收到有效目标和调谐字之后,再切换到 DDS 正弦输出。

DDS 的核心计算是调谐字:

TW = round(f_final × 2^32 / 50 000 000)

例如最终目标频率是 10 kHz,FPGA 并不是在软件里“延时 100 μs”,而是让相位累加器每个 50 MHz 时钟增加一个固定的调谐字。这样做的好处是频率分辨率高,而且所有切换都由时钟边沿决定。

探测波形的时长同样直接换算成时钟数:

扫描时长50 MHz 时钟数用途
0.1 ms5 000高频、轨迹较稀疏时快速探测
0.5 ms25 000默认的中间档
2 ms100 000低频兼容档
5 ms250 000低频或轨迹过稀时的补充档

这里有一个很容易忽视的细节:扫描时长和正弦频率不是同一个概念。 之前出现过“20 kHz 信号却按 2 ms 去算”的错误,本质就是把 FPGA 的扫描窗口当成了被测信号周期。

STM32:把系统变成一个“能操作的仪器”

STM32 负责按键、LCD 页面和两路串口转发。左右按键选择目标图形:直线、圆或无穷大;进入 Task5 后,STM32 向树莓派发出开始测量请求,并把树莓派返回的阶段、频率、质量和错误原因显示出来。

我后来把串口调试信息也放到了 LCD 上。相比只显示“发送成功”,显示实际的命令、序号和参数更有用,因为这样可以直接判断问题发生在:

  1. 树莓派没有发出命令;
  2. STM32 收到了但没有转发;
  3. FPGA 收到了但没有切换波形;
  4. 波形已经切换,视觉端却没有识别到。

树莓派:用摄像头读懂示波器

树莓派使用 Camera Module v2(OV5647)采集示波器画面。视觉程序只关注示波器屏幕中间区域,并优先提取绿色通道,因为示波器荧光轨迹在绿色通道中的对比度最高。

每一帧大致经过以下处理:

BGR 图像

提取 G 通道

去除边框与反光区域

自适应阈值 / 形态学去噪

寻找左右高亮拐点

同侧周期统计

频率估计与质量判断

10 kHz 附近得到的圆形李萨如图形

为什么低频和高频会表现得完全不同?

这次调试最折磨人的地方,是同一套算法在不同频率下会出现相反的问题。

低频时轨迹比较稀疏,单帧里能看到的拐点少。如果直接把相邻亮点的距离当成周期,算法很容易把半个周期或者一整帧的漏点误认为完整周期,最后频率会突然跳到几十 kHz。

高频时则相反:轨迹密集、亮线粘连,摄像头帧率和曝光时间会把多个周期叠在一起。此时“看起来点很多”并不代表信息更多,反而可能已经超过视觉测量的可靠范围。

所以最终采用了分档探测:

0.1 ms → 点太密?切到 0.5 ms
0.5 ms → 点仍太少?切到 2 ms
2 ms   → 仍不足?尝试 5 ms 或报告视觉超范围

每一档都先等待旧轨迹消失,再用固定时间窗口采集;不把整个 1 秒内所有帧的点数简单相加,而是使用“每帧拐点数的中位数”和有效帧比例来判断结果是否可信。

从粗测到精测:不要让摄像头帧率决定频率

粗测只能告诉我们“频率大概在哪个范围”。如果只使用相邻摄像头帧之间的相位变化,高频时很容易发生混叠:摄像头看到的相位变化可能已经绕了好几圈。

因此,精测阶段让 FPGA 产生两条间隔严格可控的细条。以 50 MHz 时钟为基准:

3 ms = 150 000 个时钟
7 ms = 350 000 个时钟

树莓派分别拟合两条细条的相位,再根据已知时间间隔消除整数周期歧义。这个思路和用长基线测频差很像:时间基线越长,频率误差对应的相位变化越明显。

相位误差的基本形式可以写成:

e = wrap(φ2 - φ1 - 2π f_coarse Δt) / 2π
Δf(k) = (e + k) / Δt

这里的 k 不能随便取,而要结合粗测结果枚举候选值,再用 3 ms 和 7 ms 两组结果共同筛选。这样做的目的,是避免“单次相位差刚好看起来合理,但实际差了整数个周期”的假锁定。

50 kHz 测试中出现的密集李萨如轨迹,说明视觉端已经接近可分辨上限

自动锁图不是一次判断,而是一个小状态机

最初的实现是“识别到圆就停止扫描”。这在静态图片上很好看,但在真实摄像头画面里太容易误判:反光、残留轨迹和曝光变化都可能让某一帧短暂像圆。

后来我把它改成几个明确状态:

WAIT_PI

COARSE_100US / COARSE_500US / COARSE_2MS

FINE_PHASE

TRACK

LOCKED

TRACK 阶段,程序不再不停地重新扫完整频率范围,而是围绕当前频率做小范围微调。圆形使用长短轴比、覆盖率和轨迹稳定性判断;直线关注拟合残差和方向;无穷大则需要额外判断双瓣结构和交点附近的覆盖情况。

实际测试中,10 kHz 左右的圆形是一个很好的例子:它先经历椭圆,再逐渐接近圆。此时正确动作不是马上判定失败,而是把椭圆长短轴差转换成一个小的频率修正量,继续在邻近频点尝试。

同一段测试中出现的椭圆状态,它是锁圆过程中的中间状态

对角线和无穷大也采用同样的原则:先识别结构,再决定是否微调;不要因为一帧不理想就立刻跳过当前频点。

一次典型的调试链路

当示波器“没有变换”时,我现在会按照下面的顺序排查:

  1. 先看树莓派日志:是否收到开始命令,是否真的发送了 PROBE_SINGLETARGET
  2. 再看 STM32 LCD 串口页:RX 和 TX 的命令、序号、校验是否一致,是否发生了只收到 STANDBY 而没有收到测量命令的情况。
  3. 最后看 FPGA 输出:探测档位是否改变,DDS 调谐字是否更新,DAC 是否从锯齿切换到正弦。
  4. 确认视觉端状态:是没有找到拐点、频率质量不合格,还是识别到了椭圆却被锁定条件拒绝。

这条链路的价值在于,它把“波形不对”拆成了可验证的几个问题,而不是一上来就怀疑摄像头或 FPGA。

这次项目给我的几个经验

1. 先设计可观测性,再设计自动化

如果没有串口日志、LCD 调试页和保存下来的掩膜图,自动锁图失败时只能凭感觉猜。调试接口不是附加功能,而是系统的一部分。

2. 视觉算法一定要承认自己的边界

相机不是频率计。曝光、帧率、反光、示波器荧光余辉都会影响测量。遇到点太密、轨迹粘连或有效帧比例过低时,程序应该报告“视觉超范围”,而不是输出一个看起来很精确的假频率。

3. 扫频和锁定是两个不同阶段

扫频的目标是找到候选频点,锁定的目标是保持在候选频点附近。把两者混成一个循环,就会出现“刚出现椭圆就跳走”或者“锁住后又重新扫完整范围”的问题。

4. 实验记录本身就是工程资产

我保留了不同频率下的示波器截图、视频抽帧、视觉掩膜和串口日志。现在回头看,这些文件比一句“当时好像能锁住”可靠得多,也让后续每一次阈值调整都有依据。

最终测试平台:FPGA、STM32、树莓派与示波器组成完整闭环

写在最后

这个项目让我对“自动化测量”有了更具体的理解:它不是把几个模块接起来,再加一个循环,而是要把时间基准、通信协议、视觉置信度、状态机和人机界面放在同一个系统里考虑。

示波器上的一条线、一个圆、一个无穷大,背后其实对应着 FPGA 的相位累加器、STM32 的按键状态、树莓派的一次图像统计,以及一串经过校验的字节。

当这些东西最终闭环,图形不再只是“显示出来”,而是可以被测量、被解释、被控制。对我来说,这就是这次电赛最值得留下来的部分。