Languages

Poorman's oscilloscope (with Arduino + Processing)

This software allows you to get a visual representation of an analog signal using Arduino and Processing. The resolution is 10 bits, which is good, but the frequency is a lot lower than that of a real oscilloscope but it is still pretty useful.

It works by sending values read from the Arduino board (pin 0) to Processing through serial communication.

Alternative Arduino oscilloscope projects

Nice! This project has been referenced in the book Practical Arduino by Jonathan Oxer and Hugh Blemings.

Ingredients

Instructions

  1. Upload the Arduino code to your board (the code is included at the end of the .pde in attachment to this post)
  2. Preferably close the Arduino program (I think it can cause interferences during serial reading)
  3. Run the Processing code
  4. The program "with zoom" (below) gives the extra option of "zooming" using the "+" and "-" keys

Scope anything you want

Sometimes, you don't want to just scope the analog input: you want to scope a modified version of it, for instance. In that case, you can use include the oscilloscope.h file and send any value you want to the Processing program by using the writeOscilloscope(int) function.

AttachmentSize
Original Processing code1.88 KB
Arduino code as an include file2.92 KB
Processing code with zoom option2.64 KB

Comments

A solution without Java

For a different implementation, which runs at almost 3000 samples per second, see lxardoscope, from http://lxardoscope.sourceforge.net/.

Features:

  • display modes: two channels, time/add/xy
  • vertical: 2mV to 10V per division
  • overall gain control for calibration
  • horizontal: 100us to 5 sec, for full sweep
  • trace position adjustments: vertical and horizontal
  • trigger: on/off, channel 1 or channel2, rising or falling edge
  • trigger level: -10 to +10V
  • signal level measurements: max, min, pp, avg, rms
  • signal levels and time shown for mouse pointer location selected on display
  • up to 3000 samples per second, per channel
  • option for recording input data stream to file
  • option to display recorded data from file
  • timebase calibration adapts to Arduino's conversion speed
  • GND calibration allows for selecting arbitrary GND potential

Niiiice!

Niiiice!

Adjustable timebase

Excellent piece of SW! :-) I'm trying to figure out how to make the timebase (or sec/div as it's known on regular oscilloscopes, I want to be able to "stretch" the trace) adjustable, any suggestions? I don't mind hardcoding it (i.e. it doesn't have to be adjustable from the application), i just can't figure out how to go about it... Bit of a noob when it comes to Processing (and coding in general..) :-)

Zooming version

I don't know if this is what you are looking for but I just added a version of the Processing code that allows "zooming" using "+" and "-". It's attached to the post.

Teensy Port?

Just wondering if there is some other dev board that might do this better for about the same price...maybe a Teensy 2.0?

Absolutely. Never tried it

Absolutely. Never tried it but should work fine using the Teensyduino add-on. Keep us posted if you try it.

Works just fine!

I picked up Practical Arduino and this project caught my eye. It works great with the Teensy 2.0 and Teensyduino also the Teensy 2.0 has 11 analogue pins.

Real scopes are only 8 bits

"The resolution is 10 bits so this is not like a real oscilloscope"

Sure it is. Scopes are only 8 bits. They just use low-noise, variable-gain circuitry to measure a huge range of values.

It's the very low sampling frequency that makes this a "poor man's" scope. Real scopes go up to the MHz.

An other possibility

Hi, i realy enjoy what you did with Arduino. But making a "poor mans's scope "can be done without it by just using one Input of your sound card. By doing so you should theorically (depend of the quality of your sound card) have at least 20Hz to 20KHz bandwidth which i admit is still a "poor man's scope but definitively a cheaper solution since the only hardware needed is the cables ...

You are right, and there are

You are right, and there are a few tutorials on how to do this online. The Arduino oscilloscope has the advantage (and the disadvantage) that you can see the real values that you get from the analogRead() call, which is useful when you create Arduino apps.

Thanks

Thanks! I just made a change in the description.

I have 1 black screen

Hi, sorry asking newbie qn. But i am currently trying to measure the oscillation of a Fluoresecent Lamp running on 220V AC power. I've used a voltage divider setup to step down the voltage to less then 5V. However, is it advisable to measure a 220V source?

Moreover, i hadnt connect the AC to my board circuit yet, and i am getting a plain dark screen from Sketc_nova when i run the codes. Is that suppose to happen?

AC...

You can't simply step down AC with a voltage divider.

If that's all you did then you will have killed your arduino board due to the 115ish volts that went through it 50 or so times a second.

If you only get a black

If you only get a black screen, processing does not find the right serial port.
Try following:

void setup() 
{
  size(640, 480);
    
  println(Serial.list());    //<--- add this line
    
  port = new Serial(this, Serial.list()[0], 9600); //(text below) change number in [ ]
  values = new int[width];
  smooth();
}

Then look into the console output of processing. You should see a list with all availible COM ports. E. g. my Arduino was at " [2] COM3 ". Next you have to change the number [0] to the number of your Arduino's COM port (in my case to [2] ). Then it works.

----

The idea of this oscilloscope is really nice!!! But the speed is not perfect ;) . Another thing to a multichannel oscilloscope: there is only ONE ADC in the ATmega and all analogue inputs are multiplexed with this one ADC. So if you need higher sample rates do NOT read from more than one channel. Maybe I will write a small article about tweaking the analogue reading for oscilloscope applications.

