attachInterrupt issue - function seems to be called twice

Status
Not open for further replies.

adeptrc

Active member
I've been playing around with some analog devices ADCs (SPI parts). One that I'm fairly familiar with is the AD7794. However, I've never used it in continuous read mode. (Teensy 3.0, Teensyduino 1.18)

In continuous read, the part uses its DOUT to indicate conversion complete and ready to read by pulling it low. You then clock in 24 bits of 0 to read the 24 bit data register, and DOUT is pulled high as soon as the last bit is clocked out. I'm just using the SPI.h library for this.

The problem I'm having is that the interrupt routine runs twice, resulting in valid data the first time, and 0xFFFFFF the second, since it's just reading the DOUT line pulled high. I'm new to the world of Arduino/Teensy, so I'm not sure if I'm misunderstanding how attachInterrupt works, and how it masks the interrupt its processing. Anyway, here is what I see on the scope: DOUT is pulled low, three bytes of 0 are clocked in, all good, but then it clocks in another 3 bytes of 0's for no apparent reason (DOUT is high). I've tried putting a delay inside the interrupt function in case it was catching the last 0 on the data line, but it makes no difference.

What am I missing?

DS1Z_QucikPrint1.png

Code:
#include <SPI.h>

volatile long adcValue;  // AD7794 continuous read value 

// attachInterrupt function for AD7794 continuous read

void readADC() {
   adcValue = SPI.transfer(0x00) << 16 | SPI.transfer(0x00) << 8 | SPI.transfer(0x00); // clock in 24 0's to read AD7794 data register
}

void setup() {
  
  Serial.begin(9600);

  // setup _CS_ AD7794
  pinMode(6, OUTPUT); digitalWrite(6, HIGH);
  
  // setup interrupt pin (wired to pin 12 - was originally attaching interrupt to pin 12, but moved in case of SPI library conflict - but no difference)
  pinMode(9, INPUT);

  SPI.begin();
  delay(3000);
  
  digitalWrite(6, LOW); // select AD7794
  
  for (int i = 0; i < 8; i++) { // reset AD7794
    SPI.transfer(0xFF);
  }
  delay(1); // settle time only 10uS actually needed
  
// *** this is just set and readback code for the configuration registers on the AD7794 while I'm testing the part ***

  Serial.println(); Serial.println("...............<<<<>>>>................."); Serial.println();
  
// **** STATUS REGISTER ****

  SPI.transfer(0b01000000); // set to read the Status register - 8 bits
  long response = SPI.transfer(0x80);
  Serial.print("Status Register: "); Serial.println(response);
  delay(1);
  
// ***** CONFIGURATION REGISTER ****

  SPI.transfer(0b01010000); // set to read the register - 16 bits
  response = SPI.transfer(0x80) << 8 | SPI.transfer(0x80);
  Serial.print("Configuration Register: "); Serial.println(response);
  
  SPI.transfer(0b00010000); // set to write the register - 16 bits
  SPI.transfer(0b00010000); SPI.transfer(0b00100000);
  delay(1);
  
  SPI.transfer(0b01010000); // set to read back the configuration - 16 bits
  response = SPI.transfer(0x80) << 8 | SPI.transfer(0x80);
  Serial.print("Configuration Register: "); Serial.println(response);
  delay(1);
  
// ***** MODE REGISTER ****  

  SPI.transfer(0b01001000);  // set to read the mode register - 16 bits
  response = SPI.transfer(0x80) << 8 | SPI.transfer(0x80);
  Serial.print("Mode Register: "); Serial.println(response);
  delay(1);
  
// Internal Calibration: zero scale
  SPI.transfer(0b00001000); //select mode register for write
  SPI.transfer(0b10000000); SPI.transfer(0b00001000); //write configuration bits to calibrate
  delay(1); //wait a moment
  SPI.transfer(0b01001000); //select mode register for read
  response = SPI.transfer(0x80) << 8 | SPI.transfer(0x80); //read new configuration
  Serial.print("Mode Register: "); Serial.println(response);
  delay(1);
  
// Internal Calibration: full scale
  SPI.transfer(0b00001000); //select mode register for write
  SPI.transfer(0b10100000); SPI.transfer(0b00001000); //write configuration bits
  delay(1); //wait a moment
  SPI.transfer(0b01001000); //select mode register for read
  response = SPI.transfer(0x80) << 8 | SPI.transfer(0x80); //read new configuration
  Serial.print("Mode Register: "); Serial.println(response);
  delay(1);
  
// Return to continuous conversion mode
  SPI.transfer(0b00001000); //select mode register for write
  SPI.transfer(0b00000000); SPI.transfer(0b00001000); //write configuration bits
  delay(1); //wait a moment
  SPI.transfer(0b01001000); //select mode register for read
  response = SPI.transfer(0x80) << 8 | SPI.transfer(0x80); //read new configuration
  Serial.print("Mode Register: "); Serial.println(response);
  delay(500);


// enable interrupt on pin 6 and initiate continuous read
  
  attachInterrupt(9, readADC, LOW);
  SPI.transfer(0x5C);
  
}


