Sunteți pe pagina 1din 18

Arduino - Motor PID Speed Control

Introduction

With PID control, the speed of a motor can be archived exactly. This article mainly introduces making a
program for the Arduino Pro Mini on your computer (using Visual Studio) to control motor speed by a
PID algorithm.

The Arduino Pro Mini is used to store motor controls, PID algorithms, and to communicate with the PC
(through COM Port). The computer should have HMI made by Visual Studio to communicate with
Arduino. HMI will show motor speed graphs and change motor speed settings.

Step 1. Hardware connection

This article doesn't mention connection details; it is based on a previous article which can be found at
this link.
Step 2. Code with the 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 commands from the computer (start/stop motor; motor
speed settings; kP, kI, kD gain of PID). Next is void detect_a(): which is an encoder for sum
calculation used for speed calculation in the 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 the 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 downloaded at this link. (file
name => motor_speed.zip)

Step 3. Code on the computer

Visual Studio 2012 is used to make HMI programs, in which: (1) Send speed settings to Arduino (2)
Send PID gain (kP, kI, kD) to Arduino (3) Receive motor speed -> show on graph
The whole code for the Visual Studio program can be downloaded at this link. (file name
=windowsformsapplication3.zip)To make a Visual Studio program, see the detailed steps in 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
Video file = arduino motor pid speed control

Let's go into detail.

Hardware needed:
1. Motor with Encoder

2. H-bridge PCB

3. Arduino Pro Mini

4. UART PCB

5. Computer (with Visual Studio)

comments
Code snippet #1Arduino
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);
}
}
}

Comments

Recompiled your project in Visual Studio 2017 and update the SDK. Getting an overload error on line 4
below. System argument out of range exception: "Index and length must refer to a location within the
string.' New to visual studio 2017 and CPP.

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
sorry late reply, i guess you should learn programming Visual Studio and make this project gradually.
One of article for detail steps programming Visual Studio, please visit here
http://engineer2you.blogspot.com/2016/12/arduino-serial-communication-visual.html
Thanks

Previous article
Arduino - Motor speed control pwm

Friday, December 23, 2016


This article will show how to control motor speed by PWM

Hardware needed:
1. Motor with encoder
2. H-bridge L298N
3. Arduino pro mini

Connected them together:


Result bellow looks not perfect in shape, but it works well

How to connect them?

(1) Connect encoder to Arduino

This encoder has pulse A, B -> connect to Arduino, don't forget connect power for it. So there will be totally 4
wires connected from motor encoder to Arduino.

Arduino pin 2 <-> Encoder pulse A

Arduino pin 3 <-> Encoder pulse B

Arduino VCC <-> Encoder power

Arduino GND <-> Encoder GND


(2) Connect to H-bridge module
H-bridge normally is used to control motor speed, direction -> let's google it to see how it works. In this
article, H-bridge uses chip L298N.

This module will have direction signal (3 wires): 1 for "Forward", 1 for "Backward", 1 for "Enable".
Arduino will output signal to direction signal to control direction of motor (through "Forward",
"Backward" signal), to control speed of motor (through "Enable" signal):
Arduino pin 4 <-> H-bridge "forward"
Arduino pin 5 <-> H-bridge "backward"
Arduino pin 6 <-> H-bridge "Enable"
Of course the H-bridge PCB will have output to Motor, this will be connected to Motor. Also power
source for it, this power is used for chip L298N, also motor. This case, it is about 12VDC.
Note that, there is another cable: GND (ground) cable from Arduino -> it needs to be connected for
sending signal from Arduino to H-bridge PCB (this GND cable is used to make the base for signal from
Arduino)

Code works:
motor_speed (part 1)
const byte pin_a = 2; //for encoder pulse A
const byte pin_b = 3; //for encoder pulse B
const byte pin_fwd = 4; //for H-bridge: run motor forward
const byte pin_bwd = 5; //for H-bridge: run motor backward
const byte pin_pwm = 6; //for H-bridge: motor speed
int encoder = 0;
int m_direction = 0;
int sv_speed = 100; //this value is 0~255
double pv_speed = 0;
int timer1_counter; //for timer

