Skip to content

概述

NTC(Negative Temperature Coefficient,负温度系数)热敏电阻是一种电阻值随温度变化的电阻:温度升高,电阻变小。它价格便宜、皮实耐用,是家电测温(空调、热水器)最常见的方案。本教程用 Ai-WB2 开发板的 ADC(模数转换:把引脚上的模拟电压变成数字值)采集 NTC 分压电路(电阻串联分压,用欧姆定律 U=I×R 反推电阻)的电压,再用查表法(对照厂商给的"电阻-温度对照表")换算成温度,串口实时打印,并走完 接线 → 代码编写 → 编译 → 烧录(把编译好的程序写进开发板芯片)→ 运行验证 的完整流程。

用大白话讲:NTC 就像一扇随温度变化的"水龙头"——温度越高,"水管"越粗(电阻越小)。把它和一个固定电阻(10kΩ)串起来接在电源上,两个电阻之间的电压就会随温度变化(这叫分压)。开发板用 ADC"量电压"(把电压变成数字),反推 NTC 的电阻,再对照厂商的"电阻-温度对照表"查出温度。本示例还多量了一路"参考电压"(电源电压的一半),这样电源电压波动时也能算准。

本教程基于安信可官方 SDKAi-Thinker-Open/Ai-Thinker-WB2,版本 release_bl_iot_sdk_1.6.40)的官方示例 applications/iot-solution/demo_ntc 编写,代码可在本地 SDK 中直接找到。

🎯本页目标用 ADC 采集 NTC 分压电压,反推电阻并查表得到温度,串口每秒打印电压、电阻与温度,理解分压电路与查表法。
🧰前置条件① Ai-WB2 开发板 + NTC 热敏电阻(10kΩ @25°C)+ 3 个 10kΩ 电阻 ② 已按 [SDK 安装](../sdk/sdk_intro) 完成环境搭建,并完成 [GPIO输出(点亮LED)](../basic/gpio_led)。
🔗相关章节ADC 基础原理见 [ADC 模数转换](../basic/adc);其他温度传感器见 [DS18B20 温度传感器](./ds18b20)。

硬件接线

本示例需要搭一个分压电路(两个电阻串联,中间抽头取电压),按官方原理图(见 SDK applications/iot-solution/demo_ntc/img/schematic.jpg)接线:

Ai-WB2 引脚 电路连接
IO11 NTC 热敏电阻与 R1(10kΩ) 的连接点(NTC 分压采样)
IO12 R2(10kΩ)R3(10kΩ) 的连接点(参考电压采样)
3V3 NTC 另一端、R2 另一端
GND R1 另一端、R3 另一端

电路关系:

3V3 ──┬── NTC(10kΩ@25°C) ──┬── R1(10kΩ) ── GND
      │                     │
      │                     └── IO11(采样点 A)
      │
      ├── R2(10kΩ) ──┬── R3(10kΩ) ── GND
                     │
                     └── IO12(采样点 B,参考电压 = VCC 的一半)

💡 采样点 A 的电压 = VCC × R1 ÷ (R1 + R_NTC),温度变 → R_NTC 变 → 电压变,ADC 量出电压就能反推温度。 💡 采样点 B 专门用来测电源电压的一半(VCC/2),电源电压波动时用它做参考,算出来的电阻才准。 💡 NTC 标称 10kΩ @25°C(25°C 时电阻 10kΩ),与代码里 R1/R2/R3 = 10000 配套;若用其他阻值的热敏电阻需要更换对应阻值的固定电阻并换表格。

进入示例工程

本教程直接使用官方 SDK 自带的 demo_ntc 示例工程,打开终端进入该工程目录:

cd ~/Ai-Thinker-WB2/applications/iot-solution/demo_ntc

说明:cd 是「进入目录」命令,这里进入 NTC 示例工程目录;后续的 make 编译、make flash 烧录命令都必须先在这个目录里执行。

工程目录结构说明:

文件 作用
demo_ntc/main.c 主程序源码(含电阻-温度对照表),本教程主要查看的文件
Makefile 编译入口,一般无需修改
proj_config.mk 工程配置(Flash 大小、功能开关等),一般无需修改
编写代码

打开 demo_ntc/main.c,本步完整代码已移至文末,见:

📜 完整代码 — 位于本页「完整代码」章节,默认折叠,点击展开,与官方示例(applications/iot-solution/demo_ntc/demo_ntc/main.c)完全一致。

代码要点:

代码 作用
ADC_NTC_PIN 11ADC_REF_PIN 12 两个 ADC 采样引脚:IO11 采 NTC 分压点、IO12 采参考电压,与接线表一致,接错就测不到
R1/R2/R3 = 10000 三个 10kΩ 固定电阻(分压 + 参考电压),阻值与接线用的电阻必须一致,否则温度全错
.mode = HOSAL_ADC_ONE_SHOT 单次采样模式:每次循环手动读一次电压,不用连续采样浪费资源
hosal_adc_add_channel(&adc0, bl_adc_get_channel_by_gpio(...)) 把引脚转换成 ADC 通道号并注册,不注册就采不到这个引脚的电压
hosal_adc_value_get(...) 读取指定通道电压(单位 mV),读不到就算不出电阻和温度
r_ntc = 20000u*v_ref/v_ntc - 10000 用分压公式(欧姆定律)反推 NTC 当前电阻,公式错温度全错
binary_search(rt_table, size, r_ntc * 10) 在电阻-温度对照表中二分查找(每次砍半查找,比逐个比较快)最接近的档位
temperature = index - 40 对照表下标从 -40°C 开始计数,下标减 40 就是实际温度(°C)
编译工程

在工程目录执行编译:

make -j8

说明:make 是「编译工程」命令,把代码变成开发板能运行的固件(程序文件);-j8 表示用 8 个核并行编译,速度更快。

编译成功后生成固件 build_out/demo_ntc.bin

⚠️ 若提示 riscv64-unknown-elf-gcc: command not found,说明工具链权限未配置,先执行 cd toolchain/riscv/Linux && . chmod755.sh 再重新编译。

烧录固件

开发板保持 USB 连接,确认串口设备号(Linux 下通常为 /dev/ttyUSB0),执行烧录:

make flash p=/dev/ttyUSB0 b=921600

说明:make flash 是「烧录」命令,把编译好的固件写进开发板芯片。p= 后面是串口设备号(要改成你电脑上实际的串口,可用 ls /dev/ttyUSB* 查看),b= 是烧录波特率(传输速度)。

⏳ 烧录过程中按提示长按开发板 EN 键进入下载模式(部分开发板自动进入),等待进度条完成即烧录成功。 Windows 平台烧录方法见 Windows 平台快速开始

运行验证

烧录完成后开发板自动重启运行,打开串口助手(波特率 921600,波特率 = 串口传输速度,收发两端必须设成一样),每 1 秒打印一次电压、电阻与温度:

V_REF = 1650 mV	V_NTC = 1650 mV	R_NTC = 10000 ohm	Temperature = 25 C
V_REF = 1650 mV	V_NTC = 1620 mV	R_NTC = 10400 ohm	Temperature = 24 C
...

室温(约 25°C)时 V_NTC 应接近 V_REFR_NTC 接近 10000 ohm、温度接近室温。用手指捏住热敏电阻,温度应缓慢上升(电阻变小);松开后回落。

看到串口每秒打印 V_REF/V_NTC/R_NTC/Temperature 四项、且温度随触摸变化即为成功;如果温度恒为 -40°C 或 125°C(查表越界)或数值乱码,说明还没成功,对照文末「常见问题与踩坑提示」排查。

💡 官方 README 附有完整的电阻-温度对照数据(img/RT.csv),可与打印的 R_NTC 对照验证。

代码执行流程

例程从启动到运行的完整流程如下(图中的循环箭头表示反复执行):


本文 API 汇总

hosal_adc_init(adc)

dev 结构体中的引脚、采样模式、采样频率初始化 ADC 控制器(本教程单次采样模式采集两路电压)。

