CCS C Software and Maintenance Offers
FAQFAQ   FAQForum Help   FAQOfficial CCS Support   SearchSearch  RegisterRegister 

ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

CCS does not monitor this forum on a regular basis.

Please do not post bug reports on this forum. Send them to CCS Technical Support

3 channel pwm with 120 degree phase shift
Goto page Previous  1, 2
 
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion
View previous topic :: View next topic  
Author Message
Ttelmah



Joined: 11 Mar 2010
Posts: 20061

View user's profile Send private message

PostPosted: Tue Jan 26, 2016 3:43 am     Reply with quote

As a 'possibly interesting', I have dug out my translation of AN857 to CCS.

This was working, so makes a starting point for such a motor. However this is designed to give speed control from an analog control input, not 'position' control, so will need modification. Also the data table for motor rate control is dependant on the motor/drive being used (the AN says how to generate this), so the one attached (which was for a 750W motor at 36V), will not be right for anything else....

To reverse direction, one stops, and then walks through the phase table in the opposite direction.

//The translation of the AN
Code:

#include <18F2620.h>
#device adc=16 //Left justify the ADC
#FUSES NOWDT, WDT128, INTRC_IO, NOFCMEN, NOIESO, NOBROWNOUT, NOMCLR, NOLVP, NOXINST
#use delay(clock=32MHz)
//Code based on AN857 for BLDC - for notes, look at the sensorless code in this AN

//Variables directly from AN
int8 AccelDelay=100;
int8 DecelDelay=10;
#define ManThresh (0x3F) //Change to 0xFF for manual mode - 0x3F else
#define AutoThresh (0x100-ManThresh)

//Again from AN
enum {RPMSetup,RPMRead,OffsetSetup,OffsetRead,Vsetup,Vidle,Vread,BEMFSetup,BEMFIdle,BEMFRead,BEMF2Idle,BEMF2Read,Inval} STATE=RPMsetup;

int8 PWMThresh=0;
int8 PhaseIndx=6,Drive,RPMIndex;
int8 ADCRPM;
int8 ADCOffset;
int8 flags=0;
int8 Vsupply;
int8 DeltaV1,DeltaV2;

int16 CCPSave,CCPT2;

int8 RampTimer;

#bit DriveOnFlag=flags.0
#bit AutoRPM=flags.1
#bit FullOnFlag=flags.4
#bit Tmr0Ovf=flags.5
#bit Tmr0Sync=flags.6

int1 change=FALSE;
#bit BEMF1Low=DeltaV1.7
#bit BEMF2Low=DeltaV2.7
#bit ADCneg=ADCOffset.7

#use fast_io(C) //for fastest output of drive bits

/*
ADC channels are wired as:
AN0 = RPM I/P voltage
AN1 = Offset I/P voltage
AN2 = BEMF sensor
*/
#define BEMF (2)
#define OFFSET (1)
#define RPM (0)

//Drive word definitions
#define OffMask (0B10010101)
#define Invalid (0B00000000)
#define Phase1  (0B01000001)
#define Phase2  (0B01000100)
#define Phase3  (0B00000110)
#define Phase4  (0B00010010)
#define Phase5  (0B00011000)
#define Phase6  (0B00001001)
//Again changed from example RC5, does not work as normal I/O on the PIC18 when special
//event triggers are selected.
//So pins RC6 & RC7 used for third output pair.

//ADC switching definitions
//Each assumes the ADC is in a defined previous state, and changes it to the next
#define ADC0to1 (0B00001000)
#define ADC1to3 (0B00010000)
#define ADC3to0 (0B00011000) //Not used.
#byte ADCON0=getenv("SFR:ADCON0") //0xFC2
#byte ADRESH=getenv("SFR:ADRESH") //0xFC4
#bit GO=ADCON0.1
#byte STAT=getenv("SFR:STATUS")
#bit CARRY=STAT.0
#bit ZERO=STAT.2
#byte TMR0=getenv("SFR:TMR0L")
#byte CCP1CON=getenv("SFR:CCP1CON")
#bit SPECIAL_EVENT=CCP1CON.0

//Adding diagnostics
//Pin B6 - high = locked
//B7 - high = fast
//A4 - pulse on phase1
//A5 pulsed on two BEMF readings

#define POWER_OFF (PIN_B4) //If this goes true, turn off drive (fail safe).

