CMotor.c

Speed control for a single-phase AC motor using Microchip's Picdem MC demo board and BoostC compiler (by Dan McFarland).



//*******************************************************************************
//  PROGRAM          : V/F control of a Single Phase Induction Motor
//  MICROCONTROLLER  : PIC18F4431  
//*******************************************************************************
//*******************************************************************************
//  AUTHOR:  Dan McFarland   email: danmcfarland @ verizon dot net
//  10/1/05  
//
//  This code is written as a speed control for a single-phase AC motor using 
//  Microchip's Picdem MC demo board, using the BoostC compiler.  The Picdem MC 
//  demo kit does not include code for a single-phase motor, so this code is 
//  derived from the 3-phase .asm code that is supplied.
//
//  The algorithm used here is described in Microchip's application note AN890 
//  "3-Phase AC-Induction Motor Control Using the PIC18F4431"
//
//  By varying the frequency of supply to the Induction motor we can control the 
//  speed of the motor.
//
//*******************************************************************************
#include <system.h>
#include <icd2.h>
//*******************************************************************************

#pragma DATA    _CONFIG1H, 0x02  // _OSCS_OFF_1H & _HS_OSC_1H
#pragma DATA    _CONFIG2L, 0x0C  // _PWRTEN_ON_2L & _BOREN_ON_2L & _BORV_20_2L  
#pragma DATA    _CONFIG2H, 0x3E  // _WDTEN_OFF_2H
#pragma DATA    _CONFIG3L, 0x3C  // _PWMPIN_OFF_3L & _LPOL_LOW_3L & _HPOL_LOW_3L & _GPTREN_ON_3L
#pragma DATA    _CONFIG3H, 0x9D  // _FLTAMX_RC1_3H & _PWM4MX_RB5_3H

#pragma DATA    _CONFIG4L, 0x81  // Non-debug mode
//#pragma DATA    _CONFIG4L, 0x01  // Debug mode

#pragma DATA    _CONFIG5L, 0x0F 
#pragma DATA    _CONFIG5H, 0xC0  
#pragma DATA    _CONFIG6L, 0x0F 
#pragma DATA    _CONFIG6H, 0xE0 
#pragma DATA    _CONFIG7L, 0x0F 
#pragma DATA    _CONFIG7H, 0x40  

#define UCHAR unsigned char
#define UINT  unsigned int

// FLAGS bits
#define TIMER0_OV_FLAG    0
#define INC_INDEX1_FLAG   4
#define INC_INDEX2_FLAG   5

// FLAGS1 bits
#define DEBOUNCE          0
#define KEY_RS            1
#define KEY_PRESSED       3
#define RUN_STOP          4
#define RESET             5
#define FREQ_UPDATE       6

#define DEBOUNCE_COUNT  0xFF

// Delay parameters
#define DELAY_COUNT1    0xFF
#define DELAY_COUNT2    0xFF

// Duty cycle limit definition, for 20KHz @20MHz, 2uS deadtime
#define MINH_DUTY_CYCLE 0x00  // minimum duty cycle corresponds to 3 x deadtime = 3 x 2uS = 6uS  PDC = 6uS/(4/Fosc)
#define MINL_DUTY_CYCLE 0x3C  // 
#define MAXL_DUTY_CYCLE 0xE0  // E0 maximum duty cycle is 4 x PTPER
#define MAXH_DUTY_CYCLE 0x03

//*******************************************************************************
// RAM locations in Access bank, uninitialized
//*******************************************************************************
int   table_Offset1    ;  //           res 1   ;Phase1 offset to the Sine table(0)
int   table_Offset2    ;  //           res 1   ;Phase2 offset to the Sine table(120)
UCHAR flags            ;  //           res 1   ;Flags registers used to indicate different status
UCHAR flags1           ;  //           res 1
UCHAR freqRefH         ;  //           res 1   ;Reference Frequency input in counts
UCHAR freqRefL         ;  //           res 1
UINT  frequency        ;  //           res 1
UCHAR debounce_Counter ;  //           res 1
UCHAR PDC0L_Temp       ;  //           res 1
UCHAR PDC0H_Temp       ;  //           res 1
UCHAR PDC1L_Temp       ;  //           res 1
UCHAR PDC1H_Temp       ;  //           res 1

