// *********************************************************************************************
// PIC32MKxxMCFxx 20msps Interleaved ADC sample code example
//
// SUMMARY:
//    TARGET CPU: PIC32MKxxMCFxx
//    OSC TYPE = 24 MHz Clock oscillator (EC Mode)
//    SYSCLK=120 MHz
//    PBCLK = 120 MHz
//    ADC TAD = SYSCLK / 2 = 60 MHz = 16.666667ns
//    ADC SAMC=3 TAD
//    ADC SAMPLE RATE = 20msps
//    INTERLEAVED ADCS
//      ADC 0: 12bit mode, ADCTRG1<TRGSRC0> PWM Generator 1 trigger PTMR time base
//      ADC 1: 12bit mode, ADCTRG1<TRGSRC1> PWM Generator 2 trigger PTMR time base
//      ADC 2: 12bit mode, ADCTRG1<TRGSRC2> PWM Generator 3 trigger PTMR time base
//      ADC 3: 12bit mode, ADCTRG1<TRGSRC3> PWM Generator 4 trigger PTMR time base
//      ADC 4: 12bit mode, ADCTRG2<TRGSRC4> PWM Generator 5 trigger PTMR time base
//      ADC 5: 12bit mode, ADCTRG2<TRGSRC5> PWM Generator 6 trigger PTMR time base
//    
//    interleaved ADC0/1/2/3/4/5 with only AN0/AN2/AN5 inputs, (20 msps combined throughput rate)
//
//   NOTE:
//     Motor Control PWM peripherals and hence PWM triggers only exists on 
//     PIC32MKxxMCxx variants. If PWM used as ADC trigger sources then PWM motor control
//     functionality would be excluded. The two independent uses are exclusive of each other.
// *********************************************************************************************
//   NOTE: (FYI only)
//     A user could configure the PIC32MK to have multiple sets of 
//     interleaved ADC's to measure multiple different analog input streams like:
//           -----------------------------  EXAMPLE  1  CONFIGURATION  -----------------------------
//       1) ADC0-ADC1 (2) ADCs with triggers OC1 and TMR5 @ 7.5msps(max) combined
//       2) ADC2-ADC3 (2) ADCs with triggers OC4 and TMR3 @ 7.5msps(max) combined
//       3) ADC4-ADC5 (2) ADCs PWM Generator 1 & PWM Generator 2 trigger PTMR time base  @ 7.5msps(max)
//           -----------------------------  EXAMPLE  2  CONFIGURATION  -----------------------------
//       4) ADC0-ADC2 (3) ADCs with triggers OC1, OC2 and TMR3 @ 10msps(max) combined
//       5) ADC3-ADC5 (3) ADCs with PWM Generator 1 / 2 / 3 trigger PTMR time base  @ 10msps(max) combined
//           -----------------------------  EXAMPLE  3  CONFIGURATION  -----------------------------
//       6) ADC0-ADC1 (2) ADCs with triggers OC1 and TMR3 @ 7.5msps(max) combined
//       7) ADC2-ADC5 (4) ADCs with PWM Generator 1 / 2 / 3 / 4  trigger PTMR time base  @ 15msps(max) combined
//           -----------------------------  EXAMPLE  4  CONFIGURATION  -----------------------------
//       8) ADC0-ADC5 (6) ADCs with PWM Generator 1/2/3/4/5/6 trigger PTMR time base  @ 20msps(max) combined
// *********************************************************************************************
//USER NOTE:
// Although the number of interleaved ADC's the user wishes to use is selectable, it is required
// "FOR THIS CODE ONLY" that those ADCx modules be sequential starting from AN0 to the last sequential
// ANx to be used for interleaving. In different code than below, any combination of interleaved ADC are allowed
//
// ****************************************************************************
//        F I L E   I N C L U D E S
// ****************************************************************************
#include <xc.h>
#include <sys/attribs.h>

// ****************************************************************************
//        C O N F I G U R A T I O N    W O R D S
// ****************************************************************************
//
//----------------------------------------------------------
//                  DEVCFG0 
//----------------------------------------------------------
#pragma config DEBUG =      OFF
#pragma config JTAGEN =     OFF
#pragma config ICESEL =     ICS_PGx2
#pragma config TRCEN =      OFF
#pragma config BOOTISA =    MIPS32
#pragma config FSLEEP =     OFF
#pragma config DBGPER =     PG_ALL
#pragma config SMCLR =      MCLR_NORM
#pragma config SOSCGAIN =   GAIN_2X
#pragma config SOSCBOOST =  ON
#pragma config POSCGAIN =   GAIN_LEVEL_3
#pragma config POSCBOOST =  ON
#pragma config EJTAGBEN =   NORMAL
#pragma config CP =         OFF

