草庐IT

ADC采样电池电压

书阁下 2023-04-10 原文

文章目录

ADC的基本了解

  • ADC,即模数转换器

  • 在单片机中的传输信号为数字信号,通过离散的高低电平表示数字逻辑1和0,但是日常生活中我们常见的信号为模拟信号,即连续变化的信号,但是我们可以把这些信号转换为电信号,再通过ADC将模拟信号转化为数字信号进行处理

ADC原理

  • 一般工作流程为:采样,比较,转换

    • 采样:指采集某一时刻的模拟电压
    • 比较:指将采样的电压在比较电路中进行比较
    • 转换:指将比较电路中结果转换成数字量
  • STM32F4采用12位逐次逼近型ADC(SAR-ADC)

    首先会对模拟信号进行采样,采样值为Vin

    然后进行比较该3位ADC有3种比较过程,如下图

    该图表示,对不同位数赋予不同的权值

    • 与1/2Vref进行比较,Vin大于1/2Vref时,将第一位赋值为1
    • 与3/4Vref进行比较,Vin小于3/4Vref时,将第二位赋值为0
    • 与5/8Vref进行比较,Vin小于5/8Vref时,将第三位赋值为0
    • 然后将各值按照1/2,1/4,1/8按权进行计算,例如以上比较后输出结果为100,则转换结果为1 * 1/2+0 * 1/4+0 * 1/8=1/2,则最终转换结果为1/2Vref.

STM32最高支持12位ADC,一般ADC的位数越多则转换精度越高,但与此同时转换的速度也会变慢

STM32内部有一个校准电压VREFINT,电压为1.2V,当供电电压不为3.3V时可以使用内部的vrefint通道采集1.2V电压作为Vref,以提高精度(STM32的校准电压Vrefint在ADC1中)。

逐次型ADC转换原理

整个电路由比较器,D/A转换器,缓冲寄存器和若干控制逻辑电路构成

  • 比较器:用于输入电压值与D/A转换器输出电压进行比较,当输入电压大于该转换器电压时输出1,反之输出0
  • D/A转换器:ADC的逆向过程,将缓冲寄存器的记录数字量转换成模拟量
  • 缓冲寄存器:记录当前转换的数字量

转换的过程如下:

  1. 将缓冲寄存器清零
  2. 将逐次逼近寄存器最高位置1
  3. 将数字值传入D/A转换器,经D/A转换后的模拟量传入比较器,为V0
  4. V0与比较器的待转换的模拟量V1比较,若V0<V1,该位保留,否则清0
  5. 再置寄存器次高位为1,将寄存器中新的数字量送D/A转换器
  6. 输出的V0再与V1进行比较,若若V0<V1,该位保留,否则清0
  7. 一直循环该过程,直至寄存器最低位,得到数字量的输出

电阻分压电路

由于电池提供的电源是24V的高电压,但是单片机引脚的耐压只有0-3.3V所以需要通过分压电路来进行处理

首先,利用200KΩ和22KΩ的分压电路将24V电压进行分压

分压后的电压送至次级电路

在次级电路中,通过100nF的电容进行滤波,使输出的电压更加稳定

接着用二极管保护电路将电压限制在0-3.3V(当电压大于3.3V时,二极管正向导通,电压被限制在3.3V;当电压小于0V时,二极管正向导通,电压被限制在0V)

Cube配置外设

在原理图中找到ADC对应的引脚

可以看到该ADC对应PF10引脚,使用ADC3的通道8

在Cube中对ADC1和ADC3进行如下配置

ADC1用于内部1.2V的Vrefint通道读取

ADC3用于电池电压ADC3通道8的读取

在ADC_Settings中对ADC1和ADC3都进行如下配置

这样就配置好了Cube

ADC内部电压的使用

由于外部供电电压不一定为 3.3V

为了在这种情况提高ADC精度,我们需要使用单片机内部的参照电压

使用ADC采样该电压来提高ADC的精度

ADC内部参照电压VREFINT为1.2V

将采样内部参照电压1.2V的ADC值和Vref的加权值进行比较,进而得到ADC的输出值

STM32的ADC采用Vcc作为Vref,但为了防止Vcc存在波动较大导致Vref不稳定,进而导致采样值的比较结果不准确,STM32可以通过内部已有的参照电压VREFINT来进行校准

接着以VREFINT为参照来比较ADC的采样值,从而获得比较高的精度

