AtMega 2560 interrupt ADC

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

Hello all, So i'm trying to get my atMega2560 to run a interrupt ADC but it doesn't seem to be updating my values correctly. I am trying to measure the ADC on PINS A0-A7 which are all thermistors. To do this I am using a interrupt on the ADC, and within the interrupt I am changing the MUX value and than reading the ADC. From reading the datasheets I believe the Interupt Flag is cleared after reading the ADC, because I'm trying to update the MUX before the flag is cleared. However by doing this I am getting all values of 1023 when this code was working when I didn't change the MUX and only measured one pin. I am developing on AtmelStudio6.2. Any help would be greatly appreciated. I attached my code.

Attachment(s): 

This topic has a solution.

Last Edited: Thu. Jul 16, 2015 - 06:42 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Since a conversion only takes about 100usecs, I dont think you need an interrupt. Just a for loop. Hook a pot up to one of the channels and turn it up and down. Nice and smooth?

Imagecraft compiler user

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

You don't want to mix free-running mode with multiple channels. By the time the interrupt is fired, the next conversion has already started so the channels get out of step. The channel of the current reading is from the previous channel, not the current one. Also, your comment of "Have to change mux before ADC read" is nonsense (whether or not you use free-running). This eliminates the reason you thought you needed "currentPin" - "nextPin".

Regards,
Steve A.

The Board helps those that help themselves.

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

Hello Steve,

Thanks for the help. Okay, so the next measurement starts before the interrupt function runs. So do you think I should instead turn off the interrupt Enable, and just check if the interrupt flag is set periodically and then change the mux, read the ADC, and clear the interrupt? I'm thinking maybe a timer interrupt instead then to check all this. I don't need it to be a fast thing. One of my concerns actually was that the ADC interrupt was going to be way faster than I was looking for.

Last Edited: Fri. Jul 10, 2015 - 04:21 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

So do you think I should instead turn off the interrupt Enable

No, you could not set ADATE and instead start the next conversion within the interrupt (after setting the next channel).

I'm thinking maybe a timer interrupt instead then to check all this. I don't need it to be a fast thing. One of my concerns actually was that the ADC interrupt was going to be way faster than I was looking for.

 In that case, you can still use ADATE, but have it trigger from a timer instead of free-running.

Regards,
Steve A.

The Board helps those that help themselves.

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

Okay, I understand now. I will change the Auto trigger to trigger on a timer interrupt instead. I'm only trying to check all 8 thermistors within about two seconds or so. So I am thinking that is a better solution than using the ADC. I appreciate all of your help Steve

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

Okay, I've been trying to change the solution to use a timer interrupt instead but I'm having some trouble. So I set the ADC to trigger on timer1 compare match B (I chose this one over counter 0 to get the 16bit OCR instead of 8). I'm trying to get the interrupt to happen at a quarter of a second. In the interrupt I'm trying to update the ADC MUX to the next one, read and save the ADC, then start the next A2D measurement by setting the ADSC bit. I also set the prescaler of the ADC because in the datasheet it said  "When a positive edge occurs on the selected trigger signal, the ADC prescaler is reset and a conversion is started" on page 277. It also mentions that one of these ways is how to safely change the MUX:

1. When ADATE or ADEN is cleared.

2. During conversion, minimum one ADC clock cycle after the trigger event.

3. After a conversion, before the Interrupt Flag used as trigger source is cleared

So I'm thinking the 3rd one means that I don't understand when the interrupt flag is set, since I'm no longer doing the free running mode I though that the next conversion will only start when you set the ADSC bit. Or do I have to manually have to set the Interrupt Flag to 1 (clear it). Also The timmer interrupt seems to be running far faster than I wanted. I set OCR1B = 62499; and the prescaler to 64. Since I want a frequency of 4 hz I got OCR1B from (16000000Hz /(64 * 4Hz)) - 1 = 62499. Any help would be greatly appreciated. I attached the code.

Attachment(s): 

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

then start the next A2D measurement by setting the ADSC bit.

Do not do this. The point of the auto-trigger is to start the next conversion for you. In the ISR, just read the conversion value, then set the MUX to the next channel.

Regards,
Steve A.