Only get a black screen

I changed the COM port as you suggested and the program works properly but I still ony get a black screen. does anyone have a suggestion of what could possibly be the problem.

Try to see if you get

Try to see if you get anything on the serial with a simple program such as:

void setup()
{
  size(640, 480);

  port = new Serial(this, Serial.list()[0], 9600); // change number in [ ] depending on which COM
  values = new int[width];
  smooth();
}

void draw() {
  if (Serial.available() > 0)
    println(Serial.read());
  else
    println("Nothing on the serial port");
}

If you get only an output like:

Nothing on the serial port
Nothing on the serial port
Nothing on the serial port
Nothing on the serial port
Nothing on the serial port

it means there's a problem with the serial. Try different COM in that case.

CUIduino?

I'm wondering, could the sample rate be improved via USB? Using the CUI and HID drivers, mapping analog inputs to joystick X/Y/Z/Throttle etc.... I'm curious.... Or is it simply a limitation of the arduino's processing/sampling power?

Mitul from Technology Blog

You have a nice blog … I liked your blog very much. The contents are worth reading and I’ve found much valuable information from your post. My learning curve is increasing :-). Thanks for sharing such a nice post with us. Keep it up.

Screenshot

Here's my screenshot: http://arduinoscope.googlecode.com/files/Screenshot-Oscilliscope.png

See this: http://code.google.com/p/arduinoscope/wiki/Usage

If you 'd like info about configuring or changing it to do other things.

Re: hi

i have used your code,i have placed the controlip5 in the right folder also, but i get error as insufficient memory,wen i change it to 2GB in preferences, i get error as memory too high,finally i set it to 1.2 GB,where i get no error but i dont see the output

kindly help

Will not compile

Is there any reason why when I compile the Arduino code it gives me, "In function 'void setup()': error: redefinition of 'void setup()' In function 'void loop()':"?

Careful

Careful, the .pde code is for Processing and will not compile. You should simply cut/paste the commented (Arduino) code at the end and put it in an Arduino file.

Using the basic code i can only get very low frequency signals

Using the basic code i can only get very low frequency signals. I used a function generator on analog pin 0 starting at 1kHz with a square wave with an amplitude of 2 volts and the oscilloscope was unable to process this i had to drop it to 10hz before i seen the waveform. Seen as you have down extensive work on this code why doesnt it work at 1kHz and what is the reason for it running at only very low frequencies?

Low Sample Rate

If you look at the code at all, you can see that the Arduino is transferring across RS232 at 9600 baud. The code uses two bytes per sample to carry the 10 bits of A/D precision. RS232 (typically) takes 10 bits on the wire to transmit 8 bits of data.

9600/10 gives us the byte rate of the serial channel: 960 bytes per second 960/2 gives us the sample rate: 480 samples per second 480/2 gives us the absolute maximum bandwidth of the system, or 240Hz. (This also assumes that the peaks of the measured signal align nicely with the sample rate.)

The ATmega chips have rather limited sample rates as well. They're not designed for this sort of work. They use a "successive approximation" type A/D that samples one bit at a time. At 10 bits of resolution, ATmel lists 15k samples per second as the maximum sample rate. This gives an absolute upper limit of 7.5kHz bandwidth at that resolution. That sample rate doesn't actually appear to allow time for actual code to run to DO anything with that data. A more realistic limit for the is about 3.8kHz.

The A/D converters onboard the ATmega chips are designed for low rate sampling, such as a few samples per second of a given voltage or temperature.

Faster sampling

Sampling takes 13 clock cycles, so the sampling rate is 1/13 of the ADC clock. "For optimum performance, the ADC clock should not exceed 200 kHz. However, frequencies up to 1 MHz do not reduce the ADC resolution significantly. ... Operating the ADC with frequencies greater than 1 MHz is not characterized."

A 1 MHz ADC clock would then be a 77 kHz sampling frequency, no? http://www.atmel.com/dyn/resources/prod_documents/DOC2559.PDF

My version

I made a version that is easier to work with in any GUI (IMHO) and an example app that reads 6 analog pins.

Here are some features:

  logic analyzer mode that shows 1's and 0's clearly.
  pause frame
  save frame
  configurable pin-count
  use as many pins as will fit on screen (tested with 12 at 800x800, seems ok)
  shows volts, based on scaling settings
I set it up to be useful as a logic analyzer for digital pins, or oscilloscope for analog.

http://code.google.com/p/arduinoscope/

I'd like to add more features like extended frame logging (continuous) and detecting bytes in logic probe view, like SPI/i2C. I'd also like to add a parallel mode, for reading 8-pins at a time into a single byte.

picture of 6 channel version

In case you're wondering what it looks like, this is a screen capture.

http://www.mittsonline.com/6_ch.gif

6 channel version

//Have fun.

import processing.serial.*;

Serial port; // Create object from Serial class int valA; int valB; int valC; int valD; int valE; int valF; // this should have been some kind of 2-diminsional array, I guess, but it works. int[] valuesA; int[] valuesB; int[] valuesC; int[] valuesD; int[] valuesE; int[] valuesF;

PFont fontA; PFont fontB;

