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 support@ccsinfo.com

Control a BLDC motor with PIC16F877A

 
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion
View previous topic :: View next topic  
Author Message
A_L_E_X



Joined: 21 Feb 2010
Posts: 11

View user's profile Send private message

Control a BLDC motor with PIC16F877A
PostPosted: Mon Dec 17, 2012 7:50 am     Reply with quote

I want to control a BDLC motor using a PIC16F877A. The min rpm is 1500 and the max is 45000. I want to modify the speed from a potentiometer and to display the rpm using a lcd. The speed displayed on the lcd is the speed related to the position of the potentiometer and not the actual speed of the motor. The motor doesn't have hall sensors.
I've written a code but the problem is that acquiring the ADC value, calculating and displaying the rpm value take some time and i think that it won't be too good for the motor.
Code:

while(1)
{
//1
val = read_adc();
rpm=((val*42.48)+1500);
//The rpm value is 1500 rpm at one end of the potentiometer and 45000 rpm at the other end of the potentiometer
printf(lcd_putc,"\fSpeed :\n%f RPM",rpm);
delay_ms(t);
//I need the formula for the delay time in order to obtain the desired speed.
output_high(TA1);
output_low(TA2);
output_low(TB1);
output_high(TB2);
output_low(TC1);
output_low(TC2);
//2
val = read_adc();
rpm=((val*42.48)+1500);
printf(lcd_putc,"\fSpeed :\n%f RPM",rpm);
delay_ms(t);
output_low(TA1);
output_low(TA2);
output_low(TB1);
output_high(TB2);
output_high(TC1);
output_low(TC2);
//3
val = read_adc();
rpm=((val*42.48)+1500);
printf(lcd_putc,"\fSpeed :\n%f RPM",rpm);
delay_ms(t);
output_low(TA1);
output_high(TA2);
output_low(TB1);
output_low(TB2);
output_high(TC1);
output_low(TC2);
//4
val = read_adc();
rpm=((val*42.48)+1500);
printf(lcd_putc,"\fSpeed :\n%f RPM",rpm);
delay_ms(t);
output_low(TA1);
output_high(TA2);
output_high(TB1);
output_low(TB2);
output_low(TC1);
output_low(TC2);
//5
val = read_adc();
rpm=((val*42.48)+1500);
printf(lcd_putc,"\fSpeed :\n%f RPM",rpm);
delay_ms(t);
output_low(TA1);
output_low(TA2);
output_high(TB1);
output_low(TB2);
output_low(TC1);
output_high(TC2);
//6
val = read_adc();
rpm=((val*42.48)+1500);
printf(lcd_putc,"\fSpeed :\n%f RPM",rpm);
delay_ms(t);
output_high(TA1);
output_low(TA2);
output_low(TB1);
output_low(TB2);
output_low(TC1);
output_high(TC2);
}


I need an example or some instructions on how to generate the signals, to acquire the potentiometer value and to calculate the speed and display it on the lcd at the same time without disturbing the signals frequency. I guess i have to use the ADC interrupt but i don't know how to do that. The speed is not so critical. It's a motor for a drilling machine.
asmboy



Joined: 20 Nov 2007
Posts: 2128
Location: albany ny

View user's profile Send private message AIM Address

PostPosted: Mon Dec 17, 2012 8:15 am     Reply with quote

Quote:

The motor doesn't have hall sensors.


Without Hall sensors, I think you are going to find that you do not have enough
processing power for a BEMF based solution, with a PIC16 part.

The amount of processing plus fast accurate ADC sampling required will be beyond this PICs capability.


You need a CIRCUIT concept, long before you need code, plus a CLUE as to how to program a BEMF based controller. It is a project for a serious HARDWARE engineer/ as code is only a part of the solution.

Got a circuit to show??? THAT is the real start point......


Last edited by asmboy on Mon Dec 17, 2012 8:33 am; edited 1 time in total
Ttelmah



Joined: 11 Mar 2010
Posts: 19346

View user's profile Send private message

PostPosted: Mon Dec 17, 2012 8:18 am     Reply with quote

