NerdKits - electronics education for a digital generation

You are not logged in. [log in]

NEW: Learning electronics? Ask your questions on the new Electronics Questions & Answers site hosted by CircuitLab.

Microcontroller Programming » Programming Digital Potentiometers with the ATMega 168

May 14, 2010
by Malicious
Malicious's Avatar

Hello everyone,

I have recently purchased a MCP4251 IC to use a digital POT for controlling a submarine. Extensive Background info can be found on this thread. Anywho, I have no idea as to how to begin to program the ATMega168 to control this chip. I have knowledge of C and PWM adn the such, but as I Was browsing over the Data Sheet for the MCP I got more and more lost the more I read. I know I have to set some registers and use serial communication from the ATMega to change the wiper, but could someone point me in the right direction as far as coding goes?

Thank you very much in advance.

Malicious

May 14, 2010
by Malicious
Malicious's Avatar

So I re-read some of the Data Sheet, I figured I have to use Serial Commands to tell the pot what to do, these are the available two-bit commands 1,1 - 1,0 - 0,0 - 0,1. I concluded I have to use UART or USART on the ATMega168, but I have no idea how. I could not find any tutorials so any help is appreciated.

The Data Sheet also specified that the POT pin accepts either positive or negative current, does this mean that is does not matter how I connect pins A and B of each POT?

Malicious

May 14, 2010
by Malicious
Malicious's Avatar

Sorry for the mass amounts of posting, but I also noticed that the ATMega168 uses pins 2 and 3 for UART communication. Does that mean that I cannot receive and send data from the computer (via serial) and the digital pot IC at the same time? Or can I set otehr pins on the ATMega168 to utilize UART?

Malicious

May 14, 2010
by Rick_S
Rick_S's Avatar

Looks like that chip is controlled via SPI. This would be the same set of pins used for ISP programming of the chip. Check out the "Multi-Panel LED Array with SPI" tutorial to get an idea of how this interface works. Didn't look close enough to see the actual protocol, but you would not use the TX/RX lines of the mcu for communication to this.

Rick

May 15, 2010
by Malicious
Malicious's Avatar

Rick,

Ok, I checked out that tutorial and got my that IC wired up to my ATMega168 correctly and I'm good to go. I'm about to check out the code for that turoial, but I was wondering how exactly I am going to differential between the two potentiometers on the chip? I read section 7 of the Datasheet and see that I hace to send 16-bit commands, and I can easily see what command does what, there are only four,but I do not understand the other 14-bits...

and gurus out there with a spare second? It would be much appreciated.

-Malicious

May 16, 2010
by hevans
(NerdKits Staff)

hevans's Avatar

Hi Malicious,

I think the answer to your question is on pages 47 and 48 of your datasheet. Basically you are going to be sending bytes down the SPI bus t your chip. You can send either 8 bit commands or 16 bit commands. In either case the 4 most significant bits of the command are an address. The table on page 48 of the datsheet lets you know that an address 0x00 (hex 0) is wiper 0, and 0x01 is wiper 1. The next 2 bits determine the command, and if required the next 10 bits are data. Does that make sense?

Humberto

May 18, 2010
by Malicious
Malicious's Avatar

Yes, Makes complete sense. The issue I am having is that I have no clue as to how to convert that logic to C code. I poured over the LED tutorial code, and came up with something like this using the SPI stuff:

// initialload.c
//
// Aaron Melhorn
// 05/18/10

#include <stdio.h>

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <inttypes.h>

#include "../libnerdkits/delay.h"
#include "../libnerdkits/uart.h"

// PIN DEFINITIONS:
// PB5 - SCK
// PB4 - MISO (Master In Slave Out)
// PB3 - MOSI (Master Out Slave In)
// PB2 SS pin for slave panels

#define ROWS 5
#define COLS_PER_ARRAY 20
#define NUM_ARRAYS 4
#define COLS (NUM_ARRAYS * COLS_PER_ARRAY)

