ADC: WTF?

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

Hi all,
despite the large number of posts regarding ADCs, I haven't been able to solve my current problem.

I am using an ATmega128 and have various analog inputs on PORTF. I am using the ADC interrupt routine to read the ADCH,ADCL and set ADCMUX for the next reading. The ADC values are stored in arrays (currently length 10 for each variable) from which I then take an average. I am using the GCC compiler.

I have a 10nF capacitor at Vref and have disabled JTAG in software. I have been confirming the contents of the ADCH and ADCL registers using an rprintf routine to print the contents to screen, so that a numerical type conversion is not the cause of the bad readings.

I have tried to sort the most relevant bits of code. Sorry if this is a bit long.

First the code, then the problems:

//Somewhere at the top of the program
---------------------------------------

DDRF = 0x00;	//Analog ports are inputs		
PORTF=0x00;	//Pull-up resistors off
sei(); 			//Interrupts are enabled.
ADMUX =TRACK_ERR_IN;	//Set ADC input port to track                        
MCUCSR=0x80;		//Disable the JTAG interface
ADCSRA = ADC_MASK;	//Initialise ADC and start first conversion
MCUCSR=0x80;		//Disable the JTAG interface.

//The macros used to define ADMUX etc.:
----------------------------------------

	//ADMUX settings for each of the inputs. All ref AVCC and R adjust. 
#define TRACK_ERR_IN 0b11001011		//2.56V reference. Differential, 200x gain ports 0,1
#define CARERR_IN 0b11001111		//As above, ports 2,3
#define CARPOS_IN 0b01000100		//VCC reference, single ended, port 4
#define FOCUS_IN 0b01000101			//VCC reference, single ended, port 5
#define SUN_IN 0b01000110			//VCC reference, single ended, port 6
#define TRACK_POS_IN 0b11000111 	//Uses 2.56V as reference voltage, single ended, port 7.
	
	//ADC control register ADCSRA setting:
	// ADC enabled
	// ADC start conversion
	// Right adjusted result
	// Not free running
	// Clear flag
	// ADC clock is CPU/128 is 125kHz with 16MHz     CPU
#define ADC_MASK 0b11001111

//The interrupt routine
------------------------------------------------

ISR(ADC_vect)
{
	volatile uint16_t ADCOutput=0;			//Stores ADC output as unsigned integer
	volatile int16_t signedOutput = 0;		//Stores ADC output as signed integer
		
	switch (ADCCycleCounter)
	{
		case 0:	//0 - track error first read, discard (differential 2.56V ref 200x gain)
			//Discard first differential reading				
			break;
		case 1: // 1 - track error second read 
			//rprintf("\r\n ADCL %x\r\n",ADCL);   //Debugging
			//rprintf("\r\n ADCH %x\r\n",ADCH);
			//rprintf("\r\n ADCMUX %x\r\n",ADMUX);
			ADCOutput = (ADCL<<6);		//Add the low bit to the output result
			ADCOutput +=(ADCH<<14);		//Add the high ADC byte to output result
			signedOutput = ADCOutput;   //Convert unsigned int to signed int -seems to work
			signedOutput>>=6;			
			trackErrArray[trackErrArrayCounter] = signedOutput; //Adding result to appropriate array
			trackErrArrayCounter++;	   //Increment array index for next result
			if (trackErrArrayCounter>=TRACKERR_ARRAY_SIZE) trackErrArrayCounter=0;															//Loop back to start of array
			ADMUX = CARERR_IN;	 //Set the ADMUX for the next input
			break;
		case 2: // 2 - car error first read - differential, not yet used
			// Discard the first differential reading
			break;
		case 3: // 3 - car error second read - differential, not yet used
			ADMUX =CARPOS_IN;	//Set ADMUX for next input.
			break;
		case 4: // 4 - car position - first read. discard (pot. single ended Vcc Ref)
			//Discard first reading with new Vref
			break;		
		case 5: // 5 - car position - second read.
			ADCOutput = (ADCL);
			ADCOutput +=(ADCH<<8);		//Add the high ADC byte to output result
			signedOutput = ADCOutput;
			carPosArray[carPosArrayCounter]=ADCOutput-CARMIDDLE;
			carPosArrayCounter++;
			if (carPosArrayCounter>=CARPOS_ARRAY_SIZE) carPosArrayCounter=0;
			ADMUX =FOCUS_IN;	
			break;
		case 6: // 6 - focus position (pot. single ended Vcc Ref)
			ADCOutput = (ADCL);
			ADCOutput +=(ADCH<<8);		//Add the high ADC byte to output result
			focusPosArray[focusArrayCounter]=ADCOutput+FOCUSOFFSET;
			focusArrayCounter++;
			if (focusArrayCounter>=FOCUS_ARRAY_SIZE) focusArrayCounter=0;
			ADMUX =SUN_IN;	
			break;
		case 7:	 	// 7 - not yet used		
			ADMUX =TRACK_POS_IN;
			break;
		case 8: // 8 - track position, first read, discard (pot. single ended. 2.56V ref)
			//Discard first reading with new Vref.
			break;
		case 9: // 9 - track position, second read.
			//rprintf("\r\nADCL: %x\r\n",ADCL);
			//rprintf("\r\nADCH: %x\r\n",ADCH);
			ADCOutput = (ADCL);
			ADCOutput += (ADCH<<8);
			trackPosArray[trackPosArrayCounter]=ADCOutput;
			trackPosArrayCounter++;
			if (trackPosArrayCounter>=TRACKPOS_ARRAY_SIZE) trackPosArrayCounter = 0;
			ADMUX =TRACK_ERR_IN;
			break;
		//default: break;
	}
	ADCCycleCounter++;		//Increment to next ADC reading
	if (ADCCycleCounter >= 10) ADCCycleCounter=0; //Loop back to first reading
	ADCSRA = ADC_MASK;		//Start next conversion
}

