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.

ADC Running too fast when driven by PWM Generator 1

Other Parts Discussed in Thread: EK-TM4C1294XL

I'm having a very strange problem and I'm running out of leads.   I've built a burst measurement system using Ping-Pong DMA to pull averaged data out of ADC0 sampler #0.   I'm generating triggers with PWM0, Generator 1.     What I see is that the ADC system appears to be free-running and not actually triggered by the PWM Generator.    I am trying to produce 64x oversampled data at 1kHz.

Heres what I know so far:

  • I can drive an IO with the PWM block that triggers the ADC, and I can see that PWM0 Generator 1 is running at 64kHz.
  • I can see the DMA ISR toggle an IO, and I know that the DMA engine is doing the correct number of transfers.  The math is right.
  • I can speed up or slow down the transfer by adjusting the sample/hold settings.
  • The interrupt handler runs at uniform intervals (until the last transfer), the correct number of times - I have counters.
  • It doesn't trigger an under-run (ADCUSTAT is clear).

I'm either missing something subtle or something obvious.  I can't tell which.   I've scrutinized the registers in the PWM block and the ADC block several times.  I must be missing something.

Code for initializing the ADC and the PWM:

	SysCtlPeripheralReset(SYSCTL_PERIPH_ADC1);
	ADCClockConfigSet(ADC1_BASE, ADC_CLOCK_SRC_PLL | ADC_CLOCK_RATE_FULL, 30); // 16MHz
	ADCReferenceSet(ADC1_BASE,ADC_REF_EXT_3V);
	ADCHardwareOversampleConfigure(ADC1_BASE,16); // Over-ridden later.


	uDMAChannelAttributeEnable(UDMA_CHANNEL_ADC0 | UDMA_PRI_SELECT, UDMA_ATTR_HIGH_PRIORITY );
	uDMAChannelAttributeEnable(UDMA_CHANNEL_ADC0 | UDMA_ALT_SELECT, UDMA_ATTR_HIGH_PRIORITY );
	uDMAChannelControlSet( UDMA_CHANNEL_ADC0 | UDMA_PRI_SELECT,
		UDMA_SIZE_16 | UDMA_SRC_INC_NONE | UDMA_DST_INC_16 | UDMA_ARB_4);
	uDMAChannelControlSet( UDMA_CHANNEL_ADC0 | UDMA_ALT_SELECT,
        UDMA_SIZE_16 | UDMA_SRC_INC_NONE | UDMA_DST_INC_16 | UDMA_ARB_4);	

	ADCSequenceDMAEnable(ADC0_BASE,BURST_SEQUENCE);	
	ADCIntEnableEx(ADC0_BASE,ADC_INT_DMA_SS0);

	// ---------------------------------------------------
	// Configure the PWM System to generate the events
	// ---------------------------------------------------
	// Use the PWM module for event generation.
	PWMClockSet(PWM0_BASE,PWM_SYSCLK_DIV_1); // The math should come out at the full rate.
	PWMGenConfigure (PWM0_BASE, PWM_GEN_1, PWM_GEN_MODE_DOWN | PWM_GEN_MODE_NO_SYNC);
	PWMGenPeriodSet (PWM0_BASE, PWM_GEN_1, samplerate2divisor(1000));	
	PWMPulseWidthSet(PWM0_BASE, PWM_OUT_2, 200); // That should be a safe value.
	PWMOutputState  (PWM0_BASE, PWM_OUT_2_BIT, true);
	PWMGenEnable    (PWM0_BASE, PWM_GEN_1);

Prior to activating the ADC capture, I can see PWM0 generating the 64kHz sampling clock.

Heres the code that sets up the ADC Transfer (error checking code omitted).   I use tables & functions to configure the sampling rate, averaging, and S&H params:

