cancel
Showing results for 
Search instead for 
Did you mean: 

Help with simple ADC on STM32F4

willschneider
Associate II
Posted on July 17, 2013 at 16:46

Hi all,

I'm trying to get a simple ADC working on the stm32f4 discoveryboard. At the moment I simply want to obtain an input value(ConvertedValue) as a variable rather than saving to memory inDMA.

I'm struggling with the pins - some of them give me a littlefunctionality and others none at all. I cant get any port to workproperly. PIN0 will work to an extent - it will read a changingvalue, but it is also outputting a voltage of 2 volts which I've nottold it to - and so any readings it gets are somewhat distorted.

I'd really appreciate anyone taking a look at the code im using(its mostly taken from various sources around the web - not really myown)

Sorry for the poor/lack of formatting <code>

<
pre
><
font
size
=
''1''
>#include ''stm32f4xx_adc.h''
#include ''stm32f4xx_gpio.h''
#include ''stm32f4xx_rcc.h''
#include ''led.h''
#include ''stm32f4_discovery.h''
#include ''stm32f4xx_tim.h''
#include ''stm32f4xx_dac.h''
int ConvertedValue = 0; //Converted value read from ADC1
</
font
><
font
size
=
''1''
>
void adc_configure(){
ADC_InitTypeDef ADC_init_structure; //Structure for adc confguration
GPIO_InitTypeDef GPIO_initStructre; //Structure for analog input pin
//Clock configuration
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);//The ADC1 is connected the APB2 peripheral bus thus we will use its clock source
RCC_AHB1PeriphClockCmd(RCC_AHB1ENR_GPIOCEN,ENABLE);//Clock for the ADC port!! Do not forget about this one ;)
//Analog pin configuration
GPIO_StructInit(&GPIO_initStructre);
GPIO_initStructre.GPIO_Pin = GPIO_Pin_0;//The channel 10 is connected to PC0
GPIO_initStructre.GPIO_Mode = GPIO_Mode_AN; //The PC0 pin is configured in analog mode
GPIO_initStructre.GPIO_PuPd = GPIO_PuPd_NOPULL; //We don't need any pull up or pull down
GPIO_Init(GPIOE,&GPIO_initStructre);//Affecting the port with the initialization structure configuration
//ADC structure configuration
ADC_DeInit();
ADC_init_structure.ADC_DataAlign = ADC_DataAlign_Right;//data converted will be shifted to right
ADC_init_structure.ADC_Resolution = ADC_Resolution_12b;//Input voltage is converted into a 12bit number giving a maximum value of 4096
ADC_init_structure.ADC_ContinuousConvMode = ENABLE; //the conversion is continuous, the input data is converted more than once
ADC_init_structure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T1_CC1;// conversion is synchronous with TIM1 and CC1 (actually I'm not sure about this one :/)
ADC_init_structure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;//no trigger for conversion
ADC_init_structure.ADC_NbrOfConversion = 1;//I think this one is clear :p
ADC_init_structure.ADC_ScanConvMode = DISABLE;//The scan is configured in one channel
ADC_Init(ADC1,&ADC_init_structure);//Initialize ADC with the previous configuration
//Enable ADC conversion
ADC_Cmd(ADC1,ENABLE);
//Select the channel to be read from
ADC_RegularChannelConfig(ADC1,ADC_Channel_10,1,ADC_SampleTime_144Cycles);
}
int adc_convert(){
ADC_SoftwareStartConv(ADC1);//Start the conversion
while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC));//Processing the conversion
return ADC_GetConversionValue(ADC1); //Return the converted data
}
int main(void){
adc_configure();//Start configuration
//dac_configure();
while(1)
{//loop while the board is working
ConvertedValue = adc_convert();//Read the ADC converted value
//DAC_Ch1_WaveConfig();
}
}</
font
>
</
pre
>

</code>
38 REPLIES 38
Posted on July 17, 2013 at 17:00

You can paste in code with the Paintbrush [<>] icon, it doesn't look unreasonable.