The Board helps those that help themselves.

Last Edited: Sat. Jul 11, 2015 - 12:36 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

So this should work then?

 

ISR(ADC_vect)
{
  //read ADC

  //change MUX
}

ISR(TIMER1_COMPA_vect)
{}

ISR(TIMER1_COMPB_vect)
{}

 

void setup() {
  Serial.begin(115200);
  // Set ADC reference to AVCC
  ADMUX |= (1 << REFS0);

  // Enable ADC, Enable interrupt, set ADC prescalar to 128, and Auto trigger Enable
  ADCSRA |= (1 << ADEN) | (1 << ADIE) | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0) | (1 << ADATE);

  // Auto Trigger mode on Timer/Counter1 Compare Match B
  ADCSRB |= (1 << ADTS2) | (0 << ADTS1) | (1 << ADTS0);
  
  // Set Clock 1 Tap frequency to 16MHz/64 = 250kHz
  TCCR1B |= (0 << CS12) | (1 << CS11) | (1 << CS10);  //64 prescaler

  // Set timer 0 to Clear Timer on Compare Match mode
  TCCR1A |= (1 << WGM11) | (0 << WGM10);
  
  // Enable Time Interrupt on compare for Timer1 channel B
  TIMSK1 |= (1 << OCIE1B);
  TIMSK1 |= (1 << OCIE1A);
  
  // Set On Compare Value for Clock 0 register B (.25 seconds)
  OCR1A = 62499;
  OCR1B = 62499; //Target Timer Count = Input Frequency / (Prescale * Target Frequncy) - 1

  // Start A2D Conversions
  ADCSRA |= (1 << ADSC);

  lastTime = 0;
  newTime = 0;

  sei();
}

 

 

 

Note: the code I updated still seems to interrupt overly fast but aside from that the changing of the MUX in the interrupt still isn't working. I changed the code back to not updating the MUX at all and the measurement is getting a correct value when i leave it on A0

Last Edited: Sat. Jul 11, 2015 - 01:18 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

So this is why I was setting the ADSC bit even with the interrupt:

"If Auto Triggering is enabled, single conversions can be started by writing ADSC in ADCSRA to one. ADSC can also be used to determine if a conversion is in progress. The ADSC bit will be read as one during a conversion, independently of how the conversion was started."

 

Either way it's not working. Maybe it's because I'm not doing one of these cases to change the MUX:

1. When ADATE or ADEN is cleared.

2. During conversion, minimum one ADC clock cycle after the trigger event.

3. After a conversion, before the Interrupt Flag used as trigger source is cleared

 

~I tried adding 1 clock cycle to channel A and on that channel change the MUX to the next channel, but then the MUX value doesn't change but rather stay's on A0. 

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

well I found one major culprit:

 

I had: ADMUX &= (ADMUX & 0xF8) | temp.pin;

 

which should of been: ADMUX = (ADMUX & 0xF8) | temp.pin;

 

The only problem is that when one pin changes, it seems to affect the saved values for all the others and I believe it is because my timer interrupt is happening much to fast.

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

You are setting timer 1 to Mode 2 which is 9 bit PWM, but setting OCR1B to 62499, so the timer compare match never happens. You should use CTC mode (Mode 4).

 

Also, there is no need for most of the "|=" in setup(). And you do not want to enable the timer interrupts. The compare match will happen whether or not the interrupt is enabled.

Regards,
Steve A.

The Board helps those that help themselves.

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

And you do not want to enable the timer interrupts.

Well, kind of a trick statement.  True the timer interrupts are not necessary.  But if not used, the IF flag needs to be cleared manually for the next auto-trigger to fire.

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 so much for your help guys. I really appreciate it. However it still doesn't seem to do anything.

 

I removed the following: 

TIMSK1 |= (1 << OCIE1B) | (1 << OCIE1A);  // Enable Time Interrupt

 

Changed TCCR1n to:

TCCR1B |= (0 << WGM13)| (1 << WGM12);  // CTC mode 4

 

and inside my ISR(ADC_vect) I added:

TIFR1 |= (1<<OCF1B);  // Clear the on compare flag

 

