Controlling a 10-bit Digital Potentiometer via SPI with Arduino

1. A brief introduction to the Serial Peripheral Interface (SPI)

The Serial Peripheral Interface a.k.a SPI or four-wire serial interface is a full-duplex serial data protocol used by microcontrollers for communicating with one or more peripheral devices. Devices communicate in master/slave mode and there are four logic signals:

  • - MOSI: Master Out Slave In, is the master line to send data to the peripheral.
  • - MISO: Master In Slave Out, is the slave line to send data to the master.
  • - SCK: Serial Clock, the clock pulses which synchronize data transmission generated by the master.
  • - SS: Slave Select, the pin on each device that the master can use to enable and disable specific devices.

For more on this topic, visit Wikipedia's page on SPI.

2. Wiring the Master - Slave

For this tutorial we will use the Non-volatile memory, 1024 Position (10-bit) Digital Potentiometer AD5131 from Analog Devices. Wiring the IC to Arduino should be pretty straight-forward by looking at the pin configuration from the Data Sheet.

You might encounter different naming conventions for the lines on the SPI interface; the most commonly used are:

  • - The Motorola terms: MOSI and MISO for 'Master Out Slave In' and 'Master In Slave Out'.
  • - The PIC terms: SDI and SDO for 'SPI Data In' and 'SPI Data Out'.

MOSI always connects to MOSI or SDI (slave) and MISO always connects MISO or SDO (slave). CLK or SCK both refer to the serial clock and SS (slave select) or CS (chip select) are the same line.

On the Arduino Uno or Duemilanove the SPI lines are available from the digital pins 11 (MOSI), 12 (MISO) and 13 (SCK). Also, MOSI, MISO and SCK are available in a consistent physical location on the ICSP header.

If you are using a different Arduino board, the SPI lines might be located on different pins numbers. To find this information, please go to the SPI Library reference on the Arduino website.  Please note that when using the SPI feature on the Arduino, the digital pins 11, 12 and 13 can't be used as digital I/O.

Now, let's take a look at our wiring schematic.

Note that, as in this example we are not using all the features from the AD5231.  We left some of the pins unconnected (O1, O2 and RDY). Also, the pins marked with the optional features Write Protect (WP) and Hardware Override (PR) should be connected to VDD if they are not used.

3. Basic Operation

The AD5231 Digital Potentiometer is designed to operate as a true variable resistor replacement device for analog signals. The digital potentiometer wiper position is determined by the RDAC register contents (Address 0x0). The RDAC register acts as a scratchpad register, allowing as many values changes as necessary to place the potentiometer wiper in the correct position.

The basic mode of setting the variable resistor wiper position (programming the scratchpad register) is accomplished by loading the serial data input register with the instruction: 0xB, Address 0x0, and the desired wiper position data.

4. Serial Data Interface

The AD5231 uses a 24-bit serial data-word loaded Most Significant Bit first (MSBFIRST). The CS (chip select) pin must be held LOW until the complete data-word is loaded into the SDI pin. When CS returns to HIGH, the serial data-word is decoded. The command bits (Cx) control the operation of the digital potentiometer. The address bits (Ax) determine which register is activated. The data bits (Dx) are the values that are loaded into the decoded register.


MSB | Command Byte 0
Data Byte 1
Data Byte 0 | LSB
C3 C2 C1 C0 Ax Ax Ax Ax
X X X X X X D9 D8
D7 D6 D5 D4 D3 D2 D1 D0

5. Programing the Interface with Arduino

The Arduino SPI library allows us to transfer [SPI.transfer(byte)] only 8-bits (1 byte) at a time. In order to transfer a complete instruction (24-bits) to the digital potentiometer we will have to send the instruction in 3 parts of 8-bits each.

The first byte or 'Command Byte' is composed by the Command + Address which allows us to write data into the RDAC register:

Command = 0xB = 1011
Address = 0x0 = 0000
Command + Address = 10110000

The following 2 bytes, the Data Bytes, contain the 10-bits (from 0 to 1023) wiper position. The Data Byte 1 only contains 2 bits from this value and 6 bits that are not used (X). In order to achieve this operation we will use the bitwise operands Logical Right Shift '>>' and AND '&'.

If you are not familiar with bitwise operations, I suggest reading  the 'Absolute Beginner's Guide to Bit Shifting' post from StackOverflow.

For this example, we will assume that we want to send the value 620 to the digital potentiometer to move the Wiper W to approximately 60%  full-scale position, then:

620 = 0000001001101100
620 >> 8 = 00000010 //Shifting 8 bits to the right
620 & 11111111 = 01101100  //Truncate to 8-bits on the LSB side

