# Brute Force Computation for Cheap Log Digital Potentiometers

## Abstract

This article covers the attempt to build a digital logarithmic potentiometer out of two linear potentiometers. The benefits of this concept are:

• Linear digital potentiometers can be easily procured and don’t cost much
• There are IC packages with two or four modules inside anyway
• The resulting logarithmic potentiometer is very flexible in terms of number of steps and steepness of the attenuation function

A program (GNU Octave script) was written to automatically calculate the optimal wiper settings combinations to produce logarithmic behaviour. A test board was built to verify the desired behaviour in a measurement.

## Motivation:

(Just another boring story, you might as well skip that part.)

As you might have seen as one of my earlier projects I built a compact HiFi stereo amplifier with a four input mixer. The clou: the amplifier has a UART input which allows you to digitally set the volume for each channel. In combination with a Raspberry Pi (hosting a web server) the volume can be adjusted from any device which is able to display a website.

Of course I used digital potentiometers wired as voltage dividers to achieve the desired attenuation of my input signal. The problem is: If you use ordinary linear potentiometers you would not have much fun with your volume control because the human ear senses sound volume in a logarithmic fashion. To achieve a decent logarithmic response I used a method that is described in this article: http://sound.westhost.com/project01.htm

The idea here is to combine a linear potentiometer with a fixed resistor (roughly 1/10 of the potentiometer’s resistance):

The transfer characteristics of the above circuit then look somewhat logarithmic (i.e. linear in a logarithmic plot):

This method does a decent job while you stay in the mid-range settings. For really low settings, however it is somewhat unsatisfactory. For my HiFi amp it does not really matter, since I rarely change the settings at all :P. Of course you can just buy special logarithmic digital potentiometers. But last time I checked, these were rather expensive. So I wondered if there was a way to fake a logarithmic potentiometer even better. And there is.

## The Idea

The idea is rather straightforward: I use two identical linear potentiometers in series. This gives me a much higher dynamic range of attenuation settings than with just a single digipot. Now all we need is a strategy to set the correct combinations of wiper settings in order to get a logarithmic response.

## Understanding the Circuit (Boring Calculations with Resistors)

Before we can write beautiful brute force spaghetti code we need to do some math with pen and paper. We need to know how much voltage comes out of our composite voltage divider depending on the wiper positions, x and y (between 0 and 1), of our two potentiometers (pot1 and pot2) which each have a total resistance of R. We also should take into account that the (electronic) wiper has an effective resistance R_w. Let us draw a schematic that helps us calculate:

replacement schematic of cascaded linear digital potentiometers

Still too complicated to see the solution right away? Let’s do an intermediate step. We assume that we drive the input at very low impedance and the circuitry at the output has a high impedance compared to the potentiometer resistance R. Then pot1 is a voltage divider which sees the total resistance of pot2 (plus the pot1 wiper resistance ) as a load.

R_w (of pot1) and R (of pot2) together represent the load for the first voltage divider

Pot2, in turn acts as an ideal voltage divider for the “mid point voltage”, so the voltage at the output of pot1 (minus the small fraction that is dropped along the pot1 wiper resistance).

So here’s the formula. It’s nothing fancy, just basic resistive network calculations. R_midpoint is the effective resistance between “mid point” and ground.

$R_{mid point} = \left(R+R_{w}\right)\parallel\left(R\cdot x\right)=\frac{1}{\frac{1}{R+R_{w}}+\frac{1}{R\cdot x}}\\ U_{mid point} = U_{input}\cdot\frac{R_{mid point}}{R_{mid point}+R\cdot(1-x)}\\ U_{output} = U_{mid point}\cdot\frac{R}{R+R_{w}}\cdot y\\ \\ Attenuation\left(x,y\right) = \frac{U_{output}}{U_{input}} = \frac{R_{mid point}}{R_{mid point}+R\cdot(1-x)} \cdot \frac{R}{R+R_{w}}\cdot y\\ \\ Attenuation_{dB}\left(x,y\right) = 20\cdot\log_{10}\left(Attenuation\left(x,y\right)\right)$

