2009-03-26 06:19 PM
Problems with ADC Initialization
2011-05-17 04:07 AM
Hi Everyone,
I am using the STM32F103 in the 64-pin package. I have 3 external inputs to the ADC: 1) 24v through a voltage divider --> normal should read around 2.18v 2) 3v through a voltage divider --> normal should read around 2.06v 3) 4v through a voltage divider --> normal should read around 2v They are all adjusted via voltage divider to fit into the ADC range. Since it is a 64-pin part, Vref+ is Vdda which is 3.25v (measured). I am also sampling the internal temperature sensor and internal reference voltage. The ADC results are transferred continuously via DMA. The primary clock is the 64Mhz internal clock. What I am seeing is that if I reset the STM32 via software, it sometimes comes up and the ADC is in a state where one or more of the voltages/temperature are read incorrectly. I have verified that the ADC input at the STM32 pin is correct. When in this state, the only thing I can see is that the ADC results are just wrong. I have attempted to re-initialize the ADC whenever this occurs but it never gets out of that state until reset (sometimes). An example of the values I am seeing are: Voltage to sample: 23.8v (measured) After voltage divider: 2.18 (measured at MCU input pin) ADC result: 1517 counts Vdda: 3.25v (measured) I believe that each LSB is Vref/4096 == Vdda/4096 = .00081v So, 1517 counts * .00018 v/count = 1.22877v Scaled back up through the voltage divider that is 13.52v. Yikes. Here is my ADC Initialization routine which is called during hardware init: =========================================================================== void adc_controller_init(void) { ADC_InitTypeDef ADC_InitStructure; uint32_t exit_counter; ADC_DeInit(ADC1); ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; ADC_InitStructure.ADC_ScanConvMode = ENABLE; ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; ADC_InitStructure.ADC_NbrOfChannel = 5; ADC_Init(ADC1, &ADC_InitStructure); // ADC1 configuration (temp) ADC_RegularChannelConfig(ADC1, ADC_Channel_16, 1, ADC_SampleTime_239Cycles5); // ADC1 configuration (vref) ADC_RegularChannelConfig(ADC1, ADC_Channel_17, 2, ADC_SampleTime_239Cycles5); // ADC1 configuration (24v) ADC_RegularChannelConfig(ADC1, ADC_Channel_8, 3, ADC_SampleTime_239Cycles5); // ADC1 configuration (2.5v) ADC_RegularChannelConfig(ADC1, ADC_Channel_9, 4, ADC_SampleTime_239Cycles5); // ADC1 configuration (3.5v) ADC_RegularChannelConfig(ADC1, ADC_Channel_6, 5, ADC_SampleTime_239Cycles5); // Enable ADC1 DMA ADC_DMACmd(ADC1, ENABLE); // Enable ADC1 ADC_Cmd(ADC1, ENABLE); // wake up delay(1); // wait 10msec ADC_Cmd(ADC1, ENABLE); // 2nd enable required // Enable temperature sensor ADC_TempSensorVrefintCmd(ENABLE); // Enable ADC1 reset calibaration register ADC_ResetCalibration(ADC1); // Check the end of ADC1 reset calibration register exit_counter = DEFAULT_HW_WAIT_COUNT; while(ADC_GetResetCalibrationStatus(ADC1)) { if (--exit_counter == 0) { SET_HW_WAIT_FAIL(WAIT_FAIL_RESET_CAL_STATUS); DEBUG_TRACE(''TMO waiting for ADC reset cal status\n''); return; } } // Start ADC1 calibaration ADC_StartCalibration(ADC1); // Check the end of ADC1 calibration exit_counter = DEFAULT_HW_WAIT_COUNT; while(ADC_GetCalibrationStatus(ADC1)) { if (--exit_counter == 0) { SET_HW_WAIT_FAIL(WAIT_FAIL_RESET_CAL_STATUS); DEBUG_TRACE(''TMO waiting for ADC get cal status\n''); return; } } // Start ADC1 Software Conversion ADC_SoftwareStartConvCmd(ADC1, ENABLE); return; } ===================================================================== My DMA initialization routine looks like this: ====================================================================== void adc_dma_init(void) { DMA_InitTypeDef DMA_InitStructure; DMA_DeInit(DMA_Channel1); DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&(ADC1->DR); DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&adc_data; DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStructure.DMA_BufferSize = 5; 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_M2M = DMA_M2M_Disable; DMA_Init(DMA_Channel1, &DMA_InitStructure); /* Enable DMA channel1 */ DMA_Cmd(DMA_Channel1, ENABLE); return; } ========================================================================= If I cycle the power on the target and let it boot, the problem does not seem to be there but I am not 100% sure of that. Sorry for the huge amount of detail but I figure too much is better than not enough :) Any ideas? Victor2011-05-17 04:07 AM
Hi-
Suggest the following: a) disable the 24V signal so that it cannot ''beat'' your Vdd upon power up and upon reset. b) is it possible that your 24V voltage divider relies on the STM32 or other logic to, ''pull to gound?'' Similar to (a) above c) Should neither (a nor b) be the cause - temporarily cease the DMA and simply monitor the ADC by a more basic method Interesting - please keep us posted... [ This message was edited by: jj.sprague on 24-03-2009 23:55 ]2011-05-17 04:07 AM
Thanks for the ideas. My guess is that the state
of the analog input can be anything when the ADC is initialized so it should not matter if the 24v beats Vdda. The voltage divider is just a simple resistor divider on the input. An interesting thing I tried was to short the analog input to ground to see if the sampled value went to zero. It didn't BUT......the next channel did. I tried the same thing on the next input and it shifted down as well. Could it be that the DMA engine is getting funky with placing the results into the circular buffer in memory? The DMA configuration registers look the same between a working and non-working test run so maybe it's a DMA init problem. I think that your third test would have led to the same conclusion. Victor Victor2011-05-17 04:07 AM
Why the ''ADC_Cmd(ADC1, ENABLE); // 2nd enable required''? That will start a conversion. And you start the calibration just after, sounds like a possible race-condition. If the conversion finishes before you interfere with the calibration, the DMA might transfer that first sample and gets off-by-one when you later start the real sequence.
In any case, I'm pretty sure you don't need the 2nd enable. I've never seen or used one.2011-05-17 04:07 AM
Hi Andreas,
Thanks for the response. I read in the reference manual (pg 373) the following comments on the ADON bit: ------------------------------------------------------------------- This bit is set and cleared by software. If this bit holds a value of zero and a 1 is written to it then itwakes up the ADC from Power Down state.
Conversion starts when this bit holds a value of 1 and a 1 is written to it. The application should
allow a delay of tSTAB between power up and start of conversion. Refer to Figure 142.
0: Disable ADC conversion/calibration and go to power down mode.
1: Enable ADC and to start conversion
Note: If any other bit in this register apart from ADON is changed at the same time, then conversionThis bit is set and cleared by software. If this bit holds a value of zero and a 1 is written to it then it wakes up the ADC from Power Down state. Conversion starts when this bit holds a value of 1 and a 1 is written to it. The application should allow a delay of tSTAB between power up and start of conversion. Refer to Figure 142. 0: Disable ADC conversion/calibration and go to power down mode. 1: Enable ADC and to start conversion Note: If any other bit in this register apart from ADON is changed at the same time, then conversion------------------------------------------------------------------------ I was confused as to whether I needed this or not. It did not make a difference. Now, I did finally get it working. I narrowed down the issue to the DMA engine. It turns out that in some cases the DMA write pointer was incorrect and the data was being written to my memory buffer at the start address + 16 Bits. Very strange. I tried using the one-shot method of conversion for all 5 inputs together and that did not help either. I finally ended up, configuring for no DMA and 1 conversion channel. Periodically I configure each channel one at a time, covert, and finally read from the DR register. This works fine but it is an abomination. I also discovered that if you init the ADC without actually configuring any inputs, you cannot reset the calibration data. Very strange. Victor
2011-05-17 04:07 AM
Hi Victor,
did you try already the simple way to configure your DMA? RCC->AHBENR |= (1<DMA1_Channel1->CMAR = (unsigned long) adc.analog; DMA1_Channel1->CPAR = (unsigned long)&(ADC1->DR); DMA1_Channel1->CNDTR = 2; //DMA_BufferSize DMA1_Channel1->CCR = 0x000025A0; DMA1_Channel1->CCR |= (1 << 0); // enable DMA Channe1 I had similar problems, no it works fine. Greets K.