//keeps the entire array. NUM_ARRAYS * COLS_PER_ARRAY
uint8_t data;

void forward() {
data = 00000100;
data_set();
}

void left() {
data = 00010100;
data_set();
}

void right() {
data = 00011000;
data_set();
}

inline void data_set() {

//Send the command to the chip, one bit at a command
uint8_t i;
for(i=0; i<8; i++) {
    data |= (1<<i);
}

}

//Sets the Master Chips Inputs and Outputs
void master_init(){

  //set MOSI,SCK,SS as output
  DDRB |= (1<<PB3) | (1<<PB5) | (1<<PB2);
  //initiate the SPI module in master mode, data rate clk/16
  SPCR |= (1<<SPE) | (1<<MSTR) | (1<<SPR0);

  //set the SS pins we are using as outputs
  DDRB |= (1<<PB2);

  //set the SS pin logic high (slave not active)
  PORTB |= (1<<PB2);

  //set up timer, when this fires all the slave panels are updated
  //timer set to clk/1024 - aprox 56Hz 
  TCCR0B |= (1<<CS02) | (1<<CS00);
  TIMSK0 |= (1<<TOIE0);

}

//Ready the POT IC for Commands (active)
inline void activate_POT(){

  PORTB &= ~(1<<PB2);

  delay_us(5);
}

//Turn off the chip from recieving commands (not active)
inline void deactivate_POT(){

  PORTB |= (1<<PB2);

  delay_us(5);
}

//timer has overflowed, update all the panels
SIGNAL(SIG_OVERFLOW0) {

}

int main() {

  master_init();

  // activate interrupts
  sei();

  // init serial port
  //This if For Computer Input
  uart_init();
  FILE uart_stream = FDEV_SETUP_STREAM(uart_putchar, uart_getchar, _FDEV_SETUP_RW);
  stdin = stdout = &uart_stream;

  char x=' ';

  while(1) {
x = uart_read();

//Controls
//Forward
if(x == 'w') {
    forward();
}
//Left
else if(x == 'a') {
    left();
}
//Right
else if(x == 'd') {
    right();
}
//Do Nothing
else {

}

  }
  return 0;
}

I have not have a chance to actually test the code because I will not be home for a few hours, but I was wondering how close or far I am from having the SPI set up correctly? I tried to set it up so that forward only increments the 0 Wiper once, and the left right increment and decrement the 1 Wiper. Also am I sending the commands correctly? I'm not completely sure of the use of 0x00 as opposed to 0000 so I may be missing something. I wish there was a book or tutorial that listed the syntax for programming Micro controllers, is there any such beast?

Malicious

May 18, 2010
by hevans
(NerdKits Staff)

hevans's Avatar

Hi Malicious,

You have some of the right ideas, but you are not all the way there yet. There is a bunch of code in there from the LED Array itself, all the timers and the timer setup. You should remove that as it just causing clutter and confusion.

The SPI module on the ATmega168 will actually take care of much of the SPI timing and stuff. Once you set it up right, all you have to do is set your data in the the SPDR register (SPI Data Register), and the SPI module will begin clocking out the data one bit a time, and toggling the clk line correctly. Take a read through the datasheets section on the SPI but, its a bit complicated, but not impossible to understand, they even have a few very simple code samples. In your code above I think you will want to set the SPDR register in your set_data() method.

You are also not quite building up the data byte you want to set correctly, you have the right idea, but when you write 00000100 you are writing a decimal literal. So you set your variable equal to one hundred. What you want is to set a particular bit in a variable. There a few ways to go about doing that. One way you can do it is to just figure out the value you want and set it in decimal. So for example the binary number 00000100 is the number 4 in decimal. So you can just set it 4. The other way is to set the individual bits of the variable using shifts, ORs, and ANDs like we teach you in the NerdKits Guide. Our bits bytes and binary video tutorial might have a little more insight.

Hope that gets you going it the right direction.

Humberto

May 19, 2010
by bretm
bretm's Avatar

