rpm and PID values from DC motor (help required)

Hello,

This is a project of obtaining rpm and PID values from DC motor

I’m told by my professor that if I put my finger on the spinning DC motor, and the value of rpm changes then I should have the microcontroller (Arduino Uno) to apply PID to readjust the rpm back to its normal regular speed…

For example…

Like let’s say the rpm of a motor goes to 12000 rpm and then I add my finger to the dc motor and it reads 8000 rpm and so my professor told me to have PID applied so that even if I put my finger into the dc motor, the PID will take care and have the dc motor go back to original 12000 rpm.

how would I make this to be?

I wrote a pseudocode below to show what I mean…
if interruption on spinning dc motor occurs
then calculate PID on rpm

how would I make this happen?

thanks.

Btw, I attempted the code below

So this is a code was attempted to try to get the PID values, calculation from according to what I described above (as objective)
and I only get rpm values as result.

#include <Wire.h>
#include <PID_v1.h>

/////////////////
 //Define Variables we'll be connecting to
 double Setpoint, Input, Output;

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

 int potPin = 0;           // Analog in 0 connected to the potentiometer
 const int transistorPin = 9;    // connected to the base of the transistor
 int potValue = 0;         // value returned from the potentiometer

 int ledPin = 13;                // IR LED connected to digital pin 13
 volatile byte rpmcount;
 unsigned int rpm;
 unsigned long timeold;

 void rpm_fun()
  {
     //Each rotation, this interrupt function is run twice, so take that into consideration for 
    //calculating RPM
    //Update count
      rpmcount++;
  }

 void setup() {
   
   ///////////////////
  //initialize the variables we're linked to
  Input = analogRead(0);
  Setpoint = 100;

  //turn the PID on
  myPID.SetMode(AUTOMATIC);
  ///////////////////////
  
   // set  the transistor pin as output:
   pinMode(transistorPin, OUTPUT);
   
   // initialize the serial communication:
  Serial.begin(9600);
   
  attachInterrupt(0, rpm_fun, FALLING);

  //Turn on IR LED
  pinMode(ledPin, OUTPUT);
  digitalWrite(ledPin, HIGH);

  rpmcount = 0;
  rpm = 0;
  timeold = 0;

 }

 void loop() {
   
   //////////////
   Input = analogRead(0);
   myPID.Compute();
   analogWrite(3,Output);
   //Serial.println();
   /////////////////////
   
   // read the potentiometer:
   int sensorValue = analogRead(A0);
   // map the sensor value to a range from 0 - 255:
   int outputValue = map(sensorValue, 0, 1023, 0, 255);
   // use that to control the transistor:

   // read the potentiometer, convert it to 0 - 255:
   potValue = analogRead(potPin) / 4;
   // use that to control the transistor:
   analogWrite(9, potValue);
   
  // send the value of analog input 0:
  //Serial.println(analogRead(A0));
  // wait a bit for the analog-to-digital converter
  // to stabilize after the last reading:
  //delay(10);
   

   delay(1000);
   //Don't process interrupts during calculations
   detachInterrupt(0);
   //Note that this would be 60*1000/(millis() - timeold)*rpmcount if the interrupt
   //happened once per revolution instead of twice. Other multiples could be used
   //for multi-bladed propellers or fans
   rpm = 30*1000/(millis() - timeold)*rpmcount;
   timeold = millis();
   rpmcount = 0;

   //Print out result to lcd
   //Serial.clear();
   Serial.println("RPM=");
   Serial.println(rpm);

   //Restart the interrupt processing
   attachInterrupt(0, rpm_fun, FALLING);
   
 
 }

I appreciate you being open that this is a school project (many hide this fact behind the word URGENT !! ;)

Some remarks wrt your code:

at 12000 rpm = 500 rps the counter volatile byte rpmcount; can overflow quite fast as it can only count to 255 == ~0.25 seconds as ==> //Each rotation, this interrupt function is run twice, so take that into consideration for ).

