Channels of ADC

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

Hallo,
I have a task concerning ATMEGA16's ADC. I need to use all the 8 ADC channels. The signals I sample are single ended ranging from 0-5V. I want a free running ADC and an ISR after each completed conversion. I place the data in a 8x10 array. Here is my code:

#include "avr/io.h"
#include "avr/interrupt.h"

#define F_15Hz 2//pokazva kolko pati trbva da vlezem v preksavaneto na timer 0 za da ot4etem ~ 15Hz
#define F_30Hz 1//pokazva kolko pati trbva da vlezem v preksavaneto na timer 0 za da ot4etem ~ 30Hz
#define ADC_length 10//masivat s dannite ot ADC moge da sahranqva do 50 stoinosti
#define ADC_channels 8//imam 8 kanal
#define ADC_range 5//maksimalnoto napregenie e 5V
#define ADC_scale 1024//imam 1024 stapki

//globalni promenlivi
int *p_adc_data;//ukazatel kam masiva s dannite za adc
int *p_adc_data_end;//ukazatel kam posledniq element na masiva za danni ot ADC
int *p_adc_data_init;//ukazatel kam posledniq element na masiva za danni ot ADC

//obedinenie za polu4avane sabirane na high i low byte ot rezultata adc konvertiraneto
union {unsigned char bytes[2];
	   int result;}adc_res;

//macros za aktovorane na periferiqta
#define ADC_START ADCSRA=0b11101010
#define ADC_STOP ADCSRA=0b10001010
#define ADC_CHAN(num) ADMUX = ((ADMUX & 0b11111000) | num)


int adc_out(unsigned char high, unsigned char low)
{
	adc_res.bytes[1]=high;
	adc_res.bytes[0]=low;
	return adc_res.result;
}

//funkciq za inicializirane na masiva na ADC
void adc_data_init(void)
{
	while(p_adc_data<=p_adc_data_end)
	{
		*p_adc_data=0;
		p_adc_data++;
	}
	p_adc_data=p_adc_data_init;
}

//Prekasvane pri priklu4vane na konvertiraneto na ADC
ISR(ADC_vect)
{
	static char channel=0;

	//1. Pro4itam rezultata	i go zapisvam v masiva s dannite
	*p_adc_data=adc_out(ADCH,ADCL);

	if(p_adc_data aktivira verigata na ADC
					  //bit6 -> start i stop na ADC
					  //bit5 -> autotrigger
					  //bit4 -> interrupt flag
					  //bit3 -> interrupt enable
					  //bit2/1/0 -> prescaler, izbran e delitel 2 => ADC clock=8MHz
	SFIOR=0x00;//free running mode
	//******************************************************************************//


	//***************** I/O port diretion *******************//
	DDRA=0x00;//PORA e vhod
	//*******************************************************//

	//razre6avam prekasvaniqta
	sei();
}



int main(void)
{
	int adc_data[ADC_length][ADC_channels];//tova emasiv v koito sahranqvam dannite ot ADC
	//vsqka kolona otgovarq na edin ot kanalite: 
		//kolona 0 -> kanal 1 ili duty cycle na impulsite kam elektrodite
		//kolona 1 -> kanal 2 ili 4estota na impulsite kam elektrodite
		//kolona 2 -> kanal 3 ili duty cycle na impulsite kam bobinata
		//kolona 3 -> kanal 4 ili 4estota na impuslite kam bobinata
		//kolona 4 -> obratna vrazka
		//kolona 5 -> obratna vrazka
		//kolona 6 -> obratna vrazka
		//kolona 7 -> obratna vrazka

	//Adresiram promenlivite za popalvane na masiva
	p_adc_data=&adc_data[0][0];//predavam na4alniq adres na masiva s dannite
	p_adc_data_init=&adc_data[0][0];//predavam na4alniq adres na masiva s dannite
	p_adc_data_end=&adc_data[ADC_length-1][ADC_channels-1];//polu4avam adresa na posledniq element ot masiva

	adc_data_init();//inicializiram masiva za dannite ot ADC s 0.00
	init();//inicializiram MCU, aktiviram periferiqta i prekasvaniqta 

	ADC_CHAN(0);//izbram kanal 0
	ADC_START;//puskam ADC

	while(1)
	{
	}

	return 0;
}

I use 16MHz crystal and I want max speed of the ADC. So I have selected a divisor factor x2. I want the ADC to operate with all 10bits.

If I use these settings and only one channel everything is fine. I sample correctly all values between 0V and 5V.
When I go over all the 8 channels it seems that the ADC misses data. The tried this:
I have 5V on the channels 0-3 and 0V on channels 4-7. And the result from the conversion is (or something close to this (in decimals)):
channels 0,1,2 = 1017, channels 3 = 673, channel 4 318, channels 5,6,7 = 7;

I change the channels in the ISR. Is this a correct approach? If not how should it be done?

Thank you.

P.S. Half an hour ago I accidentally placed 7V on the ADC channels? Now my Atmega get very very hot when I swtich it on. I measured a short circuit between pins 10 and 11(Vcc and GND). Obviously the MCU is dead but I wander if these 7V have killed it?

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

Yes, 7V can kill your processor, it’s outsider maximum allowable voltage according to datasheet.
When do you change ADC channel (MUX register)? The problem is, when you write new value into MUX you have to start conversion again. So in free running mode you are always one channel late.

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

Quote:

I want a free running ADC

Quote:

I need to use all the 8 ADC channels.

A recipe for disaster, IMO.

Search the forums for "round robin lee" for prior discussions. Several are extensive, and within the past year or so.

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

TFrancuz:
this is what I do: after I set up the ADC I chose channel 0 and start the ADC. In the following ISR I save this result as current value of channel 0. Then still in the ISR I chose channel 1 and exit the ISR.

As far as I understand you suggest me this:
- pick up for example channel 2
- wait for the current conversion to complete
- start another conversion
- when this conversion is complete store this result as current value for channel 2.

This makes sense but the sample time is longer. I will try it when I buy a new ATMEGA:)

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