void setup() {

 // make sure you have these fonts made for Processing. Use
Tools...Create Font.
 // "fontA" is a 48 pt font of some sort. It's what we use to show
the "now" value.
 fontA = loadFont("CourierNewPSMT-48.vlw");

 // "fontB" is a 14 pt font of some sort. It's what we use to show
the min and max values.
 fontB = loadFont("CourierNewPSMT-14.vlw");

 // I wouldn't change the size if I were you. There are some
functions that don't use
 // the actual sizes to figure out where to put things. Sorry about that.
 size(550, 600);

 // Open the port that the board is connected to and use the same speed
 // anything faster than 38.4k seems faster than the ADC on the
Arduino can keep up with.
 // So, if you want it to be smooth, keep it at or below 38400. 28800
doesn't work at all,
 // I do not know why. If you turn on smooth() you need to drop the
rate to 19.2k or lower.
 // You will probably have to adjust Serial.list()[1] to get your serial port.
 port = new Serial(this, Serial.list()[1], 38400);

 // These are 6 arrays for the 6 analog input channels.
 // I'm sure it could have just as well been a 2d array, but I'm not
that familiar
 // with Processing yet and this was the easy way out.
 valuesA = new int[width-150];
 valuesB = new int[width-150];
 valuesC = new int[width-150];
 valuesD = new int[width-150];
 valuesE = new int[width-150];
 valuesF = new int[width-150];
 // the -150 gives us room on the side for our text values.

 // this turns on anti-aliasing. max bps is about 19.2k.
 // uncomment out the next line to turn it on. Personally, I think
it's better left off.
 //smooth();
}

int getY(int val) {

 // I added -40 to this line to keep the lines from overlapping, to
 // keep the values within their gray boxes.
 return (int)(val / 1023.0f * (height-40)) - 1;
}

void draw() {

 String decoder = "";
 while (port.available() >= 3)
 {
     // read serial until we get to an "A".
     decoder = port.readStringUntil(65);
 }
 // sanity check. make sure the string we got from the Arduino has
all the values inside.
 if ((decoder.indexOf("B")>=1) & (decoder.indexOf("C")>=1) &
(decoder.indexOf("D")>=1) & (decoder.indexOf("E")>=1) & (decoder.indexOf("F")>=1))
 {
   // decoder string doesn't contain an A at the beginning. it's at the end.
   valA=int(decoder.substring(0,decoder.indexOf("B")));
   //println("A" + str(valA));
   valB=int(decoder.substring(decoder.indexOf("B")+1,decoder.indexOf("C")));
   //println("B" + str(valB));
   valC=int(decoder.substring(decoder.indexOf("C")+1,decoder.indexOf("D")));
   //println("C" + str(valC));
   valD=int(decoder.substring(decoder.indexOf("D")+1,decoder.indexOf("E")));
   //println("D" + str(valD));
   valE=int(decoder.substring(decoder.indexOf("E")+1,decoder.indexOf("F")));
   //println("E" + str(valE));
   valF=int(decoder.substring(decoder.indexOf("F")+1,decoder.indexOf("A")));
   //println("F" + str(valF));
 }

 //shift the new values into the array, move everything else over by one
 for (int i=0; i<width-151; i++) {
   valuesA[i] = valuesA[i+1];
   valuesB[i] = valuesB[i+1];
   valuesC[i] = valuesC[i+1];
   valuesD[i] = valuesD[i+1];
   valuesE[i] = valuesE[i+1];
   valuesF[i] = valuesF[i+1];
 }
// -151 because the array is 151 less than the width. remember we // saved room on the side of the screen for the actual text values.
 valuesA[width-151] = valA;
 valuesB[width-151] = valB;
 valuesC[width-151] = valC;
 valuesD[width-151] = valD;
 valuesE[width-151] = valE;
 valuesF[width-151] = valF;

 background(0);

 textFont(fontA);

 // I'm sure these c/should have been determined using height math,
but I don't have the time really.
 // Draw out the now values with the big font.
 text(valA + 1, (width-140), 108-5);
 text(valB + 1, (width-140), 206-5);
 text(valC + 1, (width-140), 304-5);
 text(valD + 1, (width-140), 402-5);
 text(valE + 1, (width-140), 500-5);
 text(valF + 1, (width-140), 598-5);

 textFont(fontB);
 // Draw out the min and max values with the small font.
 // the h value (30,128,266,etc) is a function of height,
 // but I didn't bother to actually do the math.
 // I guess it's (98*n)+30 where n is 0,1,2,3,4,5, but I don't know
 // exactly how height (600) relates to 98... ((h/6)-2??)
 drawdata("0", width-90,  30, valuesA);
 drawdata("1", width-90, 128, valuesB);
 drawdata("2", width-90, 226, valuesC);
 drawdata("3", width-90, 324, valuesD);
 drawdata("4", width-90, 422, valuesE);
 drawdata("5", width-90, 520, valuesF);

 for (int x=150; x<width-1; x++) {
   // next line adjusts the color of the stroke depending on the x
value. (fades out the end of the line)
   check(x,255,0,0);

   // next line draws the line needed to get this value in the array
to the next value in the array.
   // the offsets (6+ in the next line) were used to get the values
where I wanted them without
   // having to actually do real spacial math. There's a hack in getY
that offsets a little, too.
   line((width)-x,
6+((height/6)*0)+((height-1-getY(valuesA[x-150]))/6), (width)-1-x, 6+((height/6)*0)+((height-1-getY(valuesA[x-149]))/6));
   check(x,0,255,0);
   line((width)-x,
4+((height/6)*1)+((height-1-getY(valuesB[x-150]))/6), (width)-1-x, 4+((height/6)*1)+((height-1-getY(valuesB[x-149]))/6));
   check(x,0,0,255);
   line((width)-x,
2+((height/6)*2)+((height-1-getY(valuesC[x-150]))/6), (width)-1-x, 2+((height/6)*2)+((height-1-getY(valuesC[x-149]))/6));
   check(x,255,255,0);
   line((width)-x,
0+((height/6)*3)+((height-1-getY(valuesD[x-150]))/6), (width)-1-x, 0+((height/6)*3)+((height-1-getY(valuesD[x-149]))/6));
   check(x,0,255,255);
   line((width)-x,
-2+((height/6)*4)+((height-1-getY(valuesE[x-150]))/6), (width)-1-x, -2+((height/6)*4)+((height-1-getY(valuesE[x-149]))/6));
   check(x,255,0,255);
   line((width)-x,
-4+((height/6)*5)+((height-1-getY(valuesF[x-150]))/6), (width)-1-x, -4+((height/6)*5)+((height-1-getY(valuesF[x-149]))/6));
 }

 // draw the boxes in gray.
   stroke(170,170,170);

 // these 5 lines divide the 6 inputs
   line(0,108,width-1,108);
   line(0,206,width-1,206);
   line(0,304,width-1,304);
   line(0,402,width-1,402);
   line(0,500,width-1,500);

 // these four lines make up the outer box
   line(      0,        0, width-1,        0);  // along the top
   line(width-1,        0, width-1, height-1);  // down the right
   line(width-1, height-1,       0, height-1);  // along the bottom
   line(      0, height-1,       0,        0);  // up the left
}

