This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

Compiler/F28M35H52C: Best Approach to Simultaneous ADC Conversions

Part Number: F28M35H52C

Tool/software: TI C/C++ Compiler

I have a C28 program running based off the adc_soc example that uses ePWM and has code as follows but at present it's only running ADC1

void main(void)
{
// Step 1. Initialize System Control:
// PLL, WatchDog, enable Peripheral Clocks
// This example function is found in the F28M35x_SysCtrl.c file.
    InitSysCtrl();

#ifdef _FLASH
// Copy time critical code and Flash setup code to RAM
// This includes the following functions:  InitFlash();
// The  RamfuncsLoadStart, RamfuncsLoadSize, and RamfuncsRunStart
// symbols are created by the linker. Refer to the device .cmd file.
    memcpy(&RamfuncsRunStart, &RamfuncsLoadStart, (size_t)&RamfuncsLoadSize);

// Call Flash Initialization to setup flash waitstates
// This function must reside in RAM
    InitFlash();
#endif

// Step 2. Initialize GPIO:
// Initialize GPIO
    InitGpio(); // set the GPIO to it's default state.
    EALLOW;
    GpioG1CtrlRegs.GPADIR.bit.GPIO29 = 1; //set PE5_GPIO29 as output TM - this is for the direct-wired LED I added to my PCB
    EDIS;

// Step 3. Clear all interrupts and initialize PIE vector table:
// Disable CPU interrupts
    DINT;

// Initialize the PIE control registers to their default state.
// The default state is all PIE interrupts disabled and flags
// are cleared.
// This function is found in the F28M35x_PieCtrl.c file.
    InitPieCtrl();

// Disable CPU interrupts and clear all CPU interrupt flags:
    IER = 0x0000;
    IFR = 0x0000;

// Initialize the PIE vector table with pointers to the shell Interrupt
// Service Routines (ISR).
// This will populate the entire table, even if the interrupt
// is not used in this example.  This is useful for debug purposes.
// The shell ISR routines are found in F28M35x_DefaultIsr.c.
// This function is found in F28M35x_PieVect.c.
    InitPieVectTable();

// Interrupts that are used in this example are re-mapped to
// ISR functions found within this file.
    EALLOW; // This is needed to write to EALLOW protected registers
    PieVectTable.ADCINT1 = &adc1_isr;           // TM from adc_soc_c28.c, points to local isr code that is run after ADC conversion
    EDIS;   // This is needed to disable write to EALLOW protected registers

// Step 4. Initialize the Device Peripheral. This function can be
//         found in F28M35x_CpuTimers.c
//#    InitCpuTimers();     // TM initialize the Cpu Timers (not using timers so disabled)
    InitAdc1();             // TM initialize ADC1 (need to add ADC2 when we get that far)

    // Enable ADCINT1 in PIE
    PieCtrlRegs.PIEIER1.bit.INTx1 = 1;  // Enable INT 1.1 in the PIE
    IER |= M_INT1;                      // Enable CPU Interrupt 1
    EINT;                               // Enable Global interrupt INTM
    ERTM;                               // Enable Global realtime interrupt DBGM

    LoopCount = 0;          // TM not sure if we need this
    ConversionCount = 0;    // TM not sure if we need this


// Configure ADC
    EALLOW;
    Adc1Regs.ADCCTL2.bit.ADCNONOVERLAP = 1;     // Enable non-overlap mode i.e.
                                                // conversion and future
                                                // sampling events don't overlap
    Adc1Regs.ADCCTL1.bit.INTPULSEPOS   = 1;     // ADCINT1 trips after AdcResults latch

    Adc1Regs.INTSEL1N2.bit.INT1E       = 1;     // Enabled ADCINT1
    Adc1Regs.INTSEL1N2.bit.INT1CONT    = 0;     // Disable ADCINT1 Continuous mode

    Adc1Regs.INTSEL1N2.bit.INT1SEL     = 0;     // setup EOC0 to trigger ADCINT1 to fire

    Adc1Regs.ADCSOC0CTL.bit.CHSEL      = 2;     // set SOC0 channel select to ADC1A2 ### Current SS

    Adc1Regs.ADCSOC1CTL.bit.CHSEL      = 3;     // set SOC1 channel select to ADC1A3 ### Fault Current

    AnalogSysctrlRegs.TRIG1SEL.all     = 5;     // Assigning EPWM1SOCA to ADC TRIGGER 1 of the ADC module
    Adc1Regs.ADCSOC0CTL.bit.TRIGSEL    = 5;     // Set SOC0 start trigger to ADC Trigger 1(EPWM1 SOCA) of the adc
    Adc1Regs.ADCSOC1CTL.bit.TRIGSEL    = 5;     // set SOC1 start trigger to ADC Trigger 1(EPWM1 SOCA) of the adc

    Adc1Regs.ADCSOC0CTL.bit.ACQPS      = 6;     // set SOC0 S/H Window to 7 ADC Clock Cycles, (6 ACQPS + 1)

    Adc1Regs.ADCSOC1CTL.bit.ACQPS      = 6;     // set SOC1 S/H Window to 7 ADC Clock Cycles, (6 ACQPS + 1)

    EDIS;

    //// Assumes ePWM1 clock is already enabled in InitSysCtrl();

    //Set event triggers (SOCA) for ADC SOC1
    EPwm1Regs.ETSEL.bit.SOCAEN         = 1;      // Enable SOC on A group
    EPwm1Regs.ETSEL.bit.SOCASEL = ET_CTRU_CMPA;  // Select SOC from CMPA on
                                                     // upcount
    EPwm1Regs.ETPS.bit.SOCAPRD         = 3;      // Generate pulse on every 3rd
                                                     // event
    // Time-base registers
    EPwm1Regs.TBPRD = PERIOD;                   // Set timer period, PWM
                                                // frequency = 1 / period
    EPwm1Regs.TBPHS.all = 0;                    // Time-Base Phase Register
    EPwm1Regs.TBCTR = 0;                        // Time-Base Counter Register
    EPwm1Regs.TBCTL.bit.PRDLD = TB_IMMEDIATE;   // Set Immediate load
    EPwm1Regs.TBCTL.bit.CTRMODE = TB_COUNT_UP;  // Count-up mode: used for
                                                // asymmetric PWM
    EPwm1Regs.TBCTL.bit.PHSEN = TB_DISABLE;     // Disable phase loading
    EPwm1Regs.TBCTL.bit.SYNCOSEL = TB_SYNC_DISABLE;
    EPwm1Regs.TBCTL.bit.HSPCLKDIV = TB_DIV1;
    EPwm1Regs.TBCTL.bit.CLKDIV = TB_DIV1;

    // Setup shadow register load on ZERO

    EPwm1Regs.CMPCTL.bit.SHDWAMODE = CC_SHADOW;
    EPwm1Regs.CMPCTL.bit.SHDWBMODE = CC_SHADOW;
    EPwm1Regs.CMPCTL.bit.LOADAMODE = CC_CTR_ZERO; // load on CTR=Zero
    EPwm1Regs.CMPCTL.bit.LOADBMODE = CC_CTR_ZERO; // load on CTR=Zero

    // Set Compare values
    EPwm1Regs.CMPA.half.CMPA = DUTY_CYCLE_A;     // Set duty 50% initially
    EPwm1Regs.CMPB = DUTY_CYCLE_B;               // Set duty 50% initially

    // Set actions
    EPwm1Regs.AQCTLA.bit.ZRO = AQ_SET;           // Set PWM2A on Zero
    EPwm1Regs.AQCTLA.bit.CAU = AQ_CLEAR;         // Clear PWM2A on event A, up
                                                 // count

    EPwm1Regs.AQCTLB.bit.ZRO = AQ_CLEAR;         // Set PWM2B on Zero
    EPwm1Regs.AQCTLB.bit.CBU = AQ_SET;           // Clear PWM2B on event B, up
                                                 // count

// Step 6. IDLE loop. Just sit and loop forever (optional):
    for(;;) ;
}               // end of main

