目录

概述

1、原理图

2、在这先普及一下概念

3、通过查看STM32L0中文数据手册中301页,第14.10 小节 ,DataSheet

4、ADC通道转换模式的理解

5、STM32CubeMx工具的配置

6、代码

7、运行结果


概述

主控芯片:STM32L051C8T6

采用内部1.2V参考电压,解决了测量电池电量小于3.0V以下检测不准的问题。篇幅有点长,还需要各位看官慢慢阅读。

基本上可以做到1.8V~5V之间的精准检测电池电量。

1、原理图

            

2、在这先普及一下概念

在使用STM32进行ADC采集处理的时候,需要设计到参考电压的选取问题,关于模拟部分,牵扯到参考电压的引脚为:
1.100引脚以下的芯片,ADC参考电压引脚VREF+在单片机内部和VDDA引脚连接。
2.100引脚以上的芯片,ADC参考电压引脚VREF+和VDDA是分开的。
所以,在100引脚以上的芯片中,我们可以是采用单独供电,改变ADC的参考电压。但是在100引脚以下的芯片中,就必须是看VDDA的电压值范围。

查看数据手册,可以发现,VDDA为所有的模拟电路部分供电,包括:ADC模块,复位电路,PVD(可编程电压监测器),PLL,上电复位(POR)和掉电复位(PDR)模块,控制VBAT切换的开关等。即使不 使用ADC功能,也需要连接VDDA,强烈建议VDD和VDDA使用同一个电源供电。VDD与VDDA之间的电压差不能超过300mV,VDD与VDDA 应该同时上电或调电。

所以,不管是任何引脚的STM32芯片,我们必须将VDDA和VDD引脚进行连接,所以,也就是说,100引脚以下的STM32的ADC参考电压是3.3V(固定的),没办法改变的。

VREF+在硬件上是必须 大于 2.5V,当此引脚电平值不正确时,STM32单片机也不能正常工作。
今天在调试STM32 的OTG板子的时候,出现的问题就是这个原因。

3、通过查看STM32L0中文数据手册中301页,第14.10 小节 ,DataSheet

14.10 温度传感器和内部参考电压温度传感器可用于测量器件的结温 (TJ)。温度传感器在内部连接到 ADC_IN18 输入通道,该通道用于将传感器输出电压转换为数字值。温度传感器模拟引脚的采样时间必须大于数据手册中指定的最小 TS_temp 值。不使用时可将传感器置于掉电模式。
内部参考电压 (VREFINT) 为 ADC 和比较器提供了一个稳定的(带隙基准)电压输出。
VREFINT 内部连接到 ADC_IN17 输入通道。 VREFINT 的精确电压由 ST 在生产测试期间对每部分单独测量,并存储于系统存储区。访问模式为只读。图 53 显示的是温度传感器、内部参考电压与 ADC 之间连接的方框图。必须将 TSEN 位置 1 才能使能 ADC_IN18(温度传感器)的转换,必须将 VREFEN 位置 1
才能使能 ADC_IN17 (VREFINT) 的转换。温度传感器的输出电压随温度线性变化。由于工艺不同,该线的偏移量取决于各个芯片(芯片之间的温度变化可达 45 °C)。未校准的内部温度传感器更适用于对温度变量而非绝对温度进行测量的应用。为提高温度传感器测量的准确性, ST 在生产过程中将校准值存储在每个器件的系统存储器中。在制造过程中,会将温度传感器的校准数据和内部参考电压存储在系统存储区。随后,用户应用可读取这些数据,并使用这些数据提高温度传感器或内部参考的准确性。其他相关信息,请参见数据手册。.
.
.使用内部参考电压计算实际的 VDDA 电压施加给微控制器的 VDDA 电源电压可能会有变化,或无法获得准确值。在制造过程中由 ADC在 VDDA = 3 V 的条件下获得的内置内部参考电压 (VREFINT) 及其校准数据可用于评估实际的 VDDA 电压水平。以下公式可求得为器件供电的实际的 VDDA 电压:VDDA = 3 V x VREFINT_CAL / VREFINT_DATA
其中:
. VREFINT_CAL 是 VREFINT 校准值
. VREFINT_DATA 是由 ADC 转换得到的实际 VREFINT 输出值
将电源相关的 ADC 测量值转换为绝对电压值ADC 用于提供对应于模拟电源与施加给转换通道的电压之比的数字值。对于大部分应用用例,需要将该比值转换成与 VDDA 无关的电压。对于 VDDA 已知、 ADC 转换值进行了右对齐的应用,可使用以下公式得到该绝对值:VCHANNELx = VDDA * ADC_DATAx / FULL_SCALE
对于 VDDA 值未知的应用,必须使用内部参考电压, VDDA 可替换为使用内部参考电压计算实际的 VDDA 电压部分提供的表达式,从而得出以下公式:VCHANNELx = 3V * VREFINT_CAL * ADC_DATAx / VREFINT_DATA * FULL_SCALE
其中:
. VREFINT_CAL 是 VREFINT 校准值
. ADC_DATAx 是由 ADC 在通道 x 上测得的值(右对齐)
. VREFINT_DATA 是由 ADC 转换得到的实际 VREFINT 输出值
. full_SCALE 是 ADC 输出的最大数字值。例如,如果分辨率为 12 位,该值为212 - 1 = 4095,如果分辨率为 8 位,该值为 28 - 1 = 255。注: 如果执行 ADC 测量时使用的是输出格式而非 12 位右对齐格式,那么必须先将所有参数转换
为兼容格式,然后再进行计算。

