cancel
Showing results for 
Search instead for 
Did you mean: 

stm32f207 ADC+TIMER+DMA / Poor Peripheral Library Examples

adrian
Associate III
Posted on April 24, 2013 at 11:19

Hi,

I'm looking to sample a signal (well, 2 actually - quadrature analog signals) at a fixed sample rate, but I'm baffled by the lack of any example in the Standard Peripheral Library on how to achieve this.

I would have thought that this would be a pretty standard example, i.e sample x points of a signal at y kHz and store result in memory location z.

Can anyone from ST comment on why such a basic (and imo fundamental) example is missing from the Standard Peripheral Library?

Does anybody else have an basic example of how this is achieved?  There are various incomplete snippets of code if you search in this forum, but elsewhere on the internet I've been able to find zilch.

I suspect I'm going to have to go read the manual in depth to figure this lot out...oh joy!

Many thanks.

Adrian
34 REPLIES 34
Posted on April 24, 2013 at 19:46

You can't please all the people all the time, I'm not even using the ADC, so all those examples are irrelevant.

This is targeted for the F4 Discovery, but should be directly portable to the F2 with adjustments for the bus and CPU frequencies.

// STM32 ADC IQ Sample @ 200 KHz (PC.0, PC.1) STM32F4 Discovery - sourcer32@gmail.com
// Assumptions per system_stm32f4xx.c CPU @ 168 MHz, APB2 @ 168 MHz (/1), APB1 @ 42 MHz (/4)
#include ''stm32f4_discovery.h''
/**************************************************************************************/
void RCC_Configuration(void)
{
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC2, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
}
/**************************************************************************************/
void GPIO_Configuration(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/* ADC Channel 10 -> PC0
ADC Channel 11 -> PC1
*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;
GPIO_Init(GPIOC, &GPIO_InitStructure);
}
/**************************************************************************************/
void ADC_Configuration(void)
{
ADC_CommonInitTypeDef ADC_CommonInitStructure;
ADC_InitTypeDef ADC_InitStructure;
/* ADC Common Init */
ADC_CommonInitStructure.ADC_Mode = ADC_DualMode_RegSimult;
ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div2;
ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_1; // 2 half-words one by one, 1 then 2
ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles;
ADC_CommonInit(&ADC_CommonInitStructure);
ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
ADC_InitStructure.ADC_ScanConvMode = DISABLE; // 1 Channel
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; // Conversions Triggered
ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_Rising;
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T2_TRGO;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfConversion = 1;
ADC_Init(ADC1, &ADC_InitStructure);
ADC_Init(ADC2, &ADC_InitStructure); // Mirror on ADC2
/* ADC1 regular channel 10 configuration */
ADC_RegularChannelConfig(ADC1, ADC_Channel_10, 1, ADC_SampleTime_15Cycles); // PC0
/* ADC2 regular channel 11 configuration */
ADC_RegularChannelConfig(ADC2, ADC_Channel_11, 1, ADC_SampleTime_15Cycles); // PC1
/* Enable DMA request after last transfer (Multi-ADC mode) */
ADC_MultiModeDMARequestAfterLastTransferCmd(ENABLE);
/* Enable ADC1 */
ADC_Cmd(ADC1, ENABLE);
/* Enable ADC2 */
ADC_Cmd(ADC2, ENABLE);
}
/**************************************************************************************/
#define BUFFERSIZE 800 // I+Q 200KHz x2 HT/TC at 1KHz
__IO uint16_t ADCDualConvertedValues[BUFFERSIZE]; // Filled as pairs ADC1, ADC2
static void DMA_Configuration(void)
{
DMA_InitTypeDef DMA_InitStructure;
DMA_InitStructure.DMA_Channel = DMA_Channel_0;
DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)&ADCDualConvertedValues;
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)0x40012308; // CDR_ADDRESS; Packed ADC1, ADC2
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;
DMA_InitStructure.DMA_BufferSize = BUFFERSIZE; // Count of 16-bit words
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Enable;
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
DMA_Init(DMA2_Stream0, &DMA_InitStructure);
/* Enable DMA Stream Half / Transfer Complete interrupt */
DMA_ITConfig(DMA2_Stream0, DMA_IT_TC | DMA_IT_HT, ENABLE);
/* DMA2_Stream0 enable */
DMA_Cmd(DMA2_Stream0, ENABLE);
}
/**************************************************************************************/
void TIM2_Configuration(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
/* Time base configuration */
TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
TIM_TimeBaseStructure.TIM_Period = (84000000 / 200000) - 1; // 200 KHz, from 84 MHz TIM2CLK (ie APB1 = HCLK/4, TIM2CLK = HCLK/2)
TIM_TimeBaseStructure.TIM_Prescaler = 0;
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
/* TIM2 TRGO selection */
TIM_SelectOutputTrigger(TIM2, TIM_TRGOSource_Update); // ADC_ExternalTrigConv_T2_TRGO
/* TIM2 enable counter */
TIM_Cmd(TIM2, ENABLE);
}
/**************************************************************************************/
void NVIC_Configuration(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
/* Enable the DMA Stream IRQ Channel */
NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream0_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
/**************************************************************************************/
void DMA2_Stream0_IRQHandler(void) // Called at 1 KHz for 200 KHz sample rate, LED Toggles at 500 Hz
{
/* Test on DMA Stream Half Transfer interrupt */
if(DMA_GetITStatus(DMA2_Stream0, DMA_IT_HTIF0))
{
/* Clear DMA Stream Half Transfer interrupt pending bit */
DMA_ClearITPendingBit(DMA2_Stream0, DMA_IT_HTIF0);
/* Turn LED3 off: Half Transfer */
STM_EVAL_LEDOff(LED3);
// Add code here to process first half of buffer (ping)
}
/* Test on DMA Stream Transfer Complete interrupt */
if(DMA_GetITStatus(DMA2_Stream0, DMA_IT_TCIF0))
{
/* Clear DMA Stream Transfer Complete interrupt pending bit */
DMA_ClearITPendingBit(DMA2_Stream0, DMA_IT_TCIF0);
/* Turn LED3 on: End of Transfer */
STM_EVAL_LEDOn(LED3);
// Add code here to process second half of buffer (pong)
}
}
/**************************************************************************************/
int main(void)
{
RCC_Configuration();
GPIO_Configuration();
NVIC_Configuration();
TIM2_Configuration();
DMA_Configuration();
ADC_Configuration();
STM_EVAL_LEDInit(LED3); /* Configure LEDs to monitor program status */
STM_EVAL_LEDOn(LED3); /* Turn LED3 on, 500 Hz means it working */
/* Start ADC1 Software Conversion */
ADC_SoftwareStartConv(ADC1);
while(1); // Don't want to exit
}
/**************************************************************************************/
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t* file, uint32_t line)
{
/* User can add his own implementation to report the file name and line number,
ex: printf(''Wrong parameters value: file %s on line %d

'', file, line) */
/* Infinite loop */
while (1)
{
}
}
#endif
/**************************************************************************************/

