PID controller speed control for a DC motor with encoder

Here is my working version of a similar sketch to the one you wrote

// This version of Tacometer auto adjusts for difference in pulses and has Stall Recovery
#include <PID_v3.h> // uses my new version of PID_v1

// Adjust these variables:
int PulsesPerRevolution = 400;// From Tachometer How Many Pulses Pur Revolution
int SampleDuration = 30; // in Milliseconds -- How often do you want to trigger the PID Lower the number the more detailed control but the more the arduino processor time is used up
double consKp = 1, consKi = 0.01, consKd = 1;
double Setpoint = 3000;// RPM 
#define PWMpin  3
#define interruptPin 2
#define  HBridgeEnable pinMode(4, OUTPUT);digitalWrite(4, HIGH); // enables my H-Bridge Remove line 35 also 
//Define Variables we'll be connecting to

double Input, Output;

//Specify the links and initial tuning parameters
PID myPID(&Input, &Output, &Setpoint, consKp, consKi, consKd, DIRECT);

// Varables used for Calculations 
volatile unsigned long timeX = 1;
unsigned long StallTimer = SampleDuration *3;
volatile int Counts = 1;
double PulsesPerMinute;
volatile unsigned long LastTime;
volatile int PulseCtrX;
int PulseCtr;
unsigned long Counter;
unsigned long Time;

void setup() {
  Serial.begin(115200);
  Serial.println("Test Tachometer");
  PulsesPerMinute = (60 * 1000000) / (PulsesPerRevolution / Counts);// initialize
  pinMode(interruptPin, INPUT_PULLUP);
  pinMode(PWMpin, OUTPUT);
  HBridgeEnable // Remove this if you don't need it
  attachInterrupt(digitalPinToInterrupt(interruptPin), sensorInterrupt, FALLING);
  myPID.SetSampleTime(1);
  myPID.SetOutputLimits(0, 255); // Standard PWM Range
  myPID.SetMode(AUTOMATIC);
}

void loop() {
  readRpm();

  static unsigned long SpamTimer;
  if ((unsigned long)(millis() - SpamTimer) >= 15000) {
    SpamTimer = millis();
    Setpoint = (Setpoint < 5000) ? 7000 : 3000;
  }

}

// New version of sensorInterrupt
void sensorInterrupt()
{
  static int Ctr;
  unsigned long Time;
  Ctr++;
  if (Ctr >= Counts) { // so we are taking an average of "Counts" readings to use in our calculations
    Time = micros();
    timeX += (Time - LastTime); // this time is accumulative ovrer those "Counts" readings
    LastTime = Time;
    PulseCtrX ++; // will usually be 1 unless something else delays the sample
    Ctr = 0;
  }
}

void readRpm()
{
  static unsigned long STime;
  if (!PulseCtrX) {
    if ((unsigned long)(millis() - STime) >= StallTimer) {
      Time = micros();
      timeX += (Time - LastTime); // this time is accumulative ovrer those "Counts" readings
      LastTime = Time;
      PulseCtrX ++; // will usually be 1 unless something else delays the sample
      Serial.print(" Stall \t");
      //Setpoint +=100;
    } else return; // << Added lets not stop interrupts unless we know we are ready (keep other code happy).
  }
  cli ();         // clear interrupts flag
  Time = timeX;   // Make a copy so if an interrupt occurs timeX can be altered and not affect the results.
  timeX = 0;
  PulseCtr = PulseCtrX ;
  PulseCtrX = 0;
  sei ();         // set interrupts flag
  if (PulseCtr > 0) {
    Input =  (double) (PulsesPerMinute /  (double)(( (unsigned long)Time ) *  (unsigned long)PulseCtr)); // double has more percision
    //   PulseCtr = 0; // set pulse Ctr to zero
    AverageCapture(Input);
    debug();
    myPID.Compute();
    analogWrite(PWMpin, Output);
    long DSDur = ((long) Time *  (long)PulseCtr) - ((long) SampleDuration * 1000L);
    Counts -= DSDur * .015;
    Counts = max(Counts, max(1, PulsesPerRevolution * .25));
    Time = 0; // set time to zero to wait for the next rpm trigger.
    Counter += PulseCtr;
    PulseCtr = 0; // set pulse Ctr to zero
    PulsesPerMinute = (60.0 * 1000000.0) / (double)((double)PulsesPerRevolution / (double)Counts);
    STime = millis();
  }
}
float AvgArray[100];
int Readings = 0;
void AverageCapture(float in) {
  static int Position = 0;
  AvgArray[Position] = in;
  Position++;
  Readings++;
  Readings = min (100, Readings); // 100 readings 0-99;
  if (Position >= 100)Position = 0;//99 spots
}
float AverageValue() {
  float Total = 0;
  float Average;
  if (!Readings)return (0.0);
  for (int Position = 0; Position < Readings; Position++) {
    Total += AvgArray[Position];
  }
  Average = Total / Readings;
  return (Average);
}

void debug(){
  char S[20];
  for (static long QTimer = millis(); (long)( millis() - QTimer ) >= 100; QTimer = millis() ) {  // one line Spam Delay at 100 miliseconds
    Serial.print("Counts: "); Serial.print(Counts );
    //    Serial.print(" Target RPM: ");Serial.print(RPM );
    //    Serial.print(" Counts: "); Serial.print(Counts );
    //    Serial.print(" time: "); Serial.print(Time );
    //    Serial.print(" DeltaT: "); Serial.print(Time *  PulseCtr);
    //    Serial.print(" PulseCtr: "); Serial.print(PulseCtr );
    //    Serial.print(" PulsesPerMinute: "); Serial.print(dtostrf(PulsesPerMinute, 6, 1, S));
    Serial.print("\t Average: "); Serial.print(dtostrf(AverageValue(), 6, 1, S));
    //    Serial.print(" Setpoint: "); Serial.print(dtostrf(Setpoint, 6, 1, S) );
    //    Serial.print(" Calculated RPM: "); Serial.print(dtostrf(Input, 6, 1, S));
    //    Serial.print(" Output: "); Serial.print(dtostrf(Output, 6, 2, S));
    Serial.print("\t "); 
    //    Serial.println();

  }
}

the library PID_v3

has no timing issues
This drove a small motor attached directly to a 400 pulse per revolution encoder
the motor controller is just an H-bridge

Hope this helps give you ideas
ZHomeSlice