Interrupt Rising counter not matching oscilloscope measurement?

I have a frequency coming through which I am countint by attaching an interrupt, delaying 1000, then detaching. This was working very reliably.

The frequency is from a hall effect sensor on a fan that was creating noise, so I pass it through an opto isolator to keep the two circuits seperate. On the destination side, the Input Pullup pin is pulled to ground when the transistor is activated. I have measured the frequency between the pin and ground and see its being pulled at the correct frequency down to 0.5v then up to 5.5v. After making these changes to put in the opto (and moving everything to an enclosure etc), it is not working and returns counts that are almost randlm. But, I can measure the signal the arduino sees and it should be perfect.

What should I look at? Things broke after moving to a new case, but after putting in the opto I monitor the signal and it should be super clear. Why is the interrupt not tripping likewise when it once

#include <SPI.h>
#include <Wire.h>
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
#define OLED_RESET     -1 // Reset pin # (or -1 if sharing Arduino reset pin)
#define SCREEN_ADDRESS 0x3C ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
int lstart=1;// 25; //Where to start drawing updated RPM
int rstart=65;//95; 
int yRPM=33;//18;  //row where RPM info is



String lowPN="9HV08"; //14,900
int lowmin=13900;
int lowmax=15300;

String PN;

String highPN="9HVA08"; //16,100
int highmin= 15700;
int highmax= 17100;

bool lpass=true;
bool rpass=true;
bool failure=false;

int min;
int max;

int lsensorpin = 2;
int rsensorpin= 3;
int rgled=4;
int rrled=5;
int gbut=7;
int rbut=6;
int lgled=8;
int lrled=9;
int lrelaypin= 10;
int rrelaypin=11;
int flipperpin=15; //A1
bool redexit=false;
int x=0;
bool flipper=false;

int timestamp;

unsigned long L=0;
unsigned long R=0;
int lRPM=0;
int rRPM=0;

void setup() {
  pinMode(lsensorpin, INPUT_PULLUP); //2
  pinMode(rsensorpin, INPUT_PULLUP); //3
  pinMode(rgled, OUTPUT); //4
  pinMode(rrled, OUTPUT); //5
  pinMode(gbut, INPUT_PULLUP); //6
  pinMode(rbut, INPUT_PULLUP); //7
  pinMode(lgled, OUTPUT); //8
  pinMode(lrled, OUTPUT); //9
  pinMode(lrelaypin, OUTPUT); //10 
  pinMode(rrelaypin, OUTPUT); //11
  pinMode(flipperpin, INPUT_PULLUP); //15
  //SDA18
  //SCL19
  


        // IDK WHy but screen needs this
      if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
        //Serial.println(F("SSD1306 allocation failed"));
        for(;;); // Don't proceed, loop forever
      }

      display.display(); //Show Adafruit logo 2 seconds
      delay(500); // Pause for 2 seconds
      display.clearDisplay();
      //setupscreen(1); will be done under setflipper
  
  setflipper(); //UNDOOOOOOOOOOOOOOO
  fansoff();
  lightdemo();
  Serial.begin(115200);
  //Serial.println("FInish Setup");
}

//void loop(){};

void loop() {
  if (flipper!=digitalRead(flipperpin)) setflipper();

  if ((failure==true) && (digitalRead(rbut)==false)){  //There was a failure and pressing red button to reset.  If not pressing red it will go on to checking GO
    setlights(0,0,0,0);
    failure=false;
    //SOMETHING RESETTING SCREEN BACK TO DISPLAYING MIN MAX

  }

  if ((failure==false) && (digitalRead(gbut)==false)){ //No previous failure and Green is go
    setupscreen(1);
    setlights(0,0,0,0);
    L=0;
    R=0;
    initiateandmonitor(); 
    //Serial.print("Got out of initiate with redexit=");
    //Serial.println(redexit);
    
    if (redexit==false){ //Spun up without red button
      /*Serial.println("8 seconds later with fans");
        timestamp=millis()+2000;
        Serial.println("timestamp");
        while (timestamp>millis()){
        Serial.print("Pin:");
        Serial.println(digitalRead(lsensorpin));
        } 
      */
      //measure Left
      attachl();
      delay(1000);
      detachl();
      //measure right
      attachr();
      delay(1000);
      detachr();
      //shut down and measure/displauy
      fansoff();
      Serial.print("L:");
      Serial.print(L);
      Serial.print("R:");
      Serial.print(R);
      lRPM=L*30;
      rRPM=R*30;
      Serial.print("lRPM:");
      Serial.print(lRPM);
      Serial.print("rRPM:");
      Serial.print(rRPM);
      lpass= (lRPM<max && lRPM>min);
      rpass= (rRPM<max && rRPM>min);
      
      if (lpass==true && rpass==true){
        failure=false;
        setlights(1,0,1,0);
      }else if (lpass==true && rpass==false){
        failure=true;
        setlights(1,0,0,1);
      }else if (lpass==false && rpass==true){
        failure=true;
        setlights(0,1,1,0);
      }else{
        failure=true;
        setlights(0,1,0,1);
      }
      setupscreen(2);
    }
  }
}

