Friday, June 25, 2010

Digital Potentiometers (MCP42010)

Digital Potentiometers (MCP42010)
 


Potentiometers act as voltage dividers but are also commonly configured as Rheostats (variable resistors.)  Potentiometers, or ANALOG potentiometers in this case, are used to control the volume of your radio, motor speeds, lamp dimmers, or to drive the remote control of your toy remote control car.

In my case I had such a toy car and I wanted to determine a way to use an Arduino micro-controller to drive it (and with a wireless router controlling the Arduino, which will be in a future post.)  At the time, I was unable to determine how the remote actually worked in order to build a replacement for it so I did the next best thing.  As I could not control the ANALOG potentiometers in the remote, I instead decided to learn how to use DIGITAL potentiometers instead to do what the analog potentiometers were doing.  After reading some articles by Sebastian Tomczak on the Internet, I decided that the digital potentiometer ICs that he was using would also suit my purpose as well.  His purpose was to use them for varying sounds but in effect we were both using the digital potentiometer to do the same basic thing.

I ordered 4 digital potentiometer ICs from Jamco; two dual 10k digital potentiometer ICs and two single 10k digital potentiometer ICs.  (Dual MCP42010, Single MCP41010.)  This would be my first dealing with SPI communication as that is how these chips would need to have their resistances set.  According to their data sheet, to program these chips you would need to send a "Command Byte" that told the chip what it was about to do and a "Data Byte" that told the IC what value of resistance to be set at from 0 to 255.  For example, after sending the Command Byte, a 10k potentiometer could be set at 5k by sending the data byte of 10000000, or to 2.5k by sending 01000000.

I based my SPI programing code for the digital potentiometers on the "SPI By Hand" by Sebastian Tomczak.  In this one-way SPI communication for programming the ICs, the code would call "spi_out" passing the proper ChipSelect pin, Command Byte, and the Data Byte value.  Spi_out out would handle the ChipSelect pin and then call spi_transfer which did the actual sending each of the two bytes to send.  This process is commonly known as "Bit-Banging" or "Bit-Shifting" depending on your desired level of formality.  I have included example code at the bottom of this post from my "SuperCar" program.

My initial tests of my new digital potentiometers were with LEDs.  First I coded a for loop that would gradually decrease the resistance on the LED.  Next I coded another for loop that would gradually increase the resistance of the LED.  In effect I made the LED glow.  (Youtube video below.)

Later, control of those digital potentiometers became the heart of my "SuperCar" program as the project controlled the original remote (and not the car directly.)  I have included a youtube video of an early version of "SuperCar" that shows some of the initial electronics hookups and the ability to control the car by sending commands to the digital potentiometers connected to the car's remote. 

References:
Heather Dewey-Hagborg
SEBASTIAN TOMCZAK

Serial Peripheral Interface Bus - Wiki

SPI Bit Banging
http://en.wikipedia.org/wiki/Bit-banging



Example Arduino Code:  (incomplete, but the point is demonstrated)
int CS1     = 2  ;             // 1st Digital Potentiometer IC - TURN
int CLKdpot = 4  ;             // SHARED Clock for both Digital Potentiometer ICs
int MOSI    = 5  ;             // SHARED MOSI for both Digital Potentiometer ICs
int CS2     = 8  ;             // 2nd Digital Poteniometer IC - DRIVE
byte cmd_byte2 = B00010011  ;  // Binary values for sending to both pot1 and pot2 on the Digital

int resPARK   = 100

void NoDrive()  { spi_out(CS2, cmd_byte2, resPARK);  }    // Send PARK values 

void spi_out(int CS, byte cmd_byte, byte data_byte)
{
digitalWrite (CS, LOW);       // Set the passed ChipSelect pin to low to start programming
  spi_transfer(cmd_byte);     // Send the passed COMMAND BYTE
  delay(2);
  spi_transfer(data_byte);    // Send the passed DATA BYTE
  delay(2);
digitalWrite(CS, HIGH);       // Set the passed ChipSelect pin to high to end programming
}

void spi_transfer(byte working)
{
for(int i = 1; i <= 8; i++)                          // Set up a loop of 8 iterations (8 bits in a byte)
{ if (working > 127) { digitalWrite (MOSI,HIGH) ; }  // If the MSB is a 1 then set MOSI high
else { digitalWrite (MOSI, LOW) ; }                  // If the MSB is a 0 then set MOSI low
digitalWrite (CLKdpot,HIGH) ;                        // Pulse the CLKdpot high
working = working << 1 ;                             // Bit-shift the working byte
digitalWrite(CLKdpot,LOW) ;                          // Pulse the CLKdpot low
}
}

4 comments:

  1. Thanks SO MUCH for posting this! Been tearing my hair out, trying to work out how to handle the Command Byte and this has helped me no end.
    Sarah

    ReplyDelete
  2. Thanks also for the code.

    One general question. Why do all of your functions start with a semicolon?

    I've never encountered this other than to halt execution as in while(1);

    ReplyDelete
    Replies
    1. Ah! Opps! The semi-colons are not required there. I'll remove those from the blog article. Thanks!

      Delete

Keep it clean. :)