Project tutorial
Draw Anything on Your Oscilloscope

Draw Anything on Your Oscilloscope © GPL3+

Ever wanted to display any hand-drawn image on your fancy oscilloscope? Well, now you can!

  • 18 respects

Components and supplies

Mfr 25frf52 1k sml
Resistor 1k ohm
Any resistor value is acceptable as long as you keep the ratios of the DAC correct. You don't need 50 if you have 2k resistors. Or two times the value of your chosen resistor.
Ph a000066 iso (1) ztbmubhmho
Arduino UNO
Or STM32 "blue pill" or "black pill".

Apps and online services

About this project

The R-2R DAC

One of the simplest implementations of a digital to analog converter can be achieved by designing a R-2R ladder DAC. This type of DAC only needs resistors to work making it a very beginner-friendly DAC.

The basic principle of this type of DAC is that you just need two values for resistors. R and 2R. Then you can set them up as shown in the schematic below.

R can be anything as long as each occurrence of R in the schematic is the same value.

For example, if we choose R = 1k, then 2R would just be two times the amount. In this case 2R = 2k. So you could use just 1k and 2k resistors.

If you used R=3.3K then 2R = 6.6k and so on. If you just want to use the same value of resistors for everything, then to get 2R, you would just need to put them two resistors in series and that would increase your component count.

The number of bits are determined by how many branches of 2R you have sticking out. In this project we will be using an 8-bit R-2R and a 6-bit R-2R for the Arduino Uno or Nano. If you are using another microcontroller that you know supports a full port manipulation of at least 8-bits such as the STM32 "blue pill" and "black pill", then you can just use two 8-bit DACs.

Side remark on DAC ICs:

This project is focused on using a R-2R DAC, but you can feel free to use a DAC IC to achieve something similar. If you're going to be using a DAC IC, I recommend using one that supports a fast communication protocol such as SPI since I2C will be too slow to draw a detailed image. I will not cover how to use a DAC IC in this post but maybe in a future one. The R-2R still my preferred method as it can output images with more detail.

Port manipulation

We will be using parallel port manipulation of our microcontroller. The reason for doing so is because it allows for a faster output speed and also keeps code simple. You can read more details on the port registers for Arduino in this link.

The Arduino Uno and Nano have the ATmega328p microcontroller. This microcontroller shares the same pin mapping with the Atmega168:

The port pins and bits are denoted by their label PXN where X is the port letter and N is the bit number.

Example: PB1 is for PORT B bit 1. Similarly PC5 is for PORT C bit 5.

If we inspect the pins, we see that the microcontroller has ports B, C, and D. By going back to the Arduino reference we can see which ports can be written or read from or both. In our application, we only care about write.

The documentation states that PORTD is read/write and it is mapped to digital pins 0 to 7. This gives us full 8-bits of the ports to write to.

Next we have PORTB which as stated are mapped to digital pins 8-13. An additional statement is made saying that 6&7 which would complete the full 8-bits are not accessible. This port can only be used to write 6-bits.

The same is true for PORTC. It is mapped to analog pins 0-5 but two pins are also reserved giving us only 6-bits to write.

Because of these restrictions, we will have to use a combination of an 8-bit DAC for PORTD and a 6-bit DAC for either PORTC or PORTB.

We will be using PORTD and PORTB and now we can start wiring the DACs to the pins.

If you need some visual aid on how to assemble, this is how I assembled both the R-2Rs in the breadboard. Note that I used two R-2Rs since I was testing it on my STM32 "black pill." I put two jumper wires sticking out so I could connect my oscilloscope probes to it and also a ground wire on the side. I used all 10k resistors so for my 2R I put two 10k resistors in series.

Here's a cleaner breadboard setup using Tinkercad. I marked the outputs with X and Y as those will be what our oscilloscope probes connected to. If you need more guidance on the setup, I will provide schematics showing the R-2R ladders made up of all with the same resistor values and another one with two values.

Make sure to check your grounds! Every time I assemble this setup I tend to forget. Also make double check you did not leave any gaps with no connections.


Now it's time for the Arduino sketch.

First we need to setup our ports to be outputs. From the Arduino reference on port manipulation. We can see how to set the ports:

Basically a value of 1 to the bit will set it as output and a value of 0 will set it as input. For PORTD we can write directly since all bits are accessible. For PORTB, remember that bits 6 and 7 are not usable since they map to the crystal. It is good practice to use bitwise OR with a value of 0 to avoid modifying bits that won't be set.

