Monday, March 17, 2014

Arduino - Using digital potentiometers part 2 (MCP4251)

This is part two in a series of posts about using digital potentiometers with Arduino boards. Part one covered the AD8403 digital pot. This post will go over the MCP4251 from Microchip. The MCP4251 is a dual pot chip with the capability to individually disconnect the terminals of each wiper through software and a hardware shutdown pin that shuts down both wipers simultaneously. Communication with the chip is done over SPI. The chip is available in DIP and surface mount configurations. I bought the DIP version so I could use them on a breadboard. The specific part number I bought is MCP4251-103E/P which is a 10k ohm model with an 8 bit resistor network. The 8 bit versions have 256 possible positions for the wiper which works out to approximately 39 ohms increase in resistance for each wiper position on a 10k ohm model.
Two MCP4251 chips
I began searching for an Arduino software library for these pots. I found two but neither of them fit my needs. They didn't implement any of the TCON functionality which was the main reason I was interested in these pots. Here are links to those libraries if you are interested in trying them out:
   https://github.com/jmalloc/arduino-mcp4xxx and https://github.com/teabot/McpDigitalPot

Since there wasn't a preexisting library that would work for me I began figuring out how to talk to this chip. I started with the Arduino example in File > Examples > SPI > DigitalPotControl. The AD840x and AD520x series pots work right out of the box with this example but the MCP4xxx pots use different memory addresses so I started tweaking the example.

Understanding how to talk to the MCP4251

So lets start with the very basics of how to talk to these pots. Sending a command over SPI requires four steps:
1. Take the slave select pin LOW. This tells the chip to listen for commands.
2. Send the memory address for the item we want to change using SPI. This is the memory address for a wiper or terminal connections. This tells the chip what we want to change.
3. Send the new value for the item we specified in step 2. Wipers on the MCP4251 have 256 possible positions so this would be a decimal number between 0-255 or binary B00000000 - B11111111.
4. Take the slave select pin HIGH. This tells the chip to execute the changes.

The AD8406 covered in part 1 used decimal numbers 0-5 as memory addresses for each of the wipers which was very easy to understand. The MCP4251 doesn't use sequential values so I had to go digging in the data sheet to find the right values. Here is the memory map table from the data sheet:


Looking at the data sheet you can see the memory address and the data is made up of a total of 16 bits. The sheet says the data is 10 bits and the memory address is 6 bits but in practice you can send the data in two 8 bit chunks which allows you to use the 'B' binary formatter. The maximum possible value for a wiper is 255 which would be B11111111 in binary. So here is the list of memory addresses and tcon values I was able to determine:

wiper0writeAddr = B00000000;
wiper1writeAddr = B00010000;
  tconwriteAddr = B01000000;
  tcon_0off_1on = B11110000;
  tcon_0on_1off = B00001111;
 tcon_0off_1off = B00000000;
   tcon_0on_1on = B11111111;

The Wiring

Now that I had memory addresses figured out I wired up the digital pot on a breadboard with some LED's. I'm using LED's in this example because it's a good way to visualize the pots changing resistance values. I wired the shutdown pin to a 4.7k pull down resistor so the pot would go into shutdown mode if digital pin 7 wasn't HIGH. My example code also uses the software disconnects (TCON) to turn the LED's off and on.

The connections are:
* All A pins of MCP4251 connected to +5V
* All B pins of MCP4251 connected to ground
* An LED and a 220-ohm resistor in series connected from each W pin to ground
* VSS - to GND
* VDD - to +5v
* SHDN - to digital pin 7 and a 4.7k pull down resistor
* CS - to digital pin 10 (SS pin)
* SDI - to digital pin 11 (MOSI pin)
* SDO - to digital pin 12 (MISO pin)
* CLK - to digital pin 13 (SCK pin)



You can download the fritzing file here:
https://github.com/matt448/arduino/raw/master/MCP4251_tcon/MCP4251_tcon.fzz

and you can download the MCP4251 fritzing part I made here:
https://github.com/matt448/arduino/raw/master/MCP4251_tcon/MCP4251.fzpz




The code

Here is a Gist with the example code. The most recent version will be my the github repo here:
https://github.com/matt448/arduino/tree/master/MCP4251_tcon


Up next

