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.

TM4C ADC glitches

//##############################################################################
void setupADC(void)
{
    //ADC defaults to 1Msps sampling rate
    ROM_SysCtlPeripheralEnable (SYSCTL_PERIPH_ADC0);
    ROM_SysCtlPeripheralEnable (SYSCTL_PERIPH_ADC1);
    ROM_ADCReferenceSet(ADC0_BASE,ADC_REF_EXT_3V);
    ROM_ADCReferenceSet(ADC1_BASE,ADC_REF_EXT_3V);
    //Hardware averaging implementation (ADC, samples)
    //Hardware averaging implementation (ADC, samples)
    ROM_ADCHardwareOversampleConfigure (ADC0_BASE, 4); //can go up to 64
    ROM_ADCHardwareOversampleConfigure (ADC1_BASE, 4);
   // ROM_ADCHardwareOversampleConfigure(ADC0_BASE, 0); //0 disables oversampling, can go up to 64
   // ROM_ADCHardwareOversampleConfigure(ADC1_BASE, 0);

    //ADC, Sequencer, trigger, priority
    //setting priority in this order will make sure that sequencer restarts when sequencer 1 finishes
    ROM_ADCSequenceConfigure(ADC0_BASE, 0, ADC_TRIGGER_PROCESSOR, 0);//module, sequencer, interrupt, priority
    ROM_ADCSequenceConfigure(ADC0_BASE, 1, ADC_TRIGGER_PROCESSOR, 1);

    ROM_ADCSequenceConfigure(ADC1_BASE, 0, ADC_TRIGGER_PROCESSOR, 0);//module, sequencer, interrupt, priority
    ROM_ADCSequenceConfigure(ADC1_BASE, 1, ADC_TRIGGER_PROCESSOR, 1);

    //Module, sequencer, step, channel
	ROM_ADCSequenceStepConfigure(ADC0_BASE, 0, 0, ADC_CTL_CH0); //IMON_1
	ROM_ADCSequenceStepConfigure(ADC0_BASE, 0, 1, ADC_CTL_CH1); //VMON1_1
	ROM_ADCSequenceStepConfigure(ADC0_BASE, 0, 2, ADC_CTL_CH2); //VMON2_1
	ROM_ADCSequenceStepConfigure(ADC0_BASE, 0, 3, ADC_CTL_CH3); //TCASE_1
	ROM_ADCSequenceStepConfigure(ADC0_BASE, 0, 4, ADC_CTL_CH4); //IMON_2
	ROM_ADCSequenceStepConfigure(ADC0_BASE, 0, 5, ADC_CTL_CH5); //VMON1_2
	ROM_ADCSequenceStepConfigure(ADC0_BASE, 0, 6, ADC_CTL_CH6); //VMON2_2
	ROM_ADCSequenceStepConfigure(ADC0_BASE, 0, 7, ADC_CTL_CH7 | ADC_CTL_IE | ADC_CTL_END); //TCASE_2

	ROM_ADCSequenceStepConfigure(ADC0_BASE, 1, 0, ADC_CTL_CH19); //IMON_4
	ROM_ADCSequenceStepConfigure(ADC0_BASE, 1, 1, ADC_CTL_CH20); //VMON1_3
	ROM_ADCSequenceStepConfigure(ADC0_BASE, 1, 2, ADC_CTL_CH21); //IMON_3
	ROM_ADCSequenceStepConfigure(ADC0_BASE, 1, 3, ADC_CTL_TS | ADC_CTL_IE | ADC_CTL_END); //MCU Temp

	ROM_ADCSequenceStepConfigure(ADC1_BASE, 0, 0, ADC_CTL_CH8); //TCASE_3
	ROM_ADCSequenceStepConfigure(ADC1_BASE, 0, 1, ADC_CTL_CH9); //VMON2_3
	ROM_ADCSequenceStepConfigure(ADC1_BASE, 0, 2, ADC_CTL_CH10); //IMON2_1
	ROM_ADCSequenceStepConfigure(ADC1_BASE, 0, 3, ADC_CTL_CH11); //IMON2_2
	ROM_ADCSequenceStepConfigure(ADC1_BASE, 0, 4, ADC_CTL_CH12); //IMON2_3
	ROM_ADCSequenceStepConfigure(ADC1_BASE, 0, 5, ADC_CTL_CH13); //IMON2_4
	ROM_ADCSequenceStepConfigure(ADC1_BASE, 0, 6, ADC_CTL_CH14); //spare reading
	ROM_ADCSequenceStepConfigure(ADC1_BASE, 0, 7, ADC_CTL_CH15 | ADC_CTL_IE | ADC_CTL_END); //spare reading

	ROM_ADCSequenceStepConfigure(ADC1_BASE, 1, 0, ADC_CTL_CH16); //TCASE_4
	ROM_ADCSequenceStepConfigure(ADC1_BASE, 1, 1, ADC_CTL_CH17); //VMON2_4
	ROM_ADCSequenceStepConfigure(ADC1_BASE, 1, 2, ADC_CTL_CH18 | ADC_CTL_IE | ADC_CTL_END); //VMON1_4

	ROM_ADCSequenceEnable (ADC0_BASE, 0); //8 samples 0-7
	ROM_ADCSequenceEnable (ADC0_BASE, 1); //4 samples 19-21, MCU Temp
	ROM_ADCSequenceEnable (ADC1_BASE, 0); //8 samples 8-15
	ROM_ADCSequenceEnable (ADC1_BASE, 1); //3 samples 16-18
}