Technically, since it starts with a '0', the value 00000100 is an octal literal, equal to decimal 64, not decimal 100. Binary literals can be specified by the '0b' prefix, e.g. 0b00000100.

May 20, 2010
by Malicious
Malicious's Avatar

Wonderful.

Humberto, good look on the ATMega Data sheet, I completely forgot about it because I was focusing on the digital pot too much. The little snippet of code, and your guys info on the binary values got me on the right track. Everything is hooked up and working correctly. I'm using two red LEDs just so I can see what is happening for now. The only issue I noticed was the digital pot retains the setting on the wiper even after reset or power loss, is that correct? I would have thought it would have reset since it is digital?

I also have a question regarding the implementation into my whole project, but I'll ask that in a more appropriate thread.

Thank you guys for all your help.

May 20, 2010
by Malicious
Malicious's Avatar

So I got a new question....well I got more then one, but I like figuring most things out on my own.

I need the potentiometer to mimic a range of resistance from 25,000 ohms to 9,000 ohms, I currently have it drawing 5v with a 10,000 ohm resistor connected to the wiper. The range is currently 16,000 ohms to 9,000. How would I go about widening the range of resistance?

May 20, 2010
by Malicious
Malicious's Avatar

Nevermind, I found out I that is not possible. I ordered a digital POT with a 50k range, since I can easily narrow the range, and will wait until that gets here.

Referring back to my post about the pot retaining the wiper setting, I read in the Data sheet that once the voltage on the Vdd pin crosses the voltage on the Vss (powers on) the chip should set both registers to their default value. I had the Digital POT wired directly to the positive rail meaning that I assume it was always receiving a small voltage, which caused it never to reset, so I wired it to the PB3 pin on the ATMega, and turned in on once the chip was on. This reset the Digital POT every time, but ruined my SPI functionality. Any advice? I also read about the BOR (Brown out Reset) but since it didn't work as I expected, I assume I am misunderstanding something.

May 20, 2010
by hevans
(NerdKits Staff)

hevans's Avatar

Hi Malicious,

PB3 is the SPI pin, it is toggling back and forth when you are attempting to send data. If you have that connected to Vss for your chip it will constantly be resetting. Did you try connecting the power for your digital pot to a different pin that is not being used, and making that pin high when you want to turn your chip on?

From a design standpoint, my suggestion would be not to count on a power reset bringing things back to a "default" state. It is a much better solution to define the default state in your code and set the wipers to a known state as part of your initialization routine. That way you will be positively sure of where the wipers are on a power reset.

Humberto

May 20, 2010
by Malicious
Malicious's Avatar

I mis-spoke, It was actually connected to PC3 not PB3. And you are correct about initializing the start point in code, I just read the POR/BOR section of the datasheet and figured I would try to use something the chip had built in, just for experience.

May 30, 2010
by Malicious
Malicious's Avatar

So I was trying to set the initial value for POT0 on the IC to the exact mid-point, which the Datasheet specifys by sending 80h to the chip. In the Datasheet it says that I must send 10 bits, along with the 6 location and command bits, in order to complete and action (read/write). However, 80h is 01010000, only 8 bits. I tried sending that across but got strange results, what am I doing wrong?

May 30, 2010
by Malicious
Malicious's Avatar

So I was trying to set the initial value for POT0 on the IC to the exact mid-point, which the Datasheet specifys by sending 80h to the chip. In the Datasheet it says that I must send 10 bits, along with the 6 location and command bits, in order to complete and action (read/write). However, 80h is 01010000, only 8 bits. I tried sending that across but got strange results, what am I doing wrong?

May 30, 2010
by Malicious
Malicious's Avatar

BTW, page 48 has the binary commands and page 38 has the mid/full settings

May 30, 2010
by BobaMosfet
BobaMosfet's Avatar

Malicious--

0x80 = 0100 0000 in binary (128 in decimal).

Since they need 10 bits, then add 2 leading zero bits.

0x80 = 00 0100 0000

