Problem with attachInterrupt DC motor PID

Hi guys. I'm having problems with my project about using arduino for DC motor speed control. I have wrote a program mostly based on PID library and struggled with the interrupt pin. Can you guys help me please, Im new so please go easy on me. I'm kinda desperate right now, 3 days and I haven't made any progress.
P/s: i tried to add few lines about enabling and disabling interrupt, but it didnt work also

/*working variables*/
#define PWM 6
#define sensorA 2
#define sensorB 8
//====
int lastTime;
float Input, Output, Setpoint=0;
float ITerm, lastInput;
float kp, ki, kd;
int SampleTime = 100; //0.1 sec
float outMin =40, outMax;
//=====
volatile int pulse =0;
//=====
const byte numChars = 32;
char receivedChars[numChars];
char tempChars[numChars];
boolean newData = false;
float Kp,Ki,Kd;
//=====
void setup()  {
  pinMode(PWM,OUTPUT);
  pinMode(sensorA,INPUT);
  pinMode(sensorB,INPUT);
  digitalWrite(sensorA,HIGH);
  digitalWrite(sensorB,HIGH);
  attachInterrupt(0,rpm,RISING);
  Serial.begin(9600);
}
void loop() {
  recvWithStartEndMarkers();//nhan du lieu
    if (newData == true) {
        strcpy(tempChars, receivedChars);
        parseData();
        showNewData();
        newData = false;
    }
  SetTunings(Kp,Ki,Kd);//dat chi so kp ki kd
  sei();
  delay(100);
  cli();
  getMotorData();//tinh so vong quay
  Compute();//tinh output
  motorForward(Output);//viet output
  Serial.println(Input);
}
  
void Compute()
{
   unsigned long now = millis();
   int timeChange = (now - lastTime);
   if(timeChange>=SampleTime)
   {
      /*Compute all the working error variables*/
      float error = Setpoint - Input;
      ITerm+= (ki * error);
      if(ITerm> outMax) ITerm= outMax;
      else if(ITerm< outMin) ITerm= outMin;
      float dInput = (Input - lastInput);
 
      /*Compute PID Output*/
      Output = kp * error + ITerm- kd * dInput;
      if(Output > outMax) Output = outMax;
      else if(Output < outMin) Output = outMin;
 
      /*Remember some variables for next time*/
      lastInput = Input;
      lastTime = now;
   }
}
 
void SetTunings(float Kp, float Ki, float Kd)
{
  float SampleTimeInSec = ((float)SampleTime)/1000;
   kp = Kp;
   ki = Ki * SampleTimeInSec;
   kd = Kd / SampleTimeInSec;
}
 void SetOutputLimits(float Min, float Max)
{
   if(Min > Max) return;
   outMin = Min;
   outMax = Max;
    
   if(Output > outMax) Output = outMax;
   else if(Output < outMin) Output = outMin;
 
   if(ITerm> outMax) ITerm= outMax;
   else if(ITerm< outMin) ITerm= outMin;
}
//============
void getMotorData()  {
static int pulseAnt = 0; 
Input = (pulse - pulseAnt)/(SampleTime*334/1000);
pulseAnt = pulse;
}    
void rpm()  {
  if (digitalRead(sensorB),LOW) pulse++;
  else pulse--;
}
void motorForward(int Output) {
  analogWrite(PWM, Output);
}
//========
void recvWithStartEndMarkers() {
    static boolean recvInProgress = false;
    static byte ndx = 0;
    char startMarker = '<';
    char endMarker = '>';
    char rc;

    while (Serial.available() > 0 && newData == false) {
        rc = Serial.read();

        if (recvInProgress == true) {
            if (rc != endMarker) {
                receivedChars[ndx] = rc;
                ndx++;
                if (ndx >= numChars) {
                    ndx = numChars - 1;
                }
            }
            else {
                receivedChars[ndx] = '\0'; // terminate the string
                recvInProgress = false;
                ndx = 0;
                newData = true;
            }
        }

        else if (rc == startMarker) {
            recvInProgress = true;
        }
    }
}
void parseData() {

      // split the data into its parts
    char * strtokIndx; // this is used by strtok() as an index

    strtokIndx = strtok(tempChars,",");     
    Setpoint=atof(strtokIndx); 
    strtokIndx = strtok(NULL, ",");
    Kp = atof(strtokIndx); 
    strtokIndx = strtok(NULL, ",");
    Ki = atof(strtokIndx); 
    strtokIndx = strtok(NULL, ",");
    Kd = atof(strtokIndx);     
}
//=====
void showNewData() {
    if (newData == true) {
        Serial.println(Setpoint);
        Serial.println(Kp);
        Serial.println(kp);
        Serial.println(pulse);
        Serial.println(Input);
        Serial.println(Ki);
        Serial.println(ki);
    }
}
sei();//enable interrupt
delay(100);
cli();//disable interrupt

