Table of Contents
Today, we will learn how to use Digital Potentiometers or DigiPots. These handy devices can replace mechanical pots in many applications, allowing you to add digital control to analog circuits.
Introduction
If you have worked with electronics, you’ll undoubtedly be familiar with the potentiometer. This relatively simple electromechanical component is used in various applications, such as volume controls, adjustable voltage regulators, and signal conditioning circuits. We also use pots with analog inputs on microcontrollers, usually in a voltage-divider configuration.
But what if you could control resistance with the precision and flexibility of a microcontroller? Enter the Digital Potentiometer or DigiPot. This handy component bridges the gap between the analog and digital realms, allowing you to set resistance with a digital value.
These devices are inexpensive and easy to use, as you will soon see for yourself. But before buying a bunch of DigiPots to replace your mechanical potentiometers, you should know they can’t be used in every design. Digital Potentiometers have several constraints regarding voltage and current handling, so there are many applications where they are just not suitable.
However, many applications are also ideal candidates for Digital Potentiometers. Today, we’ll see how to work with DigiPots and how to use them in our projects.
Digital Potentiometers
Digital potentiometers were introduced in the early 1980s as an alternative to mechanical potentiometers. They have many advantages, including:
- Precision, reproducible control.
- No mechanical wear.
- Immunity from shock and vibration.
- Smaller size (in most cases).
- Multi-channel options in a single package (some models).
- Safe operation in hazardous environments.
One of the key advantages of Digital Potentiometers is their ability to fuse digital and analog electronics. They are often used to adjust analog filters, gain controls, and tuning circuits with a microcontroller.
Potentiometer Basics
To understand the operation of DigiPots, you should be familiar with the operation of the mechanical potentiometer.
A potentiometer, or “pot” for short, is a three-terminal resistor with a sliding or rotating contact called the “wiper.” When the wiper is moved, the resistance between the wiper and the two ends of the potentiometer changes.
Pots are often used in a voltage divider arrangement, with each end connected across a supply voltage source and an output taken from the wiper. The wiper can be moved to set the output voltage.
Potentiometers are available in a variety of sizes, styles, and configurations. Precision pots are often multi-turn devices and contain an internal gear to allow fine-tuning of the wiper position.
The resistive element of inexpensive potentiometers is often made of graphite. More expensive devices use other materials, including resistance wire, carbon particles in plastic, and a ceramic/metal mixture called cermet.
You may also run across the terms “rheostat” or “variable resistor” when working with potentiometers. These are two-terminal devices whose total resistance changes when the wiper is moved. You can use a potentiometer to replace a rheostat or variable resistor.
Tapered Potentiometers
The relationship between the potentiometer’s wiper position and its resistance value is known as its “taper.” There are two standard tapers available:
- Linear Taper – Resistance changes uniformly as the wiper is moved.
- Logarithmic Taper – Sometimes called “Audio Taper.” The resistance follows a logarithmic pattern, so it is not at 50% when the wiper is in the center.
When building a voltage divider, you would want to use a linear taper pot so the voltage changes smoothly from end to end. Linear taper potentiometers are the most common, and when the taper is not specified, you can assume it to be linear. Almost all PCB-mounted pots are linear.
Logarithmic pots are commonly used in audio circuits, especially as volume controls. Their logarithmic properties are well suited for this application, as human perception of audio volume is logarithmic.
Digital Potentiometers
Digital potentiometers emerged to replace mechanical pots in applications requiring more precision and automation. While traditional potentiometers rely on physical movement, digital potentiometers achieve resistance control electronically.
Most digital potentiometers are built using a resistive ladder and digital switches, although some specialized models use digital-to-analog converters (DACs) instead.
The number of resistors used determines the DigiPots resolution, which varies from 32 (5 bits) to 1025 (10 bits) resistor values.
Digital Potentiometer Interfaces
The type of digital interface used in DigiPots has changed since they were first introduced. Early digital potentiometers used simple up/down interfaces, but modern devices use more sophisticated communication protocols like SPI and I2C, which are easier to interface with microcontrollers.
Step Up/ Step Down
This simple interface uses two control lines (Clock and Step) to increment or decrement the wiper position. It’s straightforward, but making large resistance adjustments may require many steps and is thus slower.
SPI
The SPI (Serial Peripheral Interface) bus allows high-speed communication between the microcontroller and the digital potentiometer. Unlike the Step Up/ Step Down method, SPI will enable you to set and change resistance values directly and quickly.
SPI is a “synchronous” data bus that uses separate lines for data and clock signals. It is a bidirectional data bus with the following connections:
- SCK – The Serial Clock
- PICO – Peripheral In Controller Out (formerly called MOSI)
- POCI – Peripheral Out, Controller In (formerly called MISO)
- CS – Chip Select
In an SPI configuration, you have a Controller and a Peripheral. A DigiPot would be a Peripheral.
Incidentally, if you were wondering about the new signal names, the Open Source Hardware Association recently redefined them in a move to treat all humans with respect, a change we support here at the DroneBot Workshop.
The CS, or Chip Select line, is brought LOW to communicate with the digital potentiometer. This limits the number of DigiPots you can control with one microcontroller to the number of CS lines you can provide.
I2C
Another popular digital potentiometer interface is the Inter-Integrated Circuit or I2C bus. We have used it many times in other projects and tutorials.
I2C is also a serial data bus with a separate clock signal and a bidirectional data line. I2C peripherals have a unique address and respond to requests from the host controller but can’t initiate requests. It uses the following connections:
- SDA – Serial Data
- SCK – Serial Clock
- VCC – Power, typically 3.3-volts, but can also be 5-volts.
- GND – Ground
The VCC and GND lines can be used to power the I2C peripheral.
The biggest restriction on the number of I2C DigiPots you can hook up to a controller is the number of unique I2C addresses you can assign to them. Most digital potentiometers have a selection of addresses; you’ll need to wire each one to be unique.
Physically, a pull-up resistor (5.6k is a typical value) should be on both the SDA and SCK lines. Many peripherals provide this resistor onboard.
As with SPI, I2C has advantages over the StepUp/ Step Down method of selecting DigiPot resistance. You can instantly choose the resistance, and it is much easier to address multiple DigiPots.
DigiPot Voltage and Current Limitations
Digital Potentiometers cannot be used as replacements for standard mechanical pots in every situation. They have voltage and current limitations that you must be aware of before incorporating them into your design.
The first consideration is voltage levels. DigiPots typically have a limited voltage range they can handle, often between 3 and 5 volts for logic levels. Most DigiPots also have the same voltage limitation for the resistive element side, although some models can handle up to 30 volts. Later in this article, we will look at a digital potentiometer that allows for higher resistive voltage levels.
Current is another important consideration when designing with digital potentiometers. There are two current measurements to be concerned with:
- Wiper current -The maximum current that can flow through the wiper terminal. It’s often in the range of 1-5 mA.
- End-to-end current – The maximum current that can flow between the two end terminals. This is usually higher than the wiper current, often 10-20 mA.
You should also look at power dissipation, which is limited to under one watt on most devices.
MCP4231
The first Digital Potentiometer we will examine is an older one that is still in production and remains very popular.
The MCP4231 is a versatile 7-bit digital dual potentiometer from Microchip Technology. Its 7-bit design offers 128 possible resistance values plus zero ohms for a total of 129 steps. It uses the SPI bus for its digital interface.
The MCP231 is available in 5, 10, 50, and 100 kilohm values. The value is listed after the part number; for example, we will be using a 10k version with the part number MCP4231 104E/P.
When initialized, this DigiPot will set itself in mid position.
Microchip has other models in this series of digital potentiometers. Some have non-volatile memory to retain the last wiper position, and others have an 8-bit resolution for a total of 257 steps.
MCP4231 Pinouts
The MCP4231 is available in several different packages. We will use the 14-pin DIP version, so we will need a solderless breadboard for our experiments.
Here are the MCP4231 14-pin DIP pinouts:
- CS – Chip Select (active LOW).
- SCLK – SPI Clock.
- SDI – Serial Data In, PICO.
- GND – Ground.
- P1B – Potentiometer 1, terminal B.
- P1W – Potentiometer 1, wiper.
- P1A – Potentiometer 1, terminal A.
- P0A – Potentiometer 0, terminal A.
- P0W – Potentiometer 0, wiper.
- P0B – Potentiometer 0, terminal B.
- WP – Write Protect (can be left unconnected).
- SHDN – Shutdown(active LOW, disconnects wiper. Should be held HIGH).
- SDO – Serial Data Out, POCI
- VDD – Positive voltage, 2.7 – 5.5 VDC
MCP4231 Arduino Hookup
The MCP4231 uses the SPI bus, so hooking one to a microcontroller is straightforward. Here is how you can connect it to an Arduino Uno:
We are wiring the pots up in this hookup as voltage dividers to output 0-5 volts, depending upon their wiper position. These outputs are being fed back to the analog inputs A0 and A1 so we can monitor the voltages using the serial monitor.
MCP4231 Test Code
While there are a few libraries for the MCP series of digital potentiometers, it is so easy to use them with SPI that a dedicated library is unnecessary. You can code for these devices using the SPI library, which is included with the Arduino IDE.
Here is some code to write to both potentiometers in the MCP4231:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
/* MCP4231 Test mcp4231_test.ino Tests MCP4231 Dual SPI Digital Potentiometer DigiPots wired as voltage dividers Wipers to analog inputs A0 & A1 DroneBot Workshop 2024 https://dronebotworkshop.com */ // Include the SPI Library #include <SPI.h> // Address of two digital potentiometers in MCP4231 byte addressPot0 = 0b00000000; byte addressPot1 = 0b00010000; // Chip Select Pin int CS = 10; // Analog Inputs to monitor voltage #define POT0_IN A0 #define POT1_IN A1 // Variables for Analog data int data0 = 0; int data1 = 0; // Variables for wiper positions int wiper_position0 = 0; int wiper_position1 = 0; void writePot0(int value0) { digitalWrite(CS, LOW); SPI.transfer(addressPot0); SPI.transfer(value0); digitalWrite(CS, HIGH); } void writePot1(int value1) { digitalWrite(CS, LOW); SPI.transfer(addressPot1); SPI.transfer(value1); digitalWrite(CS, HIGH); } void setup() { // Start Serial Port Serial.begin(115200); Serial.println("MCP4231 Dual Digital Potentiometer Test"); // Set Chip Select as an output pinMode(CS, OUTPUT); // Start SPI Communications SPI.begin(); } void loop() { // Step through values 0 - 128 for (wiper_position0 = 0; wiper_position0 <= 128; wiper_position0++) { // Set Wiper1 position wiper_position1 = 128 - wiper_position0; // Set wipers on both DigiPots writePot0(wiper_position0); writePot1(wiper_position1); // Get Analog input values data0 = analogRead(POT0_IN); data1 = analogRead(POT1_IN); // Print Wiper Positions and Received Values Serial.print("Wiper Position 0 = "); Serial.print(wiper_position0); Serial.print(" ,Analog In 0 = "); Serial.println(data0); Serial.print("Wiper Position 1 = "); Serial.print(wiper_position1); Serial.print(" ,Analog In 1 = "); Serial.println(data1); // Short delay between values delay(150); } // Delay at endpoint delay(1000); } |
To understand how the code works, you must know how the MCP4231 accepts data to set the wiper position. It works as follows:
- Each potentiometer has a unique internal register with its own unique address.
- To write data to a potentiometer wiper, you first set the Chip Select (CS) line on the MCP4231 LOW. This selects the chip, which will now accept commands from the SPI data bus.
- Next, you transmit the register address of the pot you wish to control to the MCP4231.
- After that, you send the wiper position data as an integer.
- Finally, you set CS HIGH to deselect the MCP4231.
Knowing all that, the code should now make sense.
We start by loading the SPI Library, which handles all the SPI bus communications.
Next, we define the register addresses for the two potentiometers in the MCP4231. We also define pins for Chip Select and analog inputs and variables for analog data values and wiper position values.
After that, we define two nearly identical functions that can easily be combined into one. These functions update the potentiometer wiper positions. If you step through the code, you’ll see how it matches the steps listed above to update the wiper data.
Now we jump into Setup, where we do the following:
- Start the Serial Monitor.
- Set the CS pin as an OUTPUT.
- Start the SPI Communications.
Now, onto the Loop, where we step the wiper_position0 variable from 0 to 128. We subtract the result from 128 and use it as the wiper_position1 variable value. The result is the two wiper variables are stepped in the opposite direction.
Each variable is then used to write to the MCP4231 using its “writePot” function.
After writing, we read the analog input for each pot. We print the wiper position variable and analog data values to the serial monitor for each potentiometer.
After a tiny delay, inserted so humans can read the results, we do it all again!
Load the sketch to the Arduino and observe the results in the Serial Monitor. You should see that the two analog data values change in correspondence with their wiper position value and that one increments while the other decrements.
You could also measure the output from the wiper of each potentiometer with a multimeter or oscilloscope.
Adafruit DS1841 & DS3502
The two DigiPots we will work with next are both from Adafruit. They are very similar devices; both are small modules that use the I2C bus for communications. They differ in that one is a linear taper potentiometer, and the other is logarithmic.
One nice feature of the DS3502 is that it can be used with voltages higher than the supply voltage. An external voltage reference pin is used for that.
Both modules have essentially the same pinout, the exception being the V+ pin that is only on the DS3502:
- VCC – This is the power pin. It needs to be set to the logic level of 3.3 or 5 volts and is usually supplied by the I2C connection.
- GND – The Ground connection.
- SCL – The I2C clock. The board has a 10k pullup resistor.
- SDA – The I2C data. The board has a 10k pullup resistor.
- RL – The LOW pin of the potentiometer, often connected to Ground.
- RW – The potentiometer wiper.
- RH – The potentiometer’s HIGH pin.
- V+ (DS3502 Only) – The wiper bias pin. This can be used to increase the analog side’s operating voltage. A jumper on the back of the board prewires it to VCC. The analog voltage can be as high as 15.5 volts.
In addition, each module has two STEMMA QT (QWIIC) I2C connectors, which simplifies connecting one or more of them to a microcontroller with a QWIIC connector. They are compatible with both 3.3-volt and 5-volt I2C bus connections.
The modules have a couple of straps and jumpers on the back:
- V+ (DS3502 Only) – This jumper connects the VCC pin to the V+ pin. When connected, the maximum voltage the digital potentiometer’s resistive side can handle is VCC. You can cut this jumper to increase the V+ voltage, allowing you to use these modules with voltages higher than VCC. The maximum voltage you can apply is 15.5 volts.
- A0, A1 – These solder pads can be used to set the modules’ I2C address. By default, the address is 0x28; it can also be set to 0x29, 0x2A, or 0x2B.
The modules come with unsoldered pins, and Adafruit provides pins in the package.
Adafruit DS3502 Linear Potentiometer
The Adafruit DS3502 is a 10K linear digital potentiometer. It is a 7-bit device with 128 possible resistance values.
As noted, the DS3502 can use an analog (potentiometer side) voltage that differs from its logic voltage. After cutting the jumper on the back of the module, you can apply up to 15.5 volts to the V+ pin.
Adafruit DS1841 Logarithmic Potentiometer
The Adafruit DS1841 is a 3.7k – 22K logarithmic digital potentiometer. It is a 7-bit device with 128 possible resistance values.
This module has a few restrictions and quirks you should know.
First, as noted above, the resistance on this digital potentiometer does not drop to zero ohms; instead, it has a minimum resistance of 3.7K. This means that when wired up as a standard volume control, it can’t reduce the audio level to zero.
Second, unlike the DS3502, the potentiometer’s RL (Resistor Low) side is internally connected to the ground.
Finally, it works backward! Incrementing the number decreases the resistance between the wiper and RH (Resistor High).
This potentiometer is restricted to the same voltage level as the logic voltage level, usually 3.3 or 5 volts. Its internal temperature compensation table allows you to define up to 70 temperature-resistance pairs.
Testing the DS1841 & DS3502
Since both modules have a pair of QWIIC connectors, I used both and a SparkFun ESP32C6 module for a quick test (no pun intended there!).
I connected both modules to the ESP32C6 module with QWIIC cables. I wired each module’s potentiometer as a voltage divider with RH going to VCC and RL grounded on the DS3502 (it is already internally grounded on the DS1841).
As both modules have the default I2C address of 0x28, I needed to change one. You can do this semi-permanently using the solder pads, but I chose to use the pins on the side of the model to effect a temporary change.
I changed the address on the DS1841. I connected A0 to VCC using a solderless breadboard, which changed the modules’ I2C address to 0x29.
I also connected multimeters to the outputs (wipers) of each potentiometer. I used an analog and digital meter for each pot, but that isn’t a requirement. I did it because I think the analog meters display the taper differences better.
The code to drive the modules is simple, as Adafruit has libraries for each one. You’ll want to install this in your Arduino IDE. Once you have installed the libraries, you can run the following code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
/* Adafruit DS1841 & DS3502 Test ds1841_ds3502_test.ino Tests DS1841 Logarithmic & DS3502 Linear Digiral Potentiometer modules DS1841 is set for I2C Address 0x29 (connect A0 to VCC) Requires Adafruit_DS1841 & Adafruit_DS3502 Libraries DroneBot Workshop 2024 https://dronebotworkshop.com */ // Include Libraries #include <Adafruit_DS1841.h> #include <Adafruit_DS3502.h> // Create Objects Adafruit_DS1841 ds1841; Adafruit_DS3502 ds3502; // Variable for wiper position int wiper_position = 0; // I2C Address for DS1841 const int ds1841_I2C = 0x29; void setup() { // Start Serial Port Serial.begin(115200); Serial.println("Adafruit DS1841 & DS3502 Module Test"); // Check for modules if (!ds1841.begin(ds1841_I2C)) { Serial.println("Couldn't find DS1841 Module"); while (1) ; } if (!ds3502.begin()) { Serial.println("Couldn't find DS3502 Module"); while (1) ; } Serial.println("Found Both Modules"); } void loop() { // Zero the wiper and wait 2 seconds ds3502.setWiper(0); delay(2000); // Step through values 0 - 128 for (wiper_position = 0; wiper_position <= 128; wiper_position++) { // Set wipers on both DigiPots ds1841.setWiper(wiper_position); ds3502.setWiper(wiper_position); // Print Wiper Position Serial.print("Wiper Position = "); Serial.println(wiper_position); // Short delay between values delay(150); } // Delay at endpoint delay(1000); } |
We start by including the two Adafruit digital potentiometer libraries. We also define objects to represent each potentiometer.
Next, we define a variable representing the wiper position and a constant with the DS1841 I2C address.
In Setup, we start the Serial Monitor and the two digipots.
In the Loop, we simply step the wiper position variable through the values 0-128 and drive both potentiometers using functions included with the Adafruit libraries. You will note a number of relatively long delays in the Loop code. These were inserted for the benefit of my slower analog multimeters. If you are just using digital meters, you could reduce these values if you wish.
After you load the code, connect a multimeter to each potentiometer wiper and measure the voltage. You should observe it rising on one potentiometer and falling on the other.
You should also be able to see the logarithmic characteristics of the DS1841 as opposed to the linear ones of the DS3502 module.
These two Adafruit modules are easy to work with and can be used to build exciting projects. Let’s construct a couple.
Remote Audio Volume Control Project
The logarithmic DS1841 digital potentiometer module is just begging to be used as an audio volume control! Logarithmic controls are preferred for controlling volume as they mimic how the human ear hears sound.
Now, I should immediately point out that the Adafruit DS1841 has an issue that will prevent it from being used as a proper ”volume control.” This digital potentiometer doesn’t drop to zero ohms at one end of travel; its minimum resistance is around 3.3k. So, it is more of an “attenuator” than a “volume control.”
Nonetheless, connecting it to a microcontroller to control analog audio levels is simple, and it makes for a quick, fun project.
Audio Volume Control Hookup
By adding a microcontroller and an infrared receiver module, you can create an audio-level control that can accept commands from an infrared remote.
Here is a circuit using a Seeeduino XIAO microcontroller and a TL1838 IR receiver module:
I also added a couple of 3.5mm audio jacks to make connecting to an audio source and amplifier easy. Note that the input and output are connected to the jack’s Tip, the left audio channel. If you wish to use the right channel, connect to the Ring 1 instead.
Of course, you could expand this to include two DS1841 modules for stereo. If you do, remember to change the I2C address on one of the modules.
Audio Volume Control Code
Before we get to the sketch for this project, we need to select our IR remote transmitter and determine the codes used for volume up and down. We can accomplish this with some of the sample code included in the Arduino-IRremote Library, which needs to be installed from the Arduino IDE Library Manager.
You will also need to determine the IR protocol used by your remote. There are several, the most popular being the NEC protocol. The television remote I used for this project used NEC, as do those generic IR remotes you can buy with an IR sensor.
Once you have the library installed and know your protocol and command codes, you can load this sketch:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
/* Remote Volume Control with DS1841 Digital Potentiometer ds1841_remote_volume.ino Simple IR remote volume control Requires Adafruit_DS1841 Library Requires Arduino-IRremote Library DroneBot Workshop 2024 https://dronebotworkshop.com */ // Protocol and Pin Definitions (must load before IRremote library) #define DECODE_NEC #define IR_RECEIVE_PIN 2 // Include Libraries #include <Adafruit_DS1841.h> #include <IRremote.hpp> // Create Object for Digital Potentiometer Adafruit_DS1841 ds1841; // Variable for wiper position int wiper_position = 128; // IR Volume Control Codes (Change if required) #define VOL_UP_CODE 0x2 #define VOL_DOWN_CODE 0x3 void volumeUp() { // Raise Volume (decrease wiper by 8) wiper_position = wiper_position - 8; if (wiper_position < 0) { wiper_position = 0; } ds1841.setWiper(wiper_position); } void volumeDown() { // Lower Volume (increase wiper by 8) wiper_position = wiper_position + 8; if (wiper_position > 128) { wiper_position = 128; } ds1841.setWiper(wiper_position); } void setup() { // Start Serial Monitor Serial.begin(115200); // Start the IR receiver IrReceiver.begin(IR_RECEIVE_PIN, ENABLE_LED_FEEDBACK); // Check for potentiometer if (!ds1841.begin()) { Serial.println("Couldn't find DS1841 Module"); while (1) ; } } void loop() { // See if we have new IR data if (IrReceiver.decode()) { // Data received, check if it is volume control if (IrReceiver.decodedIRData.command == VOL_UP_CODE) { // Volume Up Serial.println("Volume Up"); volumeUp(); } else if (IrReceiver.decodedIRData.command == VOL_DOWN_CODE) { // Volume Down Serial.println("Volume Down"); volumeDown(); } // Resume receiving IrReceiver.resume(); } // Print Wiper Position Serial.print("Wiper Position = "); Serial.println(wiper_position); delay(10); } |
It might seem odd, but this sketch starts by defining some parameters for the Arduino-IRremote Library. These need to be present when the library is loaded, which is the next step along with the Adafruit_DS1841 library. If you use a protocol different from NEC, you must edit the first line accordingly.
Next, we create an object for the digital potentiometer and declare the wiper position variable. We also define our “Volume Up” and “Volume Down” codes. If yours differ from the ones in the sketch, you’ll need to edit these lines.
Now, we define two functions:
- volumeUp() – Increases the audio volume by incrementing the wiper variable by 8. If it exceeds 128, then it is set to 128.
- volumeDown() – Decreases the audio volume by decrementing the wiper variable by 8. If it drops below zero, then it is set to zero.
I chose 8 for the increment and decrement value as it gives 16 steps through the entire range of the potentiometer. You can change this value if you wish.
In Setup, we start the Serial Monitor, the IR Receiver, and the digital potentiometer.
In the Loop, we use the Arduino-IRremote Libraries’ “decode” function, which is active when valid IR data has been received. We check that data for the Volume Up or Volume Down command codes. If one is found, we call the function to alter the audio level.
An important step is to “resume” the IR receiver after we are done. The receiver doesn’t work while processing the “decode” function. We also print to the Serial Monitor; you can use this to troubleshoot.
And that’s it! Load it up and try it out with an audio source (I used a phone) and an amplifier (I used some computer speakers). Make sure you don’t reverse the output and input, although with the Adafruit module not dropping to zero ohms, it isn’t as dangerous to the audio source as it would be with a standard potentiometer.
As expected, it doesn’t lower the sound completely; it’s just a demo circuit. To build this correctly, you would want to add some op-amps as buffers. However, it does serve as a demonstration of how easy it is to use the Adafruit DS1841 digital potentiometer.
Variable Power Supply with Web Interface
The final project for the day is slightly unusual but a lot of fun—it might even be practical!
We will build a linear DC power supply with a variable output voltage. The design I have here produced 4 – 11.5 volts using a 9VAC “wall wart” transformer. With the correct transformer (or DC input), you could extend the top voltage to 15 volts.
The output level will be controlled by, you guessed it, a digital potentiometer. Essentially, we are putting together a linear power supply and replacing the standard pot with a digipot. But there’s more to that.
Remember that digital potentiometers have restrictions on voltage and current. The current isn’t a problem in this design, but the voltage is. Most digital potentiometers are restricted to logic-level voltages, which is not suitable.
However, the Adafruit DS3502 is an exception. By cutting the trace on the back, we can supply up to 15.5 volts to the potentiometer side of the module, allowing us to build a power supply with some useful voltage levels.
I chose an ESP32 Dev Kit board for a microcontroller, but any ESP32 that can use WiFi will work. WiFi capability is required as we will give our power supply a simple web-based interface. And by “simple,” I mean a slide pot on a screen!
Power Supply Hookup
Here is the hookup diagram for our power supply.
A few notes:
- You MUST cut the jumper on the back of the DS3502 digital potentiometer module before powering this up. This is extremely important! Check your work afterward with a multimeter; if you successfully cut the trace, the resistance between VCC and V+ should be a few megohms.
- If you can’t find a 9VAC transformer that you can SAFELY use, you can eliminate the bridge rectifier and apply about 16 VDC to the regulator input just to test the circuit.
- The regulator used, the LD1085, is one I have used in a bench power supply project. It can handle up to 3 amps, so you could assemble a more practical version of this project.
The 1N472 12-volt Zener Diode provides the V+ voltage for the digital potentiometer. The two big capacitors are filter capacitors, and their values are not critical.
The 1K resistor can be adjusted to increase or decrease the power supply range.
Note that this is an experiment, and the ESP32 is powered by its USB port. To build a stand-alone version of this power supply, add a low-drop 5-volt linear regulator to power the microcontroller.
Power Supply Code
Here is the sketch I used for my power supply:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 |
/* Remote Power Supply remote_power.ino ESP32, DS3502 Digital Potentiometer & LD1085 Voltage Regulator Requires Adafruit_DS3502 Library DroneBot Workshop 2024 https://dronebotworkshop.com */ // Include Libraries #include <Adafruit_DS3502.h> #include <WiFi.h> // Create Object for DS3502 Digital Potentiometer Adafruit_DS3502 ds3502; // Variable for wiper position int wiper_position = 0; // Network credentials (Edit as required) const char* ssid = "YOUR SSID"; const char* password = "YOUR PW"; // Web server port number WiFiServer server(80); // HTTP request variable String header; // Decode HTTP GET value String valueString = String(5); int pos1 = 0; int pos2 = 0; // Current time unsigned long currentTime = millis(); // Previous time unsigned long previousTime = 0; // Define timeout time in milliseconds (example: 2000ms = 2s) const long timeoutTime = 2000; void setup() { // Start Serial Monitor Serial.begin(115200); // Connect to Wi-Fi network with SSID and password Serial.print("Connecting to "); Serial.println(ssid); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } // Print local IP address and start web server Serial.println(""); Serial.println("WiFi connected."); Serial.println("IP address: "); Serial.println(WiFi.localIP()); server.begin(); // Start DS3502 module if (!ds3502.begin()) { Serial.println("Couldn't find DS3502 Module"); while (1) ; } Serial.println("Found DS3502 Module"); } void loop() { // Listen for incoming clients WiFiClient client = server.accept(); if (client) { // New client connected // Get current timestamp & reset time variables currentTime = millis(); previousTime = currentTime; // Print to serial monitor Serial.println("New Client Connected."); // String to hold incoming data from the client String currentLine = ""; while (client.connected() && currentTime - previousTime <= timeoutTime) { // Loop while the client is connected currentTime = millis(); if (client.available()) { // Read data from client and write to serial monitor char c = client.read(); Serial.write(c); header += c; if (c == '\n') { // If the current line is blank, you got two newline characters in a row. // That's the end of the client HTTP request, so send a response: if (currentLine.length() == 0) { // HTTP headers always start with a response code (e.g. HTTP/1.1 200 OK) // and a content-type so the client knows what's coming, then a blank line: client.println("HTTP/1.1 200 OK"); client.println("Content-type:text/html"); client.println("Connection: close"); client.println(); // HTML web page header client.println("<!DOCTYPE html><html>"); client.println("<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">"); client.println("<link rel=\"icon\" href=\"data:,\">"); // CSS client.println("<style>body { text-align: center; font-family: \"Trebuchet MS\", Arial; margin-left:auto; margin-right:auto;}"); client.println(".slider {"); client.println("-webkit-appearance: none; /"); client.println("appearance: none;"); client.println("width: 300px;"); client.println("height: 10px;"); client.println("background: #d3d3d3;"); client.println("outline: none;"); client.println("border-radius: 5px;"); client.println("}"); client.println(".slider::-webkit-slider-thumb {"); client.println("-webkit-appearance: none;"); client.println("appearance: none;"); client.println("width: 25px;"); client.println("height: 25px;"); client.println("background: #4CAF50;"); client.println("cursor: pointer;"); client.println("border-radius: 50%;"); client.println("}"); client.println(".slider::-moz-range-thumb {"); client.println("width: 25px;"); client.println("height: 25px;"); client.println("background: #4CAF50;"); client.println("cursor: pointer;"); client.println("border-radius: 50%;"); client.println("}</style>"); client.println("<script src=\"https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js\"></script>"); // Web Page client.println("</head><body><h1>Remote Power Supply</h1>"); client.println("<p>Wiper Value: <span id=\"powerPos\"></span></p>"); client.println("<input type=\"range\" min=\"0\" max=\"75\" class=\"slider\" id=\"powerSlider\" onchange=\"power(this.value)\" value=\"" + valueString + "\"/>"); client.println("<script>var slider = document.getElementById(\"powerSlider\");"); client.println("var powerP = document.getElementById(\"powerPos\"); powerP.innerHTML = slider.value;"); client.println("slider.oninput = function() { slider.value = this.value; powerP.innerHTML = this.value; }"); client.println("$.ajaxSetup({timeout:1000}); function power(pos) { "); client.println("$.get(\"/?value=\" + pos + \"&\"); {Connection: close};}</script>"); client.println("</body></html>"); //GET /?value=180& HTTP/1.1 if (header.indexOf("GET /?value=") >= 0) { pos1 = header.indexOf('='); pos2 = header.indexOf('&'); valueString = header.substring(pos1 + 1, pos2); // Rotate the Digital potentiometer wiper_position = (valueString.toInt()); ds3502.setWiper(wiper_position); // Print Wiper Position Serial.print("Wiper Position = "); Serial.println(wiper_position); } // The HTTP response ends with another blank line client.println(); // Break out of the while loop break; } else { // if you got a newline, then clear currentLine currentLine = ""; } } else if (c != '\r') { // if you got anything else but a carriage return character, currentLine += c; // add it to the end of the currentLine } } } // Clear the header variable header = ""; // Close the connection client.stop(); Serial.println("Client disconnected."); Serial.println(""); } } |
It’s a basic ESP32 web page with a JavaScript slider control. The slider’s value sets the digital potentiometer’s wiper position.
A few notes about the sketch:
- The SSID and Password variables need to be set to your network credentials.
- The web server’s IP address will be displayed in the serial monitor after the ESP32 has started up. You’ll need this IP address to visit the web page on a device sharing the same wireless network.
- Lines 114-142 are the page’s style sheet. You can change the values here to suit your personal preferences.
- In line 149, the slider range is set from 0 to 75. You can experiment with this; I found that values over 75 didn’t change the voltage much with the 1K resistor wired as shown. You may want to change the slider range if you change this value.
Load it up, hook up a voltmeter, and visit the web page at the address displayed on the Serial Monitor. Remember that the device viewing the page must share the same WiFi network as the ESP32.
Now, adjust the slider and observe the meter. You should be able to change the supply between about 4 and 11.5 volts.
Of course, this is just a breadboard experiment, but it could be turned into something more. A great addition would be to measure the voltage on the output and send it back via the web page.
Conclusion
Digital potentiometers offer significant advantages over mechanical potentiometers. By using interfaces like SPI and I2C, you can control these components programmatically, opening up a wide range of possibilities for experimenters, students, and makers.
Whether tuning a voltage divider, controlling audio output, or adjusting motor speed, digital potentiometers provide a modern, flexible solution to traditional potentiometer applications.
As you experiment with these devices, you’ll discover new ways to integrate them into your designs, opening up possibilities for more sophisticated and responsive electronic projects.
These versatile components are a valuable addition to any electronics toolkit.
Parts List
Here are some components you might need to complete the experiments in this article. Please note that some of these links may be affiliate links, and the DroneBot Workshop may receive a commission on your purchases. This does not increase the cost to you and is a method of supporting this ad-free website.
MCP4231 Dual Digital Potentiometer IC ——– DigiKey
Adafruit DS1841 Log Digipot Module ———–Adafruit
Adafruit DS3502 Linear Digipot Module ——– Adafruit
LD1085 Variable Voltage Regulator ————– DigiKey
3.5mm Breadboard Audio Jack ——————- Amazon
2.1mm Breadboard Power Jack —————— Amazon
Resources
Code Samples – All the code used in this article in one easy-to-use ZIP file!
Article PDF – A PDF version of this article in a ZIP file.
Adafruit DS1841 – Description and code samples from Adafruit for DS1841 log digipot.
Adafruit DS3502 – Same for DS3502 linear digipot.
Microchip MCP4 Series Digipots – Spec sheets for MCP4 series, including MCP4231.