//*******************************************************************************
// 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 );
}
Copyright © 2002-2006 SourceBoost Technologies