1 Triggering Multiple ADC Channels from One Source

Several ADC channels can be assigned to convert the same analog input (pin). If these channels are triggered from a single source at the same time, the ADC converts all channels sequentially, starting with the channel with the lowest number and ending with the channel with the highest number. The data-ready bit in the ADxSTAT register or the interrupt from the last processed channel with the highest number can be used to detect when all channel conversions are complete. The software can then move the conversion results from the ADxCHyDATA registers to a RAM buffer.

The code in Capturing 16 Samples Using 16 ADC Channels at 40 MSPS Conversion Rate shows how to capture 16 samples from a pin using 16 channels triggered from a single source. The code in Capturing 256 Samples Using 16 ADC Channels and CPU at 40 MSPS Conversion Rate repeats the 16-channel capture described in Capturing 16 Samples Using 16 ADC Channels at 40 MSPS Conversion Rate 16 times to obtain 256 ADC samples. For robust timing, the CPU and ADC are clocked from the same PLL, and interrupts are disabled during ADC conversions. Both examples should be executed on the dsPIC33A Curiosity Platform Development Board (EV74H48A) with the dsPIC33AK128MC106 Curiosity GP DIM (EV02G02A).

Capturing 16 Samples Using 16 ADC Channels at 40 MSPS Conversion Rate

// WDT is disabled
#pragma config FWDT_WDTEN = SW 
#include <xc.h>