lastattempt.txt (3.92 KB)

See attachInterrupt() in the Reference page, how it should be called.

You know that attachInterrupt() only works with a few pins? Which one do you use?

I don't think that your rpm calculation will work. You better clear pulse after reading it.

Thank you sir, for replying. I use pin 2 as interrupt pin, however, im not sure whether or not i need to enable and disable global interrupt to make it work. (I loaded the code and my motor stood still). And can you explain why my "rpm" wouldnt work?

Pin 2 should work as interruptPin, if you update attachInterrupt() accordingly.

I see no need for disabling interrupts in your code, except when you access volatile variables outside an ISR.

Your rpm calculation does not take into account overflows of the pulse counter, and seems to assume a fixed sampling interval.

So i removed the delay in loop function and enable interrupt every loop. The result is still the same. Please, help me

/*working variables*/
#define PWM 6
#define sensorA 2
#define sensorB 8
//====
int lastTime;
float Input, Output, Setpoint;
float ITerm, lastInput;
float kp, ki, kd;
int SampleTime = 100; //0.1 sec
float outMin =40, outMax;
//=====
volatile int pulse =0;
//=====
const byte numChars = 32;
char receivedChars[numChars];
char tempChars[numChars];
boolean newData = false;
float Kp,Ki,Kd;
//=====
void setup()  {
  pinMode(PWM,OUTPUT);
  pinMode(sensorA,INPUT);
  pinMode(sensorB,INPUT);
  digitalWrite(sensorA,HIGH);
  digitalWrite(sensorB,HIGH);
  attachInterrupt(digitalPinToInterrupt(sensorA),rpm,FALLING);
  Serial.begin(9600);
}
void loop() {
  recvWithStartEndMarkers();//nhan du lieu
    if (newData == true) {
        strcpy(tempChars, receivedChars);
        parseData();
        newData = false;
    }
  sei();
  SetTunings(Kp,Ki,Kd);//dat chi so kp ki kd
  getMotorData();//tinh so vong quay
  Compute();//tinh output
  motorForward(Output);//viet output
  Serial.println(Input);
}
  
void Compute()
{
   unsigned long now = millis();
   int timeChange = (now - lastTime);
   if(timeChange>=SampleTime)
   {
      /*Compute all the working error variables*/
      float error = Setpoint - Input;
      ITerm+= (ki * error);
      if(ITerm> outMax) ITerm= outMax;
      else if(ITerm< outMin) ITerm= outMin;
      float dInput = (Input - lastInput);
 
      /*Compute PID Output*/
      Output = kp * error + ITerm- kd * dInput;
      if(Output > outMax) Output = outMax;
      else if(Output < outMin) Output = outMin;
 
      /*Remember some variables for next time*/
      lastInput = Input;
      lastTime = now;
   }
}
//============
void getMotorData()  { //calculate velocity
Input = (pulse/(SampleTime*334/1000));
pulse=0;
}    
void rpm()  { //pulse counter
  if (digitalRead(sensorB),LOW) pulse++; //read value of sensor B, if B is low, plus 1
  else pulse--;
}
void motorForward(int Output) {
  analogWrite(PWM, Output); //print out value of Output
} 
void SetTunings(float Kp, float Ki, float Kd)
{
  float SampleTimeInSec = ((float)SampleTime)/1000;
   kp = Kp;
   ki = Ki * SampleTimeInSec;
   kd = Kd / SampleTimeInSec;
}
 void SetOutputLimits(float Min, float Max)
{
   if(Min > Max) return;
   outMin = Min;
   outMax = Max;
    
   if(Output > outMax) Output = outMax;
   else if(Output < outMin) Output = outMin;
 
   if(ITerm> outMax) ITerm= outMax;
   else if(ITerm< outMin) ITerm= outMin;
}

