DIY Arduino Oscilloscope

DIY Arduino Oscilloscope © GPL3+

A digital device that visualizes analog signal.

  • 3,243 views
  • 3 comments
  • 18 respects

Components and supplies

Apps and online services

About this project

A very basic and easy to make Arduino PC oscilloscope.

Features:

  • 50K samples/second(actually it can go up to 110K but the signal will become noisy)
  • Auto trigger
  • Frequency counter
  • Reasonably accurate voltage readings (depending on the accuracy of the resistors used for the voltage dividers)
  • Optional: selectable voltage range: 5V, 6.6V, 10V, 20V

You'll Need:

  • An Arduino Leonardo or Arduino Micro
  • 2 crocodile clamps
  • a 0.1µF capacitor (optional)
  • a 5.1V zener diode (optional)
  • a pc with Processing

For the voltage dividers (optional, if you want to measure than 5V or want selectable range):

  • 2 two-pole dual throw switches
  • two 3K resistors
  • two 1.5K resistors
  • one 1K resistor
  • a small perfboard or breadboard

If you only need to measure op to 5V, you can skip the voltage dividers and connect the probes directly to GND and A1. You'll have to modify the code a bit:

In the Arduino code, replace:

ADMUX =  B00000000;         // select external reference and port 5 (A0)

with:

ADMUX =  B01000000;         // select internal reference (Vcc - 5V) and port 5 (A0)

In the processing code, replace:

// read switch position & set voltage range boolean switch1=((buffer[writeIndex*2]&(byte)8)==8);                                                  boolean switch2=((buffer[writeIndex*2]&(byte)4)==4); if (!switch1&&!switch2) voltageRange=20; if (!switch1&&switch2) voltageRange=10; if (switch1&&!switch2) voltageRange=6.64; if (switch1&&switch2) voltageRange=5;

with:

voltageRange=5;

Code

processing codeC#
import processing.serial.*;
import java.awt.event.KeyEvent;

// Settings --------------------------------------------------------------------------
int serialBaudRate=115200;
int bufferSize=10000000;      // number of samples (memory use: 2 bytes per sample)
float samplesPerLine=1;       // increment 1 screen line for each [n] samples
boolean sync=true;            // sync to trig
float trigLevelLPF=.0001;     // trigLevel lowpass filter (exponential moving average)
boolean hold=false;           // pause
int prescaler=5;              // ADC prescaler: 3:8 | 4:16 | 5:32 | 6:64 | 7:128
// -----------------------------------------------------------------------------------

Serial serial;
byte[] buffer= new byte[bufferSize*2];
int writeIndex, prevWriteIndex, readIndex, trigIndex, trigCounter, loopCounter, windowWidth, windowHeight, offset;
float voltageRange, sps, frequency, trigLevel, timer;
int windowX=30;
int windowY=50;
boolean connected=false;
String serialPort;

void settings() {
  size(1000, 600);
}

void setup() {
  background(0);
  surface.setResizable(true);
  frameRate(50);
}