void drawdata(String pin, int w, int h, int[] values) {

 text("pin: " + pin, w, h);
 text("min: " + str(min(values) + 1), w, h + 14);
 text("max: " + str(max(values) + 1), w, h + 28);
}

void check(int xx, int rr, int gg, int bb) {

 // floating point operations in Processing are expensive.
 // only do the math for the float (fading out effect) if
 // we have to. You can change 170 to 160 if you want it to
 // fade faster, but be sure to change the other 170 to 160
 // and the 20 to 10.
 // (20 is the difference between 170 and 150)
 if (xx<=170)
 {
   float kick = (parseFloat(170-xx)/20)*255;
   // invert kick so the brighter parts are on the left side instead
of the right.
   stroke(rr,gg,bb,255-kick);
 }
 else
 {
   stroke(rr,gg,bb);
 }
}

/* This is the Arduino Code:

  1. define ANALOGA_IN 0
  2. define ANALOGB_IN 1
  3. define ANALOGC_IN 2
  4. define ANALOGD_IN 3
  5. define ANALOGE_IN 4
  6. define ANALOGF_IN 5

void setup() {

 Serial.begin(38400);
}

void loop() {

 int val[5];

 val[0] = analogRead(ANALOGA_IN);
 val[1] = analogRead(ANALOGB_IN);
 val[2] = analogRead(ANALOGC_IN);
 val[3] = analogRead(ANALOGD_IN);
 val[4] = analogRead(ANALOGE_IN);
 val[5] = analogRead(ANALOGF_IN);

 Serial.print( "A" );
 Serial.print( val[0] );
 Serial.print( "B" );
 Serial.print( val[1] );
 Serial.print( "C" );
 Serial.print( val[2] );
 Serial.print( "D" );
 Serial.print( val[3] );
 Serial.print( "E" );
 Serial.print( val[4] );
 Serial.print( "F" );
 Serial.print( val[5] );
}

  • /

Wow

Thanks very much. Is a great job

My Version:

import processing.serial.*;

int HEIGHT = 1024; int WIDTH = 800; boolean PAUSE = false; int numtraces = 6; int DIV = 8; int[] values; int val; int mode1 = 1; //numtraces = sovrapposte, 1 = distanziate int mode2 = 1; //0 = sovrapposte, 1 = distanziate

Serial port; // Create object from Serial class trace_container t; gui m_gui; boolean locked = false; boolean FMODE = false; int FFTsamples = 64; /* ----------------------------------------------GUI---------------------------------------
 */
class Button {

  int x, y;
  int size;
  color basecolor, highlightcolor;
  color currentcolor;
  boolean over = false;
  boolean pressed = false;   

  void update() 
  {
    if(over()) {
      currentcolor = highlightcolor;
    } 
    else {
      currentcolor = basecolor;
    }
  }

  boolean pressed() 
  {
    if(over) {
      locked = true;
      return true;
    } 
    else {
      locked = false;
      return false;
    }
  }

  boolean over() 
  { 
    return true;
  }

  boolean overRect(int x, int y, int width, int height) 
  {
    if (mouseX >= x && mouseX <= x+width && 
      mouseY >= y && mouseY <= y+height) {
      return true;
    } 
    else {
      return false;
    }
  }

  boolean overCircle(int x, int y, int diameter) 
  {
    float disX = x - mouseX;
    float disY = y - mouseY;
    if(sqrt(sq(disX) + sq(disY)) < diameter/2 ) {
      return true;
    } 
    else {
      return false;
    }
  }
}

class CircleButton extends Button {