//========
void recvWithStartEndMarkers() {
    static boolean recvInProgress = false;
    static byte ndx = 0;
    char startMarker = '<';
    char endMarker = '>';
    char rc;

    while (Serial.available() > 0 && newData == false) {
        rc = Serial.read();

        if (recvInProgress == true) {
            if (rc != endMarker) {
                receivedChars[ndx] = rc;
                ndx++;
                if (ndx >= numChars) {
                    ndx = numChars - 1;
                }
            }
            else {
                receivedChars[ndx] = '\0'; // terminate the string
                recvInProgress = false;
                ndx = 0;
                newData = true;
            }
        }

        else if (rc == startMarker) {
            recvInProgress = true;
        }
    }
}
void parseData() {

      // split the data into its parts
    char * strtokIndx; // this is used by strtok() as an index

    strtokIndx = strtok(tempChars,",");     
    Setpoint=atof(strtokIndx); 
    strtokIndx = strtok(NULL, ",");
    Kp = atof(strtokIndx); 
    strtokIndx = strtok(NULL, ",");
    Ki = atof(strtokIndx); 
    strtokIndx = strtok(NULL, ",");
    Kd = atof(strtokIndx);     
}
//=====
[code]

[/code]

I think my code is choking some where. I have checked, it received my parameters right. However, for every loop, the "Input" is always 0 and the Compute function doesnt seem to produce any result.

UPDATE: I have changed all my function into PID lib. The problem is my "Input" is constantly 0, so the "Output" always tends to Max. Im quite sure that there is no problem with my optical encoder. Can anyone spot out the fault in my code please. Many thanks

#include <PID_v1.h>
/*working variables*/
#define PWM1 6
#define sensorA 2
//====
unsigned long time;
double Setpoint, Input, Output;
volatile double pulse;
int SampleTime=50;//0.05s
//=====
const byte numChars = 32;
char receivedChars[numChars];
char tempChars[numChars];
boolean newData = false;
double Kp,Ki,Kd;
PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, DIRECT);
//=====
void setup()  {
  pinMode(PWM1,OUTPUT);
  pinMode(sensorA,INPUT);
  digitalWrite(sensorA,HIGH);
  attachInterrupt(digitalPinToInterrupt(sensorA),rpm,RISING);
  Serial.begin(9600);
  myPID.SetMode(AUTOMATIC);
}
void loop() {
  recvWithStartEndMarkers();//nhan du lieu
    if (newData == true) {
        strcpy(tempChars, receivedChars);
        parseData();
        newData = false;
    }
  sei();
  myPID.SetTunings(Kp,Ki,Kd);//dat chi so kp ki kd
  getMotorData();//tinh so vong quay
  myPID.Compute();//tinh output
  analogWrite(PWM1, Output);
}
  

//============
void getMotorData()  {
//calculate velocity
Input = (pulse/(SampleTime*41/1000));
Serial.println(Input);
pulse=0;
}    
void rpm()  { //pulse counter
 pulse++;
}