void loop() {

}
 
Last edited:
Depending on the interrupt setting, you will get an interrupt on just the change of the state.
Since it is getting hit more than one time, you are interrupting more than once.
One thing also to remember is that even if you just write, data is always read, even if it is unused.
One thing you could do is set a volatile variable to indicate when you are actually expecting a valid value, and ignore the bogus event if you are not expecting it.
Another way to do this is that once your transfer is completed, you need to clear the interrupt, because each transition is actually causing an interrupt.
And yet another way is to disable the IRQ while reading, and once done, re-enable it.


The last suggestion is easiest. In the ISR, detach the ISR, do your read, then attach it back.
 
Last edited:
Try interrupting on the falling edge instead of on the LOW level:
Code:
  attachInterrupt(9, readADC, FALLING);

Pete
 
I thought the interrupt was automatically masked by attachInterrupt . . . it consistently only triggers one extra reading per DOUT going low outside the function. I've used both LOW and FALLING as triggers.

Is my assumption that the interrupt gets masked until the function is complete for attachInterrupt on the Teensy 3.0 incorrect?

Thanks . . .
 
Yes, IRQ is masked, but the IRQ still happens while you are in the ISR.... once the ISR exits, it re-hits because it hit low *during the data transfer*.

Again...in the ISR, detach, read, attach.

One other helpful tip...
Outside of the ISR be sure to cli(), copy the value to a different variable, sei() to avoid the possibility of a race condition, where the value changes as you read it.
 
Yes, IRQ is masked, but the IRQ still happens while you are in the ISR.... once the ISR exits, it re-hits because it hit low *during the data transfer*.

Again...in the ISR, detach, read, attach.

One other helpful tip...
Outside of the ISR be sure to cli(), copy the value to a different variable, sei() to avoid the possibility of a race condition, where the value changes as you read it.

Ok, got it . . . I had assumed attachInterrupt disabled the IRQ for that pin while processing the ISR automatically . . . I was confused by the fact I was only getting a single extra read (one of the reasons I tested FALLING was to see if I got multiple repeats - but perhaps the uC is configured to only allow one IRQ per ISR in progress to be queued?
 
Ok, got it . . . I had assumed attachInterrupt disabled the IRQ for that pin while processing the ISR automatically . . . I was confused by the fact I was only getting a single extra read (one of the reasons I tested FALLING was to see if I got multiple repeats - but perhaps the uC is configured to only allow one IRQ per ISR in progress to be queued?
It isn't actually queued, but that is a good analogy.
What happens is that the 'ISR happened' bit is cleared BEFORE your routine is called, so that other ISR may be caught while you are doing something, so that those IRQ are not lost.
By detaching the actual ISR doing the work will ignore the pending IRQ, and clear it if it is pending when you re-attach it... Or it should if those routines are properly written.
Try my suggestion, and post if this fixed the problem or if it did not.
 
Sorry, the "Ok, got it" was the all clear, it works fine. I'll have to crack the NVIC section of the manual to understand the details better. The detach/attach process seems to add a reasonable amount of overhead - not relevant to me in this case - it's around 0.5uS - but there must be a more efficient way to disable and enable interrupt detection on a pin entering and exiting an ISR.

Thanks.
 
Status
Not open for further replies.
Back
Top