__interrupt void  adc1_isr(void)
{
    // TM I believe these occur after ADC conversions have taken place

    GpioG1DataRegs.GPASET.bit.GPIO29 = 1; // LED on as we enter the ADC ISR

    myVoltsArray[myIndex] = Adc1Result.ADCRESULT0; // TM set Volts array element from ADC result 0
    myAmpsArray[myIndex] = Adc1Result.ADCRESULT1;  // TM temp set Amps array element from ADC result 1
    myIndex++;
    if (myIndex > 10) { // TM, 10 was 12607, made smaller for testing
        myIndex = 0;
    }
    // If 10 conversions have been logged, start over
    if(ConversionCount == 9)
    {
        ConversionCount = 0;
    }
    else ConversionCount++;

    GpioG1DataRegs.GPACLEAR.bit.GPIO29 = 1; // LED off as we exit the ADC ISR

    Adc1Regs.ADCINTFLGCLR.bit.ADCINT1 = 1;  //Clear ADCINT1 flag reinitialize
                                            // for next SOC
    PieCtrlRegs.PIEACK.all = PIEACK_GROUP1; // Acknowledge interrupt to PIE

    return;
}

I want to perform simultaneous conversions on ADC1INA2 and ADC2INA2 21,600 times per second and then grab those results and store them in my circular array buffer.  I've been reading about simultaneous sampling mode in the reference manual (spruh22h.pdf) but it's not clear to me how the results come back - I think I can just set ADC1INA2 and ADC2INA2 to be triggered by the ePWM signal but do I need 2 ISRs to grab the results?

