Forums

AVR ADC multiplexer

Started by Simon November 20, 2008
Hi all,

I'm trying to sample the AVR's ADC on 3 channels at 8kHz, 8kHz and
16kHz respectively. I've set up a 32kHz interrupt on an 8MHz clock
using timer0 (prescale=1, count to 250), and within the timer
interrupt handler, I have a state machine which samples ch-1, ch-2,
ch-3, ch-2. The code is at http://www.0x0000ff.com/src/main.c if
you're interested...

To test it, I put a variable resistor between 0 and AVCC and wired it
up to ch-1 - it all worked fine, twiddling the potentiometer caused me
to get values between 0x00 and 0xFF. The problem is that changing
which pin I linked the potentiometer up to *still* gave values on
ch-1. Any of the inputs I selected with the mux would always read the
same value as the pot.

I verified it wasn't a hardware problem (linking ch1=ADC7, ch2=ADC6,
ch3=ADC5 and putting the pot on ADC-1 showed no reading. Putting the
pot on any of ADC-7, ADC-6, ADC-5 showed the reading on ADC-7. Then I
switched so ch1=ADC-1, ch2=ADC-2, ch3=ADC-3 and I saw the pot's value
on any of ADC-1, ADC-2, ADC-3).

So, am I being unreasonable, asking the ADC to switch at 32kHz ? Am I
just doing it wrong ? Any ideas very welcome :)

Cheers,
  Simon
