Hello.
I recently got a new computer, and as a result, ended up upgrading all of the Arduino IDE components. IDE 2.3.4. BEFORE the upgrade, Using a three+ year old IDE, I had a falling-edge interrupt reading a signal from the output of a power meter, worked very reliably. Without changing ANY hardware, and only updating everything to work with the new IDE, I now find my falling edge interrupt triggering anywhere between 12 and 100 times per cycle. The power meter asserts its signal for ~20ms through an optocoupler output, so connecting directly to an Arduino input with pullup is no problem - and hasn't been a problem for a year and a half running on the same Arduino, two differ PCBs, but the latest for at least six months.
Then the IDE upgrade, and it feels like the Arduino is just triggering on level and not edge.
ESP32 Dev Module is the selected board.
to reiterate, this worked, exactly like this, before I upgraded the IDE and all the associated stuff.
I include relevant code below. The whole project is way too complicated to post in its entirety. Below are ALL of the relevant code snips.
Relevant code snippets:
#define pinInputZaehler 27 //*editted, was 25 in original post* //has pull-up
volatile uint32_t zaehler = 0; //volatile - can be changed in the interrupt
volatile unsigned long zaehlerMillis=0; //volatile - can be changed in the interrupt
uint32_t zaehlerLast = 0; //only changed in function
unsigned zaehlerMillisLast = 0; //only changed in function
//in void setup():
pinMode(pinInputZaehler, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(pinInputZaehler), StromZaehler, FALLING);
// in void loop():
ZaehlerStatus();
// interrupt function:
void StromZaehler() {
zaehler = zaehler + 1;
zaehlerMillis = millis();
}
//calculation function
void ZaehlerStatus(){
if (zaehler != zaehlerLast) { // pulse = 0.5kWh. 1p/h = 0.5kW
if (zaehlerMillisLast != 0) { //avoid calculation when there haven't been two good pulses.
zaehlerPower_W = 3600.0 * 0.5 * float(zaehler - zaehlerLast) / float((zaehlerMillis - zaehlerMillisLast) / 1000.0);
sprintf(a2dstr,"Z:%i/%i T:%i/%i",zaehler,zaehlerLast,zaehlerMillis,zaehlerMillisLast);
}
zaehlerMillisLast = zaehlerMillis;
zaehlerLast = zaehler;
}
I'm using the Arduino board "NodeMCU DevKit C V4" from AZ-Delivery. It's based on WROOM-32 SOC. The "ESP32 Dev Module" is the board configuration I use in the IDE, and it has worked fine for the last years with this Arduino module.
I can't upload a minimal sketch to the board because it runs about ten other functions I don't want to be without.
I designed and had a PCB fabricated. Schematic below. Turns out the input is actual 27, but this doesn't change that it has a pull-up. The + output of the S0 signal from the power meter is connected to JE5 pin 1 and the - to JE5 pin 2. It's been this way for over a year on the hardware in the schematic below. The only change was recompiling the code in the updated IDE.
IDE Appplication version was 1.8.16, which was the latest when I downloaded. By core version, I think you mean the "Board" driver in the IDE, but unfortunately because of OneDrive's insistance on synchronizing absolutely everything... I can't get it anymore. But I pretty much didn't update any drivers until I was forced to when I got the new computer
Hmmm. that's a reasonable work-around. I would likely increase 10 to 20, since the pulse is supposed to be 20ms long. the pulses should be worst-case about half a second apart, but generally longer
Hi UKHeliBob, thought the files were autotragicly synchronized.
but you're absolutely right, the IDE settings stay with the machine.
The old IDE has esp32 from espressif Version 1.0.6
The new IDE has esp32 from espressif Version 3.1.1
I don’t have a clue about the source of the problem but if the “true” falling signal shouldn’t happen faster than 500ms, you could use 200, 300, 400ms or more.
Briefly, in order for the rising- or falling-edge interrupts to function correctly on the ESP32 GPIO pins, both the rise and fall times of the input waveform must be short enough . My testing so far suggests that a 2 us (microsecond) or shorter transition between 10% and 90% logic levels avoids false triggering.
Just in case that you had any hardware changes that could interfere with the edges of the signal ...
Hi DaveX: 3.3kW is the practical limit of the circuit, even assuming an inrush current bringing the total load to 5kW, that is still 360ms between pulses. (2000 pulses per kWh) So, yeah... the pulses should be that far apart.
The interrupt source is a button with bouncing switched on, so you get a number of interrupts when it's pressed and released.
With bouncing switched off it does react only once per falling edge ...
I know it's just simulation so not necessarily representing the real thing but might be an indication for a different issue.
ec2021
Sketch
/*
Forum: https://forum.arduino.cc/t/falling-interrupt-triggers-many-times-per-event/1356817/4
Wokwi: https://wokwi.com/projects/423601736139637761
Original: bmoerdyk2
Modified: DaveX -> Implemented Limited StromZaehler()
Modified: ec2021 -> Re-Implemented Original StromZaehler()
*/
//#define LIMITED
#define pinInputZaehler 27 //verified, has internal pull-up
volatile uint32_t zaehler = 0; //volatile - can be changed in the interrupt
volatile unsigned long zaehlerMillis = 0; //volatile - can be changed in the interrupt
uint32_t zaehlerLast = 0; //only changed in function
unsigned zaehlerMillisLast = 0; //only changed in function
double zaehlerPower_W;
char a2dstr[128];
void setup() {
//in void setup():
Serial.begin(115200);
#ifdef LIMITED
Serial.println("Limited");
#else
Serial.println("Original");
#endif
pinMode(pinInputZaehler, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(pinInputZaehler), StromZaehler, FALLING);
}
void loop() {
// in void loop():
ZaehlerStatus();
}
#ifdef LIMITED
// interrupt function Limited:
void StromZaehler() {
uint32_t now = millis();
static uint32_t lastInt = 0;
if (now - lastInt < 10) return;
lastInt = now;
zaehler = zaehler + 1;
zaehlerMillis = now;
}
#else
// interrupt function Original:
void StromZaehler() {
zaehler = zaehler + 1;
zaehlerMillis = millis();
}
#endif
//calculation function
void ZaehlerStatus() {
if (zaehler != zaehlerLast) { // pulse = 0.5kWh. 1p/h = 0.5kW
if (zaehlerMillisLast != 0) { //avoid calculation when there haven't been two good pulses.
zaehlerPower_W = 3600.0 * 0.5 * float(zaehler - zaehlerLast) / float((zaehlerMillis - zaehlerMillisLast) / 1000.0);
sprintf(a2dstr, "Z:%i/%i T:%i/%i", zaehler, zaehlerLast, zaehlerMillis, zaehlerMillisLast);
Serial.println(a2dstr);
}
zaehlerMillisLast = zaehlerMillis;
zaehlerLast = zaehler;
}
}
[Edit]: @bmoerdyk2 , did you consider to test the interrupt routine by using a different source like a separate ESP32 or the like? Would be interesting to see how the device reacts if you connnect that source directly to the GPIO pin ...
ec2021. the source is the "S0" output of a power meter. Driven via an optocoupler with 100 ohm maximum on resistance, which is then pulling down through the 4.7k resistor against the ~45k pullup in the microcontroller.
This configuration has been working just fine for over a year with this hardware. the problems started when I reprogrammed it using the new IDE and new Board configuration (v3.1.1 instead of V1.0.6). I didn't change anything else.
Thank you to everyone for all the answers.
Following DaveX's suggestion, I implemented the "rate-limit" on the interrupt.
If the interrupt is triggered within 40ms of the "last" trigger, it's ignored.
This turns out to give a very stable result.
Even though I thought I was getting good results before, it's very clear now that even though it got infiinitely worse after reprogramming, that the problem existed before and that the changes in the core driver have impacted how the pins function, making the debouncing / rate limit necessary. So, ec2021 is correct in suspecting problems with the slew rate. The ~45k internal pullups are insufficient for this function.
I'll be upgrading the hardware to reduce the pullup resistance (reduce slew rate) the next time I need to take the system offline for maintenance.
If I could mark two answers as the solution... I would...
You've already got a solution with a lockout type debouncing routine where you accept the first interrupt as valid and refuse to accept further interrutps until a timer has expired.
Another option, still using interrupts, would be to use a hardware timer called routine to check the status of the pin every say 15 milliseconds to see if there is a pulse there.
Sorry, yes of course. I misinterpreted that. Then it appears that the period the curve is in the undefined logic level area during the falling edge appears just under 1mS . Until I read the quoted article, I did not realise that there was evidence that the ESP32 was so sensitive to this.