// Oscillator frequency
#define OSCILLATOR  20000000

// Timer0 prescaler
#define TIMER0_PRESCALE 40  // 30

// PWM frequency definition
#define TIMER2_PRESCALE 25  // 01
#define PWM_FREQUENCY   20000   // 20000

// number of entries in the sine table, or the sampling frequency
#define SINE_TABLE_ENTRIES 19

//                   1  2  3  4  5   6   7   8   9   10   11   12   13   14   15   16   17   18   19
UCHAR sine_Table[]={ 0, 0, 0, 3, 9, 20, 38, 68, 99, 130, 160, 189, 216, 236, 246, 252, 255, 255, 255 };

long samples_Per_Cycle;
long instruction_Cycle;
long frequency_Scale;

long  interrupt_countL;
long  interrupt_countH;

//*******************************************************************************
//*******************************************************************************
//             INITIALIZATION
//*******************************************************************************
//*******************************************************************************

volatile bit start_stop_key@PORTD.6;          // SW1
volatile bit reset_key@PORTD.7;               // SW2

volatile bit led1@PORTD.0;
volatile bit led2@PORTD.1;
volatile bit led3@PORTD.2;
volatile bit led4@PORTC.0;

volatile bit AdGo@ADCON0.GO;

// Prototypes:
void Init_Motor               ();
void Init_PortC               ();
void Init_PortD               ();
void Init_Tmr0                ();
void Init_PCPWM               ();
void Init_Hsadc               ();
void Key_Check                ();
void Init_Interrupts          ();
void Update_PWM_Dutycycles    ();
void Update_Table_Offset      ();
void Calculate_Timer0_Reload  ();
void Process_Key_Pressed      ();
void Check_Limits             ();
void Key_Debounce             ();
void Start_Motor              ();
void Stop_Motor               ();
void Flash_LEDs               ( UCHAR rate, UCHAR led );
void Scroll_LEDs              ( UCHAR rate );

void main()
{
  start:

  samples_Per_Cycle = (SINE_TABLE_ENTRIES-1)*2;
  instruction_Cycle =  OSCILLATOR/4;
  frequency_Scale   = (instruction_Cycle/samples_Per_Cycle)/(TIMER0_PRESCALE/4);

  trisc             = 10111110b;
  trisd             = 11010000b;
  interrupt_countL  = 0;
  interrupt_countH  = 0;
  frequency         = 0xF0;   // 0;
  flags             = 0;
  flags1            = 0;

  Init_Hsadc        ();
  Init_PCPWM        ();
  Init_Tmr0         ();

  // Just to show that we're alive.
  Flash_LEDs(100, 0xF);
  Scroll_LEDs(150);

  flags1 = 0;

//*******************************************************************************  
//*******************************************************************************
//                 MAIN LOOP
//*******************************************************************************
//*******************************************************************************

  while(1)
  {
    if ( flags & (1<<TIMER0_OV_FLAG) )        // back from Timer0 overflow?        btfss flags, TIMER0_OV_FLAG
    {
      Update_PWM_Dutycycles();                // Yes, update the PWM duty cycle with new value
      Update_Table_Offset();                  // Update offsets
      flags &= ~(1<<TIMER0_OV_FLAG);          // Clear the flag
    }

    if ( flags1 & (1<<FREQ_UPDATE) )          // check polarity btfsc flags1,FREQ_UPDATE      ;back from A/D overflow?
      Calculate_Timer0_Reload();              // only calculate new Timer0 reload if frequency has been updated

    if ( AdGo == 0 )                          // If AD Conversion is complete
      AdGo = 1;                               //  then start a new conversion

    Key_Check();                              // call  KEY_CHECK       ;Check keys change
    Process_Key_Pressed();

    if ( flags1 & (1<<RESET) )
    {
      Stop_Motor();

      // Disable interrupts:
      clear_bit ( intcon, GIEL );
      clear_bit ( intcon, GIEH );

      goto start;
    }
  }
}