Tips, buy me a coffee, or three.. PayPal Venmo Up vote any posts that you find helpful, it shows what's working..
adrian
Associate III
Posted on April 25, 2013 at 08:35

Thanks for your response/time clive, I shall take a look when I start work in a bit!

I find the examples in the peripheral library invaluable, they're where I marry up what I read in the manual to something that is known working, if my code is not working then I can use their code as a reference.  I just found it odd that all the ADC examples didn't have something as basic as ''sample this signal at this sample rate''.

Now, having thought about this overnight I believe maybe I was going about this wrong, is it even possible to do what I want?

I was hoping to sample a signal at an arbitrary rate, say 10Khz and obtain 256 samples from it via DMA.  Now, my findings yesterday with timers, dma and the ADC seem to imply that this isn't possible, the adc is triggered by whatever rate I have set in the timer and then the adc converts at the rate set by the various ADC parameters and data is transferred from the ADC to memory automatically - this is not the behaviour I need.  

My pseudo code:

while(1)

{

triggerSampler();

sleepTillEndOfSamples();

processSamples();

waitUntilTimeForNextSamples();

}

However, if I forego the timer and just use ADC+DMA and by careful use of the prescaler, time between samples, sample cycle time I can set the sample rate to something around where I want, I won't be able to hit every sample rate smack on, but I should be able to get close - and using DMA means that I'm not involved.

