STM32内部参考电压+DMA精准采集电池电压

2023-11-12

最近项目又遇到了电池电压采集,锂电池的电压范围是4.2到2.8一般,当锂电池低于3.3V时,单片机供电电压会小于3.3V,那么电池电压参考计算4096就不能对应3.3,所以必须采用内部参考电压。(我项目中用到的是RP104N331 LDO,实际上当电池电压在3.5V左右时,LDO输出就已经不是3.3V,严重影响精度)

VREFINT_CAL = *(__IO uint16_t *)(0X1FF80078);

首先需要从数据手册知道VREFINT_CAL 的地址信息,读出16的值,所以这里采用了 uint16_t
同时stm32 开启两路ADC,一路是要采集的ADC,一路是内部参考电压

	ADC_ChannelConfTypeDef sConfig = {0};
	VREFINT_CAL = *(__IO uint16_t *)(0X1FF80078);
	V_temp1 = 3.3 / 4096 * (float)ADC_buffer[0] / 0.6;
	VDDA = 3 * VREFINT_CAL/(float)ADC_buffer[1];
	V_temp2 = VDDA/4096 * (float)ADC_buffer[0] / 0.6;
	printf("VDDA %f \r\n",VDDA);
	printf("%f V %f V\r\n",V_temp1,V_temp2);
	printf("%d %d\r\n",ADC_buffer[0],ADC_buffer[1]);

V1是常规的3.3V作为参考电压,VDDA可以通过VREFINT_CAL 计算得出,V_temp2是以VDDA得出,经过开关电源测试,V_temp2误差基本保持在0.01V

由于我们采集双通道,所以采用DMA传输

这里有几个参数需要注意,一个是ClockPrescaler,我这里采用ADC_CLOCK_ASYNC_DIV128,分频不同居然采集的电压不同,而且误差极大,这里一直找不到相关原因,另一个是LowPowerFrequencyMode,由于这里分频比较大,导致采用时钟很低,所以ENABLE,具体低于多少打开手册有要求,但是实测使能或者不使能差别并不是很大,最后在初始化完成之后需要增加校准函数HAL_ADCEx_Calibration_Start

下面是adc.c源码,这里的DMA是连续采集,只需在主函数开启一次HAL_ADC_Start_DMA(&hadc, (uint32_t *)ADC_buffer, 2)

/**
  ******************************************************************************
  * File Name          : ADC.c
  * Description        : This file provides code for the configuration
  *                      of the ADC instances.
  ******************************************************************************
  * @attention
  *
  * <h2><center>&copy; Copyright (c) 2020 STMicroelectronics.
  * All rights reserved.</center></h2>
  *
  * This software component is licensed by ST under BSD 3-Clause license,
  * the "License"; You may not use this file except in compliance with the
  * License. You may obtain a copy of the License at:
  *                        opensource.org/licenses/BSD-3-Clause
  *
  ******************************************************************************
  */

/* Includes ------------------------------------------------------------------*/
#include "adc.h"

/* USER CODE BEGIN 0 */
__IO uint32_t uwADCxConvertedValue1 = 0;
__IO uint32_t uwADCxConvertedValue2 = 0;
__IO uint16_t VREFINT_CAL ;
float V_temp1,V_temp2,VDDA;
extern uint32_t ADC_buffer[2];
/* 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_DIV128;
  hadc.Init.Resolution = ADC_RESOLUTION_12B;
  hadc.Init.SamplingTime = ADC_SAMPLETIME_12CYCLES_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 = ENABLE;
  hadc.Init.LowPowerAutoPowerOff = DISABLE;
  if (HAL_ADC_Init(&hadc) != HAL_OK)
  {
    Error_Handler();
  }
	
	if (HAL_ADCEx_Calibration_Start(&hadc, ADC_SINGLE_ENDED) !=  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_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 = GPIO_PIN_1;
    GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOA, &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_WORD;
    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);

  /* USER CODE BEGIN ADC1_MspInit 1 */
