概述
NTC(Negative Temperature Coefficient,负温度系数)热敏电阻是一种电阻值随温度变化的电阻:温度升高,电阻变小。它价格便宜、皮实耐用,是家电测温(空调、热水器)最常见的方案。本教程用 Ai-WB2 开发板的 ADC(模数转换:把引脚上的模拟电压变成数字值)采集 NTC 分压电路(电阻串联分压,用欧姆定律 U=I×R 反推电阻)的电压,再用查表法(对照厂商给的"电阻-温度对照表")换算成温度,串口实时打印,并走完 接线 → 代码编写 → 编译 → 烧录(把编译好的程序写进开发板芯片)→ 运行验证 的完整流程。
用大白话讲:NTC 就像一扇随温度变化的"水龙头"——温度越高,"水管"越粗(电阻越小)。把它和一个固定电阻(10kΩ)串起来接在电源上,两个电阻之间的电压就会随温度变化(这叫分压)。开发板用 ADC"量电压"(把电压变成数字),反推 NTC 的电阻,再对照厂商的"电阻-温度对照表"查出温度。本示例还多量了一路"参考电压"(电源电压的一半),这样电源电压波动时也能算准。
本教程基于安信可官方 SDK(Ai-Thinker-Open/Ai-Thinker-WB2,版本
release_bl_iot_sdk_1.6.40)的官方示例applications/iot-solution/demo_ntc编写,代码可在本地 SDK 中直接找到。
本示例需要搭一个分压电路(两个电阻串联,中间抽头取电压),按官方原理图(见 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 11、ADC_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_REF、R_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 控制器(本教程单次采样模式采集两路电压)。
参数:
adc:hosal_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 采样该通道。
参数:
adc:hosal_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 功能的引脚(本教程11、12)
返回值:对应 ADC 通道号(int);引脚不支持 ADC 时返回负值。声明于 components/platform/hosal/bl602_hal/bl_adc.h
hosal_adc_value_get(adc, channel, timeout)
对指定通道采样一次并返回电压值(单位 mV)。
参数:
adc:hosal_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占位符对应,可省略
返回值:无
完整代码
以下为 demo_ntc/main.c 完整源码,与官方示例(applications/iot-solution/demo_ntc/demo_ntc/main.c)完全一致(含官方电阻-温度对照表 rt_table,表中数值单位为 0.1Ω,下标从 -40°C 起):
📜 点击展开 demo_ntc/main.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