On the ADC, no, the interrupt is not what you want. What you have to do, is split the access through the cycles where you are doing other things. So (for instance), at one particular point in the BLDC cycle, you send the signal to say start conversion, and then at another point, which is at least the conversion time later, you read the value. This way the conversion has been done, without you ever having to wait.
Your problem is going to be the LCD. LCD's are _slow_. Realistically, you are either going to have to use a similar approach, splitting the LCD access, and never waiting for anything, but always ensuring that each part of the access will always only occur when the times needed by the last operation have passed, or (much simple), put the LCD control onto another chip.
Now, seriously, the motor needs to be handled _fast_, and at a very steady rate. Your floating point multiplication, will take perhaps 10* the time you have between pulses. The problem is that if a pulse is even a small fraction late/early, you will lose sync. Have a look at Microchip AN857, which shows how to have a 877A, handle a BLDC motor, including reading a pot to set the speed. This is fairly easy to code using CCS (I have done it), mine though is tweaked for a later PIC18, and a couple of things had to change because the CCP doesn't behave exactly the same.
45000RPM!.... Seriously, think again. Assuming a three phase motor, there is no way you are going to get to this sort of speed with a PIC16F877. If you want to get to even a large fraction of this (and the motor supports it), then look at using a proper BLDC motor control chip, and running this off the PIC. Are you sure the motor supports this?. There are quite a few low power motors that go to this sort of speed, and some BLDC fan motors, but revs like this get rarer and rarer as power outputs go up.

Best Wishes
asmboy



Joined: 20 Nov 2007
Posts: 2128
Location: albany ny

View user's profile Send private message AIM Address

PostPosted: Mon Dec 17, 2012 8:31 am     Reply with quote

you might check out the 18F4431

and google

18f4431 BLDC
A_L_E_X



Joined: 21 Feb 2010
Posts: 11

View user's profile Send private message

PostPosted: Mon Dec 17, 2012 8:41 am     Reply with quote

Quote:
Have a look at Microchip AN857, which shows how to have a 877A, handle a BLDC motor, including reading a pot to set the speed.

I did that but the code is in ASM and i don't know ASM. Is there a version in c ? It doesn't have to be for CCS, just c.
Ttelmah



Joined: 11 Mar 2010
Posts: 19346

View user's profile Send private message

PostPosted: Mon Dec 17, 2012 9:35 am     Reply with quote

No guarantees.
This was the original code I generated from the application note. However I never used it on this chip, switching to a PIC18, and then having to do a lot of changes (fault with AN3 on the chip, and the CCP behaves differently from a PIC16 one), so this was never tried:
Code:

//Code based on AN857 for BLDC

#include <16F877.h>
#device adc=16 //Left justify the ADC
#FUSES NOWDT, HS, NOBROWNOUT, NOLVP
#use delay(clock=16MHz)

#define ManThresh (0x3F) //Change to 0xFF for manual mode - 0x3F else - using 44 - almost immediate
#define AutoThresh (0x100-ManThresh)
int8 flags=0;
#bit DriveOnFlag=flags.0
#bit AutoRPM=flags.1
#bit FullOnFlag=flags.4
#bit Tmr0Ovf=flags.5
#bit Tmr0Sync=flags.6

int8 PhaseIndx=6,Drive,RPMIndex;
int8 ADCRPM;
int8 PWMThresh=0;
int8 ADCOffset;
int8 Vsupply;
int8 DeltaV1,DeltaV2;
int16 CCPSave,CCPT2;
int8 RampTimer;

#define AccelDelay 100
#define DecelDelay 10

#bit BEMF1Low=DeltaV1.7
#bit BEMF2Low=DeltaV2.7
#bit ADCneg=ADCOffset.7

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

#use fast_io(C)

/*
ADC channels are wired as:
AN0 = RPM I/P voltage
AN1 = Offset
AN3 = sense on OP/A BEMF etc..
*/

#define RPM (0)
#define OFFSET (1)
#define BEMF (3)

//Drive word definitions
#define OffMask (0B11010101)
#define Invalid (0B00000000)
#define Phase1  (0B00100001)
#define Phase2  (0B00100100)
#define Phase3  (0B00000110)
#define Phase4  (0B00010010)
#define Phase5  (0B00011000)
#define Phase6  (0B00001001)

//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)
#byte ADCON0=getenv("SFR:ADCON0") //0xFC2 //("SFR:ADCON0")
#byte ADRESH=getenv("SFR:ADRESH") //0xFC4 //("SFR:ADRESH")
#bit GO=ADCON0.1
#byte STAT=getenv("SFR:STATUS")
#bit CARRY=STAT.0
#bit ZERO=STAT.2
#byte TMR0=getenv("SFR:TMR0")
#byte CCP1CON=getenv("SFR:CCP1CON")
#bit SPECIAL_EVENT=CCP1CON.0

#include "T1Tableold.h" //You will need to use the code as shown and build this for your motor
//format is const int16 Timer1Table[256] = {xxx,xxx...};

   int save_w;
   #locate save_w=0x7f
   int save_status;
   #locate save_status=0x20

   #byte status = 3
   #bit zero_flag = status.2
   #bit t0if = 0xb.2