If that is enough OK, otherwise you might increase it to unsigned long and just use the delta with the previous value.

Serial.begin(9600); ==> Serial.begin(115200); // use the highest baudrate to minimize communicating time

   // read the potentiometer:
   int sensorValue = analogRead(A0);
   // map the sensor value to a range from 0 - 255:
   int outputValue = map(sensorValue, 0, 1023, 0, 255);
   // use that to control the transistor:

   // read the potentiometer, convert it to 0 - 255:
   potValue = analogRead(potPin) / 4;

two different ways to map unto 0..255 (the latter is faster & prefered )

delay(1000); this will block all except interrupts so it will definitely overflow ...

AND you will not compensate the RPM for one whole long second which was your goal

==> have a look at the "blink with out delay" example in the tutorial section to let your code work without using delay's will improve the realtime behaviour

   rpm = 30*1000/(millis() - timeold)*rpmcount;
   timeold = millis();
   rpmcount = 0;

make that something like (to work with delta rpmcount) The value of millis() could have incremented...

   nointerrupts();
   unsigned long temp = rpmcount;
   unsigned long t = millis();   
   interrupts();
   
   rpm = (temp - rpmold) * 30 * 1000 / (t - timeold);  
   timeold = t;
   rpmold = temp;

finally don't do detach and reattach IRQ as it is not needed to, doing the math with a delta counter is easier (you allready did it for time!)

Succes,

^ thank you for your input. I was just using 12000 rpm as an example in general… like any rpm that is allowable but my main objective is to get the PID… . like if I had some rpm in general and I put my finger on to the DC motor that was spinning, my main objective was to have the DC motor apply PID values and have the PID values show up on the serial monitor and so from there, it can adjust the rpm of DC motor that was interrupted back to the original rpm (before I put my finger on the DC motor) …

if you can help me with that, that be great… I wrote a pseudocode in my previous post… . and I attempted the PID codes in part of the code (I uploaded in my first post) but doesn’t let me output the PID…

My main objective is to get the PID values when I apply outside interference on the DC motor … like when the speed of the DC motor changes significantly, that’s when I want the PID values to appear on serial monitor and from there, readjust the rpm of the DC motor back to the rpm (before I apply outside interference on the DC motor itself)

I got a permission to use the code for the PID from this website in addition to the previous code … Improving the Beginner’s PID – Introduction « Project Blog

but I still can’t seem to get output of PID values whenever I apply interruptions on the DC motor… here is the code below

#include <Wire.h>
#include <PID_v1.h>

