Esp32 Pin appears to be bouncing

Hey everyone!
I am interfacing my esp32 board with a flow sensor, which has a spinning wheel and sends out an alternating digital signal, kind of like an encoder with only one channel. I started by using interrupts to measure the signals frequency, and I need to measure frequencies between 20 and 60Hz. When I compared my measurements to an oscilloscope as well as a signal analyzer, I realized that the higher the frequency, the more the esp32 seemed to overestimate the frequency. This immediately seemed to me like an effect of bouncing, but both the oscilloscope and the signal analyzer did not show any signs of bouncing. Nevertheless, I switched from interrupts to polling and debounced the signal in software through a delay, and that got rid of the issue. I found that a delay of 4ms was necessary for the esp to not overestimate the frequency within my range of 20-60Hz.
Does anybody have an idea what could cause the error I am experiencing? While I am fine with polling the signal, I really don't want to leave a 4ms delay in my code.

Here is the code that uses the interrupt:

#define PIN_FLOW_SENSOR 16

hw_timer_t * timerFlow = NULL;  // hw timer variable to calculate flow rate
portMUX_TYPE flowDetectMux = portMUX_INITIALIZER_UNLOCKED;
portMUX_TYPE timerFlowMux = portMUX_INITIALIZER_UNLOCKED;

volatile unsigned long counter;
volatile unsigned long pulses;
unsigned long oldPulses;
float freq = 0.0;

void IRAM_ATTR flowSense() {
  portENTER_CRITICAL_ISR(&flowDetectMux);
  counter++;
  portEXIT_CRITICAL_ISR(&flowDetectMux);
}

void IRAM_ATTR onTimer2() {
  portENTER_CRITICAL_ISR(&timerFlowMux);
  pulses = counter;
  counter = 0;
  portEXIT_CRITICAL_ISR(&timerFlowMux);
}

void setup() {
  Serial.begin(115200);
  attachInterrupt(PIN_FLOW_SENSOR, flowSense, FALLING);
  timerFlow = timerBegin(2, 80, true);
  timerAttachInterrupt(timerFlow, &onTimer2, true);
  timerAlarmWrite(timerFlow, 5000000, true);
  timerAlarmEnable(timerFlow);


}

void loop() {
  if (oldPulses != pulses) {
    freq = pulses / 5.0;
    Serial.println(freq);
    oldPulses = pulses;
  }

}

And here is the code that does the same thing but with polling:

#define PIN_FLOW_SENSOR 16

hw_timer_t * timerFlow = NULL;  // hw timer variable to calculate flow rate
portMUX_TYPE flowDetectMux = portMUX_INITIALIZER_UNLOCKED;
portMUX_TYPE timerFlowMux = portMUX_INITIALIZER_UNLOCKED;

volatile unsigned long counter;
volatile unsigned long pulses;
unsigned long oldPulses;
int pinState;
int oldPinState;
float freq = 0.0;

void IRAM_ATTR onTimer2() {
  portENTER_CRITICAL_ISR(&timerFlowMux);
  pulses = counter;
  counter = 0;
  portEXIT_CRITICAL_ISR(&timerFlowMux);
}

void setup() {
  Serial.begin(115200);
  pinMode(PIN_FLOW_SENSOR, INPUT);
  timerFlow = timerBegin(2, 80, true);
  timerAttachInterrupt(timerFlow, &onTimer2, true);
  timerAlarmWrite(timerFlow, 5000000, true);
  timerAlarmEnable(timerFlow);


}

void loop() {
  pinState = digitalRead(PIN_FLOW_SENSOR);

  if (pinState != oldPinState) {
    counter++;
    oldPinState = pinState;
    delay(4);
  }
  if (oldPulses != pulses) {
    freq = pulses / 10.0;
    Serial.println(freq);
    oldPulses = pulses;
  }

}

All kinds of de-bouncing algorithms consume uC cycles, delay() while not as extravagant as other approaches is acceptable IMO, if you can still achieve your upper-limit of RPM.

Usually, I would recommend external hardware based signal conditioning rather than delay().

hardware conditioning of sensor bounce at DuckDuckGo

Approaches can be simple RC filters to using specialized IC to debounce (555 comes to mind.)
using 555 to debounce sensor signal at DuckDuckGo

