ADC- 模数转换

ADC 原理和常见用法

模拟信号指在时域上数学形式为连续的信号,不同时间点位置的信号值是连续变化的。所传达的信息是连续变化的物理量,如温度、湿度、压力、长度、电流等等。不同的数据必须转换为相应的信号才能进行传输。
数字信号指自变量和因变量都是离散的信号,可以理解为在时域和幅值上都是离散的,不连续的。数字信号在计算机中以二进制的方式进行存储。

以一个工作电压为3.3V的处理器为例,其工作过程中始终伴随着要么是0V(逻辑0),要么是3.3V(逻辑1)的矩形波电信号。物理世界中一个电压为1V的电信号,是不能直接被CPU识别到的,其必须由一个叫做ADC的器件,将其转换为数字信号后,方能被CPU识别。
ADC全称Analog-to-Digital Converter,即模拟至数字信号转换器,作用是将物理世界中计算机无法直接识别的模拟信号,转换为能够识别的二进制数字信号。
ADC的基本工作原理是采样和量化。首先,ADC对模拟信号进行采样,即在一定的时间间隔内测量模拟信号的值。然后,采样的模拟信号被量化,即将连续的模拟信号离散化为一系列离散的数字值。这些数字值通常使用二进制表示。

ADC的分辨率表示其能够量化模拟信号的精度,它是指ADC能够表示的离散数值的数量。分辨率通常以位(bit)为单位表示,例如8位、10位、12位等。因此,一个8位的ADC可以将输入范围分成2的8次方(256)个不同的离散级别,而一个12位的ADC可以将输入范围分成2的12次方(4096)个离散级别。较高的分辨率意味着ADC可以更精确地量化模拟信号。
ADC的采样率是指在一秒钟内进行采样的次数。采样率越高,得到的离散数字信号对真实模拟信号波形的还原度越高。根据奈奎斯特采样定理,采样率至少是待测量信号最大频率的2倍,才可能保证采样后的波形不失真。在实际应用中,采样频率往往高达待采样信号最大频率的10倍甚至更大。

ADC的输入电压范围指的是它能够接受的模拟信号的电压范围。它通常以电压表示,例如0V至5V或-5V至+5V。确保输入信号不超过ADC的输入范围,以避免信号失真或损坏。
ADC的触发方式通常分软件触发和硬件触发,软件触发当设置开始转换时ADC会立即启动转换,硬件触发则需要等待指定的外部事件发生时ADC才会启动转换。ADC广泛应用于各种领域,如数据采集、传感器接口、音频处理、通信系统等。它们是将模拟信号转换为数字信号的关键组件,使得模拟信号能够与数字系统进行交互和处理。

各平台ADC说明

ADC通常位于PMU上,位于Analog IP中,PMU的典型结构图如下:

各平台ADC差异如下表:

平台 支持路数 位宽 输入电压范围(单位:V)
EC600N 1 10 0~1.3
EC600M 2 12 0~1.2
EC800N 1 10 0~1.8
EC600U 4 12 0~VBAT
EC200U 3 12 0~VBAT_BB
EC200A 2 12 0~VBAT_BB
BG95 1 16 0~1.8
EG915U 2 12 0~VBAT
EC800M 2 12 0~1.2
EG912N 2 12 0~VBAT_BB
EC600E 2 12 0~1.2
EC800E 2 12 0~1.2
  • VBAT:模组电源电压。
  • VBAT_BB:模块基带电源和射频电源。
  • VBAT没有供电时,ADC管脚不要接入电压。

ADC API说明

  • 输入电压不要超过允许电压范围的上限,超过上限的需要先进行分压。
  • 参考电压Vref由模组内部产生,无需额外提供。

ADC 创建对象

from misc import ADC
adc = ADC()

创建一个ADC对象,使用ADC功能需要先创建对象。

ADC功能初始化

adc.open()

创建对象后调用该接口初始化。

读取指定通道的电压值

adc.read(ADCn)

读取 ADC 调用该接口;读取指定通道的电压值,调用该接口首先触发 ADC 采集,转换结束后读取转换结果,然后转换为电压值返回。
ADCn 指ADC通道,例如 adc.read(ADC.ADC0) 读取 ADC 通道 0 电压值,返回值单位为 mV

  • 返回的电压值是模组管脚的电压值,部分型号模组内部有分压电阻,返回的是内部分压前的管脚的电压值。
  • ADC管脚悬空时,读取的电压可能是随机值,建议不要悬空。
  • 读取接口比较耗时,避免频繁调用。

关闭ADC

adc.close()

不使用时关闭ADC。

应用场景

电池电压采集与充电检测

准备材料:电池、EC600U模组、ME4055A。