好了,通过上面讲解得出一个结论,这个参考电压的典型值是1.20V,最小值是1.16V,最大值是1.24V。这个电压基本不随外部供电电压的变化而变化。(注意,还没完呢?还要看STM32L051C8T6的数据手册,在27页。)

这样就知道了内部基准电压存放在0x1FF80078这个地址.(注意:不同的芯片存放地址不同)。好了紧接着步伐继续深究原理。

4、ADC通道转换模式的理解

STM32的ADC有单次转换和连续转换2种模式,这两种模式又可以选择是否结合扫描模式。

单通道:

(1)CONT=0,SCAN=0   单次转换模式  (CONT为连续转换使能位,SCAN为扫描模式使能位)

举例:ADC1,通常规则通道的顺序为CH0,CH1,CH2,CH3;那么这种模式就是只转换这4个通道其中的一个通道,转换完成后,就停止转换。等待ADC的下一次启动

(2)CONT=1,SCAN=0   单次连续转换模式

举例:ADC1,通常规则通道的顺序为CH0,CH1,CH2,CH3;那么这种模式就是只转换这4个通道其中的一个通道,连续转换扫描这一个通道。

多通道:

(3)CONT=0,SCAN=1   多通道扫描转换模式

举例:ADC1,通常规则通道的顺序为CH0,CH1,CH2,CH3;那么这种模式就是以上的这4个通道,依次从CH0开始转换,转换完成后又开始转换CH1,直到所有的ADC规则通道序列都扫描转换一次,最后就停止转换。等待ADC的下一次启动

(4)CONT=1,SCAN=1   多通道连续扫描转换模式

举例:ADC1,通常规则通道的顺序为CH0,CH1,CH2,CH3;那么这种模式就是以上的这4个通道,依次从CH0开始转换,转换完成后又开始转换CH1,直到所有的ADC规则通道序列都扫描转换一次后,再从第一个CH0通道循环。连续扫描一组

PS:一般多通道采集都会结合DMA来传输数据,不会使用中断来传输数据,以此,来节约CPU资源的占用。

5、STM32CubeMx工具的配置

6、代码

