dc motor control with rotary encoder feedback

I am trying to control a dc brushed motor using this speed control, https://www.motiondynamics.com.au/12v-48v-dc-speed-control-single-direction-25a-pcb-model.html

I am having trouble with the motor pulsing at the same frequency as the time between interupts. ie, 1 sec in the attached sketch. I know it's the same frequency as the serial write, but I can comment that out and the pulsing continues.

(Pulsing is an audible tick which visibly causes a momentary reduction in rpm).

I began this project by controlling the pwm out directly through comparing actual rpm with required rpm and adjusting pwm out accordingly but I had erratic results and so switched to PID control as per attached. In both cases though, the interrupt caused a pulsing of the motor.

Using a slot type IR encoder, (lm393) to read rpm, I am trying to control the speed of the motor to a set rpm which will eventually be sent from a remote arduino. But for now, I am trying to get reliable and stable speed control.

I have a Uno with lm393 module (digital output) with 220 ohm resistor connected to pin D2. The hall effect input of the speed controller is connected to D6 and neg. Power for Uno is current via usb while power for motor is via lipo connected to speed controller.

Any idea how to stop the pulsing?

//PID controlled pwm output.
#include <PID_v1.h>

double Setpoint, Input, Output;

//Specify the links and initial tuning parameters
PID myPID(&Input, &Output, &Setpoint, 2,5,1
, DIRECT);

//RPM stuff

int speedSensor = 2;
unsigned int rpm; // rpm reading
volatile byte pulses; // number of pulses
unsigned long timeold;
unsigned long timeold2;
// number of pulses per revolution
// based on your encoder disc
unsigned int pulsesperturn = 18;
void counter()
{
  //Update count
  pulses++;
}


void setup() {
  // put your setup code here, to run once:
 Serial.begin(9600);

 pinMode(speedSensor, INPUT);
 
  analogWrite(6, 0); //Sets esc to off at start up.

  //Triggers on Falling Edge (change from HIGH to LOW)
  attachInterrupt(0, counter, RISING);
  // Initialize
  pulses = 0;
  rpm = 0;
  timeold = 0;
  timeold2 = 0;

  
//initialize the variables we're linked to for PID
  Input = rpm;
  Setpoint = 30;

  //turn the PID on
  myPID.SetMode(AUTOMATIC);
  
  Serial.println("wifi_seeder_PID");
  delay(1000);
}

void loop() {
  // put your main code here, to run repeatedly:

  //Read sensor and compute rpm
   if (millis() - timeold >= 1000) {
    //Don't process interrupts during calculations*/
    noInterrupts();
    //rpm = ((60 * 500 / pulsesperturn ) / (millis() - timeold) * pulses);
    //rpm=(pulses/pulsesperturn);
    rpm=pulses*((millis() - timeold)/1000);
    timeold = millis();
    pulses = 0;
    interrupts();
     Serial.print("RPM = ");
    Serial.print(rpm, DEC);
   }
    //Restart the interrupt processing

    //Process pwm out via PID.
 Input = rpm;
  myPID.Compute();
  analogWrite(6,Output);
  // }

    //Print rpm & pwm out.
 if (millis() - timeold2 >= 1000) {
 
  Serial.print("  Output ");
  Serial.print(Output);
  Serial.print("  Pulses ");
  Serial.println(pulses);
  timeold2 = millis();
 }
 //delay(1000);
}

Any idea how to stop the pulsing?

//PID controlled pwm output.
#include <PID_v1.h>

double Setpoint, Input, Output;

//Specify the links and initial tuning parameters
PID myPID(&Input, &Output, &Setpoint, 2,5,1
, DIRECT);

//RPM stuff

int speedSensor = 2;
unsigned int rpm; // rpm reading
volatile byte pulses; // number of pulses
unsigned long timeold;
unsigned long timeold2;
// number of pulses per revolution
// based on your encoder disc
unsigned int pulsesperturn = 18;
void counter()
{
  //Update count
  pulses++;
}


void setup() {
  // put your setup code here, to run once:
 Serial.begin(9600);

 pinMode(speedSensor, INPUT);
 
  analogWrite(6, 0); //Sets esc to off at start up.

  //Triggers on Falling Edge (change from HIGH to LOW)
  attachInterrupt(0, counter, RISING);
  // Initialize
  pulses = 0;
  rpm = 0;
  timeold = 0;
  timeold2 = 0;

  
//initialize the variables we're linked to for PID
  Input = rpm;
  Setpoint = 30;

  //turn the PID on
  myPID.SetMode(AUTOMATIC);
  
  Serial.println("wifi_seeder_PID");
  delay(1000);
}