And finally, the Arduino code.

#include <SPI.h> 

const int csPin = 10;

void setup() {

 SPI.setBitOrder(MSBFIRST); //We know this from the Data Sheet

 digitalWrite(csPin, HIGH);

void loop() {
 for(int i=0; i<1023; i++) { 

void digitalPotWrite(int address, int value) {
 digitalWrite(csPin, LOW); //select slave
 byte command = 0xB0; //0xB0 = 10110000 
 command += address; 
 byte byte1 = (value >> 8);
 byte byte0 = (value & 0xFF); //0xFF = B11111111
 digitalWrite(csPin, HIGH); //de-select slave

There are much more features available in the AD5231 such as memory storing and restoring, increment/decrement, ±6 dB/step log taper adjustment, wiper setting read-back and extra EEMEM  for user-defined information, that are not covered in this post.

Juan-Manuel Fluxà

Juan-Manuel Fluxà is a founder at ReignDesign. Fluxà is from Chile and is currently based in Barcelona, Spain where he heads ReignDesign's European operations.


  1. Hi @Shintaro,
    For the MAX5483 it would be a bit different. By looking at the Datasheet, first thing is you have to select the interface mode you want to use. For selecting the SPI interface you have to pull the SPI/UD pin (pin 6 on TSSOP package) UP. So, just wire up a digital output from the arduino to this pin and make sure is HIGH before trying to write into the interface.
    Second thing, take a look at the “Table 2. Command Decoding” (Datasheet page 14). The writing command is B00000000 and your Data bytes are MSB to the left, so the code would look something like this (not tested):
    void digitalPotWrite(int address, int value) {
    digitalWrite(csPin, LOW); //select slave
    byte command = 0x0;
    byte byte1 = (value >> int(value/255));
    byte byte0 = ((value & B11) << 6); SPI.transfer(byte1); SPI.transfer(byte0); digitalWrite(csPin, HIGH); //de-select slave } Good luck.

  2. Something different:
    The value sequence does not seem to “loop”:

    void loop() {

    I think it is because I still dont understand the address part of “void digitalPotWrite(int address, int value)”

  3. I’ve tried using your code for a 10-bit DAC (MAX5250). The
    data sheet says it takes 16 bits of data so I did the following
    code just to see if I could get an output of near 3.3V since I have
    3.3V as my refAB voltage. I’ve got pin 3 of the chip hooked up to a
    multimeter though, and the voltage is jumping all over the place.
    Could you look at my code and the datasheet and maybe give some
    insight as to what I’m doing wrong? Do I need to change the
    setdatamode or setclockdivider? #include const int csPin = 10; void
    setup() { SPI.begin(); SPI.setBitOrder(MSBFIRST); //We know this
    from the Data Sheet pinMode(csPin,OUTPUT); digitalWrite(csPin,
    HIGH); } void loop() { digitalWrite(csPin, LOW); //select slave
    SPI.transfer(B00111111); SPI.transfer(B11111100);
    digitalWrite(csPin, HIGH); //de-select slave delay(100); }

  4. Hi, I’m setting the AFE4300, asking me to enter some records set, but when the address and data are sent, you have to do something else such as shift the bits in registers or SPI.transfer (0x00) is enough.
    Here is my code.


    const int slaveSelectPin = 10;
    void setup() {
    pinMode (slaveSelectPin, OUTPUT);
    //digitalWrite (slaveSelectPin, HIGH);

    void loop() {
    digitalWrite (slaveSelectPin, LOW);
    //REGISTRO [23,16] 0 0 0 0 0 0 0 1
    //DATA MSB [15,8] 0 1 0 0 0 0 0 1
    //DATA LSB [7,0] 0 1 0 0 0 0 0 0
    //Apuntamos al registro 01
    SPI.transfer (0x01); //ponemos el bit 21 a ‘0’ para escribir datos
    SPI.transfer (0x41); //escribimos DATA el MSB [15…8]
    SPI.transfer (0x40); //escribimos DATA el LSB [7…0]
    digitalWrite (slaveSelectPin, HIGH);
    SPI.end ();

  5. Thanks for this post Juan!!! SO helpful.

    For everyone that is interested in 10 bit code requiring a 16 bit word for MAX devices see my blog post here where use highByte() and lowByte() to split a 16 bit word from one decimal number, blissfully simple.

  6. I know this is an older thread but I am running one the setup you showed above. Do I have to have the A and B connected to the arduino’s Ground and 5v or can I use those three leads on a completely separate circuit? Also is this chip able to handle 0 – 10 volts on the A and B. I will supply 5v on the vdo,wp,pr.


Leave a Reply

Your email address will not be published. Required fields are marked *