main.c文件 /* USER CODE BEGIN 2 */HAL_TIM_Base_Start_IT(&htim2);HAL_GPIO_WritePin(GPIOB, POWER_ON_Pin, GPIO_PIN_SET);  //开机HAL_GPIO_WritePin(GPIOB, BAT_EN_Pin, GPIO_PIN_SET);     //使能检测电池电压HAL_ADCEx_Calibration_Start(&hadc, ADC_SINGLE_ENDED);HAL_ADC_Start_DMA(&hadc,(uint32_t*) &adc1_val_buf, (ADC_CHANNEL_CNT*ADC_CHANNEL_FRE));/* USER CODE END 2 *//* Infinite loop *//* USER CODE BEGIN WHILE */while (1){getBatVoltage();getChipInTempVal();/* USER CODE END WHILE *//* USER CODE BEGIN 3 */HAL_Delay(1000);HAL_GPIO_TogglePin(GPIOA,GPIO_PIN_2);      //LED}/* USER CODE END 3 */
adc.h文件/* USER CODE BEGIN Includes */#define ADC_CHANNEL_CNT 3   //采样通道数
#define ADC_CHANNEL_FRE 100 //单个通道采样次数,用来取平均值extern uint32_t adc1_val_buf[ADC_CHANNEL_CNT*ADC_CHANNEL_FRE]; //传递给DMA存放多通道采样值的数组
extern uint32_t adc1_aver_val[ADC_CHANNEL_CNT]; //保存多通道的平均采样值的数组
extern uint32_t dma_cnt1;/* USER CODE END Includes *//* USER CODE BEGIN Prototypes */uint8_t getBatVoltage(void);
uint16_t getChipInTempVal(void);
void get_ADC_Channel_Val(void);/* USER CODE END Prototypes */
adc.c文件/* Includes ------------------------------------------------------------------*/
#include "adc.h"/* USER CODE BEGIN 0 */
#include "stdio.h"
#include "math.h"
#include "tim.h"#ifdef    ADC_MULTICHANNEL_DMA//对于12位的ADC,3.3V的ADC值为0xfff,温度为25度时对应的电压值为1.43V即0x6EE
#define V25  0x6EE
//斜率 每摄氏度4.3mV 对应每摄氏度0x05
#define AVG_SLOPE 0x05uint32_t adc1_val_buf[ADC_CHANNEL_CNT*ADC_CHANNEL_FRE] = {0}; //传递给DMA存放多通道采样值的数组
uint32_t adc1_aver_val[ADC_CHANNEL_CNT] = {0}; //保存多通道的平均采样值的数组
uint8_t dma_cnt1 = 0;#endif#ifdef  ADC_SINGLE_CHANNEL#endif/* USER CODE END 0 */ADC_HandleTypeDef hadc;
DMA_HandleTypeDef hdma_adc;/* ADC init function */
void MX_ADC_Init(void)
{ADC_ChannelConfTypeDef sConfig = {0};/** Configure the global features of the ADC (Clock, Resolution, Data Alignment and number of conversion) */hadc.Instance = ADC1;hadc.Init.OversamplingMode = DISABLE;hadc.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV2;hadc.Init.Resolution = ADC_RESOLUTION_12B;hadc.Init.SamplingTime = ADC_SAMPLETIME_160CYCLES_5;hadc.Init.ScanConvMode = ADC_SCAN_DIRECTION_FORWARD;hadc.Init.DataAlign = ADC_DATAALIGN_RIGHT;hadc.Init.ContinuousConvMode = ENABLE;hadc.Init.DiscontinuousConvMode = DISABLE;hadc.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;hadc.Init.ExternalTrigConv = ADC_SOFTWARE_START;hadc.Init.DMAContinuousRequests = ENABLE;hadc.Init.EOCSelection = ADC_EOC_SINGLE_CONV;hadc.Init.Overrun = ADC_OVR_DATA_PRESERVED;hadc.Init.LowPowerAutoWait = DISABLE;hadc.Init.LowPowerFrequencyMode = DISABLE;hadc.Init.LowPowerAutoPowerOff = DISABLE;if (HAL_ADC_Init(&hadc) != HAL_OK){Error_Handler();}/** Configure for the selected ADC regular channel to be converted. */sConfig.Channel = ADC_CHANNEL_1;sConfig.Rank = ADC_RANK_CHANNEL_NUMBER;if (HAL_ADC_ConfigChannel(&hadc, &sConfig) != HAL_OK){Error_Handler();}/** Configure for the selected ADC regular channel to be converted. */sConfig.Channel = ADC_CHANNEL_TEMPSENSOR;if (HAL_ADC_ConfigChannel(&hadc, &sConfig) != HAL_OK){Error_Handler();}/** Configure for the selected ADC regular channel to be converted. */sConfig.Channel = ADC_CHANNEL_VREFINT;if (HAL_ADC_ConfigChannel(&hadc, &sConfig) != HAL_OK){Error_Handler();}}void HAL_ADC_MspInit(ADC_HandleTypeDef* adcHandle)
{GPIO_InitTypeDef GPIO_InitStruct = {0};if(adcHandle->Instance==ADC1){/* USER CODE BEGIN ADC1_MspInit 0 *//* USER CODE END ADC1_MspInit 0 *//* ADC1 clock enable */__HAL_RCC_ADC1_CLK_ENABLE();__HAL_RCC_GPIOA_CLK_ENABLE();/**ADC GPIO Configuration    PA1     ------> ADC_IN1 */GPIO_InitStruct.Pin = BAT_ADC_Pin;GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;GPIO_InitStruct.Pull = GPIO_NOPULL;HAL_GPIO_Init(BAT_ADC_GPIO_Port, &GPIO_InitStruct);/* ADC1 DMA Init *//* ADC Init */hdma_adc.Instance = DMA1_Channel1;hdma_adc.Init.Request = DMA_REQUEST_0;hdma_adc.Init.Direction = DMA_PERIPH_TO_MEMORY;hdma_adc.Init.PeriphInc = DMA_PINC_DISABLE;hdma_adc.Init.MemInc = DMA_MINC_ENABLE;hdma_adc.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;hdma_adc.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;hdma_adc.Init.Mode = DMA_CIRCULAR;hdma_adc.Init.Priority = DMA_PRIORITY_LOW;if (HAL_DMA_Init(&hdma_adc) != HAL_OK){Error_Handler();}__HAL_LINKDMA(adcHandle,DMA_Handle,hdma_adc);/* ADC1 interrupt Init */HAL_NVIC_SetPriority(ADC1_COMP_IRQn, 0, 0);HAL_NVIC_EnableIRQ(ADC1_COMP_IRQn);/* USER CODE BEGIN ADC1_MspInit 1 *//* USER CODE END ADC1_MspInit 1 */}
}void HAL_ADC_MspDeInit(ADC_HandleTypeDef* adcHandle)
{if(adcHandle->Instance==ADC1){/* USER CODE BEGIN ADC1_MspDeInit 0 *//* USER CODE END ADC1_MspDeInit 0 *//* Peripheral clock disable */__HAL_RCC_ADC1_CLK_DISABLE();/**ADC GPIO Configuration    PA1     ------> ADC_IN1 */HAL_GPIO_DeInit(BAT_ADC_GPIO_Port, BAT_ADC_Pin);/* ADC1 DMA DeInit */HAL_DMA_DeInit(adcHandle->DMA_Handle);/* ADC1 interrupt Deinit */HAL_NVIC_DisableIRQ(ADC1_COMP_IRQn);/* USER CODE BEGIN ADC1_MspDeInit 1 *//* USER CODE END ADC1_MspDeInit 1 */}
}/* USER CODE BEGIN 1 */uint16_t getChipInTempVal(void)     //获取芯片内部温度
{uint16_t tempVal = 0;tempVal = (V25-adc1_aver_val[2])/AVG_SLOPE+25;printf("adc1_aver_val[2]: %d, Temp:%3d ℃ \r\n",adc1_aver_val[2],tempVal);return tempVal;
}uint16_t getBatVoltage(void)
{uint8_t i = 0;uint16_t BATVAL = 0;float VDDA = 0;float VCHANNELx = 0;  //实际测量的电池电量值__IO uint16_t VREFINT_CAL = 0;  VREFINT_CAL = *(__IO uint16_t *)(0x1FF80078); //得到一个16进制的校准值printf("\n\r ***** START PRINTF ADC INFO ******** \r\n\n");for(i=0;i<ADC_CHANNEL_CNT;i++){printf("ADC1[%02d] Sampling voltage = %1.3f V Sampling value = %04d\r\n",i,adc1_aver_val[i]*3.0f/4095,adc1_aver_val[i]);}/** * 检测实际电池电量公式:* VCHANNELx = 3V * VREFINT_CAL * ADC_DATAx / VREFINT_DATA * FULL_SCALE* ADC_DATAx : 就是读ADC分压电阻的值,对应通道adc1_aver_val[0]* VREFINT_DATA :是 ADC_CHANNEL_17 的值  adc1_aver_val[1]* FULL_SCALE :4095*/VDDA = 3*VREFINT_CAL*1000/adc1_aver_val[1]; //扩大1000倍printf("VDDA:%lf \r\n",VDDA);   printf("VREFINT_CAL:%d\r\n",VREFINT_CAL);printf("VCHANNELx: %0.4f \r\n",(float)(VDDA * adc1_aver_val[0] / 4095));   VCHANNELx = (float)((300 * VREFINT_CAL * adc1_aver_val[0]) / (adc1_aver_val[1] * 4095))/100; //printf("VCHANNELx: %0.4f \r\n",VCHANNELx);BATVAL=(VCHANNELx*3.0+0.03)*100;  //3.0是分压电阻检测ADC的系数, 0.03是跟实际少的差值100是放大一百倍
//  printf("BATVAL: %d \r\n",BATVAL); printf("g_BatVoltage: %d \r\n", BATVAL);return BATVAL; }void get_ADC_Channel_Val(void)
{uint8_t i = 0;//1ms进入中断/* 清除adc采样平均值变量 */for(i=0;i<ADC_CHANNEL_CNT;i++){adc1_aver_val[i] = 0;}/* 在采样值数组中分别取出每个通道的采样值并求和 */for(i=0;i<ADC_CHANNEL_FRE;i++){adc1_aver_val[0] +=  adc1_val_buf[i*3+0];adc1_aver_val[1] +=  adc1_val_buf[i*3+1];adc1_aver_val[2] +=  adc1_val_buf[i*3+2];}/* 依次对每个通道采样值求平均值 */for(i=0;i<ADC_CHANNEL_CNT;i++){adc1_aver_val[i] /= ADC_CHANNEL_FRE;}
}/* USER CODE END 1 */
tim.c文件/* Includes ------------------------------------------------------------------*/
#include "tim.h"/* USER CODE BEGIN 0 */
#include "adc.h"/* USER CODE END 0 */TIM_HandleTypeDef htim2;
.
.
./* USER CODE BEGIN 1 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{/*使用STM32CubeMx配置tim2,1毫秒中断一次*/if(htim->Instance == TIM2){get_ADC_Channel_Val();}
}
/* USER CODE END 1 */