The other alternative is to use ADC+TIMER and set the timer going at the appropriate sample rate and trigger the ADC and fill the buffer manually from the sample interrupt, this is what I want to avoid (for various reasons, mainly current consumption ones).

I should point out at this is our first (of many) products using the stm32f207 (BGA) and thus far it's proved to be fantastic, I'm been working with our first revision prototype for about a week finding/fixing the hardware errors and bringing up the various sections.  The interrupt system is slightly odd, but luckily we don't have that many external interrupt sources, so we're currently moving a few signals about so that we can get everything working perfectly and spend as much time in deep sleep as possible, we've used every single pin on the device!

We're designing in the stm32 in various guises into new designs as we speak as well, so we've definitely been converted!  Which reminds me, I have another schematic to check this morning....

Adrian

adrian
Associate III
Posted on April 25, 2013 at 08:45

Humn...your code implies that you can do *exactly* what I want and it doesn't look a million miles away from what I want.

I will let you know how I get on, but once again, many thanks for your time - it's highly appreciated.

Adrian
adrian
Associate III
Posted on April 25, 2013 at 11:49

It works!

Modified the clocks and the analog input channels and the sampling rate (10Khz) and let it do it's magic and it gave me the answer I was expecting. 

Well, on one of the channels, the second channel is sampling but appears to be at a DC level, so that may be a pin configuration issue or a hardware issue.

But it works!

ST take note, this is very useful example of how to use DMA+TIMER+ADC.

It also allowed me to spot the problem with my code and why it didn't appear to be sampling at the correct rate, the ADC was in continuous mode rather than triggered.

Many thanks for your example code and help, this has proved invaluable.

Adrian
adrian
Associate III
Posted on October 11, 2013 at 14:33

Sorry to dig up and old thread, but it's my old one....

This code works perfectly for circular mode (many thanks for that), but what I'd like to do now is sample once and then process data, then some time after sample again, process etc....

If I set the DMA_Mode to normal instead of circular the DMA does indeed trigger the sampling once, but for the life of me I cannot figure out how/what I need to reset/rearm to get it to take another set of samples.

pseudo code:

while(1)

{

   triggerSamples();

   waitForSampleFinish();

   processSamples();

   sleepForSomeTime();

}

What exactly do I need to setup again to trigger a whole new set of samples?

I've tried setting up the DMA registers again and then software triggering the ADC but that doesn't appear to work, I've tried just triggering the ADC but that doesn't work.

Setting up the DMA registers again results in the transfer complete interrupt firing but it appears as though only one sample was taken and this is duplicated throughout the sample buffer, like the ADC wasn't converting?

Any help, ideas, pointers, code fragments would be greatly appreciated.

Adrian

denis
Associate II
Posted on October 18, 2013 at 16:36

is there reason i get hardware fault trying to read that buffer from application?

I have the same/similar setup, with adc collecting few samples (just 8 for testing), and it dma transfers it to a ram buffer.

Then i try to just read from app thread from that buffer, and get hard fault. wtf.

immediately on first array index, bam.   its just a damn read.

Posted on October 18, 2013 at 17:04

is there reason i get hardware fault trying to read that buffer from application?

 

Well with DMA you have to be very careful not to have the HW exceed the bounds of the buffer, and if the buffer is on the stack it can corrupt all sorts of things, as the DMA can outlive the life of the routine that set it up.

Hard Faults should be one of the easier problems to trace. It's a gross error, and the CPU stores context and points you at the offending instructions/registers.

Tips, buy me a coffee, or three.. PayPal Venmo Up vote any posts that you find helpful, it shows what's working..
Posted on October 22, 2013 at 14:24

Some different code there, looks to be loading a pointer to a pointer. The value in R1 at the end likely points to an illegal address, and faults.

On would need to dig into the register and memory content.

Look at the code in the routine surrounding the line you presented. If you have a listing/code file look at that too. Figure out which structure it is accessing, and how the content of it got corrupted or wrong.

r4 = table->foo->bar;
Tips, buy me a coffee, or three.. PayPal Venmo Up vote any posts that you find helpful, it shows what's working..
Posted on October 22, 2013 at 15:56

And the value in R1 at the fault is what?

I can't see any of the stuff, and can only work with the information you provide.

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