STM32 ADC测量电池电压(使用内部参考电压)

   在使用ADC时,通常的用法是Vref+接电源VDD3.3V,然后计算时直接用3.3V做参考电压,但是这种方法忽略了一些情况如供电电压有可能随外部一些其他用电器工作使用的大电流而导致电压不稳定,还有可能MCU供电LDO转换的精度个别偏差较大。这时候依然用3.3V的定值做参考电压计算显然得出的值就会出现与实际电压偏差较大的问题。


【解决方案】
一般 100 脚以上的 STM32 MCU都有 VREF 引脚。对于 100 脚以下的芯片,STM32 没有把 VREF 引脚引出来,而是直接在内部连接到了 VDDA 引脚。这样就导致了 ADC 的供电电源和参考电源实际是一个。通常项目中我们VDDA也是连接到了VDD。
如果有 VREF 引脚,可以在VREF上接一个稳定且精度高的电压作为参考电压。

还有一种方法是启用内部参考电压。
根据STM32f10xCDE数据手册中的数据,这个参照电压的典型值是1.20V,最小值是1.16V,最大值是1.24V(-40~85度)。这个电压基本不随外部供电电压的变化而变化。

很明显,如果以这个为参考电压,我们也得测量其值,因为它对于不同的芯片是一个范围,并不是确定值。 STM32 可以通过配置将 VREFINT 接入到 ADC 内部的通道17,然后我们就可以像测试普通的通道一样测量 VREFINT 到底是多少。注意MCU 不同 具体连接的 ADC 通道也是不同的。

在这里插入图片描述

具体方法是在测量某个通道的电压值之前,先读出参照电压的ADC测量数值(即从ADC1_IN17读出的值),记为ADrefint;再读出要测量通道的ADC转换数值,记为ADchx;则要测量的电压为:

Vchx = Vrefint * (ADchx/ADrefint)

其中Vrefint为参照电压=1.20V(STM32F10x)。

推导过程:

Vchx = VREFINT* (((ADchx*(VREF/4096))/(ADre*(VREF/4096)))

注:VREFINT=1.2V(这里取1.20V实际上会有误差),VREF为参考电压值=3.3V
此公式可以理解为:(ADchx*(VREF/4096)是正常计算的含误差电压值,VREFINT/(ADre*(VREF/4096)是修正参数,ADre*(VREF/4096)得到实际的参考电压值比较接近VREFINT,VREFINT是校准电压值,VREFINT/(ADre*(VREF/4096)是约等于1的一个修正值。 – 个人理解

公式简化后:

Vchx = VREFINT*(ADchx/ADre)

该式计算得到的值是该通道的实际电压值。
注意:上面的方法针对F1芯片只给了参考电压的范围,没有提供出厂校准值的情况,下面将介绍提供了校准值的情况。

VREFINT_CAL是芯片出厂时厂家测量出来的参考电压值固化在flash中,U16两个字节,可以作为基准电压。使用时读出即可。该值不是所有系列芯片都有,F103貌似都没有,下图是L475的(注意不同芯片该值保存的地址不一致):

上图表示,厂家在30度左右环境温度下,VDDA和参考电压等于3.0V的状态下,通过ADC通道读出的参考电压值,保存在0x1FFF75AA开始地址的2个字节中。如何读取示例:VREFINT_CAL = *(u16*)0x1FFF75AA,笔者手里的L475读出来是0x067F,换算3.0*(0x067F/4095)=1.218315,接近1.20了。

我们重点关注这个公式:
 

在这里插入图片描述


VREFINT_CAL:内部参考电压校准值,直接地址读取。比如该款芯片地址:0x1FFF75AA,那么我们可以这么做:
VREFINT_CAL = *(__IO uint16_t *)(0x1FFF75AA);

FULL_SCALE:根据我们设置的ADC分辨率而定,12位ADC分辨率值:2^12 - 1 = 4096 - 1

VREFINT_DATA:从ADC_17通道读出实际内部参考电压值

ADCx_DATA:需要测试的电压通道读值
注意:公式中的3.0V有时可能是3.3V,取决于厂家给的校准值是在3.0V条件下测试还是3.3V或是其他。

推导过程
第一个公式VDDA = 3.0V x VREFINT_CAL / VREFINT_DATA 这个公式是怎么来的呢?
ST厂商 通过配置将 VREFINT 连接到 ADC 后,则有:VREFINT = 3.0V * (VREFINT_CAL / 4095); VREFINT_CAL 就是校准条件下的 ADC 采样值(校准条件就是指VDDA=Vref+=3.0V,环境温度30度),采到的VREFINT_CAL值写入到flash。
我们自己通过配置将 VREFINT 连接到 ADC:VREFINT = VDDA * (VREFINT_DATA / 4095);
因此,VDDA * (VREFINT_DATA / 4095) = 3.0 * (VREFINT_CAL / 4095);
VDDA = 3.0V x VREFINT_CAL / VREFINT_DATA

VDDA是这个公式中的重点,我们常规的算法直接用3.3V作为VDDA计算才导致了误差,因为VDDA有误差不是刚好3.3V。

第二个公式Vchannalx=VDDA*(ADCx_DATA/FULL_SCALE)
这个公式很好理解,就是我们常用的计算电压方式,ADCx_DATA是读出的采样值,如:3.3*(1650/4095)

上面两式联立就能得出最后的公式。

前面讲过的 Vchx = Vrefint * (ADchx/ADrefint),Vrefint=1.20V
实际上就是Vchannelx = (3.0xVrefint/FULL_SCALE)*(ADCx_DATA/VREFINT_DATA),其中(3.0xVrefint/FULL_SCALE)=1.20V

#define ADC1_DR_ADDR      ((uint32_t)0x4001244C)
float battaryValue;
u16 ADC_VALUE[2];
void ADC1_AdcInit(void)
{
//ADC_InitTypeDef ADC_InitStuctrue;  
	
			ADC_InitTypeDef ADC_InitStructure; 

			GPIO_InitTypeDef GPIO_InitStructure;  

			DMA_InitTypeDef DMA_InitStructure;	

			RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_ADC1,ENABLE); //使能AHB预分频器到端口A的开关

			RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);


			RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
			// RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//引脚复用 进行重映射时要开启AFIO 时钟
			//声明结构变量
			GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;             //定义PA0,PA1脚为AD输入
			GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;                      //IO口为模拟输入模式
			//GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;            //AD口设置为浮空输入
			GPIO_Init(GPIOA, &GPIO_InitStructure);    


			RCC_ADCCLKConfig(RCC_PCLK2_Div4);

			/* DMA1 channel1 configuration ----------------------------------------------*/
			DMA_DeInit(DMA1_Channel1);
			DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_ADDR;
			DMA_InitStructure.DMA_MemoryBaseAddr = (u32)&ADC_VALUE;
			DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
			DMA_InitStructure.DMA_BufferSize = 2;
			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_M2M = DMA_M2M_Disable;
			DMA_Init(DMA1_Channel1, &DMA_InitStructure);

			//DMA_ITConfig(DMA1_Channel1,DMA_IT_TC,ENABLE);
			/* Enable DMA1 channel1 */
			DMA_Cmd(DMA1_Channel1, ENABLE);

			// ADC内置温度传感器禁止
			ADC_TempSensorVrefintCmd(DISABLE);

			/* ADC1 configuration ------------------------------------------------------*/
			ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
			ADC_InitStructure.ADC_ScanConvMode = ENABLE;
			ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;//DISABLE;
			ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
			ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
			ADC_InitStructure.ADC_NbrOfChannel = 2;
			ADC_Init(ADC1, &ADC_InitStructure);

			ADC_RegularChannelConfig(ADC1,  ADC_Channel_4,      1, ADC_SampleTime_55Cycles5);
			ADC_RegularChannelConfig(ADC1,  ADC_Channel_17,     2, ADC_SampleTime_55Cycles5);

			ADC_DMACmd(ADC1, ENABLE);

			/* Enable ADC1 external trigger */
			ADC_ExternalTrigConvCmd(ADC1, DISABLE);

			ADC_Cmd(ADC1, ENABLE);
			/* 重置ADC校准寄存器 */
			ADC_ResetCalibration(ADC1);
			/* 检测ADC校准寄存器状态 */
			while(ADC_GetResetCalibrationStatus(ADC1));
			/* 开始ADC校准程序 */
			ADC_StartCalibration(ADC1);
			/* 检测ADC校准状态 */
			while(ADC_GetCalibrationStatus(ADC1));

			ADC_SoftwareStartConvCmd(ADC1, ENABLE);



			ADC_TempSensorVrefintCmd(ENABLE);//打开内部参照电压	
 
  
}


void readBattaryValue(void)
{
	 
    battaryValue=(float)1.20*((float)ADC_VALUE[0]/ADC_VALUE[1]);//参考电压1.2 V  
					
}

附带记录VDD、VDDA,VBAT,VREF的区别
MCU 的参考手册都会有一章节单独介绍 MCU 的电源管理,针对不同的 MCU(封装不同等)其外部电源如何连接也是有要求的,我们在 MCU 上一般都会发现如下引脚(注意不同 MCU 是有区别的):

VDD / VSS: VDD is the external power supply for the I/Os, the internal regulator and the system analog such as reset, power management and internal clocks. It is provided externally through VDD pins.

VDDA / VSSA: VDDA 是A/D转换器,D/A 转换器,参考电压缓冲器,运算放大器和比较器的外部模拟电源。 VDDA 电压电平与 VDD 电压无关。 不使用这些外设时,最好将 VDDA 连接到 VDD。

VBAT: 当不存在 VDD 时,VBAT 是 RTC,外部时钟 32kHz 振荡器和备用寄存器(通过电源开关)的电源。 对于没有专用引脚的小型封装,VBAT内部连接到了 VDD

VREF+ / VREF-: VREF+ 是 ADC 和 DAC 的输入参考电压。 使能后,它也是内部参考电压缓冲器的输出。当不使用 ADC 和 DAC 时,VREF+ 可以接地。VRE- 必须始终等于 VSSA。
VREF- 和 VREF+ 引脚并非在所有封装中都可用。 如果封装上未提供它们,则它们在 MCU 内部分别与 VSSA 和 VDDA 相连。

参考文章:

STM32 ADC采样使用内部参考电压 - 云禅网络

### 使用 STM32 ADC 读取电池电压 为了使用 STM32内部模数转换器 (ADC)测量电池电压,可以按照如下方法配置并编写代码。这里以 STM32F103 和 STM32L051C8T6 微控制器为例。 #### 配置 ADC 模块 首先,在初始化阶段设置 ADC 参数,包括通道选择、采样时间以及分辨率等属性。对于 STM32F103 系列而言: ```c // 初始化结构体定义 static void MX_ADC1_Init(void) { ADC_ChannelConfTypeDef sConfig = {0}; hadc1.Instance = ADC1; hadc1.Init.ScanConvMode = DISABLE; // 单次模式 hadc1.Init.ContinuousConvMode = ENABLE; // 连续转换模式开启 hadc1.Init.DiscontinuousConvMode = DISABLE; hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START; hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT; hadc1.Init.NbrOfConversion = 1; if (HAL_ADC_Init(&hadc1) != HAL_OK) { Error_Handler(); } // 配置用于连接电池分压电路的模拟输入引脚 PA0 sConfig.Channel = ADC_CHANNEL_0; sConfig.Rank = 1; sConfig.SamplingTime = ADC_SAMPLETIME_7CYCLES_5; if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) { Error_Handler(); } } ``` 这段代码展示了如何初始化 ADC 并指定要使用的具体通道[^1]。 #### 启动一次转换并获取结果 当完成上述配置之后,就可以启动单次转换操作,并等待直到获得有效的数据样本为止。 ```c uint32_t adc_value; if(HAL_ADC_Start(&hadc1) == HAL_OK){ if(HAL_ADC_PollForConversion(&hadc1, HAL_MAX_DELAY) == HAL_OK){ adc_value = HAL_ADC_GetValue(&hadc1); printf("Battery Voltage Raw Value: %lu\n", adc_value); }else{ // 转换超时处理... } } else { // 启动失败处理... } ``` 以上片段说明了怎样触发 ADC 开始工作并通过轮询方式取得最终数值。 #### 计算实际电压值 由于 ADC 返回的是未经校准的原始数字量,因此还需要将其转化为对应的物理单位——伏特(V),这通常涉及到线性变换公式 Vout=(Vin*Vref)/4096 或者其他形式的比例关系取决于所选器件的具体参数规格。 假设外部供电为 3.3V,则计算公式可能像这样: ```c float battery_voltage = ((float)(adc_value * 3300)) / 4096 ; // 将ADC值转换成mV printf("Battery Voltage: %.2fmV \n",battery_voltage ); ``` 此处的例子假定参考电压 `Vref` 是固定的 3.3V,并且采用默认的最大计数值 4096 对应满刻度范围内的最大输入信号电平。 对于更复杂的 DMA 数据传输机制,可以在不阻塞 CPU 执行路径的情况下高效地收集大量连续的数据点;而针对低功耗应用场景下的优化措施则体现在选用合适的休眠模式及时钟门控策略等方面[^2]。 最后值得注意的是不同型号之间可能存在细微差异,比如 STM32L051C8T6 提供了一个内置的温度传感器和可编程增益放大器(PGA),这些特性有助于提高特定条件下的性能表现[^3]。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值