void setflipper(){
  if (digitalRead(flipperpin)==true) { //flipper is down/off
    flipper=true;
    PN=lowPN;
    min=lowmin;
    max=lowmax;
  }else{
    flipper=false;
    PN=highPN;
    min=highmin;
    max=highmax;
  }
  setupscreen(1);

}

void tickL() {
L=L+1;
}

void tickR() {
R=R+1;
}

void fansoff() {
digitalWrite(lrelaypin, true);
digitalWrite(rrelaypin, true);
}

void setlights(bool lg, bool lr, bool rg, bool rr) {
digitalWrite(lgled, lg);
digitalWrite(lrled, lr);
digitalWrite(rgled, rg);
digitalWrite(rrled, rr);
}

void lightdemo() {
setlights(1,0,1,0);
delay(1000);
setlights(0,1,0,1);
delay(1000);
setlights(0,0,0,0);
}

void fanson() {
digitalWrite(lrelaypin, false);
digitalWrite(rrelaypin, false);
}

void attachl(){
    attachInterrupt(digitalPinToInterrupt(lsensorpin), tickL, RISING);
    //attachInterrupt(digitalPinToInterrupt(rsensorpin), tickR, RISING);
}

void detachl(){
    detachInterrupt(digitalPinToInterrupt(lsensorpin));
    //detachInterrupt(digitalPinToInterrupt(rsensorpin));

}
void attachr(){
    //attachInterrupt(digitalPinToInterrupt(lsensorpin), tickL, RISING);
    attachInterrupt(digitalPinToInterrupt(rsensorpin), tickR, FALLING);
}

void detachr(){
    //detachInterrupt(digitalPinToInterrupt(lsensorpin));
    detachInterrupt(digitalPinToInterrupt(rsensorpin));

}


void initiateandmonitor(){
    fanson();
    delay(500);
    redexit=false;
    for(x = 0; x<160;x++){
      if(digitalRead(rbut)==false){
        redexit=true;
        fansoff();
        Serial.println("Breaking the fans");
        setlights(0,1,0,1);
        setupscreen(3);
        break;
      }else{
        //timestamp=millis()+2000;
        //Serial.println("timestamp");
        //while (timestamp>millis()){
         //   Serial.print("Pin:");
          //   Serial.println(digitalRead(lsensorpin));
        //} 
        delay(50);
        if (x%20==0) setupscreen(3);  
      }    
    }

    //if(x>159) Serial.println("Spun up with no red button");
}

//SCREENSTUFFFFFFF