//*******************************************************************************
//*******************************************************************************
//               INTERRUPT SERVICE ROUTINES
//*******************************************************************************
//*******************************************************************************

//*******************************************************************************
// High priority interrupt service routine
// Timer0 overflow are checked
//*******************************************************************************

void interrupt( )                           // interrupt handler (high priority for PIC18) 
{
  if ( intcon & (1<<TMR0IF) )               // Timer0 overflow Interrupt? 
  {
    if ( interrupt_countH++ & 0x20 )
      led3 = 0;
    else
      led3 = 1;

    tmr0h = freqRefH;                       // Load the Higher byte of SpeedCommand to TMR0H
    tmr0l = freqRefL;                       // Load the Lower byte of SpeedCommand to TMR0L

    set_bit  ( flags,  TIMER0_OV_FLAG );    //
    clear_bit( intcon, TMR0IF );            // Clear the interrupt flag.  Infinite loop if this doesn't get done!!!!!
  }
}

//*******************************************************************************
//  Low priority interrupt service routine
//*******************************************************************************

void interrupt_low( )                       // low priority interrupt handler (PIC18 only) 
{
  if ( pir1 & (1<<ADIF) )                   //  HSADC Interrupt?
  {
    if ( interrupt_countL++ & 0x80 )
      led4 = 1;
    else
      led4 = 0;

    frequency = adresh;                     //  first value is group A, assigned to AN8, current measurement
                                            //  Minimum Frequency set to 5Hz (scaling factor X4) 
    if ( frequency < 0x20 )                 //  greater than W: cpfsgt  frequency  ; if frequency is less than or equal to 5Hz..
      frequency = 0x20;                     //  set it to 5Hz

                                            //  Limiting V/F to F = 60Hz (scaling factor X4) 
    if ( frequency > 0xF0 )                 //  if frequency is greater than or equal to 60Hz..
      frequency = 0xF0;                     //  set it to 60Hz

    clear_bit (pir1,   ADIF);               //  ADIF flag is cleared for next interrupt
    set_bit   (flags1, FREQ_UPDATE);        //    set flag to indicate frequency has been updated
  }
}

//*******************************************************************************
//*******************************************************************************
//             MOTOR DRIVE SUBROUTINES
//*******************************************************************************
//*******************************************************************************

//*******************************************************************************
// UPDATE_PWM_DUTYCYCLES
//
// This routine will update the PWM duty cycle on CCPx according to the 
// offset to the table with 0-120-240 degrees.
// This routine scales the PWM value from the table based on the frequency to keep V/F
// constant.
//*******************************************************************************
// UPDATE_PWM_DUTYCYCLES

void Update_PWM_Dutycycles ()
{
  UINT pdc_a;
  UINT pdc_b;

  // First phase
  pdc_b = sine_Table[table_Offset1] * frequency;
  pdc_a = pdc_b >> 6;                         // Keep only the upper 10 bits.

  // using two separate variables, because shifts will screw up the contents of the register with BoostC.  
  pdc_b = pdc_a;

  // Split up multiply result into upper and lower bytes for registers
  PDC0H_Temp = (UCHAR)( pdc_b >> 8 );
  PDC0L_Temp = (UCHAR)( pdc_a & 0xFF );

  // Second phase
  pdc_b = sine_Table[table_Offset2] * frequency;
  pdc_a = pdc_b >> 6;                         // Keep only the upper 10 bits.

  pdc_b = pdc_a;

  // Split 16 bits into two bytes:
  PDC1H_Temp = (UCHAR)( pdc_a >> 8 );
  PDC1L_Temp = (UCHAR)( pdc_b & 0xFF);

  Check_Limits();

  set_bit( pwmcon1, UDIS );                   // Disable updates to duty cycle and period

  pdc0l = PDC0L_Temp;
  pdc0h = PDC0H_Temp;

  pdc1l = PDC1L_Temp;
  pdc1h = PDC1H_Temp;

  clear_bit( pwmcon1, UDIS );                 // Enable updates to duty cycle and period to update simultaneously
}