Has the OP considered using the ESP32's PCNT API?

https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/pcnt.html

The PCNT can count pulses without utilizing the CPU. The PCNT works under the ESP32's Arduino core.

Thanks for your reply.
The thing is, I'm not really sure what I am experiencing is actually bouncing. The sensor is definitetly not bouncing, because if it was, I would see it both on the oscilloscope and the signal analyzer. So in my opinion, there is something happening on the esp board that I don't really understand.

here is some code I did using the PCNT under the Arduino IDE.

/*
   Chappie Weather upgrade/addition
   process wind speed direction and rain fall.
*/
#include "esp32/ulp.h"
//#include "ulptool.h"
#include "driver/rtc_io.h"
#include <WiFi.h>
#include <PubSubClient.h>
#include "certs.h"
#include "sdkconfig.h"
#include "esp_system.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/timers.h"
#include "freertos/event_groups.h"
#include <driver/pcnt.h>
#include <driver/adc.h>
#include <SimpleKalmanFilter.h>
#include <ESP32Time.h>
////
ESP32Time rtc;
WiFiClient wifiClient;
PubSubClient MQTTclient(mqtt_server, mqtt_port, wifiClient);
////
float CalculatedVoltage = 0.0f;
float kph = 0.0f;
float rain  = 0.0f;
/*
   PCNT PCNT_UNIT_0, PCNT_CHANNEL_0 GPIO_NUM_15 = pulse input pin
   PCNT PCNT_UNIT_1, PCNT_CHANNEL_0 GPIO_NUM_4 = pulse input pin
*/
pcnt_unit_t pcnt_unit00 = PCNT_UNIT_0; //pcnt unit 0 channel 0
pcnt_unit_t pcnt_unit10 = PCNT_UNIT_1; //pcnt unit 1 channel 0
//
//
hw_timer_t * timer = NULL;
//
#define evtAnemometer  ( 1 << 0 )
#define evtRainFall    ( 1 << 1 )
#define evtParseMQTT   ( 1 << 2 )
EventGroupHandle_t eg;
#define OneMinuteGroup ( evtAnemometer | evtRainFall )
////
QueueHandle_t xQ_Message; // payload and topic queue of MQTT payload and topic
const int payloadSize = 100;
struct stu_message
{
  char payload [payloadSize] = {'\0'};
  String topic ;
} x_message;
////
SemaphoreHandle_t sema_MQTT_KeepAlive; // used to stop all other MQTT thing do's
SemaphoreHandle_t sema_mqttOK; // protect the mqttOK variable.
SemaphoreHandle_t sema_CalculatedVoltage; // protects the CalculatedVoltage variable.
////
int mqttOK = 0; // stores a count value that is used to cause an esp reset
volatile bool TimeSet = false;
////
/*
   Topic topicOK has been subscribed to, the mqtt broker sends out "OK" messages if the client receives an OK message the mqttOK value is set back to zero.
   If the mqttOK count reaches a set point the ESP32 will reset.
*/
////
void IRAM_ATTR mqttCallback(char* topic, byte * payload, unsigned int length)
{
  memset( x_message.payload, '\0', payloadSize ); // clear payload char buffer
  x_message.topic = ""; //clear topic string buffer
  x_message.topic = topic; //store new topic
  memcpy( x_message.payload, payload, length );
  xQueueOverwrite( xQ_Message, (void *) &x_message );// send data to queue
} // void mqttCallback(char* topic, byte* payload, unsigned int length)
////
// interrupt service routine for WiFi events put into IRAM
void IRAM_ATTR WiFiEvent(WiFiEvent_t event)
{
  switch (event) {
    case SYSTEM_EVENT_STA_CONNECTED:
      break;
    case SYSTEM_EVENT_STA_DISCONNECTED:
      log_i("Disconnected from WiFi access point");
      break;
    case SYSTEM_EVENT_AP_STADISCONNECTED:
      log_i("WiFi client disconnected");
      break;
    default: break;
  }
} // void IRAM_ATTR WiFiEvent(WiFiEvent_t event)
////
void IRAM_ATTR onTimer()
{
  BaseType_t xHigherPriorityTaskWoken;
  xEventGroupSetBitsFromISR(eg, OneMinuteGroup, &xHigherPriorityTaskWoken);
} // void IRAM_ATTR onTimer()
////
void setup()
{
  eg = xEventGroupCreate(); // get an event group handle
  x_message.topic.reserve(100);
  adc1_config_width(ADC_WIDTH_12Bit);
  adc1_config_channel_atten(ADC1_CHANNEL_6, ADC_ATTEN_DB_11);// using GPIO 34 wind direction
  adc1_config_channel_atten(ADC1_CHANNEL_3, ADC_ATTEN_DB_11);// using GPIO 39 current
  adc1_config_channel_atten(ADC1_CHANNEL_0, ADC_ATTEN_DB_11);// using GPIO 36 battery volts

  // hardware timer 4 set for one minute alarm
  timer = timerBegin( 3, 80, true );
  timerAttachInterrupt( timer, &onTimer, true );
  timerAlarmWrite(timer, 60000000, true);
  timerAlarmEnable(timer);
  /* Initialize PCNT's counter */
  int PCNT_H_LIM_VAL         = 3000;
  int PCNT_L_LIM_VAL         = -10;
  // 1st PCNT counter
  // Anemometer
  pcnt_config_t pcnt_config  = {};
  pcnt_config.pulse_gpio_num = GPIO_NUM_15;// Set PCNT input signal and control GPIOs
  pcnt_config.ctrl_gpio_num  = PCNT_PIN_NOT_USED;
  pcnt_config.channel        = PCNT_CHANNEL_0;
  pcnt_config.unit           = PCNT_UNIT_0;
  // What to do on the positive / negative edge of pulse input?
  pcnt_config.pos_mode       = PCNT_COUNT_INC;   // Count up on the positive edge
  pcnt_config.neg_mode       = PCNT_COUNT_DIS;   // Count down disable
  // What to do when control input is low or high?
  pcnt_config.lctrl_mode     = PCNT_MODE_KEEP; // Keep the primary counter mode if low
  pcnt_config.hctrl_mode     = PCNT_MODE_KEEP;    // Keep the primary counter mode if high
  // Set the maximum and minimum limit values to watch
  pcnt_config.counter_h_lim  = PCNT_H_LIM_VAL;
  pcnt_config.counter_l_lim  = PCNT_L_LIM_VAL;
  pcnt_unit_config(&pcnt_config); // Initialize PCNT unit
  // 12.5ns is one APB_CLK cycle 12.5*500, debounce time
  pcnt_set_filter_value( PCNT_UNIT_0, 500); //Configure and enable the input filter, debounce
  pcnt_filter_enable( PCNT_UNIT_0 );
  pcnt_counter_pause( PCNT_UNIT_0 );
  pcnt_counter_clear( PCNT_UNIT_0 );
  pcnt_counter_resume( PCNT_UNIT_0); // start the show
  // setup 2nd PCNT
  pcnt_config = {};
  pcnt_config.pulse_gpio_num = GPIO_NUM_4;
  pcnt_config.ctrl_gpio_num  = PCNT_PIN_NOT_USED;
  pcnt_config.channel        = PCNT_CHANNEL_0;
  pcnt_config.unit           = PCNT_UNIT_1;
  pcnt_config.pos_mode       = PCNT_COUNT_INC;
  pcnt_config.neg_mode       = PCNT_COUNT_DIS;
  pcnt_config.lctrl_mode     = PCNT_MODE_KEEP;
  pcnt_config.hctrl_mode     = PCNT_MODE_KEEP;
  pcnt_config.counter_h_lim  = PCNT_H_LIM_VAL;
  pcnt_config.counter_l_lim  = PCNT_L_LIM_VAL;
  pcnt_unit_config(&pcnt_config);
  pcnt_set_filter_value( PCNT_UNIT_1, 500 );
  pcnt_filter_enable  ( PCNT_UNIT_1 );
  pcnt_counter_pause  ( PCNT_UNIT_1 );
  pcnt_counter_clear  ( PCNT_UNIT_1 );
  pcnt_counter_resume ( PCNT_UNIT_1 );
  //
  xQ_Message = xQueueCreate( 1, sizeof(stu_message) );
  //
  sema_CalculatedVoltage = xSemaphoreCreateBinary();
  xSemaphoreGive( sema_CalculatedVoltage );
  sema_mqttOK = xSemaphoreCreateBinary();
  xSemaphoreGive( sema_mqttOK );
  sema_MQTT_KeepAlive = xSemaphoreCreateBinary();
  ///
  xTaskCreatePinnedToCore( MQTTkeepalive, "MQTTkeepalive", 10000, NULL, 5, NULL, 1 );
  xTaskCreatePinnedToCore( fparseMQTT, "fparseMQTT", 10000, NULL, 5, NULL, 1 ); // assign all to core 1, WiFi in use.
  xTaskCreatePinnedToCore( fReadBattery, "fReadBattery", 4000, NULL, 3, NULL, 1 );
  xTaskCreatePinnedToCore( fReadCurrent, "fReadCurrent", 4000, NULL, 3, NULL, 1 );
  xTaskCreatePinnedToCore( fWindDirection, "fWindDirection", 10000, NULL, 4, NULL, 1 );
  xTaskCreatePinnedToCore( fAnemometer, "fAnemometer", 10000, NULL, 4, NULL, 1 );
  xTaskCreatePinnedToCore( fRainFall, "fRainFall", 10000, NULL, 4, NULL, 1 );
  xTaskCreatePinnedToCore( fmqttWatchDog, "fmqttWatchDog", 3000, NULL, 3, NULL, 1 ); // assign all to core 1
} //void setup()
static void init_ulp_program()
{
// not sharing this code.
}
////
void fWindDirection( void *pvParameters )
// read the wind direction sensor, return heading in degrees
{
  SimpleKalmanFilter KF_ADC( 1.0f, 1.0f, .01f );
  const TickType_t xFrequency = 100; //delay for mS
  float    adcValue = 0.0f;
  uint64_t TimePastKalman  = esp_timer_get_time();
  float    high = 0.0f;
  float    low = 2000.0f;
  float    ADscale = 3.3f / 4096.0f;
  int      count = 0;
  String   windDirection;
  String   MQTTinfo = "";
  windDirection.reserve(20);
  MQTTinfo.reserve( 150 );
  TickType_t xLastWakeTime = xTaskGetTickCount();
  while ( !MQTTclient.connected() )
  {
    vTaskDelay( 250 );
  }
  for (;;)
  {
    windDirection = "";
    adcValue = float( adc1_get_raw(ADC1_CHANNEL_6) ); //take a raw ADC reading
    KF_ADC.setProcessNoise( (esp_timer_get_time() - TimePastKalman) / 1000000.0f ); //get time, in microsecods, since last readings
    adcValue = KF_ADC.updateEstimate( adcValue ); // apply simple Kalman filter
    TimePastKalman = esp_timer_get_time(); // time of update complete
    adcValue = adcValue * ADscale;
    if ( (adcValue >= 0.0f) & (adcValue <= .25f )  )
    {
      // log_i( " n" );
      windDirection.concat( "N" );
    }
    if ( (adcValue > .25f) & (adcValue <= .6f ) )
    {
      //  log_i( " e" );
      windDirection.concat( "E" );
    }
    if ( (adcValue > 2.0f) & ( adcValue < 3.3f) )
    {
      //   log_i( " s" );
      windDirection.concat( "S");
    }
    if ( (adcValue >= 1.7f) & (adcValue < 2.0f ) )
    {
      // log_i( " w" );
      windDirection.concat( "W" );
    }
    if ( count >= 30 )
    {
      MQTTinfo.concat( String(kph, 2) );
      MQTTinfo.concat( ",");
      MQTTinfo.concat( windDirection );
      MQTTinfo.concat( ",");
      MQTTinfo.concat( String(rain, 2) );
      xSemaphoreTake( sema_MQTT_KeepAlive, portMAX_DELAY );
      MQTTclient.publish( topicWSWDRF, MQTTinfo.c_str() );
      xSemaphoreGive( sema_MQTT_KeepAlive );
      count = 0;
    }
    count++;
    MQTTinfo = "";
    xLastWakeTime = xTaskGetTickCount();
    vTaskDelayUntil( &xLastWakeTime, xFrequency );
  }
  vTaskDelete ( NULL );
}
// read rainfall
void fRainFall( void *pvParemeters )
{
  int16_t click = 0; //count tipping bucket clicks
  pcnt_counter_pause( PCNT_UNIT_1 );
  pcnt_counter_clear( PCNT_UNIT_1 );
  pcnt_counter_resume( PCNT_UNIT_1 );
  for  (;;)
  {
    xEventGroupWaitBits (eg, evtRainFall, pdTRUE, pdTRUE, portMAX_DELAY);
    if ( (rtc.getHour(true) == 23) && (rtc.getMinute() == 59) )
    {
      pcnt_counter_pause( PCNT_UNIT_1 );
      rain = 0.0f;
      pcnt_counter_clear( PCNT_UNIT_1 );
      pcnt_counter_resume( PCNT_UNIT_1 );
    } else {
      pcnt_counter_pause( PCNT_UNIT_1 );
      pcnt_get_counter_value( PCNT_UNIT_1, &click );
      if ( click != 0 )
      {
        rain = rain + (0.2794f * (float)click);// 0.2794mm of rain per click
        pcnt_counter_clear( PCNT_UNIT_1 );
        log_i( "count %d, rain rain = %f mm", click, rain );
      }
      pcnt_counter_resume( PCNT_UNIT_1 );
      click = 0;
    }
  }
  vTaskDelete ( NULL );
}
////
void fAnemometer( void *pvParameters )
{
  int16_t count = 0;
  pcnt_counter_clear(PCNT_UNIT_0);
  pcnt_counter_resume(PCNT_UNIT_0);
  for (;;)
  {
    xEventGroupWaitBits (eg, evtAnemometer, pdTRUE, pdTRUE, portMAX_DELAY);
    pcnt_counter_pause( PCNT_UNIT_0 );
    pcnt_get_counter_value( PCNT_UNIT_0, &count);
    kph = 2.4f * ((float)count / 60.0f);// A wind speed of 2.4km/h causes the switch to close once per second
    //log_i( "%f", kph );
    pcnt_counter_clear( PCNT_UNIT_0 );
    pcnt_counter_resume( PCNT_UNIT_0 );
  }
  vTaskDelete ( NULL );
}
//////
void fmqttWatchDog( void * paramater )
{
  int UpdateImeTrigger = 86400; //seconds in a day
  int UpdateTimeInterval = 86300; // 1st time update in 100 counts
  int maxNonMQTTresponse = 60;
  for (;;)
  {
    vTaskDelay( 1000 );
    if ( mqttOK >= maxNonMQTTresponse )
    {
      ESP.restart();
    }
    xSemaphoreTake( sema_mqttOK, portMAX_DELAY );
    mqttOK++;
    xSemaphoreGive( sema_mqttOK );
    UpdateTimeInterval++; // trigger new time get
    if ( UpdateTimeInterval >= UpdateImeTrigger )
    {
      TimeSet = false; // sets doneTime to false to get an updated time after a days count of seconds
      UpdateTimeInterval = 0;
    }
  }
  vTaskDelete( NULL );
}
//////
void fparseMQTT( void *pvParameters )
{
  struct stu_message px_message;
  for (;;)
  {
    if ( xQueueReceive(xQ_Message, &px_message, portMAX_DELAY) == pdTRUE )
    {
      // parse the time from the OK message and update MCU time
      if ( String(px_message.topic) == topicOK )
      {
        if ( !TimeSet)
        {
          String temp = "";
          temp =  px_message.payload[0];
          temp += px_message.payload[1];
          temp += px_message.payload[2];
          temp += px_message.payload[3];
          int year =  temp.toInt();
          temp = "";
          temp =  px_message.payload[5];
          temp += px_message.payload[6];
          int month =  temp.toInt();
          temp =  "";
          temp =  px_message.payload[8];
          temp += px_message.payload[9];
          int day =  temp.toInt();
          temp = "";
          temp = px_message.payload[11];
          temp += px_message.payload[12];
          int hour =  temp.toInt();
          temp = "";
          temp = px_message.payload[14];
          temp += px_message.payload[15];
          int min =  temp.toInt();
          rtc.setTime( 0, min, hour, day, month, year );
          log_i( "rtc  %s ", rtc.getTime() );
          TimeSet = true;
        }
      }
      //
    } //if ( xQueueReceive(xQ_Message, &px_message, portMAX_DELAY) == pdTRUE )
    xSemaphoreTake( sema_mqttOK, portMAX_DELAY );
    mqttOK = 0;
    xSemaphoreGive( sema_mqttOK );
  }
} // void fparseMQTT( void *pvParameters )#include <ESP32Time.h>
//////
void fReadCurrent( void * parameter )
{
  const TickType_t xFrequency = 1000; //delay for mS
  const float mVperAmp        = 185.0f;
  float    ADbits             = 4096.0f;
  float    ref_voltage        = 3.3f;
  float    mA                 = 0.0f;
  float    adcValue           = 0.0f;
  float    Voltage            = 0.0f;
  float    Power              = 0.0f;
  float    offSET             = 0.0f;
  int      printCount         = 0;
  uint64_t TimePastKalman     = esp_timer_get_time(); // used by the Kalman filter UpdateProcessNoise, time since last kalman calculation
  SimpleKalmanFilter KF_I( 1.0f, 1.0f, .01f );
  /*
     185mv/A = 5 AMP MODULE
     100mv/A = 20 amp module
     66mv/A = 30 amp module
  */
  String powerInfo = "";
  powerInfo.reserve( 150 );
  while ( !MQTTclient.connected() )
  {
    vTaskDelay( 250 );
  }
  TickType_t xLastWakeTime = xTaskGetTickCount();
  for (;;)
  {
    adc1_get_raw(ADC1_CHANNEL_3); // read once discard reading
    adcValue = ( (float)adc1_get_raw(ADC1_CHANNEL_3) );
    //log_i( "adcValue I = %f", adcValue );
    Voltage = ( (adcValue * ref_voltage) / ADbits ) + offSET; // Gets you mV
    mA = Voltage / mVperAmp; // get amps
    KF_I.setProcessNoise( (esp_timer_get_time() - TimePastKalman) / 1000000.0f ); //get time, in microsecods, since last readings
    mA = KF_I.updateEstimate( mA ); // apply simple Kalman filter
    TimePastKalman = esp_timer_get_time(); // time of update complete
    printCount++;
    if ( printCount == 60 )
    {
      xSemaphoreTake( sema_CalculatedVoltage, portMAX_DELAY);
      Power = CalculatedVoltage * mA;
      //log_i( "Voltage=%f mA=%f Power=%f", CalculatedVoltage, mA, Power );
      printCount = 0;
      powerInfo.concat( String(CalculatedVoltage, 2) );
      xSemaphoreGive( sema_CalculatedVoltage );
      powerInfo.concat( ",");
      powerInfo.concat( String(mA, 4) );
      powerInfo.concat( ",");
      powerInfo.concat( String(Power, 4) );
      xSemaphoreTake( sema_MQTT_KeepAlive, portMAX_DELAY );
      MQTTclient.publish( topicPower, powerInfo.c_str() );
      xSemaphoreGive( sema_MQTT_KeepAlive );
      powerInfo = "";
    }
    xLastWakeTime = xTaskGetTickCount();
    vTaskDelayUntil( &xLastWakeTime, xFrequency );
  }
  vTaskDelete( NULL );
} //void fReadCurrent( void * parameter )
////
void fReadBattery( void * parameter )
{
  const float r1 = 50500.0f; // R1 in ohm, 50K
  const float r2 = 10000.0f; // R2 in ohm, 10k potentiometer
  const TickType_t xFrequency = 1000; //delay for mS
  float    adcValue = 0.0f;
  float    Vbatt = 0.0f;
  int      printCount = 0;
  float    vRefScale = (3.3f / 4096.0f) * ((r1 + r2) / r2);
  uint64_t TimePastKalman  = esp_timer_get_time(); // used by the Kalman filter UpdateProcessNoise, time since last kalman calculation
  SimpleKalmanFilter KF_ADC_b( 1.0f, 1.0f, .01f );
  TickType_t xLastWakeTime = xTaskGetTickCount();
  for (;;)
  {
    adc1_get_raw(ADC1_CHANNEL_0); //read and discard
    adcValue = float( adc1_get_raw(ADC1_CHANNEL_0) ); //take a raw ADC reading
    KF_ADC_b.setProcessNoise( (esp_timer_get_time() - TimePastKalman) / 1000000.0f ); //get time, in microsecods, since last readings
    adcValue = KF_ADC_b.updateEstimate( adcValue ); // apply simple Kalman filter
    Vbatt = adcValue * vRefScale;
    xSemaphoreTake( sema_CalculatedVoltage, portMAX_DELAY );
    CalculatedVoltage = Vbatt;
    xSemaphoreGive( sema_CalculatedVoltage );
    
      printCount++;
      if ( printCount == 3 )
      {
      //log_i( "Vbatt %f", Vbatt );
      printCount = 0;
      }
    
    TimePastKalman = esp_timer_get_time(); // time of update complete
    xLastWakeTime = xTaskGetTickCount();
    vTaskDelayUntil( &xLastWakeTime, xFrequency );
    //log_i( "fReadBattery %d",  uxTaskGetStackHighWaterMark( NULL ) );
  }
  vTaskDelete( NULL );
}
////
void MQTTkeepalive( void *pvParameters )
{
  sema_MQTT_KeepAlive   = xSemaphoreCreateBinary();
  xSemaphoreGive( sema_MQTT_KeepAlive ); // found keep alive can mess with a publish, stop keep alive during publish
  // setting must be set before a mqtt connection is made
  MQTTclient.setKeepAlive( 90 ); // setting keep alive to 90 seconds makes for a very reliable connection, must be set before the 1st connection is made.
  for (;;)
  {
    //check for a is-connected and if the WiFi 'thinks' its connected, found checking on both is more realible than just a single check
    if ( (wifiClient.connected()) && (WiFi.status() == WL_CONNECTED) )
    {
      xSemaphoreTake( sema_MQTT_KeepAlive, portMAX_DELAY ); // whiles MQTTlient.loop() is running no other mqtt operations should be in process
      MQTTclient.loop();
      xSemaphoreGive( sema_MQTT_KeepAlive );
    }
    else {
      log_i( "MQTT keep alive found MQTT status %s WiFi status %s", String(wifiClient.connected()), String(WiFi.status()) );
      if ( !(wifiClient.connected()) || !(WiFi.status() == WL_CONNECTED) )
      {
        connectToWiFi();
      }
      connectToMQTT();
    }
    vTaskDelay( 250 ); //task runs approx every 250 mS
  }
  vTaskDelete ( NULL );
}
////
void connectToWiFi()
{
  int TryCount = 0;
  while ( WiFi.status() != WL_CONNECTED )
  {
    TryCount++;
    WiFi.disconnect();
    WiFi.begin( SSID, PASSWORD );
    vTaskDelay( 4000 );
    if ( TryCount == 10 )
    {
      ESP.restart();
    }
  }
  WiFi.onEvent( WiFiEvent );
} // void connectToWiFi()
////
void connectToMQTT()
{
  MQTTclient.setKeepAlive( 90 ); // needs be made before connecting
  byte mac[5];
  WiFi.macAddress(mac);
  String clientID = String(mac[0]) + String(mac[4]) ; // use mac address to create clientID
  while ( !MQTTclient.connected() )
  {
    // boolean connect(const char* id, const char* user, const char* pass, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage);
    MQTTclient.connect( clientID.c_str(), mqtt_username, mqtt_password, NULL , 1, true, NULL );
    vTaskDelay( 250 );
  }
  MQTTclient.setCallback( mqttCallback );
  MQTTclient.subscribe( topicOK );
} // void connectToMQTT()
////
void loop() {}

Thank you for your suggestion. I have never worked with the PCNT. It honestly looks a bit daunting at first glance, but I will definitely look into it when i find the time!

Then don't!
Here is my take on debouncing inputs: Buttons and other electro-mechanical inputs (introduction)
There are plenty of other examples, libraries and tutorials about debouncing without delay.

What happens if you poll without any debouncing? Just do state change detection.

Do you use a pull up resistor?

If I do not use a delay when polling, I get the same behaviour as I get with interrupts.
My main concern here is not how to debounce the signal. If I were convinced that bouncing was my issue, I would work out a debouncing solution that I am happy with and move on. However, I am unsure what my real issue is, since I can not replicate the behaviour of the esp32 with the oscilloscope/signal analyzer.

I have tried to use internal and external pullup resistors, but there was no noticable change.

OK, thanks. I don't have any more suggestions at the moment. I'll keep following the topic and if I have any ideas I'll post them.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.