Lab 7b: GPIO DAC Interface

In Lab 7b, we will write Python programs to interface to the digital-to-analog converter (DAC) we built in Lab 7a. After hooking up the DAC to the Raspberry Pi, we'll test the programming interface in stages, ultimately creating our own wacky waveforms.

The reason we used FETs in the DAC circuit is that the output voltages of the Pi's GPIO pins are unreliable/unknown (nominally around 3.3 V), and could conceivably vary from pin-to-pin. The FETs let the GPIO voltages simply switch in a well-defined 5 V. The GPIO pins also drive the LEDs directly, so don't be surprised if some light up before the power supply to the breadboard is turned on.

The Actual Lab

Some of the instructions below are less than fully descriptive. You are left to figure some things out on your own. As usual, consult the Tips to potentially save some time.

Python Code for GPIO

This lab asks that you create a variety of programs—usually building from a previous step—to progressively add functionality to your DAC interface. Don't just keep modifying the same code, or you lose the earlier versions. At each step, copy the latest code to a new file and edit the new one for the new capability.

You are expected to know how to create Python programs, make them executable, and accept command line arguments (see Lab 3 for a refresher). The key new piece is the manipulation of the GPIO pins. The core GPIO capability is illustrated in the program below.

#!/usr/bin/env python

import RPi.GPIO as GPIO

GPIO.setwarnings(False)  # eliminates warning message
GPIO.setmode(GPIO.BCM)   # use the BCM pin labeling scheme

GPIO.setup(21,GPIO.OUT)  # set GPIO21 (pin 40) as output
GPIO.output(21,GPI.HIGH) # turn GPIO21 ON
GPIO.output(21,GPI.LOW)  # turn GPIO21 OFF

Other variants are possible, and will be introduced below.