参数

  • adchosal_adc_dev_t 结构体指针,必填。关键字段:config.mode(可选值:HOSAL_ADC_ONE_SHOT 单次采样 / HOSAL_ADC_CONTINUOUS 连续采样)、config.pin(采样引脚号,本教程 ADC_NTC_PIN=11)、config.sampling_freq(采样频率 Hz,本教程 340)、port(ADC 端口,本教程 0

返回值:成功返回 0;失败返回负值错误码

hosal_adc_add_channel(adc, channel)

把一个通道注册到 ADC 接口,之后才能通过 hosal_adc_value_get 采样该通道。

参数

  • adchosal_adc_dev_t 结构体指针
  • channel:ADC 通道号(由 bl_adc_get_channel_by_gpio 从引脚换算得到)

返回值:成功返回 0;失败返回负值错误码

bl_adc_get_channel_by_gpio(gpio_num)

把 GPIO 引脚号换算成对应的 ADC 通道号(引脚与通道不是同一个编号,必须经过换算)。

参数

  • gpio_num:引脚号,可选值:支持 ADC 功能的引脚(本教程 1112

返回值:对应 ADC 通道号(int);引脚不支持 ADC 时返回负值。声明于 components/platform/hosal/bl602_hal/bl_adc.h

hosal_adc_value_get(adc, channel, timeout)

对指定通道采样一次并返回电压值(单位 mV)。

参数

  • adchosal_adc_dev_t 结构体指针
  • channel:ADC 通道号(同 hosal_adc_add_channel
  • timeout:等待超时(单位 ms),可选值:如 100

返回值:成功返回电压值(mV);失败返回 -1

binary_search(array, size, value)

降序排列的电阻-温度对照表中二分查找 value 所在位置:先比较首尾,再不断砍半缩小范围,找到最接近的档位下标。

参数

  • array:对照表首地址(rt_table),必填
  • size:表元素个数(sizeof rt_table / sizeof rt_table[0]
  • value:待查找值(本教程为 r_ntc * 10,对照表单位为 0.1Ω)

返回值:命中的表下标(uint32_t),下标减 40 即温度(°C);越界时返回 0 或末尾下标。此函数为本示例自实现的工具函数,见 demo_ntc/main.c

blog_info(fmt, ...)

输出一条 INFO 级日志(UART0,受级别过滤),本教程打印电压、电阻与温度。

参数

  • fmt:格式化字符串,同 printf 用法,必填
  • ...:变参,与 fmt 占位符对应,可省略

返回值:无

vTaskDelay(ms)

让当前任务挂起指定毫秒数,期间让出 CPU 给其他任务。

参数

  • ms:延时毫秒数,可选值:任意非负整数(内部经 pdMS_TO_TICKS 换算为系统节拍)

返回值:无


完整代码

以下为 demo_ntc/main.c 完整源码,与官方示例(applications/iot-solution/demo_ntc/demo_ntc/main.c)完全一致(含官方电阻-温度对照表 rt_table,表中数值单位为 0.1Ω,下标从 -40°C 起):

📜 点击展开 demo_ntc/main.c 完整代码
c
#include <stdio.h>
#include <string.h>
#include <FreeRTOS.h>
#include <task.h>
#include <hosal_adc.h>
#include <bl_adc.h>
#include <blog.h>

#define ADC_NTC_PIN 11
#define ADC_REF_PIN 12

#define R1 10000
#define R2 10000
#define R3 10000

const int rt_table[] = {
    2772251,
    2635767,
    2500702,
    2368296,
    2239522,
    2115129,
    1995661,
    1881489,
    1772844,
    1669829,
    1572459,
    1480666,
    1394321,
    1313254,
    1237257,
    1166103,
    1099548,
    1037340,
    979228,
    924959,
    874288,
    827877,
    784386,
    743586,
    705269,
    669248,
    635354,
    603432,
    573341,
    544954,
    518154,
    492833,
    468895,
    446248,
    424810,
    404505,
    385264,
    367020,
    349714,
    333292,
    317700,
    302534,
    288159,
    274531,
    261608,
    249350,
    237720,
    226684,
    216209,
    206263,
    196819,
    187848,
    179326,
    171227,
    163530,
    156212,
    149254,
    142637,
    136342,
    130353,
    124654,
    119229,
    114064,
    109146,
    104462,
    100000,
    95749,
    91697,
    87836,
    84155,
    80644,
    77297,
    74103,
    71057,
    68149,
    65375,
    62726,
    60197,
    57782,
    55475,
    53271,
    51165,
    49153,
    47229,
    45390,
    43632,
    41950,
    40341,
    38802,
    37330,
    35920,
    34571,
    33279,
    32042,
    30857,
    29722,
    28634,
    27592,
    26593,
    25636,
    24718,
    23837,
    22993,
    22183,
    21405,
    20659,
    19943,
    19256,
    18596,
    17962,
    17353,
    16768,
    16206,
    15666,
    15147,
    14648,
    14168,
    13707,
    13263,
    12836,
    12425,
    12030,
    11649,
    11283,
    10930,
    10590,
    10267,
    9955,
    9654,
    9363,
    9083,
    8812,
    8550,
    8297,
    8052,
    7816,
    7587,
    7366,
    7152,
    6945,
    6744,
    6558,
    6376,
    6199,
    6026,
    5858,
    5694,
    5535,
    5380,
    5229,
    5083,
    4941,
    4803,
    4669,
    4539,
    4412,
    4290,
    4171,
    4055,
    3944,
    3835,
    3730,
    3628,
    3530,
    3434,
    3341,
    3253,
    3167,
    3083,
    3002,
    2924,
    2848,
    2774,
    2702,
    2633,
    2565,
    2500,
    2437,
    2375,
    2316,
    2258,
    2202,
    2148,
    2095,
    2044,
    1994,
    1946,
    1900,
    1855,
    1811,
    1769,
    1728,
    1688,
    1650,
    1612,
    1576,
    1541,
    1507,
    1474,
    1441,
    1410,
    1379,
    1350,
    1321,
    1293,
    1265,
    1239,
    1213,
    1187,
    1163,
    1139,
    1115,
    1092,
    1070,
    1048,
    1027,
    1006,
    986,
    966,
    947,
    928,
    909,
    891,
    873,
    856,
    839,
    822,
    806,
    790,
    774,
    759,
    743,
    729,
    714,
    700,
    686,
    672,
    658,
    645,
    631,
    619,
    611,
    603,
    595,
    587,
    578,
    570,
    562,
    553,
    545,
    536,
    528,
    520,
    511,
    503,
    495,
    487,
    479,
    470,
    463,
    455,
    447,
    439,
    432,
    425,
    417,
    410,
    403,
    396,
    390,
    383,
    377,
    370,
    364,
    358,
    352,
    346,
    340,
    335,
    329,
    324,
    319,
    314,
    309,
    304,
    299,
    295,
    290,
    286,
    281,
    277,
    273,
    269,
    265,
    261,
    257,
    254,
    250,
    247,
    243,
    240,
    237,
    233,
    230,
    227,
    224,
    221,
    219,
    216,
    213,
    210,
    208,
    205,
    202,
    200,
    198,
    195,
    193,
    190,
    188,
    186,
    184,
    181,
    179,
    135,
    133,
    131,
    129,
    127,
    125,
    124,
    122,
    120,
    118,
    107,
    115,
    113,
    112,
    110,
    109,
    107,
};

uint32_t binary_search(const int *array, uint32_t size, int value)
{
    uint32_t begin = 0, end = size - 1, mid = 0, i = 0;
    if (value >= array[begin]) {
        return begin;
    }
    else if (value <= array[end]) {
        return end;
    }
    while (begin < end) {
        mid = (begin + end) / 2;
        if (value == array[mid]) {
            break;
        }
        if (value < array[mid] && value > array[mid + 1]) {
            break;
        }
        if (value > array[mid]) {
            end = mid;
        }
        else {
            begin = mid;
        }
        if (i++ > size) {
            break;
        }
    }
    
    if (begin > end) {
        return 0;
    }
    
    return mid;
}

void main(void)
{
    static hosal_adc_dev_t adc0 = {
        .cb = NULL,
        .config = {
            .mode = HOSAL_ADC_ONE_SHOT,
            .pin = ADC_NTC_PIN,
            .sampling_freq = 340,
        },
        .dma_chan = 0,
        .p_arg = NULL,
        .port = 0,
    };

    hosal_adc_init(&adc0);

    hosal_adc_add_channel(&adc0, bl_adc_get_channel_by_gpio(ADC_NTC_PIN));
    hosal_adc_add_channel(&adc0, bl_adc_get_channel_by_gpio(ADC_REF_PIN));

    for (;;)
    {
        int v_ref = hosal_adc_value_get(&adc0, bl_adc_get_channel_by_gpio(ADC_REF_PIN), 100);
        int v_ntc = hosal_adc_value_get(&adc0, bl_adc_get_channel_by_gpio(ADC_NTC_PIN), 100);

        int a = 20000u * v_ref - (v_ntc * 10000u);
        int b = v_ntc;
        int r_ntc = a / b;

        int index = binary_search(rt_table, sizeof rt_table / sizeof rt_table[0], r_ntc * 10);
        int temperature = index - 40;

        blog_info("V_REF = %ld mV\tV_NTC = %ld mV\tR_NTC = %ld ohm\tTemperature = %ld C\r\n", v_ref, v_ntc, r_ntc, temperature);

        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

常见问题与踩坑提示

⚠️ 温度恒为 -40°C 或 125°C(查表越界)
原因:算出的电阻超出对照表范围——通常是电路接反(NTC 与 R1 位置互换)或采样点接错引脚
解决:对照原理图确认 NTC 在 3V3 与采样点 A(IO11)之间、R1 在采样点 A 与 GND 之间;确认 R2/R3 参考电路接 IO12

⚠️ V_NTC 为 0 或乱码(读不到电压)
原因:采样引脚接错、固定电阻阻值用错、未共地(GND 没接),或 ADC 引脚与其他功能冲突
解决:确认 IO11/IO12 接线与阻值(三个电阻都要 10kΩ);确认 3V3 与 GND 接好(共地 = 两个设备的地线必须接在一起);确认工程中无其他模块占用 IO11/IO12

⚠️ 温度数值明显偏大/偏小
原因:固定电阻实际阻值不对(如误用了 1kΩ)、NTC 标称阻值不是 10kΩ @25°C、或对照表与 NTC 型号不匹配
解决:用万用表实测 R1/R2/R3 阻值应为 10kΩ;确认 NTC 是 10kΩ@25°C 型号;型号不同需更换官方 RT.csv 对应的对照表

⚠️ 找不到串口设备或提示无权限
原因:Linux 下 /dev/ttyUSB0 不存在或权限不足,Windows 下未安装 USB 转串口驱动
解决:Linux 用 ls /dev/ttyUSB* 确认设备号,权限不足执行 sudo usermod -aG dialout $USER 后重新登录;Windows 在设备管理器安装驱动并确认 COM 口号

⚠️ 烧录一直卡住等待,进度条不动
原因:未进入下载模式,或数据线只能充电不能传数据
解决:烧录时按提示长按 EN 键进入下载模式;换一根能传数据的 Type-C 数据线后重试

⚠️ 执行 make 报找不到 Makefile
原因:在错误的目录执行了编译命令(必须在示例工程目录内)
解决:先执行 cd ~/Ai-Thinker-WB2/applications/iot-solution/demo_ntc 进入工程目录,再执行 make -j8

运行自检

串口每秒打印 V_REF/V_NTC/R_NTC/Temperature 四项,室温时 R_NTC 接近 10000Ω、温度接近室温,手捏热敏电阻温度上升,即 NTC 温度测量验证通过。

遇到问题?

如有其他问题,请到统一的提问与讨论区:Ai-Thinker Discussions

Released under the MIT License. Build Time 2026-09-11 14:52:23