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)

Arduino Code for 3 Phase Inverter Driven by SVPWM Method
รูปที่ 1 วัดสัญญาณพัลซ์เบื้องต้น

รูปที่ 1 เป็นลักษณะของการวัดสัญญาณที่เกิดขึ้นจากการใช้โปแกรมตัวอย่างอ้างอิง [1] ทั้งนี้เพื่อสังเกตความถูกต้องของสัญญาณเบื้องต้น และการรับสัญญาณควบคุมจากตัวต้านทานปรับค่า

Arduino Code for 3 Phase Inverter Driven by SVPWM Method
รุปที่ 2 การต่อตัวต้านทานปรับค่าสำหรับปรับความเร็วรอบ

/*
   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);    
  }
}
Arduino Code for 3 Phase Inverter Driven by SVPWM Method
รูปที่ 3 ลักษณะการทดลองวงจรเบื้องต้น

รูปที่ 3 แสดงลักษณะของการทดลองด้วยเครื่องมือและอุปกรณ์ต่างๆ ซึ่งช่วยให้เข้าใจการทำงานและพฤติกรรมของการควบคุมมอเตอร์แบบ SVPWM เพิ่มขึ้น และเป็นพื้นฐานในการค้นคว้าหาข้อมูลเพื่อเรียนรู้เพิ่มเติม

Arduino Code for 3 Phase Inverter Driven by SVPWM Method
รูปที่ 4 แสดงบล็อกไดอะแกรมการทำงานของวงจร

รูปที่ 4 เป็นบล็อกไดอะแกรมแสดงการทำงานของวงจรต่างๆ ที่ใช้ในการทำลองครั้งนี้ ซึ่งในการทำลองจะใช้บอร์ดควบคุมการทำงานและบอร์ดขับกำลังมอเตอร์ 2 ส่วน เพื่อให้ใช้เวลากับการประกอบส่วนต่างๆน้อยลง

ในส่วนบทความนี้เป็นการนำโค้ดการควบคุมมอเตอร์ 3 เฟส แบบ SVPWM มาแชร์ครับ ซึ่งเป็นโค้ดที่อ้างอิงจาก [1] โดยเราสามารถใช้กับบอร์ดควบคุม Ardiuno UNO หรือ Arduino NANO ในการทดลองการทำงานต่างๆ ได้ ในส่วนของแอดมินเอง ได้ปรับแต่งโปรแกรมเดิมบ้างเล็กน้อยและได้ผลการทดลองดังคลิปวิดีโอข้างบน และถ้ามีการทดลองอัพเดตอย่างไรจะนำมาแชร์กันต่อครับ.

Reference

  1. https://www.edaboard.com/threads/svpwm-crooked-3-phase-inverter-output-what-is-wrong-with-the-circuit-proteus.385614/
  2. https://en.wikipedia.org/wiki/Space_vector_modulation
  3. https://en.wikipedia.org/wiki/Vector_control_(motor)
  4. https://www.avrfreaks.net/forum/space-vector-pwm-3-phase-inverter-using-atmega32
  5. https://docs.simplefoc.com/foc_theory
  6. https://www.infineon.com/dgdl/AP1609710_different_PWM_for_three_phase_ACIM.pdf?fileId=db3a304412b407950112b40a1bf20453
  7. http://electrotech4u.blogspot.com/2011/07/implementation-of-space-vector.html
  8. https://www.ti.com/lit/an/sprabq8/sprabq8.pdf?ts=1610357922562&ref_url=https%253A%252F%252Fwww.google.com%252F
  9. http://www.epyme.uva.es/Docs/Public/Conferences/Epe2007a.pdf
  10. https://www.nxp.com/docs/en/application-note/AN4869.pdf
  11. https://forum.arduino.cc/index.php?topic=286155.0
  12. https://www.switchcraft.org/learning/2017/3/15/space-vector-pwm-intro
  13. https://www.mathworks.com/solutions/power-electronics-control/space-vector-modulation.html
  14. https://electronics.stackexchange.com/questions/504515/inverter-using-arduino
  15. https://meaconsultingdotorg.files.wordpress.com/2015/12/spacevector_pwm_inverter.pdf
  16. https://www.youtube.com/watch?v=WCFRfYbU8do