Arduin-O-Scope v01
I put together a little oscilloscope based on an arduino duemilanove as a first project to learn about how the system works. There's plenty of polishing to be done, but it mostly works and runs at about 5kHz with the default setting.
It uses a counter interrupt as a time base and just uses the top 8 bits of the ADC to give 8-bit resolution. The Arduino reads analog input 0 every time the interrupt fires and sends the value over USB to a program running in Processing. The Processing code scans the incoming data to see when it passes through a trigger level. Once the trigger level is detected it starts saving the incoming data and then plots it to the screen. If too much time goes by it just plots what data it has so you get a 'live' view of what is going on at the input pin. Clicking anywhere on the screen sets the trigger level to the mouse location.
The Arduino code contains one special line, "#define TEST_MODE". If you leave this line as-is the Arduino ignores the analog input and sends a mathematically generated ramp wave which is nice for debugging the Processing code. Comment this line out and you will get the analog input pin values instead.
The Processing code has a few debugging items too. A white circle in the upper left-hand corner blinks when the frame updates. If this isn't blinking something is hanging in the code. There is another green circle that appears if the frame was started by a trigger event, and a red circle appears if the frame was started without a trigger event. A horizontal purple line shows where the trigger level is.
Welp, it's good enough for now and I'm sick of reading datasheets. Thanks to these forums and the Arduino team for all the great help!
e.
Known Bugs
-
The frames in Processing sometimes do not update smoothly. I think the root of this problem is that the loops that read the serial port are too short and run the CPU to 100% constantly polling for new input. Eventually the CPU wanders off to handle its other tasks and the screen hangs for a minute. There is likely a better way to do this polling.
-
Values of 5V and 0V input can run the scope trace off the screen. This is kind of annoying, but just needs a better scaling method.
To Do
-
It would be nice to have a pre-trigger, just like on a real oscilloscope, that would let you see a bit of the data that came in before the trigger event.
-
I think the sampling rate can still be faster. Maybe running the ADC on self-triggering mode and fixing the choking issue with the Processing code would help. If anyone can point me to a good guide on setting the ADC to free-running mode I'd greatly appreciate it.
-
It would be cool to add the ability for Processing to change the sampling rate from the UI by sending messages to the Arduino.
-
Add a scale to the UI for both voltage and time steps.
Arduino Code
#include <avr/io.h>
#include <avr/interrupt.h>
#define BAUD_RATE 115200
#define INPUT_PIN 0
#define LED_PIN 13
//#define TEST_MODE // comment out to read analog pin, uncomment for test ramp wave
volatile int j;
void setup()
{
Serial.begin(BAUD_RATE);
pinMode(LED_PIN, OUTPUT);
cli(); // disable interrupts while messing with their settings
TCCR1A = 0x00; // clear default timer settings, this kills the millis() funciton
TCCR1B = 0x00;
TCCR1B |= (1 << WGM12); // Configure timer 1 for CTC mode
TCCR1B |= (0 << CS12); // Set timer prescaling by setting 3 bits
TCCR1B |= (1 << CS11); // 001=1, 010=8, 011=64, 101=1024
TCCR1B |= (1 << CS10);
TIMSK1 |= (1 << OCIE1A); // Enable CTC interrupt
OCR1A = 50; // Set CTC compare value
sei(); // turn interrupts back on
}
void loop() {
// nothing to do, its all in the interrupt handler!
}
ISR(TIMER1_COMPA_vect) // when timer counts down it fires this interrupt
{
#ifdef TEST_MODE
Serial.print((j%64)*4 , BYTE); // test mode, generate a ramp wave
j++;
#else
Serial.print( analogRead(INPUT_PIN), BYTE); // real mode, sample analog pin
#endif
}
Processing Code
import processing.serial.*;
Serial inPort; // the port to read from
int BAUD_RATE = 115200; // set baud rate here, needs to be the same value as in the arduino code
int BUFFER_SIZE=200; // data buffer size
int GRIDS=10; // number of grids to draw
int inVal; // y-data read in from the arduino
int lastVal; // old value of y-data
int[] yVals = new int[BUFFER_SIZE]; // y-data buffer, scaled to the screen size
int[] xVals = new int[BUFFER_SIZE]; // x-data buffer, scaled to the screen size
int trigger; // trigger, when the incoming data passes this value a frame starts
int timeOut; // if no trigger is detected by timeOut samples, plot what is at input port
int i; // counter
boolean blinker; // blinks a light on each frame update so you know when program is running
boolean noTrigger; // true until trigger is detected
boolean noTimeOut; // true until timeout runs out if no trigger is found first
void setup()
{
inPort = new Serial(this, Serial.list()[0], BAUD_RATE);
size(600, 400);
background(0);
stroke(255);
trigger=100;
timeOut=2*BUFFER_SIZE;
}
void draw()
{
// dump any old data sent while program wasn't running or was busy
inPort.clear();
// wait for trigger event or timeout
noTrigger=true;
noTimeOut=true;
lastVal=1023;
i=0;
while(noTrigger & noTimeOut){
if (inPort.available()>1){ // wait for a byte to appear on serial port
inVal=(inPort.read()); // read the byte
if((inVal>trigger)&(lastVal<=trigger)) noTrigger=false; // check for trigger event
lastVal=inVal;
i++;
if (i>timeOut) noTimeOut=false; // check for timeout event
}
else{
delay(1);
}
}
// collect a frame of data
i=0;
while(i<BUFFER_SIZE){ // read a buffer full of date
if (inPort.available()>1){
inVal=( inPort.read());
yVals[i]=height-((height)*inVal)/254; // scale data to screen height
xVals[i]=(width*i)/BUFFER_SIZE; // scale x-value to screen width
i++;
}
else{
delay(1);
}
}
// draw grid lines
background(0);
stroke(0,64,0);
for(i=1;i<GRIDS;i++){
line((width*i)/GRIDS,0,(width*i)/GRIDS,height);
line(0,(height*i)/GRIDS,width,(height*i)/GRIDS);
}
// draw trigger level
stroke(128,0,128);
line(0,height-(height*trigger)/254,width,height-(height*trigger)/254);
// draw scope trace
stroke(255,255,0);
for (i=1;i<BUFFER_SIZE;i++){
line(xVals[i-1],yVals[i-1],xVals[i],yVals[i]);
}
// draw a dot that changes state each screen update
// if this isnt blinking, something is wrong
stroke(255);
if (blinker) ellipse(10,10,5,5);
blinker=!blinker;
// draw a green dot if trigger event fired frame
if (!noTrigger){
stroke(0,255,0);
ellipse(20,10,5,5);
}
// draw a red dot if timeout event fired frame
if (!noTimeOut){
stroke(255,0,0);
ellipse(30,10,5,5);
}
// this delay seems to be needed to let the system handle random events
delay(50);
}
// move the trigger level to wherever the user clicks the mouse
void mousePressed() {
trigger=(height-mouseY)*255/height;
}