Overview
An NTC (Negative Temperature Coefficient) thermistor is a resistor whose resistance changes with temperature: as temperature rises, resistance drops. It's cheap, sturdy and durable — the most common approach for temperature measurement in household appliances (air conditioners, water heaters). This tutorial uses the Ai-WB2 development board's ADC (analog-to-digital conversion: turning the analog voltage on a pin into a digital value) to sample the NTC voltage-divider circuit (resistors in series divide the voltage; Ohm's law U=I×R lets you work back to the resistance), then converts to temperature with the table lookup method (matching the manufacturer's "resistance-temperature lookup table"), printing on the serial in real time, and walks through the full flow of wiring → coding → building → flashing (writing the compiled program into the board's chip) → running and verification.
In plain words: the NTC is like a "water tap" that changes with temperature — the higher the temperature, the thicker the "pipe" (the smaller the resistance). String it in series with a fixed resistor (10kΩ) across the power supply, and the voltage between the two resistors changes with temperature (that's called a voltage divider). The board uses the ADC to "measure the voltage" (turn voltage into numbers), works back to the NTC's resistance, then looks up the temperature in the manufacturer's "resistance-temperature table". This example also measures an extra "reference voltage" (half the supply voltage) so the calculation stays accurate even when the supply voltage fluctuates.
This tutorial is based on the official Ai-Thinker SDK (Ai-Thinker-Open/Ai-Thinker-WB2, version
release_bl_iot_sdk_1.6.40) exampleapplications/iot-solution/demo_ntc; the code can be found directly in the local SDK.
This example needs a voltage divider circuit (two resistors in series, tap the voltage at the middle); wire per the official schematic (see the SDK’s applications/iot-solution/demo_ntc/img/schematic.jpg):
| Ai-WB2 Pin | Circuit connection |
|---|---|
| IO11 | Junction of the NTC thermistor and R1 (10kΩ) (NTC divider sampling) |
| IO12 | Junction of R2 (10kΩ) and R3 (10kΩ) (reference voltage sampling) |
| 3V3 | The NTC’s other end, R2’s other end |
| GND | R1’s other end, R3’s other end |
Circuit relationship:
3V3 ──┬── NTC(10kΩ@25°C) ──┬── R1(10kΩ) ── GND
│ │
│ └── IO11 (sampling point A)
│
├── R2(10kΩ) ──┬── R3(10kΩ) ── GND
│
└── IO12 (sampling point B, reference = half of VCC)
💡 Sampling point A’s voltage = VCC × R1 ÷ (R1 + R_NTC); temperature changes → R_NTC changes → voltage changes; the ADC measures the voltage and you can work back to temperature. 💡 Sampling point B specifically measures half the supply voltage (VCC/2); when VCC fluctuates, use it as the reference so the computed resistance stays accurate. 💡 The NTC is rated 10kΩ @25°C (10kΩ at 25°C), matching
R1/R2/R3 = 10000in the code; using another thermistor requires swapping in matching fixed resistors and a matching table.
This tutorial directly uses the demo_ntc example project shipped with the official SDK; open a terminal and enter it:
cd ~/Ai-Thinker-WB2/applications/iot-solution/demo_ntc
Note:
cdis the “change directory” command — entering the NTC example project directory; all subsequentmakebuild andmake flashflash commands must run in this directory first.
Project structure:
| File | Purpose |
|---|---|
demo_ntc/main.c |
Main program source (contains the resistance-temperature lookup table), the main file this tutorial looks at |
Makefile |
Build entry, usually no changes needed |
proj_config.mk |
Project config (flash size, feature switches, etc.), usually no changes needed |
Open demo_ntc/main.c — the complete code for this step has been moved to the end of this page:
📜 Full Code — in the “Full Code” section below, collapsed by default — click to expand, identical to the official example (
applications/iot-solution/demo_ntc/demo_ntc/main.c).
Code highlights:
| Code | Purpose |
|---|---|
ADC_NTC_PIN 11、ADC_REF_PIN 12 |
Two ADC sampling pins: IO11 samples the NTC divider point, IO12 samples the reference voltage; must match the wiring table, wrong pins = no measurement |
R1/R2/R3 = 10000 |
Three 10kΩ fixed resistors (divider + reference voltage); must match the resistors actually wired, otherwise every temperature is wrong |
.mode = HOSAL_ADC_ONE_SHOT |
One-shot sampling mode: manually read a voltage once per loop, no need for continuous sampling wasting resources |
hosal_adc_add_channel(&adc0, bl_adc_get_channel_by_gpio(...)) |
Converts the pin to an ADC channel number and registers it; unregistered = this pin’s voltage can’t be sampled |
hosal_adc_value_get(...) |
Reads the given channel’s voltage (mV); without it you can’t compute resistance or temperature |
r_ntc = 20000u*v_ref/v_ntc - 10000 |
Works back to the NTC’s current resistance with the divider formula (Ohm’s law); wrong formula = all temperatures wrong |
binary_search(rt_table, size, r_ntc * 10) |
Binary search (halves the range each time, faster than comparing one by one) for the closest entry in the resistance-temperature table |
temperature = index - 40 |
The table index starts counting at -40°C; index minus 40 is the actual temperature (°C) |
Build in the project directory:
make -j8
Note:
makeis the “build” command, turning code into firmware (the program file) the board can run;-j8builds with 8 parallel CPU cores, faster.
On success a firmware build_out/demo_ntc.bin is generated.
⚠️ If it reports
riscv64-unknown-elf-gcc: command not found, the toolchain permissions aren’t configured — runcd toolchain/riscv/Linux && . chmod755.shfirst, then rebuild.
Keep the board connected via USB, confirm the serial device (usually /dev/ttyUSB0 on Linux), and flash:
make flash p=/dev/ttyUSB0 b=921600
Note:
make flashis the “flash” command, writing the compiled firmware into the board’s chip. Afterp=comes the serial device (change it to your computer’s actual one — check withls /dev/ttyUSB*),b=is the flash baud rate (transfer speed).
⏳ During flashing, press and hold the EN button on the board when prompted to enter download mode (some boards enter automatically); wait for the progress bar to complete — that means the flash succeeded. For flashing on Windows, see Windows Quick Start.
After flashing, the board automatically restarts and runs; open the serial assistant (baud rate 921600 — the baud rate is the serial transfer speed, both ends must be set the same) and voltage, resistance and temperature print every 1 second:
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
...
At room temperature (~25°C), V_NTC should be close to V_REF, R_NTC close to 10000 ohm, and the temperature close to room temperature. Pinch the thermistor with your fingers — the temperature should rise slowly (resistance drops); release it and it falls back.
Seeing the serial print the four values V_REF/V_NTC/R_NTC/Temperature every second, with temperature following your touch, means success; if the temperature is stuck at -40°C or 125°C (table lookup out of range) or the values are garbled, it hasn’t succeeded yet — check the FAQ at the end.
💡 The official README includes the complete resistance-temperature data (
img/RT.csv) — compare against the printedR_NTCto verify.
API Summary for This Tutorial
hosal_adc_init(adc)
Configures the ADC controller per the pin, sampling mode and sampling frequency in the dev struct (this tutorial samples two voltages in one-shot mode).
Parameters:
adc:hosal_adc_dev_tstruct pointer, required. Key fields:config.mode(HOSAL_ADC_ONE_SHOTone-shot /HOSAL_ADC_CONTINUOUScontinuous),config.pin(sampling pin number, this tutorialADC_NTC_PIN=11),config.sampling_freq(sampling frequency Hz, this tutorial340),port(ADC port, this tutorial0)
Return: 0 on success; negative error code on failure
hosal_adc_add_channel(adc, channel)
Registers a channel onto the ADC interface; only then can hosal_adc_value_get sample that channel.
Parameters:
adc:hosal_adc_dev_tstruct pointerchannel: ADC channel number (converted from the pin bybl_adc_get_channel_by_gpio)
Return: 0 on success; negative error code on failure
bl_adc_get_channel_by_gpio(gpio_num)
Converts a GPIO pin number to its corresponding ADC channel number (pins and channels are not the same numbering — the conversion is required).
Parameters:
gpio_num: pin number, values: pins supporting ADC (this tutorial11,12)
Return: the corresponding ADC channel number (int); negative if the pin doesn't support ADC. Declared in components/platform/hosal/bl602_hal/bl_adc.h
hosal_adc_value_get(adc, channel, timeout)
Samples the given channel once and returns the voltage (mV).
Parameters:
adc:hosal_adc_dev_tstruct pointerchannel: ADC channel number (same ashosal_adc_add_channel)timeout: wait timeout (ms), values: e.g.100
Return: voltage (mV) on success; -1 on failure
binary_search(array, size, value)
Binary-searches the position of value in the descending resistance-temperature table: compares the two ends first, then keeps halving the range to find the closest entry index.
Parameters:
array: table base address (rt_table), requiredsize: number of elements (sizeof rt_table / sizeof rt_table[0])value: the value to find (this tutorialr_ntc * 10, the table's unit is 0.1Ω)
Return: the matched table index (uint32_t); index minus 40 is the temperature (°C); out of range returns 0 or the last index. This function is a utility implemented by this example itself — see demo_ntc/main.c
blog_info(fmt, ...)
Prints an INFO-level log (UART0, subject to level filtering); this tutorial prints voltage, resistance and temperature with it.
Parameters:
fmt: format string, same usage asprintf, required...: variadic args matching thefmtplaceholders; can be omitted
Return: none
vTaskDelay(ms)
Suspends the current task for the given milliseconds, yielding the CPU to other tasks.
Parameters:
ms: delay in milliseconds, values: any non-negative integer (internally converted to system ticks viapdMS_TO_TICKS)
Return: none
Full Code
Below is the complete demo_ntc/main.c source, identical to the official example (applications/iot-solution/demo_ntc/demo_ntc/main.c) (including the official resistance-temperature table rt_table, values in 0.1Ω, index starting at -40°C):
📜 Click to expand the full demo_ntc/main.c code
#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));
}
}FAQ & Troubleshooting
⚠️ Temperature stuck at -40°C or 125°C (table lookup out of range)
Cause: the computed resistance falls outside the table's range — usually the circuit is wired backwards (NTC and R1 swapped) or a sampling point is on the wrong pin
Fix: per the schematic, confirm the NTC sits between 3V3 and sampling point A (IO11), R1 between point A and GND; confirm the R2/R3 reference circuit is on IO12
⚠️ V_NTC is 0 or garbled (can't read voltage)
Cause: wrong sampling pin, wrong fixed resistor values, no common ground (GND not connected), or the ADC pins conflict with another function
Fix: confirm IO11/IO12 wiring and values (all three resistors 10kΩ); confirm 3V3 and GND are connected (common ground = the two devices' grounds must be joined); confirm no other module in the project occupies IO11/IO12
⚠️ Temperature clearly too high/too low
Cause: the fixed resistors' actual values are wrong (e.g. 1kΩ used by mistake), the NTC's rated value isn't 10kΩ @25°C, or the table doesn't match the NTC model
Fix: measure R1/R2/R3 with a multimeter — should be 10kΩ; confirm the NTC is a 10kΩ@25°C model; a different model needs the table corresponding to the official RT.csv
⚠️ Serial device not found or no permission
Cause: on Linux /dev/ttyUSB0 doesn't exist or permission denied; on Windows the USB-to-serial driver isn't installed
Fix: on Linux check with ls /dev/ttyUSB*; if permission denied run sudo usermod -aG dialout $USER and log back in; on Windows install the driver in Device Manager and confirm the COM port
⚠️ Flashing keeps waiting, progress bar doesn't move
Cause: download mode wasn't entered, or the cable only charges and can't transfer data
Fix: press and hold EN during flashing to enter download mode as prompted; try a Type-C data-capable cable
⚠️ make reports Makefile not found
Cause: the build command ran in the wrong directory (must be inside the example project directory)
Fix: run cd ~/Ai-Thinker-WB2/applications/iot-solution/demo_ntc first, then make -j8
Self-Check
The serial prints the four values V_REF/V_NTC/R_NTC/Temperature every second; at room temperature R_NTC is close to 10000Ω and the temperature close to room temperature, and pinching the thermistor makes the temperature rise — the NTC temperature measurement is verified.