/////////////////
 //Define Variables we'll be connecting to
 //double Setpoint, Input, Output;

 //Specify the links and initial tuning parameters
 //PID myPID(&Input, &Output, &Setpoint,2,5,1, DIRECT);
 /*working variables*/
 unsigned long lastTime;
 double Input, Output, Setpoint;
 double errSum, lastErr;
 double kp, ki, kd;
 /////////////////

 int potPin = 0;           // Analog in 0 connected to the potentiometer
 const int transistorPin = 9;    // connected to the base of the transistor
 int potValue = 0;         // value returned from the potentiometer

 int ledPin = 13;                // IR LED connected to digital pin 13
 volatile byte rpmcount;
 unsigned int rpm;
 unsigned long timeold;
 
 /////////////////////
 void Compute()
{
   /*How long since we last calculated*/
   unsigned long now = millis();
   double timeChange = (double)(now - lastTime);
 
   /*Compute all the working error variables*/
   double error = Setpoint - Input;
   errSum += (error * timeChange);
   double dErr = (error - lastErr) / timeChange;
 
   /*Compute PID Output*/
   Output = kp * error + ki * errSum + kd * dErr;
 
   /*Remember some variables for next time*/
   lastErr = error;
   lastTime = now;
   Serial.println(Output);
}
//////////////////////
void SetTunings(double Kp, double Ki, double Kd)
{
   kp = Kp;
   ki = Ki;
   kd = Kd;
}

 void rpm_fun()
  {
     //Each rotation, this interrupt function is run twice, so take that into consideration for 
    //calculating RPM
    //Update count
      rpmcount++;
  }

 void setup() {
   
   ///////////////////
  //initialize the variables we're linked to
  //Input = analogRead(0);
  //Setpoint = 100;

  //turn the PID on
  //myPID.SetMode(AUTOMATIC);
  ///////////////////////
  
   // set  the transistor pin as output:
   pinMode(transistorPin, OUTPUT);
   
   // initialize the serial communication:
  Serial.begin(9600);
   
  attachInterrupt(0, rpm_fun, FALLING);

  //Turn on IR LED
  pinMode(ledPin, OUTPUT);
  digitalWrite(ledPin, HIGH);

  rpmcount = 0;
  rpm = 0;
  timeold = 0;


 }

 void loop() {
   
   //////////////
   //Input = analogRead(0);
   //myPID.Compute();
   //analogWrite(3,Output);
   //Serial.println();
   /////////////////////
   
   // read the potentiometer:
   int sensorValue = analogRead(A0);
   // map the sensor value to a range from 0 - 255:
   int outputValue = map(sensorValue, 0, 1023, 0, 255);
   // use that to control the transistor:

   // read the potentiometer, convert it to 0 - 255:
   potValue = analogRead(potPin) / 4;
   // use that to control the transistor:
   analogWrite(9, potValue);
   
  // send the value of analog input 0:
  //Serial.println(analogRead(A0));
  // wait a bit for the analog-to-digital converter
  // to stabilize after the last reading:
  //delay(10);
   

   delay(1000);
   //Don't process interrupts during calculations
   detachInterrupt(0);
   //Note that this would be 60*1000/(millis() - timeold)*rpmcount if the interrupt
   //happened once per revolution instead of twice. Other multiples could be used
   //for multi-bladed propellers or fans
   rpm = 30*1000/(millis() - timeold)*rpmcount;
   timeold = millis();
   rpmcount = 0;

   //Print out result to lcd
   //Serial.clear();
   Serial.println("RPM=");
   Serial.println(rpm);

   //Restart the interrupt processing
   attachInterrupt(0, rpm_fun, FALLING);
   
   


 }

Also as I was researching today, I found this “pseudocode” or code… from this website A Big Magnet: Using a DC motor as a servo with PID control (part 1)

I’m wondering how could this be applied to my current code so that PID values can be calculated if I put physical interruption (massive change of rpm) of DC motor when that happens… ??

int val = analogRead(potPin); // read the potentiometer value (0 - 1023)
int target = map(val, 0, 1023, 0, 3600);// set the seek to target by mapping the potentiometer range to the encoder max count
int error = encoder0Pos - target; // find the error term = current position - target
// generalized PID formula
//correction = Kp * error + Kd * (error - prevError) + kI * (sum of errors)

// calculate a motor speed for the current conditions
int motorSpeed = KP * error;
motorSpeed += KD * (error - lastError);
motorSpeed += KI * (sumError);
// set the last and sumerrors for next loop iteration
lastError = error;
sumError += error;

I understand that you have got the basic concept of using a PID algorithm to maintain a constant motor speed. Your sketch would be configured with a target speed, read the actual speed from the motor and vary the PWM output signal to make the motor speed change to your target speed.

There is a PID library for the Arduino which implements the basic PID algorithm for you. After configuring it to specify the PID elements you give it an error signal (difference between actual and target RPM) and it gives you a control signal which you would convert to a PWM duty cycle. It seems that you're being asked to print out the duty cycle in real time, too. That's easy enough to do - it's just a variable in your sketch and printing it whenever you want is easy enough.

I'm new to microcontroller and this is the first time I'm working with Arduino Uno and even this whole PID and DC motor. I done some research ... I just thought if anyone had the time to write some basic code of PID on DC motor for me (I would appreciate that), it would help me start from somewhere..

