ESP32 Based oscillscope help

I am creating a project Build Affordable and Compact DIY ESP32 Oscilloscope
Esp32 based oscillscope.

I added a voltage divider network on the input of esp32 with 100k and 10k with a bias of 1.65volts on the resistor to convert both negative and positive voltages into 0-3.3v range. please help me edit the code so that the output on TFT screen is showing actual voltage readings after scaling from the 0-3.3 v range

Please post schematics and code, in code tags,here.

void setup_screen() {
  // Initialise the TFT registers
  tft.init();
  tft.setRotation(1);

  // Optionally set colour depth to 8 or 16 bits, default is 16 if not specified
  spr.setColorDepth(8);

  // Create a sprite of defined size (320x240)
  spr.createSprite(320, 240);
  // Clear the TFT screen to black
  tft.fillScreen(TFT_BLACK);
}

int data[320] = { 0 };  // Updated array size to match new width

float to_scale(float reading) {
  float voltage = (reading - 20480.0) / 4095.0 * 3.3;                     // Convert ADC reading to voltage
  float temp = 120 - (                                                    // Center 0V at 120 pixels
                 (((voltage + offset) * 3300 / (v_div * 6))) * (120 - 1)  // Scale within half of the screen height
               );
  return temp;
}


float to_voltage(float reading) {
  return (reading - 20480.0) / 4095.0 * 3.3;
}

uint32_t from_voltage(float voltage) {
  return uint32_t(voltage / 3.3 * 4095 + 20480.0);
}

void update_screen(uint16_t *i2s_buff, float sample_rate) {
  float mean = 0;
  float max_v, min_v;

  peak_mean(i2s_buff, BUFF_SIZE, &max_v, &min_v, &mean);

  float freq = 0;
  float period = 0;
  uint32_t trigger0 = 0;
  uint32_t trigger1 = 0;

  // If analog mode OR auto mode and wave recognized as analog
  bool digital_data = false;
  if (digital_wave_option == 1) {
    trigger_freq_analog(i2s_buff, sample_rate, mean, max_v, min_v, &freq, &period, &trigger0, &trigger1);
  } else if (digital_wave_option == 0) {
    digital_data = digital_analog(i2s_buff, max_v, min_v);
    if (!digital_data) {
      trigger_freq_analog(i2s_buff, sample_rate, mean, max_v, min_v, &freq, &period, &trigger0, &trigger1);
    } else {
      trigger_freq_digital(i2s_buff, sample_rate, mean, max_v, min_v, &freq, &period, &trigger0);
    }
  } else {
    trigger_freq_digital(i2s_buff, sample_rate, mean, max_v, min_v, &freq, &period, &trigger0);
  }

  draw_sprite(freq, period, mean, max_v, min_v, trigger0, sample_rate, digital_data, true);
}