原理:

ME4055A 是一款专用的电池充电芯片,专为单个电池提供恒流或恒压线性充电,其封装如下图所示。

CHRG 脚是充电状态指示脚,当电池正在充电中时,CHRG 脚被内部电路拉低,否则 CHRG 脚处于高阻态。

BAT 管脚是电池连接管脚,把电池的正极接到 BAT 管脚,BAT 脚输出的电压恒定为 4.2V。

VCC 是电源输入脚,为 ME4055A 提供电源。

STDBY 是充电结束指示脚,当充电结束时,STDBY 被内部电路拉低,否则STDBY是高阻态;模组通过监控CHRG和STDBY管脚的电平高低,来判断充电状态。

PROG 脚是充电电流检设置和检测脚,PROG 脚通过一个电阻 RPROG 连接到地,那么充电电流 IBAT=(VPROG/RPROG)*1100,其中在预充电模式 VPROG=0.1V,在恒流充电模式,VPROG=1V。

检测电池电压的原理是采用电阻分压的方法,即硬件上用两个电阻把电池电压进行比例分压,把分压后的电压送到模组ADC管脚进行检测,软件根据模组ADC读取的电压值经过换算计算出电池的电压。

连接硬件:如下图所示,ME4055A的CHRG和STDBY分别接模组的GPIO,BAT接电池正极,VCC接电源,通过把电池电压分压,接入到模组的ADC管脚,以测量电池电压。

点此链接下载 battery.pybattery.py 实现了电池电压采集、充电状态采集、充电事件上报、电池电量计算功能。其中充电状态检测和电池电压采集代码如下:

充电状态检测的脚本:通过读ME4055A的CHRG和STDBY脚的状态进行充电状态判断。

def __update_charge_status(self):
    """Update Charge status by gpio status"""
    if not self.__usb:
        chrg_level = self.__chrg_gpio.read()
        stdby_level = self.__stdby_gpio.read()
        if chrg_level == 1 and stdby_level == 1:
            # Not charge.
            self.__charge_status = 0
        elif chrg_level == 0 and stdby_level == 1:
            # Charging.
            self.__charge_status = 1
        elif chrg_level == 1 and stdby_level == 0:
            # Charge over.
            self.__charge_status = 2
        else:
            raise TypeError("CHRG and STDBY cannot be 0 at the same time!")
    else:
        self.__usb_charge()

def charge_status(self):
    """Get charge status
    Returns:
        0 - Not charged
        1 - Charging
        2 - Finished charging
    """
    self.__update_charge_status()
    return self.__charge_status

电池电压采集:通过间隔指定的时间多次采样,然后去掉最大值和最小值后取平均的方法作为返回数据。

def __get_adc_vbatt(self):
    """Get vbatt from adc"""
    self.__adc.open()
    utime.sleep_ms(self.__adc_period)
    adc_list = list()
    for i in range(self.__adc_period):
        adc_list.append(self.__adc.read(self.__adc_num))
        utime.sleep_ms(self.__adc_period)
    adc_list.remove(min(adc_list))
    adc_list.remove(max(adc_list))
    adc_value = int(sum(adc_list) / len(adc_list))
    self.__adc.close()
    vbatt_value = adc_value * (self.__factor + 1)
    return vbatt_value

下载 battery.py 到模组的 usr 分区后,可以通过 from usr.battery import Battery 来导入 Battery 类;创建一个 Battery 类对象,我们就可以调用相关方法。usr 分区是 Python 虚拟机文件系统的一个分区,可以存放用户的脚本,如下图:

battery.py 的 API 介绍如下。

点此查看 API 介绍详情。

实例化对象

示例:

from usr.battery import Battery
adc_args = (adc_num, adc_period, factor)
chrg_gpion = 0
stdby_gpion = 1
battery = Battery(adc_args=adc_args, chrg_gpion=chrg_gpion, stdby_gpion=stdby_gpion)

adc_num:adc通道,adc_period:adc循环读取次数,factor:计算系数;chrg_gpion:连接CHRG的gpio;stdby_gpion:连接STDBY的gpio。

设置充电事件回调函数

示例:

def charge_callback(charge_status):
    print(charge_status)
res = battery.set_charge_callback(charge_callback)

回调参数charge_status:0-未充电;1-充电中;2-充电完成。

设置电池当前温度

示例:

res = battery.set_temp(20)

设置温度为20℃。

查询电池电压

示例:

battery.voltage

返回值单位mV。

查询电池电量

res = battery.energy

返回值表示百分比,0~100。

查询充电状态

battery.charge_status

返回0:未充电,1:充电中,2:充电完成。

应用示例:

from battery import Battery

