Previously, I posted a tutorial on Raspberry Pi-Pico Oscilloscope. And that was a great success. Keeping portable options in mind, I found this pretty mini oscilloscope between thousands of pages of a Japanese website. We are using a mini-OLED 128X64 to display the signal waveform, Frequency, duty cycle. 4 tactile buttons are used to change the modes, Volts/Divisions, and time/Divisions. So, I tried this, and the results are here.

mini_wer.jpg

Note* This project is only for educational purposes and shows the capabilities of a 16Mhz 8-bit microcontroller board. This MCU Can support frequencies below 50KHz, so it can’t be applicable for commercial and professional uses. That way, the project can also be entitled POOR MAN OSCILLOSCOPE for me.

Arduino Nano/Uno:

Both the boards have the same 8-bit Atmega328p microcontroller to use any of them. This tutorial uses I2C communication to print the readings on an OLED display. Our microcontroller has 6-channel 10-bit ADC, 13 digital I/O pins, and an 8Mhz internal clock.

audio signal.gif


That's why I tried the project on breadboard first. I want to know the plus/minus points of this oscilloscope. This project is sponsored By JLCPCB, Pcb, code and circuit diagram to this project is given below. Download all Gerber file from here and Quote for PCB on JLCPCB just in $2, On first sign-up from here you will get worth $30 coupons to order PCB.

For PC: https://jlcpcb.com/SSRFor mobile phone: http://m.jlcpcb.com/ssi

Features:

  • Single-channel -20Khz bandwidth
  • Onscreen- Volt/Div and Time/Div
  • Duty cycle monitoring
  • Small 0.96-inch I2C
  • AC/DC measurements option
  • Mode changing, Hold state features
  • Low battery consumption
  • Portable and pocket-sized

Display:

alpha.jpg

0.96-inch OLED display with I2C function comes with two different models, SSD1306 and SH1106, to change the code as per requirements. We have to uncomment which version of LCD we are using in this project.

Components used:

  • Arduino Nano
  • 128X64 OLED display
  • 100k, 10k, 820k, 510k, 12k resistors
  • 100nf, 7pf, 1uf ceramic disc capacitors
  • Tactile switch x4
  • Breadboard
  • Connecting wires

Circuit diagram:

rt.png


Circuit description:

After making all the connections, this oscilloscope can be powered by a 5v @100mA power supply or 3.7 volt Battery. Significantly fewer external components are used in this project; also, it supports several actions. Here MODE button is to select the volt/div, time/div, AC/DC, and 180* wave settings.

MENU.gif

After selecting the Mode, we can either increase the value or decrease per wave format. For Volts/divisions: This oscilloscope supports a division from 0.2volts to 50volts.

VOLT DIV.gif

For Time/Div: Code is modified to display small divisions in microseconds, 1.56 microseconds to 200milli-seconds.

TIME DIV.gif

Measurements:

  • With 50Hz sine wave:
mini_20220121_143037.jpg

I tried to hook upthis oscilloscope directly with Step down transformer AC signal and here are the results:

50HZ.gif

  • With 300HZ sine wave:
SINE.gif

  • With triangular wave:
TRI.gif

  • With rectangular wave:
RECT.gif

  • With sawtooth wave:
SAWTOOTH.gif

Libraries used:

OLED display SSD1306, SH1106, Fix_fft and EEPROM libraries are available in Arduino Mange library section. From here you can download as per your needs.

ice_screenshot_20220122-232703.png

PCB:

Download all the material regarding this project from here.

Arduino oscilloscope.png

Conclusion:

This oscilloscope is suitable for analyzing waveforms between 10Hz to 20Khz. That is precisely human hearing frequency. So, we can use this to measure audio signals, amplifier signals, and different Bluetooth signals. This oscilloscope should be a good try for them. Also, I think precision matters a lot in signals. If you want to go professional but cheaper, I would recommend DSO138. But this is about 4x the cost of this Arduino oscilloscope, but this one has an analog frequency range of 200khz.