//*******************************************************************************
// UPDATE_TABLE_OFFSET
//
// This routine Updates the offset pointers to the table after every access
//*******************************************************************************

void Update_Table_Offset()
{
  if ( flags & (1<<INC_INDEX1_FLAG) )
  {
    table_Offset1++;

    if ( table_Offset1 >= SINE_TABLE_ENTRIES )
    {
      table_Offset1--;
      clear_bit(flags, INC_INDEX1_FLAG);
    }
  }
  else
  {
    table_Offset1--;

    if ( table_Offset1 < 0 )
    {
      table_Offset1++;
      set_bit(flags, INC_INDEX1_FLAG);
    }
  }

  if ( flags & (1<<INC_INDEX2_FLAG) )
  {
    table_Offset2++;

    if ( table_Offset2 >= SINE_TABLE_ENTRIES )
    {
      table_Offset2--;
      clear_bit(flags, INC_INDEX2_FLAG);
    }
  }
  else
  {
    table_Offset2--;

    if ( table_Offset2 < 0 )
    {
      table_Offset2++;
      set_bit(flags, INC_INDEX2_FLAG);
    }
  }
}


//*******************************************************************************
//  CALCULATE_TIMER0_RELOAD
//
// This routine calculates the Timer0 reload value based on ADC read value and the 
// scaling factor calculated based on the main clock and number of Sine table entries.
// Timer0 value = FFFF - (frequency_SCALE/frequency)  frequency = (adc result)
//*******************************************************************************

void Calculate_Timer0_Reload()
{
  long timer0;
  long tempTimer;

  timer0 = 0xFFFF - ( frequency_Scale/frequency );

  tempTimer = timer0;

  freqRefH = (UCHAR)(tempTimer >> 8)& 0xFF;
  freqRefL = (UCHAR)(timer0 & 0xFF);
}


//*******************************************************************************
// CHECK_LIMIT ROUTINE
//    for frequency < 60Hz, duty cycle will be less than MAX_DUTY_CYCLE (4 x PTPER)due to 
//    the selection of sine table values it is still necessary to ensure that PDC is 
//    greater or equal to MINL_DUTY_CYCLE (3 x deadtime).
//
//*******************************************************************************

void Check_Limits()
{
  if ( PDC0H_Temp == 0 )
    if ( PDC0L_Temp < MINL_DUTY_CYCLE )
      PDC0L_Temp = MINL_DUTY_CYCLE;

  if ( PDC1H_Temp == 0 )
    if ( PDC1L_Temp < MINL_DUTY_CYCLE )
      PDC1L_Temp = MINL_DUTY_CYCLE;
}


//*******************************************************************************
// This routine stops the motor by driving the PWMs to 0% duty cycle.
//*******************************************************************************

void Stop_Motor ()
{
  clear_bit( pie1, ADIE );
  clear_bit( intcon, TMR0IE );
  ovdcond = 0;                                // OVDCOND overrides PWM outputs.
  table_Offset1 = 0;
  table_Offset2 = 0;
  clear_bit ( flags, TIMER0_OV_FLAG );
}


//*******************************************************************************
// This routine starts motor from previous stop with motor parameters initialized
//*******************************************************************************

void Start_Motor ()
{
  set_bit ( flags1, RUN_STOP );
  set_bit ( pie1, ADIE );

  Init_Motor();
  Update_PWM_Dutycycles();
  Update_Table_Offset();

  set_bit ( intcon, TMR0IE );

  ovdcond = 0xFF;
}


//*******************************************************************************
//*******************************************************************************
//             KEY SWITCH SUBROUTINES
//*******************************************************************************
//*******************************************************************************

//*******************************************************************************
// This routine checks for the state of the Run/Stop and reset button.  The Reset
// button on the Picdem MC board does not do anything, so SW2 is set up to be a
// software reset.
//*******************************************************************************