//----------------------------------------------------------
//                  DEVCFG1 
//----------------------------------------------------------
#pragma config FNOSC =      SPLL
#pragma config DMTINTV =    WIN_127_128
#pragma config FSOSCEN =    OFF
#pragma config IESO =   OFF
#pragma config POSCMOD =    EC	//External 24Mhz Clock oscillator
#pragma config OSCIOFNC =   ON
#pragma config FCKSM =      CSECME
#pragma config WDTPS =      PS1048576
#pragma config WDTSPGM =    STOP
#pragma config FWDTEN =     OFF
#pragma config WINDIS =     NORMAL
#pragma config FWDTWINSZ = WINSZ_25
#pragma config DMTCNT =     DMT31
#pragma config FDMTEN =     OFF

//----------------------------------------------------------
//                  DEVCFG2
//----------------------------------------------------------
#pragma config FPLLIDIV =   DIV_3
#pragma config FPLLRNG =    RANGE_5_10_MHZ
#pragma config FPLLICLK =   PLL_POSC
#pragma config FPLLMULT =   MUL_60
#pragma config FPLLODIV =   DIV_4
#pragma config VBATBOREN = OFF
#pragma config DSBOREN =    ON
#pragma config DSWDTPS =    DSPS32
#pragma config DSWDTOSC =   LPRC
#pragma config DSWDTEN =    OFF
#pragma config FDSEN =      ON
#pragma config BORSEL =     HIGH
#pragma config UPLLEN =     OFF
//----------------------------------------------------------
//                  DEVCFG3
//----------------------------------------------------------
#pragma config USERID =     0x0ADC
#pragma config FUSBIDIO2 = OFF
#pragma config FVBUSIO2 = OFF
#pragma config PGL1WAY = ON
#pragma config PMDL1WAY = ON
#pragma config IOL1WAY = ON
#pragma config FUSBIDIO1 = OFF
#pragma config FVBUSIO1 = OFF
#pragma config PWMLOCK = OFF
//----------------------------------------------------------
//                  BF1SEQ0
//----------------------------------------------------------
#pragma config TSEQ =       0x0000
#pragma config CSEQ =       0xffff

// *************************************************************************************************
//                                  U S E R     D E F I N E S
// Note: User must configure as required
// *************************************************************************************************
#define TAD_TRIGGER_SOURCE_SPACING   3	// TAD trigger spacing value from ADC Table 14 w/12bit & 6 interleaved ADCs
#define INTERLEAVED_ADC_COUNT               6 	// Number of interleaved ADC (Cannot be <2 or >6). Changing this value will
                                         				// automatically scale the number of interleaved ADC and triggers utilized in this code.

// *************************************************************************************************
// EQUATION #1: (Assumptions: INTERLEAVED_ADC_COUNT=6)
//    TAD_TRIGGER_SOURCE_SPACING(min)  = 
//        = ((SAMC + (((#bits Resolution+1) + ADJ) * TAD)) / INTERLEAVED_ADC_COUNT)
//        = (3+12+1) + ADJ) * TAD)) / 6 ADCs)
//        = (16 + ADJ) * TAD)) / 6 ADCs
//        = (16 + 2) * TAD)) / 6 ADCs
//        = 3.0 TAD (minimum trigger spacing possible)
//
// NOTE: "ADJ" term = A user selected whole number adjustment factor between 0-4 
//                    that must yield a Minimum TAD Trigger interval that equates to an exact 
//                    multiple of a  TAD. This represents the minimum TAD_TRIGGER_SOURCE_SPACING
//                    and therefore the fastest throughput possible. Additional TAD_TRIGGER_SOURCE_SPACING
//                    and hence ADC Throughput Rates are possible in 1/2 TAD trigger spacing increments.
//                    (See examples 1-3 below)
//
//  NOTE: Once the minimum, TAD trigger spacing possible is determined, the user can configure for any 
//        additional sampling frequency required if needed in 0.5 TAD trigger spacing increments.
//
//        Example 1: (INTERLEAVED_ADC_COUNT=6)
//          TAD_TRIGGER_SOURCE_SPACING(min) = 3
//          interleaved ADC Throughput rate = TAD clock freq / TAD_TRIGGER_SOURCE_SPACING
//                                          = 60Mhz / 3.0(min)
//                                          = 20msps
//
//        Example 2: (INTERLEAVED_ADC_COUNT=6)
//          TAD_TRIGGER_SOURCE_SPACING = 3.5
//          interleaved ADC Throughput rate = TAD clock freq / TAD_TRIGGER_SOURCE_SPACING
//                                          = 60Mhz / 3.5
//                                          = 17.142857msps
//
//        Example 3: (INTERLEAVED_ADC_COUNT=6)
//          TAD_TRIGGER_SOURCE_SPACING = 4.0
//          interleaved ADC Throughput rate = TAD clock freq / TAD_TRIGGER_SOURCE_SPACING
//                                          = 60Mhz / 4.0
//                                          = 15msps
//
// *************************************************************************************************

