N00b using mega48 ADC

Go To Last Post
23 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I want to design a simple battery monitor using ADC on mega48. Because the Vcc was sourced from battery which I have to measure, I cannot use Vcc as Vref, instead I have to use internal 1.1V reference as an input channel, then connect the Vcc to the AREF pin. I cannot use internal 1.1V as Vref because that will overflow the ADCH/L register.

REFS1=0 REFS0=0 MUX3..0=1111

From mega48 datasheet page 255:

ADC=Vin*1024/Vref

Let Vin=1.1V
let Vref={5V~1V}

Vref         ADC       ADCH+ADCL    LED
------------------------------------------
5V           225       0x0E1        PD0
4V           281       0x119        PD1
3V           374       0x232        PD2
2V          1125       0x465        PD3

Since I have to use 10bit precision I must right-adjust the ADC result ADLAR=0.

During the experiment, the battery depletion is emulated by using a 10k potentiometer. With each voltage level triggers an LED.

Default system clock = 1MHz The recommend ADC clock was {50~200}kHz therefore I select prescaler = 8

1MHz/8=125kHz
ADPS2..0=011

The rest of registers need no configuration, ADCSRB and DIDRO assume their default value 0x00.

The following program should be self explanatory to most of you, I am using AVR studio 4, also I have attached the schematic of my test circuit. The potentiometer was directly copied from this: https://www.avrfreaks.net/index.p... Its been said that will give an output range of {4.85~0.15}V, and I connected the ground of potentiometer to the chip ground as proposed.

// the free running mode
#include 
#include 
#include 

#define F_CPU 1000000UL  // CPU clock = 1 MHz

void main()
{
	PORTD=0x0F; DDRD=0x0F;
	ADMUX=0x0E; ADCSRA=0xE3;

	unsigned int adcResult=0xFFF;

	while(1)
	{
		adcResult=ADCL+(0x100)*ADCH;
		if(adcResult>=0x0E1 && adcResult<0x119)
			PORTD=0x0E;
		else if(adcResult>=0x119 && adcResult<0x176)
			PORTD=0x0D;
		else if(adcResult>=0x176 && adcResult<0x232)
			PORTD=0x0B;
		else if(adcResult>=0x232)
			PORTD=0x07;
		else
			PORTD=0x0F;
	}
}

--------------------------------------------------------------
// the ADC interrupt mode
#include 
#include 
#include 

#define F_CPU 1000000UL  // CPU clock = 1 MHz

volatile unsigned int adcResult=0xFFF;

ISR(ADC_vect)
{
	adcResult=ADCL+(0x100)*ADCH;
}

void main()
{
	PORTD=0x0F; DDRD=0x0F;
	ADMUX=0x0E; ADCSRA=0xED;

	while(1)
	{
		if(adcResult>=0x0E1 && adcResult<0x119)
			PORTD=0x0E;
		else if(adcResult>=0x119 && adcResult<0x176)
			PORTD=0x0D;
		else if(adcResult>=0x176 && adcResult<0x232)
			PORTD=0x0B;
		else if(adcResult>=0x232)
			PORTD=0x07;
		else
			PORTD=0x0F;
	}
}

Those codes above does not work, PD3 was the only LED that light up all the time, turning the potentiometer had no effect. What could gone wrong?

Im new to this forum, I didn't do a proper introduction, you can call me the pin-thinge or whatever. I hope I did not break any rules in my post.

Attachment(s): 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:
I cannot use internal 1.1V as Vref because that will overflow the ADCH/L register.

Sure you can. Put a resistor divider from VCC to a switched common. Scale the resistor divider so that a fully charged battery gives you a little bit less than 1.1V. Of course, the center of the divider would be tied to an A/D input pin.

The lower end of the resistor divider should be switched with an N-CH MOSFET, OR, you can just tie the lower end of your resistor divider to an output line on your micro. This is to reduce the load on your batteries. For the micro line method, when you get ready to take a battery voltage reading, drive the output line low. Then take your A/D sample. After you're done, change the output line to an input so no current is drawn by the divider.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

gahelton wrote:
Quote:
I cannot use internal 1.1V as Vref because that will overflow the ADCH/L register.

Sure you can. Put a resistor divider from VCC to a switched common. Scale the resistor divider so that a fully charged battery gives you a little bit less than 1.1V. Of course, the center of the divider would be tied to an A/D input pin.

The lower end of the resistor divider should be switched with an N-CH MOSFET, OR, you can just tie the lower end of your resistor divider to an output line on your micro. This is to reduce the load on your batteries. For the micro line method, when you get ready to take a battery voltage reading, drive the output line low. Then take your A/D sample. After you're done, change the output line to an input so no current is drawn by the divider.