void draw_sprite(float freq,
                 float period,
                 float mean,
                 float max_v,
                 float min_v,
                 uint32_t trigger,
                 float sample_rate,
                 bool digital_data,
                 bool new_data) {
  max_v = to_voltage(max_v);
  min_v = to_voltage(min_v);

  String frequency = "";
  if (freq < 1000)
    frequency = String(freq) + "Hz";
  else if (freq < 100000)
    frequency = String(freq / 1000) + "kHz";
  else
    frequency = "----";

  String s_mean = "";
  if (mean > 1.0)
    s_mean = "Avg: " + String(mean) + "V";
  else
    s_mean = "Avg: " + String(mean * 1000.0) + "mV";

  String str_filter = "";
  if (current_filter == 0)
    str_filter = "None";
  else if (current_filter == 1)
    str_filter = "Pixel";
  else if (current_filter == 2)
    str_filter = "Mean-5";
  else if (current_filter == 3)
    str_filter = "Lpass9";

  String str_stop = "";
  if (!stop)
    str_stop = "RUNNING";
  else
    str_stop = "STOPPED";

  String wave_option = "";
  if (digital_wave_option == 0)
    if (digital_data)
      wave_option = "AUTO:Dig./data";
    else
      wave_option = "AUTO:Analog";
  else if (digital_wave_option == 1)
    wave_option = "MODE:Analog";
  else
    wave_option = "MODE:Dig./data";

  if (new_data) {
    // Fill the whole sprite with black (Sprite is in memory so not visible yet)
    spr.fillSprite(TFT_BLACK);

    draw_grid();

    if (auto_scale) {
      auto_scale = false;
      v_div = 1000.0 * max_v / 1.5;
      s_div = period / 3.5;
      if (s_div > 7000 || s_div <= 0)
        s_div = 7000;
      if (v_div <= 0)
        v_div = 550;
    }

    // Only draw digital data if a trigger was in the data
    if (!(digital_wave_option == 2 && trigger == 0))
      draw_channel1(trigger, 0, i2s_buff, sample_rate);
  }

  int shift = 150;
  if (menu) {
    for (int x = 0; x < 320; x += 10) {         // Spaced vertical dots for reference line
      spr.drawLine(x, 118, x, 122, TFT_WHITE);  // Short vertical segments
    }

    // Display menu options
    spr.fillRect(shift, 0, 102, 110, TFT_BLACK);
    spr.drawRect(shift, 0, 102, 110, TFT_WHITE);
    spr.fillRect(shift + 1, 3 + 10 * (opt - 1), 100, 11, TFT_RED);

    spr.drawString("AUTOSCALE", shift + 5, 5);
    spr.drawString(String(int(v_div)) + "mV/div", shift + 5, 15);
    spr.drawString(String(int(s_div)) + "uS/div", shift + 5, 25);
    spr.drawString("Offset: " + String(offset) + "V", shift + 5, 35);
    spr.drawString("T-Off: " + String((uint32_t)toffset) + "uS", shift + 5, 45);
    spr.drawString("Filter: " + str_filter, shift + 5, 55);
    spr.drawString(str_stop, shift + 5, 65);
    spr.drawString(wave_option, shift + 5, 75);
    spr.drawString("Single " + String(single_trigger ? "ON" : "OFF"), shift + 5, 85);

    //spr.drawLine(shift, 103, shift + 100, 103, TFT_WHITE);

    // Display Frequency at the top-left corner
    int y_top = 5; // Position at the top of the screen
    int x_start = 10; // Starting X position
    spr.drawString("Freq: " + frequency, x_start, y_top);

    // Display Vmax, Vmin, and Vpp at the bottom of the screen
    int y_bottom = 220; // Position at the bottom of the screen (240 - 20 for text height)
    int x_bottom = 10; // Starting X position

    // Calculate text widths to avoid overlapping
    String vmax_text = "Vmax: " + String(max_v) + "V";
    String vmin_text = "Vmin: " + String(min_v) + "V";
    String vpp_text = "Vpp: " + String(max_v - min_v) + "V";

    int vmax_width = spr.textWidth(vmax_text);
    int vmin_width = spr.textWidth(vmin_text);
    int vpp_width = spr.textWidth(vpp_text);

    // Draw text with proper spacing
    spr.drawString(vmax_text, x_bottom, y_bottom);
    spr.drawString(vmin_text, x_bottom + vmax_width + 20, y_bottom); // Add 20 pixels spacing
    spr.drawString(vpp_text, x_bottom + vmax_width + vmin_width + 40, y_bottom); // Add 40 pixels spacing
  } else if (info) {
    for (int x = 0; x < 320; x += 10) {  // Spaced vertical dots for reference line
      spr.drawLine(x, 118, x, 122, TFT_WHITE);  // Short vertical segments
    }

    // Display Voltage per Division, Time per Division, and Frequency at the top of the screen
    int y_top = 5; // Position at the top of the screen
    int x_start = 10; // Starting X position

    // Voltage per Division
    String v_div_text = "V/div: " + String(int(v_div)) + "mV";
    spr.drawString(v_div_text, x_start, y_top);

    // Time per Division
    String s_div_text = "Time/div: " + String(int(s_div)) + "uS";
    spr.drawString(s_div_text, x_start + spr.textWidth(v_div_text) + 20, y_top); // Add 20 pixels spacing

    // Frequency
    String freq_text = "Freq: " + frequency;
    spr.drawString(freq_text, x_start + spr.textWidth(v_div_text) + spr.textWidth(s_div_text) + 40, y_top); // Add 40 pixels spacing

    // Display Vmax, Vmin, and Vpp at the bottom of the screen
    int y_bottom = 220; // Position at the bottom of the screen (240 - 20 for text height)

    // Calculate text widths to avoid overlapping
    String vmax_text = "Vmax: " + String(max_v) + "V";
    String vmin_text = "Vmin: " + String(min_v) + "V";
    String vpp_text = "Vpp: " + String(max_v - min_v) + "V";

    int vmax_width = spr.textWidth(vmax_text);
    int vmin_width = spr.textWidth(vmin_text);
    int vpp_width = spr.textWidth(vpp_text);

    // Draw text with proper spacing
    spr.drawString(vmax_text, x_start, y_bottom);
    spr.drawString(vmin_text, x_start + vmax_width + 20, y_bottom); // Add 20 pixels spacing
    spr.drawString(vpp_text, x_start + vmax_width + vmin_width + 40, y_bottom); // Add 40 pixels spacing
  }

  // Push the drawn sprite to the screen
  spr.pushSprite(0, 0);

  yield();  // Stop watchdog reset
}
void draw_zero_reference() {
  int zero_y = to_scale(from_voltage(offset));  // Calculate zero level

  // Vertical dotted reference line at zero level
  for (int x = 0; x < 320; x += 10) {
    spr.drawPixel(x, zero_y, TFT_WHITE);
  }

  // Right-pointing green arrow
  spr.drawLine(5, zero_y, 15, 120, TFT_GREEN);  // Horizontal arrow line
  spr.drawLine(15, zero_y, 10, zero_y - 5, TFT_GREEN); // Upward arrowhead
  spr.drawLine(15, zero_y, 10, zero_y + 5, TFT_GREEN); // Downward arrowhead

  // Ground symbol (⏚) below arrow
  spr.drawLine(5, zero_y + 5, 5, zero_y + 10, TFT_GREEN);  
  spr.drawLine(2, zero_y + 10, 8, zero_y + 10, TFT_GREEN);  
  spr.drawLine(3, zero_y + 12, 7, zero_y + 12, TFT_GREEN);  
  spr.drawLine(4, zero_y + 14, 6, zero_y + 14, TFT_GREEN);  
}


