尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

用ESP32-C3自制低频示波器:从ADC采样到波形显示的完整实践

用ESP32-C3自制低频示波器:从ADC采样到波形显示的完整实践 很多嵌入式开发者在入门时都会被同一个问题拦住想调一个 I2C 通信、看 PWM 占空比或者确认电源纹波是否异常手边却没有示波器。买一台入门级台式示波器通常要大几千于是 “用 ESP32-C3 自己做一个示波器” 就成了一个看起来很诱人的方案。不过先给结论ESP32-C3 示波器的价值不在于替代台式示波器而在于它把采样、触发、幅值校准、信号调理这些示波器底层概念全部摊开让你用一块几十块钱的开发板亲手跑通一条完整测量链路。这篇文章会围绕 ESP32-C3 示波器方案讲清楚几件事ESP32-C3 内置 ADC 到底能测什么、不能测什么信号进入 ADC 之前为什么要做调理和保护固件端如何采集数据上位机又如何实时画波形以及当你看到波形不准、上升沿明显变大时问题出在哪一步。读完以后你可以判断这个方案适不适合自己手头的项目也能照着代码搭出一个能用的低频虚拟示波器。需要说明的是这篇文章不打算把 ESP32-C3 示波器包装成“平替泰克”的方案。真正调试高速数字信号、测量电源纹波或者做严格的上升时间分析时台式示波器和隔离探头仍然是不可替代的工具。但如果目标是理解信号数字化流程、验证低速协议时序、观察传感器输出ESP32-C3 是一个成本很低并且足够灵活的起点。1. ESP32-C3 做示波器先想清楚边界网上关于“ESP32 示波器”的搜索结果很多但大多数只展示效果没有说明方案的真实边界。很多人把 ESP32-C3 的 ADC 引脚直接接到被测信号上发现波形抖动、读数不准、甚至芯片发烫就开始怀疑是代码问题。实际上问题通常出在方案定位不清晰。ESP32-C3 是一颗 RISC-V 内核的单片机集成 2.4G Wi-Fi 和 BLE内置一个 12 位 SAR ADC。这意味着它可以完成“模拟信号采样 数字信号处理 无线/串口传输”的完整链路。单片机和 Arduino 环境配合几分钟就能让 ADC 读数通过串口输出。但“能采样”不等于“能当示波器用”因为示波器是一个完整的模拟前端、采集、触发、显示系统ESP32-C3 只覆盖了采集和传输这一小段。如果只看芯片手册里“12-bit SAR ADC”这个参数很容易误以为垂直分辨率很高。实际上ADC 的分辨率只是把输入电压分成 4096 级真正决定测量质量的是有效位数、采样率、输入带宽、噪声和前端阻抗。ESP32-C3 的 ADC 更适合观察缓慢变化的模拟信号例如电池电压、光敏电阻输出、低频传感器波形而不是捕捉几十纳秒的上升沿。1.1 适用场景和不适用场景从实际工程经验看ESP32-C3 示波器方案比较适合以下几类任务观察 kHz 级别的音频信号、PWM 波形、传感器输出。学习 ADC 采样、触发、串口传输和上位机绘图。验证 I2C、UART 这类低速协议的电平变化是否存在明显异常。做低成本教学实验让更多人理解“示波器内部发生了什么”。相对地以下场景不建议使用这个方案测量几十 MHz 的时钟信号、高速总线。精确测量电源纹波尤其是 mV 级别的高频噪声。测量上升沿只有几十纳秒的数字信号。任何超过 3.3V 或者没有安全隔离的电路。这里有一个容易被忽略的点示波器探头输入阻抗通常为 1MΩ同时并联十几 pF 电容。ESP32-C3 开发板的 ADC 引脚虽然也是高阻抗输入但没有经过精心设计的探头补偿网络直接探测高频信号时引脚的寄生电容和走线电感会改变被测电路的工作状态。换句话说你看到的不一定是真实信号可能是开发板自身负载造成的影响。1.2 如何判断你是否需要这个方案如果在做电池供电设备、传感器数据采集、电机驱动波形观察、低速通信协议学习ESP32-C3 示波器是一个高性价比的工具。如果你需要测量电源模块的开关纹波、DDR 时序、射频信号那应该继续使用正规台式示波器。这个方案最大的价值是学习和原理验证而不是替代实验室设备。2. 示波器的关键指标和 ESP32-C3 差在哪很多人在做 ESP32-C3 示波器时只关注代码能不能把波形画出来忽略了示波器的核心参数。理解这几个参数才知道为什么有些波形明明存在却显示不出来。2.1 采样率决定“能不能看到信号”采样定理告诉我们要还原一个信号采样率至少要高于信号最高频率的两倍。实际工程中为了保证波形幅值可信采样率通常要是信号频率的 5 到 10 倍以上。ESP32-C3 的内置 ADC 采样能力受限于 ADC 控制器、时钟配置和固件读取方式。在 Arduino 环境下用analogRead()加串口打印实际有效采样率可能只有几千到几万次每秒连 10kHz 的方波都很难还原。想要看更高频信号必须改用 ESP-IDF 的连续采样模式并且配合合理的缓冲区和传输协议。2.2 带宽和上升时间的关系示波器的带宽决定了它能多准确地显示信号的快速变化。如果一个信号的上升沿是 20ns测它的示波器带宽至少需要几百 MHz。ESP32-C3 的模拟输入链路本身没有明确带宽指标但受 ADC 采样率和前端 RC 电路限制实际可用带宽很低。这也是为什么网上经常出现“示波器测上升时间比平时大了一倍”的问题——测量系统本身的带宽不足会把快沿拉成慢沿。用 ESP32-C3 DIY 方案测一个高速信号时呈现出来的上升时间可能被放大几十倍这并不代表芯片坏了而是采集链路跟不上信号变化。2.3 垂直分辨率不等于精度12 位 ADC 意味着理论上可以把 0~3.3V 分成 4096 级每级大约 0.8mV。但实际测量中ADC 的差分非线性、偏移误差、电源噪声和参考电压波动都会影响精度。许多 ESP32 系列芯片的 ADC 有效位数低于标称分辨率实际可能只有 9~10 位有效信息。如果不对 ADC 做校准直接用analogRead()原始值换算电压误差可能在几十 mV 甚至更大。这就是为什么正规示波器需要校准为什么自制的 ESP32-C3 示波器也要做两点校准。理解以上三个指标后再回头看第 1 章的适用场景就非常清楚ESP32-C3 示波器是一个低频、低精度、用于观察波形形状和相对变化的工具。只有接受这个定位后续的信号调理和软件设计才不会跑偏。3. 信号链路设计从探头接口到 ADC 引脚常规示波器前端包含衰减器、交流/直流耦合、阻抗变换和驱动放大电路。ESP32-C3 示波器即使简化也不能省略最基本的安全保护和阻抗匹配。下面这部分是整个方案最容易被忽略、但最影响可靠性的环节。3.1 为什么不能把信号直接接到 ADC 引脚ESP32-C3 的 ADC 输入引脚耐压范围有限。开发板上的 3.3V 供电系统如果直接测量一个 5V TTL 信号超过供电轨的电压可能通过引脚内部的 ESD 二极管泄漏到电源轨轻则读数异常重则损坏芯片。另外未经处理的信号源可能有较大的输出阻抗与 ADC 引脚内部采样电容相互作用导致采样值不稳定。一个稳妥的做法是在 ADC 引脚之前加入限流电阻和钳位保护。最简单的电路是在信号源和 ADC 引脚之间串联一个 1kΩ~10kΩ 电阻同时在引脚对地并联一个 3.3V 稳压二极管或者使用两个二极管做钳位。这样即使外部瞬态电压略高于 3.3V也能把电流限制在安全范围。需要注意串联电阻会与 ADC 内部采样电容构成 RC 低通滤波电阻过大会降低高频响应所以需要在保护和带宽之间做平衡。3.2 一个最小可用的模拟前端对于电池供电、传感器输出这类低压信号一个实用的 ESP32-C3 示波器前端可以这样设计输入端串联 1kΩ 限流电阻。在 ADC 引脚对地并联一个 3.3V TVS 管或肖特基钳位二极管防止过压。如果信号源输出阻抗较高增加一级单位增益运放缓冲器。运放建议选择单电源、轨到轨输出类型电源由开发板的 3.3V 提供。如果被测信号最高超过 3.3V先用电阻分压网络将电压降到 ADC 量程内分压比例要计入软件换算。不要直接测量市电或者任何高压电路。ESP32-C3 开发板没有隔离电源USB 地线可能和被检系统共地一旦接错会带来安全风险。3.3 软件校准比追求高精度硬件更实际即使加了前端电路ADC 的绝对精度仍然有限。更可行的做法是在软件里做两点校准。先用一个已知电压源输入 0V记录 ADC 读数再输入一个准确电压例如 3.000V记录 ADC 读数。然后用两点公式计算每个 ADC 原始值对应的电压。常见形式是float adc_to_voltage(int raw) { // offset 和 scale 通过两点校准得到 return (raw - offset) * scale; }这样可以在不更换硬件的情况下把测量误差控制在一个可以接受的范围。每次更换开发板或供电方式后建议重新校准一次。4. 固件采集用 Arduino 先跑通最小数据通路在深入 ESP-IDF 连续采样之前先用 Arduino 环境跑通一条最简单的采集链路能帮你快速验证硬件连接、串口通信和上位机绘图是否正常。下面的代码基于 Arduino-ESP32 core适用于大多数 ESP32-C3 开发板。4.1 环境准备你需要准备以下内容一块 ESP32-C3 开发板确认板载串口芯片或 USB 转串口电路正常。Arduino IDE 或 Arduino CLI安装 esp32 by Espressif Systems 开发板支持包。开发板选择ESP32C3 Dev Module。一根数据线最好支持数据传输而不是只充电。安装完成后在 Arduino IDE 中选择对应串口烧录下面的示例。4.2 最小 ADC 采样和串口输出示例这个程序的逻辑很简单初始化 12 位 ADC将输入引脚衰减设置为 11dB使可测量范围接近 0~3.1V然后在主循环中不断读取 ADC 原始值通过串口发送出去。// 文件路径esp32c3_oscillo_sampler.ino const int adcPin 0; // 不同开发板丝印不同请对照原理图选择 ADC 引脚 void setup() { Serial.begin(921600); // 使用较高波特率减少传输瓶颈 analogReadResolution(12); // 配置为 12 位分辨率 analogSetPinAttenuation(adcPin, ADC_11db); // 量程约 0~3.1V } void loop() { int raw analogRead(adcPin); Serial.println(raw); // 输出原始 ADC 值方便上位机换算 }这里的核心是输出原始值而不是直接输出电压。因为不同开发板的参考电压和衰减档位可能存在差异在上位机做换算和校准会更灵活。ADC_11db对应量程约 0~3.1V适合测量大多数 3.3V 系统的信号。如果你的被测信号始终在 0~2.2V 以内可以改用更低的衰减档位牺牲量程换取更好的小信号分辨率。需要注意analogRead()加Serial.println()的方式效率并不高。每打印一行都会占用串口时间实际采样率很可能不超过几 kS/s但这对于验证数据链路已经足够。如果运行后串口没有任何输出先检查串口波特率是否匹配再检查引脚编号是否选错。4.3 用串口绘图软件快速看波形如果你的代码运行正常在 Arduino IDE 的串口监视器中应该能看到连续滚动的数字。这时可以打开 Arduino IDE 自带的串口绘图器或者使用 SerialPlot、VOFA 等第三方工具。选择 921600 波特率后软件会把每一行数字当成一个数据点显示出来。给 ADC 引脚输入一个低频正弦波信号就能看到一条类似正弦波的曲线。如果只看到一条水平直线可能是输入信号超出了量程或者信号源没有与开发板共地。许多初学者在这里会犯一个错误信号发生器输出和开发板没有连接参考地导致 ADC 看到的电压悬浮波形自然不正常。5. 上位机实时显示从串口数据到动态波形串口绘图工具虽然方便但功能有限。如果你想加入触发、保存数据和自定义显示界面用 Python 写一个简单的上位机是更灵活的选择。下面是一个基于 pyserial 和 matplotlib 的实时绘图示例。它读取串口数据每一行解析成一个整数然后维护一个固定长度的采样窗口并实时刷新图形。# 文件路径esp32c3_oscillo_viewer.py import sys import serial import matplotlib.pyplot as plt from collections import deque PORT COM3 # Linux 下通常是 /dev/ttyACM0 或 /dev/ttyUSB0 BAUD 921600 WINDOW 500 # 显示最近 500 个样本 ser serial.Serial(PORT, BAUD, timeout0.1) samples deque(maxlenWINDOW) fig, ax plt.subplots() line, ax.plot([], [], lw1) ax.set_xlim(0, WINDOW) ax.set_ylim(0, 4096) def update(frame): while ser.in_waiting: raw_line ser.readline().decode(utf-8, errorsignore).strip() try: val int(raw_line) samples.append(val) except ValueError: pass data list(samples) line.set_data(range(len(data)), data) return line, ani matplotlib.animation.FuncAnimation(fig, update, interval30) plt.show()运行前需要安装依赖pip install pyserial matplotlib将代码中的PORT改成你的实际串口号。运行后如果数据链路正常图表会持续刷新。要注意的是这个脚本没有做严格的采样率控制显示的时间轴是“样本序号”而不是真实时间。想要换算成真实时间需要在固件中记录采样周期或者在 Python 中根据预期的采样率重新映射横轴。5.1 软件触发让波形稳定下来没有触发功能的示波器在观察重复信号时会感觉波形一直在水平漂移。解决方法是加入软件触发。原理很简单在样本序列中寻找一个从低电平跨过阈值电平的位置把这个位置作为波形起点。# trigger_demo.py —— 软件上升沿触发思路 def find_rising_edge(data, threshold1500): for i in range(1, len(data)): if data[i - 1] threshold data[i]: return i return -1 samples [...] # 固件一次采集到的原始 ADC 样本 idx find_rising_edge(samples) if idx 0: # 以上升沿之前 100 个样本作为起点截取一段波形 start max(0, idx - 100) segment samples[start:start 512] print(触发位置:, idx, 截取样本数:, len(segment)) else: print(未在缓冲区中找到上升沿)实际工程中下降沿触发、脉冲触发和迟滞比较都能用类似思路扩展。需要理解的是触发并不改变 ADC 数据本身而是决定从哪个时刻开始“稳定地展示”波形。对于 ESP32-C3 这种低频设备软件触发是最可行的方式。6. 用已知信号验证你的“示波器”是否准确完成最小采集和上位机显示之后不要急于去测量复杂电路。先用一个已知信号验证测量链路的准确性这是避免后续误判的关键。推荐用 ESP32-C3 自己的 PWM 输出作为测试信号因为信号源与采集系统共地不需要额外信号发生器。6.1 用 LEDC 输出 PWM 测试波形// 文件路径esp32c3_pwm_test_signal.ino const int pwmPin 4; // 选择一个不会被 ADC 占用的引脚 void setup() { ledcSetup(0, 5000, 8); // 5kHz PWM8 位分辨率 ledcAttachPin(pwmPin, 0); ledcWrite(0, 127); // 约 50% 占空比 } void loop() { }把 PWM 输出引脚经过一个 1kΩ 串联电阻后连接到 ADC 输入引脚。注意两个引脚必须共地。此时应当能看到一个频率为 5kHz 的方波。如果你的采集系统实际采样率不够看到的不再是干净方波而是轮廓甚至混叠现象。这是学习采样定理的直观素材当采样率低于信号频率的 2 倍时显示的频率会完全错误。如果发现占空比测量结果与 50% 偏差很大排查方向是采样点的均匀性。loop()中的串口打印不是精确等间隔的这会导致某些样本停留时间更长从而改变统计出的占空比。这也是为什么真正的高性能采集通常要使用定时器或者 DMA而不是在loop()中轮询读取。6.2 直流电压校准用一节 1.5V 干电池或一个稳定的可调电源经过已知比例的分压电阻把电压降到 ADC 量程内然后测量记录值。与万用表读数对比如果误差较大说明需要校准。校准后的换算参数可以写在前面的adc_to_voltage函数中。校准操作属于低电压安全操作但如果使用可调电源务必先调低电压再接入开发板避免误操作导致过压损坏。6.3 如何判断方案是否满足项目需要观察 PWM 波形时如果频率、占空比趋势能和程序设定对得上说明 ESP32-C3 示波器足以应对这类低频逻辑检查。如果测量结果连方波的基本形状都看不出来就不要勉强去测更高频的信号。把方案用在合适的地方才能发挥最大价值。7. 想跑更高采样率从 Arduino 到 ESP-IDF 连续采样Arduino 示例的价值在于快速验证链路但性能上限明显。Serial.println()阻塞式输出、每次采样都要进入函数调用都无法发挥 ESP32-C3 的极限。如果你想把 ESP32-C3 示波器做成一个真正能用的工具需要切换到 ESP-IDF使用 ADC 连续采样模式和 DMA 缓冲区。7.1 为什么 Arduino 串口打印会成为瓶颈在 Arduino 主循环中执行Serial.println()时CPU 需要等待串口外设把数据发送完这个过程中无法同时做下一次 ADC 采样。即便是缓冲式输出高频打印也会让循环周期不稳定。解决的思路是让 ADC 连续采样并写入 DMA 缓冲区CPU 在后台批量搬移数据而不是每次转换都打断 CPU。7.2 ESP-IDF 连续采样模式简介在 ESP-IDF 中ADC 连续采样通常使用adc_continuous驱动。你需要先配置 ADC 通道、采样周期和 DMA 缓冲区然后启动连续转换再周期性读取缓冲区数据。下面是一个流程示意// 伪代码/示意实际 API 请以当前 ESP-IDF 版本的官方示例为准 #include esp_adc/adc_continuous.h adc_continuous_handle_t adc_handle; adc_continuous_handle_cfg_t handle_cfg { .max_store_buf_size 4096, .conv_frame_size 512, }; adc_continuous_new_handle(handle_cfg, adc_handle); // 配置 ADC 通道和模式后启动 adc_continuous_start(adc_handle); for (;;) { uint8_t result[512]; uint32_t bytes_read 0; adc_continuous_read(adc_handle, result, sizeof(result), bytes_read, 1000); // 在这里解析 result 中的 ADC 原始数据并通过 USB CDC 批量发送 }真实项目中还需要加入结果解析、校准和传输帧协议。因为不同版本的 ESP-IDF API 有差异最稳妥的方法不是记忆接口名而是打开官方例程找到adc_continuous相关示例在此基础上修改。重点理解两件事第一采样由硬件驱动而不是loop()轮询控制第二数据从 DMA 缓冲区搬到上位机时需要成帧发送避免每个样本都占用一个串口包。7.3 硬件层面还能怎么提升如果连续采样依然不满足需求可以考虑外置 ADC。ESP32-C3 的 SPI 接口可以外接更高采样率的 ADC 芯片但成本和复杂度会上升。对于多数学习场景先把 ESP32-C3 内置 ADC 的行为吃透比盲目堆硬件更能帮助你理解示波器原理。类似的入门项目还可以扩展到 ESP32-S3、树莓派 Pico 等平台但不同芯片的 ADC 特性和驱动差异很大切换平台后需要重新验证。8. ESP32-C3 示波器常见问题与排查问题现象可能原因排查方式解决方案ADC 读数始终为 4095 或 0输入电压超出量程或引脚悬空用万用表测量 ADC 引脚电压调整分压比例确保输入在量程内读数波动很大信号源共地不良或引脚未加滤波检查信号源地线是否连接到开发板 GND确保共地必要时在 ADC 引脚并联小电容串口输出乱码波特率不匹配或串口工具设置错误检查 Serial.begin 与上位机波特率统一为 921600 或 115200上位机无数据串口号错误或开发板未识别查看设备管理器/系统设备列表更换 USB 口重新安装驱动波形水平漂移没有触发功能在数据中寻找上升沿/下降沿作为起点加入软件触发逻辑方波显示为三角波采样率不足或信号带宽太高降低被测信号频率或用更高采样率模式使用 ESP-IDF 连续采样或只测低频信号测量上升时间比实际大很多采集链路带宽不足检查前端 RC 滤波和采样率接受系统限制不要用于高速信号测量电压读数与万用表不一致ADC 未校准或衰减配置不对用已知电压做两点校准在软件中加增益和偏移校准每个问题背后都对应一个具体的测量链路环节。建议把排查顺序固定为先确认输入电压在安全量程内再确认共地然后用直流电压校准最后再进行波形观察。不要跳过校准直接测高频信号否则很难判断误差来自 ADC、前端电路还是传输协议。9. 最佳实践与工程建议把 ESP32-C3 示波器当作工程工具来用而不是一次性的实验代码需要在硬件、软件、测试流程三个层面养成规范。9.1 硬件层面的建议输入保护是长期可靠性的底线。无论被测信号是电池还是开发板上的 3.3V 逻辑尽量在 ADC 引脚前保留串联电阻。只测量低压、隔离良好的信号不要试图测量市电或者未隔离的开关电源。如果项目需要测多个通道可以规划一个可插拔的前端小板把衰减、限流、钳位电路固定下来而不是每次都在面包板上飞线。电源去耦同样重要。ESP32-C3 开启 Wi-Fi 时电流波动较大可能影响 ADC 参考电压。为了获得更稳定的测量结果尽量使用稳压电源供电并在开发板电源引脚附近增加电容。如果必须使用 USB 供电注意电脑 USB 口输出电压会在负载变化时轻微波动这种波动会直接叠加到 ADC 测量中。9.2 软件层面的建议固件与上位机之间的数据协议尽量简单清晰。入门阶段可以只输出原始 ADC 值但进入正式项目时建议定义一帧包含通道号、样本序号、ADC 原始值和校验字段。这样上位机可以检测丢包和乱序避免把坏数据显示成波形。采样数据最好批量传输。每行一个样本在低频时没问题但采样率提高后会产生大量串口中断。更合理的做法是在固件中填充缓冲区攒够一帧再发送。Python 端按帧解析而不是逐行解析这样 CPU 占用更低波形也更平滑。9.3 测试流程的建议给 ESP32-C3 示波器做“自检”是一个非常实用的习惯。保留一个 PWM 测试程序每次改动硬件或软件后先输出一个 1kHz 方波验证波形是否正常。自检通过后再去测实际项目信号可以极大减少误判。如果发现测量结果异常优先检查测试环境而不是直接怀疑被测设备。10. 总结与后续学习方向ESP32-C3 示波器是一个很好的嵌入式学习项目但它教会你的不是“省钱替代泰克”而是完整理解示波器的链路模拟信号的调理与保护、ADC 采样与量化、触发算法、数据传输、上位机可视化以及误差校准。把这些环节走通一遍比单纯使用一台商用示波器更能帮助你建立信号完整性直觉。如果你想继续深入下一步不是马上画 PCB而是先把三件事做好 第一用本文第 6 章的 PWM 信号验证当前系统的采样率上限 第二把串口打印的阻塞式采集改成批量缓冲输出观察采样率是否显著提升 第三用外接分压器和已知直流电压完成两点校准把测量误差降低到可接受范围。然后可以阅读 ESP-IDF 官方 ADC 连续模式示例尝试自己在例程基础上增加触发和上位机协议。等到你不再关心“怎么画一条波形”而是开始思考“这条波形为什么是这样”的时候再回头看 ESP32-C3 示波器项目它的目的就已经达成了。
返回列表