示波器是任何电子工程师必备的测试仪器。它用于可视化和观察各种信号,通常作为一个二维图,其中一个或多个信号随时间绘制。它们用于电子设备的设计和调试,以查看和比较波形,并确定施加在其输入端的信号随时间变化的电压电平、频率、噪声和其他参数。这使得示波器成为电子工程师或制造商办公桌上非常重要的工具。然而,示波器相当昂贵。入门级型号的价格从 500 美元到 2,000 美元不等。而先进的示波器则要花费数千美元,这使得它们超出了基本用户的承受能力。但是如果我们能创造出一种更便宜、更紧凑、并且容易制作?这就是导致今天教程的问题。
ESP32 示波器功能
单通道
1Msps
50000 @ 16bits 缓冲区(50ms 数据,1Msps)
在 1Msps 时从 10us/div 扩展到 5ms/div
1X 模式下的最大 VPP 3.3V 和 10X 模式下的 33V
使用触觉开关进行快速响应控制。
频率计算(20hz min 由于缓冲区大小)
简单均值滤波器开/关
最大、最小、平均和峰峰值电压
时间和电压偏移
模拟、数字/数据模式
单次触发
自动缩放
构建基于 ESP32 的示波器所需的组件
ESP32 开发套件
1.69” 240x280 圆角 TFT 显示器(ST7789s)
触觉开关
单刀双掷开关
100K电阻
10K电阻
100nF电容
覆铜板或穿孔板
焊接工具
ESP32示波器电路图
下面给出了基于 ESP32 的示波器的完整电路图。
ESP32 用作数据采集的控制器。我们将利用内置的 I2S 缓冲区来存储和操作信号。这里使用 38 Pin 变体,但您也可以使用其他开发模块。
对于显示,我们使用的是 1.69” TFT 显示模块。它的分辨率为 240x280 像素。显示控制器是 ST7789S,为了驱动它,我们将使用 SPI 通信。
该模块还包含一个我们尚未使用的 SD 卡插槽。我们可以在未来的更新中将其用于波形捕获或类似应用。
键盘非常简单。带有上拉电阻的触觉开关用于此目的。我们正在使用硬件中断来检测每个按键。这将为我们提供一个非常灵敏的键盘。您可以了解我们之前介绍的ESP32 中断。
模拟输入部分相当简单。它由两个 SPDT 开关组成,用于范围选择和 AC/DC 耦合选择。对于范围选择,我们添加了一个分压器,可用于馈送峰值电压高于 3.3V 的信号。分压器将信号转换为 10:1 的比率。
构建和测试电路
您可以在 perfboard 中构建此项目,也可以使用页面底部链接中的文件制作 PCB。包括用于墨粉转移方法的 PDF 文件和用于制造的 Gerber 文件。这是示波器的 PCB 布局。
这是相同的PCB视图。
底部 PCB 视图。
用于示波器的 Arduino 代码
从本文底部给出的 Circuit Digest GitHub 存储库链接下载整个代码。在 GitHub 存储库中,您还可以找到一个名为TFT_eSPI的存档。这个修改后的库是驱动显示器所必需的。将其解压缩到 Arduino 库文件夹。如果您已经安装了TFT_eSPI 库,请确保在提取修改后的库之前将其删除。完成后,在板管理器中选择 esp32。然后编译代码并上传。就是这样,我们的 DIY 示波器就可以使用了。您可以使用底部的 Micro USB 端口为示波器供电。此端口仅用于供电。
代码
#include
#include
#include
#include
#include
#include
#include “esp_adc_cal.h”
#include “过滤器.h”
//#define DEBUG_SERIAL
//#define DEBUG_BUFF
#define 延迟 1000
//精灵的宽度和高度
#定义宽度 240
#定义高度 280
#define ADC_CHANNEL ADC1_CHANNEL_5 // GPIO33
#define NUM_SAMPLES 1000 // 样本数
#define I2S_NUM (0)
#define BUFF_SIZE 50000
#define B_MULT BUFF_SIZE/NUM_SAMPLES
#define BUTTON_Ok 32
#define BUTTON_Plus 15
#define BUTTON_Minus 35
#define BUTTON_Back 34
TFT_eSPI tft = TFT_eSPI(); // 声明对象“tft”
TFT_eSprite spr = TFT_eSprite(&tft); // 使用指向“tft”对象的指针声明 Sprite 对象“spr”
esp_adc_cal_characteristics_t adc_chars;
TaskHandle_t 任务菜单;
TaskHandle_t task_adc;
浮动 v_div = 825;
浮动 s_div = 10;
浮动偏移量 = 0;
浮动toffset = 0;
uint8_t current_filter = 1;
//选项处理程序
枚举选项 {
没有任何,
自动缩放,
分压,
斯迪夫,
抵消,
偏移量,
筛选,
停止,
模式,
单身的,
清除,
重置,
探测,
更新F,
光标1,
光标2
};
int8_t volts_index = 0;
int8_t tscale_index = 0;
uint8_t opt = 无;
布尔菜单=假;
布尔信息=真;
布尔 set_value = false;
浮动率 = 1000;//以 ksps --> 1000 = 1Msps
bool auto_scale = false;
bool full_pix = true;
布尔停止=假;
bool stop_change = false;
uint16_t i2s_buff[BUFF_SIZE];
bool single_trigger = false;
布尔数据触发器 = 假;
布尔更新屏幕=假;
布尔新数据=假;
布尔菜单操作 = 假;
uint8_t digital_wave_option = 0;//0-自动 | 1-模拟 | 2 位数据 (SERIAL/SPI/I2C/etc)
int btnok,btnpl,btnmn,btnbk;
无效 IRAM_ATTR btok()
{
btnok = 1;
}
无效 IRAM_ATTR btplus()
{
btnpl = 1;
}
无效 IRAM_ATTR btminus()
{
btnmn = 1;
}
无效 IRAM_ATTR btback()
{
btnbk = 1;
}
无效设置(){
序列号.开始(115200);
configure_i2s(1000000);
设置屏幕();
pinMode(BUTTON_Ok,输入);
pinMode(BUTTON_Plus,输入);
pinMode(BUTTON_Minus,输入);
pinMode(BUTTON_Back,输入);
attachInterrupt(BUTTON_Ok, btok, RISING);
attachInterrupt(BUTTON_Plus, btplus, RISING);
attachInterrupt(BUTTON_Minus, btminus, RISING);
attachInterrupt(BUTTON_Back, btback, RISING);
特征ADC();
#ifdef DEBUG_BUF
调试缓冲区();
#万一
xTaskCreatePinnedToCore(
core0_task,
"menu_handle",
10000, /* 以字为单位的堆栈大小 */
NULL, /* 任务输入参数 */
0, /* 任务的优先级 */
&task_menu, /* 任务句柄。*/
0); /* 任务应该运行的核心 */
xTaskCreatePinnedToCore(
core1_task,
"adc_handle",
10000, /* 以字为单位的堆栈大小 */
NULL, /* 任务输入参数 */
3、/*任务的优先级*/
&task_adc, /* 任务句柄。*/
1); /* 任务应该运行的核心 */
}
无效的core0_task(无效* pvParameters){
(void) pvParameters;
为了 (;;) {
菜单处理程序();
如果(新数据 || 菜单操作){
新数据=假;
菜单操作 = 假;
更新屏幕=真;
更新屏幕(i2s_buff,速率);
更新屏幕=假;
vTaskDelay(pdMS_TO_TICKS(10));
Serial.println("CORE0");
}
vTaskDelay(pdMS_TO_TICKS(10));
}
}
无效core1_task(无效* pvParameters){
(void) pvParameters;
为了 (;;) {
如果(!single_trigger){
而(更新屏幕){
vTaskDelay(pdMS_TO_TICKS(1));
}
如果(!停止){
如果(停止更改){
i2s_adc_enable(I2S_NUM_0);
stop_change = 假;
}
ADC_Sampling(i2s_buff);
新数据=真;
}
别的 {
如果(!stop_change){
i2s_adc_disable(I2S_NUM_0);
i2s_zero_dma_buffer(I2S_NUM_0);
stop_change = 真;
}
}
Serial.println("CORE1");
vTaskDelay(pdMS_TO_TICKS(300));
}
别的 {
浮动 old_mean = 0;
而(single_trigger){
停止=真;
ADC_Sampling(i2s_buff);
浮动平均值 = 0;
浮动 max_v, min_v;
peak_mean(i2s_buff, BUFF_SIZE, &max_v, &min_v, &mean);
//信号捕获(pp > 0.4V || 变化平均值 > 0.2V)-> 数据分析
if ((old_mean != 0 && fabs(mean - old_mean) > 0.2) || to_voltage(max_v) - to_voltage(min_v) > 0.05) {
浮动频率 = 0;
浮动周期 = 0;
uint32_t 触发器0 = 0;
uint32_t 触发器1 = 0;
//如果模拟模式或自动模式和波形识别为模拟
布尔数字数据=!假;
if (digital_wave_option == 1) {
trigger_freq_analog(i2s_buff, RATE, mean, max_v, min_v, &freq, &period, &trigger0, &trigger1);
}
否则如果(digital_wave_option == 0){
数字数据=数字模拟(i2s_buff,max_v,min_v);
如果(!数字数据){
trigger_freq_analog(i2s_buff, RATE, mean, max_v, min_v, &freq, &period, &trigger0, &trigger1);
}
别的 {
trigger_freq_digital(i2s_buff, RATE, mean, max_v, min_v, &freq, &period, &trigger0);
}
}
别的 {
trigger_freq_digital(i2s_buff, RATE, mean, max_v, min_v, &freq, &period, &trigger0);
}
单触发器 = 假;
新数据=真;
Serial.println("单机");
//在停止模式下返回正常执行
}
vTaskDelay(pdMS_TO_TICKS(1)); //其他任务开始的时间(低优先级)
}
vTaskDelay(pdMS_TO_TICKS(300));
}
}
}
无效循环(){}