tg.png

In comparison with This DSO, I have An Article about Raspberry Pi- Pico Oscilloscope, which offers android application compatibility, 2-channel, and 200khz frequency range. See this PI-PICO Oscilloscope from here.

About JLCPCB:

JLCPCB is the one of the most popular PCB makers. Price is just $2 for 2, 4 and 6 layer PCB. They just launched new purple solder mask, aluminum Pcb and 3d printing service in very low cost. Pcb quality is not compromised at any cost. Check them out right now from Here.

JLCPCB Is also providing new user coupons and sign-up rewards of up to $30. So, check them out from here. Register using this link to get Free PCB assembly service coupons. Get your 2 layer to 6-layer PCB’s just in $2, stencil and PCB assembly service in just $7.

For PC: https://jlcpcb.com/SSRFor mobile phone: http://m.jlcpcb.com/ssi

Gallery:

alpha.jpg


Updates:

We are trying to use a big touch TFT color screen with a high resolutions, So the project will be updated as soon as possible till then keep supporting us, we did not want any type of financial support, just follow us.

ice_screenshot_20220120-203539.png


More projects:

1) How to make Arduino Uno clone board.

2) How to program Arduino Using Smart Phone.

3) Arduino Nano clone board problems and solutions.

4) How to make Inductance Meter Using Arduino.

5) Raspberry Pi- PICO Oscilloscope.

Think you enjoy my work, stay tuned. Follow us on Instagram (sagar_saini_7294) and hackaday.

please support us- No donations, just follow and leave a comment.

Code: Code is quite bit long so we added small shots notes on each section, this will help you to understand


// Follow us on Hackster, Hackaday and the Instructables.
//Please First uncomment/comment the oled driver lines, which you are using
//Circuitkicker.com -- Sagar saini --sainisagar7294

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
//#include             // https://github.com/wonho-maker/Adafruit_SH1106
#include <EEPROM.h>

#define SCREEN_WIDTH   128              // OLED display width
#define SCREEN_HEIGHT   64              // OLED display height
#define REC_LENG       200              // size of wave data buffer
#define DISP_LENG      100              // size of display data
#define MIN_TRIG_SWING   5              // minimum trigger swing.(Display "Unsync" if swing smaller than this value
#define DOTS_DIV        25

// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
#define OLED_RESET     -1              // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 oled(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);   // device name is oled
//Adafruit_SH1106 oled(OLED_RESET);        // use this when SH1106

#define R_12k   4        //  12k ohm
#define R_820k  16       //  820k ohm  for AC low range
#define R_82k   17       //  82k omm for AC Hi range

// Range name table (those are stored in flash memory)
const char vRangeName[10][5] PROGMEM = {"A50V", "A 5V", " 50V", " 20V", " 10V", "  5V", "  2V", "  1V", "0.5V", "0.2V"}; // Vertical display character (number of characters including \ 0 is required)
const char * const vstring_table[] PROGMEM = {vRangeName[0], vRangeName[1], vRangeName[2], vRangeName[3], vRangeName[4], vRangeName[5], vRangeName[6], vRangeName[7], vRangeName[8], vRangeName[9]};
const char hRangeName[22][6] PROGMEM = {"200ms", "100ms", " 50ms", " 20ms", " 10ms", "  5ms", "  2ms", "  1ms", "500us", "200us", "100us", " 50us", " 81us", " 41us", " 20us", "156us", " 78us", " 31us", "15.6u", "7.8us", "3.1us", "1.56u"};  //  Hrizontal display characters
const char * const hstring_table[] PROGMEM = {hRangeName[0], hRangeName[1], hRangeName[2], hRangeName[3], hRangeName[4], hRangeName[5], hRangeName[6], hRangeName[7], hRangeName[8], hRangeName[9],
    hRangeName[10], hRangeName[11], hRangeName[12], hRangeName[13], hRangeName[14], hRangeName[15], hRangeName[16], hRangeName[17], hRangeName[18], hRangeName[19], hRangeName[20], hRangeName[21]};
