ESP8266 switching IO-pin in ISR gets switched back to low after 6 microseconds

Hi ESP8266-specialists

currently I’m examining if an Interrupt-service-routine (isr) on a ESP8266 works as expected.
The interrupt is initiated by a rising edge on an IO-pin.

The isr has the attribut IRAM_ATTR so the compiler does store the isr in RAM which gets faster executed than from flash-memory

For checking if the isr is working I invert the level of an second IO-pin (GPIO14) each time the isr gets called.
The isr gets called with a frequency of 25 Hz.

If I use a short testprogram that counts up a variable and I read-out this variable once every second and reset the variable back to zero I can measure the frequency correctly.

I’m analysing it with a two channel 100 MHz digital storage-oscilloscope
Channel 1 shows the signal that initiates the interrupts
Channel 2 shows the voltage-level of the second IO-Pin (GPIO14)

The strange thing is the voltage is switched back to LOW after 6 microseconds instead of toggling the IO-pin with 12,5 Hz.

I tried some variations.
First set IO-pin to HIGH then the state gets switched back to LOW after 6 microseconds
First set IO-pin to LOW then the state gets switched back to HIGH after 6 microseconds

is there anything different if an IO-pin is switched by an isr?
What could cause the switchback after 6 microseconds?

best regards Stefan

Your test pulse isn't 6 microseconds long by any chance?

is there anything different if an IO-pin is switched by an isr?

Not as far as i know, but show us the sketch so we can have a look.

Hi arrg, and Deva,

the input-signal which triggers the interrupt is a 50% duty-cycle with 20-30 Hz.

the complete project is a ESP8266 nodeMCU-board working as a control-unit for a ventilator where the rpm can be controlled by a PWM-signal. The ventilator has a tachometer-output one pulse per revolution.

The complete project includes send/receive with ESP-Now and a 128x32 I2C-OLED-display.

I have stripped down the code to the bare minimum ISR and a loop that does report the measured rpm to the serial port with some other values.

In this Version it works as expected. toggling the test-out-put-pin follows the inputsignal.

As the rpm is pretty low I coded two statemachines one inside the isr and one inside loop()
to make rpm-measurement work this way:

the statemachine inside loop() initiates measuring rpm through setting the isr_state to ISR_Start_Counting

so isr starts measuring with a snapshot of micros()
and continues to count up pulses until loop-statemachine sets a flag StopCounting to initiate finishing counting

the isr will do a last count and store a second snapshot of micros()
and then signaling counting has finished

Doing it this way I get a more exact time-difference how long it took to count the pulses.

There seems to be another bug in it.
In some cases it reports double the frequency. I guess this is through "bad timing" between loop() and isr

I'm not fixed to this solution. If anybody has a different solution that can measure low frequencies as 10 to 30 Hz with high precisision I'm opened to it. Maybe set/reset any of the flags just need to be moved "a little bit" but I don't know.

here is the testcode

unsigned long RPM_Timer;

const int PWM_PIN             = 5; //D1
const int RPM_pulses_InputPin = 12; //D6
const int TestPin             = 14; //D5


const byte RPM_Start_Counting    = 1;
const byte RPM_Wait_for_finished = 2;
const byte RPM_finished          = 3;

volatile byte RPM_Timer_States = RPM_Start_Counting;


const byte ISR_Wait_for_Start    = 1;
const byte ISR_Start_Counting    = 2;
const byte ISR_Count             = 3;
const byte ISR_Counting_finished = 4;

volatile byte ISR_Count_States = ISR_Wait_for_Start;

volatile int IsrCount;
         int MainCount;     
volatile unsigned long StartTime;
volatile unsigned long EndTime;

volatile boolean Finished     = true;
volatile boolean StopCounting = false;

volatile unsigned long EnterISR;
volatile unsigned long LeaveISR;
volatile unsigned long ISR_ExecutionTime;


float factor_per_minute;

int TimerNr = 0;


