[SOLVED] Digital Output and external interrupt on the same pin

Hi, I need to implement a communication protocol similar to the one used by the Ping))) ultrasonic sensor, where there is only one signal line. The sensor waits for a trigger impulse and after a short hold off period replay on the same signal line with an impulse whose width is proportional to the distance measured. I want to do the same on an Arduino UNO. I would like to practice interrupts but I can't get it to work.

Here is the question: can I use pin 2 both for calling the external interrupt and for transmitting a digital HIGH output? Is the external interrupt disabled when the ISR routine is called? Why my code doesn't work?

int pin = 2;
int pulse_duration = 1000;

void setup() {
  Serial.begin(9600);
  attachInterrupt(digitalPinToInterrupt(pin),send_impulse, RISING);
  pinMode(pin, OUTPUT);
  
  Serial.println("Ready...");

}

void loop() {
}

void send_impulse(){
    delayMicroseconds(750);
    digitalWrite(pin, HIGH);
    delayMicroseconds(pulse_duration);
    digitalWrite(pin,LOW);
    delayMicroseconds(200);
    
}

Hello,

I have no experience doing that, but as I understand it, you just need to change the pinMode when needed.

JMoro86:
can I use pin 2 both for calling the external interrupt and for transmitting a digital HIGH output? Is the external interrupt disabled when the ISR routine is called? Why my code doesn't work?

Yes.

All interrupts are disabled while a isr is running.

You do not create a rising edgde on pin 2 (only the isr manipulates the pin state).

I made this example

const byte irqPin = 2;

void toggleLED() {
  digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
}

void setup() {
  Serial.begin(115200);
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(irqPin, OUTPUT);
  attachInterrupt(digitalPinToInterrupt(irqPin), toggleLED, FALLING);
}

void loop() {
  static unsigned long lastStop;
  if (millis() - lastStop > 250) {
    lastStop = millis();
    digitalWrite(irqPin, !digitalRead(irqPin));
  }
}

Whandall:
Yes.

All interrupts are disabled while a isr is running.

You do not create a rising edgde on pin 2 (only the isr manipulates the pin state).