void draw() {
  windowWidth=width-100;
  windowHeight=height-100;
  if (!connected) {
    background(0, 0, 0);
    text("ARDUINO OSCILLOSCOPE\n\nSelect serial port:", 25, 25);
    for (int i=0; i<Serial.list().length; i++)
      text("F"+(i+1)+" - "+Serial.list()[i], 25, 80+i*20);
  } else {
    background(0, 0, 0);

    // update frequency counters every second
    loopCounter++;
    if (loopCounter>frameRate) {  
      loopCounter=0;
      float elapsedSeconds=(millis()-timer)/1000;
      timer=millis();
      sps=(writeIndex-prevWriteIndex)/elapsedSeconds;  // sample rate
      if (sps<0)sps+=bufferSize;
      prevWriteIndex=writeIndex;
      frequency=trigCounter/elapsedSeconds;            // signal frequency
      trigCounter=0;
    }

    // read switch position & set voltage range
    boolean switch1=((buffer[writeIndex*2]&(byte)8)==8);                                                 
    boolean switch2=((buffer[writeIndex*2]&(byte)4)==4);
    if (!switch1&&!switch2) voltageRange=20;
    if (!switch1&&switch2) voltageRange=10;
    if (switch1&&!switch2) voltageRange=6.64;
    if (switch1&&switch2) voltageRange=5;

    // print voltage scale
    for (int n=0; n<=10; n++)                                                                           
      text(nf(voltageRange/10*n, 2, 2)+"V", windowX+windowWidth+5, windowY+windowHeight-(n*windowHeight/10));

    // print interface and statistics
    text("[F1-F2] ZOOM | [F3] SYNC: "+sync+" | [F4] HOLD: "+hold+" | [F5-F6] TRIG LPF | [F7-F8] PRESCALER: "+nf((pow(2, prescaler)), 1, 0)+" | [<--->] OFFSET", 25, 25);
    text("frequency: "+nf(frequency, 5, 2)+"Hz"
      +" | average DCV: "+nf(trigLevel/1024*voltageRange, 2, 2)+"V"
      +" | samplerate: "+nf(sps, 5, 2)+"Hz"
      +" | samples per line: "+samplesPerLine
      +" | division: "+samplesPerLine/sps*(float)width*100+"ms", 25, height-20);

    // draw trigLevel (= average voltage) line
    stroke(0, 0, 100);                                                                                   
    int trigLevelHeight=(int)(trigLevel*(float)windowHeight/1024);
    line(windowX, windowY+windowHeight-trigLevelHeight, windowX+windowWidth, windowY+windowHeight-trigLevelHeight);  

    // draw grid
    stroke(50);                                                                                           
    for (float n=0; n<=windowWidth; n+=(float)windowWidth/10) 
      line(n+windowX, windowY, n+windowX, windowHeight+windowY); 
    for (float n=0; n<=windowHeight; n+=(float)windowHeight/10) 
      line(windowX, n+windowY, windowX+windowWidth, n+windowY);

    // ------------------------------
    // DRAW WAVEFORM
    // ------------------------------
    stroke(255);
    float prevSampleValue=0;
    if (sync) readIndex=trigIndex;                               // sync: start reading from last trig position
    if (!sync) readIndex=writeIndex;                             // no sync: start reading from last sample we received
    readIndex+=offset;
    float lineIncr=(float)1/samplesPerLine;
    if (lineIncr<1) lineIncr=1;
    for (float line=0; line<windowWidth; line+=lineIncr) {                                          // cycle screen lines (from right to left)
      float sampleValue=(float)getValueFromBuffer((int)((float)readIndex-line*samplesPerLine));     // get the value for the screen line
      sampleValue*=(float)windowHeight/1024;                                                        // scale to windowHeight
      if (line>0)
        line(windowX+windowWidth-line, 
          windowY+windowHeight-prevSampleValue, 
          windowX+windowWidth-line-lineIncr, 
          windowY+windowHeight-sampleValue);
      prevSampleValue=sampleValue;
    }

    // ------------------------------
    // BUFFER INCOMING BYTES & TRIG 
    // ------------------------------
    if (hold) {
      serial.clear();                                                                                      // HOLD: don't receive samples; clear serial buffer
    } else {
      while (serial.available ()>0) {                                                                      // RUN: receive samples
        writeIndex++;  
        if (writeIndex>=bufferSize) writeIndex=0;                                                          // handle overflow
        buffer[writeIndex+writeIndex]=(byte)serial.read();                                                 // add 1 sample (2 bytes) to buffer 
        buffer[writeIndex+writeIndex+1]=(byte)serial.read();
        trigLevel=trigLevel*(1-trigLevelLPF)+(float)getValueFromBuffer(writeIndex)*trigLevelLPF;           // level for trigger intersection = exponential moving average of voltage
        if (getValueFromBuffer(writeIndex)>=trigLevel&& getValueFromBuffer(writeIndex-1)<trigLevel) {      // rising intersect detected
          trigIndex=writeIndex;                                                                            // set trigIndex (index of buffer that corresponds to last trig)
          trigCounter++;                                                                                   // count trigs (to calculate the frequency)
        }
      }
    }
  }
}


// Read value from buffer
int getValueFromBuffer(int index) {                                                                      
  while (index<0) index+=bufferSize;                                                                       // handle overflow of circular buffer
  return((buffer[index*2]&3)<<8 | buffer[index*2+1]&0xff);                                                 // convert bytes to int
}

