PIC32MX: PWM Motor Control
Pulse Width Modulation, or PWM, is a technique used to vary the average magnitude of a signal by changing its duty cycle (the proportion of time that a signal is active or "high"). For a more in-depth introduction to PWM motor control click here.
PWM for PIC32 is discussed in more detail in the Microchip Output Compare documention.
Available Pins
The pins available for PWM are 5 input pins (OC1, OC2, OC3, OC4, and OC5) and 2 output pins (OCFA and OCFB). The output pins are for fault pin protection.
General Approach
PWM can be set up by either changing directly the special function registers or using the functions in outcompare.h (a header file included in the peripheral library (plib.h). The latter case is more straightforward and easier. This wiki describes how to use the outcompare functions. The register bits approach is described in detail in theMicrochip Output Compare documention.
There are four main functions that are used for PWM.
void OpenOCX( config, value1, value2) void OpenTimerX(config, period) unsigned int SetDCOCXPWM(dutycycle) void CloseOCX();
where X is the module (1-5) or timer(2-3) that you want to use. Each of these functions are described below.
OpenOCX configures the OCX module and loads the R(value2) and RS (value1) registers with default values. The OCxR register is a read only slave duty cycle register. OCxRS is a buffer register that is written by the user to update the PWM duty cycle. At the end of a PWM period, OCxR is loaded with the contents of OCxRS. An example is shown below:
OpenOC1( OC_ON | OC_TIMER2_SRC | OC_PWM_FAULT_PIN_DISABLE, 0, 0);
The different configuration constants are shown below. If not specified, the default configuration is used. These constants are mutually exclusive for each category (ie you can only use one of them).
Config Constant | Description |
---|---|
OC_ON | Turns the Module ON |
OC_OFF | Default - Turns the Module Off |
Stop-in-idle control | |
OC_IDLE_STOP | stop in idle mode |
OC_IDLE_CON | Default - continue operation in idle mode |
16/32 bit mode | |
OC_TIMER_MODE32 | Use 32 bit Mode |
OC_TIMER_MODE16 | Default - Use 16 bit Mode |
Timer select | |
OC_TIMER3_SRC | Timer 3 is clock source |
OC_TIMER2_SRC | Default - Timer 2 is clock source |
Operation mode select | |
OC_PWM_FAULT_PIN_ENABLE | PWM Mode on OCx, fault pin enabled |
OC_PWM_FAULT_PIN_DISABLE | PWM Mode on OCx, fault pin disabled |
OC_CONTINUE_PULSE | Generates Continuous Output pulse on OCx Pin |
OC_SINGLE_PULSE | Generates Single Output pulse on OCx Pin |
OC_TOGGLE_PULSE | Compare toggles OCx pin |
OC_HIGH_LOW | Compare1 forces OCx pin Low |
OC_LOW_HIGH | Compare1 forces OCx pin High |
OC_MODE_OFF | Default - OutputCompare x Off |
For the operation mode, to use PWM one of the first two constants must be used.
The output compare modules use either Timer 2 (default) or Timer 3. These must be set up using OpenTimerX function, where X is either 2 or 3. The period can be between 0 and 0xFFFF inclusive. The OC can be configured to use a 32 bit timer in which both Timer 2 and Timer 3 are used. An example is shown below:
OpenTimer2( T2_ON | T2_PS_1_1 | T2_SOURCE_INT, 0xFFFF);
The first input sets up the timer and the second variable is the period value. The PWM period is dependent on the Period Value (PR), the Peripheral Bus Period and the pre-scalar as shown below:
PWM Period = [(PR + 1) Tpb (TMR_Prescaler_Value)]
The configuration constants are shown below for Timer 2. Replace the 2 with a 3 for Timer 3.
Config Constant | Description |
---|---|
T2_ON | Turns the Timer ON |
T2_OFF | Default - Turns the Timer Off |
Stop-in-idle control | |
T2_IDLE_STOP | stop in idle mode |
T2_IDLE_CON | Default - continue operation in idle mode |
Timer gate control | |
T2_GATE_ON | Timer Gate accumulation mode ON |
T2_GATE_OFF | Default - Timer Gate accumulation mode OFF |
Prescale values | |
T2_PS_1_256 | Prescaler 1:256 |
T2_PS_1_64 | 1:64 |
T2_PS_1_32 | 1:32 |
T2_PS_1_16 | 1:16 |
T2_PS_1_8 | 1:8 |
T2_PS_1_4 | 1:4 |
T2_PS_1_2 | 1:2 |
T2_PS_1_1 | Default - 1:1 |
32-bit or 16-bit | |
T2_32BIT_MODE_ON | Enable 32-bit mode |
T2_32BIT_MODE_OFF | Default |
Sync external clock option | |
T2_SOURCE_EXT | External clock source |
T2_SOURCE_INT | Internal Clock source |
The duty cycle can be updated by using SetDCOCXPWM(dutycycle) where X is the module. The duty cycle gets updated on the next cycle. The duty cycle must be less than or equal to the period value register defined in Timer 2 or Timer 3. An example is shown below:
SetDCOC1PWM(PR2 / 2);
The Output Compare module can be closed as shown below:
CloseOC1();
Unidirectional Motor Control
This section will detail how to set up 2 simple programs and circuit to control a motor using a PIC microcontroller and PWM. The first program shows how to use a button to switch between 2 duty cycles and the second program shows how to use a timer interrupt to change the duty cycle.
Button Code
This code is based off the NU32 board and hardware profile. If a button called swUser is not defined, the button should be set up. When the button is pushed, the motor will turn off
#include "HardwareProfile.h" int main(void) { // Configure the proper PB frequency and the number of wait states SYSTEMConfigPerformance(SYS_FREQ); // init OC1 module OpenOC1( OC_ON | OC_TIMER2_SRC | OC_PWM_FAULT_PIN_DISABLE, 0, 0); // init Timer2 mode and period (PR2) (frequency of 1220 Hz) OpenTimer2( T2_ON | T2_PS_1_1 | T2_SOURCE_INT, 0xFFFF); while(1) { if (swUser == 0) { SetDCOC1PWM(PR2 / 2); // Turns on the motor with 50% duty cycle } else { SetDCOC1PWM(0); // Turns off the motor } } CloseOC1(); } //end main
Timer Interrupt Code
This code uses an interrupt with Timer 2 to update the duty cycle. The motor ramps up and down.
/** INCLUDES *******************************************************/ #include "HardwareProfile.h" unsigned int Pwm; // variable to store calculated PWM value unsigned int Mode = 0; // variable to determine ramp up or ramp down # define MAX_DUTY 3999 int main(void) { // Configure the proper PB frequency and the number of wait states SYSTEMConfigPerformance(SYS_FREQ); // Allow vector interrupts INTEnableSystemMultiVectoredInt(); // init OC1 module OpenOC1( OC_ON | OC_TIMER2_SRC | OC_PWM_FAULT_PIN_DISABLE, 0, 0); // init Timer2 mode and period (PR2) (frequency of 1 / 20 kHz = (3999 + 1) / 80MHz * 1 OpenTimer2( T2_ON | T2_PS_1_1 | T2_SOURCE_INT, MAX_DUTY); mT2SetIntPriority( 7); // set Timer2 Interrupt Priority mT2ClearIntFlag(); // clear interrupt flag mT2IntEnable( 1); // enable timer2 interrupts while(1) {} CloseOC1(); } //end main void __ISR( _TIMER_2_VECTOR, ipl7) T2Interrupt( void) { if ( Mode ) { if ( Pwm <= MAX_DUTY ) // ramp up mode { Pwm ++; // If the duty cycle is not at max, increase SetDCOC1PWM(Pwm); // Write new duty cycle } else { Mode = 0; // PWM is at max, change mode to ramp down } } // end of ramp up else { if ( Pwm > 0 ) // ramp down mode { Pwm --; // If the duty cycle is not at min, increase SetDCOC1PWM(Pwm); // Write new duty cycle } else { Mode = 1; // PWM is at min, change mode to ramp up } } // end of ramp down // clear interrupt flag and exit mT2ClearIntFlag(); } // T2 Interrupt
Associated Circuitry
The Uni-Directional PWM circuit is described in Driving using a single MOSFET . The PIC32 can not supply enough current to open the gate of the MOSFET. Add a buffer between the PIC32 output compare pin and the gate of the MOSFET. A non-inverting chip works well.
Bidirectional Motor Code
This example code extends the ramp example for bidirectional control of a motor using an H-bridge.
/******************************************************************** Simple PWM example for bidirectional motor control with H-bridge. Duty cycle ramps up and down with Timer 2. This example uses an ISR with Timer 2. The duty cycle gets updated each PWM cyle. Direction is determined by DIRECTION_PIN (A3 for this example). The H-bridge is enabled with ENABLE_PIN (A2 for this example). Andrew Long Northwestern University 7-30-2009 ********************************************************************/ /** INCLUDES *******************************************************/ #include "HardwareProfile.h" #define FORWARD 0 #define REVERSE 1 #define ENABLE_PIN LATCbits.LATC1 #define DIRECTION_PIN LATCbits.LATC2 #define MAX_DUTY 3999 unsigned int Pwm; // variable to store calculated PWM value unsigned int Mode; // variable to determine ramp up or ramp down int main(void) { // Configure the proper PB frequency and the number of wait states SYSTEMConfigPerformance(SYS_FREQ); // Allow vector interrupts INTEnableSystemMultiVectoredInt(); mInitAllLEDs(); //Set Enable and Direction Pins (A2, A3) as digital outputs // Initialize as low LATC &= 0xFFF; TRISA &= 0xFFF3; DIRECTION_PIN = FORWARD; Pwm = 0; Mode = 1; // init OC1 module OpenOC1( OC_ON | OC_TIMER2_SRC | OC_PWM_FAULT_PIN_DISABLE, 0, 0); // init Timer2 mode and period (PR2) 20 kHz freq ( 1 / 20 kHz = (3999 + 1) / 80MHz * 1 OpenTimer2( T2_ON | T2_PS_1_1 | T2_SOURCE_INT, MAX_DUTY); mT2SetIntPriority( 7); // set Timer2 Interrupt Priority mT2ClearIntFlag(); // clear interrupt flag mT2IntEnable( 1); // enable timer2 interrupts ENABLE_PIN = 1; // Enable the H-bridge while(1) { } CloseOC1(); } //end main void __ISR( _TIMER_2_VECTOR, ipl7) T2Interrupt( void) { if ( Mode ) { if ( Pwm <= MAX_DUTY ) // ramp up mode { mLED_1_On(); mLED_2_Off(); Pwm ++; // If the duty cycle is not at max, increase if (DIRECTION_PIN == FORWARD) { SetDCOC1PWM(Pwm); // Write new duty cycle } else { mLED_0_On(); //SetDCOC1PWM(Pwm); SetDCOC1PWM(MAX_DUTY - Pwm); } } else { Mode = 0; // PWM is at max, change mode to ramp down } } // end of ramp up else { if ( Pwm > 0 ) // ramp down mode { mLED_2_On(); mLED_1_Off(); Pwm --; // If the duty cycle is not at min, increase if (DIRECTION_PIN == FORWARD) { SetDCOC1PWM(Pwm); // Write new duty cycle } else { mLED_0_Off(); //SetDCOC1PWM(Pwm); SetDCOC1PWM(MAX_DUTY - Pwm); } } else { Mode = 1; // PWM is at min, change mode to ramp up DIRECTION_PIN = !DIRECTION_PIN; } } // end of ramp down // clear interrupt flag and exit mT2ClearIntFlag(); } // T2 Interrupt
Associated Circuitry
This pdf is the circuit using the L298 H-bridge.