const float hRangeValue[] PROGMEM = { 0.2, 0.1, 0.05, 0.02, 0.01, 0.005, 0.002, 0.001, 0.5e-3, 0.2e-3, 0.2e-3, 0.2e-3, 81.3e-6, 81.3e-6, 81.3e-6, 156.25e-6, 78.125e-6, 31.25e-6, 15.625e-6, 7.8125e-6, 3.125e-6, 1.5625e-6}; // record speed in second. ( = 25pix on screen) this value used for freq calc.

int waveBuff[REC_LENG];        // wave form buffer (RAM remaining capacity is barely)
char chrBuff[8];               // display string buffer
char hScale[] = "xxxAs";       // horizontal scale character
char vScale[] = "xxxx";        // vartical scale

float lsb5V    = 0.00563965;   // (5V)sensivity coefficient of 5V range. std=0.00563965 1.1*630/(1024*120)
float lsb50V   = 0.0512939;    // (50V)sensivity coefficient of 50V range. std=0.0512939 1.1*520.91/(1024*10.91)

float lsb5Vac  = 0.00630776;   //  std=0.00630776 V/LSB
float lsb50Vac = 0.0579751;    //  std=0.0579751 V/LSB

volatile int vRange;           // V-range number                   2:50V,  3:20V,  4:10V,  5:5V,  6:2V,  7:1V,  8:0.5V,  9:0.2V
volatile int hRange;           // H-range nubmer 0:200ms, 1:100ms, 2:50ms, 3:20ms, 4:10ms, 5:5ms, 6;2ms, 7:1ms, 8:500us, 9:200us, 10:100us, 11:50us, 12:
volatile int trigD;            // trigger slope flag,     0:positive 1:negative
volatile int scopeP;           // operation scope position number. 0:Veratical, 1:Hrizontal, 2:Trigger slope, 3:DC/AC/FFT
volatile boolean hold = false; // hold flag
volatile boolean switchPushed = false; // flag of switch pusshed !
volatile int saveTimer;        // remaining time for saving EEPROM
int timeExec;                  // approx. execution time of current range setting (ms)

int dataMin;                   // buffer minimum value (smallest=0)
int dataMax;                   //        maximum value (largest=1023)
int dataAve;                   // 10 x average value (use 10x value to keep accuracy. so, max=10230)
int dataRms;                   // 10x rms. value
int rangeMax;                  // buffer value to graph full swing
int rangeMin;                  // buffer value of graph botto
int rangeMaxDisp;              // display value of max. (100x value)
int rangeMinDisp;              // display value if min.
int trigP;                     // trigger position pointer on data buffer
boolean trigSync;              // flag of trigger detected
int att10x;                    // 10x attenuator ON (effective when 1)
int inMode;                    //  0=DC+, 1=DC+-, 2=AC
int offset5Vac;
int offset50Vac;

float waveFreq;                // frequency (Hz)
float waveDuty;                // duty ratio (%)

#include <fix_fft.h>
#define FFT_N 128
volatile boolean fftMode = false; // FFT mode false:Wave, true:FFT