Quote:

This makes sense but the sample time is longer.

Actually, it isn't. If you insist on freerunning as you have described, then here is what actually happens:

--I set up the ADC I chose channel 0 and start the ADC. [correct so far]
-- In the following ISR I save this result as current value of channel 0. [still correct so far]
-- Then still in the ISR I chose channel 1 and exit the ISR. [still fine]

But what do you do the next time that the interrupt occurs? That result will be from channel 0, not channel 1. So you must ignore that result and go on to the next.

Besides all that taking two conversion times versus 1-and-a-fraction, I'd wager a cold one that you will store a conversion result into the wrong bucket.

Trust me--do it the "right" way. Remember that if you are trying to get a high sample rate then you need to have good drive in your signals.

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

Hi Lee, thank you for the answer.
The sequence you describe is exactly what I meant. I saw my last post and I can see that I have not explained explained myself well enough there.
As far as I can understand you suggest to store the result form the ADC, which I receive during the change of channels. OK I can do this, but I don't know what use can I have of this result. And what do you mean by:

Quote:

Trust me--do it the "right" way.

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

Quote:
The sequence you describe is exactly what I meant.

Your missing Lee's point. With free running mode, by the time you get to the ADC complete ISR, the next conversion has already started, so changing the channel will not affect that conversion. Because of this, the result will always be one channel off from what you think it is.

What Lee is suggesting for the "right way" is to not use free-running mode, use single conversion. In the ISR you would then read the result, set the channel, then start the next conversion. The conversion rate will be slightly slower, but only by maybe 20 or 30 clock cycles.

Regards,
Steve A.

The Board helps those that help themselves.

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

Koshchi wrote:
Quote:
The sequence you describe is exactly what I meant.

Your missing Lee's point. With free running mode, by the time you get to the ADC complete ISR, the next conversion has already started, so changing the channel will not affect that conversion. Because of this, the result will always be one channel off from what you think it is.

What Lee is suggesting for the "right way" is to not use free-running mode, use single conversion. In the ISR you would then read the result, set the channel, then start the next conversion. The conversion rate will be slightly slower, but only by maybe 20 or 30 clock cycles.

Sheesh! Umpty-decades' worth of programming expertise here on the Web, reduced to bafflement by a one-stage pipeline???

Run in continuous-conversion mode. The conversion-complete ISR will read the ADMUX into temporary variable "chan". The value (by the time you get 'round to reading it) tells you which channel the next conversion has already begun working on. In THIS pass of the ISR, you want to

    a) read the conversion results from the ADC, and store them in their appropriate buffer slot, and
    b) update ADMUX so that when the next conversion completes, the ADC will sample and begin converting the next channel after that.

So, your ISR would look roughly like this:

volatile uint16_t conversions[8];

ISR(ADC_vect)
{   uint8_t   chan = ADMUX;
    uint8_t oldMux = chan & ~7;
    conversions [ (chan - 1) & 7 ] = ADC;
    ADMUX = oldMux | (++chan & 7);
}
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Obviously I miss something again.
Here is the last ISR I wrote:

