Want To Play Pong On Your Oscilloscope?

I always have! I don’t know why, but I like the idea of using an oscilloscope screen as a general use video display. Why not? In my case it sits on my desk full time, has a large screen area, can do multiple modes of display, and is very easy control.
Making an oscilloscope screen do your bidding is an old trick. There are numerous examples out there. Its not a finished project yet, so be nice. It is actually rather crude, using a couple parts I had on hand just on a whim. The code is a nice mixture of ArduincoreGCCish (I am sorry, still learning), and includes the following demos:

  1. Simple low resolution dot drawing
  2. A font example
  3. A very quickly and badly written demo of pong

The software runs on an Attiny84 micro controller clocked at 16Mhz, paired up with a Microchip MCP42100 dual 100k 8 bit digital potentiometer though the Attiny’s USI (Universal Serial Interface) pins. This is a fast, stable and accurate arrangement, but it requires sending 16 bits every time you want to change the value of one of the potentiometers so its also very piggy. I was just out to have some fun and did not have a proper 8 bit DAC. This was the closest thing outside of building one.
Join us after the break for pictures a (very) brief video and more.

This project has a total resolution of 256x256x1. This sounds like a lot of resolution but don’t get too excited. You can have only a few hundred to maybe 1000 pixels on screen before it starts flickering pretty badly. I am sure this can be solved by someone who is not using GCC commands for almost all of an Arduino script, furiously tying to shove 16 or 32 bits of data out of its SPI port PER PIXEL with an Attiny that has no dedicated SPI.

I had originally thought that I was going to do some form of raster scan display, much like a TV or computer monitor scanning a row of pixels one column at a time. You can see examples of this on electronixandmore in the projects section where the author converts RS170 television, and also VGA to an oscilloscope (along with a bunch of other cool stuff). Also take a look at this project that ran recently on Hackaday: NintendOscope.On my little 84 it ended up being extremely slow to scan each and every pixel and then modulating the Z axis on the back of the scope to change the pixels brightness.

Ok fine let’s keep it simple, how about some vector lines? I copied the site’s logo and quickly traced it out using the gimp using its [Web > Image Map] function. This spits out a file like this:

<img src="Untitled.bmp" width="125" height="120" border="0" usemap="#map" />
<map name="map">
<!-- #$-:Image map file created by GIMP Image Map plug-in -->
<!-- #$-:GIMP Image Map plug-in by Maurits Rijk -->
<!-- #$-:Please do not edit lines starting with "#$" -->
<!-- #$VERSION:2.3 -->
<!-- #$AUTHOR:kevin -->
<area shape="poly" coords="17,16,36,37,49,66,37,98,20,103,19,48" nohref="nohref" />

By drawing only points in that file I was able to produce the first “image” below. With a little refinement I produced a slightly cleaner result in the second image. As you can see you have to be very mindful where your little pointer is going because it leaves a trail. I was not really happy with the quality of these first try results, though its generally a very fast way to draw out wire-frame polygons (think Asteroids).

I ended up doing a mix. Instead of scanning every row and column, I only scan the rows and columns that have pixels to show. This produces a dot matrix image. I also needed an easy, but not epic, effort to convert images from computer to AVR. This was accomplished using the gimp, the XPM image format, and a little blob of lua.

Using the Hack a Day logo as an example, I roughly cropped out the skull and then used [Image >> Autocrop Image]. Next I went to [Image >> Canvas Size] and changed this to a 128×128 canvas. Finally I used [Image >> Flatten Image] with black set as the color background. The next step is to remove the black background. That’s easy enough with [Colors >> Color to Alpha].

I could display this as a solid raster but it would be way too slow to make out an image with this hardware and software. I need to remove most of the pixels. In order to do this, I use a quickie gimp cheat, go to [Filters >> Alpha to Logo >> Neon]. Now change the effect size to 5 and the glow color to white. Finally, delete the neon glow layer and flatten.



Since I am using a 1 bit per pixel color choice, I use [Image >> Mode >> Indexed], and choose 1 bit with no dithering. This produces a high quality outline of the logo, but it is still heavy on the pixels. In order to trim both ROM space and draw time I removed every other row and column by using [Filters >> Distorts >> Erase every other row]. I want it to fill with background which should be black. I did this once with rows and once with columns, even or odd is your choice, whichever looks better.

