XMega : Measure the VCC

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

Always seemed funny to me to want to measure the vcc. But this time...

It's running from 2 NIMH batteries, normally about 2.4v. If the batteries start running down, I want to turn off the LED in a vain attempt to keep them from running down.

So, I adapted my ReadADC code:

uint16_t ReadADC(uint8_t Channel, uint8_t ADCMode) // Mode = 1 for single ended, 0 for internal
{
	if ((ADCA.CTRLA & ADC_ENABLE_bm) == 0)
	{
		ADCA.CTRLA = ADC_ENABLE_bm ; // Enable the ADC
		ADCA.CTRLB = 0; // Unsigned Mode ADC_CONMODE_bm;
		ADCA.REFCTRL = 0; // Internal 1v ref
		ADCA.EVCTRL = 0 ; // no events
		ADCA.PRESCALER = ADC_PRESCALER_DIV512_gc ; // Clock/512 = 62.5K, I think
		ADCA.CALL = ReadSignatureByte(0x20) ; //ADC Calibration Byte 0
		ADCA.CALH = ReadSignatureByte(0x21) ; //ADC Calibration Byte 1
		//ADCA.SAMPCTRL = This register does not exist
		_delay_us(400); // Wait at least 25 clocks
	}
	ADCA.CH0.CTRL = ADC_CH_GAIN_1X_gc | ADCMode ; // Gain = 1, Single Ended or internal
	ADCA.CH0.MUXCTRL = (Channel<<3);//(Channel<<3) ;
	ADCA.CH0.INTCTRL = 0 ; // No interrupt
	//ADCA.CH0.SCAN Another bogus register
	for(uint8_t Waste = 0; Waste<2; Waste++)
	{
		ADCA.CH0.CTRL |= ADC_CH_START_bm; // Start conversion
		while (ADCA.INTFLAGS==0) ; // Wait for complete
		ADCA.INTFLAGS = ADCA.INTFLAGS ;
	}
	return ADCA.CH0RES ;
}

Remember this painfully developed code. I added a "mode" parameter that gets passed to the "Channel Control Register." So you can select single ended or internal measurements.

ADCA.CH0.CTRL = ADC_CH_GAIN_1X_gc | ADCMode ; // Gain = 1, Single Ended or internal

So far so good, eh?

So, to read the VCC, you use:

uint16_t vVCC = ReadADC(2,0)

Selecting Mux 2 on "Internal" is supposed to give you VCC/10, according to xMega E5 manual, pg364.

So, if I'm looking for 2.1v, that would be 0.21v, and full scale 1v is 4096 counts, so I want a count of 901... But it doesn't switch from "Mode 2" to "Mode 3" when the VCC drops below 2.1. Hmm.

It does switch when the other voltage crosses between .22 and .23 v, I measure it by ReadADC(0,1). That'supposed to tell me if the solar cell is charging the battery.

Haven't seen where I've blundered yet, so I'll post and hope somebody else spots it:


#include 
#define F_CPU 32000000UL
#include 
#include 
#include 
#include 
#include "SignatureRow.h"

uint16_t ReadADC(uint8_t Channel, uint8_t ADCMode) // Mode = 1 for single ended, 0 for internal
{
	if ((ADCA.CTRLA & ADC_ENABLE_bm) == 0)
	{
		ADCA.CTRLA = ADC_ENABLE_bm ; // Enable the ADC
		ADCA.CTRLB = 0; // Unsigned Mode ADC_CONMODE_bm;
		ADCA.REFCTRL = 0; // Internal 1v ref
		ADCA.EVCTRL = 0 ; // no events
		ADCA.PRESCALER = ADC_PRESCALER_DIV512_gc ; // Clock/512 = 62.5K, I think
		ADCA.CALL = ReadSignatureByte(0x20) ; //ADC Calibration Byte 0
		ADCA.CALH = ReadSignatureByte(0x21) ; //ADC Calibration Byte 1
		//ADCA.SAMPCTRL = This register does not exist
		_delay_us(400); // Wait at least 25 clocks
	}
	ADCA.CH0.CTRL = ADC_CH_GAIN_1X_gc | ADCMode ; // Gain = 1, Single Ended
	ADCA.CH0.MUXCTRL = (Channel<<3);//(Channel<<3) ;
	ADCA.CH0.INTCTRL = 0 ; // No interrupt
	//ADCA.CH0.SCAN Another bogus register
	for(uint8_t Waste = 0; Waste<2; Waste++)
	{
		ADCA.CH0.CTRL |= ADC_CH_START_bm; // Start conversion
		while (ADCA.INTFLAGS==0) ; // Wait for complete
		ADCA.INTFLAGS = ADCA.INTFLAGS ;
	}
	return ADCA.CH0RES ;
}


