# PID controller speed control for a DC motor with encoder

**URL:** <https://forum.arduino.cc/t/pid-controller-speed-control-for-a-dc-motor-with-encoder/985306>\
**Category:** Motors, Mechanics, Power and CNC\
**Created:** [April 26, 2022, 2:34pm UTC](https://forum.arduino.cc/t/pid-controller-speed-control-for-a-dc-motor-with-encoder/985306 "2022-04-26T14:34:35Z")\
**Posts on this page:** 5\
**Page:** 2

<div class="post-metadata">

**Author:** ![DaveX](https://dub1.discourse-cdn.com/arduino/user_avatar/forum.arduino.cc/davex/32/751443_2.png) [@DaveX](https://forum.arduino.cc/u/DaveX)\
**Post date:** [May 1, 2022, 1:59am UTC](https://forum.arduino.cc/t/pid-controller-speed-control-for-a-dc-motor-with-encoder/985306/21 "2022-05-01T01:59:17Z")

</div>

What is your code? You could back out from the 164.xx that is being printed as Input to whatever is causing it.

---

<div class="post-metadata">

**Author:** ![a.az](https://avatars.discourse-cdn.com/v4/letter/a/6bbea6/32.png) [@a.az](https://forum.arduino.cc/u/a.az)\
**Post date:** [May 1, 2022, 5:26am UTC](https://forum.arduino.cc/t/pid-controller-speed-control-for-a-dc-motor-with-encoder/985306/22 "2022-05-01T05:26:43Z")

</div>

code is like [PID controller speed control for a DC motor with encoder - #15 by zhomeslice](https://forum.arduino.cc/t/pid-controller-speed-control-for-a-dc-motor-with-encoder/985306/15)

```cpp
// 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 = 100;// 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 = 0, consKi = 0.1, consKd = 0;
double Setpoint = 150;// RPM 
#define PWMpin 12
#define interruptPin 2
#define HBridgeEnable pinMode(10, OUTPUT);digitalWrite(10, 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();

  }
}

```

---

<div class="post-metadata">

**Author:** ![DaveX](https://dub1.discourse-cdn.com/arduino/user_avatar/forum.arduino.cc/davex/32/751443_2.png) [@DaveX](https://forum.arduino.cc/u/DaveX)\
**Post date:** [May 1, 2022, 6:22am UTC](https://forum.arduino.cc/t/pid-controller-speed-control-for-a-dc-motor-with-encoder/985306/23 "2022-05-01T06:22:31Z")

</div>

My guess is that `readRPM()` doesn't calculate RPM during a stall:

> [@a.az](#):
>
> ```arduino
> 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).
> 
> ```

During a stall it should be possible to upper-bound the speed using the time since the start of the sketch or time since the last encoder pulse. It's not immediately clear what the routine does when stalled.

---

<div class="post-metadata">

**Author:** ![zhomeslice](https://dub1.discourse-cdn.com/arduino/user_avatar/forum.arduino.cc/zhomeslice/32/217972_2.png) [@zhomeslice](https://forum.arduino.cc/u/zhomeslice)\
**Post date:** [May 2, 2022, 6:42am UTC](https://forum.arduino.cc/t/pid-controller-speed-control-for-a-dc-motor-with-encoder/985306/24 "2022-05-02T06:42:30Z")

</div>

> [@a.az](#):
>
> `unsigned long StallTimer = SampleDuration *3;`

My code guessed that a good indication of a stall was 3 times the sample duration.  
if no pulses were detected within 3 sample duration then we need to trigger the stall indication

change your SampleDuration time to something larger say 100ms  
`int SampleDuration = 100; // in Milliseconds -- `  
This may improve how the PID and Stall respond to your low RPM setpoint of 150

ZHomeSlice

---

<div class="post-metadata">

**Author:** ![system](https://dub1.discourse-cdn.com/arduino/user_avatar/forum.arduino.cc/system/32/1140315_2.png) [@system](https://forum.arduino.cc/u/system)\
**Post date:** [October 29, 2022, 6:42am UTC](https://forum.arduino.cc/t/pid-controller-speed-control-for-a-dc-motor-with-encoder/985306/25 "2022-10-29T06:42:44Z")

</div>

Closed on October 29, 2022, 6:42am UTC

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.

[Previous page](https://forum.arduino.cc/t/pid-controller-speed-control-for-a-dc-motor-with-encoder/985306.md?page=1)
