How can I incorperate PI control to my existing sketch

I have put together a sketch which allows for me to control Brushed DC Motor with my Ardunio Uno and Arduino shield. Using the potentiometer the motor speed can be increased and reduced at will, however PI control has not achieved. I must be able to control the speed of the motor so that when a load is applied to it, it senses this and can adjust the speed accordingly allowing for it to maintain its speed regardless of the load. This must also be able to measure speed which I have achieved using an Opto Sensor.

I have seen many examples using PID control, but I am mainly focusing on PI control.

If any other information is required to help solve my question please contact me.

Kind regards

Lee

See attached Arduino file

/* This program is for demo of motor speed measurement.
  */

// A Opto sensor to pin 2
#define OptoSen 2
#define RedLED 6
#define WhiteLED 5
#define POT 2


// count needs to be a global variable. It is modified in the interrupt service
// procedure, so needs to be qualified as volatile.
volatile unsigned int count=0;
int potPin = A2;
int motorPin = 9;
int potValue = 0;
int motorValue = 0;


void setup(){
 pinMode(OptoSen, INPUT); // Used here to sense the wheel
 pinMode(RedLED, OUTPUT);
 pinMode(WhiteLED, OUTPUT);
 attachInterrupt(0, countup, RISING); // Make a rising signal from the opto-sensor
                                      // trigger a count up
 interrupts(); // Enable the interrupts
 Serial.begin(9600);  // Set up serial for monitoring
}

/* This is the interrupt service procedure. It increments a counter, that's
  pretty well all! Deciding what to do with the value is done in the main
  loop.
  */
void countup() {
 count++;
}


void loop(){
 
 boolean red=0;
 unsigned int c=0;
 unsigned int rpm=0;
 unsigned int oneSecondCount=0;
 unsigned int val=0;
 long int startTime,nowTime,elapsedTime; // For timing the loop

 
 while (c<2000){
   startTime=millis();
   digitalWrite(RedLED,red); // flash this to show life
   red=!red;
  
   // Wait till one second has elapsed
   elapsedTime=0; // reset
   while (elapsedTime <1000){
     // This bit happens for exactly 1 second
     val = analogRead(POT);
     val = val/4;
     analogWrite(WhiteLED,val);
     nowTime=millis();
     elapsedTime = nowTime - startTime;
                      } // End of one second loop
   oneSecondCount = count; // Store the counts in the last second
   count = 0;              // Reset count
       // Work out the rpm and display
   rpm=(oneSecondCount*60);   // Multiply by 60, for pulses per minute
   rpm=((rpm+5)/10)*10;   // Round to the nearest 10                             
   Serial.print(" Speed = ");
   Serial.print(rpm);
  
   c++; 
   
   
   potValue = analogRead(potPin);  
motorValue = map(potValue, 0, 1023, 0, 255);
analogWrite(motorPin, motorValue);  
Serial.print("rpm,  potentiometer = " );     
Serial.print(potValue);
Serial.print("\t motor = ");
Serial.println(motorValue);
delay(2);    
 // Keep doing this for 2000 seconds!
 }
}

[code]

MotorSpdMeasRefine.ino (2.27 KB)

Your current code has some blocking loops in it such as

    while (elapsedTime <1000){
      // This bit happens for exactly 1 second
      val = analogRead(POT);
      val = val/4;
      analogWrite(WhiteLED,val);
      nowTime=millis();
      elapsedTime = nowTime - startTime;
                       } // End of one second loop

Such blocking code is not conducive to responsiveness and would be the first thing that I would change.

"I have seen many examples using PID control, but I am mainly focusing on PI control."

Drop the derivative part and you've got PI control, but here in your code you've no feedback at all (but see note, below).

What are the elements of PI control?

First you need an error signal. Your pot establishes your setpoint, its value interpreted as angular velocity. Your opto gives you your motor's actual angular velocity. Scale both as appropriate and then subtract them to get your error term.

Start with the P part - proportional control: simply multiply the error by P's gain to generate a PWM value appropriate to your motor. What that gain is, is determined by the motor + load (the 'plant') response and other factors. Tune it appropriately. Some experimentation is in order.

The I part, which you also multiply by a gain term and then add to P to get your total 'motor command' value, is a bit trickier: integrators tend to saturate if the plant does not correct fast enough (consider a servo-controlled valve: once fully-open, the valve cannot be commanded to be 'more open'). Once your integrator does saturate, your feedback loop has lost control and, the longer it is saturated, the longer it will take to recover (called 'windup') unless you take proactive steps to reduce it (there are many ways of doing this; research it). Its gain term will also require hand-tuning. Generally, the slower your system responds (say, small motor/big load), the smaller the I-gain term.

Note: I might point out here that UKHeliBob is correct, your code blocks, though I realise that what you've shown here is just basically a timing loop, don't do this in your control loop. It will drive your integrator batshit crazy, among other things. Don't make your control loop wait. In real-time control systems this is an all-around bad idea. In PI control it practically guarantees integrator windup.

In the simplest general terms your steps are:

read pot (setpoint SP) and opto (measured value MV), scale appropriately then subtract them to get your error:
error = k1SP - k2MV,