yet It is still interrupting much much faster than what I am trying to do and I can't figure out why. Here is a tester file I am wrote on the Arduino IDE as a quick test. I should be seeing a time difference of around 250, but all I see is either a 1 or 0.

 

#include <avr/interrupt.h>

volatile long lastTime;
volatile long currTime;

ISR(ADC_vect)
{
  long diff = currTime - lastTime;
  Serial.println(int(diff), DEC);
  lastTime = currTime;
  TIFR1 |= (1<<OCF1B);  // Clear the on compare flag
}

void setup() {
  Serial.begin(115200);
  
  // Set ADC reference to AVCC
  ADMUX |= (1 << REFS0);
  
  // Set MUX to measure A7
  //ADMUX |=  (1 <<MUX2) | (1 << MUX1) | (1<<MUX0);

  // Enable ADC, Enable interrupt, set ADC prescalar to 128, and Auto trigger Enable
  ADCSRA |= (1 << ADEN) | (1 << ADIE) | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0) | (1 << ADATE);

  // Auto Trigger mode on Timer/Counter1 Compare Match B
  ADCSRB |= (1 << ADTS2) | (0 << ADTS1) | (1 << ADTS0);
  
  // Set Clock 1 Tap frequency to 16MHz/64 = 250kHz
  TCCR1B |= (0 << CS12) | (1 << CS11) | (1 << CS10);  //64 prescaler

  // Set timer 1 to Clear Timer on Compare Match mode
  //TCCR1A |= (0 << WGM11) | (0 << WGM10);
  TCCR1B |= (0 << WGM13)| (1 << WGM12);
  
  // Enable Time Interrupt on compare for Timer1 channel B and A
  //TIMSK1 |= (1 << OCIE1B) | (0 << OCIE1A);
  
  // Set On Compare Value for Clock 0 register B (.25 seconds)
  OCR1A = 62499;
  OCR1B = 62499; //Target Timer Count = Input Frequency / (Prescale * Target Frequncy) - 1

  lastTime = 0;
  currTime = 0;

  sei();
}

void loop() {
  currTime = millis();
}

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

Post the entire test program you are now using.  Tell the connections.  Tell what you expect to happen, and tell what >>is<< happening.  Tell how you are testing.

 

I generally do continuous conversions on all used ADC channels in a round-robin fashion.  That takes about 250us per channel.  Every 10ms or so, each channel has a recent conversion result that I keep in my per-channel "raw" array.  Then, I total a number of readings over a period of time (for slow-moving signals such as temperature it might be 50 readings in 500ms) and average them to get the values to close the application loop.

 

Thus, I don't worry about timer per se to trigger the ADC conversions.  But that would be up to you.

 

Note that thermistors (depending on the model and the connection to the AVR) are relatively high impedance.  For best results, a C to gnd next to the AVR pin is useful.  Perhaps coupled with a series R in front of that to help protect against the real world.

 

In addition, many AVR users will double-convert high impedance channels and use the second reading.

 

With a list of channels to convert, here is an ISR that carries out the above:

//
// **************************************************************************
// *
// *		A D C _ I S R
// *
// **************************************************************************
//
// ADC interrupt service routine
// with auto input scanning
interrupt [ADC_INT] void adc_isr(void)
{
// Read the AD conversion result
	adc_raw[ad_index] = ADCW;

// Select next ADC input
	if (++ad_index >= ad_count)
		{
		ad_index=0;
		}

	ADMUX = ADC_VREF_TYPE + ad_channel[ad_index];

// Start the AD conversion
	ADCSRA |= (1 << ADSC);

}

No &=~ or |=.  Other than the indexing, pretty much straight line coding.  Not rocket science.

 

 

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

Yes, I to am planning on doing a round robin style of ADC measuring on A0-A7. The 3k thermistor are hooked up on A0 - A7 as a volatge divider with a 3k resistor, where the pin will be the output reading. The measurement on the ADC seems to be working, so if I triggered the ADC interrupt on completion of the ADC measurement I believe it would work, but that would be a much quicker interrupt than what I am looking for. Which is what I was originally doing. I was than advised to try and trigger the ADC interrupt using a timer interrupt, the problem I am having now is that ADC interrupt is triggering much faster than what I am trying to accomplish of 250ms while using the timer interrupt. That is why I only posted the small Arduino test file because I believe I am missing something in the setup that I do not understand on how to get this interrupt feature to work properly.

 

 

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

