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.
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.
| Attachment | Size |
|---|---|
| Original Processing code | 1.88 KB |
| Arduino code as an include file | 2.92 KB |
| Processing code with zoom option | 2.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:
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:
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:
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:
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() { Tools...Create Font. the "now" value.fontA = loadFont("CourierNewPSMT-48.vlw"); // "fontB" is a 14 pt font of some sort. It's what we use to showthe min and max values.fontB = loadFont("CourierNewPSMT-14.vlw"); // I wouldn't change the size if I were you. There are somefunctions that don't use Arduino can keep up with. doesn't work at all, rate to 19.2k or lower. that familiar it's better left off. }int getY(int val) {
} 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 hasall 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. 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 xvalue. (fades out the end of the line) to the next value in the array. where I wanted them without that offsets a little, too. 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)); 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)); 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)); 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)); -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)); -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)); } 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 insteadof the right.stroke(rr,gg,bb,255-kick); } else { stroke(rr,gg,bb); }}/* This is the Arduino Code:
void setup() {
}void loop() {
}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() { } void draw() { }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!
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.