where k1 and k2 are scale factors so that you're subtracting apples from apples, not cherries from watermelons. What those scale factors are depends on a number of things. Say, for instance, your pot reads 512; what does '512' correspond to in terms of desired rpm? What does 512 correspond to in terms of opto counts per unit of time (which you choose)? That depends on rpm and on how many slots are in your opto wheel, and so forth. Do all the heavy lifting in floating-point arithmetic as integer arithmetic will accumulate truncation errors.

Moving on, let:
GP be your proportional gain
GI be your integrator gain

Compute your your proportional term PT:
PT = GP*error

Compute your integral term IT:
IT = IT + GI*error,

or, preferably, sum the error independently and multiply the sum by GI. This will make your I-term much less sensitive to slight changes in I-gain and will stave-off (but not prevent) windup:
ERRSUM = ERRSUM + error
IT = GI*ERRSUM

Compute your motor command MC, (ie, your PWM value):
MC = k3*(PT + IT) ,

where k3 is a scale factor, as appropriate, to map its range to the PWM range 0-255.

PI (and PID) controllers have been around for ages and so there lots of ways of doing it besides what I've outlined here; this is just the simplest form.

Thanks for your help guys, I will begin to work on this information and see where it takes me, I am quite new to this so I am finding it awfully difficult to understand terminologies in some cases but I feel I am picking this up fairly quickly.

Thanks again

How ever I would like to ask, when I am establishing the term ‘error’ does this need to written in before the void setup and does it need to be defined so for example:

define error = set point - actual
error = set point - opto
or error = Pot - opto

or is it stated else where as:

signed int error = set point - actual

this is where I am confused

Thanks ever so much for your help so far.

From what has been said this is what I understand, however I do not know how to rewrite or implement these concepts into my sketch as that what I originally had was what I understood, I understand the principals of P, PI, PD and PID control, just dont know how or where to add the info into my sketch:

Sorry if this sounds stupid, I am very new to this…

//PI control

//P - Part, Proportional control
//I - Part Integral control, multiply by the gain term + P = total 'motor command' value
read() Pot (setrpm SR) 
read() Opto (Measured Value MV)
error = k1*SP - k2*MV //  k1 and k2 are scale factors determined by what the Pot reads and what the Opto reads which correspond with one an other

// Gain is determined by the motor + load (the 'plant') response
GP //Proportional Gain 
GI //Integrator Gain

Compute() PT = GP*error //proportional term
Compute() IT = GI*error // Integral term 
ERRSUM = ERRSUM + error
IT = GI*ERRSUM

Compute() //motor command MC (PWM value)
MC = k3*(PT + IT)  // k3 is another scale factor map range between 0-255





#define setrpm //Pot establishes setpoint interpreted as angular velocity





/* This program is for demo of motor speed measurement.
  */

// A Opto sensor to pin 2
#define OptoSen 2 //Opto motors actual angular velocity
#define RedLED 6
#define WhiteLED 5
#define POT 2





// count needs to be a global variable. It is modified in the interrupt service
// procedure, so needs to be qualified as volatile.
volatile unsigned int count=0;
int potPin = A2;
int motorPin = 9;
int potValue = 0;
int motorValue = 0;


void setup(){
 pinMode(OptoSen, INPUT); // Used here to sense the wheel
 pinMode(RedLED, OUTPUT);
 pinMode(WhiteLED, OUTPUT);
 attachInterrupt(0, countup, RISING); // Make a rising signal from the opto-sensor
                                      // trigger a count up
 interrupts(); // Enable the interrupts
 Serial.begin(9600);  // Set up serial for monitoring
}

/* This is the interrupt service procedure. It increments a counter, that's
  pretty well all! Deciding what to do with the value is done in the main
  loop.
  */
void countup() {
 count++;
}


void loop(){
 
 boolean red=0;
 unsigned int c=0;
 unsigned int rpm=0;
 unsigned int oneSecondCount=0;
 unsigned int val=0;
 long int startTime,nowTime,elapsedTime; // For timing the loop

 //this part of the coding blocks loops, this is a basic timing loop - but a control loop is needed.
 // dont make control loop 'wait'
 //PI control guarantees integrator windup
 
 while (c<2000){
   startTime=millis();
   digitalWrite(RedLED,red); // flash this to show life
   red=!red;
  
   // Wait till one second has elapsed
   elapsedTime=0; // reset
   while (elapsedTime <1000){
     // This bit happens for exactly 1 second
     val = analogRead(POT);
     val = val/4;
     analogWrite(WhiteLED,val);
     nowTime=millis();
     elapsedTime = nowTime - startTime;
                      } // End of one second loop
   oneSecondCount = count; // Store the counts in the last second
   count = 0;              // Reset count
       // Work out the rpm and display
   rpm=(oneSecondCount*60);   // Multiply by 60, for pulses per minute
   rpm=((rpm+5)/10)*10;   // Round to the nearest 10                             
   Serial.print(" Speed = ");
   Serial.print(rpm);
  
   c++; 
   
   
   potValue = analogRead(potPin);  
motorValue = map(potValue, 0, 1023, 0, 255);
analogWrite(motorPin, motorValue);  
Serial.print("rpm,  potentiometer = " );     
Serial.print(potValue);
Serial.print("\t motor = ");
Serial.println(motorValue);
delay(2);    
 // Keep doing this for 2000 seconds!
 }
}
[code]

Please edit your posts and add code tags ( “</>” button).