void setup() {
  pinMode(2, INPUT_PULLUP);             // reserve (button press interrupt (int.0 IRQ))
  pinMode(3, OUTPUT);                   // PWM for trigger level
  pinMode(R_12k, INPUT);                // pin4 1/10 attenuator(Off=High-Z, Enable=Output Low)
  pinMode(5, INPUT_PULLUP);             // FFT mode
  pinMode(7, INPUT_PULLUP);             // AC mode
  pinMode(8, INPUT_PULLUP);             // Select button
  pinMode(9, INPUT_PULLUP);             // Up
  pinMode(10, INPUT_PULLUP);            // Calibration pulse output
  pinMode(11, INPUT_PULLUP);            // Hold
  pinMode(12, INPUT_PULLUP);            // Down
  pinMode(13, OUTPUT);                  // LED
  pinMode(R_820k, INPUT);               // A2
  pinMode(R_82k, INPUT);                // A3
  DIDR0 = 0x0f;                         // disable digital input buffer of A0-A3

  oled.begin(SSD1306_SWITCHCAPVCC, 0x3C); // select 3C or 3D (set your OLED I2C address)
 // oled.begin(SH1106_SWITCHCAPVCC, 0x3C);  // use this when SH1106

  auxFunctions();                       // Voltage measure (never return)
  loadEEPROM();                         // read last settings from EEPROM
#define REFERENCE_INTERNAL
#ifdef REFERENCE_INTERNAL
  analogReference(INTERNAL);            // ADC full scale = 1.1V
  trigger_level(26);                    // PWM triger level 0.5V for ET
#else
  analogReference(DEFAULT);             // ADC full scale = 5.0V
  trigger_level(128);                   // PWM triger level 2.5V for ET
#endif
  (void) analogRead(0);                 // dummy read to select A0 and reference
#ifdef USE_PIN2IRQ
  attachInterrupt(0, pin2IRQ, FALLING); // activate IRQ at falling edge mode
#else
  PCMSK0 = _BV(PCINT4) | _BV(PCINT3) | _BV(PCINT1) | _BV(PCINT0); // D8,D9,D12 pin change interrupt
  PCICR  = _BV(PCIE0);                  // enable interrupt from PCIE0 group
#endif
  startScreen();                        // display start message
}

void loop() {
  if (hRange < 15) pulse();             // calibration pulse is for realtime sampling only
  setInputOffset();                     // coupling mode set(AC/DC)
  setConditions();                      // set measurment conditions
  digitalWrite(13, HIGH);               // flash LED
  readWave();                           // read wave form and store into buffer memory
  digitalWrite(13, LOW);                // stop LED
  setConditions();                      // set measurment conditions again (reflect change during measure)
  dataAnalize();                        // analize data
  writeCommonImage();                   // write fixed screen image (2.6ms)
  plotData();                           // plot waveform (10-18ms)
  dispInf();                            // display information (6.5-8.5ms)
  oled.display();                       // send screen buffer to OLED (37ms)
  saveEEPROM();                         // save settings to EEPROM if necessary
  while (hold == true) {                // wait if Hold flag ON
    dispHold();
    if (inMode > 0) {                   //  if DC mode,
      if (acZero() == 1) {              //  if offset adj. executed
        scopeP = 0;                     //  scope position to vartical
        hold = false;                   //  cancel hold
      }
      delay(10);
    }                                   //
  }
}

int acZero() {                         // cancel AC renge offset
  if (digitalRead(8) == LOW) {         //  if select pushed
    if (vRange >= 5) {                 //  = 5V or less
      offset5Vac = dataAve / 10;       // adjust the offset
    } else {                           // range more than 5V
      offset50Vac = dataAve / 10;      //  adjust the offset
    }
    saveEEPROM();                      // 
    return 1;                          //  adjusted
  }
  return 0;                            // no adjust
}

void setInputOffset() {                //  set offset circuit
  if (inMode >= 1) {                   //  if AC mode
    if (att10x == 1) {                 //  10X-att enabled
      pull5V(R_82k);                   // ‹ pull 5V by 82k
      hiZ(R_820k);
    } else {                           //  10X-att disable
      hiZ(R_82k);
      pull5V(R_820k);                  //  pull 5V by 820k
    }
  } else {                             //  DC mode
    hiZ(R_820k);                       // Hi-Z
    hiZ(R_82k);                        // Hi-Z
  }
}

void hiZ(int n) {                      // set the pin to hi-z
  pinMode(n, INPUT);                   // set INPUT
  digitalWrite(n, LOW);                //  no pull up
}

