Interrupt clobbering int

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

I am using two interrupts, one to read 4 of the ADC mux pins, one to send this data out using I2C. The data is stored in an int[4] array. The main loop can read this data fine, but no matter how I do it, the I2C interrupt receives strange values for the array. I have a printf that runs when the I2C interrupt is called, and it shows values close, but not the same as the actual ADC counts, as though a bit is flipping. If I understand right, interrupts are disabled while in an interrupt, so I don't think it is a concurrency problem (it would affect the main loop anyway). I tried declaring the array as "volatile" but that did not help. The array is indexed properly, and correct data appears in a printf inside the main loop.

What gives? Does accessing arrays for reading change in interrupts? Is it because this is a 16-bit array?

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

It's better for us to understand if you show your code too so we can help each other better.

KISS - Keep It Simple Stupid!

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

Accessing a 16-bit variable outside of an ISR isn't done atomically by default, so an interrupt can occur in between accesses to the individual bytes of the word. Could this be what's happening? The solution is to disable interrupts while accessing the int, possibly just long enough to make a copy of it, if time is of the essence. Avr-libc 1.6.1 has macros for defining atomic blocks as described here.

Aaron T.

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

But that's what is so weird- accessing the variable in the main loop (interrupts on) is fine, accessing it in the SPI interrupt (interrupts off, because I am in one) is not. There is nothing abnormal about the SPI int access, just a data = array

;

The only time the variable is written is during the ADC interrupt, again with interrupts off.

And yes, I tried volatile already.

Basically, the "sending" printf will show some weird numbers, but the main loop (not here to save space) can read them fine. Another AVR will, of course, show the same weird data on I2C.

uint8_t TWImode;
int IRS[4];
int curData;

ISR(TWI_vect)
{
	switch(TWSR)
	{
		case TW_ST_SLA_ACK:  //own address + read has been recieved, now in slave transmit mode
		if(TWImode < 4)
		{
			curData= IRS[TWImode];
			printf("TWImode= %d, Sending %d\n", TWImode, curData);
			TWI_send_data(curData, 0);  //send IR value indexed by command
			break;
		}
		TWI_send_data(42,1);  //send one byte
		break;
		
		case TW_SR_SLA_ACK:  //own address + write, master sending command- so read it
		TWI_read_data(1);
		break;
		
		case TW_SR_DATA_NACK:  //the command was read from the master, NACK sent
		TWImode = TWDR;  //save the command for later 
		TWI_slave_wait();  //wait for next transmission
		break;
		
		case TW_ST_DATA_ACK:  //first byte of data sent, ACK recieved
		if(TWImode < 4)
			TWI_send_data(curData >> 8, 1);
		break;
		
		case TW_ST_DATA_NACK:  //data was sent, NACK recieved
		TWI_slave_wait();
		break;
		
		default:
		printf("Unhandled TWI status: %d", TWSR);
		TWI_slave_wait();
	}
}

//ADC interrupt, for when a conversion is finished - read value from IR sensor 0-3
uint8_t curADC;
ISR(ADC_vect)
{
	//printf("ADC int, curADC=%d\n",curADC);
	IRS[curADC] = ADCL;
	IRS[curADC] |= ADCH << 8;
	// Get next IR
	curADC++;
	if(curADC > 3)
		curADC = 0;
	ADMUX = curADC;
	ADMUX |= (1<<REFS0);
	//printf("conv complete - ADCL = %d\n", ADCL);
}
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I played with it in the AVR studio sim, it looks like GCC optimizations may be doing insane things with the 16 bit data access.

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

I'm not sure which processor you are using, but the newer AVRs allow you to access the ADC value as a 16-bit value:

//ADC interrupt, for when a conversion is finished - read value from IR sensor 0-3
uint8_t curADC;
ISR(ADC_vect)
{
   //printf("ADC int, curADC=%d\n",curADC);
   IRS[curADC] = ADC;
   // Get next IR
   curADC = (( curAdc + 1 ) & 0x03);
   ADMUX = curADC | (1<<REFS0);
   //printf("conv complete - ADCL = %d\n", ADCL);
}

Also, if you are using the ADC in free-run" mode, the sample for the next conversion may take place before your ADMUX switch. It would be better to either run the ADC from a timer where you know the conversion rate is slower than the interrupt latency, or to fire off the next ADC conversion in the ISR.

I did a slightly different curADC rollover technique. Probably won't help, but depending on how smart the gcc optimizer is, it may be faster. Or not. YMMV.

Don't know if any of this helps, but it's the first thing I noticed.

Stu

Engineering seems to boil down to: Cheap. Fast. Good. Choose two. Sometimes choose only one.

Newbie? Be sure to read the thread Newbie? Start here!

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

That ADC ISR seems to work fine. It is in free run, and I get 4 samples just fine in the main loop.

I realized I was using the sim wrong, the code works in sim with -O3 on. Of course, I can't simulate interrupts or real ADC operation. Interrupts are off for the duration of the TWI ISR, of course, so nothing can effect IRS[] while it is being accessed.

You optimization is correct, GCC does not change the if statement. It appears as a compare and branch.

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

Nathan2 wrote:
... Of course, I can't simulate interrupts or real ADC operation.

You can manual force an interrupt by setting the interrupt flag when using the simulator.

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

Yup, I just figured that out. Of course my code works 100% in the sim :D. It jumps to the TWI ISR, and curData is set properly to whatever IRS[0] is.

I'm going to try what stu_san suggested and slow down the ADC a bit with manual trigger, and play around with the code in the lab tomorrow. I'm sure this is all the result of some dumb little problem I missed somewhere.

This forum is really helpful, btw. I don't think I'll ever use PICs again. Thank you all!

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

stu_san wrote:
Also, if you are using the ADC in free-run" mode, the sample for the next conversion may take place before your ADMUX switch. It would be better to either run the ADC from a timer where you know the conversion rate is slower than the interrupt latency, or to fire off the next ADC conversion in the ISR.

That was the problem. I finally realized my IRS[] array was off by one with respect to the pins, because the ADMUX switch affected the next conversion in free running mode. Thanks!