You are doing a bunch of |= for timer setup.  Doesn't Arduino have timer1 already set up?  (for millis() perhaps?)

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

Oh that is interesting. For the timer stuff, I am currently editing only 3 registers (counting OCR1A and OCR1B each as one even though they are each two). 

 

I printed there default values and got:

 

TCCR1B is already set to 0x03, so that means it has a prescaler already set to 64

 

However both

OCR1A and OCR1B are set to 0 so I do not think they are being used by any of the libraries.

Last Edited: Mon. Jul 13, 2015 - 08:27 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Take a deep breath.

 

Read up on what Arduino uses time1 for, and default setup if any.  (I'm not an Arduino person.)

 

Examine your posted code--what is wrong with this picture?

 

// Set Clock 1 Tap frequency to 16MHz/64 = 250kHz
  TCCR1B |= (0 << CS12) | (1 << CS11) | (1 << CS10);  //64 prescaler   // Set timer 1 to Clear Timer on Compare Match mode
  //TCCR1A |= (0 << WGM11) | (0 << WGM10);
  TCCR1B |= (0 << WGM13)| (1 << WGM12);
  

Hint:  You modify B register twice.  One should probably be the A register.  Which probably already has an Arduino value.

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

Good eye, but unless I am wrong I think those might be the correct settings.

 

 I was originally setting TCCR1A because the datasheet list the modes of timer0 TCCR0A = 2 (pg. 128 of datasheet) would set the timer to CTC mode so I assumed this was the mode number for TCCR1A as well. Since TCCR1 is 16 bit though this didn't apply, I needed mode 4 to set TCCR1 set to mode CTC (pg. 145) as Koshchi pointed out. However in this case the bits WGM12 and WGM13 are actually on TCCR1B while WGM11 and WGM10 are on TCCR1A. I left TCCR1A there just in case I needed to edit it, but for now it's just commented.

 

As far as splitting TCCR1B into two, I did that just for simplicity of reading. One sets the prescaler the other sets the mode to CTC (I think). At least that was my intention

 

Also I googled the Arduino usage of Timers and it looks like timer1 is used by the Servo library, which I am luckily not going to be using. 

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

Yay, I think I got it. Thanks for pointing that out. I relooked at the table I mentioned on pg. 145 and noticed there are two different CTC modes. One is 4 and the other is 12. I noticed the only difference as that the top for mode 4 says OCRnA (whatever top means). While the top for mode 12 says  ICRn, so I thought I'd try it and it seems to work.

 

 

EDIT: I take back everything I said. It seems to work on the test, yes. I don't see why though. I am not trying to use the ICR register I want to use the OCRnA and OCRnB registers.... super confused

Last Edited: Mon. Jul 13, 2015 - 09:37 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Are you still doing |= ?  What about Arduino use of timer1? 

 

I guess I'm out.

 

 

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

Yeah I still am using the |=

 

TCCR1B |= (0 << CS12) | (1 << CS11) | (1 << CS10);  //64 prescaler

TCCR1B |= (1 << WGM13)| (1 << WGM12);  // CTC mode

 

The only thing I changed is the 1 infront of WGM13 instead of a 0. This shouldn't work from what I'm reading in the datasheet, but it does. Almost like 12 and 4 modes were flipped or I just got extremely lucky and for some reason it works. This is also working when I add it to my project compiling in AtmelStudio6.2

 

As for what arduino uses it for, it uses it in the servo library, but I am not including that library so it shouldn't effect what I am doing.

 

If anyone know why this seems to be working I would be extremely curios to know.

Last Edited: Mon. Jul 13, 2015 - 10:09 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

How bout this: Lets assume you need to read 5 a/ds. Lets assume your program runs every 20ms. Lets assume you have a function called readadchan that takes a chan and returns an int. So every pass of the program inc the chan, read that channel, store it in an array or something. You will be getting a reading every 20ms, all readings every 100ms. Fast enough? Notice it doesnt use any timers or interrupts. Cant possibly mess up.

Imagecraft compiler user

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

Yeah I still am using the |=

Why? There's a time to use it and a time not to. Don't think that just because it lets you make changes to a register in which some bits are already programmed that you should ALWAYS use it. For example in the lines:

TCCR1B |= (0 << CS12) | (1 << CS11) | (1 << CS10);  //64 prescaler
TCCR1B |= (1 << WGM13)| (1 << WGM12);  // CTC mode

why use |= on the first of those two? This would be better:

TCCR1B = (0 << CS12) | (1 << CS11) | (1 << CS10);  //64 prescaler
TCCR1B |= (1 << WGM13)| (1 << WGM12);  // CTC mode

but why split this anyway? Are you programming on some device that only allows 40 characters width or something? Modern editors/screens should be able to fit 100+ characters on a line without wrap. So:

TCCR1B = (0 << CS12) | (1 << CS11) | (1 << CS10) | (1 << WGM13)| (1 << WGM12);  //64 prescaler, CTC mode

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

As I said, I am aware that it can be done in one line. As each is setting a different feature, and I still wasn't getting all the features to work correctly. I split them up to make it easier to comment lines or find any other bits needed to add/remove. It's a matter of preference, don't see why that even matters as in the end they should result in setting the register to the same value. Also your saying why use  '|=', but why not use '|='? I don't see the issue. Is an OR statement slower than using an = statement?

This reply has been marked as the solution. 
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

You just aren't getting the point...

 

Let's suppose Arduino already has the timer set up, and TCCR1B already has a value of 0xAA or 0xFF or 0x33 or whatever.  (You already said that TCCR1A had a value, so one can only assume that TCCR1B also, right?)

 

So doing the |= doesn't do what you think it does, right?  Benign at best, confusing at the least -- aren't you struggling with timer setup?  And couldn't this well be the cause?

 

Is an OR statement slower than using an = statement?

Of course it is.  Now, a few cycles or a few words aren't important in a virtually null application. In the real world, every one adds a couple words and a few cycles to the app.  perhaps more important, it opens the window to possible RMW (Read Modify Write) implications. 

 

Whether you want to split the operations for your clarity at the expense of words/cycles/RMW is up to you.  But when struggling with timer setup in the

Arduino environment, and the timer may well already be set up, I cannot see why you are adamant not to re-setup the timer from scratch especially the TCC registers.

 

While I did indeed say https://www.avrfreaks.net/comment...

we got full-reel quantities of both left and right parenthesis from the distributor when they were on sale.  Buying them in quantity like that makes their use virtually free.

I've also said in https://www.avrfreaks.net/comment... and elsewhere

For some reason, many posters on this site have a love affair with |=. There must be a sale on "|" in full-reel quantities.

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.

Last Edited: Tue. Jul 14, 2015 - 05:54 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I see, I wasn't being adamant. I thought your point was that you didn't like the look of it but results in the same value. Not that there could be different values being set. I'll try it and let you know how it works out in a few hours. Thanks for the help.

Last Edited: Tue. Jul 14, 2015 - 06:44 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I think the suggestion in msg 24 solves the problem without interrupts or timers. Worth a try?

Imagecraft compiler user

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

Thanks bobgardner, that is deffinetly a solution. I wanted the interrupts though because the process doesn't need happen very fast only around 1/4 a second or so. I was deffinetly considering it though, but I'm trying to keep my loop as fast as possible while avoiding unnecessary counters in my loop as much as possible because I will be running a pretty decent sized state machine inside the loop.

 

So theusch was 100% on the right track on what was happening. The problem register wasn't TCCR1B it was TCCR1A. I finally got around to learning how to use the simulator to debug the code. Thanks for all the help everybody. The problem statement was using "init();" from "Arduino.h". I was using the file early on the print millis() while debugging (didn't know how to use the debugger) but deleted all of those statements except the init(). It ended up putting a 0x01 into TCCR1A. So when I set TCCR1B to CTC mode to 4 it was actually putting it into mode 5 which is another PWM mode.

 

Thanks theusch, bobgardner, and clawson. You guys are awesome

Last Edited: Thu. Jul 16, 2015 - 06:46 PM