[code]ISR(ADC_vect)
{
static char channel=0;//channel 0 was picked before starting ADC
static char first_ISR=0;//if this is the first ADC_complete ISR
//I don't store any data, just pick the
//next channel

if(first_ISR==0)//don't store the data from the first ISR
{
first_ISR=1;
}
else//every next ISR will store data
{
//Point to the next address
if(p_adc_data {
//Store ADC data on the appropriate addres
*p_adc_data=adc_out(ADCH,ADCL);
//next address
p_adc_data++;
}
else
{
//Store ADC data on the appropriate addres
*p_adc_data=adc_out(ADCH,ADCL);
//next address
p_adc_data=p_adc_data_init;
}
}

//Point to the next channel
if(channel<7)
{
channel++;
}
else
{
channel=0;
}

//Pick the next channel.
ADC_CHAN(channel);
}

In the init() /I have not shown this one/ I pick up channel 0 to be sampled first. When this first conversion is complete I enter the first ADC ISR. I will not store the result from the conversion, because the next time I enter the ISR channel 0 will still be active. The second thing I do in this "first ISR" is to pick up the next channel (channel 1 in this case, ADMUX=1). Now I exit the ISR.
In the following ISR (lets call it "second ISR") I store the result from the conversion. This is the first value of channel 0. After that I pick up channel 2(ADMUX=2). Meanwhile, the conversion which is in progress is the conversion of channel 1 .
In the third ISR I store the result from the conversion of channel 1 and pick up the next channel 3. Meanwhile channel 2 is being sampled.

I think that this is what you all tried to explain to me.

Now the problem: I don't get correct results. For example if I have 0V on channels 1-7. I change the value of channel 0 and I see changes in the values receive for channel 7 !?!?!?!

Do I have to wait for another conversion for every channel?

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

Quote:

Now the problem: I don't get correct results. For example if I have 0V on channels 1-7. I change the value of channel 0 and I see changes in the values receive for channel 7 !?!?!?!

And I was lambasted for using the inflamatory words "right way".

"I told you so." For old bit-pushers it is much more straightforward to do single-conversions and forget all that free-running nonsense. Yes, if you are trying to get the absolute maximum number of samples then you might save a hair. Lets see--for max resolution with a 200kHz ADC clock and 13 ADC clock cyles per conversion that is 65us per conversion. If it takes you 3us on the average to get to the next ADSC then you've increased your throughput 5%.

I never cut it that close so I don't care. Levenkay is more clever than I am, so his head doesn't hurt trying to keep it all straight.

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

Theres nothing like the data sheet..

Atmel (in the ATmega16 data sheet, page 211, second paragraph) wrote:
In Free Running mode, always select the channel before starting the first conversion. The channel
selection may be changed one ADC clock cycle after writing one to ADSC. However, the
simplest method is to wait for the first conversion to complete, and then change the channel
selection. Since the next conversion has already started automatically, the next result will reflect
the previous channel selection
. Subsequent conversions will reflect the new channel selection.

(My emphasis in the quote above)

As of January 15, 2018, Site fix-up work has begun! Now do your part and report any bugs or deficiencies here

No guarantees, but if we don't report problems they won't get much of  a chance to be fixed! Details/discussions at link given just above.

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]

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

Quote:
So, your ISR would look roughly like this:

volatile uint16_t conversions[8];

ISR(ADC_vect)
{   uint8_t   chan = ADMUX;
    uint8_t oldMux = chan & ~7;
    conversions [ (chan - 1) & 7 ] = ADC;
    ADMUX = oldMux | (++chan & 7);
} 


Which works great as long as you are guaranteed to not miss an interrupt. The minute you do, you are reading the wrong channel again. It is also incorrect on the first pass through the ISR.

Regards,
Steve A.

The Board helps those that help themselves.

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

Finally I gave up the idea of using free running mode. It is working fine in single conversion mode.
So in conclusion we should say: use free running mode only if you have one ADC channel :)

Something else bothers me now: when I have 0V on the adc channels I get 39 as a result from the conversion(this equals 0.19V according to my calculations). The same is if I have 5V(actually it is 5.15V, and the VCC is 5.15). This time the result is 990(again 0.2V offset). Isn't it too large offset. In 10bit mode the ADC step is VCC/1024~5mV. So the offset is about 4xLSB???
I consider using the NOISE reduction option, but as far as I understood it is used only for the sleep mode.
What can I do to increase the accuracy?

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

I handled the offset.
Thank you all for your help.

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

Quote:

//bit2/1/0 -> prescaler, izbran e delitel 2 => ADC clock=8MHz

Quote:

So the offset is about 4xLSB???
...
What can I do to increase the accuracy?

I cannot tell from your posted code what speed the AVR is running at. The line above shows a prescaler of /4 for the ADC clock. Unless your AVR is running at 1MHz then the ADC clock is too high for full resolution and accurate results. The recommended ADC clock is 50kHz to 200kHz.

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.