Be aware that PC0 is used USB PowerOn, per the user manual for the STM32F4-Discovery, pick a pin that isn't used.

Suggest you use PC1 (ADC123_IN11 - Channel 11) or PC2 (ADC123_IN12 - Channel 12) neither of which are used on the board.

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..
willschneider
Associate II
Posted on July 17, 2013 at 18:04

Thanks for the reply. 

I'm away from my equipment until tomorrow, however I'm relatively sure that I tried those pins (among many others) to no avail. 

PC4, specifically, has frustrated me - with my daughter board I believe it is supposed to be a dedicated ADC pin. When there is no signal attached there are low noise readings as you might expect, but as soon as I send it a signal it tells me it is reading 00000000, and will not reset. Bit of a nightmare. Might there be something wrong with my code that is making things react so erratically? 

warcatz
Associate II
Posted on July 17, 2013 at 20:35

Hi 🙂

For Pin PC0,PC1,PC2 I do it like that :

uint16_t ADC_Val; //Stores the calculated ADC value
ADC_InitTypeDef ADC_InitStructure;
ADC_CommonInitTypeDef ADC_CommonInitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
void ADC_Configuration(void) 
{
/******************************************************************************/
/* ADCs interface clock, pin and DMA configuration */
/******************************************************************************/
/* Enable peripheral clocks */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_ADC2 | RCC_APB2Periph_ADC3 , ENABLE);
/* Configure ADC Channel 12 pin as analog input */ 
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;
GPIO_Init(GPIOC, &GPIO_InitStructure);
/* ADC Common configuration *************************************************/
ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent;
ADC_CommonInit(&ADC_CommonInitStructure);
/* ADC1 regular channel 10 to 15 configuration ************************************/
ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
ADC_InitStructure.ADC_ScanConvMode = DISABLE;
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_Init(ADC1, &ADC_InitStructure);
ADC_Init(ADC2, &ADC_InitStructure);
ADC_Init(ADC3, &ADC_InitStructure);
ADC_RegularChannelConfig(ADC1, ADC_Channel_10, 1, ADC_SampleTime_3Cycles);
ADC_RegularChannelConfig(ADC2, ADC_Channel_11, 1, ADC_SampleTime_3Cycles);
ADC_RegularChannelConfig(ADC3, ADC_Channel_12, 1, ADC_SampleTime_3Cycles);
/* Enable ADC1 to ADC3*/
ADC_Cmd(ADC1, ENABLE);
ADC_Cmd(ADC2, ENABLE);
ADC_Cmd(ADC3, ENABLE);
}//end ADC_Configuration
uint16_t Get_ADC_Value(uint8_t Channel)
{
switch(Channel)
{
case 10:
ADC_SoftwareStartConv(ADC1);
while(ADC_GetSoftwareStartConvStatus(ADC1) != RESET){ADC_Val = 0;}
ADC_Val = ADC_GetConversionValue(ADC1);
break;
case 11:
ADC_SoftwareStartConv(ADC2);
while(ADC_GetSoftwareStartConvStatus(ADC2) != RESET){ADC_Val = 0;}
ADC_Val = ADC_GetConversionValue(ADC2);
break;
case 12:
ADC_SoftwareStartConv(ADC3);
while(ADC_GetSoftwareStartConvStatus(ADC3) != RESET){ADC_Val = 0;}
ADC_Val = ADC_GetConversionValue(ADC3);
break;
default:
ADC_Val = 0;
break;
}
return ADC_Val;
}

Then in my main i do call it like this :

uint16_t adc_value[3];
adc_value[0] = Get_ADC_Value(10);
adc_value[1] = Get_ADC_Value(11);
adc_value[2] = Get_ADC_Value(12);

As you see ... easy and effective 🙂 I hope it can help you. Darth.
Posted on July 18, 2013 at 00:46