Lab Procedure

  1. Connect the Raspberry Pi GPIO to your DAC breadboard using male-to-female ribbon cable. You need 8 GPIOxx pins plus one ground. Think first about making life easy later. By this, I mean that you will be writing 8-bit values to the GPIO, and this goes much more smoothly if choosing sequential GPIOxx numbers. They may not map to sequential pins, but will make coding much easier. It does not much matter which 8 sequential pins you select, or the byte order (whether the most significant bit—MSB— is at the high or low end). But a range is nice.
  2. Try a test program to send a fixed value between 0 and 255 (expressed in the program as decimal or hex—0x5a, for example) to the GPIO. See that the LEDs on the inputs light up in the correct pattern (hex is more immediately interpretable in this sense). See the tip on looping across 8 bits. Once you accomplish this feat, you are home-free. It may take you some time to get this right.
  3. Now create a new program that will accept an integer command-line argument (between 0 and 255) and present this single value to the GPIO pins. Once this is running, send a variety of values and verify that the patterns are correct. One of the tips suggests a way to make your program flexible enough to read either decimal or hex from the command line.
  4. Use your new program to verify the DAC operation using a DVM. Sending 255 (or 0xff) should yield the full-scale voltage. Take this opportunity to adjust the pot to give you the desired full-scale voltage (and see related tip). See that you get appropriate output voltages depending on the integer value you request (try a good variety).
  5. Now make a third program that takes a floating point argument, checks that it is within the valid range (exiting if not), converts to the appropriate integer value, and sends this to the DAC. The goal is to get the DAC to put out the voltage you ask for. It may not be exact, as the output is confined to a discrete number of possibilities. But you can check that you are within a single step-size.
  6. Make a fourth program to cycle through all integers (0 to 255), and repeat—in such a way as to make a saw-tooth ramp. Watch the output on the oscilloscope, triggering on a negative slope to catch the sharp drop. Triggering of the scope is likely to be jumpy, due to spikes in your waveform, which we'll fix later. If the slow-rising side is not perfectly uniform/straight, then you may have a DAC problem (resistor values, bad/mis-placed FET, etc.). Fix it up if this is the case. It could also be a mis-routing of the 8 parallel bits to your FET inputs. In fact, it may be instructive to switch two inputs to see what shape results in the waveform. Could get funky. Once the ramp is regular (aside from spikes), note the period, and calculate the time per step/update (also being aware that each step requires the writing of 8 bits). Check the amplitude and make sure it's full-scale. Zoom in to the bottom of the ramp to see individual steps, and capture an image of this.
  7. We will now explore and optimize the bit writing sequence in forming the ramp. In order to facilitate understanding, probe three things on the scope: the DAC output (ramp), the least significant bit (LSB) from the Pi, and the the bit four steps above the LSB (bit 5; skip 3).
    1. Arrange the writing of each new value to start with the LSB and proceed to the MSB. Note the direction and approximate magnitude of spikes on the ramp. Figure out why this would happen, if updating, say 00111111 to 01000000 by sequentially updating digits from the right (note: the slew rate limitations of the op-amp may limit the depth of the spikes). Now zoom in the time base to resolve the LSB digital input pattern (and why is it basically a regular square wave? think even and odd). Note where the 5th bit transitions occur relative to the LSB transitions: are they aligned/coincident or offset? It may help your understanding if you also probe other bits to see when they change relative to the LSB pattern.
    2. Now flip the order of writes to go backwards: MSB first, proceeding to the LSB. Note the tip on reversing the range so the program can remain largely the same. Repeat the tasks in the previous step for this arrangement.
    3. Now the real deal: we will write the GPIO in a single command, by sending a list of BCM numbers (pins) and a list of values they are to take on. Example syntax looks like:
      writelist = [0]*8                               # makes [0,0,0,0,0,0,0,0]
      for bcm_num in range(bcm_low,bcm_low+8):
        val = (intval >> (bcm_num - bcm_low)) & 0x01
        writelist[bcm_num - bcm_low] = val            # populate list with values to write
      GPIO.output(range(bcm_low,bcm_low+8),writelist) # write all values in one command
      Once this works, repeat the tasks from the previous two steps. Of critical importance is the relative timing of the digital outputs from the Pi. And do the spikes look better? Is the overall period of the ramp different (e.g., 8 times faster)?
  8. When the ramp is running, trigger on the negative-going (fast) side, and expand the time scale until you can measure the slew rate of the op-amp as it goes from the highest voltage to the lowest. Express in volts per microsecond, and compare to the specification for the op-amp. It is important to do this before hooking up a filter (next step)
  9. To eliminate spikes (which are due to a momentarily indeterminate state as the GPIO/DAC switches from one value to another), first look at the step size. Figuring you want to preserve some semblance of this feature, but eliminate spikes, pick a resistor/capacitor combination whose RC time constant is a few times shorter than the step size. Use a resistor at least 100 Ω (preferably higher) to keep the op-amp from having to drive too much current, and select an appropriate associated capacitor in a low-pass arrangement (R between output and scope and C from scope and ground). This should eliminate spikes and keep the steps, allowing for more robust triggering. You can think of the low pass filter as a voltage divider, with the lower leg (capacitor) acting as a resistor that is small for high frequencies, becoming arbitrarily large for low frequencies.
  10. Now that you have a smooth ramp waveform running, and stable triggering, load down the Pi to see what happens. If we were running a graphical interface, moving the mouse, dragging a window around, or starting a large application would be good choices. But in our simple interface, we will make a little program to keep the Pi busy for a few seconds. Here is an example that spends two seconds writing to the screen (in a way that does not "kill" the screen/history with a hundred thousand lines; the \r is the key, requiring writing to stdout in order to function; the flush() forces an update for each output line, keeping the display busy):
    #!/usr/bin/env python

    import time
    import sys

    beg = time.time()
    now = beg
    line = 0
    while (now - beg < 2.0):  # run for 2 seconds
      now = time.time()
      line += 1
      strn = "Printing line number %d\r" % line
      sys.stdout.write(strn)
      sys.stdout.flush()
    sys.stdout.write('\n')    # make a new line for the prompt
    What do you notice happening when the Pi is busy? Why does this happen? It can also be interesting to run the program several times without the ramp running to see how many lines get printed when the ramp generation is not consuming resources.
  11. Now be creative. Make a new program that puts out an "interesting" waveform of your choosing/design—;something a function generator cannot do. But think it out well enough in advance that you can successfully program the wave function in Python. Math is good. Sure, you could draw a row of houses, but this is hard to describe in a program (though the more ambitious may try—;can even try a chimney!). Anyway, come up with something you're happy to look upon.

Tips

Lab 7b Write-up

The write-up for Lab 7b is to be combined with a writeup of Lab 7a. For this segment, include program listings, sketches or captures of waveforms, and all numbers/calculations and descriptions requested in the sections above. Include example inputs/outputs of your programs. For example, if you put in the floating point request for 1.357 volts, what voltage do you read on the DVM. Give enough examples to be convincing.


Back to Physics 122 page