# instantiation object
adc_args = (adc_num, adc_period, factor)
chrg_gpion = 0
stdby_gpion = 1
battery = Battery(adc_args=adc_args, chrg_gpion=chrg_gpion, stdby_gpion=stdby_gpion)

def charge_callback(charge_status):
    print(charge_status)

# set charge status callback 
battery.set_charge_callback(charge_callback)
# True

# set current temp
temp = 30
battery.set_temp(temp)
# True

# get battery volt
battery.voltage
# 3000

# get battery soc
battery.energy
# 100

# get charge status
battery.charge_status
# 1

ADC功能实现按键检测

在IO资源紧张时可以用ADC的方式实现一个ADC管脚实现多按键识别。

准备材料:EC600U模组,相关硬件电路

原理:

根据不同按键按下ADC口的电压不同来判断对应按键按下。

如下图所示,当R3上端接的电压为1V时,当没有按键按下时,测试点的电压为1V。

K1按下时,测试点的电压为0.75V。

K2按下时,测试点的电压为0.5V。

K3按下时,测试点的电压为0V。

软件可以周期的读取ADC管脚电压值,并据此进行判断哪个按键按下。

实际应用时可以调整上拉电压值和阻值、开关个数。

连接硬件:如下图所示电路,由K1、K2、K3三个按键和三个电阻组成,图中的ADC port连接模组的ADC0管脚。

为提高 ADC 电压测量的准确度,ADC 接口在布线时需做包地处理。

示例代码:

from misc import ADC
import utime
adc = ADC()
adc.open()
ADC_VALUE_1 = 100
ADC_VALUE_2 = 600
ADC_VALUE_3 = 900
while True:
    # Read ADC result
    adc_value = adc.read(ADC.ADC0)
    # Detect pressed key based on ADC result
    if adc_value < ADC_VALUE_1:
        # key3 pressed
        print("K3 is now pressed")
    elif adc_value < ADC_VALUE_2:
        # key2 pressed
        print("K2 is now pressed")
    elif adc_value < ADC_VALUE_3:
        # key1 pressed
        print("K1 is now pressed")
    else:
        # no key pressed
        print("no key is now pressed") 
    utime.sleep_ms(20)

光敏传感器

准备材料:EC600U模组,光敏电阻(如GL5528)

原理:光敏电阻的电阻值随着光照强度的变化而变化。当光敏电阻受到光照时,其电阻值下降;当光照减弱或完全没有光照时,其电阻值增加,从而影响测试点的电压值。通过读取ADC电压值来计算出光敏电阻的阻值,再根据光敏电阻的光照强度和阻值关系可以知道光照的强弱。

硬件连接:如下图进行连接。R2为光敏电阻,R1为10K,根据具体光敏电阻参数选择R1的值,ADC port为测试点,连接到模组的ADC管脚。

本例程通过打印ADC测量的电压值和光敏电阻的阻值来观察不同光亮对光敏电阻的影响。

from misc import ADC
import utime 
import _thread

# unit as kΩ
def Voltage_to_Resistance(Volt):
    resistance = (10 * Volt)/(1000 - Volt)
    return resistance

def Photoresistor_thread(delay, retryCount):
    	# creat a adc device
    	AdcDevice = ADC()
    	while retryCount:
            retryCount = retryCount - 1
            # get ADC.ADC0 value
            adcvalue = AdcDevice.read(ADC.ADC0)
            print("get ADC.ADC0 Voltage value as {0}mv".format(adcvalue))
            # Converted to resistance
            resistance = Voltage_to_Resistance(adcvalue)
            print("Photoresistor resistance as {0}Ω".format(resistance * 1000))
            utime.sleep(delay)
    	pass
    	
if __name__ == "__main__":
    	# creat a thread Convert ADC to Voltage
    	_thread.start_new_thread(Photoresistor_thread, (1, 10))
    	print("main thread has exit")

常见问题

ADC 电压范围

  1. 每个 ADC 接口引脚的输入电压不能超过其允许的电压范围,当被测电压大于输入电压范围时,先进行电阻分压缩小后再接入模组ADC管脚;
  2. 在模块 VBAT 不供电的情况下,为了避免损坏模组,ADC 接口不允许直接输入任何输入电压。

ADC位宽

ADC位宽指ADC转换结果以多少位数据表示,位宽越大分辨率越高,采集越精准,例如1V电压经过10位位宽的ADC转换,每一位表示1/1024V;模组ADC返回的是处理后的电压值。

模组是否带充电功能

模组目前不支持充电功能。

如何检测电池电压

采用电阻分压的方法,测量分压后的电压值。如果要获取模组自身的VBAT电压,可以调用Power.getVbatt()接口,返回的单位mV。