// *************************************************************************************************
//                               P R O G R A M    D E F I N E S 
//                                       (User Do Not Change)
// Note: 
//       TAD_TRIGGER_SOURCE_SPACING is multiplied by (2) because
//        PWM trigger source time base is 2x faster than an ADC TAD clock.
//        2 PWM clocks = 1 ADC TAD Clock
// **************************************************************************************************
// ------------------------------------------------------------------------------------------------------------------
// NOTE: Remember that the peripheral trigger clock frequency is 2x TAD clock frequency.
//             This is why the 2x. The -1 is because the Si peripheral triggers are always 
//             on (match+1), so to compensate for correct timing subtract 1. 
// 
#define ADC_TRIG        (TAD_TRIGGER_SOURCE_SPACING * 2)        //6
#define PWM1_ADC_TRIG   (ADC_TRIG - 1)           // 5, ADC0 PWM1 Trig (Note: ADC Trig on match +1)
#define PWM2_ADC_TRIG   ((2 * ADC_TRIG) - 1)   //11, ADC1 PWM1 Trig (Note: ADC Trig on match +1)
#define PWM3_ADC_TRIG   ((3 * ADC_TRIG) - 1)   //17, ADC2 PWM2 Trig (Note: ADC Trig on match +1)
#define PWM4_ADC_TRIG   ((4 * ADC_TRIG) - 1)   //23, ADC3 PWM3 Trig (Note: ADC Trig on match +1)
#define PWM5_ADC_TRIG   ((5 * ADC_TRIG) - 1)   //29, ADC4 PWM4 Trig (Note: ADC Trig on match +1)
#define PWM6_ADC_TRIG   ((6 * ADC_TRIG) - 1)   //35, ADC5 PWM5 Trig (Note: ADC Trig on match +1)

#define ADC_CHANNEL_BUFFER_LENGTH   (uint16_t) 128         // Allowed = 2n n=0-7: (128 Samples per ADC per Buffer. Each ADC has 2 buffers)
#define HALF_BUFFER_LENGTH          (uint16_t) (ADC_CHANNEL_BUFFER_LENGTH*INTERLEAVED_ADC_COUNT)
#define TOTAL_BUFFER_LENGTH         (uint16_t) (2*ADC_CHANNEL_BUFFER_LENGTH*INTERLEAVED_ADC_COUNT)
#define ADC_SAMPLE_OFFSET           (uint16_t) (ADC_CHANNEL_BUFFER_LENGTH*2) // Sample Offset between two sequential samples in RAW ADC DMA Buffer 
#define ADC_INTER_BUFFER_OFFSET     ADC_CHANNEL_BUFFER_LENGTH // Sample offset between Buffer A and Buffer B
#define DMA_BUFA_FULL  (0x00000001 << (INTERLEAVED_ADC_COUNT-1))
#define DMA_BUFB_FULL  (0x00010000 << (INTERLEAVED_ADC_COUNT-1))
#define DMA_BUFA_FULL_INT  (0x00000100 << (INTERLEAVED_ADC_COUNT-1))
#define DMA_BUFB_FULL_INT  (0x01000000 << (INTERLEAVED_ADC_COUNT-1))
#define DMA_INT_SRC  (DMA_BUFA_FULL_INT  |  DMA_BUFB_FULL_INT)
#define BUFFER_RPT_COUNT            1 //

#if ((INTERLEAVED_ADC_COUNT  <  2) | (INTERLEAVED_ADC_COUNT  >  6))
#error  INTERLEAVED_ADC_COUNT error, it is either less than 2 or greater than 6
#endif

// *************************************************************************************************
//        F U N C T I O N     P R O T O T Y P E S
// *************************************************************************************************
void init_PWM(void);
void init_ADC(void);

// *************************************************************************************************
//        G L O B A L   V A R I A B L E S
// *************************************************************************************************
unsigned int ADC_var=0;
char buffer_select = 0;
uint8_t buffer_a_index, buffer_b_index, buffer_index;

// *************************************************************************************************
// NOTE:
// Each ADC has 2 buffers. Each buffer size can be up to 128 samples (256bytes). Thus, 
// total max buffer size per ADC is 256 samples (512 bytes). Since we are using 6 ADC 
// channels in this example, the total DMA BUffer size = 
// 128(samples per buffer)*2(buffer per adc)*6(number of adchannels) * BUFFER_RPT_COUNT 
// = 1536 samples or 3072 bytes.
// *************************************************************************************************
uint16_t adc_dma_raw_buffer[TOTAL_BUFFER_LENGTH]; //Buffer where ADC Data is dumped by the DMA
uint16_t adc_data_rearranged_buffer[BUFFER_RPT_COUNT][TOTAL_BUFFER_LENGTH]; // Buffer where ADC samples are re-arranged in sequentially
uint16_t source_index, destination_index, adc_count, index_offset, sample_count; 

unsigned short  *adc0_ptr,*adc1_ptr,*adc2_ptr,*adc3_ptr,*adc4_ptr,*adc5_ptr;
unsigned short *dest_ptr, *end_ptr;