void Key_Check()
{
  if ( !reset_key )
  {
    set_bit ( flags1, RESET );
  }

  if ( !start_stop_key )                      // Depressed button == low
  {
    if ( flags1 & (1<<DEBOUNCE) )             // Blow out if the DEBOUNCE flag is set -- Now we're waiting for the button to be released
      return;
    else
    {
      Key_Debounce();                         // Key_Debounce sets flags1, DEOUNCE after it gets called 256 times
      if ( !(flags1 & (1<<DEBOUNCE) ) )
        return;
      else
        set_bit( flags1, KEY_RS );            // Finally all debounced, now set the Run/Stop bit.
      return;
    }
  }
  else                                        // Button not depressed == high
  {
    if ( !(flags1 & (1<<DEBOUNCE)) )          // Blow out if the DEBOUNCE flag is clear
      return;
    else                                      // Button's released after having been depressed for 256 iterations of Key_Debounce().      
    {
      clear_bit( flags1, DEBOUNCE );          // Don't need the DEBOUNCE flag any more
      set_bit( flags1, KEY_PRESSED );

      if ( flags1 & (1<<KEY_RS) )             // Toggle the state of the RUN_STOP bit.
      {
        if ( flags1 & (1<<RUN_STOP) )
          clear_bit( flags1, RUN_STOP );
        else
          set_bit( flags1, RUN_STOP );
      }
    }
  }
}


//*******************************************************************************
void Key_Debounce()
{
  debounce_Counter--;

  if ( debounce_Counter != 0 )
  {
    set_bit( flags1, DEBOUNCE );
    debounce_Counter = DEBOUNCE_COUNT;
  }
}


//*******************************************************************************
void Process_Key_Pressed()
{
  if ( flags1 & (1<<KEY_PRESSED) )
  {
    if ( flags1 & (1<<KEY_RS) )               // Was the run/stop key pressed?
    {
      if ( flags1 & (1<<RUN_STOP) )           // RUN/STOP pressed, what state should we go to?
        Start_Motor ();
      else
        Stop_Motor ();

      clear_bit ( flags1, KEY_PRESSED );
      clear_bit ( flags1, KEY_RS );

      return;
    }
  }
}


//*******************************************************************************
//  Scroll_LEDs - Just to have some unique feedback.
//*******************************************************************************

void Scroll_LEDs ( UCHAR rate )
{
  bit tmp_led4;
  UCHAR i;

  led1 = 1;
  led2 = 0;
  led3 = 0;
  led4 = 0;

  for ( i = 1; i < 20; i++ )
  {
    delay_ms(rate);
    tmp_led4 = led4;
    led4=led3;
    led3=led2;
    led2=led1;
    led1=tmp_led4;
  }

  led1 = 0;
  led2 = 0;
  led3 = 0;
  led4 = 0;

}


//*******************************************************************************
//  Flash_LEDs - For some unique feedback, mostly for debugging.
//*******************************************************************************

void Flash_LEDs ( UCHAR rate, UCHAR led )
{
  UCHAR i;

  for ( i = 1; i < 20; i++ )
  {
    delay_ms(rate);

    led1 = led    & 1;
    led2 = led>>1 & 1;
    led3 = led>>2 & 1;
    led4 = led>>3 & 1;

    delay_ms(rate);

    led1 = 0;
    led2 = 0;
    led3 = 0;
    led4 = 0;
  }
}


//*******************************************************************************
//*******************************************************************************
//             INITIALIZATION SUBROUTINES
//*******************************************************************************
//*******************************************************************************

//*******************************************************************************
// Initialize High-Speed ADC
//*******************************************************************************