In our setup loop this will be the code:

The code will be attached for download, so don't worry about typing it by hand.

The for loop below the configurations simply remaps the values for the 6-bit port. These values will be from a list that will have values of 8-bit range. To remap to 6-bits, our max value will be 2^6-1 which is 63. We are mapping from a range of 0 to 2^8-1 which is 255. The reason we remap in the Arduino code is because our hand-plotter tool will output the x and y points in terms of 8-bits. We can change it in either code, but it's better to change it on the microcontroller used.

Basically we need some delay depending on the oscilloscope. I found that for mine 1 is fine. In fact, completely omitting the delay is also fine. For points, it will be automatically generated by our hand plotter tool so you can just copy and paste the number but basically this is how many points there are to plot so the iterating variable can go through them. Anyways, here's the full code:

The x_points and y_points arrays will be different for you each time you draw something and will be automatically generated with our tool.

Side remark on fast PWM:

If you are an advanceduser of microcontrollers, you might realize that if concerned about speed, then perhaps thesame can be achieved by fast PWM and modifying clock registers. Then feeding the PWM outputs to a low-pass filter to get an analog voltage which would reduce the pin count. I have tried both methods, and this still results in better support for images with more detail. At least in the Arduino Uno

Drawing Tool

I made this tool in Python 3 using tkinter. If you don't have tkinter installed, install it with pip.

pip install tk

The code will just be attached, but basically it records the coordinate on the window where your cursor was clicked. Do not try to resize the window as it will change the resolution. If you want to be more precise, use the magnifying glass tool in Windows. The window is based on 255x255 since those will be the maximum values of our bit ranges. Of course one of the dimensions will be mapped to 6-bits if you're using an Arduino UNO.

To call the tool simply use this format

python > arduino_list.txt

This will create a.txt file named arduino_list.txt with the Arduino list for x_points, y_points and NUM_POINTS generated for you, ready to copy and paste into your code.

As an example, this is how it the.txt file looks when you open it. You just need to replace those lines in the code.

Here it how it looks like once it's opened. From there you can draw anything.

I drew the following picture to test it out. The code is generated upon closing of the window.

And after uploading the code on the Arduino this is how it looked like in X-Y mode.


If you run to a compilation error talking about memory, this is due to the low memory available in the Arduino UNO. That means you should draw a less-detailed image. I think the maximum amount of points was about 400, but it might be more. If you use an STM32 "Blue pill" or "black pill" this number is upwards of 4, 000.

Oscilloscope X-Y mode

The oscilloscope must be set to x-y mode. 8-bit R-2R DAC output (PORTD) will be connected to Channel 1 and 6-bit R-2R DAC (PORTB) will be connected to Channel 2. From there you can adjust it with the knobs until you see an image.

That's it! If you have any questions feel free to ask. Additionally, if you want to try it out on a blue pill or black pill here's the link to my GitHub page with some sample code

Now here are some images I have plotted.

Feel free to share yours!!!


Important Details for memory management

As Tim mentioned in the comments

  • In your Arduino code your iterator over the points, t, is a "byte" type, so only supports up to 255 points. Why not make it an "int"?
  • Your plotter code outputs "const unsigned long" for x_points and y_points - that won't work as you later modify y-points. Also, why not just use "byte" to save memory?

Be aware of the hardware limitations you are using. For the Arduino Uno it is better to use byte, however if you are using an STM32 or even ESP32, you will have more points avaialble to plot. Thank you all for your feedback.


I've found a way to massively increase the number of points supported on an UNO - move them into program storage space. You can do this with the PROGMEM modifier. This way you can store around 15,000 points!

So declare the arrays like this:

