Pwm to frequency: Increase maximum frequency

Hello,

I am trying to convert a pwm signal to a frequency with an Arduino Uno.
Therefore I'm using an 4n35 optocoupler (to avoid any ground loop).
This works great but there is a problem with my code.

When I use my code in another code that puts info on the serial monitor the calculated value doesnt seem right anymore above a certain value. Im trying to measure up to 1000Hz but in the bigger code the measurements are only accurate up to 200Hz. Above 200Hz the Arduino starts giving wrong values, like 6 times as high, along with some other crappy values.

When I measure the incoming signal at the Arduino with an oscilloscope everything looks normal.
Of course there is a maximum frequency that can be measured accurately but I don`t think it has to do something with that, does it?

As you can see I use MegunoLink Pro to log everything:D

I hope you can help me:)

The first code is the 'naked' code.

int val; 
int prev_val = 0;
float t, cur_t;                                  
int interruptPin = 2;
volatile int state = LOW;

void setup() { 
  Serial.begin(115200);
  attachInterrupt(digitalPinToInterrupt(interruptPin), rpm, CHANGE);
}

void loop() {     
}

void rpm() {
  int sig = digitalRead(2);
  if (sig == HIGH) val = HIGH;
  else val = LOW;
  
  if (prev_val == 0 && val == 1) {  
    cur_t = micros();                 
    int hertz = 1000000 / (cur_t - t);
    Serial.print("{TIMEPLOT:Speed|DATA|Speed|T|"); //MegunoLink
    Serial.println(hertz);
    Serial.println("}");
    t = micros();                 
  }
  
prev_val = val;            
}

The second code, the integrated code:

#include "CommandHandler.h" 
#include "MegunoLink.h"
#include <OneWire.h>
OneWire ds(3);//2
CommandHandler<> SerialCommandHandler;

TimePlot MyPlot;

int OnTime = 0;
int Trackbar = 0;
int Fan = 6;//3
int a = 0;
int b = 0;
int warningLed = LOW;
unsigned long previousMillis = 0; 
const long interval = 100;  
int ledState = LOW;

int val;                                          //the digital value of the incoming analog signals
int prev_val = 0;                                 //last value
float t, cur_t;                                   //time variables
int interruptPin = 2;//3
volatile int state = LOW;

void setup() {
  Serial.begin(115200);
  
  SerialCommandHandler.AddVariable(F("OnTime"), OnTime);
  SerialCommandHandler.AddVariable(F("Trackbar"), Trackbar);

  pinMode(10, INPUT);                             //Set 10 at the Arduino as input
  pinMode(Fan, OUTPUT);
  pinMode(8, OUTPUT);
  digitalWrite(8, LOW);

  attachInterrupt(digitalPinToInterrupt(interruptPin), rpm, CHANGE);
//Set graph properties for Megunolink
  MyPlot.SetTitle("Temperature sensors");
  MyPlot.SetXlabel("Time in [h]");
  MyPlot.SetYlabel("Degrees in [C]");
  Serial.println("{TIMEPLOT|STYLE|Temp1:bn_1}");
  Serial.println("{TIMEPLOT|STYLE|Temp2:rn_1}");
  Serial.println("{TIMEPLOT|STYLE|Temp3:bn_1}");
  Serial.println("{TIMEPLOT|STYLE|Temp4:rn_1}");
  Serial.println("{TIMEPLOT|STYLE|Temp5:bn_1}");
  Serial.println("{TIMEPLOT|STYLE|Temp6:rn_1}");
}

void loop() {
  unsigned long currentMillis = millis();
  
  
  byte i;
  byte present = 0;
  byte type_s = 0;
  byte data[12];
  byte addr[8];
  int celsius;

//Write frequency to Megunolink, once every sensorcheck
  if (a == 0) {
    Table t;
    t.SendData("Frequency", (Trackbar*0.3563)-3.2857, "Hz");
  }


//Read out data sensors
  
  if ( !ds.search(addr)) {
    ds.reset_search();
    delay(5);
    Serial.println();
    a = 0;
    return;
  }
  
    ds.reset();
    ds.select(addr);
    ds.write(0x44, 1); //Parasite power supply
  
    delay(85);
  
    present = ds.reset();
    ds.select(addr);    
    ds.write(0xBE);

    for ( i = 0; i < 9; i++) {
      data[i] = ds.read();
    }
  
    int16_t raw = (data[1] << 8) | data[0];
  
    byte cfg = (data[4] & 0x00);
    if (cfg == 0x00) raw = raw & ~7;

//Conversion and display in MegunoLink
    celsius = raw / 16.0;
    a = ++a;
    Serial.print("{TIMEPLOT:Temp");
    Serial.print(a, DEC);
    Serial.print("|DATA|Temp");
    Serial.print(a, DEC);
    Serial.print("|T|");
    Serial.print(celsius);
    Serial.print("}");
    Serial.println("");

//Warning alarm light coolant temp
    if (((a<=4) && (celsius > 25))||((a>4) && (celsius > 27))) {
        if (currentMillis - previousMillis >= interval) {
          previousMillis = currentMillis;
          ledState=!ledState;
          digitalWrite(8, ledState);
        }
    }
    else {
      digitalWrite(8, LOW);
    }
}

//RPM Measurment
void rpm() {
  int sig = digitalRead(3);                       //read raw value of hall sensor
  if (sig == HIGH) val = HIGH;                    //convert it to digital 0,1 form
  else val = LOW;
  
  if (prev_val == 0 && val == 1) {                //check for rising edge
    cur_t = micros();                             //Set time
    int hertzDyno = 1000000 / (cur_t - t);        // [1/s]
    Serial.print("{TIMEPLOT:Speed|DATA|Speed|T|");
    Serial.print(hertzDyno);
    Serial.println("}");
    t = micros();                                 //Reset time
  }
  
prev_val = val;                                   //Reset value
}

Even you first code contains some fundamental errors.

Never call any method of the Serial object inside interrupt context! It may end in a freeze.

    cur_t = micros();                 
    int hertz = 1000000 / (cur_t - t);
    Serial.print("{TIMEPLOT:Speed|DATA|Speed|T|"); //MegunoLink
    Serial.println(hertz);
    Serial.println("}");
    t = micros();

You set t to the value of micros after the whole printing to the serial interface (which you shouldn't do anyway, see above). So you loose all the microseconds that these instructions need. As you've saved the micros() value at the beginning (cur_t), just set t to that value.

You're attaching the ISR so that every change of the interrupt line will trigger a run of the ISR. In the ISR you then expensively check for a rising edge. Just change the mode from CHANGE to RISING and the hardware does that for you. You will increase the accuracy because the quite expensive digitalRead() call doesn't have to happen.

The second sketch reads a OneWire device. As this code is very timing sensitive it deactivates interrupts during bus communication. So if you get an interrupt from the PWM signal during that time you calculations will be wrong. As I don't know what device you're reading I cannot give you hints to smoothen that.