void loop() {
  // put your main code here, to run repeatedly:

  //Read sensor and compute rpm
   if (millis() - timeold >= 1000) {
    //Don't process interrupts during calculations*/
    noInterrupts();
    //rpm = ((60 * 500 / pulsesperturn ) / (millis() - timeold) * pulses);
    //rpm=(pulses/pulsesperturn);
    rpm=pulses*((millis() - timeold)/1000);
    timeold = millis();
    pulses = 0;
    interrupts();
     Serial.print("RPM = ");
    Serial.print(rpm, DEC);
   }
    //Restart the interrupt processing

    //Process pwm out via PID.
 Input = rpm;
  myPID.Compute();
  analogWrite(6,Output);
  // }

    //Print rpm & pwm out.
 if (millis() - timeold2 >= 1000) {
 
  Serial.print("  Output ");
  Serial.print(Output);
  Serial.print("  Pulses ");
  Serial.println(pulses);
  timeold2 = millis();
 }
 //delay(1000);
}

Hi,

Can you please post a copy of your circuit, in CAD or a picture of a hand drawn circuit in jpg, png?

Can you post link to encoder and motor specs please.

Thanks.. Tom... :slight_smile:

    rpm=pulses*((millis() - timeold)/1000);

You are doing integer division, so the result being multiplied by pulses is 1 or perhaps 2
if delayed, not a float.

Change the 1000 to 1000.0

The pulsing is because the rpm value changes every second, so the ouput will change every
second (due to P and D terms). Do you understand about tuning a PID?

Wiring diagram.

Motor is an auto windowwinding motor for testing purposes. Geared output to approx 100rpm max. No current draw available but certainly not 25 amp.

LM393 module from fleabay. Here.

Added 1000.0, thanks. no difference.

I realise the rpm and resulting pwm change every second, but the motor is virtually stopping for a split second between changes.

Not sure what happened with the double posting of code. sorry!

I've been trying a few methods to get reliable rpm control but it iludes me. :frowning:
I've been working on interrupt free rpm measurement and PID free output but with the attached sketch, the pwmOut just keeps climbing by 1 until 255, then starts again. I've spent days on this project and am no further along.

I need help!

//PID controlled pwm output.
#include <PID_v1.h>

double Setpoint, Input, Output;

//Specify the links and initial tuning parameters
PID myPID(&Input, &Output, &Setpoint, 0.5, 0.1, 1
          , DIRECT);

const int Pin = 2; // Photo Interrupter. http://www.sparkfun.com/products/9299
const int ledPin =  9; //LED pin on the arduino ethernet, to help visualize the notch passing by

int photoState = 0;
long RPM = 0;
unsigned long timerrevCalc = 0;
unsigned long duration;


byte pwmOut = 0;

void setup() {
  Serial.begin(38400); // had problems with 9600...
  pinMode(ledPin, OUTPUT);
  pinMode(Pin, INPUT);
  analogWrite(6, 0);

  //initialize the variables we're linked to for PID
  Input = RPM;
  Setpoint = 50;

  //turn the PID on
  myPID.SetMode(AUTOMATIC);
 pwmOut = 50;
}

void loop() {
 int setRpm = 50;
  //analogWrite(6, 150);
  duration = pulseIn(Pin, HIGH, 1000000); //times the amount of microseconds the notch is exposing the IR, Times out after 100000 uS. Raise the timeout for slower RPM readings.
  timerrevCalc = duration * 21.318; //See above
  RPM = (60000000 / timerrevCalc) ; //See above
  /*
    // Pwm out set as difference between setRpm & rpm.
    pwmOut = pwmOut*(setRpm/RPM);
    analogWrite(6, pwmOut);

    //Process pwm out via PID.
    Input = RPM;
    myPID.Compute();
    analogWrite(6,Output);
  */
//for (int i=0; i<20000; i++)
//{
  if (RPM < setRpm) 
  {
    pwmOut = pwmOut+1;
  }
  else if (RPM > pwmOut) 
  {
    pwmOut = pwmOut-1;
  }
  /*else
  {
    pwmOut = pwmOut;
  }*/
//}
  analogWrite(6, pwmOut);

  Serial.print("duration  "); //print out results.
  Serial.print(duration);
  Serial.print("  ");
  Serial.print("   RPM"  );
  Serial.print(RPM);
  Serial.print("   pwmOut  "  );
  Serial.println(pwmOut);
  

}

You have a whole 1 second latency in your speed input to the PID loop - latency is the enemy of stability, you'll
have to address this first.

MarkT:
You have a whole 1 second latency in your speed input to the PID loop - latency is the enemy of stability, you'll
have to address this first.

Thanks Mark. Was beginning to lose hope for an answer. :slight_smile:

I understand what you mean, but I don't know how to improve the latency. With the sensor on a geared shaft that I wish to control at as low as 10rpm, and with 10 slots in the disc, = 0.6sec per pulse. The 1 sec timeout seems reasonable to me.

No, you don't get my point - you mustn't wait and count, you must time each pulse and
give immediate speed feedback to the PID. At low speeds you won't get decent control
with your sensor, it isn't possible.