Skip to content

Overview

The ADC (Analog-to-Digital Converter) turns analog voltage into digital values; it's the foundation for reading sensors (temperature, light, potentiometers, etc.). This tutorial samples the voltage of a divider network through ADC channel 10 (IO11), converts it to the actual voltage, and prints it over serial.

In plain words: the board only understands digits (0 and 1) and can't read continuous voltages like "3.3 volts". The ADC is like a "voltage translator": it translates the voltage on a pin into a string of numbers for the program. This tutorial also uses a divider network (like the scale marks on a water gauge) to scale the big voltage down proportionally, so the translator can measure it within range and accurately.

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/peripherals/demo_adc; the code can be found directly in your local SDK.

🎯Page GoalSample voltage via ADC channel 10 (IO11), convert to the actual voltage with the divider network, and print it over serial; learn ADC init, channel add and sampling APIs.
🧰Prerequisites① Ai-WB2 development board, 4.7kΩ resistor, 1kΩ resistor, jumper wires ② Environment set up per [SDK Installation](../sdk/sdk_intro).
🔗RelatedVoltage measurement is commonly used for sensors, see [Best Practices - Sensors](../best/); serial log output: [UART (Serial)](./uart).

Hardware Wiring

Wire per the official example (see SDK applications/peripherals/demo_adc/README.md), using a 4.7kΩ + 1kΩ divider network to bring the voltage under test into the ADC range:

Ai-WB2 Pin Connected To
IO11 (ADC channel 10) Divider node (between 4.7kΩ and 1kΩ)
Voltage under test (e.g. 3V3) Top of the 4.7kΩ
GND Bottom of the 1kΩ
VCC ── 4.7kΩ ──┬── IO11
               │
              1kΩ
               │
              GND

💡 The example converts the raw ADC output with Actual voltage = ADC value × (4700 + 1000) ÷ 1000 (unit mV), the formula restoring the actual voltage considering the 4.7kΩ/1kΩ divider. The BL602’s ADC reference voltage is 1.0V and the range (measurable voltage span) is 0~1.8V (requiring divider sampling) — wire the divider network accordingly.

Enter the Example Project

Open a terminal and enter the official demo_adc example project directory:

cd ~/Ai-Thinker-WB2/applications/peripherals/demo_adc

Note: cd is the “change directory” command; this enters the demo_adc example project. All subsequent make build and make flash commands must run inside this directory first.

Write the Code

Open demo_adc/main.c. The full 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/peripherals/demo_adc/demo_adc/main.c).

Code highlights:

Code Purpose
.mode = HOSAL_ADC_ONE_SHOT Single-shot mode (one sample returns one value); wrong mode gives unexpected results
.pin = GPIO_ADC_PIN / ADC_CHANNEL Sampling pin IO11 corresponds to ADC channel 10; the pin and channel must match, otherwise the reading is always 0
hosal_adc_init(&adc0) Initialize the ADC device; without it all sampling functions are invalid
hosal_adc_add_channel(&adc0, ADC_CHANNEL) Add channel 10 to the sampling list; without it this channel is never sampled
hosal_adc_value_get(&adc0, ADC_CHANNEL, 100) Read one sample (100ms timeout); only on success is the value available in adc->data
ret * (4700 + 1000) / 1000 Restore the actual voltage (mV) per the divider ratio; without this the printed value is the shrunken wrong number

📌 The channel mapping table in the official comments shows: channel 10 corresponds to GPIO11. The BL602 has 12 ADC channels (channels 6 and 8 reserved); check the mapping table before changing pins.

Build the Project

Build in the project directory:

make -j8

Note: make is the “build” command, turning code into firmware (a program file) the board can run; -j8 builds with 8 parallel cores, faster.

On success a firmware build_out/demo_adc.bin is generated.

Flash the Firmware

Keep the board connected via USB, confirm the serial device, 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 port — 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; wait for the progress bar to complete — that means the flash succeeded.

Run and Verify

After flashing, the board automatically restarts. Open a serial assistant (baud rate 921600); every 2 seconds a converted voltage prints:

ADC = 3300 mV
ADC = 3300 mV
...

With the voltage under test connected to 3V3, it prints about 3300mV; slowly adjust a variable power supply and the printed value follows linearly — ADC sampling works.

Seeing a voltage print every 2 seconds that follows the input voltage means success; if it keeps printing 0 or the value never changes, check the “FAQ & Troubleshooting” section at the end.

💡 Try changing the resistor values in the divider network and watch the printed voltage change; replacing the 4.7kΩ with a photoresistor (LDR) makes a simple light detector.


API Summary for This Tutorial

hosal_adc_init(adc)

Configures the ADC per the mode, pin, sampling frequency, etc. in the dev struct (initializing single-shot mode in this tutorial).

Parameters:

  • adc: hosal_adc_dev_t struct pointer, required. Key fields: config.mode (sampling mode, values: HOSAL_ADC_ONE_SHOT single-shot / HOSAL_ADC_CONTINUOUS continuous), config.pin (ADC pin number, IO11 here), config.sampling_freq (sampling frequency in Hz)

Return: 0 on success; negative error code on failure

hosal_adc_add_channel(adc, channel)

