校准STM32 ADC(VREFINT)

时间:2019-10-10 17:35:50

标签: stm32 adc stm32f0

我正在尝试在STM32F042微控制器上读取VDDA。 VDD为3.29V时,我得到了意外的结果。我必须缺少一些基本知识。

输出:

VREFINT=1917; VREFINT_CAL=1524; VDDA=2623 mV
VREFINT=1885; VREFINT_CAL=1524; VDDA=2668 mV
VREFINT=1913; VREFINT_CAL=1524; VDDA=2628 mV
VREFINT=1917; VREFINT_CAL=1524; VDDA=2623 mV
VREFINT=1917; VREFINT_CAL=1524; VDDA=2623 mV

adc_test.c:

#include <stdio.h>
#include "stm32f0xx.h"

#define VREFINT_CAL_ADDR                0x1FFFF7BA  /* datasheet p. 19 */
#define VREFINT_CAL ((uint16_t*) VREFINT_CAL_ADDR)

extern void initialise_monitor_handles(void);

int main(void)
{
    RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;     /* enable ADC peripheral clock */
    RCC->CR2 |= RCC_CR2_HSI14ON;            /* start ADC HSI */
    while (!(RCC->CR2 & RCC_CR2_HSI14RDY)); /* wait for completion */
    /* calibration */
    ADC1->CR |= ADC_CR_ADCAL;               /* start ADc CALibration */
    while (ADC1->CR & ADC_CR_ADCAL);        /* wait for completion */
    ADC1->CR |= ADC_CR_ADEN;                /* ADc ENable */
    while (!(ADC1->ISR & ADC_ISR_ADRDY));   /* wait for completion */
    ADC1->SMPR |= ADC_SMPR1_SMPR_0 |        /* sampling mode: longest */
      ADC_SMPR1_SMPR_1 |
      ADC_SMPR1_SMPR_2;
    /* VDD reference */
    ADC->CCR |= ADC_CCR_VREFEN;             /* VREF Enable */
    ADC1->CHSELR = ADC_CHSELR_CHSEL17;      /* CH17 = VREFINT */

    initialise_monitor_handles();           /* enable semihosting */

    while (1) {
        ADC1->CR |= ADC_CR_ADSTART;             /* start ADC conversion */
        while (!(ADC1->ISR & ADC_ISR_EOC));     /* wait for completion */
        uint32_t vdda = 3300UL * *VREFINT_CAL / ADC1->DR; /* ref. manual p. 252; constant and result in millivolts */
        printf("VREFINT=%lu; VREFINT_CAL=%lu; VDDA=%lu mV\n",
                (unsigned long)ADC1->DR,
                (unsigned long)*VREFINT_CAL,
                (unsigned long)vdda);
    }
}

数据表中的屏幕截图:

enter image description here

参考手册中的截图

请注意,这是指0.3V,但我认为这是一个错字,因为上面的数据表和下面的较长公式均指3.3V,而.3V低于该部分的最低工作电压

enter image description here

3 个答案:

答案 0 :(得分:1)

我目前正在为STM32L4开发ADC驱动器。在实施过程中,我遇到了几乎相同的问题。我认为第一个公式 enter image description here

不是计算VDDA,而是VREF +。 ADC用来评估ADC-IN通道的电压。 此外,VREFINT_DATA不是测量的VREF +电压,而是与控制器有关的内部基准电压。就我而言,它在控制器数据表中定义: enter image description here

这是我如何使用发布的公式的图片: enter image description here

一些评论: ln 102:计算VREF +而不是VDDA

在105-110中:计算所有等级/配置的顺序

ln 108:计算ADCpin_x测得的电压

ln 109:乘以增益以获得实际值

我认为通过为每个转换序列计算VREF +,我会得到更好的结果,因为这样可以补偿VREF +上的一些纹波。

答案 1 :(得分:1)

正如@Artur 所说 Vref + 不是 Vdda,但通常(这就是我在我的硬件设计中使用它的方式)Vref + 连接到 Vdda(根据数据表使用相应的过滤器),因此计算 Vdda 与计算 Vref +。

我将向您展示如何计算 vdda,因为我有它基于 STM32L431。

。您必须首先配置 ADC 以测量 VREFINT:

void MX_ADC1_Init(void)
{

    /* USER CODE BEGIN ADC1_Init 0 */

    /* USER CODE END ADC1_Init 0 */

    ADC_ChannelConfTypeDef sConfig = {0};

    /* USER CODE BEGIN ADC1_Init 1 */

    /* USER CODE END ADC1_Init 1 */
    /** Common config
     */
    hadc1.Instance = ADC1;
    hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;
    hadc1.Init.Resolution = ADC_RESOLUTION_12B;
    hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
    hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
    hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
    hadc1.Init.LowPowerAutoWait = DISABLE;
    hadc1.Init.ContinuousConvMode = DISABLE;
    hadc1.Init.NbrOfConversion = 1;
    hadc1.Init.DiscontinuousConvMode = DISABLE;
    hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
    hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
    hadc1.Init.DMAContinuousRequests = DISABLE;
    hadc1.Init.Overrun = ADC_OVR_DATA_PRESERVED;
    hadc1.Init.OversamplingMode = DISABLE;
    if (HAL_ADC_Init(&hadc1) != HAL_OK)
    {
        Error_Handler();
    }
    /** Configure Regular Channel
     */
    sConfig.Channel = ADC_CHANNEL_VREFINT;
    sConfig.Rank = ADC_REGULAR_RANK_1;
    sConfig.SamplingTime = ADC_SAMPLETIME_247CYCLES_5;
    sConfig.SingleDiff = ADC_SINGLE_ENDED;
    sConfig.OffsetNumber = ADC_OFFSET_NONE;
    sConfig.Offset = 0;
    if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
    {
        Error_Handler();
    }
    /* USER CODE BEGIN ADC1_Init 2 */

    /* USER CODE END ADC1_Init 2 */
}

。现在我将向您展示执行方程的代码:

vdda = 3.0 * (VREFINT_CAL /average);
vch = VREF * (average / ADC_RESOLUTION);

log("vdd = %.5f - ", vdda);
log("vchn = %.5f", vch);

哪里:

#define ADC_RESOLUTION 4095.0           // adc resolution 12 bits
#define VREFINT_CAL 1655.00             // Raw data acquired at a temperature of 30 °C (± 5 °C), VDDA = VREF+ = 3.0 V (± 10 mV)
#define VREF 3.3                        // voltage reference 3.3V

注意:

'average' 是 adc 采集的 256 个样本的平均值(它只是一个简单的过滤器)。

'log'是我为uart创建的一个类似于printf的函数。

“VREFINT_CAL”因型号而异。

结果:

vdd = 3.28035 - vchn = 1.21343

如我们所见,VREFINT 与数据表匹配(典型值为 1.212V):

VREFINT

答案 2 :(得分:0)

其实就是在计算Vdda,由于Vref的计算很简单,你要读取ADC对应的通道,采样时间比data sheet中标注的要长(一般是10us)。如果 Vdda 为 2.0 V,则 4095 的值对应于 2.0(或更多)V 绝对值(相关 GND)。以线性方式,Vref 的值将远高于在 Vdda = 3.30 V 时读取的值。因此,需要对用 2.0 V 读取的值进行补偿,以了解所读取的电压绝对值ADC 正在测量。如果它们没有得到补偿,它们将是相对于 Vdda 在那一刻的电压电平的值。 此外,还实现了电源值,这将有助于不超出微控制器的规格。