基于ESP32实现示波器的制作教程

2023-03-28  

  示波器是任何电子工程师必备的测试仪器。它用于可视化和观察各种信号,通常作为一个二维图,其中一个或多个信号随时间绘制。它们用于电子设备的设计和调试,以查看和比较波形,并确定施加在其输入端的信号随时间变化的电压电平、频率、噪声和其他参数。这使得示波器成为电子工程师或制造商办公桌上非常重要的工具。然而,示波器相当昂贵。入门级型号的价格从 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 的示波器的完整电路图。

poYBAGLKM_uAND4QAAFh3txgaIQ478.png

  ESP32 用作数据采集的控制器。我们将利用内置的 I2S 缓冲区来存储和操作信号。这里使用 38 Pin 变体,但您也可以使用其他开发模块。

pYYBAGLKM_iALX7kAAEVz0dPU5Y011.png

  对于显示,我们使用的是 1.69” TFT 显示模块。它的分辨率为 240x280 像素。显示控制器是 ST7789S,为了驱动它,我们将使用 SPI 通信。

pYYBAGLKM_KAa9TsAAChLANxAWc802.png

  该模块还包含一个我们尚未使用的 SD 卡插槽。我们可以在未来的更新中将其用于波形捕获或类似应用。

poYBAGLKM_WABLhuAAPzJG4qDJ8575.png

  键盘非常简单。带有上拉电阻的触觉开关用于此目的。我们正在使用硬件中断来检测每个按键。这将为我们提供一个非常灵敏的键盘。您可以了解我们之前介绍的ESP32 中断。

pYYBAGLKM-uAM1SFAACG2trtwyk980.png

  模拟输入部分相当简单。它由两个 SPDT 开关组成,用于范围选择和 AC/DC 耦合选择。对于范围选择,我们添加了一个分压器,可用于馈送峰值电压高于 3.3V 的信号。分压器将信号转换为 10:1 的比率。

pYYBAGLKM-mAdw3JAACh89aUV4s072.png

  构建和测试电路

  您可以在 perfboard 中构建此项目,也可以使用页面底部链接中的文件制作 PCB。包括用于墨粉转移方法的 PDF 文件和用于制造的 Gerber 文件。这是示波器的 PCB 布局。

poYBAGLKM-2AWJ1EAANux9LScV4325.png

  这是相同的PCB视图。

poYBAGLKM-GAQZ-aAALeonXbYqQ009.png

  底部 PCB 视图。

poYBAGLKM_KAKZR5AAOTKM8ElG8659.png

  用于示波器的 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));

    }

  }

}

无效循环(){}


文章来源于:电子工程世界    原文链接
本站所有转载文章系出于传递更多信息之目的,且明确注明来源,不希望被转载的媒体或个人可与我们联系,我们将立即进行删除处理。