This article provides libraries and examples code of controlling position and speed of DC motor using PID controller and auto-tuning.

Introduction

Have you ever heard about PID controller?

Refer to: Wikipedia Definition

It is a controller that is widely used in industrial to control various process variables such as temperature, pressure, force, feed rate, flow rate, chemical composition (component concentrations), weight, position, speed, and practically every other variable for which a measurement exists.

In this article, I focus on using PID to control position and speed of Brushed DC motor.

What decides performance of a PID controller?

Performance of PID is decided by Kp, Ki and Kd gain (coefficients for the proportional, integral, and derivative terms). Each system has its own optimal PID gain. Finding the best PID gains is difficult task. Therefore, we usually use an acceptable gain. This link give an illustration of impact of Kp, Ki and Kd on system

The process of finding value of these parameters is called "tuning". PID controller can manually tuning and auto-tuning...
• Manually tuning: This method requires experienced personnel
• Auto-tuning: the tuning is done by a software.
I implemented Auto-tuning library for position and speed of DC motor (see the source code) using Relay On/Off method. This code is written for PHPoC platform.

PID gain from auto-tuning is not the best gain. You can manually fine-tune based on PID gain from auto-tuning.

Thing used in this project

System Architecture of PID Controller PID is closed-loop system, we need a feedback from DC motor. Therefore, to use PID control, DC motor need to has an encoder.

Encoder will output the signal, which is used to calculated the real position and speed. The calculation of position and speed is performed by DC motor controller. DC motor sends the calculated value (called feedback value) back to Arduino. Library on Arduino will perform adjustment based on the feedback value, desired value, Kp, Ki and Kd gain, and staling factor. after adjusting, Arduino send command along with PWM duty-cycle value to DC motor controller, DC motor will output PWM signal to control DC motor. This process is repeated in a infinite loop.

Wiring
• Stack PHPoC Shield or PHPoC WiFi Shield on Arduino
• Stack DC motor controller PES-2604 on PHPoC Shield or PHPoC WiFi Shield
• Connect the terminal block of DC motor controller to DC motor and power supply for DC motor as follows:
• Terminal Block - VM <----> (+) wire - power supply for DC motor
• Terminal Block - GND <----> (-) wire - power supply for DC motor
• Terminal Block - M1+ <----> (+) wire - DC motor
• Terminal Block - M1- <----> (-) wire - DC motor
• (Optional) Connect the encoder port of DC motor controller to encoder pin of DC motor as follows:
• Encoder 1 - 5V <----> Vcc - Encoder of DC motor
• Encoder 1 - GND <----> GND - Encoder of DC motor
• Encoder 1 - 1A <----> A phase - Encoder of DC motor
• Encoder 1 - 1B <----> B phase - Encoder of DC motor

Encoder 1 is located on the upper row of encoder port. More detail is here Examples

Note that, different motor has different characteristics. You may need to adjust some parameters such as:
• Scaling factor: void setScaleFactor(float scaleFactor)
• PWM Period: void setPeriod(long period)
• Limit of integral: setIntegralLimit(long min, long max)
• Limit of integral Term: void setIntegralTermLimit(long min, long max)

Position Control

Source Code (you can also find this code on Arduino IDE -> File -> Examples -> Pid Controller -> DcPIDPosition)
Code:
```// PID Controller - Control Position of DC Motor Example
// Tutorial is available here: https://forum.phpoc.com/blogs/iot-lover/1289-arduino-pid-controller-auto-tuning-library-and-example-for-dc-motor

#include <Phpoc.h>
#include <PhpocExpansion.h>
#include <Pid.h>

DcMotorPID dcPid1(1, 1);
//DcMotorPID dcPid2(1, 2);

long targetPosition;
long position;
int count = 0;

void setup(){
Serial.begin(9600);
while(!Serial)
;

//Phpoc.begin(PF_LOG_SPI | PF_LOG_NET);
Phpoc.begin();
Expansion.begin(460800);

dcPid1.setPeriod(10000);
dcPid1.setEncoderPSR(64);
dcPid1.setDecay(0);
dcPid1.setFilterFrequency(5000);
dcPid1.setScaleFactor(16); // depending on motor, should test manually to have the best value
dcPid1.setIntegralLimit(-6000, 6000);
dcPid1.begin(PID_POSITION);

/*----------- check enc pol-------------- */
int pwm = 0; long position = 0;
while(!position)
{
pwm += 10;
dcPid1.setWidth(pwm);
position = dcPid1.getEncoderPosition();
delay(1);
}

if(position < 0)
dcPid1.setEncoderPolarity(-1);

dcPid1.setWidth(0);

//dcPid1.setGain(24.099171193725, 10.78947680748, 0.0617886);

/* PID auto-tuning */
dcPid1.beginTune();
while(!dcPid1.loopTune())
;

dcPid1.setEncoderPosition(0);

//Serial.print(F("PID TUNED Kp: ")); Serial.println(dcPid1.getKp());
//Serial.print(F("PID TUNED Ki: ")); Serial.println(dcPid1.getKi());
//Serial.print(F("PID TUNED Kd: ")); Serial.println(dcPid1.getKd());

Serial.print(F("Target_Position"));
Serial.print(" ");
Serial.println(F("Real_Position"));
}

void loop(){
if(count == 0)
targetPosition = 3000;
else if(count == 1000)
targetPosition = 4000;
else if(count == 2000)
targetPosition = -2000;

if(count < 3000) {
position = dcPid1.loop(targetPosition);

//position = dcPid1.getEncoderPosition();
Serial.print(targetPosition);
Serial.print(" ");
Serial.println(position);
count++;
}
}```
• Compile and upload code to Arduino
• Open Serial Monitor
• Wait until motor stops
• Copy the log in Serial Monitor, paste it to Excel, and plot it In the example, setpoint of position is changed. No overshoot occurs