//		if (HAL_ADCEx_Calibration_Start(&hadc, ADC_SINGLE_ENDED) !=  HAL_OK)
//		{
//    Error_Handler();
//		}
  /* 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(GPIOA, GPIO_PIN_1);

    /* ADC1 DMA DeInit */
    HAL_DMA_DeInit(adcHandle->DMA_Handle);
  /* USER CODE BEGIN ADC1_MspDeInit 1 */

  /* USER CODE END ADC1_MspDeInit 1 */
  }
} 

/* USER CODE BEGIN 1 */
void Acquisition_voltage()
{
	//float VDDA;
	ADC_ChannelConfTypeDef sConfig = {0};
	VREFINT_CAL = *(__IO uint16_t *)(0X1FF80078);
	V_temp1 = 3.3 / 4096 * (float)ADC_buffer[0] / 0.6;
	VDDA = 3 * VREFINT_CAL/(float)ADC_buffer[1];
	V_temp2 = VDDA/4096 * (float)ADC_buffer[0] / 0.6;
	printf("VDDA %f \r\n",VDDA);
	printf("%f V %f V\r\n",V_temp1,V_temp2);
	printf("%d %d\r\n",ADC_buffer[0],ADC_buffer[1]);
  
	
//	//printf("VREFINT_CAL %d \r\n",VREFINT_CAL);
	
	sConfig.Channel = ADC_CHANNEL_1;
  sConfig.Rank = ADC_RANK_CHANNEL_NUMBER;
  if (HAL_ADC_ConfigChannel(&hadc, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }
//	
//	
//	if (HAL_ADC_Start(&hadc) != HAL_OK)
//  {
//    /* Start Conversation Error */
//    Error_Handler();
//  }
//	
//   HAL_ADC_PollForConversion(&hadc, 1000);
//  
//	/* Check if the continous conversion of regular channel is finished */
//	if ((HAL_ADC_GetState(&hadc) & HAL_ADC_STATE_REG_EOC) == HAL_ADC_STATE_REG_EOC)
//	{
//		/*##-6- Get the converted value of regular channel  ########################*/
//		uwADCxConvertedValue1 = HAL_ADC_GetValue(&hadc);
//		
//	}
//	
	printf("uwADCxConvertedValue1 %d\r\n",uwADCxConvertedValue1);
//	V_temp = 3.3 / 4096 * uwADCxConvertedValue1 / 0.6;
//	printf("V_temp %f V\r\n",V_temp);

	sConfig.Channel = ADC_CHANNEL_17;
  sConfig.Rank = ADC_RANK_CHANNEL_NUMBER;
  if (HAL_ADC_ConfigChannel(&hadc, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }
//	
//	
//	if (HAL_ADC_Start(&hadc) != HAL_OK)
//  {
//    /* Start Conversation Error */
//    Error_Handler();
//  }
//	
//   HAL_ADC_PollForConversion(&hadc, 1000);
//  
//	/* Check if the continous conversion of regular channel is finished */
//	if ((HAL_ADC_GetState(&hadc) & HAL_ADC_STATE_REG_EOC) == HAL_ADC_STATE_REG_EOC)
//	{
//		/*##-6- Get the converted value of regular channel  ########################*/
//		uwADCxConvertedValue2 = HAL_ADC_GetValue(&hadc);
//	}
//	
//	printf("uwADCxConvertedValue2 %d\r\n",uwADCxConvertedValue2);
	
	
	
}
/* USER CODE END 1 */

/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

STM32内部参考电压+DMA精准采集电池电压 的相关文章

  • 什么是Qt信号槽机制

    1 信号和槽概述 信号槽是 Qt 框架引以为豪的机制之一 所谓信号槽 实际就是观察者模式 发布 订阅模式 当某个 事件 发生之后 比如 按钮检测到自己被点击了一下 它就会发出一个信号 signal 这种发出是没有目的的 类似广播 如果有对象
  • React Hooks--与传统react写法比较

    React Hooks 简介 2018年底FaceBook的React小组推出Hooks以来 所有的React的开发者都对它大为赞赏 React Hooks就是用函数的形式代替原来的继承类的形式 并且使用预函数的形式管理state 有Hoo
  • oracle按照首汉字首字母排序

    按照拼音顺序 ORDER BY nlssort NAME NLS SORT SCHINESE PINYIN M 按照部首顺序 ORDER BY nlssort NAME NLS SORT SCHINESE RADICAL M 按照笔画顺序
  • websocket实现聊天室(一)

    最近接到一个聊天室的任务 之前在学校完全没有接触过这方面的需求 在网上查找资料后 基本确定了实现方案 现在就开始着手学习 在此记录一下遇到的问题 初识websocket 在简单了解websocket后 我觉得与http请求类似 不过webs
  • 读取g2o 文件的python实现

    可以读取2D 和 3D的 g2o 文件 并可以把四元数的位姿转换为节点和边数据 import argparse import numpy as np import pyquaternion File Format Vertex 2D Rob
  • H.264 标准简介

    JVT Joint Video Team 视频联合工作组 于2001年12月在泰国Pattaya成立 它由ITU T和ISO两个国际标准化组织的有关视频编码的专家联合组成 JVT的工作目标是制定一个新的视频编码标准 以实现视频的高压缩比 高
  • Visual Studio编译出来的程序无法在其它电脑上运行

    在其它电脑 比如Windows Server 2012 上运行Visual Studio编译出来的应用程序 结果报错 无法启动此程序 因为计算机中丢失VCRUNTIME140 dll 尝试重新安装该程序以解决此问题 解决方法 属性 gt 配
  • PNP和NPN磁感应开关有什么区别

    1 我们以磁性开关为例 先要搞清楚PNP NPN 表示的意思是什么 P表示正 N表示负 PNP表示平时为高电位 信号到来时信号为负 NPN表示平时为低电位 信号到来时信号为高电位输出 接近开关和光电开关只是检测电路不同输出相同 至于PLC接

随机推荐

  • Spring 提示:无法找到元素 'aop:aspectj-autoproxy'

    问题描述 org springframework beans factory xml XmlBeanDefinitionStoreException Line 18 in XML document from class path resou
  • 程序员从初级到中级10个秘诀

    新闻来源 techrepublic comJustin James曾发表过一篇博文 10 tips for advancing from a beginner to an intermediate developer 为我们分享如何才能完成
  • syskey (win7启动密码)加密和破解方法

    1 什么是syskey Syskey是NT Service Pack 3中带的一个工具 用来保护SAM数据库不被离线破解 用过去的加密机制 如果攻击者能够得到一份加密过的SAM库的拷贝 他就能够在自己的机器上来破解用户口令 2 如何开启sy
  • [Json依赖] JSONObject的依赖包

  • Windos10专业版开启远程桌面协助

    我需要控制局域网的电脑 这台电脑是win10专业版 搜索 远程桌面设置 进入后启动远程桌面设置 然后发现当前用户已经有访问权 当前用户没有密码 那么远程失败 解决方法是 按win r 输入GPEDIT MSC 计算机配置 gt 安全设置 g
  • 前端框架React

    前端框架React 组件基础 React事件机制 哪些方法会让React重新渲染 render会做什么 React类组件和函数组件 React高阶组件 和普通组件的区别 适用场景 React受控组件和非受控组件 React有状态组件和无状态
  • java enum compare_Java Compare Enum value

    In Java you can use operator to compare Enum value 1 Java Enum example Language java package com mkyong java public enum
  • 啥?简单的题都不会,可咋整呢?

    目录 一 寻找原因 二 寻找解决方法 三 常见的刷题网站 刷题技巧 明明自觉学会了不少知识 可真正开始做题时 却还是出现了 一支笔 一双手 一道力扣 Leetcode 做一宿 的窘境 你是否也有过这样的经历 题型不算很难 看题解也能弄明白
  • Python3获取股票行情数据(中国个股/中国指数/全球指数)

    usr local bin python3 coding utf 8 source http www cnblogs com txw1958 import os io sys re time json base64 import webbr
  • Js常用面试题目知识整理

    Js代码题 1 千分位 题目 要求返回参数数字的千分位分隔符字符串 思路 在字符串长度不确定的情况下 可以使用递归 comma number 1000 是获取数字最后三位 将其放在返回值的最后面 并且在前面加一个逗号 comma Math
  • freenom域名申请教程

    freenom域名申请教程 1 注册 申请域名 打开freenom官网 注册一个账户 注意 如果没有明显的注册按钮 可以通过如下方式同时申请域名和注册账户 打开域名申请 不用注册 选择好了域名之后 点击Checkout 选择免费期限 最长的
  • shell脚本编程 实例讲解(键盘输入三个数字,按照从大到小的书顺序输出)

    1 键盘输入三个数字 按照从大到小的书顺序输出 排序题 a b c 2 10 9 a 2 b 10 c 9 第一步 两两相互进行比较 比较三次 第二步 不论谁大谁小 最后都输出 a b c 从大从小 a永远存储的都是最大值 a和b进行比较
  • 笔记&代码

    可视化前三步走 数据类型 分析目的 实现工具 2 1 类别数据可视化 显示各类别的绝对频数及百分比等 条形图 饼图等 2 1 1 条形图及其变种 垂直条形图 类别在x轴 水平条形图 类别在y轴 简单条形图 并列条形图 堆叠条形图 1 简单条
  • 前端知识——css 之 flex 布局

    目录 一 认识 flex 布局 1 flex 布局的重要概念 二 flex 相关属性 1 flex container 中的属性 1 1 flex direction item 的排布方向 1 2 flex wrap 排布是否换行 1 3
  • Java多线程下载文件

    Java多线程下载文件 优化 合理利用服务器资源 将资源利用最大化 加快下载速度 一般有两种方式 线程池里面有N个线程 多线程下载单个文件 将网络路径的文件流切割成多快 每个线程下载一小部分 然后写入到文件里面 组成一个文件 当有很多个文件
  • MQ队列消息怎么保证100%不丢失

    面试官在面试候选人时 如果发现候选人的简历中写了在项目中使用了 MQ 技术 如 Kafka RabbitMQ RocketMQ 基本都会抛出一个问题 在使用 MQ 的时候 怎么确保消息 100 不丢失 这个问题在实际工作中很常见 既能考察候
  • javaScript基础面试题 --- new操作符具体做了什么?

    当我们使用new操作符调用函数时 背后发生了很多事情 这里是简单的new操作符的行为 创建一个新的空对象 将这个空对象的原型链接到构造函数的prototype对象 使用这个新对象作为上下文 即this的值 调用该构造函数 如果构造函数返回一
  • Yii Framework 开发教程(25) 数据库-Query Builder示例

    上一篇介绍PHP使用DAO 数据库访问对象接口 访问数据库的方法 使用DAO需要程序员编写SQL语句 对于一些复杂的SQL语句 Yii提供了Query Builder来帮助程序员生成SQL语句 Query Builder提供了一中面向对象的
  • Windows7安装docker以及使用docker安装centos7

    目录 一 WIN7安装DOCKER 二 docker安装centos7 1 查看可用的 CentOS 版本 2 拉取指定版本的 CentOS 镜像 3 查看本地镜像 4 运行容器 并且可以通过 exec 命令进入 CentOS 容器 5 安
  • STM32内部参考电压+DMA精准采集电池电压

    最近项目又遇到了电池电压采集 锂电池的电压范围是4 2到2 8一般 当锂电池低于3 3V时 单片机供电电压会小于3 3V 那么电池电压参考计算4096就不能对应3 3 所以必须采用内部参考电压 我项目中用到的是RP104N331 LDO 实