//#define TESTING
/****************************************************************************
Module
PWM_PIC32.c
Description
Implementation file for the PWM Library for the PIC32
Notes
Sets the Timer2/3 clock rate to PBClk/8 which in our case gives a 2.5MHz
clock rate for the PWM/Pulse generation. This still allows at least 1%
PWM resolution up to PWM frequencies of 25kHz
History
When Who What/Why
-------------- --- --------
10/19/21 jec Started conversion from Tiva version
*****************************************************************************/
/*----------------------------- Include Files -----------------------------*/
#include <xc.h>
#include <stdbool.h>
#include "PWM_PIC32.h"
/*----------------------------- Module Defines ----------------------------*/
#define MAX_NUM_CHANNELS 5
// the standard PBClk rate for ME218
#define PBCLK_RATE 20000000L
// TIMERx divisor for PWM, standard value is 8, to give maximum resolution
#define TIMER_DIV 8
// TICS_PER_MS assumes a 20MHz PBClk /8 = 2.5MHz clock rate
#define TICS_PER_MS 2500
// a servo wants to see a 50Hz or 20ms period
#define SERVO_PERIOD (20*TICS_PER_MS)
// these limits derive from a 20Mhz PBClk divided by 8 and a 16 bit timer
#define MIN_FREQ 24
#define MAX_FREQ 25000
// this limit guarantees 1% resolution in the duty cycle
#define MIN_PERIOD 100
// there are only 5 pins defined for each output on our chip
#define MAX_PINS_PER_OUTPUT 5
/*------------------------------ Module Types -----------------------------*/
/*---------------------------- Module Functions ---------------------------*/
static bool IsPinLegalForChannel(PWM_PinMap_t WhichPin, uint8_t WhichChannel);
static bool IsChannelIllegal( uint8_t whichChannel );
/*---------------------------- Module Variables ---------------------------*/
static int8_t MaxConfiguredChannel = -1; // init to illegal value
// local store of timer periods used when calculating duty cycle
static uint16_t T2Period;
static uint16_t T3Period;
static uint8_t LocalDuty[MAX_NUM_CHANNELS] = {0,0,0,0,0};
// these arrays are used in mapping between channel number (1-5) and the
// addresses of various registers
static uint32_t volatile * const ChannelTo_pControlReg[MAX_NUM_CHANNELS]={
&OC1CON,&OC2CON,&OC3CON,&OC4CON,&OC5CON};
static uint32_t volatile * const ChannelTo_pOCRS_Reg[MAX_NUM_CHANNELS]={
&OC1RS,&OC2RS,&OC3RS,&OC4RS,&OC5RS};
static uint32_t volatile * const ChannelTo_pOCR_Reg[MAX_NUM_CHANNELS]={
&OC1R,&OC2R,&OC3R,&OC4R,&OC5R};
// which timer is in use by a channel? defaults to timer2
static uint32_t volatile * ChannelTo_pTimer[MAX_NUM_CHANNELS]={
&PR2,&PR2,&PR2,&PR2,&PR2};
// the lists of legal pin numbers for the output channels
static PWM_PinMap_t const LegalOutPins[][5] = {{ PWM_RPA0, PWM_RPB3, PWM_RPB4,
PWM_RPB7, PWM_RPB15 },
{ PWM_RPA1, PWM_RPB1, PWM_RPB5,
PWM_RPB8,PWM_RPB11 },
{ PWM_RPA3, PWM_RPB0, PWM_RPB9,
PWM_RPB10,PWM_RPB14 },
{ PWM_RPA2, PWM_RPA4, PWM_RPB2,
PWM_RPB6, PWM_RPB13 },
{ PWM_RPA2, PWM_RPA4, PWM_RPB2,
PWM_RPB6, PWM_RPB13 }
};
// these are the output mapping registers indexed by the PWM_PinMap_t value
static volatile uint32_t * const outputMapRegisters[] = { &RPA0R, &RPA1R,
&RPA2R, &RPA3R, &RPA4R,
&RPB0R, &RPB1R, &RPB2R, &RPB3R, &RPB4R, &RPB5R,
&RPB6R, &RPB7R, &RPB8R, &RPB9R, &RPB10R, &RPB11R, &RPB12R,
&RPB13R, &RPB14R, &RPB15R
};
// these are the constants used to map OCx channel constants to output pins
static uint32_t const mapChannel2PinSelConst[] = { 0b0101/*OC1*/, 0b0101/*OC2*/,
0b0101/*OC3*/, 0b0101/*OC4*/,
0b0110/*OC5*/
};
static PWM_PinMap_t const LegalOC2OutPins[] = { PWM_RPA1, PWM_RPB1, PWM_RPB5,
PWM_RPB8,PWM_RPB11
};
static PWM_PinMap_t const LegalOC3OutPins[] = { PWM_RPA3, PWM_RPB0, PWM_RPB9,
PWM_RPB10,PWM_RPB14
};
static PWM_PinMap_t const LegalOC4_5OutPins[] = { PWM_RPA2, PWM_RPA4, PWM_RPB2,
PWM_RPB6,PWM_RPB13
};
/*------------------------------ Module Code ------------------------------*/
/****************************************************************************
Function
PWMSetup_BasicConfig
1) Configures both Timer2 & Timer3 for /8 from PBClk as clock source
2) Sets up both Timer2 & Timer3 to a 50Hz frequency/ 20ms period
3) Sets the requested OCx channels to PWM mode.
4) Enables the requested OCx channels and both Timer2 & Timer3
Further function calls from the PWM HAL will be necessary to complete
the setup.
****************************************************************************/
bool PWMSetup_BasicConfig(uint8_t HowMany){
uint8_t i;
bool ReturnVal = true;
//sanity check
if ((0 == HowMany) || (HowMany > MAX_NUM_CHANNELS))
{
ReturnVal = false;
}else
{ // Requested number of channels is legal, so let's get things set up
MaxConfiguredChannel = HowMany; // note how many we have configured
// start by turning Timer2 off
T2CONbits.ON = 0;
// base Timer2 on PBClk/8
T2CONbits.TCS = 0; // use PBClk as clock source
T2CONbits.TCKPS = 0b011; // divide by 8
PR2 = SERVO_PERIOD; // default to the servo rate, 50Hz
// next, turn Timer3 off
T3CONbits.ON = 0;
// base Timer3 on PBClk/8
T3CONbits.TCS = 0; // use PBClk as clock source
T3CONbits.TCKPS = 0b011; // divide by 8
PR3 = SERVO_PERIOD; // default to the servo rate, 50Hz
// with the Timers configured, move to the PWM setup, 1 loop per channel
for ( i=0; i<MaxConfiguredChannel; i++)
{
// turn off the OC system before making changes
((__OC1CONbits_t *)ChannelTo_pControlReg[i])->ON = 0;
// default to selecting timer 2
((__OC1CONbits_t *)ChannelTo_pControlReg[i])->OCTSEL = 0;
// set PWM mode with no fault detect
((__OC1CONbits_t *)ChannelTo_pControlReg[i])->OCM = 0b110;
// set the default DC to 0
*ChannelTo_pOCRS_Reg[i] = 0; // this is the repeating cycle
*ChannelTo_pOCR_Reg[i] = 0; // this is the initial cycle
// finally, turn OC system back on (clock is still off)
((__OC1CONbits_t *)ChannelTo_pControlReg[i])->ON = 1;
}
// finish up by turning the clocks to the PWM system on
T2CONbits.ON = 1; // turn Timer2 back on
T3CONbits.ON = 1; // turn Timer3 back on
}
return ReturnVal;
}
/****************************************************************************
Function
PWMSetup_MapChannelToOutputPin
****************************************************************************/
bool PWMSetup_MapChannelToOutputPin(uint8_t WhichChannel, PWM_PinMap_t WhichPin)
{
bool ReturnVal = true;
// test that BasicConfig has been called and for legal channel
if ( ( -1 == MaxConfiguredChannel) || IsChannelIllegal(WhichChannel))
{
ReturnVal = false;
}else
{ // now check to see if the requested pin is legal for the requested channel
if( false == IsPinLegalForChannel(WhichPin, WhichChannel))
{
ReturnVal = false;
}else // OK, everything is legal, now map the pin
{
*outputMapRegisters[WhichPin] = mapChannel2PinSelConst[WhichChannel-1];
}
}
return ReturnVal;
}
/****************************************************************************
Function
PWMSetup_AssignChannelToTimer
****************************************************************************/
bool PWMSetup_AssignChannelToTimer( uint8_t whichChannel,
WhichTimer_t whichTimer )
{
bool ReturnVal = true;
uint8_t channelIndex;
if( IsChannelIllegal(whichChannel) )
{
ReturnVal = false;
}else
{
channelIndex = whichChannel-1;
if( _Timer2_ == whichTimer)
{
// save the PR register to use for this channel
ChannelTo_pTimer[channelIndex] = &PR2;
// program the channel to use the new timer
((__OC1CONbits_t *)ChannelTo_pControlReg[channelIndex])->OCTSEL = 0;
} else if (_Timer3_ == whichTimer)
{
// save the PR register to use for this channel
ChannelTo_pTimer[channelIndex] = &PR3;
// program the channel to use the new timer
((__OC1CONbits_t *)ChannelTo_pControlReg[channelIndex])->OCTSEL = 1;
}else // if not Timer2 or Timer3, then it is bad
{
ReturnVal = false;
}
}
return ReturnVal;
}
/****************************************************************************
Function
PWMOperate_SetDutyOnChannel
****************************************************************************/
bool PWMOperate_SetDutyOnChannel( uint8_t dutyCycle, uint8_t whichChannel)
{
bool ReturnVal = true;
uint32_t updateVal;
uint8_t channelIndex;
channelIndex = whichChannel-1; // convert OC1-5 to 0-4 for indexing
// sanity check, reasonable channel number & DC
if (IsChannelIllegal(whichChannel) || (100 < dutyCycle))
{
ReturnVal = false;
}else
{
// update local copy of DC used when changing freq or period
LocalDuty[channelIndex] = dutyCycle;
if (0 == dutyCycle)
{ // don't try to calculate with 0 DC
updateVal = 0;
}else
{ // reasonable duty cycle number, so calculate new pulse width
updateVal = ((*(ChannelTo_pTimer[channelIndex]))*dutyCycle)/100;
}
// 100% DC needs to be handled differently to work with the PWM hardware
if (100 == dutyCycle)
{
// To program 100% DC, simply set the RS reg higher than the period
updateVal = (*(ChannelTo_pTimer[channelIndex])) + 1;
}else
{
} // no else clause
// now update the value in the RS register
*(ChannelTo_pOCRS_Reg[channelIndex]) = updateVal;
}
return ReturnVal;
}
/****************************************************************************
Function
PWMOperate_SetPulseWidthOnChannel
****************************************************************************/
bool PWMOperate_SetPulseWidthOnChannel( uint16_t NewPW, uint8_t whichChannel)
{
bool ReturnVal = true;
if (IsChannelIllegal(whichChannel) ||
(NewPW > *ChannelTo_pTimer[whichChannel-1]))
{
ReturnVal = false;
}else
{
// everything looks good so update the value in the RS register
*(ChannelTo_pOCRS_Reg[whichChannel-1]) = NewPW;
}
return ReturnVal;
}
/****************************************************************************
Function
PWMSetup_SetPeriodOnTimer
****************************************************************************/
bool PWMSetup_SetPeriodOnTimer( uint16_t reqPeriod, WhichTimer_t WhichTimer)
{
bool ReturnVal = true;
if (MIN_PERIOD > reqPeriod)
{
ReturnVal = false;
}else
{
switch (WhichTimer)
{
case _Timer2_:
PR2 = T2Period = reqPeriod;
break;
case _Timer3_:
PR3 = T3Period = reqPeriod;
break;
default: // anything else is illegal
ReturnVal = false;
break;
}
}
return ReturnVal;
}
/****************************************************************************
Function
PWMSetup_SetFreqOnTimer
****************************************************************************/
bool PWMSetup_SetFreqOnTimer( uint16_t reqFreq, WhichTimer_t WhichTimer)
{
bool ReturnVal = true;
uint16_t newPeriod;
// sanity check for legal timers & within range
if(((_Timer2_ != WhichTimer) && (_Timer3_ != WhichTimer)) ||
(MIN_FREQ > reqFreq) || (MAX_FREQ < reqFreq))
{
ReturnVal = false;
}else
{
//Use the Frequency (expressed in Hz) to calculate a new period
newPeriod = PBCLK_RATE/TIMER_DIV /reqFreq;
// apply the new period
PWMSetup_SetPeriodOnTimer( newPeriod, WhichTimer);
}
return ReturnVal;
}
//*********************************
// private functions
//*********************************
static bool IsPinLegalForChannel(PWM_PinMap_t WhichPin, uint8_t WhichChannel)
{
bool ReturnVal = false;
uint8_t index;
WhichChannel -= 1; // convert from 1-5 to 0-4 for indexing into arrays
for( index = 0; index < MAX_PINS_PER_OUTPUT; index++)
{
if ( LegalOutPins[WhichChannel][index] == WhichPin )
{
ReturnVal = true;
break;
}
}
return ReturnVal;
}
static bool IsChannelIllegal( uint8_t whichChannel )
{
return ((0 == whichChannel) || (whichChannel > MaxConfiguredChannel));
}
//*********************************
// test harness
//*********************************
#ifdef TESTING
void WaitForRise( void );
void main(void)
{
// set up as button input on RB12 to stage tests
ANSELB = 0; //disable analog
TRISB |= 1<<12; // config pin as input
PWMSetup_BasicConfig(2);
PWMSetup_SetFreqOnTimer(50, _Timer3_);
PWMSetup_AssignChannelToTimer(1, _Timer3_);
PWMSetup_AssignChannelToTimer(2, _Timer3_);
PWMSetup_MapChannelToOutputPin(1, PWM_RPB3);
PWMSetup_MapChannelToOutputPin(2, PWM_RPB5);
PWMOperate_SetDutyOnChannel(50,1);
PWMOperate_SetDutyOnChannel(25,2);
WaitForRise();
PWMOperate_SetPulseWidthOnChannel((1*TICS_PER_MS),1);
PWMOperate_SetPulseWidthOnChannel((2*TICS_PER_MS),2);
WaitForRise();
PWMSetup_SetFreqOnTimer(100, _Timer3_);
PWMOperate_SetDutyOnChannel(50,1);
PWMOperate_SetDutyOnChannel(25,2);
while(1)
;
}
void WaitForRise( void )
{
bool currentButtonState;
do // wait for low level
{
currentButtonState = PORTBbits.RB12;
} while (1 == currentButtonState);
do // wait for rise
{
currentButtonState = PORTBbits.RB12;
}while (( 0 == currentButtonState));
return;
}
#endif