#include "T1Table.h" //This is the constant timing table calculated as in the AN

#INT_GLOBAL
void timer_0_int(void) {
   Tmr0Ovf=TRUE;
   Tmr0Sync=TRUE;
   clear_interrupt(INT_TIMER0); //fastest possible int handler - relies on instructions not changing flags etc..
}

const int8 OnTable[]={Invalid,Phase6,Phase5,Phase4,Phase3,Phase2,Phase1,Invalid};
void DriveMotor(void);
void LockTest(void);

void commutate(void) {
   //Commutation triggered by CCP1IF
   if (SPECIAL_EVENT) {
      //Here special event trigger is set
      clear_interrupt(INT_CCP1);
      if (--PhaseIndx==0) PhaseIndx=6; //Handle 'bottom of table'
      Drive=OnTable[PhaseIndx];
      DriveMotor();
   }
}

void DriveOff(void) {
   set_adc_channel(RPM); //Start with RPM required
   STATE=RPMSetup;
}

void main()
{
   int8 temp;
   output_c(0);      //Motor drivers off to start....
   set_tris_c(0);
   setup_adc_ports(AN0_TO_AN2);
   setup_adc(ADC_CLOCK_DIV_32|ADC_TAD_MUL_0); //As in note

   set_adc_channel(RPM); //select AN0
   delay_us(8);
   read_adc(ADC_START_ONLY);
   setup_spi(FALSE);
   setup_comparator(NC_NC_NC_NC);
   setup_timer_0(T0_DIV_2|T0_8_BIT);
   enable_interrupts(INT_TIMER0);
   setup_timer_1(T1_INTERNAL|T1_DIV_BY_4);
   CCP_1=0xFFFF;

   setup_ccp1(CCP_OFF);
   CCP1CON=0xB; //from AN
   clear_interrupt(INT_TIMER0);
   enable_interrupts(GLOBAL);
 
   //Now main loop
   do {
      if (interrupt_active(INT_CCP1)) commutate();
      DriveOnFlag=TRUE;
      if (!FullOnFlag) {
         temp=(int8)PWMThresh+TMR0;
         if (!CARRY) DriveOnFlag=FALSE;
         DriveMotor();
      }
      LockTest();
      switch (STATE) {
      //These are now the states from the AN
      case RPMSetup:
         if (Drive==Phase1) {
            output_high(PIN_A4);
            read_adc(ADC_START_ONLY);
            set_adc_channel(OFFSET); //Next channel - note change while reading is being done
            STATE++;
            Tmr0Sync=FALSE; 
         }
         break;     
      case RPMRead:
         if (adc_done()){
            //Conversion has finished
            ADCRPM=ADRESH; //fastest 8bit result from ADC
            STATE++;
         }
         break;
      case OffsetSetup: //Wait for Phase2 ADC Go RA3->ADC
         if (Drive==Phase2) {
            read_adc(ADC_START_ONLY); //Trigger ADC
            set_adc_channel(BEMF); //BEMF channel
            STATE++;
         }
         break;
      case OffsetRead:
         //Wait for ADC Read ADC->ADC Offset
         if (adc_done()){
            ADCOffset=ADRESH ^ 0x80; //Generate +/- offset

            PWMThresh=ADCRPM+ADCOffset;
            if (!bit_test(ADCOffset,7)) { //Offset is +ve
               if (CARRY) PWMThresh=0xFF;
            }
            else {
               if(!CARRY) PWMThresh=0;
            }
            if (ZERO) {
               DriveOff();
               break;
            }

            FullOnFlag=FALSE;
            if (PWMThresh>0xFD) FullOnFlag=TRUE; //Fixed full on threshold
            STATE++;
         }
         break;                               
      case Vsetup:
         //wait for phase4
         if (Drive==Phase4) {
            //Set Timer routine
            CCP_1=Timer1table[RPMIndex]; //Table from spreadsheet
            STATE++;
         }
         break;
      case Vidle:
         //Wait for Drive ON, wait Tacq, set ADC go
         if (DriveOnFlag) {
            delay_us(8); //Tacq
            read_adc(ADC_START_ONLY);
            STATE++;
         }
         break;                 
      case Vread:
         if(adc_done()) {
            //ADC has finished
            Vsupply=ADRESH;
            STATE++;
            Tmr0Sync=FALSE;
         }
         break; 
      case BEMFSetup:
         if(Drive==Phase5) {
            if (Tmr0Sync) {
               if (bit_test(PWMThresh,7)!=0) {
                  if (DriveOnFlag==FALSE) break;
               }
               //BEMFS1
               SPECIAL_EVENT=FALSE; //Turn off event on compare
               CCPSave=CCPT2=CCP_1; //Save two copies of CCP
               CCPT2/=2; //1/2 Phase time
               CCP_1=CCPT2/2; //1/4 phase time into CCP_1
               STATE++;
            }
         }
         break;
      case BEMFIdle:
         if (interrupt_active(INT_CCP1)) {
            DriveOnFlag=TRUE;
            DriveMotor();
            Delay_us(8);
            read_adc(ADC_START_ONLY);
            CCP_1+=CCPT2; //add 1/2 phase time to give 3/4 phase time
            clear_interrupt(INT_CCP1);
            STATE++;
         }
         break;
      case BEMFRead:
         if (adc_done()) {
            DeltaV1=ADRESH-(Vsupply/2);
            STATE++;
         }
         break;
      case BEMF2Idle:
         if (interrupt_active(INT_CCP1)) {
            DriveOnFlag=TRUE;
            DriveMotor();
            delay_us(8);
            read_adc(ADC_START_ONLY);
            set_adc_channel(RPM);
            CCP_1=CCPSave; //Full time
            clear_interrupt(INT_CCP1);
            SPECIAL_EVENT=TRUE; //enable event on compare
            STATE++;
         }
         break;
      case BEMF2Read:
         if (adc_done()) {
            DeltaV2=ADRESH-(Vsupply/2);
            STATE=RPMSetup; //Changing to start with current reading
         }
         change=TRUE;
         break;
      case Inval:
         STATE=RPMSetup;
         //Status=0;
         set_adc_channel(RPM);
         break;
      }   
   } while(TRUE);
}

