Arduino - Motor PID speed control

Saturday, December 31, 2016

With PID control, speed of motor can be archived exactly. This article mainly introduces about making program in Arduino Pro mini, program in Computer (Visual Studio) to control motor speed by PID algorithm.
Fig1. General connection

Arduino Pro mini is used to store motor control, PID algorithm, communicating with PC (through COM Port)
Computer will have HMI made by Visual Studio to communicate with Arduino. HMI will show motor speed graph, change motor speed setting
Fig2. HMI on Computer (made by Visual Studio)

Let's go detail:
Hardware needed:
1. Motor with Encoder
2. H-bridge PCB
3. Arduino Pro mini
4. UART PCB
5. Computer (with Visual Studio)

Step 1. Hardware connection
This article doesn't mention about detail connection, it is based on previous article which can be found at this link





Step 2. Code works at Arduino
void loop() {
  if (stringComplete) {
    // clear the string when COM receiving is completed
    mySt = "";  //note: in code below, mySt will not become blank, mySt is blank until '\n' is received
    stringComplete = false;
  }

  //receive command from Visual Studio
  if (mySt.substring(0,8) == "vs_start"){
    digitalWrite(pin_fwd,1);      //run motor run forward
    digitalWrite(pin_bwd,0);
    motor_start = true;
  }
  if (mySt.substring(0,7) == "vs_stop"){
    digitalWrite(pin_fwd,0);
    digitalWrite(pin_bwd,0);      //stop motor
    motor_start = false;
  }
  if (mySt.substring(0,12) == "vs_set_speed"){
    set_speed = mySt.substring(12,mySt.length()).toFloat();  //get string after set_speed
  }
  if (mySt.substring(0,5) == "vs_kp"){
    kp = mySt.substring(5,mySt.length()).toFloat(); //get string after vs_kp
  }
  if (mySt.substring(0,5) == "vs_ki"){
    ki = mySt.substring(5,mySt.length()).toFloat(); //get string after vs_ki
  }
  if (mySt.substring(0,5) == "vs_kd"){
    kd = mySt.substring(5,mySt.length()).toFloat(); //get string after vs_kd
  }
}

void detect_a() {
  encoder+=1; //increasing encoder at new pulse
  m_direction = digitalRead(pin_b); //read direction of motor
}
ISR(TIMER1_OVF_vect)        // interrupt service routine - tick every 0.1sec
{
  TCNT1 = timer1_counter;   // set timer
  pv_speed = 60.0*(encoder/200.0)/0.1;  //calculate motor speed, unit is rpm
  encoder=0;
  //print out speed
  if (Serial.available() <= 0) {
    Serial.print("speed");
    Serial.println(pv_speed);         //Print speed (rpm) value to Visual Studio
    }


  //PID program
  if (motor_start){
    e_speed = set_speed - pv_speed;
    pwm_pulse = e_speed*kp + e_speed_sum*ki + (e_speed - e_speed_pre)*kd;
    e_speed_pre = e_speed;  //save last (previous) error
    e_speed_sum += e_speed; //sum of error
    if (e_speed_sum >4000) e_speed_sum = 4000;
    if (e_speed_sum <-4000) e_speed_sum = -4000;
  }
  else{
    e_speed = 0;
    e_speed_pre = 0;
    e_speed_sum = 0;
    pwm_pulse = 0;
  }


  //update new speed
  if (pwm_pulse <255 & pwm_pulse >0){
    analogWrite(pin_pwm,pwm_pulse);  //set motor speed
  }
  else{
    if (pwm_pulse>255){
      analogWrite(pin_pwm,255);
    }
    else{
      analogWrite(pin_pwm,0);
    }
  }

}

At the beginning of program, it will receive command from Computer (start/stop motor, motor speed setting, kP, kI, kD gain of PID)
Next is void detect_a(): encoder sum calculation -> used for speed calcluation in Timer interrupt routine.
Timer interrupt routine ISR(TIMER1_OVF_vect): every 0.1 this program is called, content includes:
   (1) Calculate motor speed
   (2) Send motor speed to Computer
   (3) Calculate PWM pulse (base on PID algorithm)
   (4) Push result of PWM to H-brigde
The entire of code for Arduino Pro mini can be download at this link

Step 3. Code works at Computer
Visual Studio 2012 is used to make HMI program, in which:
   (1) Send speed setting to Arduino
   (2) Send PID gain (kP, kI, kD) to Arduino
   (3) Receive motor speed -> show on graph
Fig3. Visual studio program

The whole code of Visual Studio program can be downloaded at this link
To make a Visual Studio program -> see detail step at this article
In general, the code will have:
#pragma endregion
private: System::Void Form1_Load(System::Object^  sender, System::EventArgs^  e) {
serialPort1->Open();
timer1->Start();
mStr = "0";
i=300;
}
private: System::Void button1_Click(System::Object^  sender, System::EventArgs^  e) {
serialPort1->WriteLine("vs_set_speed"+textBox1->Text); //send set_speed to Arduino
serialPort1->WriteLine("vs_kp"+textBox2->Text); //send kP to Arduino
serialPort1->WriteLine("vs_ki"+textBox3->Text); //send kI to Arduino
serialPort1->WriteLine("vs_kd"+textBox4->Text); //send kD to Arduino
}
private: System::Void timer1_Tick(System::Object^  sender, System::EventArgs^  e) {
String^ length;
length=mStr->Length.ToString();
if(mStr->Substring(0,5)=="speed"){
speed=mStr->Substring(5,System::Convert::ToInt32(length)-6);
label1->Text=speed;
//print motor speed into Chart
this->chart1->Series["Series1"]->Points->AddXY(i,System::Convert::ToDouble(speed));
i++;
this->chart1->ChartAreas["ChartArea1"]->AxisX->Minimum=i-300; //shift x-axis
}
}
private: System::Void serialPort1_DataReceived(System::Object^  sender, System::IO::Ports::SerialDataReceivedEventArgs^  e) {
mStr=serialPort1->ReadLine();
}
private: System::Void button2_Click(System::Object^  sender, System::EventArgs^  e) {
serialPort1->WriteLine("vs_start"); //start motor
}
private: System::Void button3_Click(System::Object^  sender, System::EventArgs^  e) {
serialPort1->WriteLine("vs_stop"); //stop motor
}


Result:



1 comment

  1. Thanks, You wrote awesome, I have learn lots of things from your article. It's really helpful for any readers.
    Battery Operated Flow Meter

    ReplyDelete

 

Search This Blog

Most Reading

Google+ Followers