I dug in at 800% and touched up all the graphics with a 1 pixel paintbrush and saved as XPM format. XPM format is a basic text format where different characters represent a different pixel and color value. With only 1 bit per pixel you can quite clearly see the image in the text.

I removed the entire header, and so a lua script could phrase it I added “strings ={“ minus quotes so the file looks like:

strings = {

after the bracket and semi colon I added the following little blob of lua:

file = io.open("logo.out","w")
counter = 1;
for y = 1, #strings do
    for x = 1,#strings[y] do
        if string.sub(strings[y], x, x) == "+" then
            file:write(y .. ",".. x .. ",")
            counter = counter + 1

This scans through the data and makes a file that contains a single line of comma separated values of every white pixel of the graphic. It also spits out a number in the command line which is the number of pixels * 2 for number of XY values. You need to know the number of pixels if you want to draw any graphic since the program loops though the lua generated CSV file. It’s basic and big but it works well enough for the example.

If you have lua installed its generally “lua filename.ext”, sometimes “lua51 filename.ext”, minus the quotes.

AvrGCCiberishduino code is an Arduino sketch with void setup, void loop, and I used analog read because there was no advantage to implementing it myself. Arduino’s expandability to other models of AVR and simplified functionality does very good for many things. When you need to bring out the speed it has its issues.

Pretty soon you are twiddling port registers and looking into how to use the USI (universal serial interface) to send out SPI because it is faster than you can do it in software blah blah blah. Eventually I had an Arduino sketch tied to only the attiny84, though one would need to only change pins for other attiny’s like the 85 or 2313.
Atmegas, like what is on a normal Arduino would need different SPI code to function.)

// System
#include <avr/pgmspace.h>
#define cmdOne 0x11 // write to pot 0(X)
#define cmdTwo 0x12 // write to pot 1(Y)

// Graphics
prog_uchar hadlogo[] PROGMEM ={5,19,5,21,5,23,5,25,5,103,5,105,5,107,5,109,6,27,6,101,7,19,7,29,7,99,7,107,7,109,8,21,9,23,9,31,9,97,9,105,11,25,11,33,11,95,11,103,13,26,13,34,13,94,13,103,15,25,15,35,15,93,15,103,17,23,17,35,17,93,17,105,19,7,19,9,19,21,19,35,19,92,19,107,19,119,19,121,21,7,21,11,21,19,21,36,21,91,21,109,21,117,21,121,23,8,23,13,23,15,23,17,23,37,23,89,23,111,23,113,23,115,23,121,25,9,25,39,25,87,25,119,27,10,27,41,27,85,27,118,29,11,29,43,29,83,29,117,30,13,30,45,30,115,31,15,31,47,31,81,31,113,32,17,32,111,33,19,33,21,33,23,33,25,33,49,33,57,33,59,33,61,33,63,33,65,33,67,33,69,33,71,33,79,33,105,33,107,33,109,34,103,35,27,35,51,35,53,35,55,35,73,35,75,35,77,35,101,36,99,37,29,37,31,37,49,37,77,37,97,39,33,39,47,39,79,39,95,41,35,41,45,41,81,41,93,43,37,43,43,43,83,43,91,45,39,45,41,45,85,45,89,47,40,47,87,49,39,49,87,51,38,51,88,53,37,53,89,55,37,55,47,55,49,55,51,55,53,55,55,55,71,55,73,55,75,55,77,55,89,56,79,57,37,57,45,57,57,57,69,57,81,57,89,59,37,59,45,59,57,59,69,59,83,59,89,61,37,61,44,61,57,61,69,61,83,61,89,63,37,63,57,63,69,63,83,63,89,64,43,65,37,65,53,65,55,65,70,65,73,65,83,65,89,66,45,66,51,66,75,67,37,67,47,67,49,67,77,67,83,67,89,68,79,69,37,69,81,69,89,71,36,71,38,71,63,71,65,71,89,71,91,73,35,73,39,73,87,73,93,74,64,75,33,75,40,75,86,75,95,77,31,77,41,77,63,77,65,77,85,77,97,78,29,78,99,79,27,79,42,79,83,79,101,80,25,80,104,81,17,81,19,81,21,81,23,81,44,81,82,81,107,81,109,81,111,82,15,83,13,83,46,83,81,83,83,83,113,83,115,85,11,85,43,85,47,85,80,85,85,85,117,87,9,87,41,87,48,87,58,87,70,87,80,87,87,87,119,89,8,89,39,89,49,89,57,89,59,89,69,89,71,89,79,89,89,89,121,90,15,91,7,91,13,91,17,91,37,91,51,91,53,91,55,91,61,91,63,91,65,91,67,91,73,91,75,91,77,91,91,91,111,91,113,91,115,91,121,93,7,93,11,93,19,93,35,93,91,93,109,93,117,93,121,95,7,95,9,95,21,95,35,95,92,95,107,95,119,95,121,97,23,97,35,97,93,97,105,99,25,99,35,99,93,99,103,101,25,101,34,101,94,101,103,103,25,103,33,103,95,103,103,104,23,105,21,105,31,105,97,105,105,105,107,107,19,107,29,107,99,107,109,108,27,108,101,109,19,109,21,109,23,109,25,109,103,109,105,109,107,109,109};

// Functions
void qshift(byte input) // using the USI for SPI
USIDR = input; // put a byte into the register
USISR = _BV(USIOIF); // clear flag
while ( (USISR & _BV(USIOIF)) == 0 ) // send away
USICR = (1<<USIWM0)|(1<<USICS1)|(1<<USICLK)|(1<<USITC);
void latch(bool state) // toggle CS pin on digipot
if(state == 1) PORTA |= (1 << 3); // raise the latch
else PORTA &=~ (1 << 3); // lower the latch
void logoscrn() // draw the Hack A Day logo
for (int i = 0; i < 674; i += 2)
// 0,0 on an image is upper left, on scope its lower left
// subtract pixel Y value from 255 to flip right side up
latch(0); // lower the latch
qshift(cmdTwo); // send the write channel two command
qshift(255 - (64 + pgm_read_byte_near(hadlogo + i))); // send Y data
latch(1); // raise the latch
// image is offset by 64 as it is only has a 128x128 canvas
latch(0); // lower the latch
qshift(cmdOne); // send the write channel one command
qshift(64 + pgm_read_byte_near(hadlogo + (i + 1))); // send X data
latch(1); // raise the latch
// Arduino Setup
void setup()
// set pins of PORTA, PA7 & PA2 as inputs, the rest are outputs</p>
DDRA = 0x7B;
// Arduino Loop
void loop()

As you can see, this is a much improved picture quality.

The other 2 demos use this basic system with a button wired up between pin 6 and ground to switch demos. Demo 2 uses a counter and a scale variable to zoom from the upper left corner to full size large font with 3 lines of text and room for a few more. Demo 3 is the start of a sloppy pong game with a (stupid and jumpy) cpu opponent, no scorekeeping and a looping ball. Just hook up a potentiometer with one end on ground the other on +5 and the middle to pin to pin 11 of the Attiny84.

Software.zip you will need a Programmer, Arduino or Arduino Core, and Arduino Tiny

Unfortunately for now I have to move on from this idea, but please fear that I shall return soon enough!


15 thoughts on “Want To Play Pong On Your Oscilloscope?

  1. Nice writeup. Very thorough in all seriousness.

    But..Wait a tick. Something feels conspicuously absent. I went here today to get Dtown maker faire coverage. No love for motown? Too scarry/far away?

    Since a proper comment system is now in place I ask this way to hopefully get a public response, to avoid redundant questioning.

    In the meantime:

  2. So it’s a raster display with fixed resolution, but the quantity of pixels is limited, like the amount of vectors on a vector display. I love this, so this is not to offend, but it looks like you have ended up with the worst of two worlds :)

    I miss my analog scope. Sigh.

    1. And its vector. Really, if its on a scope it really needs to be vector to look good. Otherwise it just looks like a cheap monochrome crt.

      I still want one of the scope clocks but I have not found anyone selling kits any more.

  3. Awesome hack!

    I remember the old ‘scopes from my uni days…

    Someone told me that students had been watching soccer matches on a ‘scope with a satellite dish and decoder circuit.

    This goes one better!

    I think the most advanced function I found was Lissajous curves…

Leave a Reply

Please be kind and respectful to help make the comments section excellent. (Comment Policy)

This site uses Akismet to reduce spam. Learn how your comment data is processed.