校准STM32 ADC(VREFINT)

5

我正在尝试读取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);
    }
}

数据表截图:

这里输入图片描述

参考手册截图

注意,这里提到了0.3V,但我认为这是一个打字错误,因为上面的数据表和下面更长的公式都提到了3.3V,并且0.3V低于该元件的最小工作电压。

这里输入图片描述


2
我没有看到你的代码有任何明显的问题,我可以确认参考手册中的.3而不是3.3确实是一个打字错误(我找到的在线副本没有这个错误)。对于问题的猜测 - 你是否将Vssa引脚漂浮不连接地面?(假设你正在使用实际上有单独的Vssa引脚的STM32F042变体。)你计算Vdda时的错误非常接近一个二极管压降,如果负参考电压漂浮,这似乎是一个合理的结果。 - jasonharper
1
这是一个有趣的想法,但是该引脚(引脚32)连接到GND:https://imgur.com/gMo2GsH 有趣的是,热垫没有连接到任何东西。 - iter
5
这个原理图非常错误——它显示了零件的UFQFNP32变种的零件编号,但是引脚的标签却根据LQFP32变种进行标注(该变种甚至没有热敏垫)。在UFQFPN32上,引脚16和32是额外的B端口I/O引脚,而热敏垫是您唯一的地面连接,绝对需要进行正确操作。基本上,您的芯片只通过某些I/O引脚上的ESD保护二极管看到地面,并且2.62V确实是芯片接收到的电源供应的准确测量值。 - jasonharper
1
哦哦哦......你说得完全正确。前一个版本的原理图要求LQFP32,然后改为UFQFNP32,我想硬件人员没有仔细阅读数据手册。这看起来相当糟糕......我很惊讶芯片竟然还能工作,并且在数字领域表现得非常良好。显然,我们必须在下一轮板子中解决这个问题。作为一种权宜之计,将PB2和PB8设置为输入而不是高阻抗是否有助于将芯片中更多的电路连接到地线上,或者ESD二极管是唯一通向地线的路径? - iter
1
我怀疑OpenOCD没有任何方法可以测量电压到7个有效数字...但肯定存在实际的电压降,因为芯片的整个操作电流通过这两个引脚上低侧FET的Rds(on)。 (电压降将根据芯片正在执行的操作以及其他输出所绘制的电流而变化,因此您的ADC读数不会非常准确。)至于发布答案:由于问题完全出现在硬件中,我认为它不属于这里(并且Electronics.SE不是可以迁移问题的站点之一)。 - jasonharper
显示剩余5条评论
4个回答

1

正如@Artur所说,Vref + 不是Vdda,但通常(这也是我在硬件设计中的做法),Vref + 与Vdda相连(根据数据手册相应的滤波器),因此计算Vdda与计算Vref +相同。

我将向您展示如何根据STM32L431计算vdda。

您必须先配置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);

where:

#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打印函数的函数。

'VREFINT_CAL' 根据型号而异。

结果:

vdd = 3.28035 - vchn = 1.21343

正如我们所看到的,VREFINT与数据表(1.212V typ.)相匹配:

VREFINT


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 ln 105-110:计算所有等级/配置序列

第108行:计算ADCpin_x测量的电压。

第109行:乘以增益以获得实际值。

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


1
在我的情况下,答案是接地不良。一旦我们正确连接了接地,公式就正常工作了。 - iter

0
实际上它是在计算Vdda,由于Vref的计算非常简单,您必须使用比数据表中标记的采样时间更长的采样时间读取ADC的相应通道(通常为10微秒)。如果Vdda为2.0 V,则4095的值对应于2.0(或更高)V绝对值(相关GND)。以线性方式,如果使用Vdda = 3.30 V读取Vref的值将比使用2.0 V读取的值高得多。因此,必须补偿使用2.0 V读取的值,以了解ADC正在测量的电压的绝对值。如果不进行补偿,则它们将是相对于Vdda在那一时刻的电压水平的值。
此外,还可以获得电源值,这将有助于不超出微控制器的规格。

0
答案(非常感谢@jasonharper)是缺失的接地连接。Jason在OP上的评论是这个帖子中最明智的来源。我在这里发布一个摘要,以便这个问题可以有一个被接受的答案。
板子经历了许多次修订,在这个版本中,我们忘记连接热敏垫,而这个部分是唯一的接地连接。芯片通过连接到地的引脚上的ESD二极管得到接地。令我惊讶的是它居然还能工作。我能够通过将接地的GPIO配置为输出并将它们设置为低电平来增加芯片的电流。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接