先叠buff,这是玩具!玩具!玩具!
这是一个使用一晚上时间,指挥AI智能体实现的esp32示波器。这个也只是玩具级的demo。适配了lvgl,使用lvgl的划线功能绘制图像。代码默认开启GPIO0作为输入通道,50KHz的采样率,自动识别输入波形的频率,然后再截取固定相位的波形显示在屏幕上(3个周期)。
ESP32-C6 LCD 示波器 — 从零实现方案
背景:
已有条件:ESP32-C6 + ST7789T LCD (320×172 横屏已配好) + LVGL 已能正常显示(直接拿微雪或者其他成品代码给AI作参考适配就行了,不要闲着没事儿干丢屏驱芯片的手册,让AI给你写驱动)。 目标:实现简易示波器,ADC 采样率 50KHz,GPIO0 模拟输入。
数据流
ADC 采样(50KHz) → 触发(稳住波形) → 测量(频率+幅度) → LVGL 显示(波形+数字)
步骤 1:ADC 采样 + 校准
做什么
用 ESP-IDF 连续 ADC 驱动持续采集 GPIO0 的模拟信号,校准后以 mV 单位输出。
具体步骤
新建 组件
osc_adc.c
include/osc_adc.h
components/oscilloscope/
adc_cali_create_scheme_curve_fitting()
adc_cali_raw_to_voltage()
osc_adc_snapshot(buf, max)
ESP_LOGI
app_main
验收
编译烧录,串口持续输出采样值 GPIO0 接 GND → 打印值 ≈ 0mV GPIO0 接 3.3V → 打印值 ≈ 2500~3300mV(C6 满量程不是 3.3V) 接 1kHz 正弦波 → 数据有明显周期性高低变化
自己做这一部分的验收工作,确认adc连续采样已经可以工作了。
步骤 2:边沿触发
做什么
在采样数据中找到上升过中点的位置,让波形从固定相位开始,显示不漂移。
具体步骤
在 oscilloscope 组件中新建 和
osc_trigger.c
include/osc_trigger.h
data[i] >= mid && data[i-1] < mid
(min + max) / 2
found=false
验收
主循环:snapshot → 触发 → 打印触发位置 接正弦波,串口持续输出触发索引,值应在合理范围内跳动 后续接上波形显示后,波形应稳定不左右滚动
步骤 3:Vpp 测量
做什么
从一段采样数据中算出峰峰值电压。
具体步骤
在 oscilloscope 组件中新建 和
osc_measure.c
include/osc_measure.h
Vpp = (max_mV - min_mV) / 1000.0
ESP_LOGI
验收
接 1kHz 正弦波(信号发生器设置 1Vpp)→ 打印 Vpp ≈ 1.0V 接 1kHz 方波(信号发生器设置 3Vpp)→ 打印 Vpp ≈ 3.0V 误差 < 10%
步骤 4:频率测量 — 过零点法
做什么
统计上升过中点的次数和间距,计算基波频率。
具体步骤
在 中增加过零点测频函数
osc_measure.c
data[i] >= mid && data[i-1] < mid
ESP_LOGI
验收
1kHz 正弦波 → 打印 ≈ 1000Hz,误差 < 5% 100Hz 正弦波 → 打印 ≈ 100Hz 10kHz 正弦波 → 打印 ≈ 10000Hz 535Hz 方波 90%占空比 → 打印 ≈ 535Hz(不会误判为 1070Hz 二次谐波)
注意
< 200Hz 时频率读数会跳变(163ms 缓冲区内周期数太少),这是正常的,后续可用多帧平滑改善
步骤 5:频率测量 — FFT(可选 fallback)
做什么
为过零点失败的场景提供备选频率检测。此步骤可选,过零点法已覆盖绝大多数场景。
具体步骤
在 oscilloscope 组件中新建 和
osc_fft.c
include/osc_fft.h
sqrt(re² + im²)
验收
过零点失败时(如噪声信号),FFT 仍能给出近似频率 1kHz 正弦波 FFT 结果 ≈ 1000Hz
注意
- fft_len 必须是 2 的幂
否则 bit_reverse 死循环。用 计算 while (n*2 <= limit) n*=2 ESP32-C6 无 FPU,2048 点 FFT 会触发看门狗,限制在 512 点 方波的二次谐波比基波还强时 FFT 会误判,所以过零点必须是主方案
步骤 6:波形显示 — 基础版
做什么
在 LVGL 上画出波形,先实现最简单的版本:直接显示采样数据,不做周期缩放。
具体步骤
在 oscilloscope 组件中新建 和
osc_ui.c
include/osc_ui.h
lv_line
lv_point_t[320]
y = disp_h - 1 - (mV × disp_h / 3300)
X = 采样索引 (0~319) Y = 翻转映射:,高电压在顶部
lv_timer_handler()
osc_ui_update()
验收
接 1kHz 正弦波 → 屏幕上能看到绿色波形 波形幅度随信号发生器电压变化而变化 接 5kHz 信号 → 波形变密(周期多),但仍能看到
步骤 7:固定 3 周期显示
做什么
根据测量的周期,自动缩放波形,始终显示 3 个完整周期。
具体步骤
在 的 info 结构体中增加 字段
period_samples
osc_ui.h
period_samples = sample_rate / frequency + 0.5
osc_measure.c
src_len > wave_len
src_len = wave_len
src_len = period_samples × 3
计算要显示的采样数: 如果 ,则 (数据不够就全显示)
frac = i × (src_len-1) / 319
对每个像素 i,计算对应的采样索引 取相邻两个采样点线性插值
验收
1kHz 正弦波 → 屏幕上恰好 3 个完整周期 切换到 5kHz → 仍然 3 个周期 切换到 100Hz → 仍然 3 个周期 10kHz 信号 → 仍然 3 个周期
注意
可能小于 320(高频时),这是正常的,插值会拉伸,不需要加 的限制 >= 320 period_samples × 3
步骤 8:参考线 + 信息文字
做什么
叠加 1.65V 参考线和频率/Vpp 数字显示。
具体步骤
创建第二条 作为参考线,水平画在 位置(1.65V),颜色暗青色
y = disp_h/2
lv_line
lv_font_montserrat_14
lv_label
"%.0fHz %.2fVpp"
"%.2fkHz"
"---"
验收
屏幕上有一条暗色水平参考线 底部居中显示绿色文字,如 1.00kHz 1.50Vpp 频率和幅度随信号变化实时更新
步骤 9:整合主循环
做什么
将所有模块串联到的主循环中。
main/main.c
具体步骤
中依次初始化:LCD → LVGL → ADC → UI
app_main
vTaskDelay(50ms)
lv_timer_handler()
osc_ui_update()
osc_measure()
osc_trigger_rising()
osc_adc_snapshot()
调用 获取一帧 mV 数据 调用 找触发点 从触发点取后续数据,调用 测量频率和 Vpp 调用 更新波形和文字 调用 刷新显示 间隔
验收
接信号发生器,波形稳定显示 3 个周期 频率和 Vpp 数字合理 切换波形类型(正弦/方/三角)和频率,1 秒内稳定 长时间运行不死机(看门狗不触发)
这上面的步骤,FFT部分可以不需要做,其他的,遇到AI写Lvgl界面错误的,例如文字旋转丢失,位置不正确,波形绘制上下颠倒什么的,自己根据情况反馈给AI修正即可。
源码地址:https://gitee.com/walker2048/eetree-occurrence/tree/master/oscilloscope-c6
· END ·
请将我们设为“星标”,这样就会第一时间收到推送消息。
欢迎关注EEWorld旗下订阅号:“机器人开发圈”