void Init_Hsadc ()
{
  adcon1 = 0;
  adcon2 = 0x32;                              // 0011 0010 : AQT2,1 == 11 (A/D acquistion time == 6 TAD), 
                                              //             ADCS == 010 == A/D conversion clock select Fosc/32
                                              //             A/D output format == left justified

  adcon3 = 0x40;                              // 0100 0000 : Interrupt is generated when the 2nd & 4th words are written to the buffer

  adchs  = 0x04;                              // A/D Channel Select : AN6 selected
  ansel0 = 0x43;                              // AN6 == Analog input, AN1, AN0 == Analog input.  All others are digital I/O
  trisa  = 0x03;                              // RA1, RA0 == Inputs, all others output.
  set_bit( trise, 0 );
  ansel1 = 0x01;
  set_bit( trise, 2 );
  adcon0 = 0x05;
}


//*******************************************************************************
// Initialize PCPWM
//  NOTES:
//    1)  PTPER has 12-bit resolution, 4 LSBs of PTPERH and 8 bits of PTPERL
//    2)  In edge aligned mode, PTMR reset to zero on match with PTPER
//    3)  PDC has 14-bit resolution, 6 LSBs of PDCxH and 8 bits of PDCxL
//    4)  Lower 2 bits of PDC compared to Q clocks
//
//    5)  Resolution(of duty cycle)= log((Fosc/4)/Fpwm)/log(2) = log(5Mhz/20kHz)/log(2) = 8 bits
//        so 6 LSBs of PDCxH and 2 MSBs of PDCxL will be used.
//        (for 16kHz, resolution = log(5Mhz/16kHz)/log(2) = 8 bits, also.)
//
//*******************************************************************************

void Init_PCPWM ()
{
  ptcon0      = 0;
  ptperl      = 0xF9;
  ptperh      = 0;

  pwmcon0     = 0x40;
  pwmcon1     = 0x01;

  dtcon       = 0x0A;

  ovdcond     = 0xFF;
  ovdcons     = 0;

  fltconfig   = 0x91;

  sevtcmpl    = 0;
  sevtcmph    = 0;

  set_bit ( ptcon1, PTEN );
}


//*******************************************************************************
// Initialize Timer0
//*******************************************************************************

void Init_Tmr0 ()
{
  t0con = 10000100b;
  tmr0h = 0xF8;
  tmr0l = 0x5E;
}


//*******************************************************************************
// Initialize interrupts
//*******************************************************************************

void Init_Interrupts ()
{
  set_bit ( intcon , TMR0IE );                // Enable Timer0 overflow Interrupt
  set_bit ( intcon2, TMR0IP );                // Timer0 overflow Interrupt high priority

  asm nop;

  set_bit   ( pie1, ADIE );                   // ADConverter Interrupt enable (will trigger on 2nd and 4th writes to FIFO)
  clear_bit ( ipr1, ADIP );                   // make sure it's low priority

  asm nop;

  rcon = 10010011b;                           // RCON == Reset CONtrol.
                                              // == IPEN | !RI | !POR | !BOR
                                              // IPEN == Enable Interrupt Priority Levels
                                              // !RI  == Reset Instruction Flag Bit.  0 == Hardware reset occurred.  Must be set in firmware after a brown out reset.
                                              // !POR == Power On Reset Flag Bit.     0 == Power on reset occurred.  Must be set in firmware after a power on reset.
                                              // !BOR == Brown Out Reset Flag Bit.    0 == Brown-out reset occurred. Must be set in firmware after a brown out reset.
  asm nop;

  set_bit ( intcon, GIEL );                   // Enable low priority interrupts   
  set_bit ( intcon, GIEH );                   // Enable high priority interrupts
}


//*******************************************************************************
// Initialize Motor
//*******************************************************************************

void Init_Motor()
{
  Init_Interrupts();

  table_Offset1 = 0x03;
  set_bit   ( flags, INC_INDEX1_FLAG );

  table_Offset2 = 0x11;
  clear_bit ( flags, INC_INDEX2_FLAG );

  frequency   = 0x30;

  freqRefH    = 0xFD;
  tmr0h       = freqRefH;
  freqRefL    = 0x2C;
  tmr0l       = freqRefL;

  set_bit ( flags, TIMER0_OV_FLAG );
}



http://www.sourceboost.com/home.html

Copyright © 2002-2006 SourceBoost Technologies