// Handle keys
void keyPressed() {  
  if (key == CODED) {
    if (connected) {
      if (keyCode == KeyEvent.VK_F1) {
        samplesPerLine*=1.1;
        if (samplesPerLine*windowWidth>bufferSize) samplesPerLine=bufferSize/windowWidth;
      }
      if (keyCode == KeyEvent.VK_F2) {
        samplesPerLine/=1.1;
        if (samplesPerLine<1/(float)windowWidth) samplesPerLine=1/(float)windowWidth;
      }
      if (keyCode == KeyEvent.VK_F3) {
        sync=!sync;
      }
      if (keyCode == KeyEvent.VK_F4) {
        hold=!hold;
      }
      if (keyCode == KeyEvent.VK_F5) {
        if (trigLevelLPF<.01) trigLevelLPF*=10;
      }
      if (keyCode == KeyEvent.VK_F6) {
        if (trigLevelLPF>.000001) trigLevelLPF/=10;
      }
      if (keyCode == KeyEvent.VK_F7) {
        if (prescaler>3) prescaler--;
        serial.write((byte)prescaler);
      }
      if (keyCode == KeyEvent.VK_F8) {
        if (prescaler<7) prescaler++;
        serial.write((byte)prescaler);
      }
      if (keyCode == LEFT) {
        offset-=samplesPerLine*20;
        if (offset<-bufferSize) offset=-bufferSize;
      }
      if (keyCode == RIGHT) {
        offset+=samplesPerLine*20;
        if (offset>0) offset=0;
      }
    } else {
      serial = new Serial(this, Serial.list()[keyCode-112], serialBaudRate);
      serial.write((byte)prescaler);
      connected=true;
    }
  }
}
processing code 2C#
import processing.serial.*;
import java.awt.event.KeyEvent;

// Settings --------------------------------------------------------------------------
int serialBaudRate=115200;
int bufferSize=10000000;      // number of samples (memory use: 2 bytes per sample)
float samplesPerLine=1;       // increment 1 screen line for each [n] samples
boolean sync=true;            // sync to trig
float trigLevelLPF=.0001;     // trigLevel lowpass filter (exponential moving average)
boolean hold=false;           // pause
int prescaler=5;              // ADC prescaler: 3:8 | 4:16 | 5:32 | 6:64 | 7:128
// -----------------------------------------------------------------------------------

Serial serial;
PFont font= createFont("Lucida Console", 12, false);
byte[] buffer= new byte[bufferSize*2];
int writeIndex, prevWriteIndex, readIndex, trigIndex, trigCounter, loopCounter, windowWidth, windowHeight, offset;
float voltageRange, sps, frequency, trigLevel, timer;
int windowX=30;
int windowY=50;
boolean connected=false;
String serialPort;

void setup() {
  size(1000, 600);
  background(0, 0, 0);
  frame.setResizable(true);
  frameRate(50);    
  textFont(font);
  text("ARDUINO OSCILLOSCOPE\n\nSelect serial port:", 25, 25);
  for (int i=0;i<Serial.list().length;i++)
    text("F"+(i+1)+" - "+Serial.list()[i], 25, 80+i*20);
}

