PID control with DC motor keep oscillating in target position

Hi, I am trying to do the project like this :

My big problem is my motor would oscillate at target position. I notice the motor can barely move when the pwm is below 45 and even stop to rotate, I set the pwm from 100 to 45 ,and the error from 48 to 1 ,respectively.when error=0, I set the pwm =0 directly. But the problem occurs ,the pwm oscillates bewteen 45&0, I dont know how to fix .

I set the sample time 1ms, and use the timer interrupt to call the pid function.my pid conrtol is base on Brett Beauregard's blog.

#include <TimerOne.h>
#define MotorR_I1     8
#define MotorR_I2     9 
#define MotorL_I3   10 
#define MotorL_I4    11  
#define MotorR_ENA    5 
#define MotorL_ENB    6  
#define initial_speedr 100
#define initial_speedl 100
#define encoder0PinA 2
#define encoder0PinB 3
volatile double rpmcount=0.0;
bool inAuto =true;
 
#define manual 0
#define automatic 1
int speed_left;
double Output, Setpoint=48.0, lastInput=48.0,kp=1.0,ki=0.01,kd=0.1,outMin=0,outMax=150;
double ITerm=0;
int SampleTime;
//unsigned long lastTime; 
 void setup() {
  Serial.begin(57600);
   pinMode(2,INPUT); 
  pinMode(3,INPUT);
  attachInterrupt(0,rpm_fun,CHANGE);
  attachInterrupt(1,rpm_fun2 ,CHANGE);
  pinMode(MotorR_I1,OUTPUT);
  pinMode(MotorR_I2,OUTPUT);
  pinMode(MotorL_I3,OUTPUT);
  pinMode(MotorL_I4,OUTPUT);
  pinMode(MotorR_ENA,OUTPUT);
  pinMode(MotorL_ENB,OUTPUT);
  
  analogWrite(MotorR_ENA,0);    
  analogWrite(MotorL_ENB,0);    
 Timer1.initialize(1000);
 Timer1.attachInterrupt( Compute_pid ); 

}
void Compute_pid()
{
   if(inAuto) {
   double error = Setpoint - rpmcount;
   
   ITerm+= (ki*error)*0.001;
   if(ITerm> outMax) ITerm= outMax;
   else if(ITerm< outMin) ITerm= outMin;
   double dInput = (rpmcount - lastInput)/ 0.001;
   
   if(error>=0)
   Output=( kp * error + ITerm - kd * dInput)+45;
   else  
   Output=-( kp * error + ITerm - kd * dInput)+45;
   //Serial.println ( ITerm );
   if(Output> outMax) Output = outMax;
   else if(Output < outMin) Output = outMin;
   
   lastInput=rpmcount;
   }

 }


void advance(int a,int speed_left)    
{  
  analogWrite(MotorL_ENB,speed_left);
  digitalWrite(MotorR_I1,LOW);  
  digitalWrite(MotorR_I2,HIGH);
  digitalWrite(MotorL_I3,LOW);   
  digitalWrite(MotorL_I4,HIGH);
  delay(a * 100);
}
void stopRL(int d)  
{   
    digitalWrite(MotorR_I1,HIGH);   
    digitalWrite(MotorR_I2,HIGH);
    digitalWrite(MotorL_I3,HIGH);   
    digitalWrite(MotorL_I4,HIGH);
    delay(d * 100);
} 
void back(int d,int speed_left)  
{   
    analogWrite(MotorL_ENB,speed_left);    
    digitalWrite(MotorR_I1,HIGH);   
    digitalWrite(MotorR_I2,LOW);
    digitalWrite(MotorL_I3,HIGH);   
    digitalWrite(MotorL_I4,LOW);
    delay(d * 100);
} 



void moveForward()
{ 
  //Serial.println(Output);
  speed_left=(int)Output;
Serial.print("speed_left: ");
Serial.println(Output);
  if((Setpoint-rpmcount)>0) 
  {setMode(1);
  advance(0,speed_left);

  }
  else if(Setpoint-rpmcount==0)
  {
  Output=0;
  setMode(0);
  }
  else 
  { setMode(1);
    back(0,speed_left);
  }


}


void setMode(int Mode)
{  
    bool newAuto = (automatic==Mode);
    if(newAuto && !inAuto)
    {  /*we just went from manual to auto*/
        Initialize();
    }
    inAuto = newAuto;
}
 
void Initialize()
{
   lastInput = rpmcount;
   ITerm = Output;
   if(ITerm> outMax) ITerm=outMax;
   else if(ITerm< outMin) ITerm= outMin;
}
void rpm_fun()
{ 
   // look for a low-to-high on channel A
  if (digitalRead(encoder0PinA) == HIGH) { 
    // check channel B to see which way encoder is turning
    if (digitalRead(encoder0PinB) == LOW) {  
      rpmcount = rpmcount- 1;         // CW
    } 
    else {
      rpmcount =rpmcount  + 1;         // CCW
    }
  }

  
  else   // must be a high-to-low edge on channel A                                       
  { 
    // check channel B to see which way encoder is turning  
    if (digitalRead(encoder0PinB) == HIGH) {   
      rpmcount = rpmcount - 1;          // CW
    } 
    else {
      rpmcount = rpmcount + 1;          // CCW
    }
  }
 //Serial.println (rpmcount, DEC);        
  
  
}
void rpm_fun2()
{ // look for a low-to-high on channel B
  if (digitalRead(encoder0PinB) == HIGH) {   
   // check channel A to see which way encoder is turning
    if (digitalRead(encoder0PinA) == HIGH) {  
      rpmcount = rpmcount-1;         // CW
    } 
    else {
      rpmcount = rpmcount+1;         // CCW
    }
  }
  // Look for a high-to-low on channel B
  else { 
    // check channel B to see which way encoder is turning  
    if (digitalRead(encoder0PinA) == LOW) {   
      rpmcount = rpmcount -1;          // CW
    } 
    else {
      rpmcount = rpmcount +  1;          // CCW
    }
  }
  //Serial.println ( rpmcount, DEC); 
}
void loop() {

moveForward();

}

DC motors are hard to control, in detail without a gear box of sufficient transmission ratio. A constant controlled speed is achievable, but stopping and starting suffers from the dead zone around zero current, gear box hysteresis, friction and load.

I'm not sure, but is the +45 correct in the negative case? Check whether -(... )+45 shouldn't read -(... +45), to skip the dead zone.

If so, should I use stepper motor instead of dc motor to do pid control?

But I saw many people in the internet that use pid control the dc motor with gearbox..

What do you want to control, motor speed or position? PID is fine for speed, critical for position.

A stepper motor doesn't deserve an encoder, you simply make it move the required number of steps. But it needs a "home" position indicator, and consumes more power in stopped state than while moving.

A car with constant speed ,follow a map like this ROBOTC: Intelligent Path Planning - YouTube

It stops 0.5s every grid, I want my car can do this, and go straightly,turn 90 degree.

Can it work if I use stepper motor?

If the only position "knowledge" is the rotation of the wheels I doubt if you will get the system to work because there will be some slippage between the wheels and the ground regardless of what sort of motor you use.

If you have some external position reference (for example detecting the grid lines, or using an accelerometer and compass) you can probably get the desired effect with a simple DC motor.

...R

Yeah, but I bought a hmc6352 compass,and it only has two axles, it would get wrong data if it is inclined,so I want to buy a 3 axle-compass,and see how it work...

yangkai:
and it only has two axles,

"axes". An axle is the thing that a wheel rotates on.

Maybe you can mount your 2-axes compass in a gimbal like they do on boats.

...R