PWM2

This is a revision of David Barker's PWM Module posted previously. This module makes the following changes:

  • Added functions for control of the second PWM output connected to the CCP2 module
  • Changes to the original SetFreq() function
  • Added the SetFreqByTable() function.

The SetFreqByTable() function uses a constants table of PR2 and Timer2 Prescaler load values so the user can switch easily between optimum frequencies and duty cycle resolution. A small utility program has been created to simplify the creation of this array of constants which can be downloaded from HERE. A screenshot of this utility is provided below.


PWM.bas Revised 29.09.2007

{
*****************************************************************************
*  Name    : PWM.BAS                                                        *
*  Author  : David John Barker & Warren Schroeder                           *
*  Notice  : Copyright (c) 2007 Mecanique                                   *
*          : All Rights Reserved                                            *
*  Date    : 23/08/2007                                                     *
*  Version : 1.0                                                            *
*  Notes   : 29/09/2007 - Added support for 2 PWM outputs                   *                    
*                       - Added FreqSetByTable() function                   *
*                       - PWM Table Writer Utility available at             *
*                          http://circuit-ed.com/uplds/pwm_writer.exe       *
*****************************************************************************
}                                                                                                                 
Module PWM
Dim
   FMaxDuty As Word,
   FTMR2ON As T2CON.2
#if _device in (18F1220, 18F1320)
   Dim FPWM1Pin As PORTB.3    // RB3 connected to CCP1 module
#else
   Dim FPWM1Pin As PORTC.2    // RC2 connected to CCP1 module
   Dim FPWM2Pin As PORTC.1    // RC1 connected to CCP2 module
#endif
{
****************************************************************************
* Name    : Start1                                                         *
* Purpose : Start PWM Channel 1                                            *
****************************************************************************
}  
Public Sub Start1()
    CCP1CON = $0C
    Output(FPWM1Pin) 
    FTMR2ON = 1   
End Sub
{
****************************************************************************
* Name    : Start2                                                         *
* Purpose : Start PWM Channel 2                                            *
****************************************************************************
}  
Public Sub Start2()
    CCP2CON = $0C
    Output(FPWM2Pin) 
    FTMR2ON = 1   
End Sub
{
****************************************************************************
* Name    : Stop1                                                          *
* Purpose : Stop PWM Channel 1                                             *
****************************************************************************
}  
Public Sub Stop1()
   Input(FPWM1Pin)
   CCP1CON = $00
End Sub
{
****************************************************************************
* Name    : Stop2                                                          *
* Purpose : Stop PWM Channel 2                                             *
****************************************************************************
}  
Public Sub Stop2()
   Input(FPWM2Pin)
   CCP2CON = $00
End Sub
{
****************************************************************************
* Name    : SetDuty1                                                       *
* Purpose : The CCPR1L contains the eight MSbs And the CCP1CON<5:4>        *
*         : contains the two LSbs. This 10-Bit value is represented by     *
*         : CCPR1L:CCP1CON<5:4>.                                           *
****************************************************************************
} 	
Public Sub SetDuty1(pDuty As Word)
   CCP1CON.5 = pDuty.1
   CCP1CON.4 = pDuty.0	
   CCPR1L = pDuty >> 2
End Sub
{
****************************************************************************
* Name    : SetDuty2                                                       *
* Purpose : The CCPR2L contains the eight MSbs And the CCP2CON<5:4>        *
*         : contains the two LSbs. This 10-Bit value is represented by     *
*         : CCPR2L:CCP2CON<5:4>.                                           *
****************************************************************************
} 	
Public Sub SetDuty2(pDuty As Word)
   CCP2CON.5 = pDuty.1
   CCP2CON.4 = pDuty.0	
   CCPR2L = pDuty >> 2
End Sub
{
****************************************************************************
* Name    : SetDuty1Percent                                                *
* Purpose : Set the duty as a percentage for channel 1                     *
****************************************************************************
} 	
Public Sub SetDuty1Percent(pPercent As Byte)
   SetDuty1(FMaxDuty * pPercent / 100)
End Sub
{
****************************************************************************
* Name    : SetDuty2Percent                                                *
* Purpose : Set the duty as a percentage for channel 2                     *
****************************************************************************
} 	
Public Sub SetDuty2Percent(pPercent As Byte)
   SetDuty2(FMaxDuty * pPercent / 100)