void draw_grid() {
  for (int i = 0; i < 32; i++) {  // Updated to 32 for 320 pixels
    spr.drawPixel(i * 10, 40, TFT_WHITE);
    spr.drawPixel(i * 10, 80, TFT_WHITE);
    spr.drawPixel(i * 10, 120, TFT_WHITE);
    spr.drawPixel(i * 10, 160, TFT_WHITE);
    spr.drawPixel(i * 10, 200, TFT_WHITE);
  }
  for (int i = 0; i < 240; i += 10) {
    for (int j = 0; j < 320; j += 40) {  // Updated to 320 pixels
      spr.drawPixel(j, i, TFT_WHITE);
    }
  }
}


void draw_channel1(uint32_t trigger0, uint32_t trigger1, uint16_t *i2s_buff, float sample_rate) {
  // Screen wave drawing
  data[0] = to_scale(i2s_buff[trigger0]);
  low_pass filter(0.99);
  mean_filter mfilter(5);
  mfilter.init(i2s_buff[trigger0]);
  filter._value = i2s_buff[trigger0];
  float data_per_pixel = (s_div / 40.0) / (sample_rate / 1000);

  uint32_t index_offset = (uint32_t)(toffset / data_per_pixel);
  trigger0 += index_offset;
  uint32_t old_index = trigger0;
  float n_data = 0, o_data = to_scale(i2s_buff[trigger0]);
  for (uint32_t i = 1; i < 320; i++) {  // Updated to 320 pixels
    uint32_t index = trigger0 + (uint32_t)((i + 1) * data_per_pixel);
    if (index < BUFF_SIZE) {
      if (full_pix && s_div > 40 && current_filter == 0) {
        uint32_t max_val = i2s_buff[old_index];
        uint32_t min_val = i2s_buff[old_index];
        for (int j = old_index; j < index; j++) {
          // Draw lines for all this data points on pixel i
          if (i2s_buff[j] > max_val)
            max_val = i2s_buff[j];
          else if (i2s_buff[j] < min_val)
            min_val = i2s_buff[j];
        }
        spr.drawLine(i, to_scale(min_val), i, to_scale(max_val), TFT_GREEN);
      } else {
        if (current_filter == 2)
          n_data = to_scale(mfilter.filter((float)i2s_buff[index]));
        else if (current_filter == 3)
          n_data = to_scale(filter.filter((float)i2s_buff[index]));
        else
          n_data = to_scale(i2s_buff[index]);

        spr.drawLine(i - 1, o_data, i, n_data, TFT_GREEN);
        o_data = n_data;
      }
    } else {
      break;
    }
    old_index = index;
  }
}
void configure_i2s(int rate) {
  /*keep in mind:
     dma_buf_len * dma_buf_count * bits_per_sample/8 > 4096
  */
  i2s_config_t i2s_config =
  {
    .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_ADC_BUILT_IN),  // I2S receive mode with ADC
    .sample_rate = rate,                                                          // sample rate
    .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,                                 // 16 bit I2S
    .channel_format = I2S_CHANNEL_FMT_ALL_LEFT,                                   // only the left channel
    .communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB),   // I2S format
    .intr_alloc_flags = 1,                                                        // none
    .dma_buf_count = 2,                                                           // number of DMA buffers
    .dma_buf_len = NUM_SAMPLES,                                                   // number of samples
    .use_apll = 0,                                                                // no Audio PLL
  };
  adc1_config_channel_atten(ADC_CHANNEL, ADC_ATTEN_11db);
  adc1_config_width(ADC_WIDTH_12Bit);
  i2s_driver_install(I2S_NUM_0, &i2s_config, 0, NULL);

  i2s_set_adc_mode(ADC_UNIT_1, ADC_CHANNEL);
  SET_PERI_REG_MASK(SYSCON_SARADC_CTRL2_REG, SYSCON_SARADC_SAR1_INV);
  i2s_adc_enable(I2S_NUM_0);
}

