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.

TMS320F28027F: Simple ADC program?

Part Number: TMS320F28027F
Other Parts Discussed in Thread: C2000WARE

Does anyone have a very simple 'getting started' program for using the ADC with the TMS320F28027F?

The example in 2000Ware (Example_2802xAdcSoc with f2802x_adc.c) works but is very complicated - using interrupts for SOC, averaging  etc. - so it is difficult to see what is essential.

I just need a simple program that sets up the ADC in free-running mode, monitors the voltage on one input and places the value in a register. It will just use a wait-loop for EOC from the ADC.

Many thanks

Roger

  • Hi Roger,

    I've modified the C2000Ware example (Example_2802xAdcSoc) to strip away the interrupts and have a simpler trigger scheme. 

    Please let me know if you have any questions.

    //
    // Included Files
    //
    #include "DSP28x_Project.h"     // Device Headerfile and Examples Include File
    
    //
    // Function Prototypes
    //
    void Adc_Config(void);
    
    //
    // Globals
    //
    uint16_t Voltage1;
    
    //
    // Main
    //
    void main(void)
    {
        //
        // WARNING: Always ensure you call memcpy before running any functions from
        // RAM InitSysCtrl includes a call to a RAM based function and without a 
        // call to memcpy first, the processor will go "into the weeds"
        //
    #ifdef _FLASH
        memcpy(&RamfuncsRunStart, &RamfuncsLoadStart, (size_t)&RamfuncsLoadSize);
    #endif
    
        //
        // Step 1. Initialize System Control:
        // PLL, WatchDog, enable Peripheral Clocks
        // This example function is found in the f2802x_SysCtrl.c file.
        //
        InitSysCtrl();
    
        //
        // Step 2. Initialize GPIO:
        // This example function is found in the f2802x_Gpio.c file and
        // illustrates how to set the GPIO to it's default state.
        //
        //InitGpio();  // Skipped for this example
    
        //
        // 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 f2802x_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 f2802x_DefaultIsr.c.
        // This function is found in f2802x_PieVect.c.
        //
        InitPieVectTable();
    
        //
        // Initialize all the Device Peripherals
        //
        InitAdc();  // For this example, init the ADC
        AdcOffsetSelfCal();
    
        //
        // Configure ADC
        // Note: Channel ADCINA4  will be double sampled to workaround the ADC 1st 
        // sample issue for rev0 silicon errata
        //
        EALLOW;
        
        // ADCINT1 trips after AdcResults latch
        AdcRegs.ADCCTL1.bit.INTPULSEPOS	= 1;
        
        // Enable ADCINT1
        AdcRegs.INTSEL1N2.bit.INT1E = 1;
    
        // Disable continuous sampling for ADCINT1
        AdcRegs.INTSEL1N2.bit.INT1CONT = 0;
    
        // setup EOC1 to trigger ADCINT1 to fire
        AdcRegs.INTSEL1N2.bit.INT1SEL	= 1;
        
        // set SOC0 channel select to ADCINA4
        AdcRegs.ADCSOC0CTL.bit.CHSEL 	= 4;
        
        // set SOC1 channel select to ADCINA4
        AdcRegs.ADCSOC1CTL.bit.CHSEL 	= 4;
        
        // set SOC0 start on software trigger
        AdcRegs.ADCSOC0CTL.bit.TRIGSEL 	= 0;
    
        // set SOC1 start on software trigger
        AdcRegs.ADCSOC1CTL.bit.TRIGSEL 	= 0;
        
        // set SOC0 S/H Window to 7 ADC Clock Cycles, (6 ACQPS plus 1)
        AdcRegs.ADCSOC0CTL.bit.ACQPS 	= 6;
        
        // set SOC1 S/H Window to 7 ADC Clock Cycles, (6 ACQPS plus 1)
        AdcRegs.ADCSOC1CTL.bit.ACQPS 	= 6;
        
        
        //--------- ADC Conversion -------------//
        
        // Software Trigger
        AdcRegs.ADCSOCFRC1.bit.SOC0 = 1;
    
        // Software Trigger
        AdcRegs.ADCSOCFRC1.bit.SOC1 = 1;
        
        // Wait for the conversion to be done
        while (AdcRegs.ADCINTFLG.bit.ADCINT1 == 0){}
    
        // Clear ADCINT1 flag
        AdcRegs.ADCINTFLGCLR.bit.ADCINT1 = 1;
        
        // Store the conversion result
        Voltage1 = AdcResult.ADCRESULT1;
    
        EDIS;
    }
    //
    // End of File
    //

    Best Regards,

    Marlyn

  • Hi Marlyn,

    Many thanks for a speedy reply! It's nice to know that "help is out there"!  I tried your program and it works fine (I put the last part 'Adc conversion' in a while(1) loop for continuous sampling - and by the way, this part does not need EALLOW). Just a couple of questions:

    (1) Is the double sampling (SOC0  & SOC1) necessary? Will it not halve the sampling rate? If it is only a problem with the very first sample, I  could probably live with that. It seems to work OK without the software trigger to SOC0.

    (2) The program is now much easier to understand. (I am used to the 38335, which uses sequencing for the ADC.) But your program still calls a lot of code in f2802x_adc.c and (for me) it is difficult to understand why all the code is necessary. Could a lot of it be 'thinned out' for this simple purpose?

    (3) In contrast to the 38335, there does not seem to be a ADC clock chain - only an option for dividing the SYSCLK by 2. So the ADC apparently just runs a full (or 1/2) conversion speed. Is that correctly understood?

    Regards

    Roger

  • Hi Roger,

    (1) Is the double sampling (SOC0  & SOC1) necessary? Will it not halve the sampling rate? If it is only a problem with the very first sample, I  could probably live with that. It seems to work OK without the software trigger to SOC0.

    It is only necessary for the first conversion within a sequence of conversions. If you enclosed the code for the conversions within a while (1) loop then just discard the very first SOC0 result (which the code already does) and then continue triggering only SOC1 and logging that result. Alternatively, you could remove the SOC0 trigger and conversion all together and discard the very first SOC1 conversion result. More information on this initial conversion can be found within the F2802x Errata under the (ADC: Initial Conversion) Advisory.

    (2) The program is now much easier to understand. (I am used to the 38335, which uses sequencing for the ADC.) But your program still calls a lot of code in f2802x_adc.c and (for me) it is difficult to understand why all the code is necessary. Could a lot of it be 'thinned out' for this simple purpose?

    I tried to thin it out as much as I could and only leave necessary code. Could you please give me an example of what lines of code are difficult to understand and I could help walk you through what its doing or why its needed.

    (3) In contrast to the 38335, there does not seem to be a ADC clock chain - only an option for dividing the SYSCLK by 2. So the ADC apparently just runs a full (or 1/2) conversion speed. Is that correctly understood?

    Yes, this device only has CLKDIV2EN (on Rev A). If you need other values you'll need to change SYSCLK but this will also result in a change to the overall device's operating frequency.

    Best Regards,

    Marlyn

  • Hej Marlyn,

    Many thanks for the quick reply.

    Re. (2): The  new file Example_ADC.c with main() is clear enough now, but there is a lot going on in the support file f2802x_adc.c which seems unnecessary (and confusing) - but it is called from main() (e.g. some averaging going on in  AdcConversion() which is called by AcdOffsetSelfCal() which is called in main()). I wonder if f2802x_adc.c could also be 'thinned out'.

    Regards

    Roger

  • Hi Roger,

    The 'AdcOffsetSelfCal()' function is called from main in order to re-calibrate the ADC zero offset error. The offset error of an ADC is the deviation between the measured and ideal reading, in terms of Least Significant Bits (LSBs), when a 0 volt (V) input is applied to any of the input pins. Usually offset error is adjusted for by correcting it through the OFFTRIM bits in the ADCOFFSETTRIM register. 

    What the AdcOffsetSelfCal function does is:

    • Sets the internal mux which connects VREFLO to B5 (This way 0V is observed on B5)
    • Applies an artificial offset (+80) to account for any negative offset there may be
    • Does conversions on B5 to find the average actual reading with a 0V input
    • Writes to the ADCOFFSETTRIM register the new offset value (80 - average conversion)
    • Sets B5 back to being a normal ADC input pin

    In order to properly calibrate the offset there needs to be a large enough sample size of conversions. AdcConversion() is specifically called in order to do these conversions. The function does 256 conversions. Within this function, the conversions are carried out by doing "pin-pong" sampling, just a technique to help speed up the conversions. Essentially, ADCINT1 is triggered by end of conversion 6, and initiates conversions on SOC8-15. ADCINT2 is triggered by EOC14 and triggers SOC0-7  (this is why they call it ping-pong sampling). All of these conversions are then summed up and averaged in order to get an accurate representation of what the ADC reads when 0V is applied. 

    These functions don't need to be changed. TI provides the code for these in order to help eliminate the ADC offset error. 

    Best Regards,

    Marlyn

  • Hej Marlyn,

    (Many thanks for your speedy reply! I read you reply with interest and have now got round to considering this subject again...).

    OK, I will just use the code in your Example_ADC.c, where it calls InitAdc() and AdcOffsetSelfCal() - and also does all the configuration in the EALLOW part.

    (1) It works fine with this loop in main():

    while(1)
      {
        // Software Trigger -  Works without (Roger)
        //AdcRegs.ADCSOCFRC1.bit.SOC0 = 1;

        // Software Trigger
        AdcRegs.ADCSOCFRC1.bit.SOC1 = 1;
        
        // Wait for the conversion to be done
        while (AdcRegs.ADCINTFLG.bit.ADCINT1 == 0){}

        // Clear ADCINT1 flag
        AdcRegs.ADCINTFLGCLR.bit.ADCINT1 = 1;
        
        // Store the conversion result
        Voltage1 = AdcResult.ADCRESULT1;

    // Do something with the result...

     }

    (2) If I just put (once):

    AdcRegs.ADCSOCFRC1.bit.SOC0 = 1;

    before the loop, and only use SOC1 in the loop, I assume the first value read  AdcResult.ADCRESULT1 in the loop will be correct (but it is not so important with the first value).

    (3) If I put both SOC0 and SOC1 triggers in the loop, will this not halve the sampling rate?

    (4) But I think I have misunderstood something about the relationship between SOC's and ADCRESULT: Why do you do SOC0 and SOC1 but only read AdcResult.ADCRESULT1?

    Regards

    Roger

  • Hi Roger,

    Thank you for following up on the thread. The reason why SOC0 and SOC1 are triggered is due to the Errata Notice we have on ADC Initial Conversions (ADC: Initial Conversion)  which states, "When the ADC conversions are initiated by any source of trigger in either sequential or simultaneous sampling mode, the first sample may not be the correct conversion result", as a workaround the following is suggested: "For sequential mode, discard the first sample at the beginning of every series of conversions. For instance, if the application calls for a given series of conversions, SOC0→SOC1→SOC2, to initiate periodically, then set up the series instead as SOC0→SOC1→SOC2→SOC3 and only use the last three conversions, ADCRESULT1, ADCRESULT2, ADCRESULT3, thereby discarding ADCRESULT0". 

    If you did some testing and ADCRESULT1 is correct while only calling AdcRegs.ADCSOCFRC1.bit.SOC0 = 1; once outside the loop then you should be able to implement it this way. The errata is for a series of conversations and the workaround is applied as a precaution in case the first conversion result is incorrect. 

    Best Regards,

    Marlyn

  • Hej Marlyn,

     Thanks for the continued support. I have a question about the conversion time.

    Using this code (based on yours):

    while(1){
                  // Software Trigger
                 AdcRegs.ADCSOCFRC1.bit.SOC1 = 1;

                 // Wait for the conversion to be done
                 while (AdcRegs.ADCINTFLG.bit.ADCINT1 == 0){}

                 // Clear ADCINT1 flag
                 AdcRegs.ADCINTFLGCLR.bit.ADCINT1 = 1;

                 // Store the conversion result
                 AD_Value=AdcResult.ADCRESULT1;
                 IO34_Toggle(); // Signal after an A/D conversion is done (for monitoring the period)
        
        } // End while

    I measure an IO toggle time of 2.1 microseconds. This seems much longer than the expected time in the data sheet sprui09-TMS320F2802x-Reference-Manual.pdf  Table 6-1 p. 406  , i.e. 333 ns (ACQPS is set to 6 in your code) – even allowing for about 130 ns for executing the IO toggle (which I have measured by only having the toggle in the loop). Is the 2.1 microseconds the expected conversion time using your code?

    Regards
    Roger

  • Hi Roger,

    I noticed that you have the GPIO toggle within a function call. Can you please toggle the GPIO directly from within the loop to get a closer estimate? If you are toggling the GPIO in a function call then you are also accounting for the time it takes to branch to that function and back.

    Also note that you are including the time it takes to clear the ADCINT1 flag, store the result, and trigger the start of the SOC. Something else you could try is setting a GPIO right after you initiate the software trigger and then clearing the GPIO after the conversion is done. If you account for the GPIO setting/clearing time then this should get you much closer to the actual sampling/conversion time.

    Best Regards,

    Marlyn