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