void draw() {
  windowWidth=width-100;
  windowHeight=height-100;

  if (connected) {
    background(0, 0, 0);
    
    // update frequency counters every second
    loopCounter++;
    if (loopCounter>frameRate) {  
      loopCounter=0;
      float elapsedSeconds=(millis()-timer)/1000;
      timer=millis();
      sps=(writeIndex-prevWriteIndex)/elapsedSeconds;  // sample rate
      if (sps<0)sps+=bufferSize;
      prevWriteIndex=writeIndex;
      frequency=trigCounter/elapsedSeconds;            // signal frequency
      trigCounter=0;
    }

    // read switch position & set voltage range
    boolean switch1=((buffer[writeIndex*2]&(byte)8)==8);                                                 
    boolean switch2=((buffer[writeIndex*2]&(byte)4)==4);
    if (!switch1&&!switch2) voltageRange=20;
    if (!switch1&&switch2) voltageRange=10;
    if (switch1&&!switch2) voltageRange=6.64;
    if (switch1&&switch2) voltageRange=5;

    // print voltage scale
    for (int n=0; n<=10; n++)                                                                           
      text(nf(voltageRange/10*n, 2, 2)+"V", windowX+windowWidth+5, windowY+windowHeight-(n*windowHeight/10));

    // print interface and statistics
    text("[F1-F2] ZOOM | [F3] SYNC: "+sync+" | [F4] HOLD: "+hold+" | [F5-F6] TRIG LPF | [F7-F8] PRESCALER: "+nf((pow(2, prescaler)), 1, 0)+" | [<--->] OFFSET", 25, 25);
    text("frequency: "+nf(frequency, 5, 2)+"Hz"
      +" | average DCV: "+nf(trigLevel/1024*voltageRange, 2, 2)+"V"
      +" | samplerate: "+nf(sps, 5, 2)+"Hz"
      +" | samples per line: "+samplesPerLine
      +" | division: "+samplesPerLine/sps*(float)width*100+"ms", 25, height-20);

    // draw trigLevel (= average voltage) line
    stroke(0, 0, 100);                                                                                   
    int trigLevelHeight=(int)(trigLevel*(float)windowHeight/1024);
    line(windowX, windowY+windowHeight-trigLevelHeight, windowX+windowWidth, windowY+windowHeight-trigLevelHeight);  

    // draw grid
    stroke(50);                                                                                           
    for (float n=0; n<=windowWidth; n+=(float)windowWidth/10) 
      line(n+windowX, windowY, n+windowX, windowHeight+windowY); 
    for (float n=0; n<=windowHeight; n+=(float)windowHeight/10) 
      line(windowX, n+windowY, windowX+windowWidth, n+windowY);

    // ------------------------------
    // DRAW WAVEFORM
    // ------------------------------
    stroke(255);
    float prevSampleValue=0;
    if (sync) readIndex=trigIndex;                               // sync: start reading from last trig position
    if (!sync) readIndex=writeIndex;                             // no sync: start reading from last sample we received
    readIndex+=offset;
    float lineIncr=(float)1/samplesPerLine;
    if (lineIncr<1) lineIncr=1;
    for (float line=0; line<windowWidth; line+=lineIncr) {                                          // cycle screen lines (from right to left)
      float sampleValue=(float)getValueFromBuffer((int)((float)readIndex-line*samplesPerLine));     // get the value for the screen line
      sampleValue*=(float)windowHeight/1024;                                                        // scale to windowHeight
      if (line>0)
        line(windowX+windowWidth-line, 
        windowY+windowHeight-prevSampleValue, 
        windowX+windowWidth-line-lineIncr, 
        windowY+windowHeight-sampleValue);
      prevSampleValue=sampleValue;
    }

    // ------------------------------
    // BUFFER INCOMING BYTES & TRIG 
    // ------------------------------
    if (hold) {
      serial.clear();                                                                                      // HOLD: don't receive samples; clear serial buffer
    }
    else {
      while (serial.available ()>0) {                                                                      // RUN: receive samples
        writeIndex++;  
        if (writeIndex>=bufferSize) writeIndex=0;                                                          // handle overflow
        buffer[writeIndex+writeIndex]=(byte)serial.read();                                                 // add 1 sample (2 bytes) to buffer 
        buffer[writeIndex+writeIndex+1]=(byte)serial.read();
        trigLevel=trigLevel*(1-trigLevelLPF)+(float)getValueFromBuffer(writeIndex)*trigLevelLPF;           // level for trigger intersection = exponential moving average of voltage
        if (getValueFromBuffer(writeIndex)>=trigLevel&& getValueFromBuffer(writeIndex-1)<trigLevel) {      // rising intersect detected
          trigIndex=writeIndex;                                                                            // set trigIndex (index of buffer that corresponds to last trig)
          trigCounter++;                                                                                   // count trigs (to calculate the frequency)
        }
      }
    }
  }
}

// Read value from buffer
int getValueFromBuffer(int index) {                                                                      
  while (index<0) index+=bufferSize;                                                                       // handle overflow of circular buffer
  return((buffer[index*2]&3)<<8 | buffer[index*2+1]&0xff);                                                 // convert bytes to int
}