//##############################################################################
void readADC(void)
{
	struct Reg *reg = allBanks[gen];
	int count = 0;
	/*
	unsigned int timeout = getFutureTime(.004f);

    //clear ADC sample done flag on sequencer
    ROM_ADCIntClear(ADC0_BASE, 0);
    ROM_ADCIntClear (ADC0_BASE, 1);
    ROM_ADCIntClear(ADC1_BASE, 0);
    ROM_ADCIntClear (ADC1_BASE, 1);

    //ADC conversion trigger by sequencer
    ROM_ADCProcessorTrigger (ADC0_BASE, 0);
    ROM_ADCProcessorTrigger (ADC0_BASE, 1);
    ROM_ADCProcessorTrigger (ADC1_BASE, 0);
    ROM_ADCProcessorTrigger (ADC1_BASE, 1);

    */

	//Get the data from the sequencer FIFO and store into buffer
	//assume that ADCs read in same order as appear in register map
	ROM_ADCSequenceDataGet (ADC0_BASE, 0, ui32ADC0_sq0);
	for (count = 0; count < 8; count++)
	{
		//ADCs 0-7
		reg[count + ADC0_RAW].data.ui = ui32ADC0_sq0[count];
	}
	ROM_ADCSequenceDataGet (ADC0_BASE, 1, ui32ADC0_sq1);
	for (count = 0; count < 4; count++)
	{
		//ADCs 19-21, temp
		reg[count + ADC19_RAW].data.ui = ui32ADC0_sq1[count];
	}
	ROM_ADCSequenceDataGet (ADC1_BASE, 0, ui32ADC1_sq0);
	for (count = 0; count < 8; count++)
	{
		//ADCs 8-15
		reg[count + ADC8_RAW].data.ui = ui32ADC1_sq0[count];
	}
	ROM_ADCSequenceDataGet (ADC1_BASE, 1, ui32ADC1_sq1);
	for (count = 0; count < 3; count++)
	{
		//ADCs 16-18, temp
		reg[count + ADC16_RAW].data.ui = ui32ADC1_sq1[count];
	}

	dataCaptured = true;
}
//##############################################################################
void triggerADCSample(void)
{
	//clear ADC sample done flag on sequencer
	    ROM_ADCIntClear(ADC0_BASE, 0);
	    ROM_ADCIntClear (ADC0_BASE, 1);
	    ROM_ADCIntClear(ADC1_BASE, 0);
	    ROM_ADCIntClear (ADC1_BASE, 1);

	    //ADC conversion trigger by sequencer
	    ROM_ADCProcessorTrigger (ADC0_BASE, 0);
	    ROM_ADCProcessorTrigger (ADC0_BASE, 1);
	    ROM_ADCProcessorTrigger (ADC1_BASE, 0);
	    ROM_ADCProcessorTrigger (ADC1_BASE, 1);

	    //clear trigger flag and wait for ADC sampling to be captured
	    dataCaptured = false;
}