// STM32 ADC1 CH11 (PC.1) STM32F4 Discovery - sourcer32@gmail.com
#include ''stm32f4_discovery.h''
/**************************************************************************************/
void RCC_Configuration(void)
{
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
}
/**************************************************************************************/
void GPIO_Configuration(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/* ADC Channel 11 -> PC1 */
GPIO_InitStructure.GPIO_Pin = 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_Mode_Independent;
ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div2;
ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled;
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_None; // Manual
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);
/* ADC1 regular channel 11 configuration */
ADC_RegularChannelConfig(ADC1, ADC_Channel_11, 1, ADC_SampleTime_144Cycles); // PC1
/* Enable ADC1 */
ADC_Cmd(ADC1, ENABLE);
}
/**************************************************************************************/
#define BUFFERSIZE 128
uint16_t ADCConvertedValues[BUFFERSIZE];
int main(void)
{
int i;
RCC_Configuration();
GPIO_Configuration();
ADC_Configuration();
STM_EVAL_LEDInit(LED3); /* Configure LEDs to monitor program status */
STM_EVAL_LEDOn(LED3); /* Turn LED3 on */
i = 0;
while(1) // Don't want to exit
{
/* Start ADC1 Software Conversion */
ADC_SoftwareStartConv(ADC1);
while(ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);
ADCConvertedValues[i++] = ADC_GetConversionValue(ADC1);
i %= BUFFERSIZE;
/* Toggle LED3 */
STM_EVAL_LEDToggle(LED3);
}
}

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..
willschneider
Associate II
Posted on July 18, 2013 at 10:50

Wonderfull, thanks for the replies

I'm using clive1's code, and getting it to work. However, how can I change the sample rate? Or do I need to introduce a TIM function to allow for that?

Thanks again, Will

Posted on July 18, 2013 at 13:04

The example uses an ''On Demand'' model, you can change the pace at which you ask for conversions. You could ask for conversions to start in SysTick, and then catch the EOC interrupt when it completes. You can control how long the sample takes.

To hit hard sample rate numbers, I'd look to driving with a TIM unit.
Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..
willschneider
Associate II
Posted on July 18, 2013 at 14:25

I'll need to get a sampling rate of aroud 200KHz, so I think i'll need to be driving with TIM. Which means a bit of redesigning is in order 😉

EDIT: Also do you think i'll need to use DMA if i'm sampling that quick, or can I get away with just a buffer like in your example?

Posted on July 18, 2013 at 18:21

Yes 200 KHz is a bit higher than I'd probably want to interrupt.

Try this:

// STM32 ADC Sample @ 200 KHz (PC.1) STM32F4 Discovery - sourcer32@gmail.com
// Assumptions per system_stm32f4xx.c CPU @ 168 MHz, APB2 @ 84 MHz (/2), 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_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
}
/**************************************************************************************/
void GPIO_Configuration(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/* ADC Channel 11 -> PC1
*/
GPIO_InitStructure.GPIO_Pin = 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_Mode_Independent;
ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div2;
ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled;
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);
/* ADC1 regular channel 11 configuration */
ADC_RegularChannelConfig(ADC1, ADC_Channel_11, 1, ADC_SampleTime_15Cycles); // PC1
/* Enable DMA request after last transfer (Single-ADC mode) */
ADC_DMARequestAfterLastTransferCmd(ADC1, ENABLE);
/* Enable ADC1 DMA */
ADC_DMACmd(ADC1, ENABLE);
/* Enable ADC1 */
ADC_Cmd(ADC1, ENABLE);
}
/**************************************************************************************/
#define BUFFERSIZE 400 // 200KHz x2 HT/TC at 1KHz
__IO uint16_t ADCConvertedValues[BUFFERSIZE];
static void DMA_Configuration(void)
{
DMA_InitTypeDef DMA_InitStructure;
DMA_InitStructure.DMA_Channel = DMA_Channel_0;
DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)&ADCConvertedValues[0];
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;
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
}

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..
willschneider
Associate II
Posted on July 19, 2013 at 10:57

Thanks for this Clive, I am in your debt!

If, for example, I want to throw the values back out on a DAC, will i need to create new instances of TIM or can I use the same one as a trigger? I'm getting a bit confused trying to find which bit of the code links to timer to the ADC event. Or is NVIC the link between the two?

Cheers, Will