cancel
Showing results for 
Search instead for 
Did you mean: 

Fast external SPI ADC sampling approach.

Peter Morrow
Associate II

Hello,

MCU - STM32F746NGH.

FreeRTOS.

I'm trying to interface with 2 external single ended ADC (texas instruments ads8660 - http://www.ti.com/lit/ds/symlink/ads8860.pdf). The ADCs are connected via SPI2 and SPI3 respectively. The ADC can sample at a max rate of 1M samples per second.

My SPI buses are set to run at 25Mhz (the fastest speed for SPI2 and SPI3 on this MCU), I'm using a timer interrupt to enable the buses, data is clocked in in interrupt mode and my ISR accumulates samples till a specific oversampling limit is reached - then the results are averaged. After this an RTOS task which is blocking on a 1 message queue is signalled with a new sample value.

I'm finding things work well with my timer interrupt firing every 10 microseconds (SPI buses running at 25Mhz), i.e. 100K samples per second - however things start to break down at around 250K samples per second. It seems that when the timer interrupt frequency is increased RTOS tasks do not get switched in. The timer interrupt runs at priority 7, SPI interrupts run at priority 10. Essentially the MCU is busy handling the timer and 2 SPI bus interrupts, nothing else gets enough time.

I've thought about using DMA with interrupts but don't think this would help the situation, thus I am here looking for input. I've posted a snippet of the code so you can get an idea as to whats going on. I'm happy to post more code but it's just the usual boiler plate abstracted out around some APIs I've written.

Thanks,

Peter.

	/***************************************************************************
	Sample SPI bus interrupt handler.
	***************************************************************************/
	void
	SPI2_IRQHandler (void)
	{
		if (!lgSampleTx && (SPI2->SR & SPI_FLAG_RXNE))
		{
			SPI2->CR2 &= ~SPI_CR2_RXNEIE;
			lgSampleTx = true;
 
			if ((lgAdcRefValuesCollected == lgOversamplingFactor) &&
				(lgAdcSampleValuesCollected == lgOversamplingFactor))
			{
				uint32_t SampleAndRefValue;
 
				// Average the samples and signal the RTOS task that a value
				// is available.
				lgAdcRefValue = lgAdcRefAccumValue / lgOversamplingFactor;
				lgAdcRefValuesCollected = 0;
				lgAdcRefAccumValue = 0;
 
				lgAdcSampleValue = lgAdcSampleAccumValue / lgOversamplingFactor;
				lgAdcSampleValuesCollected = 0;
				lgAdcSampleAccumValue = 0;
 
				SampleAndRefValue = ((uint32_t) lgAdcSampleValue) | \
					(((uint32_t) lgAdcRefValue) << 16);
 
				osMessagePut(lgSampleAndRefAdcValueQ, SampleAndRefValue, 0);
			}
			else
			{
				if (lgAdcSampleValuesCollected < lgOversamplingFactor)
				{
					lgAdcSampleAccumValue += (uint16_t) SPI2->DR;
					lgAdcSampleValuesCollected++;
				}
			}
		}
		else if (lgSampleTx && (SPI2->SR & SPI_FLAG_TXE))
		{
			SPI2->DR = 0xABAB;
			SPI2->CR2 &= ~SPI_CR2_TXEIE;
			lgSampleTx = false;
		}
	}
	/**************************************************************************/
 
 
	/***************************************************************************
	Reference SPI bus interrupt handler.
	***************************************************************************/
	void
	SPI3_IRQHandler (void)
	{
		if (!lgRefTx && (SPI3->SR & SPI_FLAG_RXNE))
		{
			SPI3->CR2 &= ~SPI_CR2_RXNEIE;
			lgRefTx = true;
 
			if ((lgAdcRefValuesCollected == lgOversamplingFactor) &&
				(lgAdcSampleValuesCollected == lgOversamplingFactor))
			{
				// Average the samples and signal the RTOS task that a value
				// is available.
				uint32_t SampleAndRefValue;
 
				lgAdcRefValue = lgAdcRefAccumValue / lgOversamplingFactor;
				lgAdcRefValuesCollected = 0;
				lgAdcRefAccumValue = 0;
 
				lgAdcSampleValue = lgAdcSampleAccumValue / lgOversamplingFactor;
				lgAdcSampleValuesCollected = 0;
				lgAdcSampleAccumValue = 0;
 
				SampleAndRefValue = ((uint32_t) lgAdcSampleValue) | \
					(((uint32_t) lgAdcRefValue) << 16);
 
				osMessagePut(lgSampleAndRefAdcValueQ, SampleAndRefValue, 0);
			}
			else
			{
				if (lgAdcRefValuesCollected < lgOversamplingFactor)
				{
					lgAdcRefAccumValue += (uint16_t) SPI3->DR;
					lgAdcRefValuesCollected++;
				}
			}
		}
		else if (lgRefTx && (SPI3->SR & SPI_FLAG_TXE))
		{
			SPI3->DR = 0xABAB;
			SPI3->CR2 &= ~SPI_CR2_TXEIE;
			lgRefTx = false;
		}
	}
	/**************************************************************************/
 
 
	/***************************************************************************
	Description:	Timer event occurred interrupt.  When this interrupt fires
					we collect a single ADC sample for both ADCs on SPI2 and SPI3.
	***************************************************************************/
	void
	TIM1_UP_TIM10_IRQHandler (void)
	{
		if (TIM10->SR & TIM_FLAG_UPDATE)
		{
			TIM10->SR &= ~TIM_FLAG_UPDATE;
 
			// Generate a rising edge on ConvST whilst nCS is high.
			mac_GPIO_RESET(GPIO_PERIPHERAL_CONV_ST_PORT,
						   GPIO_PERIPHERAL_CONV_ST_PIN);
			mac_GPIO_SET(GPIO_PERIPHERAL_CONV_ST_PORT,
						 GPIO_PERIPHERAL_CONV_ST_PIN);
 
			SPI2->CR2 |= SPI_CR2_RXNEIE | SPI_CR2_TXEIE;
			SPI2->CR1 |= SPI_CR1_SPE;
 
			SPI3->CR2 |= SPI_CR2_RXNEIE | SPI_CR2_TXEIE;
			SPI3->CR1 |= SPI_CR1_SPE;
		}
	}
	/**************************************************************************/

