Documente Academic
Documente Profesional
Documente Cultură
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.
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)
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
Hardware needed:
1. Motor with Encoder
2. H-bridge PCB
4. UART PCB
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.
Previous article
Arduino - Motor speed control pwm
Hardware needed:
1. Motor with encoder
2. H-bridge L298N
3. Arduino pro mini
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.
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
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;
}
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)
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
}
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);
}
}
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();
}
(4) Click to serialPort1 -> see at Properties toolbox -> click at "Event" icon -> double click at
"DataReceived"
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.
++++++++++