void setupscreen(int y) {  //y=1 for showing PN being tested.  y=2 to display results
        display.clearDisplay();

        
        display.setTextSize(2); // Draw 2X-scale text
        display.setTextColor(SSD1306_WHITE);

        if (y==1){
        //Lines
        display.drawLine(0, 15, display.width()-1, 15, SSD1306_WHITE);
        display.drawLine(0, 16, display.width()-1, 16, SSD1306_WHITE);
        display.drawLine(62, 15, 62, 55, SSD1306_WHITE);
          
          //Header

          display.setCursor(0, 0);
          display.print("PN:");
          display.setCursor(50, 0);
          display.print(PN);
          
          //RPM HEaders
          display.setTextSize(1);
          display.setCursor(0, 18);
          display.print("MIN:");
          display.setCursor(70, 18);
          display.print("MAX:");

          //Print Min Max
          display.setCursor(lstart, yRPM);
          display.print(min);
          display.setCursor(rstart, yRPM);
          display.print(max);
        }else if (y==2){
          //Lines
          display.drawLine(0, 15, display.width()-1, 15, SSD1306_WHITE);
          display.drawLine(0, 16, display.width()-1, 16, SSD1306_WHITE);
          display.drawLine(62, 15, 62, 55, SSD1306_WHITE);
          
          //Header
          display.setCursor(0, 0);
          display.print("Left");
          display.setCursor(68, 0);
          display.print("Right");
          
          //RPM HEaders
          display.setTextSize(1);
          display.setCursor(0, 18);
          display.print("RPM:");
          display.setCursor(70, 18);
          display.print("RPM:");

          //PN at bottom when printing RPM
          display.setCursor(0,55);
          display.print(String(PN));
          display.setCursor(65,48);
          display.print(min);
          display.setCursor(65,55);
          display.print(max);
          Serial.println("Part is " + PN);

          //Print RPM's
          display.setTextSize(2);
          display.setCursor(lstart, yRPM);
          display.print(lRPM);
          display.setCursor(rstart, yRPM);
          display.print(rRPM);
        }else if (y==3){ //fans spinning
          if (redexit==true){
            display.setTextSize(3);
            display.setCursor(5,24);
            display.print("STOP");
          }else{
            //Lines
            display.drawLine(0, 15, display.width()-1, 15, SSD1306_WHITE);
            display.drawLine(0, 16, display.width()-1, 16, SSD1306_WHITE);
            
            //Header
            display.setCursor(0, 0);
            display.print("SPIN UP");

            //Countdown
            display.setTextSize(3);
            display.setCursor(50,20);
            display.print(8-(x/20));
          }
        }
        display.display();

}

Post your code so we see what you are doing. 1 second delay sounds rather long if the oscilloscope is showing a 1kHz trace. Also add a schematic so it is clear how you have wired it all.

Code Posted. Note- I have two of these Opto grounding circuits that are independent other than tying both interrupt pins to ground. I activate one interrupt, measure over 1 second, deactivate. Activate the other, measure, deactivate. I chose 1 second as that is around 500Hz and plenty of samples to eliminate measurement noise, but agree could delay to 0.2 seconds even.

I just recently have found it hanging up on the delay portion... almost wondering if I need to drop a new Arduino in. Image below, my oscilloscope measurement is on the bottom/arduino side of Opto so top should just be for FYI. Top circuit is 100% isolated.

These must be declared as volatile because they are updated in an ISR. That is the first easy thing to try.

Ok, added that, no change. Dug into the signals some more- Input signal looks pretty clean, other than some tiny bumps

I then had it measure high and low durations using pulseIn, which was very chaotic, the 9XX readings are good ones-

21:59:36.858 -> High:192
21:59:36.858 -> Low:0
21:59:36.858 -> High:315
21:59:36.899 -> Low:981
21:59:36.899 -> High:0
21:59:36.899 -> Low:0
21:59:36.899 -> High:0
21:59:36.902 -> Low:986
21:59:36.902 -> High:132
21:59:36.902 -> Low:0
21:59:36.902 -> High:484

Could it be catching onto those tiny 0.1V bumps in the input signal as a change in logic level?

I then had it read the input and just spit it back out to output for me to read, which looks pretty good other than the bit of noise (which could be the "0" duration readings)

So is my issue those tiny tiny bumps in the input signal?

Can you get this simple two interrupt test code to give you reasonable values?

//pulse counts changed within count_ISR's
volatile unsigned int count1;
volatile unsigned int count2;

//variables for protected copies of counts
unsigned int copyCount1;
unsigned int copyCount2;

void count1_ISR()
{
  count1++;
}

void count2_ISR()
{
  count2++;
}
void setup() {

  Serial.begin(115200);
 pinMode(2, INPUT_PULLUP);
 pinMode(3, INPUT_PULLUP);


  /*
    //send test signal to interrupt pins
    analogWrite(5,127);//980 hz timer 0 jumper pin 5 to pin 2
    analogWrite(9,127);//490 hz timer 2 jumper pin 9 to pin 3
  */
  
  attachInterrupt(digitalPinToInterrupt(2), count1_ISR, RISING); 
  attachInterrupt(digitalPinToInterrupt(3), count2_ISR, RISING); 
}