void IRAM_ATTR isr_Count_TimeStamps() { //xxy
  EnterISR = micros();
  noInterrupts();
  
  digitalWrite(TestPin,!digitalRead(TestPin));
  
  switch (ISR_Count_States) {
    
    case ISR_Wait_for_Start:
      //IsrCount = 0;
      break;

    case ISR_Start_Counting:
      StartTime = micros();
      IsrCount = 1;
      ISR_Count_States = ISR_Count;
      break;

    case ISR_Count:
      IsrCount++;
      if (StopCounting) {
        ISR_Count_States = ISR_Counting_finished;
      }
      break;

    case ISR_Counting_finished:
      EndTime = micros();     
      //IsrCount++;
      Finished     = true;
      StopCounting = false;
      ISR_Count_States = ISR_Wait_for_Start;
      break;
  }
  LeaveISR = micros();
  ISR_ExecutionTime = LeaveISR - EnterISR;
}


boolean TimePeriodIsOver (unsigned long &expireTime, unsigned long TimePeriod) {
  unsigned long currentMillis  = millis();
  if ( currentMillis - expireTime >= TimePeriod )
  {
    expireTime = currentMillis; // set new expireTime
    return true;                // more time than TimePeriod) has elapsed since last time if-condition was true
  } 
  else return false;            // not expired
}


void setup() {
  Serial.begin(74880); // reset-message is sent in 74880 baud
  Serial.println();
  Serial.println("setup() start"); 

  pinMode(TestPin, OUTPUT);
  digitalWrite(TestPin,LOW);

  //pinMode(RPM_pulses_InputPin, INPUT_PULLUP);
  pinMode(RPM_pulses_InputPin, INPUT);
  attachInterrupt(RPM_pulses_InputPin, isr_Count_TimeStamps, FALLING);
  pinMode(PWM_PIN,OUTPUT);

}

void loop() {

  if (TimePeriodIsOver(RPM_Timer,1000) ) {

    // remember this switch-case is only called once 
    // every period of time set in the last paremater of 
    // the if-condition
    switch (RPM_Timer_States) { //xxy  
  
      case RPM_Start_Counting:
        if (ISR_Count_States == ISR_Wait_for_Start) {
          RPM_Timer_States = RPM_Wait_for_finished;
          ISR_Count_States = ISR_Start_Counting;   
          break;
        }
  
      case RPM_Wait_for_finished:
        StopCounting = true;
        
        if (Finished) {
          StopCounting = false;
          factor_per_minute = IsrCount * 1000000;
          Serial.print(" factor_per_minute = IsrCount * 1000000;:");
          Serial.print(factor_per_minute);
          
          factor_per_minute = factor_per_minute / (EndTime - StartTime) ; 
          Serial.print(" EndTime - StartTime:");
          Serial.print(EndTime - StartTime);
          Serial.print("  factor_per_minute:");
          Serial.println(factor_per_minute);
          RPM_Timer_States = RPM_finished;
          break;
        }
  
      case RPM_finished:
        Finished = false;        
        RPM_Timer_States = RPM_Start_Counting;
        ISR_Count_States = ISR_Wait_for_Start;
        break;
    }

    Serial.print("TimerNr:");
    Serial.print(TimerNr++);

    Serial.print(" ISR_ExecutionTime:");
    Serial.print(ISR_ExecutionTime);
    
    Serial.print("  RPM_Timer_States:");
    Serial.print(RPM_Timer_States);
    Serial.print("  ISR ISR_Count_States:");
    Serial.print(ISR_Count_States);
    
    if (Finished) {
      Serial.print(" Finished TRUE");
    }
    else  {
      Serial.print(" Finished false");
    }
    
    Serial.print(" IsrCount:");
    Serial.print(IsrCount);
    Serial.println();
  }
  
}

About the short pulses with the testpin:
Next step is to obey my always quoted rule: add one thing at a time to see when does the quick switch-back to other output-level happen.

best regards Stefan

StefanL38:
I have stripped down the code to the bare minimum ISR and a loop that does report the measured rpm to the serial port with some other values.

In this Version it works as expected. toggling the test-out-put-pin follows the inputsignal.

So you're saying that the code you posted works as expected? If so, that's not much to go on. It would be more useful to post an MRE that demonstrates the problem.