void ADC_Sampling(uint16_t *i2s_buff){
  for (int i = 0; i < B_MULT; i++) {
    //TODO i2s_read_bytes is deprecated, replace with new function
    i2s_read_bytes(I2S_NUM_0, (char*)&i2s_buff[i * NUM_SAMPLES],  NUM_SAMPLES * sizeof(uint16_t), portMAX_DELAY);    
  }
}

void set_sample_rate(uint32_t rate) {
  i2s_driver_uninstall(I2S_NUM_0);
  configure_i2s(rate);
}

The program is multifile please tell me which file should I provide

I am using a 100k and 10k voltage divider with the 10k connected to 1.65volts and 100k connected to the input and esp32 is connected to a TFT display
Please help me as I am in some urgent to submit my project
Thanks for reply

Hi, @rahanachu3
Welcome to the forum.

Tom.... :smiley: :+1: :coffee: :australia:

Hai @tomgeorge ! can you please help me to solve my problem I am in urgent need of this

Did you read the comments on that website, and update the code you posted to fix problems with various functions? Or did you just post the initial code and not make the corrections?

I corrected the code but I need to modify the circuit to increase range of measurement

You say the scaling is wrong, but you haven't given us any indication of how much it needs changing by.

An attenuator built using 100kΩ and 10kΩ divides the signal by 11.

Is the problem that the voltage readings are only an eleventh of what they should be?

 float voltage = (reading - 20480.0) / 4095.0 * 3.3;           // Convert ADC reading to voltage

You could multiply 'voltage' by a 'scaling factor' to give the correct reading.

I want to scale voltage for example +15 or -15 to a range in 0.to 3.3v for adc input of esp32.
I want to scale back to original voltage readi g in software (program) to back to show +15 or -15 multiplying by scaling factor doesn't seem to work

I have noticed that the code uses "uS" and "uS/div" to represent microseconds and microseconds per division.

In the MKS system, a capital 'S' is used to represent siemens - the unit of conductance.
Seconds are represented by a small 's'.

You should change all occurrences of "uS" to "us" or even better "µs".

I'd mark you down if I saw "uS" in your project.

I admit the mistake and will correct it but the code does not serve my purpose.
This code was taken from github

Have you built and run that first?
If not, may I suggest you do so and work STAGE by STAGE from there?

Tom.... :smiley: :+1: :coffee: :australia:

Sorry I didn't understand

That formula that I quoted in post #8 occurs in two functions, to_scale( ) and to_voltage( ). You would need to multiply each by a 'scaling factor'.

float to_scale(float reading) {
  float voltage = (reading - 20480.0) / 4095.0 * 3.3;                     // Convert ADC reading to voltage
  float temp = 120 - (                                                    // Center 0V at 120 pixels
                 (((voltage + offset) * 3300 / (v_div * 6))) * (120 - 1)  // Scale within half of the screen height
               );
  return temp;
}


float to_voltage(float reading) {
  return (reading - 20480.0) / 4095.0 * 3.3;
}

Done it but doesn't work

I can't see why it doesn't work.

Unfortunately I don't have suitable hardware to do any testing on.

It seems strange to me that in the original project, that they show the attenuator together with a switch to select/de-select it, but don't have anything in the software to adjust the scaling to take account of that attenuator.

Have you already built the project in the YouTube video?
If not, I suggest you do FIRST to make sure the hardware and base code works.

Tom.... :smiley: :+1: :coffee: :australia:

I have done it

Did it work?
If so please post the working code.

Tom.... :smiley: :+1: :coffee: :australia:

The code works but the voltage and frequency measurements are wrong.