Go Down

Topic: Vector drive of 3-phase induction motor. (Read 2797 times) previous topic - next topic

MarkT

I've recently been experimenting with an old washing machine
motor of the 3-phase induction variety.  Its nominally 195V but
I'm limited to 30V with current bench supply and inverter board
ATM (which makes things a lot safer of course).

The vector control technique for induction motors relies on
keeping separate control of the rotor flux vector's amplitude
and rate of rotation (slip speed).  

This allows instant change of torque and operation as a servo
motor, since the slip speed and stator currents are controlled
in a coordinated fashion.

The basic circuit is a 3-phase inverter bridge (in my case 6 MOSFETs,
a FAN7388 3-phase driver chip and two hall-effect current sensors
to measure the stator current.



(And lots of cermaic decoupling capacitors of course).

The two tiny devices at top left and top right are little SPI ADC chips
capable of clocking to 20MHz and 1MSPS - these digitize the current
values from the ASC711's in a few microseconds, as the main control
loop has to be very fast.

The basic current control compares the three phase current values against
a reference set.  The direction of the difference is determined from the
largest absolute value of the 3 phase errors, and this directly chooses
the next time-steps invert switching state.  This loop runs at about 35kHz,
and direct port manipulation is key to getting this performance.  Many
variables are only one byte to keep things lean and fast too.

Also the reference values are computed at each loop, using the
flux and torque input amplitudes combined with current rotor
position and slip-position.  A cosine-lookup table is used to keep
this efficient and the entries are also 8 bit.

At the outer level a PID loop (currently only P and D terms) drives
the torque input and slip frequency (which is integrated by the inner
loop to get the slip phase).  The outer loop also reads an absolute
magnetic encoder (AS5040) and runs at about 4kHz.

The end result is:
https://www.youtube.com/watch?v=ldiqWYxPoXk&feature=youtu.be

- A fully functional servo motor, in theory scalable to 200V and 800W,
motor was £20 on ebay (had to fiddle a bit to stop the rotor rubbing
the stator, it had obviously been dropped).

The motor's tachogenerator was ripped out and an AS5040 magnetic encoder
fitting in its place, which has a 12 absolute angular resolution and update rate
of 96us.
[ I will NOT respond to personal messages, I WILL delete them, use the forum please ]

MarkT

#1
Jul 01, 2014, 04:48 pm Last Edit: Jul 01, 2014, 04:49 pm by MarkT Reason: 1
And the current, very rough, code:

Code: [Select]
#define ENC_CS   7
#define ENC_SCLK 6
#define ENC_DATA 8

#define ADC_CSU  9
#define ADC_CSW  10


//#define USE_HI_COUNTS 1
//#define SATURATING_ADC 1

void setup ()
{
 pinMode (ADC_CSU, OUTPUT) ;
 pinMode (ADC_CSW, OUTPUT) ;
 pinMode (SCK, OUTPUT) ;
 pinMode (MOSI, OUTPUT) ;
 pinMode (ENC_CS, OUTPUT) ;
 pinMode (ENC_SCLK, OUTPUT) ;

 for (byte i = A0 ; i <= A5 ; i++)
   pinMode (i, OUTPUT) ;
 PORTC = 0b010101 ;

 Serial.begin (57600) ;
 SPSR = 0x01 ;
 SPCR = 0x50 ;
 int junk =  SPSR ;
 junk &= SPDR ;
 crud (junk) ;
}

void crud (int junk)
{
 current_setup () ;
 calc_setup () ;
}


char adc ()
{
 SPDR = 0x00 ;
 while ((SPSR & (1<<SPIF)) == 0) {}
 byte hi = SPDR - 0x08 ;
 
 SPDR = 0x00 ;
 while ((SPSR & (1<<SPIF)) == 0) {}
 byte lo = SPDR ;
 PORTB |= 0x04 ;
 
 int res = (hi << 8) | lo ;
#ifdef SATURATING_ADC
 if (res > 508)
   return 127 ;
 if (res < -508)
   return -127 ;
#endif
 return (char) (res >> 2) ;  // not saturating arith yet.
}

// 166mV / amp at 5V,  136 count / amp
// want +/- 127 to be current range, actually represents +/- 508, so
// shift 12 bit value right 2, in other words 8 bits from 10 bits value.

char u_zero ;
char w_zero ;

char u_target ;
char w_target ;

void current_setup ()
{
 int sum = 0 ;
 char res ;
 for (byte i = 0 ; i < 64 ; i++)
 {
   PORTB &= 0xFD ;  // pin 9 CS
   res = adc () ;
   PORTB |= 0x02 ;
   sum += res ;
 }
 u_zero = (char) ((sum + 32) >> 6) ;
 sum = 0 ;
 for (byte i = 0 ; i < 64 ; i++)
 {
   PORTB &= 0xFB ;  // pin 10 CS
   res = adc () ;
   PORTB |= 0x04 ;
   sum += res ;
 }
 w_zero = (char) ((sum + 32) >> 6) ;

 u_target = 34 ;
 w_target = -17 ;
}


byte old_patt = 0 ;

#ifdef USE_HI_COUNTS
byte uhi_count = 0 ;
byte vhi_count = 0 ;
byte whi_count = 0 ;
#endif


byte cycle = 0 ;

int rotor_phase = 0 ;
long rotor_pos = 0L ;
int rotor_prev  = 0 ;
int rotor_freq  = 0 ;


void encoder ()   // upto 64 LSB changes per sample at 16000 rpm
{
 PORTD &= 0x7F ;  // ENC_CS
 int res = 0 ;
 for (byte i = 0 ; i < 16 ; i++)
 {
   PORTD &= 0xBF ;  // ENC_SCLK
   res |= PINB & 1 ;
   PORTD |= 0x40 ;  // ENC_SCLK
   res <<= 1 ;
 
 }
 res |= PINB & 1 ;
 res &= 0xFFC0 ;  // ignore status
 ;
 PORTD |= 0x80 ;  // ENC_CS
 rotor_phase = res ; // upto 0x1000 change per sample so expand to use more bits
 rotor_freq += (((rotor_phase - rotor_prev) << 3) - rotor_freq + 8) >> 4 ;  // digital filter, average over 4ms or so
 rotor_pos += (rotor_phase - rotor_prev) >> 6 ;
 rotor_prev = rotor_phase ;
}
 
void current_loop ()
{
 cycle += 1 ;

 PORTB &= 0xFD ;  // pin 9 CS
 char u_actual = adc () - u_zero ;  // truncated value
 PORTB |= 0x02 ;
 
 PORTB &= 0xFB ;
 char w_actual = w_zero - adc () ;
 PORTB |= 0x04 ;

 char u_error = u_target - u_actual ;
 char w_error = w_target - w_actual ;
 char v_error = - u_error - w_error ;

 char u_abs = u_error < 0 ? -u_error : u_error ;
 char v_abs = v_error < 0 ? -v_error : v_error ;
 char w_abs = w_error < 0 ? -w_error : w_error ;

 char max_abs = u_abs > v_abs ?
   (w_abs > u_abs ? w_abs : u_abs) :
   (w_abs > v_abs ? w_abs : v_abs) ;

 byte pattern = 0b010101 ;

 if (max_abs > 2)  // threshold value, a guess currently
 {
   if (u_abs >= v_abs && u_abs > w_abs)
   {
     if (u_error >= 0)
     {
pattern = 0b100101 ;
#ifdef USE_HI_COUNTS
vhi_count = 0 ;
whi_count = 0 ;
#endif
     }
     else
     {
pattern = 0b011010 ;
#ifdef USE_HI_COUNTS
uhi_count = 0 ;
#endif
     }
   }
   else if (v_abs >= w_abs && v_abs > u_abs)
   {
     if (v_error >= 0)
     {
pattern = 0b011001 ;
#ifdef USE_HI_COUNTS
uhi_count = 0 ;
whi_count = 0 ;
#endif
     }
     else
     {
pattern = 0b100110 ;
#ifdef USE_HI_COUNTS
vhi_count = 0 ;
#endif
     }
   }
   else if (w_abs >= u_abs && w_abs > v_abs)
   {
     if (w_error >= 0)
     {
pattern = 0b010110 ;
#ifdef USE_HI_COUNTS
uhi_count = 0 ;
vhi_count = 0 ;
#endif
     }
     else
     {
pattern = 0b101001 ;
#ifdef USE_HI_COUNTS
whi_count = 0 ;
#endif
     }
   }
 }
#ifdef USE_HI_COUNTS
 if (pattern == 0b010101)
 {
   uhi_count = 0 ;
   vhi_count = 0 ;
   whi_count = 0 ;
 }

 if (uhi_count == 100)
 {
   pattern ^= 0b110000 ;
   uhi_count = 0 ;
 }
 if (vhi_count == 100)
 {
   pattern ^= 0b001100 ;
   vhi_count = 0 ;
 }
 if (whi_count == 100)
 {
   pattern ^= 0b000011 ;
   whi_count = 0 ;
 }
#else
 if ((cycle & 0x3F) == 0)
   pattern = 0b010101 ;
#endif

 byte diff = pattern ^ old_patt ;
 old_patt = pattern ;
 
 PORTC &= ~diff ;
#ifdef USE_HI_COUNTS
 uhi_count ++ ;
 vhi_count ++ ;
 whi_count ++ ;
#else
 asm ("nop") ;
 asm ("nop") ;
 asm ("nop") ;
#endif
 PORTC = pattern ;
}


int  flux_coeff = 34 * 141 / 100 ; // units of ADC, 1A = 34
int  torque_coeff = 300/5 ;



char cos_tab [0x100] ;

void calc_setup ()
{
 for (int i = 0 ; i < 0x100 ; i++)
 {
   float angle = PI * i / 128.0 ;
   float cosine = cos (angle) ;
   cos_tab [i] = (char) (127.0 * cosine) ;
 }
}

long slip_phase = 0L ;
long slip_freq = 300L << 11 ;

void calc_loop (boolean do_u)
{
 slip_phase += slip_freq ;
 long angle = rotor_phase ;
 angle = (angle << 16) + slip_phase ;
 byte ph = ((byte *) &angle)[3] ;
 if (do_u)
 {
   byte ph2 = ph + 0x40 ;
   int u = cos_tab [ph]  * flux_coeff + cos_tab [ph2] * torque_coeff ;
   u_target = (char) ((u+64) >> 7) ;
 }
 else
 {
    byte ph3 = ph - 0x55 ;
    byte ph4 = ph - 0x15 ;
    int w = cos_tab [ph3] * flux_coeff + cos_tab [ph4] * torque_coeff ;
    w_target = (char) ((w+64) >> 7) ;
 }
}
 

char read_u ()
{
 PORTB &= 0xFD ;  // pin 9 CS
 char res = adc () ;
 PORTB |= 0x02 ;
 return res ;
}

char read_w ()
{
 PORTB &= 0xFB ;  // pin 9 CS
 char res = adc () ;
 PORTB |= 0x04 ;
 return res ;
}

long target = 0L ;

void pid ()
{
 long error = (target - rotor_pos) << 3 ; // * 10 ;
 error -= rotor_freq ;
 if (error > 250) error = 250 ;
 if (error < -250) error = -250 ;
 slip_freq = error ;
 slip_freq <<= 10 ;
 torque_coeff = (error + (error << 2)) >> 4 ;
 flux_coeff = 48 ; // units of ADC, 1A = 34
}

unsigned long prev_ts = 0L ;
byte * ptr = 0 ;

void loop ()
{
 if ((cycle & 7) == 0)
 {
   slip_phase += slip_freq ;
   encoder () ;
 }
 if ((cycle & 15) == 2)
 {
   pid () ;
   
   if (millis () - prev_ts > 100)
   {
     prev_ts += 100 ;
     target = 2 * *ptr++ ;
   }
   
 }
 current_loop () ;
 calc_loop (false) ;
 current_loop () ;
 calc_loop (true) ;
}




[ I will NOT respond to personal messages, I WILL delete them, use the forum please ]

chinuhark

You didn't mention anywhere..This is DTC and not FOC right? That's my guess. Please confirm. Also some more info would be nice. Like equations used. You said that there is a current control loop running at 35kHz that follows the reference currents. Please elaborate. I thought the Hysteresis control is applied to torque and flux and not the currents.

MarkT

#3
Mar 16, 2015, 06:50 pm Last Edit: Mar 16, 2015, 06:50 pm by MarkT
I'm just driving the current directly - I've got an encoder on the motor, so
no rotor estimation is needed I know where it is so all I do is set the
input torque / flux components and slip speed directly.  The control loop
switches the bridge to keep the measured currents matching the desired ones.

The torque/flux phasors are basically rotated by a phasor that rotates at
actual speed + desired slip speed.
[ I will NOT respond to personal messages, I WILL delete them, use the forum please ]

PaulMurrayCbr

Code: [Select]

 int res = (hi << 8) | lo ;
#ifdef SATURATING_ADC
 if (res > 508)
   return 127 ;
 if (res < -508)
   return -127 ;
#endif


What happens if res is exactly equal to 508?
http://paulmurraycbr.github.io/ArduinoTheOOWay.html

MarkT

#5
Mar 26, 2015, 06:55 pm Last Edit: Mar 26, 2015, 06:57 pm by MarkT
Here's a hint:
Code: [Select]

#ifdef SATURATING_ADC
  if (res > 508)
    return 127 ;
  if (res < -508)
    return -127 ;
#endif
  return (char) (res >> 2) ;  // not saturating arith yet.



You seem to have missed that -508 is not +508 and that 127*4 = 508,
in GPU terms this is just clamp (res/4, -127, 127)
[ I will NOT respond to personal messages, I WILL delete them, use the forum please ]

tobias559

@MarkT Have you got any data on the electronics you designed? I'm currently investigating position control of linear induction motors and am looking into a proper 3 phase current driver to use.

CatherineV

@tobias559 Have you founded any proper 3 phase current driver to use? If not, you may take a look at the Veichi AC70 vector control VFD drive.

PaulMurrayCbr

http://paulmurraycbr.github.io/ArduinoTheOOWay.html

Go Up
 


Please enter a valid email to subscribe

Confirm your email address

We need to confirm your email address.
To complete the subscription, please click the link in the email we just sent you.

Thank you for subscribing!

Arduino
via Egeo 16
Torino, 10131
Italy