这两个通道,可以使用工具goto进去看看,结果发现就是我们要使用的通道17,上面的代码基本使用STM32CubeMx工具生成的,只需添加自己逻辑与功能实现代码即可。

7、运行结果

大于3.3V以上检测的电压值

小于3.0V以下检测的电压值

这里我只测试了4.4V的电压值,不敢往上测试更高的电压值,怕烧坏产品中的LDO管子,在这种情况下,只要是带电池的设备基本都能满足。

源码链接

参考文章:https://blog.csdn.net/u010307522/article/details/56681013

https://blog.csdn.net/qq_34160496/article/details/89259793

在这分享一些HAL其他外设操作的例程文章:http://www.waveshare.net/study/article-646-1.html

基于STM32HAL库ADC+DMA模式,高精度采集电池电量与芯片内部温度方法 (48脚 使用内部参考电压方案)相关推荐

  1. STM32F0使用LL库实现DMA方式AD采集

    在本次项目中,限于空间要求我们选用了STM32F030F4作为控制芯片.这款MCU不但封装紧凑,而且自带的Flash空间也非常有限,所以我们选择了LL库实现.在本文中我们将介绍基于LL库的ADC的DM ...

  2. STM32F4 ADC+DMA单通道采集

    背景:对锂电池电压进行采集,由于电池电压为12V,已经提前对12V进行分压,保证ADC采集电压的范围为0~3.3V.对电池电压的采集不用太过频繁,循环模式下的ADC+DMA对一直采集电压浪费资源.于是 ...

  3. STM32-HAL库串口DMA空闲中断的正确使用方式+解析SBUS信号

    STM32-HAL库串口DMA空闲中断的正确使用方式+解析SBUS信号 一. 问题描述 二. 方法一--使用HAL_UART_Receive_DMA 三. 方法二--使用HAL_UARTEx_Rece ...

  4. STM32G070RBT6基于STM32CubeMX创建ADC DMA多通道采样工程

    STM32G070RBT6基于STM32CubeMX创建ADC DMA多通道采样工程 -

  5. stm32l151 ADC通过DMA通道定时采样电池电量

      最近在使用stm32l151开发一个项目,我的项目需求是ADC采集电池电量,通过DMA通道传送出来.然而我并不是得到了电池电量数据后就立马连续输出,而是通过tim4定时器每1s访问一次采样得到的电 ...

  6. AI之AutoML:autosklearn/Auto-Sklearn(基于scikit-learn库的自动化的机器学习工具)的简介、安装、使用方法之详细攻略

    AI之AutoML:autosklearn/Auto-Sklearn(基于scikit-learn库的自动化的机器学习工具)的简介.安装.使用方法之详细攻略 目录 autosklearn/Auto-S ...

  7. STM32 HAL库ADC+DMA(非定时器)代码和遇到的问题

    目录 一.整体说明 二.部分知识点预览 三.代码部分 四.遇到的问题及现象 (1)仿真进入错误 (2)非连续模式软件触发时adcbuf里面的数据不对 (3)HAL库的ADC DMA相关中断的嵌套实现 ...

  8. 第九章 AT32F403A基于V2库串口 dma接收不定长数据

    目录 概述 硬件 DMA 软件 流程 初始化 初始化代码: 中断服务函数: DMA1通道5设置函数:(重新使能通道) DMA1通道4发送函数:(设置dma长度和内存地址) 测试 最后 概述 本文主要是 ...

  9. 基于STM32HAL库使用ADC采样方式,检测电压值与自带芯片温度值

    目录 概述 1.原理图 2.STM32CubeMx工具配置 3.代码 1.串口(串口重映射打印配置) 2.ADC 3.main 4.运行结果 概述 主控芯片:STM32L051C8T6 IDE: ke ...