void DriveMotor(void) {
   if (DriveOnFlag & (input(POWER_OFF)==FALSE)) { //Turning off drive if input goes TRUE
      output_c(Drive | 0x80);
   }
   else {
      output_c(Drive & OffMask);
   }
}

void LockTest(void) {
   change=FALSE;
   if(Tmr0Ovf==TRUE) {
      if (bit_test(PWMThresh,7)){
         //On longer than off
         if (DriveOnFlag==FALSE)
            return;
      }
      //Here drive is either on, and on>off, or drive off, and off>on - LT05
      Tmr0Ovf=FALSE;
      if (--RampTimer!=0)
         return;
      AutoRPM=TRUE;
      if (BEMF1Low) {
         //LT10
         output_low(PIN_B6);
         output_high(PIN_B7); //ahead of lock
         RampTimer=AccelDelay;
         if (AutoRPM==FALSE) {
            //Manual code
            RPMIndex=ADCRPM;
            return;
         }
         if (RPMIndex==0xFF) {
            return;
         }
         RPMIndex++;
         return;
      }
      RampTimer=DecelDelay;
      if (!BEMF2Low) {
         output_low(pin_B6);
         output_low(PIN_B7); //behind lock
         if (AutoRPM==FALSE) {
            //Manual code again
            RPMIndex=ADCRPM;
            return;
         }
         if (RPMIndex>0) --RPMIndex;
         return;
      }
      output_high(PIN_B6);
      if(AutoRPM==FALSE) {
         //Manual code
         RPMIndex=ADCRPM;
      }
   }
}   


The motor/timer control look up table
Code:
           