  PFont myFont;
  String label;
  CircleButton(String l,int ix, int iy, int isize, color icolor, color ihighlight) 
  {
    myFont = createFont("DejaVu Serif", 22);
    label = new String(l);
    x = ix;
    y = iy;
    size = isize;
    basecolor = icolor;
    highlightcolor = ihighlight;
    currentcolor = basecolor;
  }

  boolean over() 
  {
    if( overCircle(x, y, size) ) {
      over = true;
      return true;
    } 
    else {
      over = false;
      return false;
    }
  }

  void display() 
  {
    stroke(255);
    fill(currentcolor);
    ellipse(x, y, size*2, size);
    stroke(0);
    textFont(myFont);
    strokeWeight(2);
    textAlign(CENTER);
    fill(0, 102, 153);
    text(label, x, y);
  }
}

class RectButton extends Button {

  RectButton(int ix, int iy, int isize, color icolor, color ihighlight) 
  {
    x = ix;
    y = iy;
    size = isize;
    basecolor = icolor;
    highlightcolor = ihighlight;
    currentcolor = basecolor;
  }

  boolean over() 
  {
    if( overRect(x, y, size, size) ) {
      over = true;
      return true;
    } 
    else {
      over = false;
      return false;
    }
  }

  void display() 
  {
    stroke(255);
    fill(currentcolor);
    rect(x, y, size, size);
  }
}

class HScrollbar {

  int swidth, sheight;    // width and height of bar
  int xpos, ypos;         // x and y position of bar
  float spos, newspos;    // x position of slider
  int sposMin, sposMax;   // max and min values of slider
  int loose;              // how loose/heavy
  boolean over;           // is the mouse over the slider?
  boolean locked;
  float ratio;
  int[] FFTmodes = {
    4,8,16,64,128,256,512
  };

  HScrollbar (int xp, int yp, int sw, int sh, int l) {
    swidth = sw;
    sheight = sh;
    int widthtoheight = sw - sh;
    ratio = (float)sw / (float)widthtoheight;
    xpos = xp;
    ypos = yp-sheight/2;
    spos = xpos + swidth/2 - sheight/2;
    newspos = spos;
    sposMin = xpos;
    sposMax = xpos + swidth - sheight;
    loose = l;
  }

  void update() {
    if(over()) {
      over = true;
    } 
    else {
      over = false;
    }
    if(mousePressed && over) {
      locked = true;
    }
    if(!mousePressed) {
      locked = false;
    }
    if(locked) {
      newspos = constrain(mouseX-sheight/2, sposMin, sposMax);
    }
    if(abs(newspos - spos) > 1) {
      spos = spos + (newspos-spos)/loose;
      DIV =   constrain ( round( (spos/swidth)*16 ), 1, 16 );  
      for (int i = 0; i< 7; i++) {
        if (FFTmodes[i] < width/DIV) {
          FFTsamples = FFTmodes[i];
          //println(FFTsamples);
        }
      }
    }
  }

  int constrain(int val, int minv, int maxv) {
    return min(max(val, minv), maxv);
  }

  boolean over() {
    if(mouseX > xpos && mouseX < xpos+swidth &&
      mouseY > ypos && mouseY < ypos+sheight) {
      return true;
    } 
    else {
      return false;
    }
  }

  void display() {
    fill(255);
    rect(xpos, ypos, swidth, sheight);
    if(over || locked) {
      fill(153, 102, 0);
    } 
    else {
      fill(102, 102, 102);
    }
    rect(spos, ypos, sheight, sheight);
    text("Resolution "+DIV, width - 80, 50);
  }

  float getPos() {
    // Convert spos to be values between
    // 0 and the total width of the scrollbar
    return spos * ratio;
  }
}

class gui {

  HScrollbar hs1;
  CircleButton changeMode, stop, pause,FFT;
  color currentcolor;
  gui() {
    // Define and create circle button
    color buttoncolor = color(204);
    color highlight = color(153);
    ellipseMode(CENTER);
    stop = new CircleButton( "Stop",width - 50, height -100,50, buttoncolor, highlight);
    // Define and create circle button
    changeMode = new CircleButton( "Mode",width - 50, height -200,50, buttoncolor, highlight);
    pause = new CircleButton( "Pause",width - 50, height -300,50, buttoncolor, highlight);
    FFT = new CircleButton( "FFT",width - 50, height -400,50, buttoncolor, highlight);
    hs1 = new HScrollbar(0,10, width, 10, 1);
  }
  void update(int x, int y)
  {
    if(locked == false) {
      changeMode.update();
      pause.update();
      stop.update();
      hs1.update();
      FFT.update();
    } 
    else {
      locked = false;
    }

    if(mousePressed) {
      if(changeMode.pressed()) {
        if ( (mode2 == 1)&&(mode1==1) ) {
          mode1 = numtraces;
          mode2 = 0;
        } 
        else {
          mode1 = 1;
          mode2 = 1;
        }
      } 
      else if(stop.pressed()) {
        exit();
      } 
      else if(pause.pressed()) {
        if (PAUSE)
          PAUSE = false;
        else
          PAUSE = true;
      }
      else if(FFT.pressed()) {
        if (FMODE)
          FMODE = false;
        else
          FMODE = true;
      }
    }
  }
  void display() {
    background(0);
    update(mouseX, mouseY);
    stop.display();
    changeMode.display();
    pause.display();
    hs1.display();
    FFT.display();
  }
}

