Two MCP4251 chips |
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.
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:
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
https://github.com/matt448/arduino/tree/master/MCP4251_tcon
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
Matthew McMillan | |
@matthewmcmillan | |
http://matthewcmcmillan.blogspot.com | |
Created 12 Mar 2014 | |
Digital Pot Control (MCP4251) | |
This example controls a Microchip MCP4251 digital potentiometer. | |
The MCP4251 has 2 potentiometer channels. Each channel's pins are labeled: | |
A - connect this to voltage | |
W - this is the pot's wiper, which changes when you set it | |
B - connect this to ground. | |
The MCP4251 also has Terminal Control Registers (TCON) which allow you to | |
individually connect and disconnect the A,W,B terminals which can be useful | |
for reducing power usage or motor controls. | |
A value of 255 is no resistance | |
A value of 128 is approximately 5k ohms | |
A value of 0 is 10k ohm resistance | |
The MCP4251 is SPI-compatible. To command it you send two bytes, | |
one with the memory address and one with the value to set at that address. | |
The MCP4251 has few different memory addresses for wipers and tcon (Terminal Control) | |
*Wiper 0 write | |
*Wiper 0 read | |
*Wiper 1 write | |
*Wiper 1 read | |
*TCON write | |
*TCON read | |
The circuit: | |
* 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) | |
created 12 Mar 2014 | |
Thanks to Heather Dewey-Hagborg and Tom Igoe for their original tutorials | |
*/ | |
// inslude the SPI library: | |
#include <SPI.h> | |
// set pin 10 as the slave select for the digital pot: | |
const int slaveSelectPin = 10; | |
const int shutdownPin = 7; | |
const int wiper0writeAddr = B00000000; | |
const int wiper1writeAddr = B00010000; | |
const int tconwriteAddr = B01000000; | |
const int tcon_0off_1on = B11110000; | |
const int tcon_0on_1off = B00001111; | |
const int tcon_0off_1off = B00000000; | |
const int tcon_0on_1on = B11111111; | |
void setup() { | |
// set the slaveSelectPin as an output: | |
pinMode (slaveSelectPin, OUTPUT); | |
// set the shutdownPin as an output: | |
pinMode (shutdownPin, OUTPUT); | |
// start with all the pots shutdown | |
digitalWrite(shutdownPin,LOW); | |
// initialize SPI: | |
SPI.begin(); | |
} | |
// This loop adjusts the brightness of two LEDs and | |
// uses the TCON registers to indvidually disconnect | |
// the wipers which turns off the LEDs. | |
void loop() { | |
digitalWrite(shutdownPin,HIGH); //Turn off shutdown | |
delay(1000); | |
digitalPotWrite(wiper0writeAddr, 200); // Set wiper 0 to 200 | |
digitalPotWrite(wiper1writeAddr, 200); // Set wiper 1 to 200 | |
delay(1000); | |
digitalPotWrite(tconwriteAddr, tcon_0off_1on); // Disconnect wiper 0 with TCON register | |
delay(1000); | |
digitalPotWrite(tconwriteAddr, tcon_0off_1off); // Disconnect both wipers with TCON register | |
digitalPotWrite(wiper0writeAddr, 255); //Set wiper 0 to 255 | |
delay(1000); | |
digitalPotWrite(tconwriteAddr, tcon_0on_1on); // Connect both wipers with TCON register | |
} | |
// This function takes care of sending SPI data to the pot. | |
void digitalPotWrite(int address, int value) { | |
// take the SS pin low to select the chip: | |
digitalWrite(slaveSelectPin,LOW); | |
// send in the address and value via SPI: | |
SPI.transfer(address); | |
SPI.transfer(value); | |
// take the SS pin high to de-select the chip: | |
digitalWrite(slaveSelectPin,HIGH); | |
} |
Hey Matt,
ReplyDeleteIs it possible to control a 12v motor with a digi pot?
Thanks.
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:
Deletehttp://www.instructables.com/id/Use-Arduino-with-TIP120-transistor-to-control-moto/
Hi Matt
ReplyDeleteThanks 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.
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.
DeleteMatt, will you ever upload part 3 of this article? If you do I will be very thankful, wink wink!
ReplyDeleteThanks for the reminder. I'll try and get back to this. I get distracted easily. ;-)
Deleteagain... ready yet?
DeleteDid you get reading values from the MCP4251 to work?
ReplyDeleteI 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.
Update:
ReplyDeleteI'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
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.
DeleteYou're welcome, Matt.
ReplyDeleteWriting 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.