Skip to content

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) example applications/iot-solution/demo_ntc; the code can be found directly in the local SDK.

🎯Page GoalSample the NTC divider voltage with the ADC, work back to the resistance and look up the temperature, print voltage, resistance and temperature on the serial every second, and understand the voltage divider and table lookup.
🧰Prerequisites① An Ai-WB2 development board + an NTC thermistor (10kΩ @25°C) + three 10kΩ resistors ② Development environment set up per [SDK Installation](../sdk/sdk_intro), and [GPIO Output (LED)](../basic/gpio_led) done.
🔗RelatedADC fundamentals: [ADC Analog-to-Digital](../basic/adc); another temperature sensor: [DS18B20 Temperature Sensor](./ds18b20).

Hardware Wiring

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 = 10000 in the code; using another thermistor requires swapping in matching fixed resistors and a matching table.

Enter the Example Project

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: cd is the “change directory” command — entering the NTC example project directory; all subsequent make build and make flash flash 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
Write the Code

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 11ADC_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 the Project

Build in the project directory:

make -j8

Note: make is the “build” command, turning code into firmware (the program file) the board can run; -j8 builds 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 — run cd toolchain/riscv/Linux && . chmod755.sh first, then rebuild.

Flash the Firmware

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 flash is the “flash” command, writing the compiled firmware into the board’s chip. After p= comes the serial device (change it to your computer’s actual one — check with ls /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.

Run and Verify

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 printed R_NTC to 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_t struct pointer, required. Key fields: config.mode (HOSAL_ADC_ONE_SHOT one-shot / HOSAL_ADC_CONTINUOUS continuous), config.pin (sampling pin number, this tutorial ADC_NTC_PIN=11), config.sampling_freq (sampling frequency Hz, this tutorial 340), port (ADC port, this tutorial 0)

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_t struct pointer
  • channel: ADC channel number (converted from the pin by bl_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 tutorial 11, 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_t struct pointer
  • channel: ADC channel number (same as hosal_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), required
  • size: number of elements (sizeof rt_table / sizeof rt_table[0])
  • value: the value to find (this tutorial r_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 as printf, required
  • ...: variadic args matching the fmt placeholders; 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 via pdMS_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
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));
    }
}

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.

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