I was trying to apply PI controller to a motor using Arduino. I have somewhat achieved the task but the problem is that it oscillates too much. Also i couldn't figure out what to do when pwm signal are calculated as negative

  int counter = 0;
  double end_time=0;
  double ini_time=0;
  double rpm=0;
  double my_time=0;
  double temp = 0;
  int pid_speed=0;
  float kp=2.25;
  float ki=.01;
  float kd=0.005;
  int prev_error=0;
  double pwm_sig=0;
  int integrator  = 0;
  int motor_sig_pin= 5;
  int error=0;
  int absolute = 0;
  float desired_rpm=1000; //my desired RPM value
  double zero_time=0;
  double i_time=0;
  double f_time=0;
  int rpm_tacho=0;
  int previous_error=0;
  int derivative=0;

  void setup() {
    digitalWrite(10,LOW );
    attachInterrupt (0,Count,RISING);
    ini_time = micros();

   void Count(){ //calculating time for one round using encoder

      if (counter>=100){

  void loop() { 
      rpm = ((1000000*60)/(end_time));
      rpm = abs(rpm);
      rpm_tacho = ((1000000*60)/(zero_time));
      if (rpm <10 || rpm>5000){

      integrator += error;
      pid_speed = kp*error; + ki*integrator;


      Serial.print("       ");

      if (pid_speed> 0){
        if (pid_speed>255)
      Serial.print ("      ");
  • Just a few comments: 1) end_time is used both in normal context and in interrupt context, it should therefore be qualified as volatile. 2) In loop, as end_time is more than one byte, you should copy it to a local variable while blocking interrupts. 3) You are not using kd. 4) There is a ; before + ki*integrator, which means that the computation of the integral term is a no-op. Commented Jun 8, 2015 at 6:08
  • @EdgarBonet what do you mean by blocking the interrupt.
    – Adnan
    Commented Jun 8, 2015 at 7:06
  • noInterrupts(); double end_time_copy = end_time; interrupts();, then use the copy instead of the original. Otherwise Count() may update end_time while you are in the process of reading it. Commented Jun 8, 2015 at 7:23
  • 5) ini_time and end_time should be unsigned long or uint32_t, otherwise the tachometer will return garbage when micros() rolls-over to zero. 6) Then there is no point in abs(rpm), as rpm cannot be negative. 7) rpm_tacho is unused, and computed by dividing by zero. 8) You should properly indent your code before posting it. Commented Jun 8, 2015 at 7:42
  • 1
    constrain(). Or better yet, use a ready-made PID library. Commented Jun 8, 2015 at 12:03

2 Answers 2


Instead of thinking of the PID output as pid_speed, think of it as motor_power, and what the PI controller is doing is translating errors in rpm into a motor power setting.

You are probably getting oscillation because of integral windup. If your loop is fast relative to your system, each loop worth of error adds to integrator quickly/quicker than the system can adjust. For example, if a loop takes 1ms, an error of 100rpm could overflow the "int integrator" past 32768 in only 0.327 seconds, and past the threshold where the integrator could peg the output 25500 (=255/Ki) in only 0.255sec If the integrator is faster than the system, it can easily cause overshoot. You might consider slowing the loop down to 10ms, 100ms or 1000s to match the physical process and make the kI term be a sane conversion of the integrator's rpmErrorseconds/100, rpmErrorseconds/10, or rpmError*seconds into output power.

To handle the numerics and constraints better, like overflowing integrators and negative output values, it would probably be best to use the Arduino PID library from http://playground.arduino.cc/Code/PIDLibrary as in Edgar Bonet's comment.


I do not know if this question still exists or you managed to solve it. Regardless, I was (still am) working on a self balancing robot where I also faced such an issue and I came to this forum. The way I tackled it was to set PID outputs (assuming you are using Arduino 's PID library) 0 - 255 instead of -255 to 255. Now for my project I needed to change direction of the motor depending upon the inclination angle of the robot. So, each time I need to change the motor rotation direction, I do the following

  • I first reset the Iterm (or make it zero).
  • Change the direction of rotation.
  • Begin PID computation again.

Earlier I was also trying to achieve this by -255 to 255, but it was very jerky and I was not sure if that was the correct way to do it. With my current approach, I have managed to solve my problem.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.