void adc_burst_sample(unsigned samplecount, unsigned samplerate, tADCChannelSelection channel) {	
	PWMGenPeriodSet(PWM0_BASE, PWM_GEN_1, samplerate2divisor(samplerate));				
			
	adc_burst_state.samplerate = samplerate; // Record what happened.

	adc_burst_state.dma_nextp = (int16_t *) ADC_AREA;
	adc_burst_state.requested = 0; // Nothing is scheduled yet.
	adc_burst_state.completed = 0; // Nothing is done yet.
	adc_burst_buffer_reset(); 
	adc_burst_state.channel = channel; // Remember
	unsigned int channel_encoded = adc_channel2mask(channel);
	channel_encoded |= samplerate2SH(samplerate); // Set Sample&Hold Params
	
	ADCHardwareOversampleConfigure(ADC0_BASE,samplerate2average(samplerate)); 

ADCSequenceDisable(ADC0_BASE, BURST_SEQUENCE); ADCSequenceConfigure(ADC0_BASE, BURST_SEQUENCE, ADC_TRIGGER_PWM_MOD0|ADC_TRIGGER_PWM1, 0); ADCSequenceStepConfigure(ADC0_BASE, BURST_SEQUENCE, 0, channel_encoded|ADC_CTL_END); ADCSequenceEnable(ADC0_BASE,BURST_SEQUENCE); uDMAChannelTransferSet( UDMA_CHANNEL_ADC0 | UDMA_PRI_SELECT, UDMA_MODE_PINGPONG, (void*) (ADC0_BASE + ADC_O_SSFIFO0), adc_burst_state.dma_nextp, DMA_WORKUNIT); adc_burst_state.dma_nextp += DMA_WORKUNIT; adc_burst_state.requested += DMA_WORKUNIT; uDMAChannelTransferSet( UDMA_CHANNEL_ADC0 | UDMA_ALT_SELECT, UDMA_MODE_PINGPONG, (void*) (ADC0_BASE + ADC_O_SSFIFO0),adc_burst_state.dma_nextp, DMA_WORKUNIT); uDMAChannelEnable(UDMA_CHANNEL_ADC0); adc_burst_state.dma_nextp += DMA_WORKUNIT; adc_burst_state.requested += DMA_WORKUNIT; PWMGenIntTrigEnable(PWM0_BASE,PWM_GEN_1, PWM_TR_CNT_ZERO); adc_burst_state.duration_ms = 0; adc_burst_state.startstamp_ms = g_ulSystemTimeMS; }

And here's the handler:

void ADC0SS0Handler() {
	
	adc_burst_state.completed += DMA_WORKUNIT; // It might be less.
	adc_burst_state.requested -= DMA_WORKUNIT; // There are fewer in flight. 

	ADCIntClearEx(ADC0_BASE,ADC_INT_DMA_SS0); // Clear the Irq to prevent re-trigger out of the ISR.

	// Check to see if we're finished.
	if ( adc_burst_state.completed >= adc_burst_state.totalcount )  {
			adc_burst_sample_wrapup();
			return;
			}
	
	unsigned long ulMode1 = uDMAChannelModeGet(UDMA_CHANNEL_ADC0 | UDMA_PRI_SELECT);
	unsigned long ulMode2 = uDMAChannelModeGet(UDMA_CHANNEL_ADC0 | UDMA_ALT_SELECT);

	if ( (ulMode1 == UDMA_MODE_STOP ) && ( ulMode2 == UDMA_MODE_STOP ) ) {
		usprintf(trace_scribble,"!ADC Ur %x", adc_burst_state.dma_nextp);
		UARTprintf("%s\n",trace_scribble);
		return;
		}
	if ( (ulMode1 != UDMA_MODE_STOP ) && ( ulMode2 != UDMA_MODE_STOP ) ) {
		trace_add("!ADC Oddness"); // This should not happen.
		}
		
	// Count them - Remaining work is total - completed - in flight.
	int remaining = adc_burst_state.totalcount - \
					( adc_burst_state.requested + adc_burst_state.completed );
	
	// We have already done the checks - We know that at least one
	// channel is in the stopped state, but not both.
	if ( remaining ) {
		int workunit;
		if ( remaining >= DMA_WORKUNIT ) workunit = DMA_WORKUNIT;
		else workunit = remaining;
		
		if ((ulMode1 == UDMA_MODE_STOP ) && (ulMode2 != UDMA_MODE_STOP ) ) {
			uDMAChannelTransferSet(UDMA_CHANNEL_ADC0 | UDMA_PRI_SELECT, UDMA_MODE_PINGPONG,
			(void*) (ADC0_BASE + ADC_O_SSFIFO0), adc_burst_state.dma_nextp, workunit);
			count_adc0_prichannel++;
			}
		
		if ( (ulMode1 != UDMA_MODE_STOP ) && (ulMode2 == UDMA_MODE_STOP ) && remaining ) {
			uDMAChannelTransferSet(UDMA_CHANNEL_ADC0 | UDMA_ALT_SELECT, UDMA_MODE_PINGPONG,
			(void*) (ADC0_BASE + ADC_O_SSFIFO0), adc_burst_state.dma_nextp, workunit);	
			count_adc0_altchannel++;
			}
			
		adc_burst_state.dma_nextp += workunit;
		adc_burst_state.requested += workunit;
		}
 	
	}