最新文章

  1. 链表问题6——环形单链表的约瑟夫问题(初阶)
  2. 四 Spring的工厂类,xml的配置
  3. 2021牛客多校10 - Train Wreck(贪心)
  4. 认识学习网络布线与数制转换
  5. 关于splice()方法,slice() 、split()方法讲解,reverse()方法、replace()方法
  6. 写在S3C2440A平台+winCE5.0+NAND +HIVE注册表的实现
  7. Jquery特殊效果
  8. python的环境变量设置
  9. python sftp连接_python 进行ftp服务器和sftp服务器连接
  10. 删除HTML标签的正则表达式
  11. Maven 项目添加jetty 插件
  12. 2021年中国城市建设状况公报有关供水、燃气、供热数据已出
  13. adb 通过WiFi连接小米8手机
  14. Python上使用及安装tesseract
  15. java面试教程视频
  16. Wordpress站点使用七牛云对象储存以及CDN加速
  17. 图神经网络:GAT学习、理解、入坑
  18. [转]SIFT,SURF,ORB,FAST 特征提取算法比较
  19. Python如何自制包、如何安装自己的包并导入
  20. Windows 2003 磁盘分区工具(易我分区大师)

热门文章

  1. 显示硬件发展与视频开发系列(6)----显示标注与视频处理单元(2):VPU
  2. Linux学习笔记 驱动开发篇
  3. Android-模块化-面向接口编程深度解析,值得收藏
  4. 【Unity】Unity下载器下载不下IOS/Android等模块的解决办法
  5. 公司邮箱注册申请流程,好用的邮箱功能开启高效办公
  6. Joda-Time 实战
  7. 竞赛题-6283. 正整数和负整数的最大计数
  8. 高光谱影像伪彩色显示
  9. Build和Rebuild的区别
  10. ros学习——gmapping建图