Adds the specified ADC channel to the sampling list (channel 10 in this tutorial).

Parameters:

  • adc: hosal_adc_dev_t struct pointer
  • channel: channel number, values: 0~11 (6 and 8 reserved, unusable); 10 here (corresponds to IO11)

Return: 0 on success; negative error code on failure

hosal_adc_remove_channel(adc, channel)

Removes the specified channel from the sampling list.

Parameters:

  • adc: hosal_adc_dev_t struct pointer
  • channel: channel number, values: 0~11 (must match the one added)

Return: 0 on success; negative error code on failure

hosal_adc_value_get(adc, channel, timeout)

Triggers one sample in single-shot mode and waits for the result; the value is stored back into adc->data (converted to the actual voltage before dividing in this tutorial).

Parameters:

  • adc: hosal_adc_dev_t struct pointer
  • channel: channel number, values: 0~11 (6, 8 reserved)
  • timeout: wait timeout (unit ms), values: e.g. 100 (returns failure if no result within the timeout)

Return: 0 on success (raw value in adc->data); negative error code on timeout or failure

hosal_adc_start(adc, data, size)

Starts continuous sampling in continuous mode; data is delivered one by one through the interrupt callback.

Parameters:

  • adc: hosal_adc_dev_t struct pointer
  • data: output buffer pointer for sample data
  • size: buffer size

Return: 0 on success; negative error code on failure

hosal_adc_stop(adc)

Stops continuous sampling.

Parameters:

  • adc: hosal_adc_dev_t struct pointer

Return: 0 on success; negative error code on failure

blog_info(fmt, ...)

Outputs an INFO-level log (UART0, filtered by level).

Parameters:

  • fmt: format string, same usage as printf, required
  • ...: variadic args matching fmt placeholders, optional

Return: none


Full Code

Below is the complete demo_adc/main.c source, identical to the official example (applications/peripherals/demo_adc/demo_adc/main.c):

📜 Click to expand the full demo_adc/main.c code
c
#include <stdio.h>
#include <string.h>
#include <FreeRTOS.h>
#include <task.h>
#include <hosal_adc.h>
#include <blog.h>

/**********   BL602  ************
*    channel0   ----->     gpio12 
*    channel1   ----->     gpio4
*    channel2   ----->     gpio14
*    channel3   ----->     gpio13
*    channel4   ----->     gpio5
*    channel5   ----->     gpio6
*    channel7   ----->     gpio9
*    channel9   ----->     gpio10
*    channel10  ----->     gpio11
*    channel11  ----->     gpio15
*/

#define GPIO_ADC_PIN 11
#define ADC_CHANNEL 10

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

    hosal_adc_init(&adc0);

    hosal_adc_add_channel(&adc0, ADC_CHANNEL);

    for (;;) {
        int ret = hosal_adc_value_get(&adc0, ADC_CHANNEL, 100);

        /// ADC
        ///  |
        /// ┌┴┐
        /// | | 4.7 kΩ
        /// └┬┘
        ///  |
        ///  ├-----IO11
        ///  |
        /// ┌┴┐
        /// | | 1 kΩ
        /// └┬┘
        ///  |
        ///  ┴
        /// GND
        /// Actual Voltage = ADC output * 1000 / (4700 + 1000)  (Unit: mV)
        
        ret = ret * (4700 + 1000) / 1000;

        blog_info("ADC = %ld mV\r\n", ret);

        vTaskDelay(pdMS_TO_TICKS(2000));
    }
}

FAQ & Troubleshooting

⚠️ The printed voltage is abnormal (too large or too small)
Cause: the divider resistor values were changed but the formula wasn't updated, or the voltage under test exceeds the ADC range
Fix: confirm the conversion formula matches the divider resistors; the BL602 ADC reference voltage is 1.0V with a limited range — the voltage under test must go through a divider before sampling

⚠️ The sample is always 0 or a fixed value that never changes
Cause: the pin and channel numbers don't match (e.g. using channel 10 on IO15)
Fix: pick the pin and channel per the official channel mapping table (channel 10 → IO11); when changing pins, change both GPIO_ADC_PIN and ADC_CHANNEL

⚠️ The sample jumps around with lots of noise
Cause: jumper wires too long, no common ground, or the sampling point is floating
Fix: shorten the jumper wires and confirm common ground with the board; add a 100nF capacitor in parallel at the sampling point for filtering; in software, sample multiple times and average

⚠️ Flashing keeps waiting, the 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 to enter download mode when prompted; try a Type-C cable that can transfer data and retry

⚠️ Serial device not found or permission denied
Cause: /dev/ttyUSB0 doesn't exist or permissions are insufficient on Linux; USB-to-serial driver not installed on Windows
Fix: on Linux confirm the device with ls /dev/ttyUSB*, for permissions run sudo usermod -aG dialout $USER then log in again; on Windows install the driver in Device Manager and confirm the COM number

⚠️ Running make reports no Makefile found
Cause: the build command ran in the wrong directory (it must run inside the example project)
Fix: first cd ~/Ai-Thinker-WB2/applications/peripherals/demo_adc into the project directory, then run make -j8

Self-Check

The serial prints a voltage every 2 seconds, and the printed value follows the input voltage linearly — ADC is verified.

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