//========
void recvWithStartEndMarkers() {
    static boolean recvInProgress = false;
    static byte ndx = 0;
    char startMarker = '<';
    char endMarker = '>';
    char rc;

    while (Serial.available() > 0 && newData == false) {
        rc = Serial.read();

        if (recvInProgress == true) {
            if (rc != endMarker) {
                receivedChars[ndx] = rc;
                ndx++;
                if (ndx >= numChars) {
                    ndx = numChars - 1;
                }
            }
            else {
                receivedChars[ndx] = '\0'; // terminate the string
                recvInProgress = false;
                ndx = 0;
                newData = true;
            }
        }

        else if (rc == startMarker) {
            recvInProgress = true;
        }
    }
}
void parseData() {

      // split the data into its parts
    char * strtokIndx; // this is used by strtok() as an index

    strtokIndx = strtok(tempChars,",");     
    Setpoint=atof(strtokIndx); 
    strtokIndx = strtok(NULL, ",");
    Kp = atof(strtokIndx); 
    strtokIndx = strtok(NULL, ",");
    Ki = atof(strtokIndx); 
    strtokIndx = strtok(NULL, ",");
    Kd = atof(strtokIndx);     
}
//=====

This time, I tried to separate blocks, still, focusing on counting. I tried this simple version of pulse counting and realize that my time interval is far more longer than it should. The result is approx. 2.5secs where as in my calculation, it should be less than 5ms. Im using pin 2 as interrupt pin, checked pin 3 as well, result still not improved. Any suggestion what should I do next to check pin interrupt will be appreciated

#define sensorA 2


 volatile double pulse;

double rpm;

 unsigned long timeold;
 unsigned long now=millis();
 void setup()
 {   pinMode(sensorA,INPUT);
  digitalWrite(sensorA,HIGH);
  Serial.begin(9600);
   attachInterrupt(digitalPinToInterrupt(sensorA), counter, FALLING);

   pulse = 0;
   rpm = 0;
   timeold = 0;
 }

 void loop()
 {
   unsigned long now=millis();
   if (pulse >= 100) { 
     //Update RPM every 20 counts, increase this for better RPM resolution,
     //decrease for faster update
     rpm = (60.0*1000.0*pulse)/(334.0*(now-timeold));
    Serial.println((now-timeold),DEC);
     timeold = now;     
pulse=0;
   }
 }

 void counter()
 {
   pulse++;
 }

Avoid floats for time critical variables, like pulse. You are counting integral pulses, no need for fractions here.

Disable interrupts while accessing volatile variables outside an ISR.

The update time depends on the pulse frequency. How many pulses do you get per revolution? What's the 334.0 factor good for?

Im quite sure that there is no problem with my optical encoder.

I'm not as sure as you are. When I test your code by adding tone(2,100) to setup, I see correct times of 1000.

For your RPM code

  1. I am assuming your motor only turns in one direction.
  2. You can calculate you RPM with only the duration between pulses
  3. you only need the last instance of RPM to successfully control the motor

Try something like this:

void rpm()  { 
  unsigned long PulseTime =  micros();
  PulseDuration = PulseTime - LastPulseTime;
  LastPulseTime = PulseTime;
  pulse++;
}

// do the conversion to RPM later when you need to know
// I'm rusty at the math but you should be able to easily convert Microseconds per revolution into RPM 
unsigned long MicrosecondsPerRevolution = (PulseDuration * PulsesPerRevolution);
int RPM = 1/(MicrosecondsPerRevolution * .000001) * 60;

you can use the pulse++ to tell if the motor is running.

Thank you guys for replying.
I finally get my motor to work, the problem was with my encoder indeed, its pins were loose. Now, I'm going back to the calculation algorithm. I.e if I set parameters as <60,2,0,0> (<setpoint,Kp,Ki,Kd>), my speed will only reach about 45rpm and it stays there. My assumption is: the program needs the error to differ from 0 to produce a meaning output (what I mean is: if the error equals to 0, duty of PWM will turn to 0 and my motor stop). Therefore at 45 rpm, the duty of PWM and my speed is balanced and the program will continuously produce the same Output for PWM. Can anyone confirm?
Thanks

The output of your P regulator is proportional to the error, i.e. a non-zero error will persist even in balanced state. That's why Ki should be added (become non-zero), so that this remaining error is almost eliminated after some (short) time.

You must adopt (increase) Kp, if the desired effect is not reached. In theory Kp can become arbitrarily high, but if you make it too high I suspect that the loop can start oscillating due to the motor response.