void LedPowerOn(void)
{
	TCC4.CTRLA = 1 ; // 32MHz clock
	TCC4.CTRLB = 3 ; // singleslope mode
	TCC4.CTRLC = (1<<1) ; // Enable output on A low
	TCC4.CTRLD = 0 ;
	TCC4.CTRLE = 1 ; // Compare on A enabled
	TCC4.CTRLF = 0 ;
	TCC4.INTCTRLA = 0 ; // Interruptions nix!
	TCC4.INTCTRLB = 0 ;
	TCC4.CTRLGCLR = 255 ;
	TCC4.CTRLHCLR = 255 ;
	TCC4.PER = 256 ;
	TCC4.CCA = 50 ; // The pulse width 10/256
}

void LedPowerOff(void)
{
	TCC4.CTRLA = 0 ; // Stop the timer
	TCC4.CTRLE = 0 ; // No compare on A
	TCC4.CTRLC = 0 ; // No output on a.
}

void ClockFast(void)
{
	OSC.CTRL|=OSC_RC32MEN_bm;
	while (!(OSC.STATUS & OSC_RC32MRDY_bm));
	CCP=CCP_IOREG_gc;
	CLK.CTRL=CLK_SCLKSEL_RC32M_gc;
	// If you want to disable RC2M
	OSC.CTRL&=(OSC_RC2MEN_bm);
}

void ClockSlow(void)
{
	OSC.CTRL|=OSC_RC32KEN_bm;
	while (!(OSC.STATUS & OSC_RC32KRDY_bm));
	CCP=CCP_IOREG_gc;
	CLK.CTRL=CLK_SCLKSEL_RC32K_gc;
	// If you want to disable RC2M
	OSC.CTRL&=(OSC_RC32KEN_bm);
}

void StartDayChecker(void)
{
	if ((OSC.CTRL & OSC_RC32KEN_bm)==0) 
	{ // Fast Osc
		PMIC.CTRL = 1 ; // Low level interrupts
		TCD5.CTRLA = 0 ; // Stop the timer
		TCD5.CTRLB = 3 ; // Single Slope
		TCD5.CTRLC = 0 ;
		TCD5.CTRLD = 0 ;
		TCD5.CTRLE = 0 ;
		TCD5.CTRLF = 0 ;
		TCD5.INTCTRLA = 1 ; 
		TCD5.INTCTRLB = 0 ;
		TCD5.CTRLGCLR = 255 ;
		TCD5.CTRLHCLR = 255 ;
		TCD5.CNT = 0 ;
		TCD5.CTRLA = 5 ; // 32MHz/32/65535 = 15Hz
		
	}
	else
	{ // Slow Osc
		PMIC.CTRL = 1 ; // Low level interrupts
		TCD5.CTRLA = 0 ; // Stop the timer
		TCD5.CTRLB = 3 ; // Single Slope
		TCD5.CTRLC = 0 ;
		TCD5.CTRLD = 0 ;
		TCD5.CTRLE = 0 ;
		TCD5.CTRLF = 0 ;
		TCD5.INTCTRLA = 1 ; // low level interrupt on overflow (about 2 seconds)
		TCD5.INTCTRLB = 0 ;
		TCD5.CTRLGCLR = 255 ;
		TCD5.CTRLHCLR = 255 ;
		TCD5.CNT = 0 ;
		TCD5.CTRLA = 1 ; // About once in 2 seconds
	}
}

ISR(TCD5_OVF_vect)
{
	uint8_t OpMode = 0 ;
	TCD5.INTFLAGS = TCD5.INTFLAGS ; // Clear the int flags
	uint16_t vPCell = ReadADC(0,1) ;
	if (vPCell > 1117) OpMode = 1 ; // It's daytime, vPCell > 3v
	else // Nighttime
	{
		uint16_t vVCC = ReadADC(2,0) ; // Internal 2 is VCC/10
		if (vVCC>901) // Batteries ok
		{
			OpMode = 2 ; // It's night time and we have juice
		}
		else
		{
			OpMode = 3 ; // It's night time but batteries are low
		}
	}
	PORTC.OUT = (OpMode<<4) ; /// Just display the opmode

}

