Following the last lab, you've probably wondered how we can take signals from a digital device and translate them into something that an analog circuit can use. When would you want this? Well, it turns out that playing 1s and 0s from a speaker doesn't sound so good. Motors, electromagnets, and lights are also typically things you want to have more fine control of than just turning on or off.
We'll start with the simplest option: Not actually converting to analog. How can this work? For things that care about the average of an input over time! If you toggle an LED between on and off quickly enough (a few hundred times a second), it just looks like it is on but dimmer. Some motors also can sometimes be controlled this way, as they'll keep spinning due to inertia when the power is off. And if we vary the relative amount of time an output is on or off, we get even finer control. This technique is know as Pulse Width Modulation or PWM.
We'll start here with a pair of humble LED circuits, shown below:
duty option.Measure the average voltage of $V_{in,1}$Measure the average voltage of $V_{in,2}$
Adjust the duty percentage until the average voltages match. Check if the product of 5V * the duty cycle (as a percent) reasonably matches your power supply's voltage setting.
While this works for a surprising number of situations, there are some limitations. One is that you'll always be rapidly switching voltages, which can cause unwanted oscillations elsewhere in a larger design. Some devices also don't appreciate being switched on and off quickly, and will let you know by dying. As a practical limit many microcontrollers can only output a few mA of current; transistors are your friend in such instances.
Okay, maybe you need to have something that does better than being the same voltage on average. One way of doing this is essentially the inverse of the successive approximation method we used last week. Consider the circuit below to the left:
Each of our digital inputs can take on values of either 0V or 5V. So, how do we analyze this? The answer is that we treat it like a series of voltage dividers. If all of the inputs are off, there's not going to be current anywhere, so $V_{out} = 0V$. Let's separate the last part of the circuit and analyze it via Thevenin's theorem.
The equivalent series resistance is 2R and 2R in parallel, which is just R. The series equivalent voltage is $V_{in,0}/2$, where $V_{in,0}$ is either 0V or 5V.
Let's use this info to redraw an equivalent circuit:
From here, we can immediately combine the two resistors in series to an equivalent of 2R. Then, we have another voltage divider circuit. This one is a 50/50 split between $V_{in,0}/2$ and $V_{in,1}$. Our four possible outputs for the four different combinations of these inputs are 0V, 1.25V, 2.5V, and 3.75V. If you want more precision, add more stages. Each new input improves the resolution by a factor of 2.
Build the R-2R ladder shown previously, and test that you get (approximately) the expected outputs as you change the inputs. Note that the inputs should be either at 0 (0V) or 1 (5V). This may be easiest to accomplish by connecting a long-ish wire to each input and moving the far end to a rail hooked up to either 0 or 5 volts.
You probably didn't notice it at the time, but the UChicago Black Box™ that we used in the first lab? That used an 8-bit version of this circuit.
We'll be using a TLC7528C chip for our digital to analog conversion today. It uses this same R-2R topology to do its conversion. This chip was chosen in part because it has eight parallel inputs, making it so we once again don't need to worry about encoding a sequence of values in time. Let's take a look at the chip's pins now:
The relevant groups of pins are as follows:
Pins 2 and 20 serve as a pair of outputs that can be toggled, letting you use one set of controls to update a pair of outputs. This is why there are OUTA and OUTB connections. This particular chip is designed for the outputs to be analog currents, but we'll use a different mode
Pins 1 and 5 are listed as AGND and DGND respectively. While there is some subtlety to the need for different grounds for analog and digital parts of a circuit, that is a problem for another course entirely thankfully.
Pins 3 and 19 are for feedback resistors (Resistor FeedBack A/RFBA and RFBB) used for tweaking the behavior of the R-2R ladder.
Pins 4 and 18 are reference voltages, which in our case will be the outputs we're using for the device.
Pins 6, 15, and 16 are used to select different behaviors of the chip. 6 toggles which output is being updated. 15 is the Chip Select (CS) pin, used to communicate that the chip should be ready to capture a new set of inputs. 16 is the WRite pin, used to start the conversion of the current digital inputs to analog. The bar over the top of the lettering denotes that the logic is inverted, i.e. setting pin 16 to 0V will constantly update the output, and 5V will stop updates.
Pins 7 through 14 are the digital inputs, from most to least significant bit respectively. The most significant bit is the one with the greatest impact (i.e. toggling it will increase the output by half of its total range) whereas the least significant bit will only increase the output by a factor of $\frac{1}{2^8}$ of the range.
Last but not least, pin 17 is used for powering the chip $V_{DD}$. This particular model needs some overhead; if you power the chip with 5V the maximum analog value out seems to be only around 4V. However, the accepted digital inputs depend on this voltage as well, and at +15V it will not recognize the outputs of the Pi Pico.
To start out, we'll use some switches as our inputs to our DAC, and read out the output on the scope.
Build and test the circuit, observing the output on the oscilloscope.
Please be careful with these chips, as it is easy to bend their legs due to their length. You can use chip extractor (which looks like a little set of tongs) to remove them, or gently wedge a screwdriver under the length of the chip to pop it out of the board.
Right now, even though you can read a voltage from the output, it isn't much use for powering anything. Its capabilities max out around 10mA of current, which will barely light an LED. This looks like another job for a follower!
Add your follower to the end of the signal chain and experiment with your switch inputs.
It makes more sense for us to maybe use a computer here, so that we can dynamically change the inputs at a decent speed. For this, we're going to use a Raspberry Pi Pico running MicroPython. This isn't a course on coding, so we've got a basic program already in place. If you know Python, you'll be able to dig through what it is doing a bit.
Connect the pins of your Pico to the digital outputs of your ADC as shown below
After connecting the device, plug the Pico's USB cable into the lab computer.
With this, the Pico will start outputting a 100Hz sine wave that the DAC will interpret.
Use your oscilloscope to observe the output of your DAC.
Use the other channel of your oscilloscope to observe a 100 Hz sine wave from the function generator.
If you want to affect what it's doing, you'll need to send commands yourself. Open the Thonny application, and click on the hamburger menu in the lower right corner. Select MicroPython (Raspberry Pi Pico) · Board CDC @ COM## where ## is some number that depends on your machine. After doing this you should see the “shell” window on the bottom of the application update to the following:
MicroPython v1.28.0 on 2026-04-06; Raspberry Pi Pico with RP2040 Type "help()" for more information. >>>
If the pico ever becomes unresponsive, you can restart it simply by removing the USB cable on the PC side and plugging it back in. You'll need to use the bottom-right menu in Thonny to re-connect after doing this.
You can now alter the outputs by adjusting the following wave properties:
offset from 0 to 1
amplitude from 0 to 1
freq
func as one of sine, pulse, gaussian, sinc, exponential, or noise
pars which depends on the function.
for a ''pulse'', there are three parameters corresponding to risetime, uptime, and falltime for ''gaussian'' the width is affected for ''sinc'' the scaling is affected for ''exponential'' the decay time is affected for ''noise'' the distribution characteristics are affected.
Up the frequency of the output to 200 Hz. You can do this by entering wave.freq = 200, pressing return, and then typing refresh()
Verify that the outputs on the scope properly update.
Switch to a square wave, by entering the following commands:
wave.func=pulse
wave.pars = [0,.5,0]
wave.amplitude = .5
wave.offset = .25
refresh()
The output should be a 2.5V pk-pk square wave at 200 Hz.
Test the limits of your setup to determine what the smallest registerable change in amplitude is. Note that the wave.amplitude is expressed as a percentage of the maximum, so you'll want to make
That's it for the experiment, though you can feel free to experiment a bit with the Pico to generate different outputs or to hook up a speaker to the op-amp (inline with an appropriate resistor to not deafen your labmates).