:::::::::

I didn't read it enough to determine your address value, so we'll just use 'XXXX' in place of the binary bits. Your 16-bit command would look like this:

AAAA CC DDDDDDDDDD
------------------
XXXX 00 0001000000

BM

June 01, 2010
by Malicious
Malicious's Avatar

Ah!

BM,

Thank you, I was adding the zeros to the right of the command, not the left.

-Malicious

June 14, 2010
by Malicious
Malicious's Avatar

I know its has been a little while, but I am still trying to tell the Digital Pot exactly what wiper setting to use, as opposed to using the decrement and increment commands. I attempt to set the pot to its mid point with the binary data "data = 0b0000000001000000" which, according to the Datasheet - I have link in the first post - should place the wiper at the midpoint - This seems to work, or at least I thought it did, but sending "data = 0b0000000000000000" should set the wiper to 0 which it does not, and "data = 0b0000010000000000" should set the wiper to its maximum setting. I did notice that the wiper settigns seemed to range from decimal 0 to 256, which I should also be able to send over, however, the decrement and increment commands are decimal 4 and 8 respectively. So, if you understood that reambaling, what the heck am I doing wrong? I even tried sending hex values across, since that is what the datasheet gives you, but nothing works? Any suggestions or solutions?

June 14, 2010
by hevans
(NerdKits Staff)

hevans's Avatar

Hi Malicious,

Just a thought. How are you sending the 16 bit commands? The SPI module is built for sending 8 bits at a time, if you are putting a 16 bit number onto the SPDR register it is only going to send 8 bits. It looks from the datasheet like you are sending the bits most significant bit first, which means you have to send the top 8 bits first, then right after start sending the next 8 bits. Hope that gets you on the right track.

Humberto

June 14, 2010
by Malicious
Malicious's Avatar

I was using the SPI module, so I tried using
data = 0; data_set(); data = 128; data_set();

void data_set() { activate_POT();

/ Start transmission / SPDR = data;

/ Wait for transmission complete / while(!(SPSR & (1<<SPIF))) ;

deactivate_POT(); }

but everything went screwy, and the POT did not seem to resopnd...

June 14, 2010
by hevans
(NerdKits Staff)

hevans's Avatar

Hi Malicious,

What is your activate_POT() and deactivate_POT() functions doing? You definitely cannot toggle the SS line in between the two bytes. Your digital pot expects the 16 bits to just come continuously one after the other. Perhaps you will need to make a data_set_16bit() function that handles the 16bit communication differently than the 8-bit communication.

Humberto

June 14, 2010
by BobaMosfet
BobaMosfet's Avatar

hevans-

Great insight and true. I have gone through the datasheet on this chip that malicious is using, as well as the ATMEGA168 for SPI communications, and have some notes. I think one of the first things that needs checked (and no one has asked this) is, what is he using to clock this chip with? It requires (up to) a 10MHz clock input. That can be a crystal, or the ATMEGA168 generating a clock at even 8Mhz.

Also, this chip requires using both the 16 and 8-bit commands to talk to it, not just one or the other. The voltage and primarily the amperage limits MUST be paid careful attention to.

BM

June 14, 2010
by Malicious
Malicious's Avatar

I would assume I am using the clock the chip generates, I'm not positive honestly, I don't completely have my head around all the 'built in' function such as that. I can post my code if needed. However, Humberto had a great point. I am pulling the SS line high and low with those functions, I did not even realize that I was doing that. Should I just make second function which does not toggle the SS line, and still use two calls to send the data, 8 bits at a time? (Then toggle it off of course)

June 15, 2010
by hevans
(NerdKits Staff)

hevans's Avatar

HI Malicious,

I was thinking you could try just the top 8 bits, waiting till its done, and setting the next 8 bits right after.