This is further complicated by the requirement that, if the ADC1INA2 value maxes out, I need to substitute a reading from ADC1INA3 (which is reading the same source at a ten times less sensitive scale) so what I really want is to change the source of the ADC1INAx value if the reading goes above (and then  below) a set value).  Another way to solve this might be to always take the ADC1INA2 and ADC2INA2 readings and then immediately follow up with an ADC1INA3 reading (which will come < 1uS later but that's OK); in this approach, I would test the ADC1INA2 value and if it were maxed out, I'd simply use the ADC1INA3 value divided by ten and stored in my results array. 

Note that I am taking 12 bit results and storing them in 16 bit registers so if I'm storing the second result x10, it will still fit into the 16 bit results array - in this way I capture values for an overcurrent.

  • Whoops, posted too early, the question I forgot to ask is what is the best/recommended way to achieve my requirement?

    Thanks in advance.
  • Ted,

    I believe your method of tying these to the same trigger source is the correct one.  In fact you could also tie ADCIN1A3 to this same trigger source; under the default round robin scheme ADCIN1A2 will always sample then ADCIN1A3 will follow.  Only exception would be if you sample ADCIN1A2 on its own, which would change the state of the round robin pointer, but from above I don't think you would do this.

    As for the ISRs, since the analog subsystem is derived from the same clock, and you are triggering ADC1 and ADC2 from the same PWM trigger, you can rely on either of the end of conversions to tell you that both are complete.  In fact, if you wanted to set the ISR off of ADCIN1A3 you could read all 3 results(ADCIN1A2, ADCIN2A2, ADCIN1A3) and know they are the most current.  If you can't absorb the sampling time of ADCIN1A3 in your control loop, you can trigger off either A2's, and depending on your ISR code ADCIN1A3 may be complete at some point, without the need for another ISR(which eats some cycles since it is effectively a discontinuity to any code running).

    Finally, even thought it does not apply here, I'd like to mention the simultaneous mode bit in each of the ADCs for the sake of completeness.  This mode would simultaneously sample the Ax and Bx complement in a given ADC, with the only penalty being the conversion of the samples in sequential.  Your method, using different ADC modules, is faster as it gives you both results in a shorter time period overall, no need to use this bit or feature in this case.

    Best regards,

    Matt

  • Matt,

    Thanks for the advice. My code above is taken from the soc_adc example so there are some areas that I'm still not clear on; a few questions if I may...

    I think you're saying that I can have 3 SOCs, SOC0, SOC1, and SOC2 for my 3 values, one on ADC1 and the other 2 on ADC2, and then have them all set up to trigger from ePWM but then have ONLY SOC2 be the one that triggers the ISR and then all 3 results will be the latest and valid because they will be done in SOC# order, is that right?

    I'm presently only trying to measure ADC1A2 and ADC1A3, they use separate SOCs (0+1) but it looks like only SOC1 is set to be triggered by this code

        //Set event triggers (SOCA) for ADC SOC1
        EPwm1Regs.ETSEL.bit.SOCAEN         = 1;      // Enable SOC on A group
        EPwm1Regs.ETSEL.bit.SOCASEL = ET_CTRU_CMPA;  // Select SOC from CMPA on
                                                         // upcount

    Is that correct? and if so, don't I also need to trigger SOC0?  If this is done by the round-robin thing then please explain.

    Does the code as written above perform a round-robin conversion and, if yes, which statements do that and will the result registers Adc1Result.ADCRESULT0 and Adc1Result.ADCRESULT1 always relate to the SOC numbers in the way it's configured?

    When I look at the LED drive on GPIO29, I see a 750 nS-wide pulse, 21,600 times per second.  Right at the top of my code, I specify

    #define C28_FREQ       150      //CPU frequency in MHz
    
    #define DUTY_CYCLE_A   1106
    #define DUTY_CYCLE_B   1106
    #define PERIOD         2212     // these seem to be x2 but the period of 2212 gives me 22.6 kHz sampling

    But I don't understand what units these are in, If they were uS then 44.24 uS would be the right period for 22,600 Hz so this is confusing me (or perhaps the ISR is being called twice or something)?  Can you clarify please?  (I'm pretty sure that the sister code for the m3 is set up to run the C28 at 150 MHz)

    Can you outline the extra steps I need to take to add in ADC2?

    Thanks in advance.

  • Ted,

    PWM:

    The numbers for the PWM period and duty should just be in counter values, so it will be based on ePWM clock rate.  If PWM was a 150MHz, we should see 67.8kHz(2212 * 1/150MHz).  Let's check the M3 setup code to make sure we are at C28x = 150MHz/ M3 = 75MHz.  There is alt max clock for M3 = 100MHz, but that makes the C28x = 100MHz(since they are derived from the same clock source).  With your numbers and another divide by 2 I get 50MHz which seems to match your output toggle.

    ADC:

    Below is the code modifications I would add/change to get the channels sampled/triggered and ISR as I described.  You can then add a read of ADC2 to your ISR like this:

    myOtherVoltArray[myIndex] = Adc2Result.ADCRESULT0

    Setup Code

    // Modified ADC code
    
    // ADC1
    
        EALLOW;
    
        Adc1Regs.ADCCTL2.bit.ADCNONOVERLAP = 1;     // Enable non-overlap mode i.e.
    
                                                    // conversion and future
    
                                                    // sampling events don't overlap
    
        Adc1Regs.ADCCTL1.bit.INTPULSEPOS   = 1;     // ADCINT1 trips after AdcResults latch
    
    
    
        Adc1Regs.INTSEL1N2.bit.INT1E       = 1;     // Enabled ADCINT1
    
        Adc1Regs.INTSEL1N2.bit.INT1CONT    = 0;     // Disable ADCINT1 Continuous mode
    
    
        //changed this to use EOC1, ADC1A3 to trigger the ISR
        Adc1Regs.INTSEL1N2.bit.INT1SEL     = 1;     // setup EOC1 to trigger ADCINT1 to fire
    
    
    
        Adc1Regs.ADCSOC0CTL.bit.CHSEL      = 2;     // set SOC0 channel select to ADC1A2 ### Current SS
    
    
    
        Adc1Regs.ADCSOC1CTL.bit.CHSEL      = 3;     // set SOC1 channel select to ADC1A3 ### Fault Current
    
    
    
        AnalogSysctrlRegs.TRIG1SEL.all     = 5;     // Assigning EPWM1SOCA to ADC TRIGGER 1 of the ADC module
    
        Adc1Regs.ADCSOC0CTL.bit.TRIGSEL    = 5;     // Set SOC0 start trigger to ADC Trigger 1(EPWM1 SOCA) of the adc
    
        Adc1Regs.ADCSOC1CTL.bit.TRIGSEL    = 5;     // set SOC1 start trigger to ADC Trigger 1(EPWM1 SOCA) of the adc
    
    
    
        Adc1Regs.ADCSOC0CTL.bit.ACQPS      = 6;     // set SOC0 S/H Window to 7 ADC Clock Cycles, (6 ACQPS + 1)
    
    
    
        Adc1Regs.ADCSOC1CTL.bit.ACQPS      = 6;     // set SOC1 S/H Window to 7 ADC Clock Cycles, (6 ACQPS + 1)
    
    
    	
    	// ADC2
      
    
        Adc2Regs.ADCCTL2.bit.ADCNONOVERLAP = 1;     // Enable non-overlap mode i.e.
    
                                                    // conversion and future
    
                                                    // sampling events don't overlap
    
        Adc2Regs.ADCCTL1.bit.INTPULSEPOS   = 1;     // ADCINT1 trips after AdcResults latch
    
    
        
      
    
        Adc2Regs.ADCSOC0CTL.bit.CHSEL      = 2;     // set SOC0 channel select to ADC2A2 
    
    
       
        Adc2Regs.ADCSOC0CTL.bit.TRIGSEL    = 5;     // Set SOC0 start trigger to ADC Trigger 1(EPWM1 SOCA) of the adc
    
        
    
    
    
        Adc2Regs.ADCSOC0CTL.bit.ACQPS      = 6;     // set SOC0 S/H Window to 7 ADC Clock Cycles, (6 ACQPS + 1)
    
    
    	
    
        EDIS;

  • Matt,

    Thanks so much for doing this for me.  I dodn't get the chance to try it today but I will before the week's done.  The setup code for my M3 is as follows:

    // TM user variables
    #pragma DATA_SECTION(myVoltsArray, ".vArray");
    unsigned short myVoltsArray[0x3140];  // defines an array of 12,608 x 16 bit unsigned integers
    
    #pragma DATA_SECTION(myAmpsArray, ".iArray");
    unsigned short myAmpsArray[0x3140];  // defines an array of 12,608 x 16 bit unsigned integers
    
    char strMssg[64];
    unsigned short myIndex;
    
    
    int
    main(void)
    {
    
        int i = 0;
    
        // Allow writes to protected registers.
        HWREG(SYSCTL_MWRALLOW) = 0xA5A5A5A5;
    
        // Sets up PLL, M3 running at 75MHz and C28 running at 150MHz
        SysCtlClockConfigSet(SYSCTL_USE_PLL | (SYSCTL_SPLLIMULT_M & 0xF) |
                             SYSCTL_SYSDIV_1 | SYSCTL_M3SSDIV_2 |
                             SYSCTL_XCLKDIV_4);
    
    // TM assign S0 thru S7 (all) of the shared ram for use by the c28 TM taken from RAM_Management ControlSuite example
    // Details of how c28 uses these memory sections is defined in the c28 linker file.(28M35H52C1_RAM_lnk.cmd)
    // TM needs #include "driverlib/ram.h"
        RAMMReqSharedMemAccess((S0_ACCESS | S1_ACCESS |  S2_ACCESS | S3_ACCESS | S4_ACCESS | S5_ACCESS | S6_ACCESS | S7_ACCESS), SX_C28MASTER);
    
    #ifdef _FLASH
    // Copy time critical code and Flash setup code to RAM
    // This includes the following functions:  InitFlash();
    // The  RamfuncsLoadStart, RamfuncsLoadSize, and RamfuncsRunStart
    // symbols are created by the linker. Refer to the device .cmd file.
        memcpy(&RamfuncsRunStart, &RamfuncsLoadStart, (size_t)&RamfuncsLoadSize);
    
    // Call Flash Initialization to setup flash waitstates
    // This function must reside in RAM
        FlashInit();
    #endif
    
    
        //Enable processor interrupts.
        IntMasterEnable();
    
        // Enable all GPIOs
        PinoutSet();    // TM this runs the pinout settings that correspond to the DSTR PCB
                        // settings are in set_pinout_f28m35x.c and set_pinout_f28m35x.h (modified by TM to match DSTR PCB)
    
        // Enable the UART peripherals
        SysCtlPeripheralEnable(SYSCTL_PERIPH_UART0);
        SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOA);
        SysCtlPeripheralEnable(LED_0_PERIPH); // LED0 is on PE5 extracted from blinky_dc_m3.c example
    
        // Configure the UART for 115,200, 8-N-1 operation.
        UARTConfigSetExpClk(UART0_BASE, SysCtlClockGet(SYSTEM_CLOCK_SPEED), 115200,
                            (UART_CONFIG_WLEN_8 | UART_CONFIG_STOP_ONE |
                             UART_CONFIG_PAR_NONE));
    
        // Enable the UART interrupt.
        IntRegister(INT_UART0, UARTIntHandler);
        IntEnable(INT_UART0);
        UARTIntEnable(UART0_BASE, UART_INT_RX | UART_INT_RT);

    I'll report back and let you know how I did.

    Ted

  • Matt,

    I used your code and now get conversions on ADC2A2 and ADC1A2 and my ISR is still called at 21,600 Hz.  What's not clear is what's happening regarding the adc1_isr; there's 2 lines, 1 for ADC1 and 1 for ADC2 that say that acd1_isr is triggered after ADC results latch...

        Adc1Regs.ADCCTL1.bit.INTPULSEPOS   = 1;     // ADCINT1 trips after AdcResults latch
        Adc2Regs.ADCCTL1.bit.INTPULSEPOS   = 1;     // ADCINT1 trips after AdcResults latch

    but ADC1 has two SOCs, 0 and 1, vs ADC2 which only has one, SOC 0 (is it correct that there are SOC0's for ADC1 and ADC2?) so the ADC1 conversions will take twice as long and cause 2 triggers while the ADC2 conversion will only happen once and cause 1 trigger? Or is the comment "ADCInt1 trips after AdcResults latch" misleading me because there's only 1 line that actually causes adc1_isr to trigger?

        Adc1Regs.INTSEL1N2.bit.INT1SEL     = 1;     // setup EOC1 to trigger ADCINT1 to fire

    I'd appreciate you clearing up my confusion.  I'll go ahead and mark your previous answer as having solved the issue, thanks again for your help.

    Ted