On Nov 20, 10:03=A0am, Simon <goo...@gornall.net> wrote:
> Hi all, > > I'm trying to sample the AVR's ADC on 3 channels at 8kHz, 8kHz and > 16kHz respectively. I've set up a 32kHz interrupt on an 8MHz clock > using timer0 (prescale=3D1, count to 250), and within the timer > interrupt handler, I have a state machine which samples ch-1, ch-2, > ch-3, ch-2. The code is athttp://www.0x0000ff.com/src/main.cif > you're interested... > > To test it, I put a variable resistor between 0 and AVCC and wired it > up to ch-1 - it all worked fine, twiddling the potentiometer caused me > to get values between 0x00 and 0xFF. The problem is that changing > which pin I linked the potentiometer up to *still* gave values on > ch-1. Any of the inputs I selected with the mux would always read the > same value as the pot. > > I verified it wasn't a hardware problem (linking ch1=3DADC7, ch2=3DADC6, > ch3=3DADC5 and putting the pot on ADC-1 showed no reading. Putting the > pot on any of ADC-7, ADC-6, ADC-5 showed the reading on ADC-7. Then I > switched so ch1=3DADC-1, ch2=3DADC-2, ch3=3DADC-3 and I saw the pot's val=
ue
> on any of ADC-1, ADC-2, ADC-3). > > So, am I being unreasonable, asking the ADC to switch at 32kHz ? Am I > just doing it wrong ? Any ideas very welcome :) > > Cheers, > =A0 Simon
You don't say which uC you are using... I think that most of the AVRs cannot support this acquisition rate. Usually they have a limit of 200kHz on the ADC clock, and a minimum of 13 clocks for each conversion. Sorry, didn't look at your code enough to see why your multiplexer switching was broken. -f
Simon wrote:
> Hi all, > > I'm trying to sample the AVR's ADC on 3 channels at 8kHz, 8kHz and > 16kHz respectively. I've set up a 32kHz interrupt on an 8MHz clock > using timer0 (prescale=1, count to 250), and within the timer > interrupt handler, I have a state machine which samples ch-1, ch-2, > ch-3, ch-2. The code is at http://www.0x0000ff.com/src/main.c if > you're interested...
Are you sure the ADC can handle this sampling rate? Check your settings against ADC clock limits and conversion period.
> So, am I being unreasonable, asking the ADC to switch at 32kHz ? Am I > just doing it wrong ? Any ideas very welcome :)
Your problem might result from wrong definition of buffer access macros - make sure you use "volatile" keywrd when necessary. Also, if you use AVR-GCC, examine the compiler output carefully (.lss file). You may encounter some surprises there - "overoptimized" code is quite common. One basic idea about your program, not directly related to your problem: DO NOT put any waiting loop in the interrupt service routine. The timer interrupt routine should read and store the result of previous conversion, start the next one and exit. You will get the result of just-started conversion on the next interrupt.
On Nov 20, 11:22=A0am, BlueDragon <easy2f...@the.net> wrote:
> Simon wrote: > > Hi all, > > > I'm trying to sample the AVR's ADC on 3 channels at 8kHz, 8kHz and > > 16kHz respectively. I've set up a 32kHz interrupt on an 8MHz clock > > using timer0 (prescale=3D1, count to 250), and within the timer > > interrupt handler, I have a state machine which samples ch-1, ch-2, > > ch-3, ch-2. The code is athttp://www.0x0000ff.com/src/main.cif > > you're interested... > > Are you sure the ADC can handle this sampling rate? Check your settings > against ADC clock limits and conversion period. > > > So, am I being unreasonable, asking the ADC to switch at 32kHz ? Am I > > just doing it wrong ? Any ideas very welcome :) > > Your problem might result from wrong definition of buffer access macros > - make sure you use "volatile" keywrd when necessary. Also, if you use > AVR-GCC, examine the compiler output carefully (.lss file). You may > encounter some surprises there - "overoptimized" code is quite common.
I will look at the assembly... I thought the 'volatile' was only necessary when accessing variables both inside *and* outside the ISR, though. At the moment, I push the ADC value into a circular buffer from within the loop, which should just copy and increment a buffer pointer.
> > One basic idea about your program, not directly related to your problem: > DO NOT put any waiting loop in the interrupt service routine. The timer > interrupt routine should read and store the result of previous > conversion, start the next one and exit. You will get the result of > just-started conversion on the next interrupt.
Yeah, I've tried a few variations on the theme before posting. Agreed on the wait. The next plan was to use the ADC-result-ready ISR to just (1) store the value in the correct buffer, (2) switch the MUX, (3) start a new conversion. I didn't think that would solve my MUX issue though, so thought I'd post the code... On Nov 20, 10:52 am, cassiope <f...@u.washington.edu> wrote:
> > You don't say which uC you are using...
It's a Mega644
> I think that most of the AVRs cannot support this acquisition rate. > Usually they have a limit of 200kHz on the ADC clock, and a minimum of 13 > clocks for each conversion.
I thought you could trade accuracy for sample-rate. I'm only using 8- bits of resolution here (the sampled audio doesn't have to sound great :), so I thought I could push the sample rate higher. I guess that's part of what I'm asking - is that true ? Or do I just have crap code ? :) Cheers, Simon
Simon wrote:
> On Nov 20, 11:22 am, BlueDragon <easy2f...@the.net> wrote: >> Simon wrote: >>> Hi all, >>> I'm trying to sample the AVR's ADC on 3 channels at 8kHz, 8kHz and >>> 16kHz respectively. I've set up a 32kHz interrupt on an 8MHz clock >>> using timer0 (prescale=1, count to 250), and within the timer >>> interrupt handler, I have a state machine which samples ch-1, ch-2, >>> ch-3, ch-2. The code is athttp://www.0x0000ff.com/src/main.cif >>> you're interested... >> Are you sure the ADC can handle this sampling rate? Check your settings >> against ADC clock limits and conversion period. >> >>> So, am I being unreasonable, asking the ADC to switch at 32kHz ? Am I >>> just doing it wrong ? Any ideas very welcome :) >> Your problem might result from wrong definition of buffer access macros >> - make sure you use "volatile" keywrd when necessary. Also, if you use >> AVR-GCC, examine the compiler output carefully (.lss file). You may >> encounter some surprises there - "overoptimized" code is quite common. > > I will look at the assembly... I thought the 'volatile' was only > necessary when accessing variables both inside *and* outside the ISR, > though. At the moment, I push the ADC value into a circular buffer > from within the loop, which should just copy and increment a buffer > pointer. >
You haven't posted code for the cb* types and functions, making it difficult to check them. Typically, you'll want to make the buffer pointers volatile but not the buffer itself.