A resistor divider sounds like a good idea, I will give a try sometimes, but similar problem could occur for that as well, I mean everything looks correct but doesn't work, you don't have to read everything I said, just from your experience what could gone wrong if a program fail to make ADC work?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Did your mother name you "twentyfive vertical bars between square brackets"? You can fill out your city and country in your profile in case any other avrfreaks live in your land of silly names.

Imagecraft compiler user

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:
I hope I did not break any rules in my post.
Probably not but some people may not be willing to help with a name like yours.

John Samperi

Ampertronics Pty. Ltd.

https://www.ampertronics.com.au

* Electronic Design * Custom Products * Contract Assembly

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

bobgardner wrote:
Did your mother name you "twentyfive vertical bars between square brackets"? You can fill out your city and country in your profile in case any other avrfreaks live in your land of silly names.

Cuz their parallel I guess, sure I would like to meet some other freaks with dumb name.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

js wrote:
Quote:
I hope I did not break any rules in my post.
Probably not but some people may not be willing to help with a name like yours.

They many as well not want to join a forum that call itself "freaks".

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Are you afraid to use your own name or ashamed? Maybe its a stoopid name like Harry Balls? If that was my name I'd use a handle like BigStudMuffin so everyone would think I'm cool.

Imagecraft compiler user

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

bobgardner wrote:
Are you afraid to use your own name or ashamed? Maybe its a stoopid name like Harry Balls? If that was my name I'd use a handle like BigStudMuffin so everyone would think I'm cool.

Oh come on, when was the last time you saw people use their "own name" on internet? I mean real name.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Harry Sacks? It helps if we know where in the world you are - especially for language issues and where to get things. Let's move on....

Rather than: adcResult=ADCL+(0x100)*ADCH;
you can simply put: adcResult = ADC; //the compiler will read the 16 bit value

Your interrupt code will not work. You did not enable interrupts.After initialisation, add sei();

Why use free run mode? Or interrupts? Just write a simple function that selects the ADC input, start the conversion, waits for completion then return the result?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Some modifications to your circuit has been made (see attached).

1. Change your current limiting resistor for your LEDs to a much higher value (approximately 715 ohms instead of 100 ohms). A resistance of 100 would be far to low for most LEDs, and would probably give you 30mA+ for each LED.

2. Change up your divider arrangement so that you get approximately 0.9936V with 9V VCC. Divider input is connected to PC0 (ADC0).

3. Lower end of resistor divider connected to PB5. Drive PB5 low when you want to take a battery voltage measurement. Change port line back to an input when your not reading voltage.

4. Connect capacitor between AREF and common.

Software:

To initialize ADC,
Set ADMUX to 0xC0. This selects 1.1V internal VREF with external cap on AREF, and ADC0 as the input channel.
Set ADCSRA to 0xEB. ADC Enable, Start Conversion, Auto Trigger Enable, ADC Interrupt Enable, ADC prescale 8 (125KHZ ADC clock with 1MHZ main clock). I don't know why you want to use free running mode, but so be it.
Set DIDR0 to 0x01. This disables digital input buffer from ADC0 input.

Recheck your math for assignment to adcResult.

Attachment(s): 

Last Edited: Thu. Aug 23, 2012 - 04:31 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I guess you realize that the last time you saw someone use their Real Name on the internet was the previous message to the one you responded to? I'm sorry for your unfortunate name you don't like to use.

Imagecraft compiler user

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

He thinks you are Bob THE gardner...

John Samperi

Ampertronics Pty. Ltd.

https://www.ampertronics.com.au

* Electronic Design * Custom Products * Contract Assembly

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I'm Kirk Artman.

"Bob the gardner, can he fix it, Yes he can!"
Apologies to Bob the Builder
http://www.google.com.au/imgres?...

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Stop it! Please! I must have heard that a gazillion times since my 2 year old son got a Bob the Builder DVD from his grandparents two weeks ago.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

Oh come on, when was the last time you saw people use their "own name" on internet? I mean real name.

Umm--I do.

Anyway, back on topic: No resistor divider is needed.

Leave your connections as they are. Now, generally with modern AVRs such as the family you are using we will not connect AVcc to AREF. Rather, just a cap to ground as recommended, and then internally select "Use AVcc as Vref".

OK, so now you have a Vcc that varies between e.g. 4.5V with three fresh batteries and 3V when they are nearly dead.

Using that supply voltage as the ADC reference voltage, do a conversion on the internal 1.1V bandgap "channel". Back-calculate the reference voltage, which is the supply voltage.

Example: ADC returns 341 counts. 341/1023 = 1.1/Vcc. Vcc => ~3.3V.

Now, the 1.1V is nominal and needs a one-time cal for precision work. For battery monitoring I never bother-- +/- a few percent is no matter.

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Five Stars! Succint and understandable! *****

Imagecraft compiler user

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Let's get this thread back on track...