// $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
// $                                         M A I N        R O U T I N E                                             $
// $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
int main(void)
{
    // *************************************************************************************************
    // CPU Performance Optimization:
    // *************************************************************************************************
    register unsigned long tmp_cache;                 //KSEG0 cache enable
    asm("mfc0 %0,$16,0" :  "=r"(tmp_cache));
    tmp_cache = (tmp_cache & ~7) | 3;
    asm("mtc0 %0,$16,0" :: "r" (tmp_cache));
    
    CHECONbits.PFMWS=3;       //Flash wait states = 3 CPU clock cycles @ 120Mhz        
    CHECONbits.PREFEN = 1;    //PREFEN
    PRISSbits.PRI7SS = 1;	//DMA Interrupt with priority level of 7 uses Shadow Set 1
    INTCONbits.MVEC = 1;	//Enable multi-vector interrupts
    __builtin_mtc0(12,0,(__builtin_mfc0(12,0) | 0x0001)); // Global Interrupt Enable
        
    SYSKEY = 0xAA996655;       // Write Key1 to SYSKEY
    SYSKEY = 0x556699AA;       //Unlock
    PB1DIVbits.PBDIV = 0;      //0 = 1:1 = SYSCLK/1 = 120Mhz
    PB2DIVbits.PBDIV = 0;      //0 = 1:1 = SYSCLK/1 = 120Mhz
    PB3DIVbits.PBDIV = 0;      //0 = 1:1 = SYSCLK/1 = 120Mhz
    PB4DIVbits.PBDIV = 0;      //0 = 1:1 = SYSCLK/1 = 120Mhz
    PB5DIVbits.PBDIV = 0;      //0 = 1:1 = SYSCLK/1 = 120Mhz
    PB6DIVbits.PBDIV = 3;      //0 = 4:1 = SYSCLK/4 = 30Mhz
    SYSKEY = 0x00000000;        //Lock

    // *************************************************************************************************
    // Initialize and enable ADC + ADC DMA then ADC PWM trigger sources
    // *************************************************************************************************      
    init_ADC();                //Initialize and enable interleaved ADCs first before PWM ADC triggers are enabled
    
    while(1)
    {
          // USERS CODE GOES HERE
    }        
} 

// $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
// $                       F U N T I O N    S U B _ R O U T I N U E S                                       $
// $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$

// *************************************************************************************************
// Interleaved ADC PWM Trigger initialization:
//   - PWM output pins disabled
//   - PWMs 1-x = 50% duty cycle
//   - PWM Period Timer = 6x ADC TAD trigger spacing
//   - PWM ADC conversion triggers staggered in 1/6 increments
//  NOTE:
//   1): In this application, PWM functionality is exclusive to only the interleaved ADC trigger(s)
//   2): PWM when used as ADC trigger(s) must never be enabled before ADC initialization 100% complete  
// *************************************************************************************************
void init_PWM(void)
{
    PTPER = (INTERLEAVED_ADC_COUNT * ADC_TRIG);

    PDC1 = (PTPER >> 1);	//50% of period
    TRIG1 = PWM1_ADC_TRIG;	//PWM1 Trigger point

    PDC2 = PDC1;
    TRIG2 = PWM2_ADC_TRIG;	//PWM2 Trigger point

    if(INTERLEAVED_ADC_COUNT   >  2)
    {
        PDC3 = PDC1;
        TRIG3 = PWM3_ADC_TRIG;	//PWM3 Trigger point
    }

    if(INTERLEAVED_ADC_COUNT   >  3) 
    {
        PDC4 = PDC1;
        TRIG4 = PWM4_ADC_TRIG;	//PWM4 Trigger point
    }

    if(INTERLEAVED_ADC_COUNT   >  4)
    {
        PDC5 = PDC1;
        TRIG5 = PWM5_ADC_TRIG;	//PWM5 Trigger point
    }

    if(INTERLEAVED_ADC_COUNT   >  5)
    {
        PDC6 = PDC1;
        TRIG6 = PWM6_ADC_TRIG;	//PWM6 Trigger point
    }

    IOCON1 = 0x00030000;	//Fault disabled, GPIO controls PWM1H/L pins
    IOCON2 = 0x00030000;	//Fault disabled, GPIO controls PWM2H/L pins
    IOCON3 = 0x00030000;	//Fault disabled, GPIO controls PWM3H/L pins
    IOCON4 = 0x00030000; 	//Fault disabled, GPIO controls PWM4H/L pins
    IOCON5 = 0x00030000;   	//Fault disabled, GPIO controls PWM5H/L pins
    IOCON6 = 0x00030000;   	//Fault disabled, GPIO controls PWM6H/L pins

    PTCONbits.PTEN = 1;
}

