# How to use AVR ADC with multiplier?

I am learning about AVR microcontroller programming and currently have a simple LED blinker set up on an ATtiny26.

A 10k potentiometer is connected to ADC7 (pin 7) as per the example in part 1 of this tutorial. The output pins of port B are connected to LEDs via transistors in a multiplexed array. (The circuit works just fine without the ADC involved.)

To test/learn, I am blinking a single LED and waiting for a number of milliseconds equal to what is read from the ADC. I am having a problem when I try to multiply the value to implement longer delays.

I thought the problem was that the variable I store the ADCH in was an int but needs to be something else because this is an 8-bit microcontroller and an int can only be 0-255. Substituting long in did not work, however.

Here is the code that works:

#define F_CPU 1000000UL
#define ADC_VREF_TYPE = 0x40
int SD = 1;
int main()
{
// Set port A to output (1)
DDRA = 0b11111111;
// Set port B, pin 3, to input (0)
DDRB &= ~(1 << PB3);
// Set control and status register
ADCSR = 0b11100111;
// Set ADC multiplexer selection register
ADMUX  = 0b00100111;

while (1)
{
// Set SD (step delay) to value from ADC
SD = ADCH;
// Turn on LED (it is a multiplexer, so two pins are used)
PORTA = 0b00010001;
// Wait for SD milliseconds
milli_delay(SD);
// Turn off LED
PORTA = 0b00000000;
// Wait for SD milliseconds
milli_delay(SD);
}
}


The problem occurs when I try to use a multiplier like this:

SD = ADCH * 10;


When doing this, the LED will flash quickly at one end of the potentiometer sweep, but will light solid and stay that way at some point while turning the pot to slower speeds (larger ADCH values); essentially "locking up" and requiring a reset while the potentiometer is at the original position again. My guess is that I'm trying to store a value greater than the numeric type holds, but I can't seem to overcome this by changing the variable type. I've tried long and uint32_t but I don't really understand what the numeric types hold on this micro.

Q: What do I need to do so that I can implement delays ranging from 0 to say 2000 milliseconds?

FYI I have the following libraries included:

#include <inttypes.h>
#include <avr/io.h>
#include <util/delay.h>
#include <stdlib.h>
#include <math.h>


Edit:

I had placed _delay_ms(x) directly in my example to avoid posting the functions I am actually using which call _delay_ms(1) inside a loop. My thought was it would be more concise, but as one answer pointed out, it actually appeared to be a mistake.

• can you post a schematic of your transistor/mux setup as well? Who knows, someone might see a prob there. – justing Aug 21 '12 at 16:32
• I did the schematic first on graph paper by hand, and just yesterday redid it in Eagle. If you think it's useful I'll include it; though when I ignore the ADC and use a hard coded value, everything works as expected. Additionally if I use the ADC value without trying to multiply it, I get good results (0-255 millisecond delays). – JYelton Aug 23 '12 at 16:17

## 2 Answers

int can only be 0-255

That's not correct. In the C programming language, an int is always at least 16 bits wide, even on 8-bit micros. A char is 8 bits wide.

But when using inttypes.h, I strongly recommend using the explicit types like uint8_t,int16_t or uint32_t.

Your real error is using _delay_ms() with a variable parameter. This is not allowed, as it is a macro involving floating point calculations - at least in avr gcc. You are supposed to use something like this:

void DelayMs(int delay) {
while(delay--) _delay_ms(1);
}

• Heh, the funny thing is I modified the _delay_ms() lines for posting my question, as they are actually function calls that do exactly as you specify for avr gcc. I modified them thinking it would be less code to post. – JYelton Aug 21 '12 at 15:22
• If the int value is 16 bits, then I would think ADCH * 10 would work flawlessly. Any additional insight as to why it might not? – JYelton Aug 21 '12 at 15:27
• Guys, are you sure about this macro thing?! The util/delay.h I'm looking at, defines _delay_ms() as a regular function. – DimKo Aug 21 '12 at 18:47
• I am thinking that it's something to do with language usage or something. See stackoverflow.com/q/12095890/161052 – JYelton Aug 23 '12 at 16:12
• The solution to my problem turned out to be optimization settings on the compiler. Still, your information was very helpful, and kept me on the right track. Thanks! – JYelton Dec 19 '12 at 8:39

I think the problem is that you have the ADC result as left justified in the results register:

Your code below shows that you have set ADLAR to 1 (left justified):

 // Set ADC multiplexer selection register
ADMUX  = 0b00100111;


This means that when you are reading the ADC value in ADCH, you are completly skipping the two least significant bits (LSB's). This means you are reading the number in as if it is 4 times smaller (two bit shifts right).

What you want to do is set it to RIGHT justified:

 // Set ADC multiplexer selection register
ADMUX  = 0b00000111;


and read your ADC value from ADCL (the lower ADC result register):

while (1)
{
// Set SD (step delay) to value from ADC
SD = ADCL;
// Turn on LED (it is a multiplexer, so two pins are used)
PORTA = 0b00010001;
// Wait for SD milliseconds
milli_delay(SD);
// Turn off LED
PORTA = 0b00000000;
// Wait for SD milliseconds
milli_delay(SD);
}

• This didn't help, sorry. If I use the ADCH value and left-justify the result, I get my expected values (0-255). I tested this by writing a bit of code that lights a number of LED's equal to 1/16th of the value. Turning the potentiometer yields the expected lit LEDs. I am suspecting a code/language/usage issue, see stackoverflow.com/q/12095890/161052 – JYelton Aug 23 '12 at 16:14