const int NUM_POINTS = ...
const byte x_points[NUM_POINTS] PROGMEM = {...
const byte y_points[NUM_POINTS] PROGMEM = {...

Don't do the modification to the y_points in the setup function (i.e. remove the for loop there). Instead do a bit shift in loop function.

You also need a special command to read data from program memory (pgm_read_byte_near). So the for loop in the void loop() looks like:

for (int t=0; t < NUM_POINTS; t++) // run through points
PORTD = pgm_read_byte_near(x_points + t);
PORTB = pgm_read_byte_near(y_points + t) >> 2;

Then it's possible to store one high res image or multiple smaller images and cycle between them 😃


Arduino codeArduino
Arduino sketch to use in an Arduino Uno or Nano board
const byte FIGURE_DELAY = 1; // trace delay in uS. adjust if needed
const int NUM_POINTS = 87;// number of XY points in figure

// x and y coordinates to plot 
byte x_points[NUM_POINTS] = {106,106,105,104,103,102,101,100,99,98,97,96,95,94,93,92,91,90,90,89,88,87,87,87,86,86,86,86,87,89,90,91,93,95,97,99,101,102,102,104,104,105,105,106,106,106,106,106,106,108,109,110,112,113,115,117,119,121,122,123,123,124,124,124,124,124,123,122,121,120,119,118,117,116,115,114,113,112,111,110,110,109,109,109,108,107,107};
byte y_points[NUM_POINTS] = {78,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,98,99,100,101,103,105,106,108,110,112,113,113,114,115,115,115,115,115,115,114,112,112,110,109,107,106,106,108,110,112,114,114,115,116,116,117,117,117,117,117,116,115,113,112,110,108,106,104,103,102,100,99,98,97,96,95,94,93,92,91,90,89,87,86,84,82,81,80,78};

void setup(){
  // initialize port D and B for writing
  DDRD = B11111111; 
  DDRB = B00111111;
  byte t;
  for (t=0; t < NUM_POINTS; t++)
    y_points[t] = map(y_points[t],0,255,0,63); // re-map Y points to 6 bits since port B is limited to 6 bits on Arduino

void loop(){
    for (byte t=0; t < NUM_POINTS; t++)// run through points
      PORTD = x_points[t];
      PORTB = y_points[t];

Plotting ToolPython
Drawing tool to export points to plot
import tkinter as tk
X = []
Y = []
lastx, lasty = 0, 0

# xy and addLine are for graphical purposes only
# on_move_press is the one that logs the one to the list and fixes Y

def xy(event):
    # logs coordinates when mouse is clicked
    global lastx, lasty
    lastx, lasty = event.x, event.y

def addLine(event):
    # draws line from old point to new point
    global lastx, lasty
    canvas.create_line((lastx, lasty, event.x, event.y))
    # this makes the new starting point of the drawing
    lastx, lasty = event.x, event.y

# logs clicked coordinate on list
def on_move_press( event):
    curX, curY = (event.x, event.y)
    curY=255-curY # since tkinter uses different coordinates

# set up needed for window
root = tk.Tk()
root.columnconfigure(0, weight=1)
root.rowconfigure(0, weight=1)
canvas = tk.Canvas(root)
canvas.grid(column=0, row=0, sticky=(tk.N, tk.W, tk.E, tk.S))

# bind left click and drag to functions and start loop
canvas.bind("<Button-1>", xy)
canvas.bind("<B1-Motion>", addLine)


# delete every 2nd entry to reduce points and increase trace refresh
for i in range(1,int(len(X)/2)):

print("const int NUM_POINTS = %s;" % str(len(X)))
print("const unsigned long x_points[NUM_POINTS] = {%s};" % ','.join(X))
print("const unsigned long y_points[NUM_POINTS] = {%s};" % ','.join(Y))

#call python > arduino_list.txt


8 Bit R2R schematic with different values for R and 2R
8bitr2differentvalues mq29l1ri1e
8 bit R2R schematic with same resistor values for all
8bitr2rsame tftdrcturw
6 bit R2R schematic using the same value of resistors for all
6bitr2rsame 1oentqeerb
6 bit R2R schematic using different values of resistors
6bitr2rdifferent lpb4emzp00


Similar projects you might like

Arduino - Web Oscilloscope (Support Trigger)

Project tutorial by phpoc_man

  • 58 respects

How to Make a Mini Oscilloscope at Home Using Arduino Nano

Project tutorial by CiferTech

  • 55 respects

SmartGPU2 - Arduino Oscilloscope

Project showcase by Vizic Technologies

  • 37 respects

DIY Arduino Oscilloscope

by Ameduino

  • 35 respects

DIY 10Hz-50kHz Oscilloscope on 128x64 LCD Display

Project tutorial by Mirko Pavleski

  • 36 respects

Advanced oscilloscope

Project tutorial by aimukhin

  • 9 respects
Add projectSign up / Login