Simon wrote:


> I'm trying to sample the AVR's ADC on 3 channels at 8kHz, 8kHz and > 16kHz respectively. I've set up a 32kHz interrupt on an 8MHz clock > using timer0 (prescale=1, count to 250), and within the timer > interrupt handler, I have a state machine which samples ch-1, ch-2, > ch-3, ch-2. The code is at http://www.0x0000ff.com/src/main.c if > you're interested...
[...]
> So, am I being unreasonable, asking the ADC to switch at 32kHz ? Am I > just doing it wrong ? Any ideas very welcome :)
1. The internal ADC of AVR can be set to work as you described, although the accuracy will be down to 8 bits or so. 2. Your code exposes the great deal of inexperience. Apparently there are some silly bugs. Vladimir Vassilevsky DSP and Mixed Signal Design Consultant http://www.abvolt.com
Ok, so here's a followup - the code now looks like:

/
******************************************************************************
\
|* ADC has finished a conversion, store it and trigger another
\******************************************************************************/
ISR (ADC_vect)
	{
	static uint8_t channel 	= SAMPLE_LEFT;

	switch (channel)
		{
		case SAMPLE_LEFT:
			__adc[SAMPLE_LEFT]  = ADCH;
			PORTB = ADCH;
			channel 			= SAMPLE_TC1;
			ADMUX 				= _BV(ADLAR) | TIMECODE_CHANNEL;
			ADCSRA			   |= _BV(ADSC);
			break;

		case SAMPLE_TC1:
			__adc[SAMPLE_TC1]   = ADCH;
			channel 			= SAMPLE_RIGHT;
			ADMUX 				= _BV(ADLAR) | RIGHT_AUDIO_CHANNEL;
			ADCSRA			   |= _BV(ADSC);
			break;

		case SAMPLE_RIGHT:
			__adc[SAMPLE_RIGHT] = ADCH;
			channel 			= SAMPLE_TC2;
			ADMUX 				= _BV(ADLAR) | TIMECODE_CHANNEL;
			ADCSRA			   |= _BV(ADSC);
			break;

		default:
		case SAMPLE_TC2:
			__adc[SAMPLE_TC1]   = ADCH;
			channel 			= SAMPLE_LEFT;
			ADMUX 				= _BV(ADLAR) | LEFT_AUDIO_CHANNEL;
			ADCSRA			   |= _BV(ADSC);
			break;
		}
	}

/
******************************************************************************
\
|* Timer-0 overflow. Either change the ADC MUX or sample a value
\******************************************************************************/
ISR (TIMER0_COMPA_vect)
	{
	static uint8_t state 	= SAMPLE_LEFT;

	/
**************************************************************************
\
	|* State machine for setting up samples, and sampling
	
\**************************************************************************/
	switch (state)
		{
		case SAMPLE_LEFT:
			state = SAMPLE_TC1;
			cbWrite(&_left, __adc[SAMPLE_LEFT]);
			break;

		case SAMPLE_TC1:
			state = SAMPLE_RIGHT;
			cbWrite(&_tc, __adc[SAMPLE_TC1]);
			break;

		case SAMPLE_RIGHT:
			state = SAMPLE_TC2;
			cbWrite(&_right, __adc[SAMPLE_RIGHT]);
			break;

		case SAMPLE_TC2:
			state = SAMPLE_LEFT;
			cbWrite(&_tc, __adc[SAMPLE_TC1]);
			break;
		}
	TCNT0 	= 0;
	}