void loop() {
  static unsigned long lastSecond;
  if (micros() - lastSecond >= 1000000L)
  {
    lastSecond += 1000000;
    getCount();
    Serial.println(copyCount1);
    Serial.println(copyCount2);
    Serial.println();
  }

}
unsigned int getCount()
{
  noInterrupts();
  copyCount1 = count1;
  count1 = 0;
  copyCount2 = count2;
  count2 = 0;
  interrupts();
}

Couple quick questions- isnt the analog write writing a static value? Where is frequency being assigned?

Lastsecond- is this ever initially given a value? before adding?

What is the L at the end of the time value comparison?

Falling edges should be cleaner than rising because the pull down of the optocoupler transistor should be sharper than the "drift up" cause by the pin pullup resistor against any capacitance in your wiring. You could also try a stronger external pullup resistor on the pins, say 1k to 4.7k.

I had also thought about non-atomic access operations, which @cattledog 's code sample covers, but it looks like you handle contention by accessing the counters L and R only when interrupts are detached.

The voltage drop of 0.5 volts across the optocoupler's transistor looks just a bit high. It also appears you are using the hall effect sensor to short out the optocoupler led (if I understand your circuit correctly). Maybe also rewire it so the hall effect sensor powers the optocoupler led.

You can also try adding some debouncing in your ISR something like :

void tickL() {
  static uint32_t lastRunAtUs = 0;  // static initialized Once only 
  if (micros() - lastRunAtUs > 100 ) {  // don't accept short pulses
     lastRunAtUs = micros() ;
     L=L+1;
   }
 }

Yes, the analogWrite() is a static value just used to test the code. You should use your actual signals instead.

In this code, the counts are initialized to 0 and reset to 0 every second.

The L formatter forces the numerical constant 1000000 to be treated by the compiler as a long integer and not be truncated to the max value of a 16 bit integer.
https://www.arduino.cc/reference/en/language/variables/constants/integerconstants/

Correct on your interpretation. My measurement on the destination side of the opto tells me the hall effect side seems to be working correctly.

The destination side pullup that you mention- im using the interal pin pullup with is 20k-50k per various sources for an Uno. I am concerned this was too much and driving too little current throigh the transistor. 5k would provide 1ma, so I am much less than 1ma.

I am thinking to try:

  1. Manually pull up using a 5kish rather than fhe internal
  2. Flip the circuit so I make a pulldown on collector side and read there, with 5v going to the emitor. In my head this seems to make a stronger collector/ emitter but not sure if any different. The ground would be 0V rather than 0.5v I think.
  3. Add 0.1uf debounce cap. Im concerned charecterizing the bounce properly to code it correctly.

I have debounced the circuit to eliminate that as a source (it isnt). I have also done other things to help the circuit get current for transistor to be happy.

I have added a second arduino to the destination side of the opto so all it does is read this pulse and not everything else in the machine. This is a fully authentic Arduino. It also is failing, and I have no idea how after everything has been simplified. This interrupt in previous HW was using fine. Wrong variable type or something could cause a jump like this but it all looks fine.

I have atleast found it reads the frequency accurate as things speed up then as it approached about halfway to 550Hz it jumps everywhere. PulseIn also returns many "0" durations with good 9XX readings.

Below is an example debounce circuit, the outputted signal (can see its smoothed from previous images), the poor readings that jump once the fan speeds up, and the code I am using.


21:34:12.750 -> Htz: 0
21:34:14.575 -> L: 44
21:34:14.575 -> Htz: 88
21:34:15.265 -> L: 70
21:34:15.265 -> Htz: 140
21:34:15.479 -> L: 89
21:34:15.479 -> Htz: 178
21:34:15.913 -> L: 109
21:34:15.913 -> Htz: 218
21:34:16.452 -> L: 117
21:34:16.577 -> Htz: 234
21:34:16.986 -> L: 134
21:34:16.986 -> Htz: 268
21:34:17.511 -> L: 186
21:34:17.511 -> Htz: 372
21:34:18.036 -> L: 213
21:34:18.036 -> Htz: 426
21:34:18.567 -> L: 702
21:34:18.567 -> Htz: 1404
21:34:19.195 -> L: 1913
21:34:19.196 -> Htz: 3826
21:34:19.838 -> L: 3508
21:34:19.838 -> Htz: 7016