int main(){
    
    // initialize clock
    OSCCTRLbits.POSCEN = 1; // POSC enable
    OSCCFGbits.POSCMD = 0; // EC POSC clock on OSCI pin connected to 8MHz MEMS
    
    PLL1CONbits.ON = 1;
    OSCCTRLbits.PLL1EN = 1;
    while(OSCCTRLbits.PLL1RDY == 0);
    PLL1CONbits.FSCMEN = 0; // disable clock fail monitor
        
    VCO1DIVbits.INTDIV = 4; // VCO divider 1:8 = 200MHz
    
    PLL1DIVbits.PLLFBDIV = 200; // VCO = 1600 MHz
    PLL1DIVbits.PLLPRE = 1;
    PLL1DIVbits.POSTDIV1 = 5; // PLL1 = 1600MHz:5 = 320 MHz for ADC
    PLL1DIVbits.POSTDIV2 = 1;
    
    PLL1CONbits.DIVSWEN = 1;
    while(PLL1CONbits.DIVSWEN == 1);    
    
    PLL1CONbits.NOSC = 3; // clock from POSC EC (8MHz MEMS on board)

    PLL1CONbits.OSWEN = 1;
    while(PLL1CONbits.OSWEN == 1);        

    PLL1CONbits.FOUTSWEN = 1;
    while(PLL1CONbits.FOUTSWEN == 1);
    PLL1CONbits.PLLSWEN = 1;
    while(PLL1CONbits.PLLSWEN == 1);
    
    while(PLL1CONbits.CLKRDY == 0);   
       
    // CPU clock 200 MHz (Generator 1)
    CLK1CONbits.ON = 1;
    CLK1CONbits.NOSC = 7; // PLL1 VCO divider
    CLK1CONbits.OSWEN = 1;
    while(CLK1CONbits.OSWEN == 1); 
    while(CLK1CONbits.CLKRDY == 0);
    
    // ADC input clock (Generator 6)
    CLK6CONbits.ON = 1;
    CLK6CONbits.NOSC = 5; // PLL1 320 MHz or 40MSPS
    CLK6CONbits.OSWEN = 1;
    while(CLK6CONbits.OSWEN == 1); 
    while(CLK6CONbits.CLKRDY == 0);
    
    // CCPs clock  (Generator 12) should be same as ADC to trigger 
    CLK12CONbits.ON = 1;
    CLK12CONbits.NOSC = 5; // PLL1 320 MHz
    CLK12CONbits.OSWEN = 1;
    while(CLK12CONbits.OSWEN == 1); 
    while(CLK12CONbits.CLKRDY == 0);

    // initialize UART
#define UART_BAUD (230400UL) // UART baud rate
#define FCY (200000000UL) // FCY frequencyu in Hz    
    _RP58R = 9; // map UART1 TX to RP58/RD9 pin (board PKOB UART)
    U1BRG = FCY/2/UART_BAUD/4-1;    
    U1CONbits.BRGS = 1;   
    U1CONbits.TXEN = 1;
    U1CONbits.ON = 1;     

    // All channels triggered periodically from one source (CCP1 = option 12)
    AD2CH0CONbits.TRG1SRC = 12; // from CCP1
    AD2CH1CONbits.TRG1SRC = 12; // from CCP1
    AD2CH2CONbits.TRG1SRC = 12; // from CCP1
    AD2CH3CONbits.TRG1SRC = 12; // from CCP1
    AD2CH4CONbits.TRG1SRC = 12; // from CCP1
    AD2CH5CONbits.TRG1SRC = 12; // from CCP1
    AD2CH6CONbits.TRG1SRC = 12; // from CCP1
    AD2CH7CONbits.TRG1SRC = 12; // from CCP1
    AD2CH8CONbits.TRG1SRC = 12; // from CCP1
    AD2CH9CONbits.TRG1SRC = 12; // from CCP1
    AD2CH10CONbits.TRG1SRC = 12; // from CCP1
    AD2CH11CONbits.TRG1SRC = 12; // from CCP1
    AD2CH12CONbits.TRG1SRC = 12; // from CCP1
    AD2CH13CONbits.TRG1SRC = 12; // from CCP1
    AD2CH14CONbits.TRG1SRC = 12; // from CCP1
    AD2CH15CONbits.TRG1SRC = 12; // from CCP1

    // All channels are assigned to/converting one pin 
    AD2CH0CONbits.PINSEL = 9; // AD2AN9/RA8 connected to J4 connector
    AD2CH1CONbits.PINSEL = 9; // AD2AN9 connected to J4
    AD2CH2CONbits.PINSEL = 9; // AD2AN9 connected to J4
    AD2CH3CONbits.PINSEL = 9; // AD2AN9 connected to J4    
    AD2CH4CONbits.PINSEL = 9; // AD2AN9 connected to J4
    AD2CH5CONbits.PINSEL = 9; // AD2AN9 connected to J4
    AD2CH6CONbits.PINSEL = 9; // AD2AN9 connected to J4
    AD2CH7CONbits.PINSEL = 9; // AD2AN9 connected to J4    
    AD2CH8CONbits.PINSEL = 9; // AD2AN9 connected to J4
    AD2CH9CONbits.PINSEL = 9; // AD2AN9 connected to J4
    AD2CH10CONbits.PINSEL = 9; // AD2AN9 connected to J4
    AD2CH11CONbits.PINSEL = 9; // AD2AN9 connected to J4    
    AD2CH12CONbits.PINSEL = 9; // AD2AN9 connected to J4
    AD2CH13CONbits.PINSEL = 9; // AD2AN9 connected to J4
    AD2CH14CONbits.PINSEL = 9; // AD2AN9 connected to J4
    AD2CH15CONbits.PINSEL = 9; // AD2AN9 connected to J4    

    // Minimum sampling time to get 40MSPS
    AD2CH0CONbits.SAMC = 0; // 0.5 TAD
    AD2CH1CONbits.SAMC = 0; // 0.5 TAD
    AD2CH2CONbits.SAMC = 0; // 0.5 TAD
    AD2CH3CONbits.SAMC = 0; // 0.5 TAD    
    AD2CH4CONbits.SAMC = 0; // 0.5 TAD
    AD2CH5CONbits.SAMC = 0; // 0.5 TAD
    AD2CH6CONbits.SAMC = 0; // 0.5 TAD
    AD2CH7CONbits.SAMC = 0; // 0.5 TAD    
    AD2CH8CONbits.SAMC = 0; // 0.5 TAD
    AD2CH9CONbits.SAMC = 0; // 0.5 TAD
    AD2CH10CONbits.SAMC = 0; // 0.5 TAD
    AD2CH11CONbits.SAMC = 0; // 0.5 TAD    
    AD2CH12CONbits.SAMC = 0; // 0.5 TAD
    AD2CH13CONbits.SAMC = 0; // 0.5 TAD
    AD2CH14CONbits.SAMC = 0; // 0.5 TAD
    AD2CH15CONbits.SAMC = 0; // 0.5 TAD    
    
    // enable ADC
    AD2CONbits.ON = 1;
    while(AD2CONbits.ADRDY == 0);      
      
    CCP1CON1bits.CLKSEL = 1; // use generator 12 same as ADC clock = 320MHz
    CCP1CON1bits.MOD = 0; // timer mode
    CCP1CON1bits.T32 = 1; // 32-bit     
    CCP1PR = 320000000UL-1; // trigger every second all 16 channels
    CCP1TMR = 0;
    CCP1CON1bits.ON = 1; // start conversions
        
    while(1){
        // wait for the last channel data is available
        while(AD2STATbits.CH15RDY == 0);
		
		// output to UART
        U1TXB = 0x55; // MPLAB Data Visualizer start token
        U1TXB = (unsigned char)AD2CH0DATA; // low byte of UINT16
        U1TXB = (unsigned char)(AD2CH0DATA>>8); // high byte of UINT16
        U1TXB = 0xaa; // MPLAB Data Visualizer end token
        U1TXB = 0x55;
        U1TXB = (unsigned char)AD2CH1DATA;
        U1TXB = (unsigned char)(AD2CH1DATA>>8);
        U1TXB = 0xaa;
        while(U1STATbits.TXBE == 0); // UART FIFO 8 bytes
        U1TXB = 0x55;
        U1TXB = (unsigned char)AD2CH2DATA;
        U1TXB = (unsigned char)(AD2CH2DATA>>8);
        U1TXB = 0xaa;
        U1TXB = 0x55;
        U1TXB = (unsigned char)AD2CH3DATA;
        U1TXB = (unsigned char)(AD2CH3DATA>>8);
        U1TXB = 0xaa;
        while(U1STATbits.TXBE == 0);
        U1TXB = 0x55;
        U1TXB = (unsigned char)AD2CH4DATA;
        U1TXB = (unsigned char)(AD2CH4DATA>>8);
        U1TXB = 0xaa;
        U1TXB = 0x55;
        U1TXB = (unsigned char)AD2CH5DATA;
        U1TXB = (unsigned char)(AD2CH5DATA>>8);
        U1TXB = 0xaa;
        while(U1STATbits.TXBE == 0);
        U1TXB = 0x55;
        U1TXB = (unsigned char)AD2CH6DATA;
        U1TXB = (unsigned char)(AD2CH6DATA>>8);
        U1TXB = 0xaa;
        U1TXB = 0x55;
        U1TXB = (unsigned char)AD2CH7DATA;
        U1TXB = (unsigned char)(AD2CH7DATA>>8);
        U1TXB = 0xaa;
        while(U1STATbits.TXBE == 0);
        U1TXB = 0x55;
        U1TXB = (unsigned char)AD2CH8DATA;
        U1TXB = (unsigned char)(AD2CH8DATA>>8);
        U1TXB = 0xaa;
        U1TXB = 0x55;
        U1TXB = (unsigned char)AD2CH9DATA;
        U1TXB = (unsigned char)(AD2CH9DATA>>8);
        U1TXB = 0xaa;
        while(U1STATbits.TXBE == 0);
        U1TXB = 0x55;
        U1TXB = (unsigned char)AD2CH10DATA;
        U1TXB = (unsigned char)(AD2CH10DATA>>8);
        U1TXB = 0xaa;
        U1TXB = 0x55;
        U1TXB = (unsigned char)AD2CH11DATA;
        U1TXB = (unsigned char)(AD2CH11DATA>>8);
        U1TXB = 0xaa;
        while(U1STATbits.TXBE == 0);
        U1TXB = 0x55;
        U1TXB = (unsigned char)AD2CH12DATA;
        U1TXB = (unsigned char)(AD2CH12DATA>>8);
        U1TXB = 0xaa;
        U1TXB = 0x55;
        U1TXB = (unsigned char)AD2CH13DATA;
        U1TXB = (unsigned char)(AD2CH13DATA>>8);
        U1TXB = 0xaa;
        while(U1STATbits.TXBE == 0);
        U1TXB = 0x55;
        U1TXB = (unsigned char)AD2CH14DATA;
        U1TXB = (unsigned char)(AD2CH14DATA>>8);
        U1TXB = 0xaa;
        U1TXB = 0x55;
        U1TXB = (unsigned char)AD2CH15DATA;
        U1TXB = (unsigned char)(AD2CH15DATA>>8);
        U1TXB = 0xaa;
    }
    return 1;
}

