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) exampleapplications/peripherals/demo_adc; the code can be found directly in your local SDK.
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.
Open a terminal and enter the official demo_adc example project directory:
cd ~/Ai-Thinker-WB2/applications/peripherals/demo_adc
Note:
cdis the “change directory” command; this enters the demo_adc example project. All subsequentmakebuild andmake flashcommands must run inside this directory first.
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 in the project directory:
make -j8
Note:
makeis the “build” command, turning code into firmware (a program file) the board can run;-j8builds with 8 parallel cores, faster.
On success a firmware build_out/demo_adc.bin is generated.
Keep the board connected via USB, confirm the serial device, 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 port — 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; wait for the progress bar to complete — that means the flash succeeded.
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_tstruct pointer, required. Key fields:config.mode(sampling mode, values:HOSAL_ADC_ONE_SHOTsingle-shot /HOSAL_ADC_CONTINUOUScontinuous),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_tstruct pointerchannel: channel number, values:0~11(6 and 8 reserved, unusable);10here (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_tstruct pointerchannel: 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_tstruct pointerchannel: 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_tstruct pointerdata: output buffer pointer for sample datasize: buffer size
Return: 0 on success; negative error code on failure
hosal_adc_stop(adc)
Stops continuous sampling.
Parameters:
adc:hosal_adc_dev_tstruct 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 asprintf, required...: variadic args matchingfmtplaceholders, 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
#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.

