Instructables

Arduino - Multi-Channel Oscilloscope (Poor Man's Oscilloscope)

I am presenting software upgrades for the Arduino Oscilloscope (Poor Man's Oscilloscope) that allow to visualize two or three data channels simultaneously. The main features are identical to the original oscilloscope with exception of a few lines that I added to improve the users ability to estimate what the actual measured voltages are. RuiSantos had found the original software in the net and shared this information in his instructable and on his website. Thank you very much RuiSantos for sharing this.
Note that this "device" is not an actual oscilloscope. Most of the features you'd expect from an oscilloscope are not available. However, if you like to visualize data that is not too fast, then this is simply awesome. For example I will be using it to check a data sequence that I am going to create with a couple of 555 timers. But that is another story.

Features of the software/oscilloscope:
1-channel (original and fastest), 2-channel (slower), or 3-channel (even slower) data acquisition and display
0-5V signal range determined by hardware, i.e. Arduino
Time scale zoom in/out by pressing +/-
Red line = channel1 data
Green line = channel2 data
Blue line = channel3 data
Grey lines = 0.5V steps
Dark yellow lines = 1V steps
Screenshot functionality: On mouse button click the program stores a screen shot of your data window. This essentially allows you to "stop" the data and look at it. Convenient for data analysis. To make this work you have to go into the program "draw ()" section and adjust the file path to one that works with your computer.

All you need is an Arduino and a USB cable.
 
Remove these adsRemove these ads by Signing Up
ssriphan4 months ago

Hi guy. Firstly, I would like to thanks for this procedures, I tried it, It's work very well.

But I have a little problems when I add more channels such as 3 or 4 or 5 and then I change a little bit code for support many channels. It's not appear from processing program, meaning analog data from sensors aren't shown in graph.

please help me. I don't know why? or program can support 3 channels only?

This is my processing code for 4 channels. It's run well but graph is blank.

*/

import processing.serial.*;

Serial myPort; /* Create object from Serial class */

char HEADER = 'H'; /* character to identify the start of a message */

short LF = 10; /* ASCII linefeed */

short portIndex = 1; /* determines the USB port used */

int[] val = {-1, -1, -1, -1}; /* Variable used for getY function, 3 positions for 3channels */

int[] valuesCh1; /* The next three variables will hold the data of the window in memory so that they can be pushed and displayed */

int[] valuesCh2;

int[] valuesCh3;

int[] valuesCh4;

float zoom; /* Define "zoom" as floating-point variable */

void setup()

{

//size(1014, 690); /* Opens a window of specific size, max size of my laptop screen, delay problems half through window */

size(600, 400); /* smaller screen => less to calculate => no delay problems */

myPort = new Serial(this, Serial.list()[portIndex], 9600); /* Open the port that the board is connected to and use the same speed (9600 bps) */

valuesCh1 = new int[width]; /* Define array with as many elements as x-pixel in window, used for plotting data of Channel1 */

valuesCh2 = new int[width]; /* ... of Channel2 */

valuesCh3 = new int[width]; /* ... of Channel3 */

valuesCh4 = new int[width];

zoom = 1.0f; /* Start with 1x zoom factor */

smooth(); /* Drawing images with smooth edges */

}

/* This function converts a channel data value into pixels shown from

* top of graph (which is the 0 position)

* this function returns an integer value */

int getY(int val)

{

return (int)(height - val / 1023.0f * (height - 1));

}

/* Function that reads the serial string data that were sent by arduino

* Puts out an array with the three values of the arduino

* Function is called in draw function */

int[] getData()

{

int[]ChValues = {-1, -1, -1, -1}; /* Define array for this function */

String message = myPort.readStringUntil(LF); /* Read in the serial data string sent by arduino */

if(message != null) /* Do this only when a complete valid message was received */

{

String [] data = message.split(","); /* Split the comma-separated message into it's segments */

if(data[0].charAt(0) == HEADER) /* Check for header character in the first field, always true for complete message */

{

for (int i = 1; i < data.length-1; i++) /* Skip the header and terminate cr and lf => look only at the three data points */

{

ChValues[i-1] = Integer.parseInt(data[i]); /* Write channel data into array, i shifted from data to array by 1 due to header */

}

}

}

return ChValues; /* Returns array ChValues which contains channel data */

}

/* This function pushes all the data points of the window one position further to the left, then it adds the data point that was just read */

void pushValue(int[] value)

{

for (int i=0; i<width-1; i++)

{

valuesCh1[i] = valuesCh1[i+1]; /* Move the data point over by on position */

valuesCh2[i] = valuesCh2[i+1];

valuesCh3[i] = valuesCh3[i+1];

valuesCh4[i] = valuesCh4[i+1];

}

valuesCh1[width-1] = value[0]; /* Add data point */

valuesCh2[width-1] = value[1];

valuesCh3[width-1] = value[2];

valuesCh4[width-1] = value[3];

}

/* This function draws the data into the window */

void drawLines()

{

int displayWidth = (int) (width / zoom); /* Calculates width of window, considering the x-change if a zoom is set */

int k = valuesCh1.length - displayWidth; /* This calculates position up to which the data points are shown */

int x0 = 0; /* x value at very left of window (=0) is assigned to x0 and used for all channels */

int ya0 = getY(valuesCh1[k]); /* y value of the last shown point is assigned to ya0 for channel1 */

int yb0 = getY(valuesCh2[k]); /* y value of the last shown point is assigned to yb0 for channel2 */

int yc0 = getY(valuesCh3[k]); /* y value of the last shown point is assigned to yc0 for channel3 */

int yd0 = getY(valuesCh4[k]);

for (int i=1; i<displayWidth-1; i++) /* Loop that runs from point k to the very right side of window */

{

k++; /* Increment k for next data point */

int x1 = (int) (i * (width-1) / (displayWidth-1)); /* Calculate next x value */

int ya1 = getY(valuesCh1[k]); /* Get next y-value for channel1 */

int yb1 = getY(valuesCh2[k]); /* Get next y-value for channel2 */

int yc1 = getY(valuesCh3[k]); /* Get next y-value for channel3 */

int yd1 = getY(valuesCh4[k]);

strokeWeight(2); /* Draw thicker lines */

stroke(255, 0, 0, 0); /* Draw a red line for channel1 */

line(x0, ya0, x1, ya1); /* Plot a line segment for channel1 */

stroke(0, 255, 0,0); /* Draw a green line for channel2 */

line(x0, yb0, x1, yb1); /* Plot a line segment for channel2 */

stroke(0, 0, 255, 0); /* Draw a blue line for channel3 */

line(x0, yc0, x1, yc1); /* Plot a line segment for channel3 */

stroke(0, 0, 0, 255); /* Draw a blue line for channel3 */

line(x0, yd0, x1, yd1); /*

x0 = x1; /* Shift x value to calculate next line segments */

ya0 = ya1; /* Shift y-value for channel1 to calculate next line segments */

yb0 = yb1; /* Shift y-value for channel2 to calculate next line segments */

yc0 = yc1; /* Shift y-value for channel3 to calculate next line segments */

yd0 = yd1;

}

}

/* This function draws grid lines into the window

* I spaced the lines so they represent 10% and 20% steps in 2 different colors

* For Signals of 5V max, that is 0.5 & 1.0V steps */

void drawGrid()

{

stroke(150, 150, 0);

line(0, height/5, width, height/5);

line(0, height*2/5, width, height*2/5);

line(0, height*3/5, width, height*3/5);

line(0, height*4/5, width, height*4/5);

stroke(150, 150, 150);

line(0, height/10, width, height/10);

line(0, height*3/10, width, height*3/10);

line(0, height*5/10, width, height*5/10);

line(0, height*7/10, width, height*7/10);

line(0, height*9/10, width, height*9/10);

}

/* This function allows to zoom in the x-axis of the data

* It runs in the background and notices when the right key is pressed

* Zoom in with pressing "+"

* Zoom out with pressing "-" */

void keyReleased()

{

switch (key)

{

case '+':

zoom *= 2.0f;

println(zoom);

if ( (int) (width / zoom) <= 1 )

zoom /= 2.0f;

break;

case '-':

zoom /= 2.0f;

if (zoom < 1.0f)

zoom *= 2.0f;

break;

}

}

/* This is the main function that calls the other functions

* This function runs continuously */

void draw()

{

background(1); /* Sets the background of the window */

drawGrid(); /* Draws the grid into the window */

val = getData(); /* Reads the data from the three Channels as sent by the arduino into an array */

if (val[0] != -1) /* If data is in first channel, then carry out function */

{

pushValue(val); /* Pushes data down one position and adds one new data point */

}

drawLines(); /* Add the next data set to the window */

if (mousePressed) /*perform action when mouse button is pressed */

{

save("/YourPathHere/OsciData1.png"); /* save screen shot of data window, but beware image will be overwritten with second mouse click */

}

}

...and this is my arduino code for 4 channels.

/* CommaDelimitedOutput sketch */

#define ANALOG_IN_Ch1 0

#define ANALOG_IN_Ch2 1

#define ANALOG_IN_Ch3 2

#define ANALOG_IN_Ch4 3

void setup()

{

Serial.begin(9600);

}

void loop()

{

int val1 = analogRead (ANALOG_IN_Ch1); /* Reads values from analog Pin0 = Channel1 */

int val2 = analogRead (ANALOG_IN_Ch2); /* Reads values from analog Pin1 = Channel2 */

int val3 = analogRead (ANALOG_IN_Ch3); /* Reads values from analog Pin2 = Channel3 */

int val4 = analogRead (ANALOG_IN_Ch4); /* Reads values from analog Pin3 = Channel4 */

Serial.print('H'); /* Unique header to identify start of message */

Serial.print(",");

Serial.print(val1,DEC);

Serial.print(",");

Serial.print(val2,DEC);

Serial.print(",");

Serial.print(val3,DEC);

Serial.print(","); /* Note that a comma is sent after the last field */

Serial.print(val4,DEC);

Serial.print(",");

Serial.println(); /* send a cr/lf */

delay(50);

}

txoof7 months ago

Thanks for the great instructable. I'm having some trouble with your Processing sketch though. I get the following error:

The function readStringUntil(short) does not exist.

I'm running processing v2.1 on OS X. I've managed to run other processing oscilloscope sketches using single outputs, but was intrigued by your multiple output version. Can you offer any suggestions?

Also, where can I set the serial port (2) in the sketch? I can't quite work that out.

Thanks for the help!

txoof txoof7 months ago

Wow! I just got everything working and this is fantastic. In the hour I've been playing with your sketches I've learned so much about how 555s work! Actually getting to see on a 'scope what's happening to the voltages is really instructive. Thanks very much for this post!

In other news: The problem I had is due to the fact that the function has been removed in the latest version. More details here: http://forum.processing.org/two/discussion/838/fun...

An older version of processing works great.

Superbender (author)  txoof7 months ago

Hi txoof,

Sorry about my delayed answer. I am really glad to hear that you enjoy working with the scope. I learned a lot when programming the extra channel versions and it makes my day to hear it that it is being used. Good input on the processing version / issue as well. I haven't done anything with processing with the exception of this scope. So good to know when I start the scope up again on my freshly installed computer.

Ubiqvit9 months ago
Hello everybody! I have arduino Mega ADK and decided to try out this project. Anyway, i tested it with function generator and I can nicely see slow (<10Hz) frequency signals. But when frequency is increase above 15 Hz signal becomes unreadable. It looks like some random(aliasing) signal. In the comments I read that you can read signals up to kHz. Any advice what could be wrong? Thank you for answer.
Superbender (author)  Ubiqvit9 months ago
Hi Ubigvit,
I never really hooked it up with a function generator. I typically look at pretty slow TTL type signals only with this. I'll put this on my list to do and will check it out in the next couple of weeks (or more realistic: sometime this year :) ). In any case, I would have guessed the speed to be in the range you are observing.
If you think about it, 10 -15 Hz doesn't really sound that bad considering that the data is being send via serial connection, which takes a significant amount of time. The speed will further depend on how many channels you actually were looking at. The one channel version should be faster than the two channel version, should be faster than the three channel version.
I found another option to increase the speed for my old computer is to reduce the window size. This will also increase the response time. To do that find line:
size(1280, 480); and change it to a smaller size.
Last not least if you like to look at kHz signals you might be better off trying some other oscilloscopes builds. There are a number out there that are working with the input into an audio card, if I remember correctly. For starters, check this list: http://www.instructables.com/id/DIY-Oscilloscopes/
I'll send an update when I get to check my system with a function generator.
WWC1 year ago
Hi
I am having trouble with your scope. I can get readings in the Arduino serial monitor but can not get any output on the Processing scope. I tried changing the port number to the port i am using but then it throws an error. I think i am not setting something correctly in the processing code, any hints.

Thanks
Wayne
Superbender (author)  WWC1 year ago
WWC,
Hard to come up with something without seeing the code.
If you can see the values on the serial monitor it seems to me that it has something to do with the processing script.
1. Did you check if a simple script runs in processing?
2. Did you get the single channel version of the scope to work for you? You had a post there that you had some difficulties with it. I'd suggest to confirm the single channel version is working and then we can work our way up to multi-channel.

Post or send me your code and I'll look at it.
Hi thanks for the reply.
Yes running in Processing. The scope screen will display but no wave forms will display.
Yes the single channel version i can't get to show in Processing also, although it does show in the serial monitor.
This is of-course the single channel mode sketch that i was trying.
Thanks

#define ANALOG_IN 0

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

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


/*
* Oscilloscope
* Gives a visual rendering of analog pin 0 in realtime.
*
* This project is part of Accrochages
* See http://accrochages.drone.ws
*
* (c) 2008 Sofian Audry (info@sofianaudry.com)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import processing.serial.*;

Serial port; // Create object from Serial class
int val; // Data received from the serial port
int[] values;
float zoom;

void setup()
{
size(1280, 480);
// Open the port that the board is connected to and use the same speed (9600 bps)
port = new Serial(this, Serial.list()[0], 9600);
values = new int[width];
zoom = 1.0f;
smooth();
}

int getY(int val) {
return (int)(height - val / 1023.0f * (height - 1));
}

int getValue() {
int value = -1;
while (port.available() >= 3) {
if (port.read() == 0xff) {
value = (port.read() << 8) | (port.read());
}
}
return value;
}

void pushValue(int value) {
for (int i=0; i values[i] = values[i+1];
values[width-1] = value;
}

void drawLines() {
stroke(255);

int displayWidth = (int) (width / zoom);

int k = values.length - displayWidth;

int x0 = 0;
int y0 = getY(values[k]);
for (int i=1; i k++;
int x1 = (int) (i * (width-1) / (displayWidth-1));
int y1 = getY(values[k]);
line(x0, y0, x1, y1);
x0 = x1;
y0 = y1;
}
}

void drawGrid() {
stroke(255, 0, 0);
line(0, height/2, width, height/2);
}

void keyReleased() {
switch (key) {
case '+':
zoom *= 2.0f;
println(zoom);
if ( (int) (width / zoom) <= 1 )
zoom /= 2.0f;
break;
case '-':
zoom /= 2.0f;
if (zoom < 1.0f)
zoom *= 2.0f;
break;
}
}

void draw()
{
background(0);
drawGrid();
val = getValue();
if (val != -1) {
pushValue(val);
}
drawLines();
}
Superbender (author)  WWC1 year ago
Hi WWC,
I tried a couple of things and got your code running on my machine. Here is my report. Hope that fixes it.

First I tried to run my one channel oszi scripts to confirm my setup works. => success.
Second I tried your arduino code with my processing code. => success
Third I tried your arduino code with your processing code. Three issues came up:
1)
The window was too large for my screen, so I made the window smaller. This should also increase the response time. To do that find line:
size(1280, 480);

change into:
size(640, 240); a couple of errors came up that I corrected as follows:

2) Seemed like a loop wasn't working. To correct it find this section:
void pushValue(int value) {
for (int i=0; i values[i] = values[i+1];
values[width-1] = value;
}

change into:
void pushValue(int value) {
for (int i=0; ivalues[i] = values[i+1];
values[width-1] = value;
}

3) Seemed like another loop wasn't working. To correct it find this section:
find this section:
int x0 = 0;
int y0 = getY(values[k]);
for (int i=1; i k++;
int x1 = (int) (i * (width-1) / (displayWidth-1));
int y1 = getY(values[k]);
line(x0, y0, x1, y1);
x0 = x1;
y0 = y1;

change into:
int x0 = 0;
int y0 = getY(values[k]);
for (int i=1; ik++;
int x1 = (int) (i * (width-1) / (displayWidth-1));
int y1 = getY(values[k]);
strokeWeight(2); /* Draw thicker lines */
stroke(255, 0, 0); /* Draw a red line for channel1 */
line(x0, y0, x1, y1);
x0 = x1;
y0 = y1;

After that it worked as it should. Looks to me that you may have been plotting either none or only one data point.
Check out the changes and let me know.
Superbender (author)  Superbender1 year ago
/*
Correction to the last reply. Seems like there was a copy / paste issue when I pasted the code. Don't know if that was a user error, my computer, or the website. In any case you'll notice that the codes under 2) and the codes under 3) are the same for the change to section. There are parts missing.
Let me try to write it out without using copy / paste;
New 2)
Find:
void pushValue(int value) {
for (int i=0; i values[i] = values[i+1];
values[width-1] = value;
}

Change into:
void pushValue(int value) {
for (int i=0; ivalues[i] = values[i+1];
values[width-1] = value;
}

New 3)
Find:
int x0 = 0;
int y0 = getY(values[k]);
for (int i=1; i k++;
int x1 = (int) (i * (width-1) / (displayWidth-1));
int y1 = getY(values[k]);
line(x0, y0, x1, y1);
x0 = x1;
y0 = y1;

change into:
int x0 = 0;
int y0 = getY(values[k]);
for (int i=1; ik++;
int x1 = (int) (i * (width-1) / (displayWidth-1));
int y1 = getY(values[k]);
strokeWeight(2); /* Draw thicker lines */
stroke(255, 0, 0); /* Draw a red line for channel1 */
line(x0, y0, x1, y1);
x0 = x1;

So I hope this time I did it right.
*/
y0 = y1;
Superbender (author)  Superbender1 year ago
Again the code was changed when I pressed the "post" button.
Very strange. I'll pm you and send you the code in a txt file.
Copy and past in this form rarely works as you have found out.
CraigHyatt1 year ago
Install the Windows 32 bit version of Processing 2.0.  The Windows 64 bit version says "serial does not work in 64 bit mode" when you run it.
CraigHyatt1 year ago
/* CommaDelimitedOutput sketch */
const int CHANS=3;
void setup() {
  Serial.begin(115200);
  // Increase ADC timebase to 1MHz
  bitClear(ADCSRA,ADPS0);
  bitClear(ADCSRA,ADPS1);
  bitSet(ADCSRA,ADPS2);
}

// Sends CSV from ports A0, A1, ... up to CHANS channels
// Readings 0..1023 -> 0..ADCREF (typically 5V or 3.3V)
void loop() {
  Serial.print("H,");
  for (int i=0; i<CHANS; i++) {
    Serial.print(analogRead(A0+i));
    Serial.print(",");
  }
  Serial.println();
  delay(50);
}
i used a.01 capacitor for the variable capacitor
I made a probe for it http://www.instructables.com/id/How-to-make-an-Arduino-oscilloscope-probe/
Superbender (author)  NeoRecasata1 year ago
Sweet. A probe is a definitely a good addition to the scope. I'll check out your instructable as soon as I find some time. Thanks for letting me know.