Capturing 256 Samples Using 16 ADC Channels and CPU at 40 MSPS Conversion Rate

// WDT is disabled
#pragma config FWDT_WDTEN = SW 
#include <xc.h>

#define BUFFER_SIZE     256  // must be divisible by 16 channels
unsigned long buffer[BUFFER_SIZE];

int main(){
    
    // initialize clock
    OSCCTRLbits.POSCEN = 1; // POSC enable
    OSCCFGbits.POSCMD = 0; // EC POSC clock on OSCI pin connected to 8MHz MEMS
    
    PLL1CONbits.ON = 1;
    OSCCTRLbits.PLL1EN = 1;
    while(OSCCTRLbits.PLL1RDY == 0);
    PLL1CONbits.FSCMEN = 0; // disable clock fail monitor
        
    VCO1DIVbits.INTDIV = 4; // VCO divider 1:8 = 200MHz
    
    PLL1DIVbits.PLLFBDIV = 200; // VCO = 1600 MHz
    PLL1DIVbits.PLLPRE = 1;
    PLL1DIVbits.POSTDIV1 = 5; // PLL1 = 1600MHz:5 = 320 MHz for ADC
    PLL1DIVbits.POSTDIV2 = 1;
    
    PLL1CONbits.DIVSWEN = 1;
    while(PLL1CONbits.DIVSWEN == 1);    
    
    PLL1CONbits.NOSC = 3; // clock from POSC EC (8MHz MEMS on board)

    PLL1CONbits.OSWEN = 1;
    while(PLL1CONbits.OSWEN == 1);        

    PLL1CONbits.FOUTSWEN = 1;
    while(PLL1CONbits.FOUTSWEN == 1);
    PLL1CONbits.PLLSWEN = 1;
    while(PLL1CONbits.PLLSWEN == 1);
    
    while(PLL1CONbits.CLKRDY == 0);   
       
    // CPU clock 200 MHz (Generator 1)
    CLK1CONbits.ON = 1;
    CLK1CONbits.NOSC = 7; // PLL1 VCO divider
    CLK1CONbits.OSWEN = 1;
    while(CLK1CONbits.OSWEN == 1); 
    while(CLK1CONbits.CLKRDY == 0);
    
    // ADC input clock (Generator 6)
    CLK6CONbits.ON = 1;
    CLK6CONbits.NOSC = 5; // PLL1 320 MHz or 40MSPS
    CLK6CONbits.OSWEN = 1;
    while(CLK6CONbits.OSWEN == 1); 
    while(CLK6CONbits.CLKRDY == 0);
    
    // CCPs clock  (Generator 12) should be same as ADC to trigger 
    CLK12CONbits.ON = 1;
    CLK12CONbits.NOSC = 5; // PLL1 320 MHz
    CLK12CONbits.OSWEN = 1;
    while(CLK12CONbits.OSWEN == 1); 
    while(CLK12CONbits.CLKRDY == 0);

    // initialize UART
#define UART_BAUD (230400UL) // UART baud rate
#define FCY (200000000UL) // FCY frequencyu in Hz    
    _RP58R = 9; // map UART1 TX to RP58/RD9 pin (board PKOB UART)
    U1BRG = FCY/2/UART_BAUD/4-1;    
    U1CONbits.BRGS = 1;   
    U1CONbits.TXEN = 1;
    U1CONbits.ON = 1;     

    // All channels triggered periodically from one source (CCP1 = option 12)
    AD2CH0CONbits.TRG1SRC = 12; // from CCP1
    AD2CH1CONbits.TRG1SRC = 12; // from CCP1
    AD2CH2CONbits.TRG1SRC = 12; // from CCP1
    AD2CH3CONbits.TRG1SRC = 12; // from CCP1
    AD2CH4CONbits.TRG1SRC = 12; // from CCP1
    AD2CH5CONbits.TRG1SRC = 12; // from CCP1
    AD2CH6CONbits.TRG1SRC = 12; // from CCP1
    AD2CH7CONbits.TRG1SRC = 12; // from CCP1
    AD2CH8CONbits.TRG1SRC = 12; // from CCP1
    AD2CH9CONbits.TRG1SRC = 12; // from CCP1
    AD2CH10CONbits.TRG1SRC = 12; // from CCP1
    AD2CH11CONbits.TRG1SRC = 12; // from CCP1
    AD2CH12CONbits.TRG1SRC = 12; // from CCP1
    AD2CH13CONbits.TRG1SRC = 12; // from CCP1
    AD2CH14CONbits.TRG1SRC = 12; // from CCP1
    AD2CH15CONbits.TRG1SRC = 12; // from CCP1

    // All channels are assigned to/converting one pin 
    AD2CH0CONbits.PINSEL = 9; // AD2AN9/RA8 connected to J4 connector
    AD2CH1CONbits.PINSEL = 9; // AD2AN9 connected to J4
    AD2CH2CONbits.PINSEL = 9; // AD2AN9 connected to J4
    AD2CH3CONbits.PINSEL = 9; // AD2AN9 connected to J4    
    AD2CH4CONbits.PINSEL = 9; // AD2AN9 connected to J4
    AD2CH5CONbits.PINSEL = 9; // AD2AN9 connected to J4
    AD2CH6CONbits.PINSEL = 9; // AD2AN9 connected to J4
    AD2CH7CONbits.PINSEL = 9; // AD2AN9 connected to J4    
    AD2CH8CONbits.PINSEL = 9; // AD2AN9 connected to J4
    AD2CH9CONbits.PINSEL = 9; // AD2AN9 connected to J4
    AD2CH10CONbits.PINSEL = 9; // AD2AN9 connected to J4
    AD2CH11CONbits.PINSEL = 9; // AD2AN9 connected to J4    
    AD2CH12CONbits.PINSEL = 9; // AD2AN9 connected to J4
    AD2CH13CONbits.PINSEL = 9; // AD2AN9 connected to J4
    AD2CH14CONbits.PINSEL = 9; // AD2AN9 connected to J4
    AD2CH15CONbits.PINSEL = 9; // AD2AN9 connected to J4    

    // Minimum sampling time to get 40MSPS
    AD2CH0CONbits.SAMC = 0; // 0.5 TAD
    AD2CH1CONbits.SAMC = 0; // 0.5 TAD
    AD2CH2CONbits.SAMC = 0; // 0.5 TAD
    AD2CH3CONbits.SAMC = 0; // 0.5 TAD    
    AD2CH4CONbits.SAMC = 0; // 0.5 TAD
    AD2CH5CONbits.SAMC = 0; // 0.5 TAD
    AD2CH6CONbits.SAMC = 0; // 0.5 TAD
    AD2CH7CONbits.SAMC = 0; // 0.5 TAD    
    AD2CH8CONbits.SAMC = 0; // 0.5 TAD
    AD2CH9CONbits.SAMC = 0; // 0.5 TAD
    AD2CH10CONbits.SAMC = 0; // 0.5 TAD
    AD2CH11CONbits.SAMC = 0; // 0.5 TAD    
    AD2CH12CONbits.SAMC = 0; // 0.5 TAD
    AD2CH13CONbits.SAMC = 0; // 0.5 TAD
    AD2CH14CONbits.SAMC = 0; // 0.5 TAD
    AD2CH15CONbits.SAMC = 0; // 0.5 TAD    
    
    // enable ADC
    AD2CONbits.ON = 1;
    while(AD2CONbits.ADRDY == 0);      

    // initialize triggers
    CCP1CON1bits.CLKSEL = 1; // use generator 12 same as ADC clock = 320MHz
    CCP1CON1bits.MOD = 0; // timer mode
    CCP1CON1bits.T32 = 1; // 32-bit     
    // Triggers period is  16 channels x (320MHz : 40MSPS)
    CCP1PR = 16UL*(320UL/40UL)-1;
    CCP1RB = 0; 
    
    while(1){
        
        CCP1TMR = 0; // initialize the trigger time base       
        INTCON1bits.GIE = 0; // disable interrupts
        asm volatile(
            // each CCP1 trigger is 16 conversions
            // buffer length / 16
            "mov.l #%1/16, w1 \n"
            // load buffer address
            "mov.l #%0, w2 \n"
            // start conversions, ON bit = 1
            "bset CCP1CON1, #15 \n" 
            // start reading next 16 conversions in advance 
            // when channel 9 has finished conversion
            "1: \n" 
            "btst AD2STAT, #9 \n"
            "bra z, 1b \n"
            "mov.l AD2CH0DATA, w0 \n"
            "mov.l w0, [w2++] \n"
            "mov.l AD2CH1DATA, w0 \n"
            "mov.l w0, [w2++] \n"
            "mov.l AD2CH2DATA, w0 \n"
            "mov.l w0, [w2++] \n"
            "mov.l AD2CH3DATA, w0 \n"
            "mov.l w0, [w2++] \n"
            "mov.l AD2CH4DATA, w0 \n"
            "mov.l w0, [w2++] \n"
            "mov.l AD2CH5DATA, w0 \n"
            "mov.l w0, [w2++] \n"
            "mov.l AD2CH6DATA, w0 \n"
            "mov.l w0, [w2++] \n"
            "mov.l AD2CH7DATA, w0 \n"
            "mov.l w0, [w2++] \n"
            "mov.l AD2CH8DATA, w0 \n"
            "mov.l w0, [w2++] \n"
            "mov.l AD2CH9DATA, w0 \n"
            "mov.l w0, [w2++] \n"
            "mov.l AD2CH10DATA, w0 \n"
            "mov.l w0, [w2++] \n"
            "mov.l AD2CH11DATA, w0 \n"
            "mov.l w0, [w2++] \n"
            "mov.l AD2CH12DATA, w0 \n"
            "mov.l w0, [w2++] \n"
            "mov.l AD2CH13DATA, w0 \n"
            "mov.l w0, [w2++] \n"
            "mov.l AD2CH14DATA, w0 \n"
            "mov.l w0, [w2++] \n"
            "mov.l AD2CH15DATA, w0 \n"
            "mov.l w0, [w2++] \n"
            // check if all buffer locations are written
            // if not then repeat
            "dtb w1, 1b \n"
            // stop triggers, ON bit  = 0
            "bclr CCP1CON1, #15 \n"         
        ::"i"((unsigned long)buffer),"i"(BUFFER_SIZE):"w0","w1","w2");        
        INTCON1bits.GIE = 1; // enable interrupts    

        // output to UART
        unsigned short count = BUFFER_SIZE; 
        unsigned long* pointer = buffer;
   
        while(count--){
            while(U1STATbits.TXBE == 0); // wait for a free space in FIFO
            U1TXB = 0x55; // MPLAB Data Visualizer start token
            U1TXB = (unsigned char)*pointer; // low byte of UINT16
            U1TXB = (unsigned char)(*pointer>>8); // high byte of UINT16    
            U1TXB = 0xaa;// MPLAB Data Visualizer end token
            pointer++;
        }
    }    
    
    return 1;
}