void pull5V(int n) {                   // ‹ pull 5V through registor 
  pinMode(n, OUTPUT);                  //  set OUTPUT
  digitalWrite(n, HIGH);               //  OUTPUT HIGH
}

void pullGND(int n) {                  //  pull GND through registor 
  pinMode(n, OUTPUT);                  //  set OUTPUT
  digitalWrite(n, LOW);                //  output LOW
}

void setConditions() {              //  measuring condition setting
  if (digitalRead(7) == LOW) {      // set AC/DC
    inMode = 1;                     // ƒ‰
  } else {
    inMode = 0;                     // 
  }

  // get range name from PROGMEM
  strcpy_P(hScale, (char*)pgm_read_word(&(hstring_table[hRange])));  // H range name
  strcpy_P(vScale, (char*)pgm_read_word(&(vstring_table[vRange])));  // V range name

  switch (vRange) {                // setting of Vrange
    case 0:                        // 削除した、delaeted Auto50V range
      att10x = 1;                  // use input attenuator
      break;

    case 1:                        // 削除した、delaeted Auto 5V range
      att10x = 0;                  // no attenuator
      break;

    case 2:                        // 50V range
      if (inMode == 0) {
        rangeMax = 50.0 / lsb50V;  // set full scale pixcel count number
        rangeMaxDisp = 5000;       // vartical scale (set100x value)
        rangeMin = 0;
        rangeMinDisp = 0;
      } else {
        rangeMax = offset50Vac + 25.0 / lsb50Vac; // set full scale pixcel count number
        rangeMaxDisp = 2500;       // vartical scale (set100x value)
        rangeMin = offset50Vac - 25.0 / lsb50Vac;
        rangeMinDisp = -2500;
      }
      att10x = 1;                  // use input attenuator
      break;

    case 3:                        // 20V range
      if (inMode == 0) {
        rangeMax = 20.0 / lsb50V;  // set full scale pixcel count number
        rangeMaxDisp = 2000;
        rangeMin = 0;
        rangeMinDisp = 0;
      } else {
        rangeMax = offset50Vac + 10.0 / lsb50Vac;  // set full scale pixcel count number
        rangeMaxDisp = 1000;
        rangeMin = offset50Vac - 10.0 / lsb50Vac;
        rangeMinDisp = -1000;
      }
      att10x = 1;                  // use input attenuator
      break;

    case 4:                        // 10V range
      if (inMode == 0) {
        rangeMax = 10.0 / lsb50V;  // set full scale pixcel count number
        rangeMaxDisp = 1000;
        rangeMin = 0;
        rangeMinDisp = 0;
      } else {
        rangeMax = offset50Vac + 5.0 / lsb50Vac;  // set full scale pixcel count number
        rangeMaxDisp = 500;
        rangeMin = offset50Vac - 5.0 / lsb50Vac;
        rangeMinDisp = -500;
      }
      att10x = 1;                  // use input attenuator
      break;

    case 5:                        // 5V range
      if (inMode == 0) {
        rangeMax = 5.0 / lsb5V;    // set full scale pixcel count number
        rangeMaxDisp = 500;
        rangeMin = 0;
        rangeMinDisp = 0;
      } else {
        rangeMax = offset5Vac + 2.5 / lsb5Vac;    // set full scale pixcel count number
        rangeMaxDisp = 250;
        rangeMin = offset5Vac - 2.5 / lsb5Vac;
        rangeMinDisp = -250;
      }
      att10x = 0;                  // no input attenuator
      break;

    case 6:                        // 2V range
      if (inMode == 0) {
        rangeMax = 2.0 / lsb5V;    // set full scale pixcel count number
        rangeMaxDisp = 200;
        rangeMin = 0;
        rangeMinDisp = 0;
      } else {
        rangeMax = offset5Vac + 1.0 / lsb5Vac;    // set full scale pixcel count number
        rangeMaxDisp = 100;
        rangeMin = offset5Vac - 1.0 / lsb5Vac;
        rangeMinDisp = -100;
      }
      att10x = 0;                  // no input attenuator
      break;

    case 7:                        // 1V range
      if (inMode == 0) {
        rangeMax = 1.0 / lsb5V;    // set full scale pixcel count number
        rangeMaxDisp = 100;
        rangeMin = 0;
        rangeMinDisp = 0;
      } else {
        rangeMax = offset5Vac + 0.5 / lsb5Vac;    // set full scale pixcel count number
        rangeMaxDisp = 50;
        rangeMin = offset5Vac - 0.5 / lsb5Vac;
        rangeMinDisp = -50;
      }
      att10x = 0;                  // no input attenuator
      break;

    case 8:                        // 0.5V range
      if (inMode == 0) {
        rangeMax = 0.5 / lsb5V;    // set full scale pixcel count number
        rangeMaxDisp = 50;
        rangeMin = 0;
        rangeMinDisp = 0;
      } else {
        rangeMax = offset5Vac + 0.25 / lsb5Vac;  // set full scale pixcel count number
        rangeMaxDisp = 25;
        rangeMin = offset5Vac - 0.25 / lsb5Vac;
        rangeMinDisp = -25;
      }
      att10x = 0;                  // no input attenuator
      break;

    case 9:                        // 0.2V range
      if (inMode == 0) {
        rangeMax = 0.2 / lsb5V;    // set full scale pixcel count number
        rangeMaxDisp = 20;
        rangeMin = 0;
        rangeMinDisp = 0;
      } else {
        rangeMax = offset5Vac + 0.1 / lsb5Vac;  // set full scale pixcel count number
        rangeMaxDisp = 10;
        rangeMin = offset5Vac - 0.1 / lsb5Vac;
        rangeMinDisp = -10;
      }
      att10x = 0;                  // no input attenuator
      break;
  }
}