//declared in ...startup_ccs.c interrupt vector file, ~every 40us
//##############################################################################
void SSIADCIntHandler(void)
{
    unsigned long status;

    status = GPIOIntStatus(GPIO_PORTJ_BASE, true);
    //if interrupt on DRDY, this function must finish before DRDY asserted low again
    if (status & GPIO_INT_PIN_0)
    {
        //clear interrupt status
        GPIOIntClear(GPIO_PORTJ_BASE, GPIO_INT_PIN_0);

        //fixme if timing is too tough, internal just needs to be once in every 4 cycles
        //start internal ADC reading
        if(dataCaptured)
        	triggerADCSample();

		//negative buffer so that can eventually catch up if out of sync
		ROM_SSIDataGetNonBlocking(SSI0_BASE, &SSIADCDataRx[0]);

        //buffer out data to gather direct read of channel
	    	//Frame the transmit data with CS
		ROM_GPIOPinWrite(GPIO_PORTC_BASE, GPIO_PIN_5, 0);//CS low
		//buffer out dummy data
		ROM_SSIDataPutNonBlocking (SSI0_BASE, SSIADCDataTx[0]);
		ROM_SSIDataPutNonBlocking (SSI0_BASE, SSIADCDataTx[1]);
		getData = true;
		//ROM_GPIOPinWrite(GPIO_PORTC_BASE, GPIO_PIN_5, 0xFF);//CS high, do this in interrupt and then grab data
    }
}

//##############################################################################
/*
 * Control Loop
 */
//##############################################################################
int main(void)
{
	int i;
	//FIXME check for warmboot
	mainInit(); //setupADC(); called in here, no reason to post this code otherwise
	//control loop
	/*while(true)//TESTING
	{
		writePWMModule(tec0);
	}*/
	triggerADCSample();

	while(true)
	{
		ROM_GPIOPinWrite(GPIO_PORTC_BASE, GPIO_PIN_7, LED_STATUS_MASK);
		flash(true);
		//grab internal ADC data if ready, Fixme want to have completed by next external interrupt
	    if ((ROM_ADCIntStatus (ADC0_BASE, 1, false))
	    		&& (ROM_ADCIntStatus (ADC1_BASE, 1, false))
				&& (ROM_ADCIntStatus (ADC0_BASE, 0, false))
				&& (ROM_ADCIntStatus (ADC1_BASE, 0, false)))
	    {
	    	readADC();
	    }
		ROM_GPIOPinWrite(GPIO_PORTC_BASE, GPIO_PIN_7, 0x00);
		flash(false);
        }
}

I am trying to run through all the ADC channels on a TM4C1231H6PZI. I enable both ADC module 0 and 1 with sequencers 0 and 1 of each. I understand that the modules can run separately and I can trigger the sampling of another sequencer through a processor trigger. With sequencer 0 FIFO depth of 8 and sequencer 1 FIFO depth of 4 I should be able to read all 22 (+1 internal MCU temperature). I initialized the AIN pins using the pinMux utility as well as enable each ADC module. I am trying to run the sampling at the full 1MSPS rate and will trigger a new set of samples every 4us unless the conversions are not complete, then I wait for the next cycle in my control loop.  While running this code I see that I get large glitches every so often in my ADC readings. When parsing through the data, these glitches seem to happen most often on ADC 1 sequencer 0, but I saw the issue once on ADC 0 sequencer 0. I have looked at my AIN input signals on a scope and the glitches do not seem to be real.  I even tried swapping the input signals on ADC0, seq0 with ADC1,seq0 signals and the issue seemed to persist on ADC1, seq 0. In fact the raw data seems to suggest that the sequencer sometimes randomly samples a different sample in the sequence. The channel that is randomly sampled is not always the channel next to the expected channel, but it is always a channel from that is to be sampled in the same sequencer.  Also although this is not the main issue, I tried turning on HW averaging to lessen the impact of these rogue samples, but with averaging of 8 or more the ADC raw values stop updating even when the input signals are clearly different. The code snippets from my project are above:

MAP_SysCtlPeripheralEnable and MAP_GPIOPinTypeADC calls are done automatically in the pinMux generated c file

 

  • Hello Joseph

    The assumptions you have made are correct. With the ADC0+1 Sequencer 0+1, you can convert all 22 channels + 1 Temp Sense.

    When triggering the ADC it is important to note that there is a ADC Busy Status that must be checked before the next conversion is requested.

    Also what is the input signal conditioning circuit looking like and what is the deviation of the converted ADC from the expected ADC?

    Regards
    Amit
  • Amit,

    I thought using this code
    if ((ROM_ADCIntStatus (ADC0_BASE, 1, false))
    && (ROM_ADCIntStatus (ADC1_BASE, 1, false))
    && (ROM_ADCIntStatus (ADC0_BASE, 0, false))
    && (ROM_ADCIntStatus (ADC1_BASE, 0, false)))
    {
    readADC();
    }

    Was sufficient for waiting for the data. All of the ADC sequencers should have their interrupt set when they are done. If I capture the raw data data several times I get many consistent and stable readings around a value such as 1674 and then randomly I will see 294 in that channel's raw data. This 294 is the same number that another channel's raw data (in the same sequencer) is stable around. It is not always the channel next to it that the data looks to come from, nor is it always faulting to the same mismatched channel. This is just one example, the other faults are of the same type, but different raw readings.

    Regards,
    Joseph
  • Hello Joseph

    Can you please also show the code where the Processor Trigger is being done for ADC conversion and the ADC Interrupt being cleared?

    Another check to do is to see if the ADC is busy or not. The ADCBUSY bit is in the ADCTL register

    Regards
    Amit
  • The triggerADCSample() does the clear and the processor trigger. This function is called in SSIADCIntHandler() which is an external interrupt that occurs ~40us. I have confirmed that this interrupt is occurring.

    regards,
    Joseph
  • Hello Jospeh

    And when the issue occurs, there is not a double trigger? Always check if there is a underflow or overflow flag set before reading the data

    Regards
    Amit
  • Hi Amit,

    That's just (another) great Amit tip. Does that, "Always check if there is a underflow or overflow flag set before reading the data" guidance appear effectively w/in MCU manual, Peripheral Driver User Guide, and general, "How to survive w/TM4C booklet?"    (pending cb1 production)

    If not - it should!

  • cb,

    I have not seen the guidance relating to checking this behavior in any of the examples or manuals, but Tiva C Series TM4C123x ROM User's Guide does have functions for checking and clearing these bits. I will implement these, but if this is causing my issue, I find it odd that this could occur with the code as it is now. I can implement a clear and reread the data if I see one of these set, but that is only solving the symptom, not the root cause. Will post back with the results.

    Regards,
    Joseph
  • Hello Joseph,

    We have not seen such an issue with multiple channels yet on the TM4C123x class of device. But since you are facing one, the only way to go about is to debug with as many possible hooks to see what the issue could be.

    Regards
    Amit
  • @Joseph,

    Believe two areas of exploration will prove useful:

    • Does this (unwanted) effect appear across multiple boards?   Always - "single board anomalies" EAT time/effort/money - bad joint and/or overheating or ESD may cause - and we'll not know!
    • Is each/every ADC input (properly) treated?   No input may float!  Small value bypass cap should appear very close to each ADC pin - and your output impedance should (attempt) to match the ADC's input spec.

    It is well known that signal "cross-talk" and/or "bleeding" occurs - but that registers as "outside" your issue.   When that errant reading occurs - is it in fact a (valid) reading.   (have you read the input voltage on the offending channel via an alternate (reliable) means?  Do they match?)

    Few have reported your (exact) issue - leads one to believe "dreaded, single board anomaly" has struck - thus my suggestion of, "Never build just ONE board!"   Makes BOTH you and your hapless helpers KRAZY!!!

  • Cb,

    This does happen across multiple boards, I have confirmed. I have looked at the circuits and they look good as well. Flipping the signals to different modules does not change the behavior on ADC module 1. It is always the worse. Still yet to attempt the overflow, underflow detection.

    Regards,
    Joseph
  • Hi,

    You are trigger a conversion start this way:

    ROM_ADCProcessorTrigger (ADC0_BASE, 0);  

    ROM_ADCProcessorTrigger (ADC0_BASE, 1);

    which is not correct, since there is not any guarantee that the ADC will finish previous conversions before the second command.

    You can start conversions for different ADCs this way, not for two sequencers of the same converter. You need to change/modify the command to get all channels to be converted correctly - one sequencer at the time.

    Also, if you have some timing expectation for all conversions, take into account the hardware oversampling will lenghten the conversion time - that will be 16 us per channel instead of 1, due to oversampling by 2^4, as you configure that.

  • Hello Petrei

    That was suggested as well with the use of ADC is Busy check before triggering.... However we have not heard anything from the poster on the earlier proposed experiment.

    Regards
    Amit
  • On some configurations of the chips I have some of the input pins floating, but that should not affect the other signals. Basically I created the code to support multiple configurations with the same code, so all the ADC inputs are read, but not all of them are used in calculations. If I use the wrong channel in the code that is my fault, but I don't see how this would create such behavior as what I have seen.
  • Joseph Senay54 said:
    I have some of the input pins floating, but that should not affect the other signals.

    Really - is it (ever) a good idea to leave input (ADC) pins floating?  (this reporter thinks NOT!) 

    Small value (close in) bypass capacitors (to ground) and matching the ADC's input impedance prove a superior "technique" than allowing such pins to "float."

  • It's not necessarily true that floating channels won't affect others. Certainly not a good idea to rely on that being the case.

    Robert
  • Also a pulldown.

    Robert
  • I did not see this reply to my post as  I did not see that my thread continued onto two pages. This seemed to be my issue. I now use interrupts on the sequencers to trigger my next sequencer for the same ADC module. Now I can work on the timing constraints. At which point I may consider using the uDMA instead of having the readADC() function to move my data into my registers. Corrected code below:

    //main readADC() call changed to:
    if(ADC0Done && ADC1Done)
        readADC();
    
    //##############################################################################
    void triggerADCSample(void)
    {
    	//clear ADC sample done flag on sequencer, not necessary since already done
    	// inside of interrupt handler
    	/*
    	ROM_ADCIntClear(ADC0_BASE, 0);
    	ROM_ADCIntClear (ADC0_BASE, 1);
    	ROM_ADCIntClear(ADC1_BASE, 0);
    	ROM_ADCIntClear (ADC1_BASE, 1);
    	 */
    
    	//ADC conversion trigger by sequencer
    	ROM_ADCProcessorTrigger (ADC0_BASE, 0);
    	//ROM_ADCProcessorTrigger (ADC0_BASE, 1);
    	ROM_ADCProcessorTrigger (ADC1_BASE, 0);
    	//ROM_ADCProcessorTrigger (ADC1_BASE, 1);
    
    	//clear trigger flag and wait for ADC sampling to be captured
    	dataCaptured = false;
    }
    
    //##############################################################################
    void InterADCIntHandler(void)
    {
        //ADC module 0
    	if(ROM_ADCIntStatus (ADC0_BASE, 0, false))
    	{
    		ROM_ADCIntClear(ADC0_BASE, 0);
    		ROM_ADCProcessorTrigger (ADC0_BASE, 1);
    	}
    	else if(ROM_ADCIntStatus (ADC0_BASE, 1, false))
    	{
    		ROM_ADCIntClear(ADC0_BASE, 1);
    		ADC0Done = true;
    		//do this in processtrigger
    		//ROM_ADCProcessorTrigger (ADC0_BASE, 1);
    	}
    
    	//ADC module 1
    	if(ROM_ADCIntStatus (ADC1_BASE, 0, false))
    	{
    		ROM_ADCIntClear(ADC1_BASE, 0);
    		ROM_ADCProcessorTrigger (ADC1_BASE, 1);
    	}
    	else if(ROM_ADCIntStatus (ADC1_BASE, 1, false))
    	{
    		ROM_ADCIntClear(ADC1_BASE, 1);
    		ADC1Done = true;
    		//do this in processtrigger
    		//ROM_ADCProcessorTrigger (ADC0_BASE, 1);
    	}
    }
    

  • Amit,

    I did check the status in my code, although triggering one sequencer right after the other hid any symptom the busy bit may have shown. I saw them all assert and then they all cleared as my original code expected and waited for. I had not known that one sequencer right after the other was invalid at that point. The fix I provided works although I could have also chosen not to do an interrupt and just poll the busy signal for each individually before starting another sequencer in the main loop.

    Regards,
    Joseph