## With brute force – The algorithm

So as you might have guessed, I fired up my favourite matrix calculation tool (at that time GNU Octave) and started scripting. (You can find the script in the “Download” section)

The algorithm is pretty straight-forward:

• The linear digital potentiometers that I bought have 257 steps (positions). This gives me 257*257 combinations of wiper settings. For each combination we calculate the attenuation of the above circuit in decibels and we store the results in a 257*257 matrix which we will call the “attenuation matrix“.
• I decided that I want my resulting log pot to have 64 steps and the highest attenuation value should be at -64 dB. So I define a “target attenuation list” with equally spaced attenuation values between -64 dB and 0 dB.
• For each entry in the target attenuation list we tell the computer to find the attenuation matrix element that is closest in value. The corresponding coordinates in the matrix (wiper combinations) are written down in a look-up table.

Here are some nice pictures to visualize the results.

The ideal attenuation values (blue line) and the closest matching entries of the attenuation matrix (blue dots)

The attenuation matrix as a color map (red = low attenuation, blue = high attenuation) and the optimal “trajectory” connecting the 64 calculated wiper combinations. Pretty messy trajectory though

The deviation of the selected attenuation matrix elements from the ideal attenuation settings. Maximum error is 0.02 dB! Not bad, huh?

The results look stunning. But there is one thing that gives me a stomach ache: The “trajectory” described by the selected coordinate combinations is jumping around in a pretty ugly fashion and you can see that the algorithm seems to like settings where one of the pots is set to very low values (close to the x and the y axis). This is bad since the relative error of a real existing, non-ideal potentiometer is largest when it is almost “closed”. The individual tiny resistors inside the chip have a certain variance. The fewer of these resistors you select as the lower part of your voltage divider, the greater the influence of the resistor variance. So it would be good to distribute the amount of attenuation more or less equally between both pots so each can operate in a low-error regime.

## An improved algorithm, less error, more beauty

Let us make the circuit operate in a low-error regime. Therefore I took the attenuation formula and did some propagation of error calculations. The resulting formula looks messy so I won’t show it here. For simplicity I assumed that both potentiometers have an absolute error of one poti step. With that I calculated a second 257*257 matrix which contains the attenuation error for each wiper combination (or a quantity that is proportional to this error). This matrix I call the “punishment matrix“. In the following, when the algorithm tries to find the attenuation matrix elements with the smallest deviation from the target values, the corresponding value of the punishment matrix is summed to the deviation (multiplied by a tweakable “punishment factor”). Long story short, all that hokus pokus is just a trick, to keep the “trajectory” of selected wiper combinations away from the error-prone edges of the attenuation matrix. The punishment factor was tweaked by hand until the resulting trajectory looked visually pleasing to me.

The attenuation matrix with an improved, smoothened and more balanced trajectory

The same trajectory on top of the “punishment matrix”, close to the x and the y axis, the error becomes large, so we want to keep away from it

The theoretical response of our combi-log-pot still looks the same from far away …

… when we look at the errors now, we see they have gotten a little bigger, but still: only 0.1 dB maximum theoretical error.

So what we did achieve here is a trade-off. We allow more deviation from the target values (we produce a known error) but in turn get a higher immunity against imperfections of the parts we use (reduce unknown error). At least that was my intention 🙂

## Does it work? – Let’s build something and measure stuff!

To see if I really get a decent logarithmic potentiometer I built a test board. It has the following components:

• A quad digital potentiometer: Microchip MCP4461, 257 steps, 10k, I2C interface. (four linear pots = one stereo pair of log pots)
• A quad operational amplifier (TLC274) that I use as a unity gain impedance converter after the output of the composite log pot. (I don’t want to introduce unwanted load to the voltage divider)
• One of the amplifiers I misuse to generate a reference voltage between VCC (5V) and GND
• Stereo audio input and output is facilitated by two 3.5 mm (quarter inch) headphone jacks