---------------------------------------------------
Problems:

1) The differential intput in (case 0,1) measures a positive result, even when I switch the inputs (ADCH is always 0x00). I am not sure what I am actually measuring.

2) The pot input (case 8,9) needs a long time to settle to the 2.56V reference. The manual says throw out the first input, but I need to throw out about 20 before the readings are correct. This seems ridiculous. Before this point, I get about half the magnitude of result, suggesting Vcc is still selected as Vref.

The pot in problem 2) is 2.5kOhm, with 5V across it. The wiper is connected directly to PORTF 7.

I have been using rprintf to determine ensure that the ADMUX is set correctly for each input.

Thanks for reading up to here. I am grateful for any advice and happy to give any additional information.

Nick

Last Edited: Thu. Apr 24, 2008 - 12:59 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
MCUCSR=0x80;      //Disable the JTAG interface 
ADCSRA = ADC_MASK;   //Initialise ADC and start first conversion 
MCUCSR=0x80;      //Disable the JTAG interface. 

Will NOT work (that is it WON'T disable the JTAG).

As the datasheet sys you have to write the JTD but in MCUCSR twice within four cycles. Your access to ADCSRA in the middle of the sequence almost certainly means you do not meet the 4 cycle requirement. You should use:

MCUCSR=0x80;      //Disable the JTAG interface 
MCUCSR=0x80;      //Disable the JTAG interface. 
ADCSRA = ADC_MASK;   //Initialise ADC and start first conversion 

and make sure you build with optimisation switched on so that the double write to MCUCSR is done in the shortest time possible (check the generated asm to make sure the 4 cycle limit is being achieved)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
ADCOutput +=(ADCH<<14);

ADCH is an 8-bit byte, so if you shift it 14 places, none of the original digits are left in the result.
You can get the whole 10-bit result in an int by reading ADCW instead of ADCL & ADCH separately.
(ADCW is not a register in the avr, but a "helper" provided by avr-libc, which reads and shifts the result for you)

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

Surely the 14 (treated as 'int') in there will cause the ADCH value that's read to be extended to 16 bits first so the <<14 is going to leave you with the bottom two bits. Now why you'd want to do this I have no idea but I don't think it's right to say that NONE of the original digits are left in the result.

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

Thanks for the answers.

Clawson, I'll give that a go.

mrono, I was assuming the because ADCOutput is 16-bit that ADCH would be promoted to 16-bit for the calculation. The ADCW sounds good.

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

Yes but you are shuffling the 6 most significant bits of ADCH "off the end" and only adding in the bottom 2 bits?!? Is that really what you intended?

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

Quote:
Surely the 14 (treated as 'int') in there will cause the ADCH value that's read to be extended to 16 bits first so the <<14 is going to leave you with the bottom two bits.

you're probably right. I thought the promotion would leave me with a 16-bit uint, without changing the mangnitude (i.e. simply adding zeros in the eight MSBs). With the shifting, I was aiming to get my left-adjusted 10 bits, then assign this to a signed integer.

I suppose the way I will do this now is (int16_t) = ADCW where ADCW is a left-adjusted result.

For problem 1) however, the results of the ADCL and ADCH were wrong before I did any numerical conversion.

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