int main(void) 
{

	PORTA.DIR = ~(1<<0) ; // All out save to measure Solar Cell.
	PORTA_PIN0CTRL = 7 ;
	PORTA_PIN1CTRL = (3<<3);
	PORTA_PIN2CTRL = (3<<3);
	PORTA_PIN3CTRL = (3<<3);
	PORTA_PIN4CTRL = (3<<3);
	PORTA_PIN5CTRL = (3<<3);
	PORTA_PIN6CTRL = (3<<3);
	PORTA_PIN7CTRL = (3<<3);
	PORTA.OUT = 0 ;
	
	PORTC.DIR = 255 ;
	PORTC_PIN0CTRL = (3<<3);
	PORTC_PIN1CTRL = (3<<3);
	PORTC_PIN2CTRL = (3<<3);
	PORTC_PIN3CTRL = (3<<3);
	PORTC_PIN4CTRL = (3<<3);
	PORTC_PIN5CTRL = (3<<3);
	PORTC_PIN6CTRL = (3<<3);
	PORTC_PIN7CTRL = (3<<3);
	PORTC.OUT = 0 ;
	
	PORTD.DIR = 255 ;
	PORTD_PIN0CTRL = (3<<3);  // I really need to learn to use that "set all these" feature.
	PORTD_PIN1CTRL = (3<<3);
	PORTD_PIN2CTRL = (3<<3);
	PORTD_PIN3CTRL = (3<<3);
	PORTD_PIN4CTRL = (3<<3);
	PORTD_PIN5CTRL = (3<<3);
	PORTD_PIN6CTRL = (3<<3);
	PORTD_PIN7CTRL = (3<<3);
	PORTD.OUT = 0 ;	

	ClockFast() ;
	LedPowerOn() ;
	
	StartDayChecker() ;
	
    while(1)
    {
		set_sleep_mode(SLEEP_MODE_IDLE);
		sleep_enable();
		sei();
		sleep_cpu();
		sleep_disable();
    }
}

If you don't know my whole story, keep your mouth shut.

If you know my whole story, you're an accomplice. Keep your mouth shut. 

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

I'm going to hold my breath If I don't get an answer before I have to let go, I'll add 2 resistors and a cap and measure it from an outside pin.

If you don't know my whole story, keep your mouth shut.

If you know my whole story, you're an accomplice. Keep your mouth shut. 

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

Oh wow! The Capcha was "xyzzy"

If you don't know my whole story, keep your mouth shut.

If you know my whole story, you're an accomplice. Keep your mouth shut. 

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

Which chip/rev? Have you checked the errata for it?

Gamu The Killer Narwhal
Portland, OR, US
_________________
Atmel Studio 6.2
Windows 8.1 Pro
Xplained boards mostly

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

It's an 8E5. There are LOTS of errata concerning the ADC, but none that mentions this.

Usually, when something refuses to work, I assume it's some mistake I made, but with xMega, that assumption seems rash. :shock:

If you don't know my whole story, keep your mouth shut.

If you know my whole story, you're an accomplice. Keep your mouth shut. 

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

Do you need to enable the bandgap?

Attachment(s): 

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

It definitely works okay, and if you select the bandgap as the reference voltage it should enable automatically. If you post a minimal test case I could look at it - I don't have time to sift through your big chunks of code.

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

Hmm. I haven't tried bandgap 'cause I don't know what it's for. I'm now pouring over the pdfs.

Ok, it selects a 1.1v reference 'stead of the 1.0v reference. So?

Ok, I've reduced the program to one loop:

    while(1)
    {
		uint16_t vPCell = ReadADC(0,1) ;
		if (vPCell > 372) // 1v / 11 * 4096 
		{
			PORTC.OUTSET = (1<<4);
		}
		else
		{
			PORTC.OUTCLR = (1<<4);
		}
		uint16_t vVCC = ReadADC(2,0) ;
		if (vVCC>819) // 2.0v / 10 * 4096 
		{
			PORTC.OUTSET = (1<<5);
		}
		else
		{
			PORTC.OUTCLR = (1<<5);
		}
    }