unsigned long timestamp;
int lsensorpin = 2;
int PWMpin = 3;
String printout;
unsigned int reading;
unsigned long L;
unsigned int lRPM;
unsigned int Htz;

void setup() {
  pinMode(lsensorpin, INPUT);//_PULLUP); //2
  pinMode(PWMpin, OUTPUT);
  analogWrite(PWMpin, 100);
  Serial.begin(9600);
  Serial.println("FInish Setup");
}

void loop() {

  //printout=" High:";
  //reading= pulseIn(lsensorpin, HIGH);
  //printout=printout + reading + " Low:";
  //reading= pulseIn(lsensorpin, LOW);
  //printout= printout + reading;
  //Serial.println(printout + reading);
  
  L=0;
  attachl();
  delay(500);
  detachl();
  Htz=L*2;
  lRPM= L*60;
  Serial.print("L: ");
  Serial.println(L);
  //Serial.print("Htz: ");
  //Serial.println(Htz);
  
  //Serial.print("RPM: ");
  //Serial.println(lRPM);
}

void tickL() {
L=L+1;
}

void attachl(){
    attachInterrupt(digitalPinToInterrupt(lsensorpin), tickL, RISING);
}

void detachl(){
    detachInterrupt(digitalPinToInterrupt(lsensorpin));
}




Needs to be
volatile unsigned long L;

The attach and detatch of the interrupt in loop and reading during a delay is not standard practice.

Does the code in post 6 which uses periodic reading of an interrupt attached in setup perform any better?

Using your code, only removing the second channel has same results. Signal below, which both positive and ground are taken at same signal/board ground points.... frustrating!

Edit: also tryed without the debounce, issue remained. I could solve in code probably, just seems like it def shouldnt be needed.

13:59:29.284 -> 315
13:59:30.267 -> 2072
13:59:31.290 -> 7423
13:59:32.273 -> 7069
13:59:33.265 -> 6655
13:59:34.275 -> 6480
13:59:35.267 -> 6518
13:59:36.268 -> 6446
13:59:37.275 -> 3841

There is something difficult to understand about your oscilloscope trace from the circuit in post #11

If I simulate it, I get this:

I've simply used a mosfet in place of your switch and pulsed its gate at 500Hz.
I'd say that your debouncing is far too aggressive for this frequency and the 1k resistor in series with the switch is preventing the trace reaching 0v.
I don't see, with the values you say you are using, that is a 10k resistor and a 0.1uF capacitor, how you get reasonably near to an ideal trace.

My debounce resustor is around 1.2K, sorry. Ive tuned it with a variable resistor.

Ok- i have 2 knowns now and I dont know why:

  1. I can get it to run and measure correctly (original code) when only one fan is running
  2. If I run both fans, even with the destination opto completely disconnected, the reading is high.

Thus the hall affect must be getting mixed on the source side, even if my measurements on destination look clean. How, or what is going on, im unsure. I would think if the oscilloscope shows good where I measure it would be good, but something more going on. Note grounds on source side in the optos are tied

OK. so the circuit you are currently using does not appear to have a strong resemblance to that in post #11.

Another thing that I find interesting is the duty cycle of the trace on the oscilloscope is always very near to 50%. What element of the fan is triggering the hall effect sensor, how is it mounted and maybe also supply a link to this sensor.

How and where do you disconnect the second signal when both fans are running?

Appreciate the help. I have pulled out the debounce as it didnt fix the issue and doesnt appear to be it.

The hall effect is internal to the fan, but it seems it is designed to trip through a 90 degree rotation, twice a full circle. So, on 90 off 90 on 90 off 90. The fan document doesnt detail that detail but does recommend the general pullup im using.

Sorry if this seems like bouncing around, im taking a methodological approach but there is some fundamental disfunction causing a few somehow related issues.

Note- i started looking upstream due to, even though the second fan opto outputs went nowhere, having the ground termnal of the output connected to ground caused noise in the readings. Im wondering if one fan tripping a hall will ground both sidea

On the output of the opto. Where i did for troubleshoot sake. In design it is meant to both be hooked up

When you went to the enclosure, are the fans in close proximity to each other? Can the magnets from one fan be tripping the sensors on the other?

What makes no sense to me if that were the case is that the scope signal is clean. Are the scope images you posted with both fans running? Is the measurement taken at the input pin to the Arduino?