#INT_GLOBAL
void timer_0_int(void) {
   #asm
   //store current state of processor
   MOVWF save_w
   SWAPF status,W
   BCF   status,5
   BCF   status,6
   MOVWF save_status
   // Save anything else your code may change
   // You need to do something with PCLATH if you have GOTOs

   // remember to check to see what interrupt fired if using more than one!!

   // code for isr
   #endasm
   Tmr0Ovf=TRUE;
   Tmr0Sync=TRUE;
   clear_interrupt(INT_TIMER0); //fastest possible int handler - relies on instructions not changing flags etc..

   #asm
   // restore processor and return from interrupt
   SWAPF save_status,W
   MOVWF status
   SWAPF save_w,F
   SWAPF save_w,W
   #endasm
}

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); //Changing to start with my current reading
   STATE=RPMSetup;
}

void main(void)
{
   int8 temp;
   output_c(0);      //Motor drivers off to start....
   set_tris_c(0);
   //setup_adc_ports(sAN0|sAN1|sAN3);
   setup_adc(ADC_CLOCK_DIV_32);

   set_adc_channel(RPM);
   setup_spi(FALSE);
   setup_timer_0(T0_DIV_2);
   enable_interrupts(INT_TIMER0);
   setup_timer_1(T1_INTERNAL|T1_DIV_BY_4);
   setup_ccp1(CCP_COMPARE_RESET_TIMER);
   clear_interrupt(INT_TIMER0);
   enable_interrupts(GLOBAL);

   do {
      if (interrupt_active(INT_CCP1)) commutate();
      DriveOnFlag=TRUE;
      if (!FullOnFlag) {
         temp=(int8)PWMThresh+TMR0;
         if (!CARRY) DriveOnFlag=FALSE;
         DriveMotor();
      }
      LockTest();
      switch (STATE) {
      case RPMSetup:
         if (Drive==Phase1) {
            output_high(PIN_A4);
            read_adc(ADC_START_ONLY);
            set_adc_channel(OFFSET); //Offset default
            STATE++;
            Tmr0Sync=FALSE; 
            output_low(PIN_A4);
         }
         break;
      case RPMRead:
         if (adc_done()) {
            ADCRPM=ADRESH;
         }
         break;
      case OffsetSetup: //Wait for Phase2 ADC Go RA3->ADC
         if (Drive==Phase2) {
            read_adc(ADC_START_ONLY); //Trigger ADC
            set_adc_channel(BEMF);
            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) {
            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
         }
         break;
      case Inval:
         STATE=RPMSetup;
         //Status=0;
         set_adc_channel(RPM);
         break;
      }   
   } while(TRUE);
}

void DriveMotor(void) {
   if (DriveOnFlag) {
      output_c(Drive | 0x80);
   }
   else {
      output_c(Drive & OffMask);
   }
}

void LockTest(void) {
   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 (ADCRPM<=ManThresh) {
         AutoRPM=FALSE;
      }
      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;
      }
      //LT20 - else
      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;
      }
      //Showlocked
      output_high(PIN_B6);
      if(AutoRPM==FALSE) {
         //Manual code
         RPMIndex=ADCRPM;
      }
   }
}   

You'll have to use the spreadsheet in the application note, to build a a T1Tableold.h, to match your motor requirements.

You'll also have to add the instructions to save the registers to int_global (look at ex_glint.c), I'd already started assuming I was going to use the PIC18, which saves the critical registers automatically.

Have done this for you now....


Best Wishes
SherpaDoug



Joined: 07 Sep 2003
Posts: 1640
Location: Cape Cod Mass USA

View user's profile Send private message

PostPosted: Tue Dec 18, 2012 6:20 pm     Reply with quote

A_L_E_X wrote:
Quote:
Have a look at Microchip AN857, which shows how to have a 877A, handle a BLDC motor, including reading a pot to set the speed.

I did that but the code is in ASM and i don't know ASM. Is there a version in c ? It doesn't have to be for CCS, just c.


If you are going to do this sort of intense optimized embedded processor programming in C or any other language you MUST have at least a familiarity with ASM for the chip you are using. Spend some time exploring the instruction set and registers. It will be a good investment.
_________________
The search for better is endless. Instead simply find very good and get the job done.
PCM programmer



Joined: 06 Sep 2003
Posts: 21708

View user's profile Send private message

PostPosted: Tue Dec 18, 2012 7:22 pm     Reply with quote

If you want to Google for CCS code, then try a search string like this:
Quote:

18F4431 OR PIC18F4431 BLDC "#use delay"

You can also try it with PICs in the same family, such as the 18F2331,
18F2431 and 18F4331.
Display posts from previous:   
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion All times are GMT - 6 Hours
Page 1 of 1

 
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