Varying the photocell input, I can turn one of the LEDs on and off, but varying the VCC, the other LED is always on (high) indicating that ReadADC(2,0) returned a value > 819.

If you don't know my whole story, keep your mouth shut.

If you know my whole story, you're an accomplice. Keep your mouth shut. 

Last Edited: Mon. Oct 14, 2013 - 01:30 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You might be going too slow on the ADC clock. The datasheet says min. 100 kHz (which sounds a bit strange to me, since you'll never be able to use the ADC if the peripheral clock is slower than 400 kHz).

I would try with the prescaler set at 256 or 128. However, I have no idea if it will help you with the supply voltage measurement.

You're absolutely right. This member is stupid. Please help.

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

Good point ErikT. I have found that the temperature sensor and VCC/10 signals do work well below 100KHz, but obviously it can't be guaranteed out of spec.

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

Oh! It DOES work. It DOES work.

I set the VCC for 2v and did a binary search to find a value.

I have

		uint16_t vVCC = ReadADC(2,0) ;
		if (vVCC>1000) // 2.0v / 10 * 4096 

Now, it seems to me that 1000 / 4096 * 10 would be 2.44v, not 1.9v, unless my 29-year-old calculator is going senile.

If you don't know my whole story, keep your mouth shut.

If you know my whole story, you're an accomplice. Keep your mouth shut. 

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

Another point that might not help you: In my experience with the Xmega, it is easier to get a useful result when using the ADC in "signed" mode. Yes, I only get 11 bits resolution (which is absolutely fine with me in most situations), but at least I get rid of that annoying offset, and I get some good results. I haven't seen these wild inaccuracies that a lot of prople were complaining about in the beginning.

Might it be that which is making your day difficult?

You're absolutely right. This member is stupid. Please help.

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

mojo-chan wrote:
Good point ErikT. I have found that the temperature sensor and VCC/10 signals do work well below 100KHz, but obviously it can't be guaranteed out of spec.

Now, I've wondered about this prescaler! The only info I found on it by googling was that the xMega ADC only works fairly slowly, but that was old info, and he might have been playing with a part with old errata.

The manual says to set the prescaler to get an ADC clock that matches the application requirements and is withing the operating range of the ADC. But I haven't found in 2 PDF's what IS the operating range of the ADC.

Also, since the values I read and what I calculate differ by a surprising amount, I wonder if I'm goofing something up by setting the calibration bytes.

Attachment(s): 

If you don't know my whole story, keep your mouth shut.

If you know my whole story, you're an accomplice. Keep your mouth shut. 

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

Quote:
Now, it seems to me that 1000 / 4096 * 10 would be 2.44v, not 1.9v, unless my 29-year-old calculator is going senile.

When you run in unsigned mode, there is a stupid offset, about 205 ADC counts. If you get an ADC value which is 1000, it is actually 795 when you compensate for the offset. And 1 V * (795 / 4096) = 0.194, which corresponds with 1.94 V.

I recommend you use signed mode.

About the ADC clock working range, you'll find it in the datasheet, table 36-8.

You're absolutely right. This member is stupid. Please help.

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

I set the prescaler to 128:

ADCA.PRESCALER = ADC_PRESCALER_DIV128_gc ;

and it seems to work the same. That's an improvement if I don't have to go it so slow.

I've heard of this offset and saw a suggestion in the same, old, web page to use signed mode. Maybe I'll play with that, but perhaps in the gadget that has a display so I don't have to probe with compile-burn-curse cycles.

If you don't know my whole story, keep your mouth shut.

If you know my whole story, you're an accomplice. Keep your mouth shut. 

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

Now I've switched to signed mode.

In the project with a display, if I shorted the bottom resistor of the battery measuring divider so the input was 0, I'd get like 1500 counts from the ADC. So I switched to signed mode and get very near 0 counts from the ADC with the bottom resistor shorted. Also, it seems to get reasonable, but varyin results. (I insert a decimal point, so the function returns v*10.) The meter is very stable at 3.03v while the display jumps around between 2.9 and 3.2. Maybe I should design in some more capacitor? I tried switching to internal 1v ref instead of 1.6xVCC hoping for a steadier result.

	long v = ReadADC(ADCBatteryChannel,0);
	v = (v * 40)/ 2048 ;
	return (int)v ;

If you don't know my whole story, keep your mouth shut.

If you know my whole story, you're an accomplice. Keep your mouth shut.