即可以通过一个函数对1.2V的电压进行多次采样,并计算其平均值,然后将其与ADC采样的数据进行比较得到单位数字电压的模拟电压值voltage_vrefint_proportion,设采样得到的数字值为average_adc,公式如下

ADC采样相关函数

  1. HAL_ADC_ConfigChannel()

    HAL_StatusTypeDef HAL_ADC_ConfigChannel(ADC_HandleTypeDef* hadc, ADC_ChannelConfTypeDef* sConfig)
    

    作用:设置ADC通道的各个属性值,包括转换通道,序列排序,采样时间等

    返回值:为HAL_StatusTypeDef类型,即表示状态,如成功则返回HAL_OK

    参数:a. TIM_HandleTypeDef * hadc,即输入&hadc1等

    ​ b.ADC_ChannelConfTypeDef* sConfig,即ADC的参数设置结构体需要先对sConfig结构体进行赋值

  1. HAL_ADC_Start()

    HAL_StatusTypeDef HAL_ADC_Start(ADC_HandleTypeDef* hadc)
    

    作用:开启ADC的采样

    返回值:同样是HAL_StatusTypeDef类型

    参数:ADC_HandleTypeDef* hadc,即&hadc1等

  2. HAL_ADC_PollForConversion()

    HAL_StatusTypeDef HAL_ADC_PollForConversion(ADC_HandleTypeDef* hadc, uint32_t Timeout)
    

    作用:等待ADC转换结束

    返回值:同样是HAL_StatusTypeDef类型

    参数:1.ADC_HandleTypeDef* hadc;2.uint32_t Timeout,即等待的最大时间

  1. HAL_ADC_GetValue()

    uint32_t HAL_ADC_GetValue(ADC_HandleTypeDef* hadc)
    

    作用:获取ADC值

    返回值:HAL_StatusTypeDef类型

    参数:TIM_HandleTypeDef* hadc类型

代码编写

程序流程如下:

获得基准电压值

首先要对内部电压VREFINT进行采样,并将其作为校准值

通过init_vrefint_reciprocal函数执行以上过程

void init_vrefint_reciprocal(void) 
{
    uint8_t i = 0; 
    uint32_t total_adc = 0; 
    for(i = 0; i < 200; i++) //对电压VREFINT进行200次采样
    { 
        total_adc += adcx_get_chx_value(&hadc1, ADC_CHANNEL_VREFINT);
    }
    voltage_vrefint_proportion = 200 * 1.2f / total_adc;//使VREFINT的电压值1.2V除以采样得到的均值,后面ADC采样到的电压值与这个voltage_vrefint_proportion相乘就可以计算出内部参考电压做过校准的ADC值
}

获得电池电压

然后对ADC的电压值进行采样,再将该采样值与voltage_vrefint_proportion(模拟电压值)相乘得到ADC的采样值

得到ADC采样值后,我们可以根据这个值反算出电压的值,分压的电压阻值为200KΩ,22KΩ,则(22K Ω + 200K Ω) / 22K Ω = 10.09,把采样值乘以该值后所得即为电池电压值

该求电压的代码实现如下:

fp32 get_battery_voltage(void)
{ 
    fp32 voltage; 
    uint16_t adcx = 0; 
    adcx = adcx_get_chx_value(&hadc3, ADC_CHANNEL_8);
    //(22KΩ+200KΩ)/22KΩ=10.090909090909090909090909090909 
    voltage = (fp32)adcx * voltage_vrefint_proportion * 10.090909090909090909090909090909f; 
    return voltage;
}

获取温度

通过ADC获得板载温度传感器的温度值

先经过ADC值进行采样

然后将采样结果通过公式计算出温度值

temperate = (adc - 0.76f) * 400.0f + 25.0f

fp32 get_temprate(void)
{ 
    uint16_t adcx = 0;
    fp32 temperate; 
    adcx = adcx_get_chx_value(&hadc1, ADC_CHANNEL_TEMPSENSOR);//先进行ADC采样 
    temperate = (fp32)adcx * voltage_vrefint_proportion;
    temperate = (temperate - 0.76f) * 400.0f + 25.0f; //使用公式计算出温度值 
    return temperate; 
}

最终编写的代码