/* ----------------------------------------------FFT------------------------------------------

 */
public class Complex {
  private final double re;   // the real part
  private final double im;   // the imaginary part

  // create a new object with the given real and imaginary parts
  public Complex(double real, double imag) {
    re = real;
    im = imag;
  }

  // return a string representation of the invoking Complex object
  public String toString() {
    if (im == 0) return re + "";
    if (re == 0) return im + "i";
    if (im <  0) return re + " - " + (-im) + "i";
    return re + " + " + im + "i";
  }

  // return abs/modulus/magnitude and angle/phase/argument
  public double abs() { 
    return Math.hypot(re, im);
  }  // Math.sqrt(re*re + im*im)
  public double phase() { 
    return Math.atan2(im, re);
  }  // between -pi and pi

  // return a new Complex object whose value is (this + b)
  public Complex plus(Complex b) {
    Complex a = this;             // invoking object
    double real = a.re + b.re;
    double imag = a.im + b.im;
    return new Complex(real, imag);
  }

  // return a new Complex object whose value is (this - b)
  public Complex minus(Complex b) {
    Complex a = this;
    double real = a.re - b.re;
    double imag = a.im - b.im;
    return new Complex(real, imag);
  }

  // return a new Complex object whose value is (this * b)
  public Complex times(Complex b) {
    Complex a = this;
    double real = a.re * b.re - a.im * b.im;
    double imag = a.re * b.im + a.im * b.re;
    return new Complex(real, imag);
  }

  // scalar multiplication
  // return a new object whose value is (this * alpha)
  public Complex times(double alpha) {
    return new Complex(alpha * re, alpha * im);
  }

  // return a new Complex object whose value is the conjugate of this
  public Complex conjugate() {  
    return new Complex(re, -im);
  }

  // return a new Complex object whose value is the reciprocal of this
  public Complex reciprocal() {
    double scale = re*re + im*im;
    return new Complex(re / scale, -im / scale);
  }

  // return the real or imaginary part
  public double re() { 
    return re;
  }
  public double im() { 
    return im;
  }

  // return a / b
  public Complex divides(Complex b) {
    Complex a = this;
    return a.times(b.reciprocal());
  }

  // return a new Complex object whose value is the complex exponential of this
  public Complex exp() {
    return new Complex(Math.exp(re) * Math.cos(im), Math.exp(re) * Math.sin(im));
  }

  // return a new Complex object whose value is the complex sine of this
  public Complex sin() {
    return new Complex(Math.sin(re) * Math.cosh(im), Math.cos(re) * Math.sinh(im));
  }

  // return a new Complex object whose value is the complex cosine of this
  public Complex cos() {
    return new Complex(Math.cos(re) * Math.cosh(im), -Math.sin(re) * Math.sinh(im));
  }

  // return a new Complex object whose value is the complex tangent of this
  public Complex tan() {
    return sin().divides(cos());
  }
}

public class FFT {

  Complex[] buffer;
  int[] spectrum;
  FFT() {
    spectrum = new int[256];
  }
  int[] computeFFT( circularBuffer samples ) {
    buffer = new Complex[FFTsamples];
    for (int i=0;i<FFTsamples;i++) {
      //println( samples[width/DIV-1 - FFTsamples +i] );
      buffer[i] = new Complex((double) samples.get(width/DIV-1 - FFTsamples +i),(double) 0);
    }
    buffer= fft( buffer );
    for (int i=0;i<FFTsamples/2;i++) {
      spectrum[i] = round( (float)buffer[i].abs() );
    }
    for (int i=0;i<FFTsamples/2;i++) {
      spectrum[i] = round(spectrum[i]/(float)max(spectrum)*(height-20));
    }
    return spectrum;
  }

  // compute the FFT of x[], assuming its length is a power of 2
  public Complex[] fft(Complex[] x) {
    int N = x.length;

    // base case
    if (N == 1) return new Complex[] { 
      x[0]
    };

    // radix 2 Cooley-Tukey FFT
    if (N % 2 != 0) { 
      throw new RuntimeException("N is not a power of 2");
    }

    // fft of even terms
    Complex[] even = new Complex[N/2];
    for (int k = 0; k < N/2; k++) {
      even[k] = x[2*k];
    }
    Complex[] q = fft(even);

    // fft of odd terms
    Complex[] odd  = even;  // reuse the array
    for (int k = 0; k < N/2; k++) {
      odd[k] = x[2*k + 1];
    }
    Complex[] r = fft(odd);

    // combine
    Complex[] y = new Complex[N];
    for (int k = 0; k < N/2; k++) {
      double kth = -2 * k * Math.PI / N;
      Complex wk = new Complex(Math.cos(kth), Math.sin(kth));
      y[k]       = q[k].plus(wk.times(r[k]));
      y[k + N/2] = q[k].minus(wk.times(r[k]));
    }
    return y;
  }
}

/* ----------------------------------------------Sampling---------------------------------------

 */
class timerscale extends trace {
  private long last;
  private long ctime;
  private int  val;
  timerscale(int s) {
    super(s,0);
    last = 0;
    ctime = 0;
    val = 0;
  }
  public void stat() {
  };
  public void labels() {
  };
  private void add() {
    ctime = millis();
    if ( (ctime-last) > 1000 ) {
      if (  (val>0)&&( (ctime-last) < 2000 ) )
        val = 0;
      else
        val = 1024/height * 10;
      last = ctime;
    }
    super.add(val);
  }
  int getY(int val) {
    return (int)(val / 1023.0f* height + 1);
  }
}