// Handle keys
void keyPressed() {  
  if (key == CODED) {
    if (connected) {
      if (keyCode == KeyEvent.VK_F1) {
        samplesPerLine*=1.1;
        if (samplesPerLine*windowWidth>bufferSize) samplesPerLine=bufferSize/windowWidth;
      }
      if (keyCode == KeyEvent.VK_F2) {
        samplesPerLine/=1.1;
        if (samplesPerLine<1/(float)windowWidth) samplesPerLine=1/(float)windowWidth;
      }
      if (keyCode == KeyEvent.VK_F3) {
        sync=!sync;
      }
      if (keyCode == KeyEvent.VK_F4) {
        hold=!hold;
      }
      if (keyCode == KeyEvent.VK_F5) {
        if (trigLevelLPF<.01) trigLevelLPF*=10;
      }
      if (keyCode == KeyEvent.VK_F6) {
        if (trigLevelLPF>.000001) trigLevelLPF/=10;
      }
      if (keyCode == KeyEvent.VK_F7) {
        if (prescaler>3) prescaler--;
        serial.write((byte)prescaler);
      }
      if (keyCode == KeyEvent.VK_F8) {
        if (prescaler<7) prescaler++;
        serial.write((byte)prescaler);
      }
      if (keyCode == LEFT) {
        offset-=samplesPerLine*20;
        if (offset<-bufferSize) offset=-bufferSize;
      }
      if (keyCode == RIGHT) {
        offset+=samplesPerLine*20;
        if (offset>0) offset=0;
      }
    }
    else {
      serial = new Serial(this, Serial.list()[keyCode-112], serialBaudRate);
      serial.write((byte)prescaler);
      connected=true;
    }
  }
}
code C#
#define bytesPerPackage 32
#define switch1 4               //sw1 pin
#define switch2 3               //sw2 pin

uint8_t bytesRead;
byte inputBuffer[bytesPerPackage];
byte outputBuffer[bytesPerPackage];
boolean sw1, sw2;

void setup() {
  pinMode(switch1, INPUT);
  digitalWrite(switch1, HIGH);  // enable internal pullup
  pinMode(switch2, INPUT);
  digitalWrite(switch2, HIGH);
  ADMUX =  B00000000;           // set external aref and port
  ADCSRA = B10101101;           // ADC enable, interrupt enable, set default prescaler
  ADCSRB = B00000000;           // free running mode
  sei();		        // enable interrupts
  ADCSRA |=B01000000;           // start first conversion
  Serial.begin(115200);
}

void loop() {
  sw1 = digitalRead(switch1);
  sw2 = digitalRead(switch2);
  if (bytesRead >= bytesPerPackage) {  // Buffer full. Send the package.
    cli();
    bytesRead = 0;
    for (uint8_t i = 0; i < bytesPerPackage; i += 2) {
      byte adch = inputBuffer[i];
      if (!sw1) adch |= B00001000;      // switch position in bits 10 & 11 of each byte pair 
      if (!sw2) adch |= B00000100;
      outputBuffer[i] = adch;
      outputBuffer[i+1] = inputBuffer[i+1];
    }
    sei();
    Serial.write(outputBuffer, bytesPerPackage);
  }
  
  if (Serial.available()) {                // incoming byte -> set prescaler
    byte inByte = (byte)Serial.read();
    cli();
    ADCSRA= B10101000|(inByte&B00000111);  // last 3 bytes of ADCSRA set the prescaler
    sei();
    ADCSRA |=B01000000;                    // start first conversion
  }
}

ISR(ADC_vect) {
  if(bytesRead<bytesPerPackage-1){
    inputBuffer[bytesRead+1] = ADCL;
    inputBuffer[bytesRead] = ADCH;
    bytesRead+=2;
  }
}

Schematics

Circiot
F38lalvikfruns1 0dr21lljui

Comments

Similar projects you might like

DIY Universal CNC Machine

Project tutorial by Arduino “having11” Guy

  • 15,990 views
  • 5 comments
  • 69 respects

Arduino - Web Oscilloscope (Support Trigger)

Project tutorial by phpoc_man

  • 6,360 views
  • 13 comments
  • 52 respects

DIY Relay Outlet Arduino

Project tutorial by Ben Jones

  • 25,672 views
  • 18 comments
  • 33 respects

Arduino Oscilloscope (6-Channel)

by Meeker6751

  • 23,768 views
  • 12 comments
  • 93 respects

DIY Smart Cane Using Arduino

Project tutorial by suhail jr

  • 10,444 views
  • 1 comment
  • 13 respects
Add projectSign up / Login