const int16 Timer1Table[256] = {
8568,
8539,
8511,
8482,
8453,
8425,
8396,
8367,
8338,
8310,
8281,
8252,
8224,
8195,
8166,
8138,
8109,
8080,
8051,
8023,
7994,
7965,
7937,
7908,
7879,
7851,
7822,
7793,
7764,
7736,
7707,
7678,
7650,
7621,
7592,
7564,
7535,
7506,
7477,
7449,
7420,
7391,
7363,
7334,
7305,
7277,
7248,
7219,
7190,
7162,
7133,
7104,
7076,
7047,
7018,
6990,
6961,
6932,
6903,
6875,
6846,
6817,
6789,
6760,
6731,
6703,
6674,
6645,
6616,
6588,
6559,
6530,
6502,
6473,
6444,
6416,
6387,
6358,
6329,
6301,
6272,
6243,
6215,
6186,
6157,
6129,
6100,
6071,
6042,
6014,
5985,
5956,
5928,
5899,
5870,
5842,
5813,
5784,
5755,
5727,
5698,
5669,
5641,
5612,
5583,
5555,
5526,
5497,
5468,
5440,
5411,
5382,
5354,
5325,
5296,
5268,
5239,
5210,
5181,
5153,
5124,
5095,
5067,
5038,
5009,
4981,
4952,
4923,
4894,
4866,
4837,
4808,
4780,
4751,
4722,
4694,
4665,
4636,
4607,
4579,
4550,
4521,
4493,
4464,
4435,
4407,
4378,
4349,
4320,
4292,
4263,
4234,
4206,
4177,
4148,
4120,
4091,
4062,
4033,
4005,
3976,
3947,
3919,
3890,
3861,
3833,
3804,
3775,
3746,
3718,
3689,
3660,
3632,
3603,
3574,
3546,
3517,
3488,
3459,
3431,
3402,
3373,
3345,
3316,
3287,
3259,
3230,
3201,
3172,
3144,
3115,
3086,
3058,
3029,
3000,
2972,
2943,
2914,
2885,
2857,
2828,
2799,
2771,
2742,
2713,
2685,
2656,
2627,
2598,
2570,
2541,
2512,
2484,
2455,
2426,
2398,
2369,
2340,
2311,
2283,
2254,
2225,
2197,
2168,
2139,
2111,
2082,
2053,
2024,
1996,
1967,
1938,
1910,
1881,
1852,
1824,
1795,
1766,
1737,
1709,
1680,
1651,
1623,
1594,
1565,
1537,
1508,
1479,
1450,
1422,
1393,
1364,
1336,
1307,
1278,
1250
};

This will need to be recalculated for any other motor.
mahdi70



Joined: 05 Jan 2016
Posts: 44

View user's profile Send private message

PostPosted: Tue Jan 26, 2016 4:20 am     Reply with quote

Ttelmah wrote:
The first thing is that you don't want three phase the way you show it.....

BLDC motors like this have forward and reverse drive on each coil. The sequence is:
Code:

Phase     coil A    coil B    coil C
1           +          -          0
2           0           -         +
3           -           0         +
4           -           +         0
5           0          +          -
6           +          0          -

If fact for smooth motion, the '+' energisation (for example), wants to be a synthesised sinusoidal waveform, so a much higher frequency PWM, varying from almost nothing to full power and then back down to almost nothing over the cycle. When the motor is stationary, the current PWM ratio on the correct pins has to continue. Two coils active at any one time.
You need six FET drivers, three energising the coils in a positive direction, and three in a -ve direction. A total of six control pins on the PIC.
For the slow speeds that the gimbal will actually rotate, you may well be able to run without BEMF sensing, and instead treat all motions as if they are initial 'start' operations. AN857, does show how to generate these waveforms from just about any PIC.


tnx...this is my speed control that i design it and install on my quad and work properly...




above speed control work with bemf system...in each rotor station , if bemf sense then goto to next station if not all switch off and show error....the bemf system cant sense speed under 200 rpm...you should rotate motor without bemf....Now the question comes ,Where to find out the rotor rotate properly until goto to next station?

this is example code:
Code:
while (1){
station(1);//phase a=+ ... b=- ... c=float
delay_ms(10);
station(2);//phase a=+ ... b=float ... c=-
delay_ms(10);
station(3);//phase a=float ... b=+ ... c=-
delay_ms(10);
station(4);//phase a=- ... b=+ ... c=float
delay_ms(10);
station(5);//phase a=- ... b=float ... c=+
delay_ms(10);
station(6);//phase a=float ... b=- ... c=+
delay_ms(10);}


for example i want to rotate motor with 1 degree per second....how to do it? this is my problem

sorry for poor english

tnx
mahdi70



Joined: 05 Jan 2016
Posts: 44

View user's profile Send private message

PostPosted: Tue Jan 26, 2016 4:32 am     Reply with quote

Ttelmah wrote:
As a 'possibly interesting', I have dug out my translation of AN857 to CCS.


Very very tnx but It is a little hard to understand for me.
Display posts from previous:   
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion All times are GMT - 6 Hours
Goto page Previous  1, 2
Page 2 of 2

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum


Powered by phpBB © 2001, 2005 phpBB Group