Skip to main content
Explorer II
March 29, 2024
Solved

STM32 ADC DMA low raw/Voltage readings

  • March 29, 2024
  • 15 replies
  • 10203 views

Hello,
I am experiencing the following problem. When I setup an ADC with DMA for 5 channels, occasionally I get lower readings than the expected ones. It also happens in some of our hardware of our production, but others perform ok. We have inspected the components and everything seems identical. We have measured the reference voltage and all the expected voltages on ADC input pins (some are fixed and known) and all looking good too. On the other hand, when I sample only one ADC channel in polling mode, I do not get this problem, the voltages read as expected.
Here is the ADC configuration with DMA (source code attached too):

KoSt_0-1711711015173.pngKoSt_1-1711711038152.png

KoSt_2-1711711052740.png

KoSt_3-1711711167318.png

 

    This topic has been closed for replies.
    Best answer by waclawek.jan

    > ADCrefINT== 379

    Assuming that was taken as 10-bit measurement, after calibration (i.e. the ADC is already "true"). That then can be used to calculate the real VREF+ voltage:

    3.0V * (1668 / 4096) / (379 / 1024) = 3.301V

    JW

    15 replies

    Super User
    March 29, 2024

    > occasionally I get lower readings than the expected ones

    Try to elaborate.

    How is VREF+ connected?

    JW

    KoStAuthor
    Explorer II
    March 29, 2024

    The Vref+
    KoSt_0-1711712996067.pngKoSt_1-1711713036705.png

    Regarding the readings a healthy ADC reading at 10-bit resolution should read something like 914 raw value which will correspond to 3652mV for the battery voltage, as opposed to the lower reading which will be something like 837 raw value which corresponds to 3346mV for the battery voltage.

    Super User
    March 30, 2024

    And what does "occasionally" here mean, exactly?

    a) that on a particular board, most of the readings are 914 raw, and once in a time there's a 837 reading? If so, can you quantify "once in a time"? More precisely, you should try to relate it to some other event (e.g. activating a high-current peripheral).

    b) that on most boards all readings are 914 raw, but on a few boards all readings are 837? If so, you should try to find what is common to "failing" boards and different to "non-failing" boards; try to think out of the box, e.g. cable arrangement.

    JW

    KoStAuthor
    Explorer II
    March 30, 2024

    It is a little bit hard to quantify. We run some tests and we noticed that we get failures due to voltage and current measurements were lower than the pre set thresholds in the firmware. Those failures are happening occasionally.

    We have PCBs of the same batch in 3 countries, but only the ones tested in China are failing. At all times the ADC readings read lower than the expected values for all the ADC channels (I think this is were we need to focus, since it is a constant behaviour), but the PCBs at the other countries return the expected reading in all channels. All those PCBs are identical and they are driving small motors, have a keypad, have some LEDs UI, and a UART debugging port were we connect a USB to TTL UART cable. Maybe we can test this last connection if any power from the PC USB port is messing up anything?

    Besides those issues which make sense to point to a hardware difference, is the software used correctly in terms of reading the ADC?

    Why the polling method doesn't cause this issue? I tried to sample only one channel to see what am I getting but it makes the firmware crashing. I tried to use the same channel in all those 5 scannings, but same the firmware is crashing. Why running a single channel in DMA makes the firmware crashing?

    Super User
    March 31, 2024

    This is hard to judge without knowing the intimate details.

    JW

    KoStAuthor
    Explorer II
    March 31, 2024

    I have posted the source code, the cube mx configuration, the schematic, the test results. Am I missing something?

    Super User
    March 31, 2024

    The rest of code (including Cube, the compiler, particular settings of it), the rest of schematics, the hardware to which it is connected, PCB layout...

    I have had a glance at the code and can't see anything obvious. I also don't use Cube.

    Sometimes it happens that a problem can be discovered by having a short look at some snippet of code, but more often than not, it's ultimately up to the user to find the problem, we can perhaps give better of worse clues.

    Common denominator of many ADC-related problems is too high signal impedance/too short sampling period, crosstalk/interference (often related to improper grounding/return arrangement), unstable reference.

    JW

    Super User
    April 1, 2024

    You've said that the problem is with battery voltage measurement, yet you're showing schematics for measurement on a divider not related to battery.

    Read AN2834.

    JW

    KoStAuthor
    Explorer II
    April 1, 2024

    I said that the problem is with all the ADC channels and I used the battery voltage as an example to answer your question with some measurements. 

    Super User
    April 1, 2024

    So, are the *raw* readouts (i.e. the values in the buffer, unmodified by any of your functions) shifted against expectation? Are they *offset* by a constant, or are they *scaled* by a constant, or anything else?

    This might bring us to your "calibration" method. I don't really like it, there should be no need for that. If the precision of regulator generating voltage on VREF+ is not sufficient, you should calibrate it against the internal VREFINT reference.  Also, do you calibrate the ADC as outlined in the Calibration (ADCAL) subchapter of ADC chapter in Reference Manual?

    JW

     

    KoStAuthor
    Explorer II
    April 2, 2024

    1) The raw readouts are coming directly from the buffer used in DMA and I pass those by value to other functions for the conversions, hence I do not see any alteration in their contents.

    2) I had the impression that the G0 chip does not support the extended functions from ST library such calibration. I tried  the following sequence in my initialisation function and I do not get any ADC readings:

     

     

     HAL_ADC_Stop(&hadc1);
     HAL_ADCEx_Calibration_Start(&hadc1);
     //HAL_Delay(2000);
     while ((HAL_ADC_GetState(&hadc1) & HAL_ADC_STATE_READY) != 1) {}
     HAL_ADC_Start_DMA(&hadc1, (uint16_t*)ADCreadings, sizeof(ADCreadings)/sizeof(ADCreadings[V_BAT]));

     

    3) I was not aware of the Vrefint. In my case the reading returns raw 409 for 10-bit resolution. The power for the microcontroller is at 3V3, hence the voltage is 1.318V (is this expected?).

    4) Yes my calibration is a workaround, but since not having anything of the above working yet, it was the next thing I could think of adjusting the whole ADC on a known voltage such as the battery. Ideally I should not do that, but the hardware should report the readings correctly in range.

    Super User
    April 2, 2024

    > I tried the following sequence in my initialisation function and I do not get any ADC readings

    I don' use Cube/HAL but you should be able to use the calibration as outlined in RM; and you also should use it before taking ADC readings.

    > I do not get any ADC readings

    You should find out, why. You may want to read this thread.

    > Vrefint. In my case the reading returns raw 409 for 10-bit resolution. The power for the microcontroller is at 3V3, hence the voltage is 1.318V (is this expected?).

    Provided the timing prescribed by DS (startup, sampling) was used, the result is too high, should be nominally 1.212V, see Embedded internal voltage reference table in DS. Lack of calibration may explain this, too. 

    There's also a factory-measured value stored in the system memory, read it out and compare (adjusting for different VREF+ if needed, and adjusting for the 10-bit resolution), see Internal voltage reference chapter in DS.

    JW

    KoStAuthor
    Explorer II
    April 2, 2024

    I have got some good news:
    1) Adding delays in the generated HAL calibration function between enable and disable ADC stages as per thread you pointed, is fixing the calibration problem and the ADC is starting and it is working as normal after the calibration.

    /**
     * @brief Perform an ADC automatic self-calibration
     * Calibration prerequisite: ADC must be disabled (execute this
     * function before HAL_ADC_Start() or after HAL_ADC_Stop() ).
     * @note Calibration factor can be read after calibration, using function
     * HAL_ADC_GetValue() (value on 7 bits: from DR[6;0]).
     * @PAram hadc ADC handle
     * @retval HAL status
     */
    HAL_StatusTypeDef HAL_ADCEx_Calibration_Start(ADC_HandleTypeDef *hadc)
    {
     HAL_StatusTypeDef tmp_hal_status;
     __IO uint32_t wait_loop_index = 0UL;
     uint32_t backup_setting_cfgr1;
     uint32_t calibration_index;
     uint32_t calibration_factor_accumulated = 0;
     uint32_t tickstart;
    
     /* Check the parameters */
     assert_param(IS_ADC_ALL_INSTANCE(hadc->Instance));
    
     __HAL_LOCK(hadc);
    
     /* Calibration prerequisite: ADC must be disabled. */
    
     /* Disable the ADC (if not already disabled) */
     tmp_hal_status = ADC_Disable(hadc);
    
     // A minimum delay is required after enabling or disabling the ADC
     HAL_Delay(1);
    
     /* Check if ADC is effectively disabled */
     if (LL_ADC_IsEnabled(hadc->Instance) == 0UL)
     {
     /* Set ADC state */
     ADC_STATE_CLR_SET(hadc->State,
     HAL_ADC_STATE_REG_BUSY,
     HAL_ADC_STATE_BUSY_INTERNAL);
    
     /* Manage settings impacting calibration */
     /* - Disable ADC mode auto power-off */
     /* - Disable ADC DMA transfer request during calibration */
     /* Note: Specificity of this STM32 series: Calibration factor is */
     /* available in data register and also transferred by DMA. */
     /* To not insert ADC calibration factor among ADC conversion data */
     /* in array variable, DMA transfer must be disabled during */
     /* calibration. */
     backup_setting_cfgr1 = READ_BIT(hadc->Instance->CFGR1, ADC_CFGR1_DMAEN | ADC_CFGR1_DMACFG | ADC_CFGR1_AUTOFF);
     CLEAR_BIT(hadc->Instance->CFGR1, ADC_CFGR1_DMAEN | ADC_CFGR1_DMACFG | ADC_CFGR1_AUTOFF);
    
     /* ADC calibration procedure */
     /* Note: Perform an averaging of 8 calibrations for optimized accuracy */
     for (calibration_index = 0UL; calibration_index < 8UL; calibration_index++)
     {
     /* Start ADC calibration */
     LL_ADC_StartCalibration(hadc->Instance);
    
     /* Wait for calibration completion */
     while (LL_ADC_IsCalibrationOnGoing(hadc->Instance) != 0UL)
     {
     wait_loop_index++;
     if (wait_loop_index >= ADC_CALIBRATION_TIMEOUT)
     {
     /* Update ADC state machine to error */
     ADC_STATE_CLR_SET(hadc->State,
     HAL_ADC_STATE_BUSY_INTERNAL,
     HAL_ADC_STATE_ERROR_INTERNAL);
    
     __HAL_UNLOCK(hadc);
    
     return HAL_ERROR;
     }
     }
    
     calibration_factor_accumulated += LL_ADC_GetCalibrationFactor(hadc->Instance);
     }
     /* Compute average */
     calibration_factor_accumulated /= calibration_index;
    
     // A minimum delay is required after enabling or disabling the ADC
     HAL_Delay(1);
     
     /* Apply calibration factor */
     LL_ADC_Enable(hadc->Instance);
     // A minimum delay is required after enabling or disabling the ADC
     HAL_Delay(1);
     LL_ADC_SetCalibrationFactor(hadc->Instance, calibration_factor_accumulated);
     // A minimum delay is required after enabling or disabling the ADC
     HAL_Delay(1);
     LL_ADC_Disable(hadc->Instance);
     // A minimum delay is required after enabling or disabling the ADC
     HAL_Delay(1);
    
     /* Wait for ADC effectively disabled before changing configuration */
     /* Get tick count */
     tickstart = HAL_GetTick();
    
     while (LL_ADC_IsEnabled(hadc->Instance) != 0UL)
     {
     if ((HAL_GetTick() - tickstart) > ADC_DISABLE_TIMEOUT)
     {
     /* New check to avoid false timeout detection in case of preemption */
     if (LL_ADC_IsEnabled(hadc->Instance) != 0UL)
     {
     /* Update ADC state machine to error */
     SET_BIT(hadc->State, HAL_ADC_STATE_ERROR_INTERNAL);
    
     /* Set ADC error code to ADC peripheral internal error */
     SET_BIT(hadc->ErrorCode, HAL_ADC_ERROR_INTERNAL);
    
     return HAL_ERROR;
     }
     }
     }
    
     /* Restore configuration after calibration */
     SET_BIT(hadc->Instance->CFGR1, backup_setting_cfgr1);
    
     /* Set ADC state */
     ADC_STATE_CLR_SET(hadc->State,
     HAL_ADC_STATE_BUSY_INTERNAL,
     HAL_ADC_STATE_READY);
     }
     else
     {
     SET_BIT(hadc->State, HAL_ADC_STATE_ERROR_INTERNAL);
    
     /* Note: No need to update variable "tmp_hal_status" here: already set */
     /* to state "HAL_ERROR" by function disabling the ADC. */
     }
    
     __HAL_UNLOCK(hadc);
    
     return tmp_hal_status;
    }


    2) Having the calibration completed with a calibration factor result of 64, now the ADCrefint reads at 377 which converts to 1.215V which seems more reasonable value.

    3) The embedded internal voltage reference set during production reads as 1655, not sure what that means, and the comment in the source code I find the register address says:

    /* ADC internal channels related definitions */
    /* Internal voltage reference VrefInt */
    #define VREFINT_CAL_ADDR ((uint16_t*) (0x1FFF75AAUL)) /* Internal voltage reference,
     address of parameter VREFINT_CAL: VrefInt ADC raw data acquired at temperature 30 DegC (tolerance: +-5 DegC),
     Vref+ = 3.0 V (tolerance: +-10 mV). */



    Super User
    April 2, 2024

    > The embedded internal voltage reference set during production reads as 1655, not sure what that means

    That's the ADC reading which was taken from VREFINT in factory:

    1655 / 4096 * 3.0V = 1.212V

    The VREFINT reference has some manufacturing spread (cca -2.5%+1.5%, according to DS); this should give you a better individual value (10mV/3.0V, i.e. cca 0.33%), all these at room temperature.  The LDO you are using is nominally +-1% at room temperature, so using VREFINT instead of relying on LDO being precise enough won't give you that much, but there are also worse LDOs out there.

    JW

     

    KoStAuthor
    Explorer II
    April 2, 2024

    In other words I will not get much of a benefit by calibrating based on Vrefint manually, because the LDO has a higher error margin? Hence the ST HAL calibration function should be good enough in my case?

    We will test tomorrow and verify if we get any better readings in those devices in China. 

    Super User
    April 2, 2024

    > In other words I will not get much of a benefit by calibrating based on Vrefint manually, because the LDO has a higher error margin?

    I'm not sure we understand each other.

    If you're confident the LDO output is 3.3V precisely enough, forget about VREFINT.

    If you're not sure about  the exact LDO output voltage but you are sure it's stable, you can somewhat increase precision by using VREFINT.

    In your case, I'd start with ignoring VREFINT. I mentioned it just to get you to measure a "known fixed voltage" so that we know where may be the root of the problem. The ADC calibration as per RM is necessary to take out a manufacturing-dependent offset; that should also remove the need for your "extra" calibration.

    JW

    KoStAuthor
    Explorer II
    April 5, 2024

    Unfortunately the ST calibration does not solve the problem. The ADC problem has to do with the wrong gain as opposed to the wrong offset.
    The most interesting part is that now we experienced the problem in a device in another country than China. Hence it is probably not hardware dependent.

    Super User
    April 5, 2024

    Wrong gain may be caused by different-than-expected VREF+ voltage.

    What are the VREFINT readings for the problematic devices?

    JW