Sorry I forgot to mention that I attached to pin 2 another hardware (let's say another arduino) which transmit the trigger impulse, activating the external interrupt

Not a good idea to connect two outputs...

Whandall:
I made this example

const byte irqPin = 2;

void toggleLED() {
 digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
}

void setup() {
 Serial.begin(115200);
 pinMode(LED_BUILTIN, OUTPUT);
 pinMode(irqPin, OUTPUT);
 attachInterrupt(digitalPinToInterrupt(irqPin), toggleLED, FALLING);
}

void loop() {
 static unsigned long lastStop;
 if (millis() - lastStop > 250) {
   lastStop = millis();
   digitalWrite(irqPin, !digitalRead(irqPin));
 }
}

I don't really get your example... Aren't you toggling a pin, LED_BUILTIN (which is not pin 2) ? Is lastStop even initialized before being used in the if statement?
Thanks for your patience.

The led is toggled by the isr, the interrupt pin by the loop.

Initial value of lastStop is 0. (standard for not explicit initialized storage)

Whandall:
Not a good idea to connect two outputs...

I know but it is not my choice... I have to deal with it. Is there a solution? As I mentioned the specifics must be the way I described.
Thanks again for your help.

p.s. the output that triggers the external interrupt of course changes its state to input soon after to receive the variable length impulse.

If you can make the responding side to drive the output only for the measurement
(and read or tristate else), you could switch the pin direction between trigger and measurement.

Why my code doesn't work?

There is another piece of code which sends the trigger signal and then reads the pulse coming back. You will need to post that in order for us to help with troubleshooting.

How do you know the coded you posted (which sends a pulse when receiving an input) does not work?

When I drive the posted sketch as written with this one on another Arduino, and connect pin2 to pin2, I see an expected pulse of 1000 microseconds come back every 5 seconds.

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

void loop() 
{
 pinMode(2,OUTPUT);
 digitalWrite(2,HIGH);
 delayMicroseconds(20);
 digitalWrite(2,LOW);
 pinMode(2,INPUT);
 Serial.println(pulseIn(2,HIGH));
 delay(5000);
}

cattledog:
There is another piece of code which sends the trigger signal and then reads the pulse coming back. You will need to post that in order for us to help with troubleshooting.

How do you know the coded you posted (which sends a pulse when receiving an input) does not work?

When I drive the posted sketch as written with this one on another Arduino, and connect pin2 to pin2, I see an expected pulse of 1000 microseconds come back every 5 seconds.

void setup()

{
Serial.begin(9600);
}

void loop()
{
pinMode(2,OUTPUT);
digitalWrite(2,HIGH);
delayMicroseconds(20);
digitalWrite(2,LOW);
pinMode(2,INPUT);
Serial.println(pulseIn(2,HIGH));
delay(5000);
}

Hi, the other piece of code is written with Labview and make use of a NI USB-6221 DAQ and has been tested with an oscilloscope, but it does pretty much the same function you wrote. I've got updates, not good by the way. First of all the pin must be set as INPUT in the setup() function, if not the trigger signal from the DAQ is distorted to 1 V and doesn't trigger the interrupt (of course it goes all in the arduino given the fact that there is almost no input resistance, no damage to the Arduino fortunately).

When the interrupt is triggered I toggle pinMode to OUTPUT, generate the output impulse, toggle back to INPUT before exiting the ISR. This sequence doesn't produce the expected results by the way. It gives a train of identical impulses (of the right width) and so I guess that the first response impulse triggers the interrupt a second time and so on. My guess is interrupts are not disabled during the ISR.

Then I played with the noInterrupt() and interrupts() functions which I placed inside the ISR but still I had a strange behaviour: this sequence produce two identical impulses of the right width, it seems like the ISR is called twice...

I can't figure out a predictable behaviour and I ask you if it could depend from my Arduino UNO which is rev 2, so a bit outdated...

I'm a bit confused now... Thank you all.

This is the latest code i wrote. Notice that moving the noInterrupts() and interrupts() function gives totally unpredictable behaviours...

int pin = 2;
int pulse_duration = 500;

void setup() {
  Serial.begin(9600);
  attachInterrupt(digitalPinToInterrupt(pin),send_impulse, RISING);
  pinMode(pin, INPUT);
  
  Serial.println("Ready...");

}

void loop() {
}

void send_impulse(){
    delayMicroseconds(200);
    noInterrupts();
    pinMode(pin, OUTPUT);
    digitalWrite(pin, HIGH);
    interrupts();
    delayMicroseconds(pulse_duration);
    digitalWrite(pin,LOW);
    pinMode(pin, INPUT);
    delayMicroseconds(200);
    Serial.println("Ciao interrupt!");
    
    
}

Do not use delay in an isr.
Do not use Serial in an isr.
Do not use interrupts or noInterrupts in an isr unless you really know what you are doing.

The interrupts are also fairly dumb. They can't tell the difference between an external input setting a pin high and you setting the pin high as an output. (This is sometimes useful if you need to test your own interrupts or you need an interrupt to be synchronised to a PWM output.)

You need to keep another variable to record if it's an input or an output and check that during the interrupt to decide if it's a "real" external interrupt.

MorganS:
The interrupts are also fairly dumb. They can't tell the difference between an external input setting a pin high and you setting the pin high as an output. (This is sometimes useful if you need to test your own interrupts or you need an interrupt to be synchronised to a PWM output.)

You need to keep another variable to record if it's an input or an output and check that during the interrupt to decide if it's a "real" external interrupt.

So do you imply that interrupts are not disabled when the ISR is running? To my experience I would say that the ISR routine is not as a matter of fact interrupted, but the trigger (if any, in my case the ISR itself produce another trigger) is stored and subsequently repeatedly executed. Am I totally wrong?

So do you imply that interrupts are not disabled when the ISR is running? To my experience I would say that the ISR routine is not as a matter of fact interrupted, but the trigger (if any, in my case the ISR itself produce another trigger) is stored and subsequently repeatedly executed. Am I totally wrong?

Response to interrupts is disabled within an ISR. During the execution of that ISR, new interrupt event flags are queued up, and executed in a prioritized order after exit from the ISR.

See Nick Gammons interrupt tutorial.

cattledog:
Response to interrupts is disabled within an ISR. During the execution of that ISR, new interrupt event flags are queued up, and executed in a prioritized order after exit from the ISR.

See Nick Gammons interrupt tutorial.

Thanks a lot! Now it's clear.

For those who care(d) I finally succeeded in what I was trying to do. It may be poor coding but I feel this thread clears a bit of confusion around interrupts and most of all the project works as expected. A big thank you to cattledog who pointed me towards the right direction.

The problem was that the interrupt flag needed to be cleared before exiting the ISR routine otherwise the signal generated triggers another interrupt which executes repeatedly.

To clear the interrupt 0 (INT0, the one mapped to pin 2) flag it is needed just the following line of code:

EIFR = 0x01;

This is the complete code I used:

int pin = 2;

void setup() {
  Serial.begin(9600);
  attachInterrupt(digitalPinToInterrupt(pin),send_impulse, RISING);
  pinMode(pin, INPUT);
  
  Serial.println("Ready...");

}

void loop() {
}

void send_impulse(){
    int tmp = analogRead(0);
    double val = 16383.0*tmp/1023; //Response impulse width
    int pulse_duration = (int) val;

    //Response impulse generation
    delayMicroseconds(755);
    pinMode(pin, OUTPUT);
    digitalWrite(pin, HIGH);
    delayMicroseconds(pulse_duration);
    digitalWrite(pin,LOW);
    delayMicroseconds(200);
    pinMode(pin, INPUT);

    EIFR = 0x01; //Clear INT0 flag
}

Cheers

Neither of the delay() functions will work reliably inside an interrupt. They need interrupts enabled to increment the counters that sit behind them. You have been lucky that you didn't hit one of those increment periods in your testing. analogRead() can also be problematic inside an interrupt, if you are using analogRead() anywhere else in your program.

Have the interrupt set a variable like NeedToOutput and record the time. The main loop can see this and do the analog reading and switch the output pin as appropriate. The interrupt should test to check that an output is in progress so that it doesn't mess with an output underway.