Note that: Since the result is depended on characteristics of each DC motor, if the result is not so good, you can adjust other parameters such as Scaling Factor, Limit of integral, Limit of integral term, PWM Period.

Speed Control

Source Code (you can also find this code on Arduino IDE -> File -> Examples -> Pid Controller -> DcPIDSpeed)
Code:
```// PID Controller - Control Position of DC Motor Example
// Tutorial is available here: https://forum.phpoc.com/blogs/iot-lover/1289-arduino-pid-controller-auto-tuning-library-and-example-for-dc-motor

#include <Phpoc.h>
#include <PhpocExpansion.h>
#include <Pid.h>

DcMotorPID dcPid1(1, 1);
//DcMotorPID dcPid2(1, 2);

int targetSpeed;
int speed;
int count = 0;

void setup(){
Serial.begin(9600);
while(!Serial)
;

//Phpoc.begin(PF_LOG_SPI | PF_LOG_NET);
Phpoc.begin();
Expansion.begin(460800);

dcPid1.setPeriod(10000);
dcPid1.setEncoderPSR(64);
dcPid1.setDecay(0);
dcPid1.setFilterFrequency(5000);
dcPid1.setScaleFactor(8); // depending on motor, should test manually to have the best value
dcPid1.setIntegralLimit(-80000, 80000);
dcPid1.setIntegralTermLimit(-2000, 2000);
dcPid1.begin(PID_SPEED);

//dcPid1.setGain(1, 0.1, 0);

/* PID auto-tuning */
dcPid1.beginTune();
while(!dcPid1.loopTune())
;

//Serial.print(F("PID TUNED Kp: ")); Serial.println(dcPid1.getKp());
//Serial.print(F("PID TUNED Ki: ")); Serial.println(dcPid1.getKi());
//Serial.print(F("PID TUNED Kd: ")); Serial.println(dcPid1.getKd());

delay(2000);

Serial.print(F("Target Speed"));
Serial.print(" ");
Serial.println(F("Real Speed"));
}
}

void loop(){
if(count == 0)
targetSpeed = 3000;
else if(count == 1000)
targetSpeed = 5000;
else if(count == 2000)
targetSpeed = 1000;

if(count < 3000) {
speed = dcPid1.loop(targetSpeed);

//speed = dcPid1.getSpeed();
Serial.print(targetSpeed);
Serial.print(" ");
Serial.println(speed);
count++;
} else {
dcPid1.setWidth(0);
}
}```
• Compile and upload code to Arduino
• Open Serial Monitor
• Wait until motor stops
• Copy the log in Serial Monitor, paste it to Excel, and plot it In the example, setpoint of speed is changed.

Note that: Since the result is depended on characteristics of each DC motor, if the result is not so good, you can adjust other parameters such as Scaling Factor, Limit of integral, Limit of integral term, PWM Period.

Library Code

PID_Controller.zip
{"ft_lang_label":"__label__en","ft_lang_prob":0.64525664,"math_prob":0.71081394,"size":8240,"snap":"2022-40-2023-06","text_gpt3_token_len":2149,"char_repetition_ratio":0.1531083,"word_repetition_ratio":0.30060294,"special_character_ratio":0.28373787,"punctuation_ratio":0.19243313,"nsfw_num_words":0,"has_unicode_error":false,"math_prob_llama3":0.98353577,"pos_list":[0,1,2,3,4,5,6,7,8],"im_url_duplicate_count":[null,null,null,6,null,null,null,null,null],"WARC_HEADER":"{\"WARC-Type\":\"response\",\"WARC-Date\":\"2023-02-05T07:44:33Z\",\"WARC-Record-ID\":\"<urn:uuid:9b863d1a-6015-4514-9fca-41475ca9c27b>\",\"Content-Length\":\"64598\",\"Content-Type\":\"application/http; msgtype=response\",\"WARC-Warcinfo-ID\":\"<urn:uuid:07fb1645-58c3-4dbe-adc6-def1d6e9534a>\",\"WARC-Concurrent-To\":\"<urn:uuid:46161f27-9caa-46a5-a00c-28083bb2fa8e>\",\"WARC-IP-Address\":\"104.16.196.6\",\"WARC-Target-URI\":\"https://forum.phpoc.com/blogs/iot-lover/1289-arduino-pid-controller-auto-tuning-library-and-example-for-dc-motor\",\"WARC-Payload-Digest\":\"sha1:ULT6SHV3RPYEVE4AV4WD3CL6HHHYD5NF\",\"WARC-Block-Digest\":\"sha1:INK5QQGYQKOKUG2UFKE233UPGHMNCDQD\",\"WARC-Identified-Payload-Type\":\"application/xhtml+xml\",\"warc_filename\":\"/cc_download/warc_2023/CC-MAIN-2023-06/CC-MAIN-2023-06_segments_1674764500250.51_warc_CC-MAIN-20230205063441-20230205093441-00232.warc.gz\"}"}