class trace_container {

  PFont myFont1, myFont2;
  trace[] traces;
  timerscale t;
  long last;
  float mean;
  private void FFTgui() {
    //freq = fs * (0 : N/2) / N;
    float fs = 1/mean*1000;
    float s = FFTsamples/2;
    textAlign(LEFT);
    textFont(myFont2);
    text("0 Hz", 0,height-10);
    textAlign(CENTER);
    text(round((fs * s/4) / FFTsamples) + "Hz", (width/4),height - 10);
    text(round((fs * s/2) / FFTsamples) + "Hz", (2*width/4 ), height - 10);
    text(round((fs * 3*s/4) / FFTsamples) + "Hz", (3*width/4),height - 10);
    textAlign(RIGHT);
    text(round(fs * s / FFTsamples) + "Hz", width, height - 10);
  }
  trace_container() {
    mean = 0;
    last = millis();
    myFont1 = createFont("DejaVu Serif", 28);
    myFont2 = createFont("DejaVu Serif", 16);
    textFont(myFont1);
    strokeWeight(2);
    traces = new trace[numtraces];
    t =  new timerscale( 150);
    for (int i=0;i<numtraces;i++) {
      traces[i] = new trace( (int)(255/numtraces*i ),i);
    }
  }
  void mean_updt() {            //media ricorsiva
    long per = millis() - last;
    last += per;
    mean = mean + 0.004*( per - mean );
  }
  void showmean() {
    textFont(myFont2);
    text("Samplerate " + round(mean) + " ms", width - 150, height -5);
  }
  void plot() {
    while (port.available() >= (numtraces*2 + 1) ) {
      if (port.read() == 0xff) {
        mean_updt();
        for (int i=0;i<numtraces;i++) {
          int val =  (port.read() << 8) | (port.read());
          traces[i].add(  val  );
          //print("Read " + val  + " on " + i +"\t");
        }
        //println("\n");
      }
    }
    textFont(myFont1);
    textAlign(LEFT);
    for (int i=0;i<numtraces;i++) {
      traces[i].shift();
      traces[i].plot();
    }
    if(FMODE) {
      FFTgui();
      return;
    }
    t.add();
    t.shift();
    t.plot();
    showmean();
  }
}

class circularBuffer {

  private int[] values;
  private int it;
  circularBuffer(int size) {
    values = new int[width];
    it = 0;
  }
  int get(int i) {
    return (values[ (it+i)%(width/DIV) ]);
  }
  void add(int i) {
    values[ (it++)%(width/DIV) ]=i;
  }
  float mmax() {
    int s=0;
    for (int x=1; x<(width/DIV); x++) {
      if (get(x)>s)
        s=get(x);
    }
    //print ( values[x] + "  ");
    //println( min( values)  ) ;
    return s;
  }
  float mean() {
    float sum=0;
    for (int x=1; x<(width/DIV); x++) {
      sum+=  (float)get(x);
    }
    //print ( values[x] + "  ");
    //println( min( values)  ) ;
    return round (sum/(width/DIV -1));
  }
  float mmin() {
    int s=height;
    for (int x=1; x<(width/DIV); x++) {
      if (get(x)<s)
        s=get(x);
    }
    //print ( values[x] + "  ");
    //println( min( values)  ) ;
    return s;
  }
}

class trace {

  private FFT mFFT;
  RectButton pause;
  private circularBuffer values;
  //private int[] values;
  private int cval;
  private int m_stroke;
  private int m_shift;
  trace(int s, int sh) {
    mFFT = new FFT();
    m_shift = sh;
    m_stroke = s;
    cval = getY(0);
    //values = new int[width];
    values = new circularBuffer(width);
  }
  public void add(int i) {
    cval = i;
    //println(i);
  }
  private float  invY(float val) {
    return round ((constrain ( round ( ( val- (mode2*m_shift*( (height-12)/numtraces ) + 12 ) )/ (((height-12 - 2)/numtraces)*mode1 )*1023.0f ),0,1024))/1024.0f*5000);
  }
  public void labels() {
    stroke(255);
    strokeWeight(1);
    line(  width-1,   height-(m_shift*( (height-12)/numtraces ) + 12), 1, height-(m_shift*( (height-12)/numtraces ) + 12));
    strokeWeight(4);
    stroke(255, m_stroke, 255 - m_stroke);
    fill(255, m_stroke, 255 - m_stroke);
    text("Analog"+m_shift, 10, height-(m_shift*( (height-12)/numtraces ) + 12));
  }

