跳转至

02 ADC

作者:王城钧 | 最后修改:2026-04-13

一、模数转换(ADC)概述

1.1 ADC 简介

ADC 指模拟/数字转换器,是指将连续变量的模拟信号转换为离散的数字信号的器件。 合宙 硬件产品中的 ADC 接口主要用来检测模拟电压信号量,用于电池电压检测,温湿度检测,TDS 检测等应用。

1.2 ADC 接口介绍

  1. Air8000 内部 ADC 接口精度为 12bits
  2. Air8000 内部具有 4 个 ADC 接口,ADC0 ~ ADC3
  3. 两个特殊通道:

CPU 内部温度 Temp : -- adc.CH_CPU

主供电脚电压 VBAT : -- adc.CH_VBAT

1.3 输入电压的范围

注意:外接输入电压切记不可超过量程,否则有烧毁风险!!!

设置分压(adc.setRange)要在 adc.open 之前设置,否则无效!!

关于 ADC 的常见指标有两个:

一个是电压输入范围,一个是分辨率;

Air8000 内部 ADC 接口精度为 12bits

特别注意!

合宙主流模组软件上对ADC都可以配置ADC_RANGE_MAX和ADC_RANGE_MIN两种量程

Air8000adc.ADC_RANGE_MIN对应量程为0-1.5V
adc.ADC_RANGE_MAX量程对应0-3.3V

1.当被测电压最大值在1.5V以内,使用adc.ADC_RANGE_MIN
2.当被测电压大于1.5V小于3.3V,使用adc.ADC_RANGE_MAX,且不添加外部分压电路;
3.当被测量电压最高值在3.3v以上时, 使用ADC_RANGE_MIN,且必须添加外部分压电路;

测量电压相关的函数主要有两个,请务必注意二者的区别:

adc.read(id)

功能

读取 adc 通道计算值

参数

id

参数含义:通道id,通常从0开始;
数据类型:number
取值范围:参考第三章合宙模组的adc id的一些说明
是否必选:必须传入此参数;
注意事项:暂无;
参数示例:0123adc.CH_CPUadc.CH_VBAT

返回值

local data0,data1 = adc.read(id)

data0

含义说明:adc的一个返回值,没有意义;
数据类型:number
取值范围:当量程设置为adc.ADC_RANGE_MIN,最大可获取到4096
         当量程设置为adc.ADC_RANGE_MAX,最大可获取到4096
注意事项:暂无;
返回示例:暂无;

data1

含义说明:adc的计算值,单位是mV
数据类型:number
取值范围:当量程设置为adc.ADC_RANGE_MIN,最大可获取到1500mv
         当量程设置为adc.ADC_RANGE_MAX,最大可测量到3600mv8101对应2400mv);
注意事项:暂无;
返回示例:暂无;

示例

local function adc_task()
     while true do
         sys.wait(1000)--延时1S
         adc.setRange(adc.ADC_RANGE_MIN)--设置ADC引脚的测量范围0-1.5V,这种方式被测电压可以经过外部电阻分压后再挂在ADC上;
         adc.open(0)--打开ADC通道1
         local data0,data1 = adc.read(0)--获取adc的原始值和计算值,将获取到的值赋给data0,data1
         adc.close(0)--关闭ADC通道1
         log.info("adc通道0", data0,data1)--打印adc计算值
     end
 end

sys.taskInit(adc_task)

adc.get(id)

功能

获取 adc 计算值

参数

id

参数含义:通道id,从0开始;
数据类型:number
取值范围:参考第三章合宙模组的adc id的一些说明
是否必选:必须传入此参数;
注意事项:暂无;
参数示例:0123adc.CH_CPUadc.CH_VBAT

返回值

local data = adc.get(id)

data

含义说明:adc的计算值,单位是mV,  如果是adc.CH_CPU通道会返回温度值,单位千分之一摄氏度,
         若读取失败,会返回-1
数据类型:number
取值范围:当量程设置为adc.ADC_RANGE_MIN,最大可获取到1500mv          
         当量程设置为adc.ADC_RANGE_MAX,最大可测量到3600mv8101对应2400mv)
         当量程设置为adc.ADC_RANGE_MAX,最大可测量到3600mv8101对应2400mv);
注意事项:暂无;
返回示例:暂无;

示例

local function adc_task()
     while true do
         sys.wait(1000)--延时1S
         adc.setRange(adc.ADC_RANGE_MIN)--设置ADC引脚的测量范围0-1.5V,这种方式被测电压可以经过外部电阻分压后再挂在ADC上;
         adc.open(0)--打开ADC通道1
         local data = adc.get(0)--获取adc计算值,将获取到的值赋给data
         adc.close(0)--关闭ADC通道1
         log.info("adc读出值", data)--打印adc计算值
     end
 end