This is a tachometer program that I used with a optical encoder with 400 steps per revolution. It uses PID to control a motor with direct drive to the encoder at 2000 RPM
This requires the PID_v2 library I created to maintain stable PID control (PID_v1 is flawed).

I Posted earlier with an easier way to calculate RPM I seen this from another example I haven't adapted this RPM calculation to the new method but it works none the less.

Enjoy
Z
Test setup:
(The board is an atmega328p (UNO) arduino at heart motor controller I created)

Tachometer_Final_with_Self_Test.ino (4.98 KB)

PID_v2.cpp (9.18 KB)

MPU6050 - Copy.h (3.08 KB)

Thank you for your example. Though, it's a huge load of knowledge to absorb :slight_smile:
I will try it out in my program

zhomeslice:
This is a tachometer program that I used with a optical encoder with 400 steps per revolution. It uses PID to control a motor with direct drive to the encoder at 2000 RPM

More interesting stuff. Thank you.

I have taken a copy of your program for study.

Do you know if it will work (without any code changes) if the setpoint speed is controllable by the user.

And what about if the load on the motor changes?

These lines

   myPID.Compute();
    analogWrite(PWMpin, Output);

suggest that the compute function produces a non-zero value when there is no error. But in your robot-balancer it seemed to produce a zero output when there was no error.

...R

Robin2:
More interesting stuff. Thank you.

Welcome

I have taken a copy of your program for study.

Do you know if it will work (without any code changes) if the setpoint speed is controllable by the user.

this toggled between 2 speeds I have it fixed now at 2100 but when testing i would switch it between 800 and 2100 rpm
put drag on the shaft and more below 800 for my setup was less than what the motor would stay running.

  if ((unsigned long)(millis() - SpamTimer) >= 15000) {
    SpamTimer = millis();
    Setpoint = (Setpoint == 2100) ? 2100:2100;
  }

And what about if the load on the motor changes?

The PID is quite responsive and loads would power up the motor to 100% quite quickly

************ Note about my (1 line) blink without delay() timer ************

for (static long QTimer = millis(); (long)( millis() - QTimer ) >= 100; QTimer = millis() ) {  // one line Spam Delay at 100 miliseconds
// stuff
}
// translates to 
static long QTimer = millis(); 
if((long)( millis() - QTimer ) >= 100){
 QTimer = millis();
// stuff
}

I was scolded a while back on this forum for suggesting this by AARG who thinks this is bad coding because it is confusing. ( Sorry if your were confused about this...) :slight_smile:


These lines

   myPID.Compute();

analogWrite(PWMpin, Output);



suggest that the compute function produces a non-zero value when there is no error. But in your robot-balancer it seemed to produce a zero output when there was no error.

...R

Yes and I have thought about this I make a change to the PID_v2 which allows us to prime the integral portion of the PID
PrimeIntegral(double DesiredOutput)
if the motor is stopped you can prime the integral to a starting point high enough to cause the motor to run and start receiving RPM data allowing for additional adjustments to take place. I haven't tested this yet I may need a day or so but here's the altered PID_v2 code

PID_v2.cpp (9.71 KB)

PID_v2.h (3.16 KB)

Many thanks @zhomeslice. More food for thought - but I won't get much chance to think about it for a couple of days.

...R

Robin2:
Many thanks @zhomeslice. More food for thought - but I won't get much chance to think about it for a couple of days.

...R

Thanks Robin2 you gave me an idea that improved my PID code just added and tested the PrimeIntegral(X); function with excellent results Take a look:
Motor starts quickly and seamlessly with this easy startup sequence:

  myPID.SetMode(AUTOMATIC);
  myPID.PrimeIntegral(100);
  myPID.Compute();
  analogWrite(PWMpin, Output);
  delay(100);

Just made some basic changes to the encoder code and WOW with a more powerful 12V 2 amp supply I got these readings with the setpoint at 7100 RPM! My calculations are crazy! With my encoder at 400 pulses per revolution at 118.3 revolutions per second we are counting 47300+ pulses per second! :slight_smile: and it maintained setpoint!