  public void plot() {
    labels();
    if (FMODE) {
      int[] fft = mFFT.computeFFT(values);
      for (int x=0; x<FFTsamples/2; x++) {
        line(round(x/(float)FFTsamples*width*2), height - getY(0),round(x/(float)FFTsamples*width*2),   height - getY(fft[x]) );//x/FFTsamples*width*2
      }
    }
    else {
      for (int x=1; x<(width/DIV); x++) {
        line(width-(x-1)*DIV,   height-values.get(x-1), width-x*DIV, height-values.get(x));
      }
    }
    stat();
  }
  public void stat() {
    if (PAUSE) {
      text("Min: "+ invY( values.mmin() ) + "mV", width/4, height-(m_shift*( (height-12)/numtraces ) + 12));
      text("Max: "+ invY( values.mmax() ) + "mV", 2*width/4, height-(m_shift*( (height-12)/numtraces ) + 12));
      text("Mean: "+ invY( values.mean() ) + "mV", 3*width/4, height-(m_shift*( (height-12)/numtraces ) + 12));
    }
  }
  public void shift() {
    if (PAUSE) {
      return;
    }
    values.add( getY( cval ) );
    //    for (int i=0; i<(width/DIV-1); i++) {
    //      values[i] = values[i+1];
    //      values[width/DIV-1] = getY( cval );
    //    }
  }
  int getY(int val) {
    return (int)(val / 1023.0f * ( ((height-12 - 2)/numtraces)*mode1 ) + mode2*m_shift*( (height-12)/numtraces ) + 12  );
  }
  public void test() {
    println("Trace OK");
    println(width);
    println(height);
  }
}

/* ----------------------------------------------MAIN---------------------------------------
 */
void setup() {

  size(HEIGHT, WIDTH );
  t = new trace_container();
  m_gui = new gui();
  // Open the port that the board is connected to and use the same speed (9600 bps)
  //port = new Serial(this, Serial.list()[0], 9600);
  port = new Serial(this, Serial.list()[2], 115200);
  smooth();
}

void draw() {

  m_gui.display();
  t.plot();
}

Missing code

Hi,

Not sure whats happened but the code for 6 inputs is not fully listed, any chance you can send it on? ian.macdonald@aibh-ye.net

Thanks

Ian

Sorry, it is my fault. I put

Sorry, it is my fault. I put it back properly.

Is there a reason why the

Is there a reason why the bps rate is limited to 9600? Wouldn't it make more sense to go higher- say 115,200?

I tried doing it but somehow

I tried doing it but somehow the Processing app was "jamming". But it would totally make sense to do so, I just wasn't able to figure out the problem.

Max frequency?

Hi, This is cool. I guess that in this case the max input frequency is restricted by the serial output... Couldn't be possible to catch 1K values and send them? In this case... how many samples per second would be achievable? Anyone?

Resolution of the arduino's

Resolution of the arduino's ADC is 4.9 mV, worst-case sampling rate is approximately 10kHz (if not using serial communication at the same time).

hi, I'm very interested in

hi, I'm very interested in knowing how you calculated the worst case sampling rate. To me the only available spec (which is a bandwidth of 115200bps) really don't mean anything in real-world terms.

Interesting!

Posted and translated into Spanish for my Blog. Thank you! www.albinoblackcrow.es

Great! Muchas gracias!

Great! Muchas gracias!

displaying a grid with values

Hi there, I'm not very familiar with Processing.

Could I expand your sketch to see a grid or the peak values?!? Maybe some lines for the timbase would also be very helpful.

Thanks for your great sketch! And regards from Germany, jo

Works Great...Thanks!

This is a screen shot of your oscope displaying the output of a photo resistor aimed at an LED flashing a 1kHz.

Thanks for the work!

http://www.bnkk.net/images/o_scope.jpg

Code

Dear friend, Could you share your oscilloscope code (Arduino and Processing)? Thanks

Hi can u help me??

Hello!!!

Im making an osciloscope and i dont have any idea about how can make this in processing, and the real code in arduino, in other way the teacher ask me for make in the processing a zoom and for the scale of the signal, can u help me with this please, my email is andy483 ---NO_SPAM_AT--- hotmail ---DOT--- com.

As you'll see at the end of

As you'll see at the end of the Oscilloscope.pde code, there's the Arduino code.

BTW I'd suggest you don't write your email address on websites like this: spambots are crawling the web and you'll start to get tons of spam in your mailbox. I've changed it in your comment to make it spambot-safe.

// The Arduino code.

#define ANALOG_IN 0

void setup() {
  Serial.begin(9600); 
}

void loop() {
  int val = analogRead(ANALOG_IN);
  Serial.print( 0xff, BYTE);
  Serial.print( (val >> 8) & 0xff, BYTE);
  Serial.print( val & 0xff, BYTE);
}

Are you sure about that frequency?

1khz? How did you managed to get this scope running so fast? I'm barely getting one sample per second :(

There's probably a problem

There's probably a problem with you setup. Did you upload the exact same program? Try using a higher baudrate.

D.A Vajvod.

It'is wery fun! 1 KHz for Oscilloscope!!!

I seem that ADC (ex. AD 7810 -100 KHZ) + MCU is many better than built-in ADC.

Sorry, bad english)))

+2 pins?

Thanks for the code, it works great! I wanted to do some more measuring. How can I go about measuring (and displaying) voltage from more than just pin 0? For that matter, any other colors available on the display? I'm a little light on processing/wiring.

Excellent

Thanks for this, what a great addition to my desk!

Easty.

5V limit?

I am not an electronics guru, so bare with me. If I am correct, there is a 5V limit on the analog pin. It would be nice if maybe 20V could be used. Any idea with what components you could create some kind of reference voltage. To make this more clear is, if the max voltage that can be applied is 20V, then I would like to have this converted to 5 V, 18V is then 4.5V (18*5/20), 12V is then 3V.

Would be nice if this is doable.