End Sub
{
****************************************************************************
* Name    : MaxDuty                                                        *
* Purpose :                                                                *
****************************************************************************
}   
Public Inline Function MaxDuty() As FMaxDuty
End Function
{
****************************************************************************
* Name    : SetFreq                                                        *
* Purpose :                                                                *
* Notes   : Initializes Freq settings                                      * 
*         : Resets Duty Cycle To 0                                         *
*         : Requires PWM.Start afterward if PWM is not running             *               
****************************************************************************
} 
Public Function SetFreq(pFrequency As LongWord) As Boolean
   Const Fosc As LongWord = _clock * 1000000
   Dim Prescale As Byte
   Dim PR2Value, PRConst As Word

   // loop through all the valid prescalers...
   PRConst = Fosc / pFrequency / 4
   Prescale = 1
   Result = false
   Repeat
      PR2Value = PRConst / Prescale - 1        // calculate a PR2 value
      If PR2Value < 256 Then                   // if it is a valid value, then... 
         FMaxDuty = (PR2Value + 1) * 4         // determine maximum duty... 
	 PR2 = PR2Value                        // initialise PR2                 
         Select Prescale                       // configure T2CON prescale
            Case 1  : Prescale = %00000000     // prescale 1
	    Case 4  : Prescale = %00000001     // prescale 4
	    Case 16 : Prescale = %00000011     // prescale 16
         End Select
         SetDuty1(0)                           // output = 0V
         SetDuty2(0)                           //
         T2CON = (T2CON And 252) Or Prescale   // load prescaler value
         Result = true                         // function return true (success)         
         Exit                                  // exit the sub
      EndIf   
      Prescale = Prescale * 4
   Until Prescale > 16
End Function
{
****************************************************************************
* Name    : SetFreqByTable                                                 *
* Purpose : Change PWM frequency settings by indexing table                *
* Notes   : Initializes Freq settings                                      * 
*         : Resets Duty Cycle To 0                                         *
*         : Requires PWM.Start afterward if PWM is not running             *               
****************************************************************************
}
Public Sub SetFreqByTable(pIndex As Byte)
 Const pwmtbl(4) As Byte = (255,1,127,3)
  // (Fosc=48) 11719; 5859; 
 Dim prv As Byte

   pindex = pindex * 2                          // table offset value
   prv = pwmtbl(pindex)                         // get new PR2 value from array for frequency value
   FMaxDuty = (prv + 1) * 4                     // maximum duty cycle resolution based on PR2 value  
   PR2 = prv                                    // load PR2
   SetDuty1(0)                                  // output = 0V
   SetDuty2(0)                                  //
   T2CON = (T2CON And 252) Or pwmtbl(pindex+1)  // load prescaler value from array
End Sub

// initialise the module
FMaxDuty = 0

Here is example code that uses some of the newly added functions. This example uses an 18F2550 @ 48MHz. Potentiometers are connected to AN2 and AN3 which create the variable duty-cycle for PWM1 and PWM2 outputs. A pushbutton connected to GND and PORTB.0 is used to toggle between PWM frequencies of 11.75KHz and 5.8KHz which have resolutions of 1024 and 512, respectively. These frequencies were determined from a constants table created with the PWM_Writer Utility. The EasyPIC3 PIC development board was used for this test.

Device = 18F2550
Clock = 48

' set up config for 8MHz crystal and USB
Config USBDIV = 2
Config CPUDIV = OSC1_PLL2
Config PLLDIV = 2
Config FOSC   = HSPLL_HS

Include "pwm.bas"
Include "adc.bas"

Dim adc0,adc1 As Word
Dim tt As Byte

   TRISA = 15
   TRISB = 1
   tt = 0
   ADCON1 = 11                   // AN0..AN3 active ADC's
   INTCON2.7 = 0                 // RBPU active
   PWM.SetFreqByTable(tt)
   PWM.Start1
   PWM.Start2
   While true
      adc0 = ADC.Read(2)
      adc1 = ADC.Read(3)
      If tt = 0 Then
         PWM.SetDuty1(adc0)
         PWM.SetDuty2(adc1)
      Else
         PWM.SetDuty1(adc0 >> 1)
         PWM.SetDuty2(adc1 >> 1)
      End If
      If (PORTB And 1) = 0 Then
         Inc(tt)
         tt = tt And 1
         PWM.SetFreqByTable(tt)
         Repeat
           DelayMS(5)
         Until (PORTB And 1) <> 0
      EndIf     
   Wend