sys.taskInit(adc_task)

二、演示功能概述

本章节演示了 Air8000 内部 4 个 普通 ADC 接口:ADC0,ADC1,ADC2,ADC3

以及 2 个特殊通道:CPU 内部温度 Temp -- adc.CH_CPU 主供电脚电压 VBAT -- adc.CH_VBAT 的使用教程。

1.通过 adc.get(id)读取 4 个普通 ADC 接口的外部输入模拟电压

注意:启用分压后可超量程测电压,但不建议使用避免烧坏,
     若想测量更高的外部电源电压,需要外接分压电阻,
     具体设计请参考[ADC接口设计指导](https://docs.openluat.com/air8000/luatos/hardware/design/adcledi2cspiusb/);本教程外部输入电压在3.3v以内
     需要外部电源与模组共地,保持参考电压一致

2.通过 adc.get(adc.CH_VBAT)读取 VBAT 电压

3.通过 adc.get(adc.CH_CPU)读取 CPU 温度

三、准备硬件环境

Air8000 整机开发板 + 两个 Air9000P(直流电源)+ 杜邦线 +Type-c 数据线

三根 Type-c 数据线分别给两个 Air9000P 和 Air8000 开发板

一个 Air9000P 输出 1.2V 电压通过鳄鱼夹和杜邦线连接到 Air8000 开发板 ADC0 和 ADC1 通道

另一个 Air9000P 输出 3.3V 电压通过鳄鱼夹和杜邦线连接到 Air8000 开发板 ADC2 和 ADC3 通道

两个 Air9000P 和开发板通过鳄鱼夹和杜邦线共地

3.1 直流稳压电源

合宙功耗分析仪 Air9000P,淘宝购买链接:Air9000P 淘宝购买链接

四、软件环境

在开始实践本示例之前,先筹备一下软件环境:

1.Luatools 工具

2.内核固件文件(底层 core 固件文件)

本demo开发测试时使用的固件为 LuatOS-SoC_V2014_Air8000_2.soc,本demo对固件版本没有什么特殊要求,所以你如果要测试本demo时,可以直接使用最新版本的内核固件;如果发现最新版本的内核固件测试有问题,可以使用我们开发本demo时使用的内核固件版本来对比测试;

3.luatos 需要的脚本和资源文件

脚本和资源文件右键点我

lib 脚本文件:使用 Luatools 烧录时,勾选 添加默认 lib 选项,使用默认 lib 脚本文件;

准备好软件环境之后,接下来查看如何烧录项目文件到 Air8000 开发板中,将本篇文章中演示使用的项目文件烧录到 Air8000 开发板中。

五、模数转换(ADC)软硬件资料

5.1 API 接口介绍

本教程使用 api 接口为:adc - 模数转换 - LuatOS 文档

5.2 ADC 硬件设计

ADC 硬件设计参考:adc - 硬件设计

六、代码示例介绍

6.1 读取 adc 外部输入模拟电压

硬件连接如第三章所示

6.1.1 代码介绍

首先设置量程,然后打开 ADC 通道 0,进行采样循环,将获取到的输入模拟电压值存入数组,接下来关闭 ADC 通道 0,最后进行数据处理、打印。

adc.setRange(adc.ADC_RANGE_MIN)  
-- 设置ADC量程,注意量程设置一定要在adc.open()之前,通道0的量程设置为了adc.ADC_RANGE_MIN
-- 此演示对通道0外加了1.2V电压,这样设置与通道1形成对比,方便观察1.2V外部供电下adc.ADC_RANGE_MAX和adc.ADC_RANGE_MIN两种量程的测量精准度。
adc.open(0)
for _ = 1, num_samples do
    table.insert(samples[0], adc.get(0))
end
adc.close(0)
process_channel(samples[0], "adc通道0")

6.1.2 运行结果展示

如下是 adc 通道 0 设置 ADC_RANGE_MIN 量程,外部供电 1.2V,

adc 通道 1 设置 ADC_RANGE_MAX 量程,外部供电 1.2V,

adc 通道 2 设置 ADC_RANGE_MAX 量程,外部供电 3.3V,

adc 通道 3 设置 ADC_RANGE_MIN 量程,外部供电 3.3V 的环境下测试的,代码运行结果如下:

这样设置量程和外部供电是为了更直观地观察两种量程下不同供电电压对精准度的影响,可以看到如下测量的数据是符合预期的

对于 Air8000:

ADC0 通道,设置的为 ADC_RANGE_MIN 量程,外部供电 1.2V,可以看到读取到的电压换算过后为 1.19488V。

ADC1 通道,设置的为 ADC_RANGE_MAX 量程,外部供电 1.2V,可以看到读取到的电压换算过后为 1.189V。

对比 ADC0 和 ADC1 通道可以发现,测量 1.2V 电压,ADC_RANGE_MIN 量程更精准一些。

ADC2 通道,设置的为 ADC_RANGE_MAX 量程,外部供电 3.3V,可以看到读取到的电压换算过后为 3.37675V。

ADC3 通道,设置的为 ADC_RANGE_MIN 量程,外部供电 3.3V,可以看到读取到的电压换算过后为 1.565V。

对比 ADC2 通道和 ADC3 通道可以发现,测量 3.3V 电压,ADC_RANGE_MAX 量程更准确

而 ADC_RANGE_MIN 量程受量程上限约束最大可测量到 1.565V

[2025-09-10 16:13:57.926][000000517.170] I/user.adc通道0 处理值: 1194.88 mV (样本数:10)

[2025-09-10 16:13:57.946][000000517.174] I/user.adc通道1 处理值: 1189.00 mV (样本数:10)

[2025-09-10 16:13:57.967][000000517.178] I/user.adc通道2 处理值: 3376.75 mV (样本数:10)

[2025-09-10 16:13:57.992][000000517.182] I/user.adc通道3 处理值: 1565.00 mV (样本数:10)

[2025-09-10 16:13:58.016][000000517.187] I/user.CPU TEMP 温度值: 34.00 (样本数:10)

[2025-09-10 16:13:58.042][000000517.191] I/user.VBAT 处理值: 4414.75 mV (样本数:10)

6.2 读取供电电压

硬件连接如第三章所示

6.2.1 代码介绍

-- VBAT通道采集处理
adc.open(adc.CH_VBAT)
for _ = 1, num_samples do
    table.insert(samples[5], adc.get(adc.CH_VBAT))
end
adc.close(adc.CH_VBAT)
process_channel(samples[5], "VBAT")

6.2.2 运行结果展示

通过下图可以看到,VBAT 端供电电压平均为 3.74V

6.3 读取 CPU 温度

6.3.1 代码介绍

-- CPU温度通道采集处理
adc.open(adc.CH_CPU)
for _ = 1, num_samples do
    table.insert(samples[4], adc.get(adc.CH_CPU))
end
adc.close(adc.CH_CPU)
process_channel(samples[4], "CPU TEMP")

6.3.2 运行结果展示

通过下图可以看到,模组 CPU 平均温度为 32.5 摄氏度

6.4 数据处理函数

当通道样本数大于 2 时,先对样本升序排序并剔除首尾极值,计算剩余样本的平均值,最后根据标签(如 CPU 温度或电压)格式化输出带单位的处理值及总样本数,否则记录样本不足。

local function process_channel(channel_samples, tag)
    if #channel_samples > 2 then
        -- 升序排序
        table.sort(channel_samples)

        -- 创建剔除极值后的新数组
        local trimmed = {}
        for i = 2, #channel_samples-1 do
            table.insert(trimmed, channel_samples[i])
        end

        -- 计算平均值
        local sum = 0
        for _, v in ipairs(trimmed) do
            sum = sum + v
        end
        local avg_value = sum / #trimmed

        -- 根据tag进行不同处理  
        if tag == "CPU TEMP" then  
            log.info(tag, string.format("温度值: %.2f ℃(样本数:%d)", avg_value/1000, #trimmed+2))  
        else  
            log.info(tag, string.format("处理值: %.2f mV (样本数:%d)", avg_value, #trimmed+2)) 
        end  

        -- -- 格式化输出
        -- log.info(tag, string.format("处理值: %.2f mV (样本数:%d)", avg_value, #trimmed+2))
    else
        log.info(tag, "样本不足无法处理")
    end
end

七、总结

本教程简单举例了如何读取 adc 输入电压、读取供电电压、读取 CPU 温度,除此之外,adc 可以将各种连续变化的模拟信号(如温度、湿度、压力、电压、电流等)转换为离散的数字信号,本模块内部 ADC 精度 12bits,对许多应用已经足够,如温湿度传感器、压力传感器、音频信号处理等,然而对于需要更高精度的应用,可以外挂更高精度的 ADC,如 16 位,24 位等。使用过程中需注意量程范围,不可超出量程。

常见问题

  1. ADC 测量电压来回跳变,为什么?

看外部输入电压是否与模块共地,正常情况需要共地,保持参考电压一致。

  1. adc.read()和 adc.get()要用哪一个?

使用 adc.get(),adc.read()不使用。 adc.get()返回的就是计算值,单位是 mV,推荐使用这个接口来获取电压值。 adc.read()的第一个返回值,没有意义,不做解释,第二个返回值与 adc.get()的返回值一样为计算值,故推荐使用 adc.get()。