29.5.2.1 Interleaved Sampling Step Command Program
This section describes the step command programming for implementing the timing sequence shown in Figure 29-11.
The following assumptions are made:
- Trigger Input 0 is connected to the PWM1 signal. The rising edge of this signal starts the sequence. PWM1 has a period of 50us.
- Output Trigger 12 is connected to the ADC module. This signal gives command to the ADC module to begin a sample and conversion process.
- Interrupt 0 is used to indicate to the processor that a subsequence has started (provides status).
- Interrupt 1 is used to indicate to the processor that the entire sequence has completed.
- The PTG clock source is 125 MHz.
- The initial trigger delay is 5 μs.
- The second trigger delay is 6 μs.
- In each PWM cycle, the ADC will be triggered 25 times.
- The basic sequence is executed twice.
Interleaved Sampling Step Command Program
#include "xc.h"
#include "ptg.h" //Contains Examples 2-1, 2-2, and 2-3
//Allocate buffers for ADC results.
//Size of 50 is based on 2 PWM cycles, 25 results per cycle.
#define RESULT_BUFFER_SIZE 50
unsigned int voltage_buffer[RESULT_BUFFER_SIZE];
int voltage_buffer_index = 0;
unsigned int current_buffer[RESULT_BUFFER_SIZE];
int current_buffer_index = 0;
_Bool readings_ready = 0;
void clocks_initialize() {
//Set up CPU clock (Fcy) to run at 200 MHz.
//Start by configuring PLL1
PLL1CONbits.ON = 1; //Enable PLL1, if not already enabled
//PLL1 has input frequency 8MHz (FRC)
PLL1DIVbits.PLLPRE = 1; //Reference input will be 8MHz, no division
PLL1DIVbits.PLLFBDIV = 125; //Fvco = 8MHz * 125 = 1000MHz
PLL1DIVbits.POSTDIV1 = 5; //Divide Fcvo by 5
PLL1DIVbits.POSTDIV2 = 1; //Fpllo = Fvco / 5 / 1 = 200 MHz
//The PLLSWEN bit controls changes to the PLL feedback divider.
//Request PLL1 feedback divider switch
PLL1CONbits.PLLSWEN = 1;
//Wait for PLL1 feedback divider switch to complete
while(PLL1CONbits.PLLSWEN);
//The FOUTSWEN bit controls changes to the PLL output dividers.
//Request PLL1 output divider switch
PLL1CONbits.FOUTSWEN = 1;
//Wait for PLL1 output divider switch to complete
while(PLL1CONbits.FOUTSWEN);
VCO1DIVbits.INTDIV = 2; //Divide Fvco by 4
//The DIVSWEN bit controls changes to the VCO divider.
//Request PLL1 VCO divider switch
PLL1CONbits.DIVSWEN = 1;
//Wait for PLL1 VCO divider switch to complete
while(PLL1CONbits.DIVSWEN);
//Reset CLKGEN1 dividers for 1:1 ratio
CLK1DIVbits.INTDIV = 0;
CLK1DIVbits.FRACDIV = 0;
//Request CLKGEN1 divider switch
CLK1CONbits.DIVSWEN = 1;
//Wait for divider switch to complete
while(CLK1CONbits.DIVSWEN);
CLK1CONbits.NOSC = 5; //Set PLL1 Fout as CPU clock source
CLK1CONbits.OSWEN = 1; //Request clock switch
while (CLK1CONbits.OSWEN); //Wait for switch to complete
//Configure CLKGEN5 to provide a 125MHz PWM MCLK
CLK5CONbits.ON = 1; //Enable CLKGEN5, if not already enabled
CLK5DIVbits.INTDIV = 1; //Divide input clock by 2
CLK5DIVbits.FRACDIV = 0;
//Request CLKGEN5 divider switch
CLK5CONbits.DIVSWEN = 1;
//Wait for CLKGEN5 divider switch to complete
while(CLK5CONbits.DIVSWEN);
CLK5CONbits.NOSC = 7; //Use divided PLL1 VCO (250MHz)
CLK5CONbits.OSWEN = 1; //Request clock switch
while(CLK5CONbits.OSWEN); //Wait for switch to complete
PCLKCONbits.MCLKSEL = 1; //Use CLKGEN5 as PWM MCLK source
//Configure CLKGEN6 to provide a 250MHz input clock to the ADC
CLK6CONbits.ON = 1; //Enable CLKGEN6, if not already enabled
//Reset CLKGEN6 dividers for 1:1 ratio
CLK6DIVbits.INTDIV = 0;
CLK6DIVbits.FRACDIV = 0;
//Request CLKGEN6 divider switch
CLK6CONbits.DIVSWEN = 1;
//Wait for CLKGEN6 divider switch to complete
while(CLK6CONbits.DIVSWEN);
CLK6CONbits.NOSC = 7; //Use divided PLL1 VCO (250MHz)
CLK6CONbits.OSWEN = 1; //Request clock switch
while (CLK6CONbits.OSWEN); //Wait for switch to complete
//Configure CLKGEN10 to provide a 125MHz clock for the PTG.
CLK10CONbits.ON = 1; //Enable CLKGEN10, if not already enabled
CLK10DIVbits.INTDIV = 1; //Divide input clock by 2
CLK10DIVbits.FRACDIV = 0;
//Request CLKGEN10 divider switch
CLK10CONbits.DIVSWEN = 1;
//Wait for CLKGEN10 divider switch to complete
while(CLK10CONbits.DIVSWEN);
CLK10CONbits.NOSC = 7; //Use divided PLL1 VCO (250MHz)
CLK10CONbits.OSWEN = 1; //Request clock switch
while (CLK10CONbits.OSWEN); //Wait for switch to complete
}
void PTG_initialize() {
//Enable PTG interrupts 0 and 1
_PTG0IE = 1;
_PTG1IE = 1;
//Set up control registers
PTGT0LIM = 625; //5us T0 delay
PTGT1LIM = 125; //1us T1 delay
PTGC0LIM = 24; //Repeat C0 loop 25 times
PTGC1LIM = 1; //Repeat C1 loop once
PTGHOLD = 625; //5us (used to restore T0 delay)
PTGADJ = 125; //1us (added to T0 delay)
PTGQPTR = 0; //Initialize step queue pointer
//Outer loop
PTGQUE0bits.STEP0 = PTGWHI(0); // Wait for positive edge trigger 0 (PWM1 ADC Trigger 2)
PTGQUE0bits.STEP1 = PTGCTRL(t0Wait); // Start PTGT0, wait for time out
PTGQUE0bits.STEP2 = PTGIRQ(0); // Generate IRQ 0
// Inner loop
PTGQUE0bits.STEP3 = PTGTRIG(12); // Generate output trigger 12 (ADC conversion)
PTGQUE1bits.STEP4 = PTGCTRL(t1Wait); // Start PTGT1, wait for time out
PTGQUE1bits.STEP5 = PTGJMPC0(3); // Go to STEP3 if PTGC0 != PTGC0LIM, increment PTGC0 (ie. repeat steps 3-5 24 times)
// End inner loop
PTGQUE1bits.STEP6 = PTGADD(t0Limit); // Add PTGADJ to PTGT0LIM
PTGQUE1bits.STEP7 = PTGJMPC1(0); // Jump to 0 PTGC1LIM times (once, making 2 iterations)
// End outer loop
PTGQUE2bits.STEP8 = PTGIRQ(1); // Generate IRQ 1
PTGQUE2bits.STEP9 = PTGCOPY(t0Limit); // Copy PTGHOLD to PTGT0LIM (restore original value)
PTGQUE2bits.STEP10 = PTGJMP(0); // Jump to start of queue
//Start PTG
PTGCONbits.ON = 1;
PTGCONbits.PTGSTRT = 1;
}
void PWM1_initialize() {
_TRISD2 = 0; //Set PWM1H pin as output to observe cycle
PG1CONbits.CLKSEL = 1; // //Main PWM clock (no dividing or scaling) used for PWM1
PG1IOCONbits.PENH = 1; // PWM generator 1 controls PWM1H pin (RD2)
PG1EVTbits.ADTR2EN1 = 1; // Enable PG1TRIGA match as PWM1 ADC Trigger 2 source
PG1PER = 6250 << 4; //Period of 50us
PG1PHASE = 0; //0 phase offset
PG1DC = 3125 << 4; //50% duty cycle
PG1TRIGA = 0x0000; // ADC trigger 2 (PTG input) will happen at start of cycle
PG1CONbits.ON = 1; // Enable PWM Generator 1
}
void ADC_initialize() {
//Use RA2 as input to be converted by ADC (voltage reading)
_ANSELA2 = 1;
_TRISA2 = 1;
//Use RA4 as input to be converted by ADC (current reading)
_ANSELA4 = 1;
_TRISA4 = 1;
//Initialize ADC
AD1CONbits.ON = 1;
while(!AD1CONbits.ADRDY);
//AD1AN0 input selected by data channel 0, conversion is triggered by ADC trigger 30
AD1CH0CONbits.MODE = 0; //Single-sample mode
AD1CH0CONbits.PINSEL = 0; //Positive input is AD1AN0/RA2
AD1CH0CONbits.NINSEL = 0; //Single-ended mode
AD1CH0CONbits.TRG1SRC = 30; //Channel 0 triggered by ADC trigger 30 (PTG trigger 12)
//AD1AN1 input selected by data channel 1, conversion is triggered by ADC trigger 30
AD1CH1CONbits.MODE = 0; //Single-sample mode
AD1CH1CONbits.PINSEL = 1; //Positive input is AD1AN1/RA4
AD1CH1CONbits.NINSEL = 0; //Single-ended mode
AD1CH1CONbits.TRG1SRC = 30; //Channel 1 ADC trigger 30 (PTG trigger 12)
//Enable ADC Channel 0 and 1 interrupts
_AD1CH0IE = 1;
_AD1CH1IE = 1;
}
void calculate_average_power() {
//To do: Use the buffered ADC readings to calculate average power over the last 2 PWM cycles.
//Calculation details are application-specific.
//Below is a simplified calculation for demonstration purpose.
float power_sum = 0;
for (int i = 0; i < RESULT_BUFFER_SIZE; i++) {
//Power = Current * Voltage
power_sum += ((float)voltage_buffer[i] * (float)current_buffer[i]);
}
//Take average
float average_power = power_sum / RESULT_BUFFER_SIZE;
//Toggle I/O indicator to show completion
_LATD10 ^= 1;
}
int main(void) {
//Set digital outputs for timing indicators
_TRISD2 = 0;
_TRISD5 = 0;
_TRISD6 = 0;
_TRISD10 = 0;
clocks_initialize();
PWM1_initialize();
ADC_initialize();
PTG_initialize();
while(1) {
//Wait for readings to be ready
if (readings_ready) {
//Perform power calculations and reset for next time.
calculate_average_power();
readings_ready = 0;
}
}
return 0;
}
void __attribute__((__interrupt__)) _PTG0Interrupt() {
_PTG0IF = 0;
//Interrupt indicates a series of 25 ADC readings will now be collected.
}
void __attribute__((__interrupt__)) _PTG1Interrupt() {
_PTG1IF = 0;
//2 PWM cycles of ADC results have been collected, average power can be calculated for those 2 cycles.
readings_ready = 1;
//Reset buffer indices here to prevent overrun.
voltage_buffer_index = 0;
current_buffer_index = 0;
}
void __attribute__((__interrupt__)) _AD1CH0Interrupt() {
unsigned int voltage_result = AD1CH0DATA;
_AD1CH0IF = 0;
//Buffer ADC result for later use
voltage_buffer[voltage_buffer_index++] = voltage_result;
_LATD5 ^= 1; //Toggle indicator I/O
}
void __attribute__((__interrupt__)) _AD1CH1Interrupt() {
unsigned int current_result = AD1CH1DATA;
_AD1CH1IF = 0;
//Buffer ADC result for later use
current_buffer[current_buffer_index++] = current_result;
_LATD6 ^= 1; //Toggle indicator I/O
}
See PTG Command Definitions for PTG command definitions. See PTGCTRL Options and Options for PTGADD and PTGCOPY Commands for PTGCTRL
, PTGADD
and PTGCOPY
command options.