void writeCommonImage() {                 // 共通画像の作画 Common screen image drawing
  oled.clearDisplay();                    // 全クリア erase all(0.4ms)
  if (fftMode == true) return;            // no need for the FFT display
  oled.setTextColor(WHITE);               // write in white character
  oled.drawFastVLine(26, 9, 55, WHITE);   // left vartical line
  oled.drawFastVLine(127, 9, 3, WHITE);   // right vrtical line up
  oled.drawFastVLine(127, 61, 3, WHITE);  // right vrtical line bottom

  oled.drawFastHLine(24, 9, 7, WHITE);    // Max value auxiliary mark
  oled.drawFastHLine(24, 36, 2, WHITE);
  oled.drawFastHLine(24, 63, 7, WHITE);

  oled.drawFastHLine(51, 9, 3, WHITE);    // Max value auxiliary mark
  oled.drawFastHLine(51, 63, 3, WHITE);

  oled.drawFastHLine(76, 9, 3, WHITE);    // Max value auxiliary mark
  oled.drawFastHLine(76, 63, 3, WHITE);

  oled.drawFastHLine(101, 9, 3, WHITE);   // Max value auxiliary mark
  oled.drawFastHLine(101, 63, 3, WHITE);

  oled.drawFastHLine(123, 9, 5, WHITE);   // right side Max value auxiliary mark
  oled.drawFastHLine(123, 63, 5, WHITE);

  for (int x = 26; x <= 128; x += 5) {
    oled.drawFastHLine(x, 36, 2, WHITE);  // Draw the center line (horizontal line) with a dotted line
  }
  for (int x = (127 - 25); x > 30; x -= 25) {
    for (int y = 10; y < 63; y += 5) {
      oled.drawFastVLine(x, y, 2, WHITE); // Draw 3 vertical lines with dotted lines
    }
  }
}

void readWave() {                          // 波形の読み取ã‚