Counts: 1228 DeltaT: 25992 PulseCtr: 1 Average: 7063.7 Calculated RPM: 7086.8 Output: 230.63
Counts: 1233 DeltaT: 26072 PulseCtr: 1 Average: 7069.4 Calculated RPM: 7093.8 Output: 228.89
Counts: 1238 DeltaT: 26176 PulseCtr: 1 Average: 7073.7 Calculated RPM: 7094.3 Output: 229.90
Counts: 1243 DeltaT: 26256 PulseCtr: 1 Average: 7077.3 Calculated RPM: 7101.2 Output: 231.29
Counts: 1247 DeltaT: 26376 PulseCtr: 1 Average: 7079.7 Calculated RPM: 7091.7 Output: 229.96
Counts: 1251 DeltaT: 26460 PulseCtr: 1 Average: 7082.1 Calculated RPM: 7091.8 Output: 230.47
Counts: 1255 DeltaT: 26584 PulseCtr: 1 Average: 7083.8 Calculated RPM: 7081.3 Output: 231.25
Counts: 1259 DeltaT: 26592 PulseCtr: 1 Average: 7085.5 Calculated RPM: 7101.8 Output: 230.07
Counts: 1263 DeltaT: 26620 PulseCtr: 1 Average: 7087.6 Calculated RPM: 7116.8 Output: 229.21
Counts: 1267 DeltaT: 26744 PulseCtr: 1 Average: 7089.0 Calculated RPM: 7106.3 Output: 230.17
Counts: 1271 DeltaT: 26840 PulseCtr: 1 Average: 7090.5 Calculated RPM: 7103.2 Output: 230.67
Counts: 1275 DeltaT: 26876 PulseCtr: 1 Average: 7091.9 Calculated RPM: 7116.0 Output: 229.76
Counts: 1279 DeltaT: 27024 PulseCtr: 1 Average: 7093.3 Calculated RPM: 7099.2 Output: 228.02
Counts: 1283 DeltaT: 27080 PulseCtr: 1 Average: 7094.2 Calculated RPM: 7106.7 Output: 229.62
Counts: 1287 DeltaT: 27176 PulseCtr: 1 Average: 7095.0 Calculated RPM: 7103.7 Output: 229.99
Counts: 1291 DeltaT: 27260 PulseCtr: 1 Average: 7096.1 Calculated RPM: 7103.8 Output: 228.91
Counts: 1295 DeltaT: 27372 PulseCtr: 1 Average: 7096.8 Calculated RPM: 7096.7 Output: 228.51
Counts: 1299 DeltaT: 27396 PulseCtr: 1 Average: 7097.6 Calculated RPM: 7112.4 Output: 227.98
Counts: 1303 DeltaT: 27520 PulseCtr: 1 Average: 7098.0 Calculated RPM: 7102.1 Output: 227.44
Counts: 1307 DeltaT: 27584 PulseCtr: 1 Average: 7098.7 Calculated RPM: 7107.4 Output: 226.51
Counts: 1311 DeltaT: 27704 PulseCtr: 1 Average: 7099.2 Calculated RPM: 7098.3 Output: 228.89
Counts: 1315 DeltaT: 27792 PulseCtr: 1 Average: 7099.7 Calculated RPM: 7097.4 Output: 229.36
Counts: 1319 DeltaT: 27880 PulseCtr: 1 Average: 7100.5 Calculated RPM: 7096.5 Output: 231.05
Counts: 1323 DeltaT: 27896 PulseCtr: 1 Average: 7101.1 Calculated RPM: 7113.9 Output: 229.19
Counts: 1327 DeltaT: 27988 PulseCtr: 1 Average: 7102.1 Calculated RPM: 7112.0 Output: 226.99
Counts: 1331 DeltaT: 28104 PulseCtr: 1 Average: 7102.9 Calculated RPM: 7104.0 Output: 226.66
Counts: 1335 DeltaT: 28140 PulseCtr: 1 Average: 7103.5 Calculated RPM: 7116.2 Output: 229.33
Counts: 1339 DeltaT: 28180 PulseCtr: 1 Average: 7104.4 Calculated RPM: 7127.4 Output: 227.44
Counts: 1343 DeltaT: 28380 PulseCtr: 1 Average: 7104.7 Calculated RPM: 7098.3 Output: 226.42
Counts: 1347 DeltaT: 28396 PulseCtr: 1 Average: 7105.3 Calculated RPM: 7115.4 Output: 227.11
Counts: 1351 DeltaT: 28568 PulseCtr: 1 Average: 7105.8 Calculated RPM: 7093.6 Output: 225.77
Counts: 1355 DeltaT: 28548 PulseCtr: 1 Average: 7106.8 Calculated RPM: 7119.6 Output: 227.48
Counts: 1359 DeltaT: 28648 PulseCtr: 1 Average: 7107.3 Calculated RPM: 7115.7 Output: 225.20
Counts: 1363 DeltaT: 28680 PulseCtr: 1 Average: 7107.8 Calculated RPM: 7128.7 Output: 225.50
Counts: 1367 DeltaT: 28760 PulseCtr: 1 Average: 7108.8 Calculated RPM: 7129.7 Output: 224.62
Counts: 1371 DeltaT: 28540 PulseCtr: 1 Average: 7111.6 Calculated RPM: 7205.7 Output: 219.67
Counts: 1375 DeltaT: 28564 PulseCtr: 1 Average: 7115.9 Calculated RPM: 7220.6 Output: 216.95
Counts: 1379 DeltaT: 28668 PulseCtr: 1 Average: 7120.1 Calculated RPM: 7215.4 Output: 215.49
Counts: 1383 DeltaT: 28840 PulseCtr: 1 Average: 7123.9 Calculated RPM: 7193.1 Output: 214.21
Counts: 1387 DeltaT: 28984 PulseCtr: 1 Average: 7127.4 Calculated RPM: 7178.1 Output: 212.80
Counts: 1389 DeltaT: 29036 PulseCtr: 1 Average: 7130.5 Calculated RPM: 7175.6 Output: 211.83
Counts: 1389 DeltaT: 29084 PulseCtr: 1 Average: 7133.2 Calculated RPM: 7163.7 Output: 210.24
Counts: 1389 DeltaT: 29204 PulseCtr: 1 Average: 7135.0 Calculated RPM: 7134.3 Output: 211.66
Counts: 1389 DeltaT: 29864 PulseCtr: 1 Average: 7134.0 Calculated RPM: 6976.6 Output: 214.00
Counts: 1389 DeltaT: 29340 PulseCtr: 1 Average: 7132.1 Calculated RPM: 7101.2 Output: 212.06
Counts: 1389 DeltaT: 29116 PulseCtr: 1 Average: 7133.8 Calculated RPM: 7155.9 Output: 210.23
Counts: 1389 DeltaT: 29196 PulseCtr: 1 Average: 7135.6 Calculated RPM: 7136.3 Output: 210.87
Counts: 1389 DeltaT: 29192 PulseCtr: 1 Average: 7137.0 Calculated RPM: 7137.2 Output: 209.60
Counts: 1389 DeltaT: 29212 PulseCtr: 1 Average: 7138.0 Calculated RPM: 7132.3 Output: 209.63
Counts: 1389 DeltaT: 29232 PulseCtr: 1 Average: 7138.9 Calculated RPM: 7127.5 Output: 208.31
Counts: 1389 DeltaT: 29212 PulseCtr: 1 Average: 7139.7 Calculated RPM: 7132.3 Output: 208.11
Counts: 1389 DeltaT: 29248 PulseCtr: 1 Average: 7140.5 Calculated RPM: 7123.6 Output: 207.94
Counts: 1389 DeltaT: 29280 PulseCtr: 1 Average: 7140.8 Calculated RPM: 7115.8 Output: 208.50
Counts: 1389 DeltaT: 29276 PulseCtr: 1 Average: 7141.3 Calculated RPM: 7116.8 Output: 208.45
Counts: 1389 DeltaT: 29268 PulseCtr: 1 Average: 7141.6 Calculated RPM: 7118.7 Output: 208.43
Counts: 1389 DeltaT: 29268 PulseCtr: 1 Average: 7141.8 Calculated RPM: 7118.7 Output: 207.89
Counts: 1389 DeltaT: 29268 PulseCtr: 1 Average: 7141.8 Calculated RPM: 7118.7 Output: 207.15
Counts: 1389 DeltaT: 29348 PulseCtr: 1 Average: 7141.8 Calculated RPM: 7099.3 Output: 205.76
Counts: 1389 DeltaT: 29336 PulseCtr: 1 Average: 7141.3 Calculated RPM: 7102.2 Output: 206.15
Counts: 1389 DeltaT: 29328 PulseCtr: 1 Average: 7140.5 Calculated RPM: 7104.1 Output: 206.54
Counts: 1389 DeltaT: 29344 PulseCtr: 1 Average: 7137.6 Calculated RPM: 7100.3 Output: 207.15
Counts: 1389 DeltaT: 29324 PulseCtr: 1 Average: 7133.0 Calculated RPM: 7105.1 Output: 207.10
Counts: 1389 DeltaT: 29324 PulseCtr: 1 Average: 7128.5 Calculated RPM: 7105.1 Output: 207.52
Counts: 1389 DeltaT: 29336 PulseCtr: 1 Average: 7124.5 Calculated RPM: 7102.2 Output: 207.34
Counts: 1389 DeltaT: 29356 PulseCtr: 1 Average: 7120.8 Calculated RPM: 7097.4 Output: 207.80
Counts: 1389 DeltaT: 29376 PulseCtr: 1 Average: 7117.4 Calculated RPM: 7092.5 Output: 207.34
Counts: 1389 DeltaT: 29372 PulseCtr: 1 Average: 7114.6 Calculated RPM: 7093.5 Output: 207.44
Counts: 1389 DeltaT: 29336 PulseCtr: 1 Average: 7112.6 Calculated RPM: 7102.2 Output: 207.45
Counts: 1389 DeltaT: 29352 PulseCtr: 1 Average: 7113.7 Calculated RPM: 7098.3 Output: 206.96