// *************************************************************************************************
// init_ADC
// Interleaved ADC Initialization Function:
//    Used analog inputs if:
//       INTERLEAVED_ADC_COUNT  = 2   (Analog inputs, AN0 only)
//       INTERLEAVED_ADC_COUNT  = 3   (Analog inputs, AN0 & AN2 only)
//       INTERLEAVED_ADC_COUNT  = 4   (Analog inputs, AN0 & AN2 only)
//       INTERLEAVED_ADC_COUNT  = 5   (Analog inputs, AN0 & AN2 only)
//       INTERLEAVED_ADC_COUNT  = 6   (Analog inputs, AN0 & AN2 & AN5 only)
// *************************************************************************************************
// *********************************************************************************
//  These steps are already done in this code example, this is just a reminder 
//   to the user for any new original code development.
//
// CAUTION:(Required)  
// 1) The user SHOULD always set the minimum sampling time of each Class_1 ADC 
//      equal to 3TAD as defined by ADCxTIME<SAMC>. 
// 2) The user should insure that the selected trigger source time base clock rate is 
//      exactly 2x faster than the ADC clock TAD rate. This will provide the highest 
//      timing resolution along with the most sampling rate options as defined in 
//      Table 14 of this app note. 
// 3) The user selected trigger sources have to be able to be mapped to a single 
//      synchronized time base.
//      For example:
//        a) TMR3 & OCx (x=1,3,5 PIC32MZxxxx)
//        b) TMR3 & OCx (x=1-4 PIC32MKxxGPxx)
//        c) PTMRx & TRIGx ( x=1-12 PIC32MKxxMCxx PWM)
//        d) PTMRx & STRIGx ( x=1-12 PIC32MKxxMCxx PWM)
// 4) In a same group of interleaved ADCs a user MUST NOT use two separate timer trigger sources.
//     Example: ADC0, ADC1 and ADC2 are being used in an interleaved group. 
//       Trigger sources can be any combination of valid ADC OCx edge triggered sources synchronized to a SINGLE TMRx.
//       It would not be allowed to use in the same group of interleaved ADCs OCx, TMR3 & TMR5 for example. 
//       Separate TMRx in different interleaved ADC groups is OK. 
// 5  If using the DMA for the ADC the user MUST use SYSCLK as the ADC 
//     source clock, (i.e. ADCCON3<ADCSEL>).
// 6) User must configure ADC for 512 TAD warm-up time 
//     (ADCANCONbits.WKUPCLKCNT = 0x9) "BEFORE" enabling ADC.
// 7) Users SW  must wait for ADC BANGAP and ADC warm-up time "AFTER" 
//      enabling ADC and before activating the ADC trigger sources.
// 8) ADC must always be initialized 1st BEFORE activating the ADC trigger sources
//
// NOTE: Failure to follow these recommendations will result in inaccurate ADC
//             data acquisition and poor performance.
//******************************************************************************
void init_ADC(void)
{
    ADC0CFG = DEVADC0;        //Load ADC0 Calibration values
    ADC1CFG = DEVADC1;        //Load ADC1 Calibration values
    ADC2CFG = DEVADC2;        //Load ADC2 Calibration values
    ADC3CFG = DEVADC3;        //Load ADC3 Calibration values
    ADC4CFG = DEVADC4;        //Load ADC4 Calibration values
    ADC5CFG = DEVADC5;        //Load ADC5 Calibration values
    ADC7CFG = DEVADC7;        //Load ADC7 Calibration values

    ADCANCONbits.WKUPCLKCNT = 0x9; // ADC Warm up delay = (512 * TADx)
    
    //******************************************************************************
    // If using the DMA the user MUST use SYSCLK as the ADC source clock, (ADCCON3<ADCSEL> = 3)
    //******************************************************************************
    ADCCON3bits.ADCSEL = 3;        // Select ADC input clock source = SYSCLK 120Mhz
    ADCCON3bits.CONCLKDIV = 0;     // Analog-to-Digital Control Clock (TQ) Divider = SYSCLK Divide by 1
    ADCCON1bits.FSSCLKEN = 1;	   //Fast synchronous SYSCLK to ADC control clock is enabled

    //******************************************************************************
    // ADC0 Module setup:
    //    TAD = SYSCLK/2, 12bit, DMA enb, SAMC=3TAD, PWM1 Trigger
    //******************************************************************************
    ADC0TIMEbits.ADCDIV = 1;              // ADCx Clock Divisor, Divide by 2, 60Mhz
    ADC0TIMEbits.SAMC = 1;                 // 3 TAD (Hardware enforced sample time)
    ADC0TIMEbits.SELRES = 3;              //ADC0 12bit resolution mode
    ADC0TIMEbits.BCHEN=1;                 // ADC data saved in DMA system ram buffer
    ADCTRG1bits.TRGSRC0 = 10;           // Set AN0 to trigger from PWM Trigger 1
    ADCANCONbits.ANEN0 = 1;              // Enable ADC0 analog bias/logic
    ADCCON3bits.DIGEN0 = 1;               // Enable ADC0 digital logic

    //******************************************************************************
    // ADC1 Module setup:
    //    TAD = SYSCLK/2, 12bit, DMA enb, SAMC=3TAD, PWM2 Trigger
    //******************************************************************************
    ADC1TIMEbits.ADCDIV = 1;
    ADC1TIMEbits.SAMC = 1;                  // 3 TAD
    ADCTRGMODEbits.SH1ALT = 3;       // AN0 is the input to ADC1
    ADC1TIMEbits.SELRES = 3;              //ADC1 12bit resolution mode
    ADC1TIMEbits.BCHEN=1;                  // ADC data saved in DMA system ram buffer
    ADCTRG1bits.TRGSRC1 = 11;           // Set AN1 to trigger from PWM Trigger 2
    ADCANCONbits.ANEN1 = 1;              // Enable ADC1 analog bias/logic
    ADCCON3bits.DIGEN1 = 1;               // Enable ADC1 digital logic

    //******************************************************************************
    // ADC2 Module setup:
    //    TAD = SYSCLK/2, 12bit, DMA enb, SAMC=3TAD, PWM3 Trigger
    //******************************************************************************
    if(INTERLEAVED_ADC_COUNT  >  2)
    {
        ADC2TIMEbits.ADCDIV = 1;
        ADC2TIMEbits.SAMC = 1;               // 3 TAD
        ADCTRGMODEbits.SH2ALT = 0;    // AN2 is the input to ADC2
        ADC2TIMEbits.SELRES = 3;           //ADC2 12bit resolution mode
        ADC2TIMEbits.BCHEN=1;              // ADC data saved in DMA system ram buffer
        ADCTRG1bits.TRGSRC2 = 12;       // Set AN2 to trigger from PWM Trigger 3
        ADCANCONbits.ANEN2 = 1;          // Enable ADC2 analog bias/logic
        ADCCON3bits.DIGEN2 = 1;           // Enable ADC2 digital logic      
    }

    //******************************************************************************
    // ADC3 Module setup:
    //    TAD = SYSCLK/2, 12bit, DMA enb, SAMC=3TAD, PWM4 Trigger
    //******************************************************************************
    if(INTERLEAVED_ADC_COUNT  >  3) 
    {
        ADC3TIMEbits.ADCDIV = 1;
        ADC3TIMEbits.SAMC = 1;                         // 3 TAD
        ADCTRGMODEbits.SH3ALT = 1;   // AN0 is the input to ADC3
        ADC3TIMEbits.SELRES = 3;          //ADC3 12bit resolution mode
        ADC3TIMEbits.BCHEN=1;             // ADC data saved in DMA system ram buffer
        ADCTRG1bits.TRGSRC3 = 13;       // Set AN3 to trigger from PWM Trigger 4
        ADCANCONbits.ANEN3 = 1;          // Enable ADC2 analog bias/logic
        ADCCON3bits.DIGEN3 = 1;           // Enable ADC2 digital logic     
    }

    //******************************************************************************
    // ADC4 Module setup:
    //    TAD = SYSCLK/2, 12bit, DMA enb, SAMC=3TAD, PWM5 Trigger
    //******************************************************************************
    if(INTERLEAVED_ADC_COUNT  >  4)
    {
        ADC4TIMEbits.ADCDIV = 1;
        ADC4TIMEbits.SAMC = 1;                         // 3 TAD
        ADCTRGMODEbits.SH4ALT = 3;   // AN0 is the input to ADC4
        ADC4TIMEbits.SELRES = 3;          //ADC4 12bit resolution mode
        ADC4TIMEbits.BCHEN=1;             // ADC data saved in DMA system ram buffer
        ADCTRG2bits.TRGSRC4 = 14;       // Set AN4 to trigger from PWM Trigger 5
        ADCANCONbits.ANEN4 = 1;          // Enable ADC2 analog bias/logic
        ADCCON3bits.DIGEN4 = 1;           // Enable ADC2 digital logic      
    }

    //******************************************************************************
    // ADC5 Module setup:
    //    TAD = SYSCLK/2, 12bit, DMA enb, SAMC=3TAD, PWM6 Trigger
    //******************************************************************************
    if(INTERLEAVED_ADC_COUNT  >  5) 
    {
        ADC5TIMEbits.ADCDIV = 1;
        ADC5TIMEbits.SAMC = 1;              // 3 TAD
        ADCTRGMODEbits.SH5ALT = 0;   // AN5 is the input to ADC5
        ADC5TIMEbits.SELRES = 3;          //ADC5 12bit resolution mode
        ADC5TIMEbits.BCHEN=1;             // ADC data saved in DMA system ram buffer
        ADCTRG2bits.TRGSRC5 = 15;     // Set AN5 to trigger from PWM Trigger 6
        ADCANCONbits.ANEN5 = 1;         // Enable ADC2 analog bias/logic
        ADCCON3bits.DIGEN5 = 1;           // Enable ADC2 digital logic
    }
        
    //******************************************************************************
    // ADC DMA Master Configuration (PIC32MKxxxx Only)
    // 12 buffers @128 samples/buffer = (6 ping pong buffer A?s, 6 ping pong buffer B?s) 
    //******************************************************************************
    ADCCON1bits.DMABL = 7; // ADC Buffer length = 128 samples 
    
    ADCDMAB = (unsigned int) &adc_dma_raw_buffer & 0x1FFFFFFF;
    ADCDSTATbits.DMAEN = 1;
     
    ADCDSTAT = (ADCDSTAT | DMA_INT_SRC);	// Enb DMA Buff Full A&B Interrupt
    IFS3bits.AD1FCBTIF = 0; 		// Clear DMA Buffer full interrupt flag
    IPC26bits.AD1FCBTIP = 1;		// Set DMA Buffer full interrupt priority
    IPC26bits.AD1FCBTIS = 1;     	// Set DMA Buffer full interrupt sub-priority
    IEC3bits.AD1FCBTIE = 1;         	// Enable DMA Buffer full interrupt

    ADCCON1bits.ON = 1;        		// Turn the ADC on

    //******************************************************************************
    // Waiting for ADC warm-up time and ADC bandgap reference to stabilize
    //******************************************************************************
    while(!ADCCON2bits.BGVRRDY);	// Wait until the reference voltage is ready
    while(!ADCANCONbits.WKRDY0); 	// Wait until ADC0 is ready
    while(!ADCANCONbits.WKRDY1); 	// Wait until ADC1 is ready
    if(INTERLEAVED_ADC_COUNT  >  2) while(!ADCANCONbits.WKRDY2); // Wait until ADC2 is ready
    if(INTERLEAVED_ADC_COUNT  >  3) while(!ADCANCONbits.WKRDY3); // Wait until ADC3 is ready
    if(INTERLEAVED_ADC_COUNT  >  4) while(!ADCANCONbits.WKRDY4); // Wait until ADC4 is ready
    if(INTERLEAVED_ADC_COUNT  >  5) while(!ADCANCONbits.WKRDY5); // Wait until ADC5 is ready
        
    init_PWM();             //Initialize internal PWM interleaved triggers and 
                                   //configure PWM pins for general purpose I/O or alternate PPS
}