Part 3 in my series of digital potentiometer posts will cover reading data from the MCP4251 to determine the wiper positions and the tcon status. Part 4 will cover using multiple SPI digital potentiometers. I will add a links here as I complete those posts.

11 comments:

  1. Hey Matt,
    Is it possible to control a 12v motor with a digi pot?

    Thanks.

    ReplyDelete
    Replies
    1. If you trying to control the speed of a motor it would be better to use pulse width modulation and a transistor. Motors draw a lot of current which most digital pots can't handle. A TIP120 transistor can handle 5 amps. I found this how-to page that might be useful for you:

      http://www.instructables.com/id/Use-Arduino-with-TIP120-transistor-to-control-moto/

      Delete
  2. Hi Matt

    Thanks for this has been really useful for use in my project for University.

    Was wondering however as I am using a Raspberry Pi instead of the Arduino if that example code is transferable or if I will have to convert/modify.

    Thanks in advance.

    ReplyDelete
    Replies
    1. You will certainly have to modify it some. How much I suppose depends on what programming language you will be using on the Pi and if that language has an SPI library. My understanding is Arduino code is similar to C.

      Delete
  3. Matt, will you ever upload part 3 of this article? If you do I will be very thankful, wink wink!

    ReplyDelete
    Replies
    1. Thanks for the reminder. I'll try and get back to this. I get distracted easily. ;-)

      Delete
  4. Did you get reading values from the MCP4251 to work?

    I found this page after getting writing to the pots to work by hacking someone else's non-working example code, but I can't get reading the status byte to work. It should just return one of two values, depending on the shutdown pin, but I only get one value, whether I ground it or not, though the chip does go into shutdown when I do.

    FWIW, the write (and read) should be a 16 bit transfer, according to the datasheet, but it seems it doesn't need to be, since your code apparently works (I haven't tried it).

    I have the write command like this:

    SPI.transfer16((potNumber<<12) | value);

    - where potNumber is 0 or 1.


    For reading the status, I tried:

    const int readSTATUScmd= 0x5fff; // only the top six bits matter

    ...

    unsigned int statusByte() { // Return the contents of the status register of the chip
    delay(10); // Probably not necessary, just there until I see it working.
    return SPI.transfer16(readSTATUScmd);
    }

    - The datasheet shows an overlap of the command word being sent, and the 9 bit data value being sent back, so after receiving the important first six bits, it should be sending back the data bits. For me, that doesn't work.

    ReplyDelete
  5. Update:

    I've realised I didn't work the CS input, so I have now got it to work.

    The relevant parts:

    const int readSTATUScmd= 0x5c00;
    //const int readSTATUScmd= 0x5fff; // either is fine - only the top six bits matter

    const int slaveSelectPin = 10; // The CS pin, active low

    unsigned int statusByte() { // Return the contents of the status register of the chip
    //delay(10);
    digitalWrite(slaveSelectPin,LOW);
    return SPI.transfer16(readSTATUScmd) & 0x1ff;
    digitalWrite(slaveSelectPin,HIGH);
    }

    void showTable() // Show the values in the registers
    {
    unsigned int val;
    //delay(10);
    for(int addr=0; addr<6; addr++) // 2-3 are reserved, so always zero, but never mind
    {
    int cmd= (addr<<12) | 0xc00; // specify a READ, with the address as the top 4 bits
    digitalWrite(slaveSelectPin,LOW);
    val= SPI.transfer16(cmd) & 0x1ff; // 9 bit value
    digitalWrite(slaveSelectPin,HIGH);
    Serial.print(" A"); Serial.print(addr); Serial.print("=="); Serial.print(val); Serial.print(" ");
    }
    }

    // You probably want Serial.print("\n"); after using this

    ReplyDelete
    Replies
    1. Nice! I didn't put much effort into reading values. My company ended up scraping the project that used these digi-pots so I never went back to look at this. Thanks for posting your code.

      Delete
  6. You're welcome, Matt.

    Writing to them is the important thing, definitely. Mostly, I was determined to get reading to work as well because this is the first time I've tried using an SPI device, and I wanted to get the hang of it properly.

    ReplyDelete

Please note all comments are moderated by me before they appear on the site. It may take a day or so for me to get to them. Thanks for your feedback.