Parts:
using UNO with rotary encoder LPD3806-400bw-g5-24c and small 12V dc brushed motor
connections:
only using 1 encoder wheel wire colors red +5V, black GND white to Pin2 with a 2K ohm resister between Red and White
output using PWM on pin 3 (pin 4 enables my H-Bridge) a simple mosfet switch would suffice for single direction testing
Attaching all files used:

PID_v2.cpp (9.71 KB)

PID_v2.h (3.16 KB)

Tachometer_Final_with_Self_Test.ino (5.4 KB)

Thanks again. I won't have time to study it today.

Here are a couple of ideas to give you something to think about.

First, something relatively trivial. I find that my motors don't always start at the same PWM so I have been thinking of a system that simply updates the value from zero in several steps if the motor has stalled (or stopped deliberately) then when pulses start being produced it uses them in the usual feedback mode.

I think it is not your basic design (so I presume you won't be offended) but it seems to me to be very lazy programming to use floats and doubles in a microprocessor. I am impressed by the performance you have posted but I suspect it would be much less trouble for the Arduino if all the PID calcs were done with integers. I wonder, with a bit of care with the maths, if it might even be possible to do it with 16 bit ints rather than 32 bit longs.

As a separate issue, one of my "problems" is that my present "encoder" only produces 1 pulse per revolution (ppr). I may be able to increase that to 3 ppr If I can find a suitable source of hex bar. I don't feel able to paint three (or even 2) spots on a round bar accurately at 180 deg spacing. Either way, I think this will make it harder to maintain an even speed because there is more time for acceleration (or deceleration) between speed measurements.

And, of course, I want my Arduino (Attiny1634) to run an nRF24 wireless transceiver as well as controlling the speed.

...R