seen this - http://arduino.cc/playground/Code/PIDLibrary - ?

^ yeah... I seen it... but and that's where I got some of the code in my first post in this thread... http://arduino.cc/playground/Code/PIDLibrary

I'm trying to combine the code that I have currently and the code from that website... to get PID values of DC motor.

so I rewrote the code and attempted it doing it again… by combining from the website http://arduino.cc/playground/Code/PIDLibrary and Improving the Beginner’s PID – Introduction « Project Blog

and this is what I got…

//-------------------------------
//modified with PID values
//-------------------------------

#include <Wire.h>
#include <PID_v1.h>



 int potPin = 0;           // Analog in 0 connected to the potentiometer
 const int transistorPin = 9;    // connected to the base of the transistor
 int potValue = 0;         // value returned from the potentiometer

 int ledPin = 13;                // IR LED connected to digital pin 13
 volatile byte rpmcount;
 unsigned int rpm;
 unsigned long timeold;

/////////////////////////////////
 unsigned long lastTime; 
 double desiredrpm, rpmPID;
 double errSum, lastErr;
 double KP, KI, KD; 
/////////////////////////////////

 void rpm_fun()
  {
     //Each rotation, this interrupt function is run twice, so take that into consideration for 
    //calculating RPM
    //Update count
      rpmcount++;
  }

 void setup() {

  
   // set  the transistor pin as output:
   pinMode(transistorPin, OUTPUT);
   
   // initialize the serial communication:
  Serial.begin(9600);
   
  attachInterrupt(0, rpm_fun, FALLING);

  //Turn on IR LED
  pinMode(ledPin, OUTPUT);
  digitalWrite(ledPin, HIGH);

  rpmcount = 0;
  rpm = 0;
  timeold = 0;

/////////////////////////
 desiredrpm = 6000;
 lastErr = 0; 
 lastTime = 0;
 KP = 0.5;
 KI = 200;
 KD = 10;
//////////////////////// 
 

 }

 void loop() {
   

   
   // read the potentiometer:
   int sensorValue = analogRead(A0);
   // map the sensor value to a range from 0 - 255:
   int outputValue = map(sensorValue, 0, 1023, 0, 255);
   // use that to control the transistor:

   // read the potentiometer, convert it to 0 - 255:
   potValue = analogRead(potPin) / 4;
   // use that to control the transistor:
   analogWrite(9, potValue);
   
  // send the value of analog input 0:
  //Serial.println(analogRead(A0));
  // wait a bit for the analog-to-digital converter
  // to stabilize after the last reading:
  //delay(10);
   

   delay(1000);
   //Don't process interrupts during calculations
   detachInterrupt(0);
   //Note that this would be 60*1000/(millis() - timeold)*rpmcount if the interrupt
   //happened once per revolution instead of twice. Other multiples could be used
   //for multi-bladed propellers or fans
   rpm = 30*1000/(millis() - timeold)*rpmcount;
   timeold = millis();
   rpmcount = 0;

  

   Serial.print("RPM = ");
   Serial.println(rpm);

   //Restart the interrupt processing
   //attachInterrupt(0, rpm_fun, FALLING);

//////////////////////////////
 double timeChange = (double)(timeold - lastTime);
 // compute error variables 
 double error = (double)(rpm - desiredrpm);
 errSum += (error*timeChange);
 double dErr = (error-lastErr)/timeChange;

 //compute PID output 

 rpmPID = (KP*error + KI*errSum + KD*dErr);

 lastErr = error;
 lastTime = timeold; 
 Serial.print("PID outputfor rpm = ");
 Serial.println(rpmPID); 
/////////////////////////////// 
   
  //Restart the interrupt processing
  attachInterrupt(0, rpm_fun, FALLING);

 }

so far no error and rpm is running BUT

I always get “-0.00” on the output for PID… is there something I’m doing wrong here?

help would be appreciated.