Bobamosfet however also raises a great point. You will need to set a clock prescaler for the SPI bus, sot that it is in a range your digital pot can handle. Check out the table at the top of page 170. By setting the SPR bits you select a scale factor that your SPI module uses to run. For example with fosc/2 your SPI module will run at 14.7456Mhz/2 or about 7.4Mhz. Either fosc/2 or fosc/4 should work (fosc/4 might be a bit more reliable).

Let us know how it works!

Humberto

June 25, 2010
by Malicious
Malicious's Avatar

May be a dumb question, but what part of this line sets the clock to clk/16? And then how can I change it to fosc/4?

//initiate the SPI module in master mode, data rate clk/16
 SPCR |= (1<<SPE) | (1<<MSTR) | (1<<SPR0);
June 25, 2010
by Malicious
Malicious's Avatar

Will simply

SPCR |= (1<<SPE) | (1<<MSTR)

work?

June 25, 2010
by BobaMosfet
BobaMosfet's Avatar

Malicious--

You should explicitly set all the bits the way you want them. If you don't a bit may contain a value other than what you want.

I would not use anything less than Fosc/4, which is the fastest speed the SPI will run at (unless you run it at full speed (14.74MHz). 14.74MHz/4 = 3.686MHz.

In order to set speed, you have to set both the SPI control register (SPCR) accordingly, and the SPI status register (SPSR).

BM

July 18, 2010
by Malicious
Malicious's Avatar

Hey Guys,

Its been a short while since I've said anything, I had some other projects to take care of, but I am wondering how exactly to set the clock speed to Fosc/4. I have tried all but everything, and I cannot get the MCP to respond to my commands. Here is my current Code and I know I have the clock currently set /16 but that's the only source example I could find. I tried setting the SPR0 and SPR1 to 0, as the datasheet said, but It didn't work. Any suggestions?

July 18, 2010
by Malicious
Malicious's Avatar

Actually just changed the code to what I think should work. Give it a look

July 21, 2010
by hevans
(NerdKits Staff)

hevans's Avatar

Hi Malicious,

As per the table on page 170 of the datasheet, you should not need to set any of the SPR1, SPR0, or SPI2X registers in order to get a clk/4 on the SPI bus. You should be able to just comment out the lines that are setting those registers and you will be going at clk/4.

Humberto

July 25, 2010
by BobaMosfet
BobaMosfet's Avatar

Malicious-

Thanks for providing me information to duplicate your transmitter setup so that I could really look at this and help you with it.

I disassembled the transmitter, and then reverse engineered the entire thing simply by how the traces ran and the components on their tiny mobo and how they were inter-related, plus voluminous scope readings and probings.

PREFACE:

Input voltage to transmitter is 12.88VDC (8 'AA' Batteries). Voltage readings were taking with the oscilloscope (TekTronix DSO), as a standard digital meter is simply not accurate enough (not sufficient resolution and too much 'loading'). Current in most of the circuit is on the uA scale, and therefore is also not measurable by most store-brand digital meters able to read current.

Findings:

  1. JOYSTICKS/RHEOSTATS:

First, these analog trimpots are wired as rheostats. For those less knowledgeable in electronics, a trimming potentiometer and a rheostat do not perform the same electrical function (being wired differently one from another), although they are built the same. A trimpot provided a voltage indicator on the whisker with very little current. Whereas, a rheostat provides all the current and voltage across the whisker. This is an important distinction.

The rheostats used in the Joysticks are 47K Ohm items. They respond from 75 Ohm to 45.7K Ohm, on a turn radius of 160 degrees. However, when installed in the joystick module, only a fraction of the turn radius is used, and that comes out to approximately 21K Ohm to 31K Ohm. Trimmers on each joystick will adjust from 0 to 3K Ohm, for additional offset tuning.

IMPORTANT: Now, interestingly, they are not using these rheostats as one expects. No constant voltage or current is being applied, and is the primary reason that Malicious has been having trouble getting valid readings in either voltage or current off of them. Instead , what they've done is create an ingenious little strobe-based pulse width modulator using an RC circuit, with the rheostat being a variable R in the equation.

Here's what the raw strobing looks like being send through the rheostats (Note that each channel represents each joystick, and they are default centered due to a spring positioning mechanism):

Fig 1 : Joystick Strobes (Raw)

  1. CLOCK:

We can see that they are strobing every 35.72ms. That strobe is based on the following clock-signal (raw) shown on channel 1. Second channel is a reference to strobe on joystick so we can see the time relationship:

Fig 2 : Chan 1 - Raw Clock Signal

Now, the magic of what they are doing across the entire circuit is they are using RC circuits, a bunch of them and a Schmitt Trigger array to clean up and reshape the waveform. Here's the clock signal cleaned up:

Fig 3 : Chan 1 - Reshaped Clock Signal

  1. PWM:

Essentially, they strobe to charge a capacitor, and then let it bleed off through the rheostat in the RC circuit and then they watch how long it takes to drain (note the slope on the top of the raw strobe waveform going through the rheostat). Adjusting a joystick will shorten or lengthen the width of the pulse. This has an inherent A to D conversion, by the way, simply via the Schmitt Trigger array to reshape the waveform. They rely on the Schmitt Trigger to clean up the PWM wave-form as well, and the later encoding waveforms.

With a joystick at minimum, we get a 1ms pulse, and with it all the way to the max, we get a 2ms pulse. Here are the cleaned up waveforms. channel 1 is min, and channel 2 is max:

Fig 4 : Chan 1 - Min Pulse; Chan 2 - Max Pulse

  1. PULSE ENCODING:

Once they have a pulse, and it's cleaned up, they encode as 3 quick pulses: A 'mark' or 'start' pulse, followed by two pulses that represent the start and the stop of the PWM Pulse. I've added some explanatory text to the image:

Fig 5 : Pulse Encoded

Beyond this, they use some AM/FM transistors to up the gain on the signal, in league with some tiny inductors and and they transmit a carrier wave at 27.26Mhz, with a full amplitude of 55.36V. See the figure:

Fig 6 : Transmitter Base Frequency

  1. MCP4251

Now that we know what is really going on, we know more about what we're actually trying to accomplish. Since the viable range of each rheostat is only about 10K Ohm, the MCP4251 will work fine. We also know that the voltage going through the rheostats is between 4.96VDC and 5.6VDCs - so that is acceptable - in other words, 5VDC will work just fine.

Enjoy!

BM

July 25, 2010
by Ralphxyz
Ralphxyz's Avatar

BM where does the Ch 1 55.36V come from?

Ralph

July 25, 2010
by BobaMosfet
BobaMosfet's Avatar

Ralphxyz-

I apologize if that was not clear. That is the signal off the antenna. What the transmitter is broadcasting (frequency and amplitude).

BM

July 27, 2010
by Malicious
Malicious's Avatar

Question for the group....

Now that we have the engineering figured out (and it does indeed work, I did a few tests this morning) I am trying to program a sensible control scheme. With the MCP4251, how would you go about reading the current position of the wiper? I tried quite a few things, but they all gave me unexpected results...

Page 47 of the data sheet is where the good info on this subject begins...

Also, I was curious, and this is one of the main reasons I want to know the exact wiper pos, will the ATMega act on every keypress sent through PuTTy or is there some lag/delay where a few may be skipped or ignored - such as if you held a button down. OR does the ATMega put this in a buffer and read through it at it's own pace?

July 28, 2010
by hevans
(NerdKits Staff)

hevans's Avatar

Hi Malicious,

Glad you are making headway with your project.

The current implementation of the uart code only reads from the UART module when you tell it to (when you call uart_read()). There is no buffer so you have to make sure you are reading from it faster than new samples are coming down the pipe. There are ways to switch the implementation to an interrupt based one, where an interrupt is fired in your code when a new sample is ready, and then you can then move it into your own buffer for consumption as necessary.

Humberto

Post a Reply

Please log in to post a reply.

Did you know that a NerdKit can be used to build an iPhone-controlled R/C car? Learn more...