Controlling a digital potentiometer MCP4561

Published on Author Tomas Stibrany2 Comments

[UPDATE 2.9.2016] If you are missing an I2C library, you can use the one Peter Fleury wrote. You will need to replace my I2C function calls, but that’s it.

Microchip offers quite a few digital potentiometers/rheostats which you might find handy when you want to adjust for example a power supply or audio amplifier digitally.  Unfortunately these potentiometers (at least the MCP4561 which I had used) can’t handle more than ±2.5 mA through each of the pins P0A,P0W and P0B so don’t use it to drive external loads.

MCP45XX and MCP46XX devices are single potentiometers controlled via I2C interface. In this tutorial I will be using MCP4561 which is a 8-bit, 10 kΩ non-volatile digital potentiometer. Non-volatile means that the potentiometer has a EEPROM memory inside from which it loads the wiper value after reset. You can also get a volatile version (MCP4551) which always sets the wiper to middle after reset.

Pinout (taken from the data sheet )  and Test board

MCP45X1 Pinoutmcp4561 on board

 

At first you should decide what I2C address you want to set the potentiometer to.  Manufacturer left only 1 pin – A0 for customization, so there are only 2 options:

// You do not need to shift the address 1 bit to the left
#define MCP4561_ADDR 0b01011100 //A0 connected to GND
//or
#define MCP4561_ADDR 0b01011110 //A0 connected to Vdd

What I like to do at this point is to try to read some register with known value so I know my I2C interface is working correctly. So this is a optional self-check function.(Full source code is at the bottom of the page)

char mcp4561_selfCheck()
{
     return (mcp4561_Read(MCP4561_STATUS) == 0x01F0);
}

Next thing we need to do is enable the potentiometer itself and output pins (P0B, P0W, P0A). We do this by setting a few bits in the TCON register.[codegroup]

void mcp4561_enableAllTerminals()
{
unsigned char config = 0;

config = MCP4561_TCON_R0HW;
config |= MCP4561_TCON_R0A;
config |= MCP4561_TCON_R0W;
config |= MCP4561_TCON_R0B;
mcp4561_Write(MCP4561_TCON, MCP4561_WRITE, config);
}
// Register memory address
#define MCP4561_WIPER0 0x00
#define MCP4561_WIPER0_NV 0x02
#define MCP4561_TCON 0x04
#define MCP4561_STATUS 0x05

// Command Operation
#define MCP4561_WRITE 0x00
#define MCP4561_READ (0x03 << 2)
#define MCP4561_INC (0x01 << 2)
#define MCP4561_DEC (0x02 << 2)

// TCON Register Bits
#define MCP4561_TCON_GCEN (1 << 8) /* General Call Enable bit*/
#define MCP4561_TCON_R0HW (1 << 3) /* Hardware configuration control bit*/
#define MCP4561_TCON_R0A (1 << 2) /* Terminal A connect control bit*/
#define MCP4561_TCON_R0W (1 << 1) /* Wiper connect control bit*/
#define MCP4561_TCON_R0B (1 << 0) /* Terminal B connect control bit */

Now we are all set and we can set the wiper value.

uint8_t mcp4561_setWiper0(uint16_t val)
{
     uint8_t error = 0;
     // Sanity check. Datasheet says there are 257 steps(values) available
     val = val % 257;
     error = mcp4561_Write(MCP4561_WIPER0, MCP4561_WRITE, val);

     /* We also save this value to EEPROM, so after reset,
        this value is set */ 
     error |= mcp4561_Write(MCP4561_WIPER0_NV, MCP4561_WRITE, val);

     return error;
}

There is also an option to increment or decrement wiper value. The value will increment up to 0x100 (P0W=P0A) and decrement down to 0 (P0W=P0B).

void mcp4561_incrementWiper0()
{
     mcp4561_Write(MCP4561_WIPER0, MCP4561_INC,0);
}

void mcp4561_decrementWiper0()
{
     mcp4561_Write(MCP4561_WIPER0, MCP4561_DEC,0);
}

Now only thing that’s left are the read and write functions, so here they are:

uint8_t mcp4561_Write(uint8_t memAddr,uint8_t cmd, uint16_t data)
{
     uint8_t cmdHasData = false;
     uint8_t setVal = 0;

     if ( (cmd != MCP4561_INC) && (cmd != MCP4561_DEC))
     cmdHasData = true;

     setVal = ((memAddr << 4) & 0xF0); // 4b Memory address      
     setVal |= cmd; // 2b Command operation      
     setVal |= (((data & 0x01FF) >> 8) & 0x03); // 2b Data

     i2c.Write(setVal);
     if (cmdHasData) {
          i2c.Write(data & 0x00FF);
     }
     // args: I2C address, number of bytes to receive
     i2c.StartTransmittion(MCP4561_ADDR,0);
     i2c.Wait();
     _delay_ms(10);

     if (cmdHasData){ // and so we can check whether it has accepted the setting
          uint8_t set_reading = mcp4561_Read(memAddr);
          if (set_reading == data)
          {
               return 0;
          }
          return 1;
     }
     return 0;
}

uint16_t mcp4561_Read(uint8_t memAddr)
{
     uint8_t cmd = (memAddr << 4) | MCP4561_READ;
     uint16_t data;

     i2c.Write(cmd);
     // args: I2C address, number of bytes to receive
     i2c.StartTransmittion(MCP4561_ADDR, 2);
     i2c.Wait();

     data = (i2c.Read() << 8);
     data |= i2c.Read();

     return data;
}

Test setup

MCP4561 Middle Value measurement

 

 

 

 

 

 

 

This is my test setup.  Wiper value was set to 127 and the multimeter measured P0W to P0A resistance of 4.91 kΩ.

And finally, here is the full source code. The code is written with the help of custom I2C library for AVR (as you’ve probably found out by now) so you will need to rewrite the read and write functions a bit.

Full source code

zip icon
MCP4561_driver.zip (3 kB)

 

 

2 Responses to Controlling a digital potentiometer MCP4561

Leave a Reply

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