Arduino Code for 3 Phase Inverter Driven by SVPWM Method
บทความนี้เป็นการขอยกตัวอย่างโค้ดโปรแกรมจากเว็บไซต์ต่างประเทศ ในการทดลองเกี่ยวกับการขับมอเตอร์ 3 เฟส ด้วยวิธีการ Space Vector Pulse Width Modulation (SVPWM) โดยใช้บอร์ด Arduino และคิดว่าน่าจะเป็นแนวทางให้ท่านที่กำลังศึกษาเกี่ยวกับการขับมอเตอร์แบบ Vector control ได้นำไปทดลอง ซึ่งตัวโค้ดไม่ต้องใช้ไลบารี่เพิ่มเติมและสามารถใช้กับบอร์ด Arduino UNO หรือ Arduino NANO ในการทดลองได้ครับ (วิดีโอการทดลอง Beginners for SVPWM 3 Phase Induction Motor with Arduino UNO ในช่อง Youtube : https://www.youtube.com/watch?v=RU93KwQ5eLQ&t=6s)
รูปที่ 1 เป็นลักษณะของการวัดสัญญาณที่เกิดขึ้นจากการใช้โปแกรมตัวอย่างอ้างอิง [1] ทั้งนี้เพื่อสังเกตความถูกต้องของสัญญาณเบื้องต้น และการรับสัญญาณควบคุมจากตัวต้านทานปรับค่า
- *Reference : (loompa) https://www.edaboard.com/threads/svpwm-crooked-3-phase-inverter-output-what-is-wrong-with-the-circuit-proteus.385614/
/*
Arduino Program code credit in the experiment
https://www.edaboard.com/threads/svpwm-crooked-3-phase-inverter-output-what-is-wrong-with-the-circuit-proteus.385614/
*/
#include <math.h>
int sector_pos = 1; //current sector.
float freq_sth = 10000.00; // Switching freqency 5000Hz
float freq = 50.0; // Variable fundamental freqency 50
float sample_time = (freq_sth/freq); // sample_time(per cycle) = switching_time/fundamental_output_freq.
float base_angle = (360.0/sample_time); //Base angle will be used to increment angle per cycle.
float m = 0.8; // modulation index (Controls max Vref magnitude) 0>=m<=1.
int vdc = 100; // rectified voltage
int vref = (vdc)*(m); // vref, controlled by some modulation index.
float tz = ((1/freq_sth))/pow(10,-6); // Total switching time Tz in (us)
float timer1 = 0, timer2 = 0, timer0 = 0; // Switching times
float angle = 0; // (in degrees, must be changed to radians.) Gives us angle to be incremented per 'Tz' seconds.
float angle_rad = 0; // Angle in radians.Computed for each 'Tz' seconds in time_cal().
float timer_div = 0; // Constant in time calculation equations. (refer to t1,t2 equations)
double theta1 = 0, theta2 = 0; // trignometric part of t1,t2 equations, computated seperately to reduce clutering of code in one line.(i.e t1 = timer_div*theta1)
int mf1 = 2, mf2 = 3, mf3 = 4, mf4 = 5, mf5 = 6, mf6 = 7; // Arduino digital pins
int sensorValue = 0;
void setup() {
Serial.begin(9600);
// Arduino digital pins
pinMode(mf1, OUTPUT); // High U PIN D2
pinMode(mf2, OUTPUT); // High V PIN D4
pinMode(mf3, OUTPUT); // High W PIN D6
pinMode(mf4, OUTPUT); // Low U PIN D3
pinMode(mf5, OUTPUT); // Low V PIN D5
pinMode(mf6, OUTPUT); // Low W PIN D7
digitalWrite(mf1, LOW);
digitalWrite(mf2, LOW);
digitalWrite(mf3, LOW);
digitalWrite(mf4, LOW);
digitalWrite(mf5, LOW);
digitalWrite(mf6, LOW);
}
void loop() {
// /*
sensorValue = analogRead(A0);
sensorValue = (sensorValue/30);
if (sensorValue<3) {
sensorValue = 3;
}
base_angle = sensorValue;
// Serial.println(base_angle);
// delay(100);
// base_angle = (360.0/sensorValue);
// Serial.println(base_angle);
// delay(300);
// */
sector_tracker();
time_cal();
mosfet_switch();
angle_incr();
// delay(10);
}
void sector_tracker()
{
if(angle >= 60) //When angle is >= 60 that means we have covered one 1 sector.
{
if(sector_pos != 7) // this condition is here to make sure we are never in sector 7 (i.e wer
{
angle = 0;
sector_pos = (sector_pos+1);
}
if(sector_pos == 7) // if after expression 'sector_pos = sector_pos + 1' in above case,we get 7, we need to set everything to initial point since no sector 7 exists.
{ //reason for putting this condition after the above condition is because there was a case when sector_pos = 7 after we update the sector_pos.
angle = 0;
sector_pos = 1;
}
}
}
void time_cal() {
timer_div = (2/sqrt(3) * tz * m);
angle_rad = angle * (M_PI/180); //converting angle->degrees to angle->radians. Need radian term for sin.
theta1 = sin((M_PI/3.0)-angle_rad);
theta2 = sin(angle_rad);
timer1 = timer_div * theta1;
timer2 = timer_div * theta2;
timer0 = (tz - (timer1 + timer2))/2.0;
//all time in (us)
timer1 = round(timer1);
timer2 = round(timer2);
timer0 = round(timer0);
/*
Serial.print("Sector: ");
Serial.println(sector_pos);
Serial.print("Timer 1:");
Serial.println(timer1, 2);
Serial.print("Timer 2:");
Serial.println(timer2, 2);
Serial.print("Timer 0:");
Serial.println(timer0, 2);
delay(300);
*/
}
void angle_incr() {
angle = angle + base_angle;
}
void mosfet_switch() {
//Symmetric switching pattern/sequences used for optimal results.(low THD etc)
//refer to 'document' for rules for each V-state switches.
//switches in each V-state are defined by table( using the one given at wikipedia)
if (sector_pos == 1)
{
//v0-v1-v2-v7-v2-v1-v0
//-----
//v0:
digitalWrite(mf1,LOW);
digitalWrite(mf3,LOW);
digitalWrite(mf5,LOW);
digitalWrite(mf2,HIGH);
digitalWrite(mf4,HIGH);
digitalWrite(mf6,HIGH);
delayMicroseconds(timer0);
//v1:
digitalWrite(mf1,HIGH);
digitalWrite(mf3,LOW);
digitalWrite(mf5,LOW);
digitalWrite(mf2,LOW);
digitalWrite(mf4,HIGH);
digitalWrite(mf6,HIGH);
delayMicroseconds(timer1);
//v2:
digitalWrite(mf1,HIGH);
digitalWrite(mf3,HIGH);
digitalWrite(mf5,LOW);
digitalWrite(mf2,LOW);
digitalWrite(mf4,LOW);
digitalWrite(mf6,HIGH);
delayMicroseconds(timer2);
//v7:
digitalWrite(mf1,HIGH);
digitalWrite(mf3,HIGH);
digitalWrite(mf5,HIGH);
digitalWrite(mf2,LOW);
digitalWrite(mf4,LOW);
digitalWrite(mf6,LOW);
delayMicroseconds(timer0);
//v2:
digitalWrite(mf1,HIGH);
digitalWrite(mf3,HIGH);
digitalWrite(mf5,LOW);
digitalWrite(mf2,LOW);
digitalWrite(mf4,LOW);
digitalWrite(mf6,HIGH);
delayMicroseconds(timer2);
//v1:
digitalWrite(mf1,HIGH);
digitalWrite(mf3,LOW);
digitalWrite(mf5,LOW);
digitalWrite(mf2,LOW);
digitalWrite(mf4,HIGH);
digitalWrite(mf6,HIGH);
delayMicroseconds(timer1);
//v0:
digitalWrite(mf1,LOW);
digitalWrite(mf3,LOW);
digitalWrite(mf5,LOW);
digitalWrite(mf2,HIGH);
digitalWrite(mf4,HIGH);
digitalWrite(mf6,HIGH);
delayMicroseconds(timer0);
}
if (sector_pos == 2)
{
//v0-v3-v2-v7-v2-v3-v0
//v0:
digitalWrite(mf1,LOW);
digitalWrite(mf3,LOW);
digitalWrite(mf5,LOW);
digitalWrite(mf2,HIGH);
digitalWrite(mf4,HIGH);
digitalWrite(mf6,HIGH);
delayMicroseconds(timer0);
//v3:
digitalWrite(mf1,LOW);
digitalWrite(mf3,HIGH);
digitalWrite(mf5,LOW);
digitalWrite(mf2,HIGH);
digitalWrite(mf4,LOW);
digitalWrite(mf6,HIGH);
delayMicroseconds(timer2);
//v2:
digitalWrite(mf1,HIGH);
digitalWrite(mf3,HIGH);
digitalWrite(mf5,LOW);
digitalWrite(mf2,LOW);
digitalWrite(mf4,LOW);
digitalWrite(mf6,HIGH);
delayMicroseconds(timer1);
//v7:
digitalWrite(mf1,HIGH);
digitalWrite(mf3,HIGH);
digitalWrite(mf5,HIGH);
digitalWrite(mf2,LOW);
digitalWrite(mf4,LOW);
digitalWrite(mf6,LOW);
delayMicroseconds(timer0);
//v2:
digitalWrite(mf1,HIGH);
digitalWrite(mf3,HIGH);
digitalWrite(mf5,LOW);
digitalWrite(mf2,LOW);
digitalWrite(mf4,LOW);
digitalWrite(mf6,HIGH);
delayMicroseconds(timer1);
//v3:
digitalWrite(mf1,LOW);
digitalWrite(mf3,HIGH);
digitalWrite(mf5,LOW);
digitalWrite(mf2,HIGH);
digitalWrite(mf4,LOW);
digitalWrite(mf6,HIGH);
delayMicroseconds(timer2);
//v0:
digitalWrite(mf1,LOW);
digitalWrite(mf3,LOW);
digitalWrite(mf5,LOW);
digitalWrite(mf2,HIGH);
digitalWrite(mf4,HIGH);
digitalWrite(mf6,HIGH);
delayMicroseconds(timer0);
}
if (sector_pos == 3)
{
//v0-v3-v4-v7-v4-v3-v0
//v0:
digitalWrite(mf1,LOW);
digitalWrite(mf3,LOW);
digitalWrite(mf5,LOW);
digitalWrite(mf2,HIGH);
digitalWrite(mf4,HIGH);
digitalWrite(mf6,HIGH);
delayMicroseconds(timer0);
//v3:
digitalWrite(mf1,LOW);
digitalWrite(mf3,HIGH);
digitalWrite(mf5,LOW);
digitalWrite(mf2,HIGH);
digitalWrite(mf4,LOW);
digitalWrite(mf6,HIGH);
delayMicroseconds(timer1);
//v4:
digitalWrite(mf1,LOW);
digitalWrite(mf3,HIGH);
digitalWrite(mf5,HIGH);
digitalWrite(mf2,HIGH);
digitalWrite(mf4,LOW);
digitalWrite(mf6,LOW);
delayMicroseconds(timer2);
//v7:
digitalWrite(mf1,HIGH);
digitalWrite(mf3,HIGH);
digitalWrite(mf5,HIGH);
digitalWrite(mf2,LOW);
digitalWrite(mf4,LOW);
digitalWrite(mf6,LOW);
delayMicroseconds(timer0);
//v4:
digitalWrite(mf1,LOW);
digitalWrite(mf3,HIGH);
digitalWrite(mf5,HIGH);
digitalWrite(mf2,HIGH);
digitalWrite(mf4,LOW);
digitalWrite(mf6,LOW);
delayMicroseconds(timer2);
//v3:
digitalWrite(mf1,LOW);
digitalWrite(mf3,HIGH);
digitalWrite(mf5,LOW);
digitalWrite(mf2,HIGH);
digitalWrite(mf4,LOW);
digitalWrite(mf6,HIGH);
delayMicroseconds(timer1);
//v0:
digitalWrite(mf1,LOW);
digitalWrite(mf3,LOW);
digitalWrite(mf5,LOW);
digitalWrite(mf2,HIGH);
digitalWrite(mf4,HIGH);
digitalWrite(mf6,HIGH);
delayMicroseconds(timer0);
}
if (sector_pos == 4)
{
//v0-v5-v4-v7-v4-v5-v0
//v0:
digitalWrite(mf1,LOW);
digitalWrite(mf3,LOW);
digitalWrite(mf5,LOW);
digitalWrite(mf2,HIGH);
digitalWrite(mf4,HIGH);
digitalWrite(mf6,HIGH);
delayMicroseconds(timer0);
//v5:
digitalWrite(mf1,LOW);
digitalWrite(mf3,LOW);
digitalWrite(mf5,HIGH);
digitalWrite(mf2,HIGH);
digitalWrite(mf4,HIGH);
digitalWrite(mf6,LOW);
delayMicroseconds(timer2);
//v4:
digitalWrite(mf1,LOW);
digitalWrite(mf3,HIGH);
digitalWrite(mf5,HIGH);
digitalWrite(mf2,HIGH);
digitalWrite(mf4,LOW);
digitalWrite(mf6,LOW);
delayMicroseconds(timer1);
//v7:
digitalWrite(mf1,HIGH);
digitalWrite(mf3,HIGH);
digitalWrite(mf5,HIGH);
digitalWrite(mf2,LOW);
digitalWrite(mf4,LOW);
digitalWrite(mf6,LOW);
delayMicroseconds(timer0);
//v4:
digitalWrite(mf1,LOW);
digitalWrite(mf3,HIGH);
digitalWrite(mf5,HIGH);
digitalWrite(mf2,HIGH);
digitalWrite(mf4,LOW);
digitalWrite(mf6,LOW);
delayMicroseconds(timer1);
//v5:
digitalWrite(mf1,LOW);
digitalWrite(mf3,LOW);
digitalWrite(mf5,HIGH);
digitalWrite(mf2,HIGH);
digitalWrite(mf4,HIGH);
digitalWrite(mf6,LOW);
delayMicroseconds(timer2);
//v0:
digitalWrite(mf1,LOW);
digitalWrite(mf3,LOW);
digitalWrite(mf5,LOW);
digitalWrite(mf2,HIGH);
digitalWrite(mf4,HIGH);
digitalWrite(mf6,HIGH);
delayMicroseconds(timer0);
}
if (sector_pos == 5)
{
//v0-v5-v6-v7-v6-v5-v0
//v0:
digitalWrite(mf1,LOW);
digitalWrite(mf3,LOW);
digitalWrite(mf5,LOW);
digitalWrite(mf2,HIGH);
digitalWrite(mf4,HIGH);
digitalWrite(mf6,HIGH);
delayMicroseconds(timer0);
//v5:
digitalWrite(mf1,LOW);
digitalWrite(mf3,LOW);
digitalWrite(mf5,HIGH);
digitalWrite(mf2,HIGH);
digitalWrite(mf4,HIGH);
digitalWrite(mf6,LOW);
delayMicroseconds(timer1);
//v6:
digitalWrite(mf1,HIGH);
digitalWrite(mf3,LOW);
digitalWrite(mf5,HIGH);
digitalWrite(mf2,LOW);
digitalWrite(mf4,HIGH);
digitalWrite(mf6,LOW);
delayMicroseconds(timer2);
//v7:
digitalWrite(mf1,HIGH);
digitalWrite(mf3,HIGH);
digitalWrite(mf5,HIGH);
digitalWrite(mf2,LOW);
digitalWrite(mf4,LOW);
digitalWrite(mf6,LOW);
delayMicroseconds(timer0);
//v6:
digitalWrite(mf1,HIGH);
digitalWrite(mf3,LOW);
digitalWrite(mf5,HIGH);
digitalWrite(mf2,LOW);
digitalWrite(mf4,HIGH);
digitalWrite(mf6,LOW);
delayMicroseconds(timer2);
//v5:
digitalWrite(mf1,LOW);
digitalWrite(mf3,LOW);
digitalWrite(mf5,HIGH);
digitalWrite(mf2,HIGH);
digitalWrite(mf4,HIGH);
digitalWrite(mf6,LOW);
delayMicroseconds(timer1);
//v0:
digitalWrite(mf1,LOW);
digitalWrite(mf3,LOW);
digitalWrite(mf5,LOW);
digitalWrite(mf2,HIGH);
digitalWrite(mf4,HIGH);
digitalWrite(mf6,HIGH);
delayMicroseconds(timer0);
}
if (sector_pos == 6)
{
//v0-v1-v6-v7-v6-v1-v0
//v0:
digitalWrite(mf1,LOW);
digitalWrite(mf3,LOW);
digitalWrite(mf5,LOW);
digitalWrite(mf2,HIGH);
digitalWrite(mf4,HIGH);
digitalWrite(mf6,HIGH);
delayMicroseconds(timer0);
//v1:
digitalWrite(mf1,HIGH);
digitalWrite(mf3,LOW);
digitalWrite(mf5,LOW);
digitalWrite(mf2,LOW);
digitalWrite(mf4,HIGH);
digitalWrite(mf6,HIGH);
delayMicroseconds(timer2);
//v6:
digitalWrite(mf1,HIGH);
digitalWrite(mf3,LOW);
digitalWrite(mf5,HIGH);
digitalWrite(mf2,LOW);
digitalWrite(mf4,HIGH);
digitalWrite(mf6,LOW);
delayMicroseconds(timer1);
//v7:
digitalWrite(mf1,HIGH);
digitalWrite(mf3,HIGH);
digitalWrite(mf5,HIGH);
digitalWrite(mf2,LOW);
digitalWrite(mf4,LOW);
digitalWrite(mf6,LOW);
delayMicroseconds(timer0);
//v6:
digitalWrite(mf1,HIGH);
digitalWrite(mf3,LOW);
digitalWrite(mf5,HIGH);
digitalWrite(mf2,LOW);
digitalWrite(mf4,HIGH);
digitalWrite(mf6,LOW);
delayMicroseconds(timer1);
//v1:
digitalWrite(mf1,HIGH);
digitalWrite(mf3,LOW);
digitalWrite(mf5,LOW);
digitalWrite(mf2,LOW);
digitalWrite(mf4,HIGH);
digitalWrite(mf6,HIGH);
delayMicroseconds(timer2);
//v0:
digitalWrite(mf1,LOW);
digitalWrite(mf3,LOW);
digitalWrite(mf5,LOW);
digitalWrite(mf2,HIGH);
digitalWrite(mf4,HIGH);
digitalWrite(mf6,HIGH);
delayMicroseconds(timer0);
}
}
รูปที่ 3 แสดงลักษณะของการทดลองด้วยเครื่องมือและอุปกรณ์ต่างๆ ซึ่งช่วยให้เข้าใจการทำงานและพฤติกรรมของการควบคุมมอเตอร์แบบ SVPWM เพิ่มขึ้น และเป็นพื้นฐานในการค้นคว้าหาข้อมูลเพื่อเรียนรู้เพิ่มเติม
รูปที่ 4 เป็นบล็อกไดอะแกรมแสดงการทำงานของวงจรต่างๆ ที่ใช้ในการทำลองครั้งนี้ ซึ่งในการทำลองจะใช้บอร์ดควบคุมการทำงานและบอร์ดขับกำลังมอเตอร์ 2 ส่วน เพื่อให้ใช้เวลากับการประกอบส่วนต่างๆน้อยลง
ในส่วนบทความนี้เป็นการนำโค้ดการควบคุมมอเตอร์ 3 เฟส แบบ SVPWM มาแชร์ครับ ซึ่งเป็นโค้ดที่อ้างอิงจาก [1] โดยเราสามารถใช้กับบอร์ดควบคุม Ardiuno UNO หรือ Arduino NANO ในการทดลองการทำงานต่างๆ ได้ ในส่วนของแอดมินเอง ได้ปรับแต่งโปรแกรมเดิมบ้างเล็กน้อยและได้ผลการทดลองดังคลิปวิดีโอข้างบน และถ้ามีการทดลองอัพเดตอย่างไรจะนำมาแชร์กันต่อครับ.
Reference
- https://www.edaboard.com/threads/svpwm-crooked-3-phase-inverter-output-what-is-wrong-with-the-circuit-proteus.385614/
- https://en.wikipedia.org/wiki/Space_vector_modulation
- https://en.wikipedia.org/wiki/Vector_control_(motor)
- https://www.avrfreaks.net/forum/space-vector-pwm-3-phase-inverter-using-atmega32
- https://docs.simplefoc.com/foc_theory
- https://www.infineon.com/dgdl/AP1609710_different_PWM_for_three_phase_ACIM.pdf?fileId=db3a304412b407950112b40a1bf20453
- http://electrotech4u.blogspot.com/2011/07/implementation-of-space-vector.html
- https://www.ti.com/lit/an/sprabq8/sprabq8.pdf?ts=1610357922562&ref_url=https%253A%252F%252Fwww.google.com%252F
- http://www.epyme.uva.es/Docs/Public/Conferences/Epe2007a.pdf
- https://www.nxp.com/docs/en/application-note/AN4869.pdf
- https://forum.arduino.cc/index.php?topic=286155.0
- https://www.switchcraft.org/learning/2017/3/15/space-vector-pwm-intro
- https://www.mathworks.com/solutions/power-electronics-control/space-vector-modulation.html
- https://electronics.stackexchange.com/questions/504515/inverter-using-arduino
- https://meaconsultingdotorg.files.wordpress.com/2015/12/spacevector_pwm_inverter.pdf
- https://www.youtube.com/watch?v=WCFRfYbU8do