I use a Bus Pirate to send I2C commands from my laptop to the digital potentiometer. The audio input of the test board is connected to my sound card headphone output, the output of the test board is connected to the line-in of my sound card.

I set up a shell script and some ugly perl snipplets to implement the following scanning procedure:

• Set a certain combination of wiper settings via the I2C interface of the Bus Pirate (the same settings for both stereo channels)
• Play a 30s long 1kHz sine tone and at the same time …
• … record for 20s the sound that came back from the test board and store it in a .wav file on my hard drive.
• Repeat the above steps for all combinations on our look-up table

The playback and the recording can be done with aplay and arecord, both part of the standard linux package “alsa-utils”:

aplay tone.wav -d 23 & sleep 1; arecord -fdat ./acquisition/\$filename -d 20

First I used sox (linux command line tool to manipulate sound files) to analyze the recorded .wav files, i.e. to display the RMS amplitude of the recoded sound. The method was quite limited, because I could only measure attenuations down to approx -20 dB. This is due to the fact that for high attenuations the RMS amplitude of the sound file is dominated by the noise of the line-in and not by the recorded sine wave.

But GNU Octave came to the rescue. Using the octave-signal and octave-audio package it was painlessly possible to load the whole recorded .wav file into data vectors (left and right channel individually). Then I calculate the FFT of the input data and only look at the height of the resulting 1 kHz peak. This sifts away most of the noise which is distributed more or less evenly across the whole spectral range. See the script [here].

So, here is the result of the measurement:

Okay, what can we observe:

• The response looks quite linear … which is pretty cool
• Both channels, left and right show very similar behaviour … which is good
• We do not reach all the way down to -64 dB but to almost -60 dB … not perfect but … hmm okay
• The part of the curve below -40 dB seems slightly bent upwards … could this be due to noise or limitations of our crude measurement tools? (after all we are covering a pretty large dynamic range)
• There are some few strange kinks/anomalies in the measured transfer curve. I don’t know where they come from. They won’t disappear when I run the scan again. They seem to be real errors of my system since they have been measured on both stereo channels.

In addition to this measurement I played some music through my potentiometer and for fun I made a small HTML5 GUI which allowed me to set the volume with a slider. The composite potentiometer did a killer job as a volume control.

## Conclusion

Okay. It worked! We have in fact “synthesized” a digital logarithmic potentiometer out of two linear digital potentiometers. It is not absolutely perfect but it does what it was intended for. There are a few points in the measured transition curve which slightly deviate from a straight line. I don’t know where they come from. Probably you can get rid of these kinks when you just pick other wiper combinations which have similar attenuation values (the attenuation matrix is huge!).

The above calculations and tests were performed with a 257 step potentiometer with a resistance of 10k and a wiper resistance of 45 ohms. But you can easily modify the Octave script to use other parameters. Also you can specify a different number of target steps and another attenuation range. Apart from the reduced cost, the latter might just be the biggest advantage of this technique.

Here you find the GNU Octave script for calculating the look-up table:

And here are the schematics and KiCAD source files for the test board, as well as the Octave script to analyze the 1 kHz .wav files:

How to execute the Octave script?

Linux users: Install GNU Octave with your usual package manager, e.g. sudo apt-get install octave

Open a terminal, “cd” to the folder containing calc_lookup.m and call the script by typing “octave calc_lookup.m” It generates several plots and a look-up table in csv format.

Windows users: You can use Cygwin, which is a Linux-like software distribution. (https://cygwin.com/install.html) In the install wizard search and mark the package “octave” for installation. Then open the cygwin terminal, “cd” to the folder containing calc_lookup.m and call the script by typing: “octave calc_lookup.m” It generates several plots and a look-up table in csv format.