4 REPLIES 4
T J
Lead

I would suggest the OS overhead will be your limitation.

If you really need to go fast, then do it by hand, stop using RTOS

10uS in an interrupt is tooo long.

I noticed you are printing within the interrupt, this will slow you down dramatically.

in your timer interrupt; these lines could be culled. just leave these bits set.

			SPI2->CR2 |= SPI_CR2_RXNEIE | SPI_CR2_TXEIE;
			SPI2->CR1 |= SPI_CR1_SPE;
 
			SPI3->CR2 |= SPI_CR2_RXNEIE | SPI_CR2_TXEIE;
			SPI3->CR1 |= SPI_CR1_SPE;

pin used to Generate a rising edge on ConvST could be an output compare pin, thereby removing the need for the interrupt totally.. ( possibly not, but you can work on it)

is there a specific target speed that you are after ? 250KHz ? is that good enough ?

do you need exact timing on the conversion ? or can timing jitter be tolerated ?

I fail to see how does this code fulfill the timing requirements of the ADC at all, an all respects. Do you have waveforms to show for us to chew on?

ISRs are expensive. You have only some 200 ticks per conversion, it would be a some advanced optimization if you could fit 5 ISRs within that timespan.

I'd use a timer to generate the required pulse for start of conversion on one channel, and setting other channel after the minimum conversion time elapsed it would write to SPI Tx through DMA (2 channels needed if the ADCs are not daisy-chained, not all timers can do that but they can be chained if needed); and of course received data would be stored using DMA, too.

Do you intend to run the conversions continuously or just bursts of them (to reach the "oversampling" ratio) with delays between for calculations? In the former case, just picking the DMA-stored data and calculating the average mightpresent a non-negligible load, which then has to be factored into the overall performance.

JW

Please don't do this

TIM10->SR &= -TIM_FLAG_UPDATE;

Do this, it has the same effect without the hazards

TIM10->SR = ~TIM_FLAG_UPDATE; // Why does the tilde look like a minus on this forum?

Tips, buy me a coffee, or three.. PayPal Venmo Up vote any posts that you find helpful, it shows what's working..
Peter Morrow
Associate II

Thanks all.

JW - Yes, I have some wave forms which I'll try to get uploaded when I get the time. Your suggestions sound like a good approach to me - I'm intending to continously be converting so running via DMA continously seems like a good idea.

Peter.