Heres a a partial register dump of the PWM block during the operation - Note that the trigger is enabled (0x44):

4002:8000  0000:0000 0000:0000  0000:0004 0000:0000  ................

4002:8010  0000:0000 0000:0000  0000:0000 0000:0000  ................

4002:8020  0000:0000 0000:0000  0000:0000 0000:0000  ................

4002:8030  0000:0000 0000:0000  0000:0000 0000:0000  ................

4002:8080  0000:0001 0000:0100  0000:000B 0000:0000  ................

4002:8090  0000:0752 0000:0300  0000:068A 0000:0000  R...L...........

4002:80A0  0000:008C 0000:080C  0000:0000 0000:0000  ................

4002:80B0  0000:0000 0000:0000  0000:0000 0000:0000  ................

  • Oy. I forgot some key data.... The part in question is a rev2 TM4C129x. I've deployed thousands of these boards with great success. The hardware is known good.
  • Hello Robert

    The configuration seems to show a conflict. Part of the code references to ADC1 including setting the over sampler, but the configuration of the sequencer being done is for ADC0.

    Regards
    Amit
  • Sorry, wrong code excerpt.   ADC1 has a different job.   The burst DMA is used with ADC0:

    	SysCtlPeripheralReset(SYSCTL_PERIPH_ADC0); // Start by resetting the ADCs per errata.
    	ADCClockConfigSet(ADC0_BASE, ADC_CLOCK_SRC_PLL | ADC_CLOCK_RATE_FULL, 30);
    	ADCReferenceSet(ADC0_BASE,ADC_REF_EXT_3V);
    

  • Hello Robert,

    So, is the ADCHardwareOversampleConfigure(ADC1_BASE,16); also changed to ADCHardwareOversampleConfigure(ADC0_BASE,16); later?

    Can you please take a register dump of ADC0 and share the results?

    Regards
    Amit
  • I didn't add any special trickery to this, so there is an under-run caused by the dump itself.
    That under-run isn't there if I skip over the data register.


    4003:8000 0001:0101 0000:0000 0000:0100 0000:0000 ................
    4003:8010 0000:0000 0000:0007 0000:000F 0000:0000 ................
    4003:8020 0000:3210 0000:0000 0000:0000 0000:0000 .2..............
    4003:8030 0000:0006 0000:0000 0000:0001 0000:0000 ................
    4003:8040 0000:0000 0000:0002 0000:01E0 0000:0133 ........a..."...
    4003:8050 0000:0000 0000:0000 0000:0000 0000:0008 ................
    4003:8060 0000:0000 0000:0000 0000:0A4A 0000:0100 ........J.......
    4003:8070 0000:0000 0000:0000 0000:0000 0000:0000 ................
    4003:8080 0000:0000 0000:0000 0000:0808 0000:0100 ................
    4003:8090 0000:0000 0000:0000 0000:0000 0000:0000 ................
    4003:80A0 0000:0000 0000:0000 0000:0000 0000:0100 ................
    4003:80B0 0000:0000 0000:0000 0000:0000 0000:0000 ................
    4003:80C0 0000:0000 0000:0000 0000:0000 0000:0000 ................
    4003:80D0 0000:0000 0000:0000 0000:0000 0000:0000 ................
    4003:80E0 0000:0000 0000:0000 0000:0000 0000:0000 ................
    4003:80F0 0000:0000 0000:0000 0000:0000 0000:0000 ................
  • Also, yes. The burst init function calls ADCHardwareOversampleConfigure(). You can confirm that with the register dump.
  • Hello Robert,

    I may have to recreate the scenario on a EK-TM4C1294XL as the only discrepancy I see is in the call of ADCSequenceStepConfigure(ADC0_BASE, BURST_SEQUENCE, 0, channel_encoded|ADC_CTL_END); where normally an IE is also set.

    Regards
    Amit
  • Thanks Amit.

    I'll take a look at the docs again about the sequences and the IE. As I understand the docs, there is a specific bit for enabling the DMA-triggered interrupt. (ADC_INT_DMA_SS0)

    If you'll contact me directly via email using my userid info, I can share a complete code sample.
  • Also - The ADC clock control register (FC8) is correct - 1D0 = PLL / 30.
  • Hello Robert,

    Why is the UDMA_ARB_4 being used when only transfer has to be performed? Did you try changing it to UDMA_ARB_1 and also the IE bit setting as suggested yesterday?

    Regards
    Amit
  • The docs for the ADC say that the DMA engine will do a burst transfer when the FIFO is half full if you set it to ARB4. I'll try that first.

    I'll give the IE bit a try. I'm a bit concerned that I'll get one interrupt per ADC completed sample.
  • I changed the arbitration -

    	uDMAChannelControlSet( UDMA_CHANNEL_ADC0 | UDMA_PRI_SELECT,
    		UDMA_SIZE_16 | UDMA_SRC_INC_NONE | UDMA_DST_INC_16 | UDMA_ARB_1);
    	uDMAChannelControlSet( UDMA_CHANNEL_ADC0 | UDMA_ALT_SELECT,
            UDMA_SIZE_16 | UDMA_SRC_INC_NONE | UDMA_DST_INC_16 | UDMA_ARB_1);	
    

    No change.   Thats whet I'd expect.  Lots of single transfers, but the uDMA engine just knows what to do.

    Also, IE bit has no effect:

    	ADCSequenceDisable(ADC0_BASE, BURST_SEQUENCE);
        ADCSequenceConfigure(ADC0_BASE, BURST_SEQUENCE, ADC_TRIGGER_PWM_MOD0|ADC_TRIGGER_PWM1, 0);
        ADCSequenceStepConfigure(ADC0_BASE, BURST_SEQUENCE, 0, channel_encoded|ADC_CTL_END|ADC_CTL_IE); 
    	ADCSequenceEnable(ADC0_BASE,BURST_SEQUENCE); 
    
    
    Handler:
    
    	uint32_t irqs = ADCIntStatusEx(ADC0_BASE,true);
    
    	ADCIntClearEx(ADC0_BASE,irqs); // Clear the Irq to prevent re-trigger out of the ISR.
    

    I'm a bit surprised that this doesn't generate lots of IRQs.  I guess the DMA engine is intercepting them.

  • Hello Robert,

    I checked the configuration (from the register dump) and a test case and here are my observations

    1. The PWM generates a trigger every 64KHz or 15.625 us.
    2. The Hardware average logic is working "almost" fine, When a trigger comes in, it does the conversion of the channel in a repeated manner as given by the average value. So in this case it will perform 16 conversions and average the same before sending a DMA Request.

    The averaging logic does not work once per trigger but N times per trigger irrespective of the END bit setting.

    Regards
    Amit
  • Thats it! Thanks for the help!