Draw anything on an oscilloscope

2013-09-21 10.15.01b

I have an old analog oscilloscope I got from university surplus, and it’s been more than enough for all the work I’ve done so far.

One thing I’ve wanted to do for a while was draw arbitrary stuff on it using X/Y mode, recreating the vector graphics systems of the past, especially stuff like the seminal Tennis for Two.  Unfortunately, a plain Arduino can’t output true analog signals–the analogWrite function just sets pulse-width modulation (PWM) so the pin is up x% of the time rather than truly being x% of the voltage.  So I needed a digital-to-analog converter (DAC).

I was originally going to build a R-2R DAC out of resistors, but that eats a ton of pins, so I wouldn’t be able to scale down to a smaller controller later if I wanted.

Instead, I decided on getting a dedicated serial DAC, which is an IC that receives the voltage level via serial digital communication, then outputs it.  Originally I tried to get a super-cheap audio DAC, the TDA1543, but the protocol for that is weird, and I got a non-standard “japanese interface” variant, and also I may have fried it.  Anyway, it didn’t work, so I got the much more standard MCP4802.

The MCP4802 is from the MCP48xx line (datasheet), with the model number indicating it has 8 bits of resolution and two independent channels.  It communicates over plain SPI, a well known standard with hardware support on Arduino.  I found a ready-made library for it here. (I had to fix one thing in it — it didn’t include SPI.h in its own header file. I also renamed CHANNEL_A/B to CH_A/B for brevity.)

I wired it as described here, and it worked perfectly.  With this, I hooked up the X and Y lines to the two outputs of the DAC, and set about drawing some stuff.  After getting it to draw a circle (x=cos(theta), y=sin(theta), scale output to 0..4095), I decided to draw some arbitrary stuff.

To encode the art, I wrote a small Processing app to let me mouse-draw some stuff, with the coordinates being saved to a file in CSV format.  Code:

PrintWriter output;

void setup()
{
  size(256,256);
  output = createWriter("positions.txt");
}

// x0=-1 means "the next click is the start of a new line segment"
int x0=-1,y0=-1;
int x,y;

void draw() {}

void mouseDragged() {
  x = mouseX;
  y = mouseY;
  output.println(String.format("%d,%d",x,(256-y)));
  output.flush();
  if (x0>=0) {
    line(x0,y0,x,y);
  }
  x0=x;
  y0=y;
}

void mouseReleased() {
  x0=-1;
}

So now I can draw stuff:

rad

I wrote a small Python app to convert the CSV list of coordinates to array declarations for Arduino. (I could have done this formatting in Processing directly, but that would have involved arrays and a save function, and I’m not very familiar with Processing yet, so this was faster.)  This code also drops every other coordinate to improve the refresh rate.

import fileinput

X=[]
Y=[]

every_n = 2 # we throw away every other pixel to improve the refresh rate

i=0
for line in fileinput.input():
	i += 1
	if i%every_n != 0: continue
	x,y = line.strip().split(",")
	X.append(x)
	Y.append(y)

print "byte X[] = {%s};" % ','.join(X)
print "byte Y[] = {%s};" % ','.join(Y)

I run it with:

  $ python pos2code.py positions.txt > rad.txt

I then dropped those two lines into the top of my Arduino program:

// MCPDAC relies on SPI.
#include
#include
#include

int i=0;

byte X[] = {...INSERT VALUES HERE...};
byte Y[] = {...INSERT VALUES HERE...};

void setup() {
  MCPDAC.begin(10,9); // CS pin, LDAC pin (latter only needded for x/y sync)

  MCPDAC.setGain(CH_A,HIGH); // Set the gain to "HIGH" mode - 0 to 4096mV.
  MCPDAC.setGain(CH_B,HIGH); // Set the gain to "HIGH" mode - 0 to 4096mV.

  MCPDAC.shutdown(CH_A,false);
  MCPDAC.shutdown(CH_B,false);
}

void loop() {
  int x,y;

  x = X[i]*16; // *16 to go from 0..255 to 0..4095
  y = Y[i]*16;

  MCPDAC.setVoltage(CH_A,x&0x0fff);
  MCPDAC.setVoltage(CH_B,y&0x0fff);
  MCPDAC.update(); // this toggles LDAC, actually changing the outputs -- you can omit this if not using the LDAC pin (leaving it grounded)

  i++;
  if (i>=sizeof(X)) i=0; // wrap around the array

}

The program cycles through the values, sending them to the MCP4802, thus driving the oscilloscope, which was set to XY mode.  Now I can play with the code above to start trying out some effects, like this “crawling ant” pattern:

My setup:

2013-09-21 09.43.28c

2 Replies to “Draw anything on an oscilloscope”

  1. Dear Tyler
    first of all i would like to congratulate you for your work. I am very interested to learn how to ”draw anything on an oscilloscope”. I am not familiar with Arduino and so i have not quite understood what you did.

    Would you be able to explain your setup in more detail please? – starting by a simple block diagram maybe!

    Do you think you can achieve non-traditional shapes on the oscilloscope by the use of discrete electronics rather than a programmable microcontroller kit?

    thanks
    Sarah

    • Basically, in XY mode, two analog voltages tells the oscilloscope what X and Y offset to light up. If you trace a curve in XY coordinates, it can form the shape of your choosing. To set these analog voltages, I used an digital-to-analog (DAC) converter (the MCP4802 IC). The connectivity is actually straightforward. The way I hook up the MCP4802 is in the Kerry Wong blog I linked, and the only other connection is from the outputs of that IC to the X and Y inputs of the oscilloscope.

      You can draw things with discrete electronics. In fact, see this video:

      https://www.youtube.com/watch?v=Gwo3pEH7hUE

      It shows an “octopus circuit”, which is a fancy name for a circuit that uses an oscilloscope to draw voltage/current curves. In a setup like this, a capacitor makes a circular or oval shape.

      That said, it’s not clear to me how you’d use this technique to get arbitrary shapes…that’s why I used the microcontroller approach. Getting crazy arbitrary shapes out of an oscilloscope using discrete components is not something I’m familiar with.

      Best of luck!

Leave a Reply to Sarah Pule Cancel reply

Your email address will not be published. Required fields are marked *

*