// $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
// $     I N T E R R U P T   S E R V I C E    R O U T I N E S        $
// $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$

// *************************************************************************************************
//        D M A   I S R (Interrupt Service Routine)
// *************************************************************************************************
void __attribute__((interrupt(ipl7auto), at_vector(_ADC_DMA_VECTOR), aligned(16))) isr ()
{
    ADC_var = ADCDSTAT;               // To preserve ADCSTAT reg after read
      
    //******************************************************************************
    // DMA Ping-Pong Buffer A full processing
    // Each ADC samples (1 / INTERLEAVED_ADC_COUNT) of the analog input signal. For 
    // CPU processing of the interleaved ADC data it must be read from each of the
    // active interleaved ADC's dedicated ping-pong buffer A and rearranged in 
    // sequential order in a CPU ADC result system SRAM buffer, for signal processing  
    //******************************************************************************
    if(ADC_var & DMA_BUFA_FULL)       //Last sequential ADC RAM DMA ping-pong Buffer A full.
    {       
        adc0_ptr = (unsigned short*)&adc_dma_raw_buffer; // Initialize ADC0 Source pointer to ADC0_BUFFER_A location in SRAM
        adc1_ptr = adc0_ptr + ADC_SAMPLE_OFFSET;	// Initialize ADC1 Source pointer to ADC1_BUFFER_A location in SRAM

        #if (INTERLEAVED_ADC_COUNT  >  2)
            adc2_ptr = adc1_ptr +ADC_SAMPLE_OFFSET;	// Init ADC2 Src ptr to ADC2_BUFFER_A
        #endif

        #if (INTERLEAVED_ADC_COUNT  >  3)
            adc3_ptr = adc2_ptr +ADC_SAMPLE_OFFSET;	// Init ADC3 Src ptr to ADC3_BUFFER_A
        #endif

        #if (INTERLEAVED_ADC_COUNT  >  4)
            adc4_ptr = adc3_ptr +ADC_SAMPLE_OFFSET;	// Init ADC4 Src ptr to ADC4_BUFFER_A
        #endif

        #if (INTERLEAVED_ADC_COUNT  >  5)
            adc5_ptr = adc4_ptr +ADC_SAMPLE_OFFSET;	// Init ADC5 Src ptr to ADC5_BUFFER_A
        #endif
      
       //******************************************************************************
       // Initialize DMA destination pointer to base address where re-arranged 
       // ADC data from Buffer A is stored in SRAM
       //******************************************************************************
       dest_ptr = (unsigned short*)&adc_data_rearranged_buffer[buffer_index][0];

       //******************************************************************************
       // Initialize DMA End address value for re-arranged ADC Data from Buffer A.
       //******************************************************************************
       end_ptr  = (unsigned short*)&adc_data_rearranged_buffer[buffer_index][0]+ HALF_BUFFER_LENGTH; 

       do
        {
             *dest_ptr++ = *adc0_ptr++;
             *dest_ptr++ = *adc1_ptr++;

             #if (INTERLEAVED_ADC_COUNT  >  2)
                 *dest_ptr++ = *adc2_ptr++;
             #endif

             #if (INTERLEAVED_ADC_COUNT  >  3)
                 *dest_ptr++ = *adc3_ptr++;
             #endif

             #if (INTERLEAVED_ADC_COUNT  >  4)
                 *dest_ptr++ = *adc4_ptr++;
             #endif

             #if (INTERLEAVED_ADC_COUNT  >  5)
                 *dest_ptr++ = *adc5_ptr++;
             #endif
        }
        while(dest_ptr < end_ptr); // Continue Re-arranging ADC data until all Buffer A data for active ADCs is complete
        
        buffer_a_index++; 	// Increment buffer_a_index by 1 at the end of re-arrangement of Buffer A data.
    }

    //******************************************************************************
    // DMA Ping-Pong Buffer "B" full processing
    // Each ADC samples (1/INTERLEAVED_ADC_COUNT)of the analog input signal. For 
    // CPU processing of the interleaved ADC data it must be read from each of the
    // active interleaved ADC's dedicated ping-pong buffer "B" and rearranged in 
    // sequential order in the CPU ADC result system SRAM buffer.   
    //******************************************************************************
    if(ADC_var & DMA_BUFB_FULL)       //Last sequential ADC RAM DMA ping-pong Buffer B full
    {   
        adc0_ptr = (unsigned short*)&adc_dma_raw_buffer + ADC_INTER_BUFFER_OFFSET; // Init ADC0 Src ptr to ADC0_BUFFER_B in SRAM
        adc1_ptr = adc0_ptr +ADC_SAMPLE_OFFSET;					// Init ADC1 Src ptr to ADC1_BUFFER_B in SRAM

        #if (INTERLEAVED_ADC_COUNT  >  2)
            adc2_ptr = adc1_ptr +ADC_SAMPLE_OFFSET;	// Init ADC2 Src ptr to ADC2_BUFFER_B
        #endif

        #if (INTERLEAVED_ADC_COUNT  >  3)
            adc3_ptr = adc2_ptr +ADC_SAMPLE_OFFSET;	// Init ADC3 Src ptr to ADC3_BUFFER_B
        #endif

        #if (INTERLEAVED_ADC_COUNT  >  4)
            adc4_ptr = adc3_ptr +ADC_SAMPLE_OFFSET;	// Init ADC4 Src ptr to ADC4_BUFFER_B
        #endif

        #if (INTERLEAVED_ADC_COUNT  >  5)
            adc5_ptr = adc4_ptr +ADC_SAMPLE_OFFSET;	// Init ADC5 Src ptr to ADC5_BUFFER_B
        #endif
 
       //******************************************************************************
       // Initialize DMA destination pointer to base address where re-arranged 
       // ADC data from Buffer B is stored in SRAM
       //******************************************************************************     
        dest_ptr = (unsigned short*)&adc_data_rearranged_buffer[buffer_index][0] + HALF_BUFFER_LENGTH;

       //******************************************************************************
       // Initialize DMA End address value for re-arranged ADC Data from Buffer B.
       //******************************************************************************
        end_ptr  = (unsigned short*)&adc_data_rearranged_buffer[buffer_index][0]+TOTAL_BUFFER_LENGTH; // End addr for re-arranged ADC Data from Buffer B.

        do
        {
          *dest_ptr++ = *adc0_ptr++;
          *dest_ptr++ = *adc1_ptr++;

          #if (INTERLEAVED_ADC_COUNT  >  2)
              *dest_ptr++ = *adc2_ptr++;
          #endif

          #if (INTERLEAVED_ADC_COUNT  >  3)
              *dest_ptr++ = *adc3_ptr++;
          #endif

          #if (INTERLEAVED_ADC_COUNT  >  4)
              *dest_ptr++ = *adc4_ptr++;
          #endif

          #if (INTERLEAVED_ADC_COUNT  >  5)
              *dest_ptr++ = *adc5_ptr++;
          #endif

        }
        while(dest_ptr < end_ptr);	// Continue Re-arranging ADC data until all Buffer A data for active ADCs is complete
        
        buffer_b_index++;  		// Increment buffer_a_index by 1 at the end of re-arrangement of Buffer A data.
    }

    //******************************************************************************
    // adc_data_rearranged_buffer is a 2 dimensional buffer. If both Buffer A and 
    // Buffer B has been transferred, increment the most significant buffer index
    // for next set of Buffer A and Buffer B data
    //******************************************************************************
    if(buffer_a_index == buffer_b_index) buffer_index++;

    //******************************************************************************
    // adc_data_rearranged_buffer is also circular in nature, after reaching the
    // end of the buffer, the index is re-initialized to start of the buffer.
    //******************************************************************************
    if (buffer_index >= BUFFER_RPT_COUNT) buffer_index = 0;
    
    IFS3bits.AD1FCBTIF = 0;  // Clear the ADC DMA Interrupt
  
} //End DMA ISR