void setup() {
pinMode(pin_a,INPUT_PULLUP);
pinMode(pin_b,INPUT_PULLUP);
pinMode(pin_fwd,OUTPUT);
pinMode(pin_bwd,OUTPUT);
pinMode(pin_pwm,OUTPUT);
attachInterrupt(digitalPinToInterrupt(pin_a), detect_a, RISING);
// start serial port at 9600 bps:
Serial.begin(9600);
//--------------------------timer setup
noInterrupts(); // disable all interrupts
TCCR1A = 0;
TCCR1B = 0;
timer1_counter = 34286; // preload timer 65536-16MHz/256/2Hz

TCNT1 = timer1_counter; // preload timer


TCCR1B |= (1 << CS12); // 256 prescaler
TIMSK1 |= (1 << TOIE1); // enable timer overflow interrupt
interrupts(); // enable all interrupts
//--------------------------timer setup

while (!Serial) {
; // wait for serial port to connect. Needed for native USB port only
}

In part 1, variables are declared. Next is set pulse A, pulse B of encoder as input; output for "forward",
"backward", "PWM" signal
Let's go inside meaning:
attachInterrupt(digitalPinToInterrupt(pin_a), detect_a, RISING);
This code is used to detect rising edge of pulse A encoder.
Encoder of motor is kind of disk, compacted with IR sensor, then output pulse A and B which is 90dg
oppositely. In case run forward, rising edge of pulse A, B will be at 0 logic. In case run backward, rising
edge of pulse A, B will be at 1 logic. By this, motor direction can be detected.
In the code, every time at rising edge of pulse A, an interrupt routine (name: detect_a()) will be called.
In part 2 of code, sum of encoder will be calculated, and direction of motor also detected.
Next of the code is declare about UART communication -> this will be used to print out result for User
Next is timer set up -> in sort, this timer is configured at 0.5sec -> means that every 0.5sec, timer1 is
ticked -> program will come to timer1 interrupt routine (in part 2). Timer1 is used to calculated motor
speed

motor_speed (part 2)
void loop() {
digitalWrite(pin_fwd,0); //run motor backward
digitalWrite(pin_bwd,1); //run motor backward
analogWrite(pin_pwm,sv_speed); //set motor speed
Serial.print("speed (rpm) is : ");
Serial.print(pv_speed); //Print speed value to Computer
Serial.print(" -- ");
Serial.print("Direction = ");
Serial.println(m_direction);
delay(500);
}

void detect_a() {
encoder+=1;
m_direction = digitalRead(pin_b);
}
ISR(TIMER1_OVF_vect) // interrupt service routine - tick every 0.5sec
{
TCNT1 = timer1_counter; // set timer
pv_speed = 60*(encoder/200.0)/0.5;
encoder=0;
}

Part 2 is continued of program.


At first, motor will be set to run backward. Then set PWM pulse to control motor speed (Google it to see
how PWM control speed):
analogWrite(pin_pwm,sv_speed);
In this case, PWM pulse width is controlled by variable sv_speed, now sv_speed=100 of 255 (max) ~
39%
Next of code is print result to UART port
Let's see 2 interrupt routine:
Encoder interrupt: as explain in part 1, every time at rising edge of encoder pulse A, this routine is called
-> sum encoder, and read motor direction
Interrupt timer1 routine: every 0.5s, this routine is called -> calculate motor speed. In this case, motor
encoder 1 revolution has 200 pulse.

Joint 2 parts of code. Compile it and download to Arduino -> Open serial port in Arduino IDE to see
result (Tool > Serial Monitor).
The total code can be downloaded here (Google share) (file name => motor_speed.zip)

Result:

At first, motor speed is 0 rpm (we turn off 12VDC power for H-bridge)
Then, 12VDC power is supplied for H-bridge, motor is running, speed is increasing
If motor shaft is touched (has some load), motor speed will decreased.

++++++++++
Arduino - serial communication Visual Studio
Sunday, December 18, 2016
This article will show how to make serial communication between Arduino and Visual Studio C++ through COM
(UART) port
(1) Arduino will sent data to Visual Studio program:

(2) Visual Studio program will send data to Arduino. Arduino received it, then feed it back to Visual Studio
program

Overview steps:
1. Hardware connection
2. Arduino program
3. Visual Studio program

1. Hardware connection
2. Arduino program
Make a program for Arduino. At start up, Arduino will send a string (a sentence) in every 0.5sec. This
will stop until User send data (string) to Arduino, then Arduino will send it back to User. In this case,
User is Visual Studio program (in part 3)

The code can be download at here - Google share (serialcallresponse_rv1.zip)


Arduino program
String mySt;
char myChar = 0;
int i=0;

void setup() {
// start serial port at 9600 bps:
Serial.begin(9600);
while (!Serial) {
; // wait for serial port to connect. Needed for native USB port only
}

establishContact(); // send a byte to establish contact until receiver responds


}

void loop() {
if (Serial.available() > 0) {
myChar = Serial.read();
mySt +=myChar; //receive string from Computer
}
if ((mySt.length() >0)&(!Serial.available())) {
Serial.print(mySt); //Print received string to Computer
mySt="";
}
}
void establishContact() {
while (Serial.available() <= 0) {
Serial.print("Arduino send: ");
Serial.println(i); //Print increasing value to Computer
i+=1;
delay(500);
}
}

3. Visual Studio porgram


Make a window application -> save it

Add button, textbox, label to the Form( taken from Toolbox in the left):

Click on button, textbox, label to see Properties on Toolbox in the right. Remember the name of each
item for program in latter section.
Add component "serial Port" and "timer"

Also see properties to know the name each item. Remember to rename "serialPort1" -> "Portname" to
COM-Port of Arduino (this case is COM4)

Programming works:
The whole code of Visual Studio program can be downloaded at here - Google
share(windowsformsapplication3.zip)
(1) Double click to Form1 -> input following code.
Meaning: open (Arduino) COM-Port
start timer1
Form1 code
private: System::Void Form1_Load(System::Object^ sender, System::EventArgs^ e) {
serialPort1->Open();
timer1->Start();
}

(2) Double click to Button1 -> input following code


Meaning: send string from textBox1 to (Arduino) COM-Port
Send data code
private: System::Void button1_Click(System::Object^ sender, System::EventArgs^ e) {
serialPort1->WriteLine(textBox1->Text);
}

(3) Double click to timer1 -> input following code


Meaning: every time timer1 is tick (it will tick every 0.1s -> setting in "Interval" of Properties of
timer1), label1 will update information.
timer code
private: System::Void timer1_Tick(System::Object^ sender, System::EventArgs^ e) {
label1->Text=mStr;
}

(4) Click to serialPort1 -> see at Properties toolbox -> click at "Event" icon -> double click at
"DataReceived"

Then, input following code.


Meaning: read COM-Port data at every time receiving -> save it to "mStr"
COM-Port code
private: System::Void serialPort1_DataReceived(System::Object^ sender,
System::IO::Ports::SerialDataReceivedEventArgs^ e) {
mStr=serialPort1->ReadLine();
}
Auxiliary code:
(1) Meaning: when Form1 is close -> close COM-Port, and stop timer1
~Form1()
{
if (components)
{
delete components;
}
serialPort1->Close();
timer1->Stop();
}

(2) Meaning: make global variable "mStr"


private:
/// <summary>
/// Required designer variable.
String^ mStr;
/// </summary>

After all, click icon "Local Window Debugger" to build and run program

If every thing run smoothly, a window form will appear (note: Arduino should connected to Computer
through COM4 with program as in Step2)
At result (1): Form1 will show result from Arduino
At result (2): Form1 will sent data to Arduino, then receive it after Arduino sending.

++++++++++

S-ar putea să vă placă și