/* USER CODE BEGIN PV */
float Batvalt=0;//电池电压
float Vrefint=0;//单位数字电压的模拟电压值
float Temperate=0;//板载温度传感器的温度值
/* USER CODE END PV */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
    HAL_ADC_Start(&hadc1);//启动ADC1采样
    while(!HAL_ADC_PollForConversion(&hadc1,1));//等待ADC1进行1s的转换
    /*当该函数成功运行后输出成功的状态(1)
    故我们这里需要取非(“!”),即让该函数成功运行后就结束循环*/
    Vrefint=1.2/HAL_ADC_GetValue(&hadc1);
    /*HAL_ADC_GetValue(&hadc1),获取内部电压的采样值
      Vrefint,单位数字电压的模拟电压值
    */

    HAL_ADC_Start(&hadc3);//启动ADC3采样
    while(!HAL_ADC_PollForConversion(&hadc3,1));//等待ADC3进行1s的转换
    Batvalt=HAL_ADC_GetValue(&hadc3)*Vrefint*10.090909090909090909090909090909f;
    //HAL_ADC_GetValue(&hadc3),即获取ADC3的采样值
    //Vrefint,内部电压的基准值(单位数字电压的模拟电压值)
    //10.090909090909090909090909090909f,由(22KΩ+200KΩ)/22KΩ计算所得,分压比
  
    HAL_ADC_Start(&hadc1);//启动ADC1采样
    while(!HAL_ADC_PollForConversion(&hadc1,1));//等待ADC1进行1s的转换
    Temperate=(HAL_ADC_GetValue(&hadc1)-0.76f)*400.0f+25.0f;
    /*HAL_ADC_GetValue(&hadc1),获取内部电压的采样值
      根据公式计算出板载温度传感器的温度值
    */ 
  }
  /* USER CODE END 3 */

结果如图所示:

参考资料

从零开始制作RoboMaster步兵机器人-11.ADC采样电池电压
ADC数模转换
《RoboMaster开发板 C 型嵌入式软件教程文档》
《Description of STM32F4 HAL and low-layer drivers》

