
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:

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:
