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采样使用内部参考电压 - 云禅网络

  • 29
    点赞
  • 66
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
您好!对于精准测量电池电压,您可以使用STM32微控制器的模数转换器(ADC)功能来实现。 首先,您需要连接电池电压测量点到STM32微控制器的一个ADC引脚上。确保电压测量点的电压范围在ADC的输入电压范围内。 然后,您可以使用STM32提供的库函数来配置和读取ADC的值。以下是一个示例代码片段,用于配置和读取ADC的值: ```c #include "stm32fxxx.h" // 替换为您使用的具体STM32型号的头文件 // 配置ADC void ADC_Configuration(void) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); // 启用ADC1时钟 ADC_InitTypeDef ADC_InitStructure; ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; // 独立模式 ADC_InitStructure.ADC_ScanConvMode = DISABLE; // 禁用扫描模式 ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; // 禁用连续转换模式 ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; // 数据右对齐 ADC_InitStructure.ADC_NbrOfChannel = 1; // 设置通道数量为1 ADC_Init(ADC1, &ADC_InitStructure); ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5); // 配置ADC通道和采样时间 ADC_Cmd(ADC1, ENABLE); // 使能ADC1 } // 读取ADC值 uint16_t ADC_ReadValue(void) { ADC_SoftwareStartConvCmd(ADC1, ENABLE); // 启动ADC转换 while(ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET); // 等待转换完成 return ADC_GetConversionValue(ADC1); // 返回ADC转换结果 } int main(void) { ADC_Configuration(); // 配置ADC while (1) { uint16_t adcValue = ADC_ReadValue(); // 读取ADC值 // 在这里执行您的电压计算逻辑,将ADC值转换为电压值 // 等待一段时间,然后进行下一次电压测量 } } ``` 这是一个简单的示例代码,您可以根据您的具体需求进行修改和扩展。注意,在实际使用中,您可能还需要考虑参考电压的设置、校准和滤波等问题,以确保测量结果的准确性和稳定性。 希望对您有所帮助!如果您有任何其他问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值