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:
  1. Trigger Input 0 is connected to the PWM1 signal. The rising edge of this signal starts the sequence. PWM1 has a period of 50us.
  2. Output Trigger 12 is connected to the ADC module. This signal gives command to the ADC module to begin a sample and conversion process.
  3. Interrupt 0 is used to indicate to the processor that a subsequence has started (provides status).
  4. Interrupt 1 is used to indicate to the processor that the entire sequence has completed.
  5. The PTG clock source is 125 MHz.
  6. The initial trigger delay is 5 μs.
  7. The second trigger delay is 6 μs.
  8. In each PWM cycle, the ADC will be triggered 25 times.
  9. 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.