Since you are using a voltage regulator your VCC is 5V and will stay 5V until your battery voltage drops way below the rated 9V! Down to maybe 6V! At 6V the battery is pretty dead and all you basically can measure is:
1) Battery is full...
2) Oh sh*t my battery is dead! (When the 5V starts to drop)

The voltage divider (resistors) should be connected to VBAT (battery) directly, not your regulated VCC!

- Brian

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Kartman wrote:
Harry Sacks? It helps if we know where in the world you are - especially for language issues and where to get things. Let's move on....

Rather than: adcResult=ADCL+(0x100)*ADCH;
you can simply put: adcResult = ADC; //the compiler will read the 16 bit value

Your interrupt code will not work. You did not enable interrupts.After initialisation, add sei();

Why use free run mode? Or interrupts? Just write a simple function that selects the ADC input, start the conversion, waits for completion then return the result?

Thanks for reminding me the sei() I usually do not make that kind mistake now I feel like a moron. I was wrong about the modes, I thought I was using free running because there was some confusion on the register setting, the code was modified as your recommended but still need some work, thanks for the advises they are helpful.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

gahelton wrote:
Some modifications to your circuit has been made (see attached).

1. Change your current limiting resistor for your LEDs to a much higher value (approximately 715 ohms instead of 100 ohms). A resistance of 100 would be far to low for most LEDs, and would probably give you 30mA+ for each LED.

2. Change up your divider arrangement so that you get approximately 0.9936V with 9V VCC. Divider input is connected to PC0 (ADC0).

3. Lower end of resistor divider connected to PB5. Drive PB5 low when you want to take a battery voltage measurement. Change port line back to an input when your not reading voltage.

4. Connect capacitor between AREF and common.

Software:

To initialize ADC,
Set ADMUX to 0xC0. This selects 1.1V internal VREF with external cap on AREF, and ADC0 as the input channel.
Set ADCSRA to 0xEB. ADC Enable, Start Conversion, Auto Trigger Enable, ADC Interrupt Enable, ADC prescale 8 (125KHZ ADC clock with 1MHZ main clock). I don't know why you want to use free running mode, but so be it.
Set DIDR0 to 0x01. This disables digital input buffer from ADC0 input.

Recheck your math for assignment to adcResult.

Good call on the LED part, actually that lead me to discover something, the LED seems to affect ADC, the ADC works normally after I reduce LED current. I still didn't get the result but the thing is piratically working. I don't have to replace the resistors, I can compensate that by flashing the LED constantly. Thanks for the advise I have no plan to change my circuit yet but I will try if nothing else works.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

theusch wrote:
Quote:

Oh come on, when was the last time you saw people use their "own name" on internet? I mean real name.

Umm--I do.

Anyway, back on topic: No resistor divider is needed.

Leave your connections as they are. Now, generally with modern AVRs such as the family you are using we will not connect AVcc to AREF. Rather, just a cap to ground as recommended, and then internally select "Use AVcc as Vref".

OK, so now you have a Vcc that varies between e.g. 4.5V with three fresh batteries and 3V when they are nearly dead.

Using that supply voltage as the ADC reference voltage, do a conversion on the internal 1.1V bandgap "channel". Back-calculate the reference voltage, which is the supply voltage.

Example: ADC returns 341 counts. 341/1023 = 1.1/Vcc. Vcc => ~3.3V.

Now, the 1.1V is nominal and needs a one-time cal for precision work. For battery monitoring I never bother-- +/- a few percent is no matter.

Ok this is what you meant

Vref=AVcc {REFS1..0}=01 "AVCC with external capacitor at AREF pin"

Vin=1.1V {MUX3..0}=1110 "select 1.1v as ADC input"

ADC=1.1V*1024/AVcc

since ADC=Vin*1024/Vcc 1/Vcc=ADC/(Vin*1024) Vcc=1/[ADC/(Vin*1024)]

Therefore Vcc=(Vin*1024)/ADC

Ok that makes sense, I can calculate the current Vcc myself, but I think ADC register value is in hex right? Do I have to do some conversion?

Thanks for the advise, I will try this ASAP.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Geronimo wrote:
Let's get this thread back on track...

Since you are using a voltage regulator your VCC is 5V and will stay 5V until your battery voltage drops way below the rated 9V! Down to maybe 6V! At 6V the battery is pretty dead and all you basically can measure is:
1) Battery is full...
2) Oh sh*t my battery is dead! (When the 5V starts to drop)

The voltage divider (resistors) should be connected to VBAT (battery) directly, not your regulated VCC!


5V to divider is only a voltage source, the whole point here is to make a variable divider that provides different voltages to the chip's ADC, Im not measuring any batteries here, this is a test circuit to study ADC behavior, the 5V should not change, I am using a variable resistor to generate different voltage levels from a constant 5V source. The actual battery I have to measure uses no regulator.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

A-ha! I thought you wanted to measure the voltage of the 9V battery supplying the circuit :)

- Brian