You're right, I didn't read the code carefully enough. But still, you're making the reading more difficult than it is with all that shifting.

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

Quote:
But still, you're making the reading more difficult than it is with all that shifting.

You're right. I will be using ADCW from now on ;)

Clawson, I tried

MCUCSR=0x80;      //Disable the JTAG interface
MCUCSR=0x80;      //Disable the JTAG interface. 

and have the GCC optimiser set to -O2 but still get the same result for problem 2). Could it be possible that the original code did work to disable the JTAG interface. The reason I ask is that before I added

MCUCSR=0x80;      //Disable the JTAG interface
ADCSRA = ADC_MASK;   //Initialise ADC and start first conversion
MCUCSR=0x80;      //Disable the JTAG interface.

I was measuring 5V on the upper PORTF pins, and after adding, it, I measured 0V.

Also, if I waste a lot of time, either by discarding another 20 ADC readings or writing some rubbish to screen, I do get the correct answer. Unless JTAG is somehow half on and causing some sort of delay in adjusting Vref, I don't think it is interfering.

Just to be sure...

Quote:
check the generated asm to make sure the 4 cycle limit is being achieved

how do I do this?

Is there any reason why the Vref would be taking so long to stabilise?

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

To check the asm:

1) look at the .lss file
or
2) "make file.s" to generate the assembler file passed from compiler to assembler
or
3) load .elf into Studio and use disassembler in the simulator (start debugging then right click and "Goto disassembly")

But if you are having problems why not just switch the JTAGEN fuse?

Cliff

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

Cliff, I am assuming the following means that the four clock cycle criteria is met (following your suggestion 3))...

292:      	ADMUX =TRACK_ERR_IN;	//Set ADC input port to track input
+00000AE5:   EC8B        LDI     R24,0xCB         Load immediate
+00000AE6:   B987        OUT     0x07,R24         Out to I/O location
293:      	MCUCSR=0x80;		//Disable the JTAG interface
+00000AE7:   E880        LDI     R24,0x80         Load immediate
+00000AE8:   BF84        OUT     0x34,R24         Out to I/O location
294:      	MCUCSR=0x80;		//Disable the JTAG interface.
+00000AE9:   BF84        OUT     0x34,R24         Out to I/O location
295:      	ADCSRA = ADC_MASK;	//Initialise ADC and start first conversion

Quote:
why not just switch the JTAGEN fuse?

I need the JTAG connection for programming.

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

Yup the side by side OUTs to 0x34 guarantee that this WILL be disabling the JTAG pins - so if the code still doesn't work the problem is something else.

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

It's something else then. Still haven't managed to solve 1) or 2).

I know it's never the chip, but I tried a new processor anyway.

Will try the code on my STK 501 to make sure its not some weird hardware problem.

Still grateful for any other suggestions.

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

Okay, just in case anyone's interested,

I tested the code at the top using a different board and setup and different input sources.

1) was due to the signal source having practically no short-circuit current and therefore not sufficiently driving the differential input port.

2) When changing between Vcc and 2.56V as Vref, it takes a very long time for the result to reflect the new Vref (about 10 ADC reads). I tried this with my STK500, STK501 and applied the analogue voltage directly to pin 7. Unless I missed something here, the manual is inaccurate.

I will be keeping a single Vref for all my ADC measurements and changing some of the pot. input voltages instead to get better use out of my 10 bits.

Thanks mrono and Cliff for your responses.

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

Quote:
2) When changing between Vcc and 2.56V as Vref, it takes a very long time for the result to reflect the new Vref (about 10 ADC reads). I tried this with my STK500, STK501 and applied the analogue voltage directly to pin 7. Unless I missed something here, the manual is inaccurate.

There was an extensive thread on this a while back. The ADc doesn't draw much off Vref, apparently, and especially when coupled on the AREF pin with a cap it takes a long time to "die down" from higher voltage to lower. I cannot recall if a short-cut was found to pull it down faster.

Lee

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

I found it! [Darn I'm one old elephant--9 months ago.]
https://www.avrfreaks.net/index.p...

Lee

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

Thanks Lee,

certainly some important information regarding the settling time required when switching between internal voltage references. Certainly something that could be included in the manual. Unfortunate that I missed it in my original search, due to some slightly varying terminology (e.g ADC as opposed to A/D).

Regarding 1), my problem is clearly due to input requirements for differential ADC channels. If I cannot find this information, I will post a more specific question in a new thread.

Nick