有关ADC采样电池电压的更多相关文章

  1. ruby - 随机采样数组的唯一子集 - 2

    如果我有一个数组:a=[1,2,3]如何随机选择数组的子集,使每个子集的元素都是唯一的?也就是说,对于a,可能的子集是:[][1][2][3][1,2][2,3][1,2,3]我无法生成所有可能的子集,因为a的实际大小非常大,所以有很多很多子集。目前,我正在使用“随机游走”的想法——对于a的每个元素,我都会“抛硬币”,如果硬币正面朝上则将其包括在内——但我不确定这是否真的对空间进行了均匀采样。感觉它偏向于中间,但这可能只是我的想法在进行模式匹配,因为会有更多中等大小的可能性。我使用的方法是否正确,或者我应该如何随机抽样?(我知道这更像是一个与语言无关的“数学”问题,但我觉得这不是真正的

  2. ruby - 有效地处理数字数组的 "scale"或 "resize"的算法(音频重采样) - 2

    做音频处理(虽然它也可以是图像处理)我有一个一维数字数组。(它们恰好是代表音频样本的16位有符号整数,这个问题同样适用于float或不同大小的整数。)为了匹配不同频率的音频(例如,将44.1kHz样本与22kHz样本混合),我需要拉伸(stretch)或压缩值数组以满足特定长度。将数组减半很简单:每隔一个样本丢弃一次。[231,8143,16341,2000,-9352,...]=>[231,16341,-9352,...]将数组宽度加倍稍微不那么简单:将每个条目加倍(或可选地在相邻的“真实”样本之间执行一些插值)。[231,8143,16341,2000,-9352,...]=>[2

  3. javascript - 通过采样/插值减少大型数据集的大小以提高图表性能 - 2

    我有一大组(>2000)时间序列数据,我想在浏览器中使用d3显示这些数据。D3非常适合向用户显示数据的一个子集(~100点),但我还想要一个“上下文”View(likethis)来显示整个数据集并允许用户选择作为子区域进行查看细节。但是,当尝试在d3中显示那么多点时,性能很糟糕。我觉得一个好的解决方案是选择一个数据样本,然后使用某种插值(样条、多项式等,这是我知道怎么做的部分)来绘制一条与实际数据。但是,我不清楚应该如何选择子集。数据(如下所示)具有相当平坦的区域,在这些区域需要较少的样本才能进行适当的插值,而其他区域的绝对导数非常高,需要更频繁的采样。更复杂的是,数据存在间隙(生成数

  4. 电池供电遥测终端RTU 遥测终端机 低功耗遥测采集终端 智能远传 防水IP68 - 2

    平升电子电池供电遥测终端RTU/遥测终端机/低功耗遥测采集终端是基于4G、5G、NB-IoT网络实现数据采集、远程传输、分析计算、越限报警的智能设备,具有功耗低、IP68防水等特点。特别适合用在无供电条件、防水防尘要求高的监测现场。随着通信网络更迭、产品持续改进,平升电子电池供电遥测终端RTU自2007年问世至今,已经拥有了4款系列产品,可应用于多种场合:管网监测、地下水监测、无线远程抄表、水资源取用水计量监测、油田长停井监测等。功能特点  ★ 远程监测仪表/传感器数据  ★ 4G/5G/NB-IoT无线远传  ★ 数据越限、设备异常自动报警,及时发现事故隐患  ★ IP68级防护——防水防潮

  5. javascript - 如何在 javascript 中获取设备电池电量 - 2

    我在我的应用程序中每15秒进行一次网络调用,如果用户设备的电池电量百分比低于20%,那么我希望改为每30秒进行一次调用。如何获取用户设备的当前电池电量?可能吗?任何帮助将不胜感激。 最佳答案 看看BatteryStatusAPI.它并不适用于所有浏览器,但这是一个开始。对于支持它的浏览器,像这样的东西应该可以工作:navigator.getBattery().then(function(battery){battery.addEventListener('levelchange',function(){//Dostuffwhenth

  6. javascript - 将 PCM 音频从 44100 下采样到 8000 - 2

    我从事音频识别演示已有一段时间了,api需要我传递采样率为8000或16000的.wav文件,所以我必须对其进行下采样。我尝试了以下两种算法。虽然他们都没有像我希望的那样解决问题,但结果存在一些差异,我希望这会使它更清楚。这是我的第一次尝试,当sampleRate%outputSampleRate=0时效果很好,但是当outputSampleRate=8000或1600时,结果音频文件是silent(表示输出数组的每个元素的值为0):functioninterleave(inputL){varcompression=sampleRate/outputSampleRate;varleng

  7. uniapp系列-改变底部安全区-顶部的手机信号、时间、电池栏颜色样式 - 2

    uniapp的默认安全区域的颜色是白色,如果我们做了沉浸式页面,背景色也是白色的话,就会看不到电池栏,等的颜色,如何修改呢?首先来说底部安全区域下图是底部安全区原始状态,感觉和整个页面格格不入修改代码配置safeareamanifest.json(下面代码仅支持ios)//在app-plus下配置:"safearea":{//安全区域配置,仅iOS平台生效"background":"#F5F6F9",//安全区域外的背景颜色,默认值为"#FFFFFF""bottom":{//底部安全区域配置"offset":"none|auto"//底部安全区域偏移,"none"表示不空出安全区域,"auto

  8. windows - 如何获得音频驱动程序的 native 采样率 (Windows) - 2

    我有一个简单的样本混合器,在分析时我注意到大约40-50%的时间花在了重新采样上(44.1=>48kHz,他们必须做一些比lerp更复杂的事情)。当我在48kHz模式下打开播放设备(在我的例子中是DSound)时,这一步就没有了。问题是:有没有办法查询音频驱动程序的默认(native)采样率以避免重采样?我尝试搜索网络/文档但一无所获,我认为这可能是一个简单的API调用。谢谢。 最佳答案 如果您仍然好奇,我也有同样的问题,但找不到答案。有人为我指出了正确的方向,我能够获得一个用于获取播放属性的工作代码示例(Win>=Vista)。您

  9. windows - 如何在 Batch 中获取电池百分比? - 2

    所以,我只想知道如何在Batch中获取电池百分比。我觉得如果格式是这样就好了::foreverget-batteryif"%battery%"=="100%"gotoreached100gotoforever:reached100echoYourbatteryhasfinishedcharging!gotoforever 最佳答案 scientist_7的答案应该被标记为正确。当然,没有法律禁止从批处理中调用powershell。powershell-command"(Get-WmiObjectWin32_Battery).Esti

  10. windows - 用于检查电池状态变量并在 if 语句中使用它的脚本,是否有任何脚本可以使 .bat 文件每分钟作为隐藏进程运行 - 2

    目前,我正在尝试创建一个脚本文件,如果它检测到笔记本电脑正在使用电池而不是交流电运行,它将启动其他程序。前提是我使用的是Windows8.1。我创建了一个.bat文件并输入了以下脚本:@ECHOOFFREMToCheckthebatterystatus,providingthat2isconnectedtotheACWMICPathWin32_BatteryGetBatteryStatusREMCheckthecontentofbatterystatusvariableIFNOT"%BatteryStatus%"=="2"(echolaptopstartedtouseitsbatter

随机推荐