Note that PORTB is being set to the value of ADCH within the ISR, and
that there's no loops waiting for anything any more - the ADC_vect is
self-triggering once the first conversion has been requested.

In this situation, the LEDs light up for port-B no matter which input
(TIMECODE_CHANNEL, LEFT_AUDIO_CHANNEL, RIGHT_AUDIO_CHANNEL) is
connected to the potentiometer. I tried changing the ADC sample-rate
(setting the ADC prescaler to 128 from 16) and the interrupt-sample-
rate (setting timer0's prescaler to CLK/64 from CLK). No difference.

At this point, it looks as though the AVR is simply ignoring my
request to change the MUX. I'm seriously thinking of getting 3 AT-
Tiny's and making them do one channel each rather than try to mux it
on the mega-AVR...

Simon

On 20 Nov, 18:03, Simon <goo...@gornall.net> wrote:
> Hi all, > > I'm trying to sample the AVR's ADC on 3 channels at 8kHz, 8kHz and > 16kHz respectively. I've set up a 32kHz interrupt on an 8MHz clock > using timer0 (prescale=1, count to 250), and within the timer > interrupt handler, I have a state machine which samples ch-1, ch-2, > ch-3, ch-2. The code is athttp://www.0x0000ff.com/src/main.cif
I have used a number of AVRs and you seem to be using free running mode. Ypu will need to be careful about when you change the MUX. The MUX will only change after the current conversion completes. In free-running mdoe a conversion is in progress when you change the mux so the change won't happen until that conversion completes. If you use free-running mode you should use the ADC conversion complete interrupt and then change the mux but remember by the time you enter your ISR a new conversion will have started so you need to set the mux to the state you want for the conversion 2 after the one you are reading the result for....! Either way I can confirm that you can get the ADC mux to work in AVRs you just have to read the datasheets carefully and understand what they are saying. HTH Iain
On Nov 21, 8:54=A0am, iai...@googlemail.com wrote:
> On 20 Nov, 18:03, Simon <goo...@gornall.net> wrote: > > > Hi all, > > > I'm trying to sample the AVR's ADC on 3 channels at 8kHz, 8kHz and > > 16kHz respectively. I've set up a 32kHz interrupt on an 8MHz clock > > using timer0 (prescale=3D1, count to 250), and within the timer > > interrupt handler, I have a state machine which samples ch-1, ch-2, > > ch-3, ch-2. The code is at http://www.0x0000ff.com/src/main.cif > > I have used a number of AVRs and you seem to be using free running > mode.
I'm not (well ADCSRA:ADATE is set to 0), I'm just re-triggering the ADC by setting ADCSRA:ADSC to 1 within the ADC_vect ISR. The ISR and timer functions are actually above.
> Ypu will need to be careful about when you change the MUX. > The MUX will only change after the current conversion completes. > In free-running mdoe a conversion is in progress when you change the > mux so the =A0change won't happen until that conversion completes. > If you use free-running mode you should use the ADC conversion > complete interrupt and then change the mux but remember by the time > you enter your ISR a new conversion will have started so you need to > set the mux to the state you want for the conversion 2 after the one > you are reading the result for....!
Hmm - perhaps there's something in that, although I use an internal state machine in the ISR and set it to (state+1 % 4). The thing is that (without the auto-trigger firing), I think that ought to be correct. ADMUX is set first, then ADCSRA:ADSC is set to start the conversion. I think it has to be an issue with the MUX, and nothing to do with any volatile variables because I'm setting PORTB (the leds) to the value of ADCH, and no matter which input I connect my potentiometer to (ADC5, ADC6, ADC7) , those leds light up as I rotate the pot. It's a shame to dedicate an AVR as a 1-channel ADC, and it offends my sense of elegance, but they're so damn cheap that using 3 of them is probably the most efficient solution in terms of time...
> Either way I can confirm that you can get the ADC mux to work in AVRs > you just have to read the datasheets carefully and understand what > they are saying.
Now that's just frustrating :) Cheers, Simon.
Simon wrote:
> Ok, so here's a followup - the code now looks like: > > / > ****************************************************************************** > \ > |* ADC has finished a conversion, store it and trigger another > \******************************************************************************/ > ISR (ADC_vect) > { > static uint8_t channel = SAMPLE_LEFT; > > switch (channel) > { > case SAMPLE_LEFT: > __adc[SAMPLE_LEFT] = ADCH; > PORTB = ADCH; > channel = SAMPLE_TC1; > ADMUX = _BV(ADLAR) | TIMECODE_CHANNEL; > ADCSRA |= _BV(ADSC); > break; > > case SAMPLE_TC1: > __adc[SAMPLE_TC1] = ADCH; > channel = SAMPLE_RIGHT; > ADMUX = _BV(ADLAR) | RIGHT_AUDIO_CHANNEL; > ADCSRA |= _BV(ADSC); > break; > > case SAMPLE_RIGHT: > __adc[SAMPLE_RIGHT] = ADCH; > channel = SAMPLE_TC2; > ADMUX = _BV(ADLAR) | TIMECODE_CHANNEL; > ADCSRA |= _BV(ADSC); > break; > > default: > case SAMPLE_TC2: > __adc[SAMPLE_TC1] = ADCH; > channel = SAMPLE_LEFT; > ADMUX = _BV(ADLAR) | LEFT_AUDIO_CHANNEL; > ADCSRA |= _BV(ADSC); > break; > } > } > > / > ****************************************************************************** > \ > |* Timer-0 overflow. Either change the ADC MUX or sample a value > \******************************************************************************/ > ISR (TIMER0_COMPA_vect) > { > static uint8_t state = SAMPLE_LEFT; > > / > ************************************************************************** > \ > |* State machine for setting up samples, and sampling > > \**************************************************************************/ > switch (state) > { > case SAMPLE_LEFT: > state = SAMPLE_TC1; > cbWrite(&_left, __adc[SAMPLE_LEFT]); > break; > > case SAMPLE_TC1: > state = SAMPLE_RIGHT; > cbWrite(&_tc, __adc[SAMPLE_TC1]); > break; > > case SAMPLE_RIGHT: > state = SAMPLE_TC2; > cbWrite(&_right, __adc[SAMPLE_RIGHT]); > break; > > case SAMPLE_TC2: > state = SAMPLE_LEFT; > cbWrite(&_tc, __adc[SAMPLE_TC1]); > break; > } > TCNT0 = 0; > } > > Note that PORTB is being set to the value of ADCH within the ISR, and > that there's no loops waiting for anything any more - the ADC_vect is > self-triggering once the first conversion has been requested. > > In this situation, the LEDs light up for port-B no matter which input > (TIMECODE_CHANNEL, LEFT_AUDIO_CHANNEL, RIGHT_AUDIO_CHANNEL) is > connected to the potentiometer. I tried changing the ADC sample-rate > (setting the ADC prescaler to 128 from 16) and the interrupt-sample- > rate (setting timer0's prescaler to CLK/64 from CLK). No difference. > > At this point, it looks as though the AVR is simply ignoring my > request to change the MUX. I'm seriously thinking of getting 3 AT- > Tiny's and making them do one channel each rather than try to mux it > on the mega-AVR... > > Simon >
I don't understand why you use both the timer and the ADC interrupts: use the ADC complete if you want to "free run" at max rate _or_ use the timer for a controlled sample rate. After that, try setting a breakpoint inside your ISR and step through the code. You may have to disable other interrupt sources to do it, but you should always step through all your code and test all execution paths. (that's _after_ it passes lint, of course). One other thing: what do you connect to the other inputs while you move the pot around? You aren't leaving them floating, are you? Floating inputs are basically leaky capacitors and as you cycle it the MUX will happily charge all of them to the same voltage as your one low-impedance source. Bob ** Posted from http://www.teranews.com **