Overview
The Slave is the passive side of BLE communication: it keeps advertising (like shouting through a loudspeaker: "I'm here, come connect to me!") waiting for a master or phone to connect. This tutorial makes the Ai-WB2 play the slave role, registering a service named UUID1 (a functional module); after a phone or BLE Master connects, the two sides can send and receive data freely — "serial ⇄ Bluetooth" two-way transparent transfer. This is the most common form of devices like wristbands, sensors and smart door locks.
In plain words: the slave is like the shop assistant standing behind the counter, holding a "Welcome" sign (advertising) and waiting for customers (master/phone) to come in. Once a customer arrives (connection succeeds), the two sides pass notes by the GATT rules (the Bluetooth communication protocol: services contain characteristics, and all data flows through characteristics): the customer drops a note into the "inbox" (characteristic) and the assistant sees it on the serial; the assistant stuffs data into the "outbox" and the customer receives a notification. The "inbox/outbox" in this tutorial is the UUID1 service registered by the official project.
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/bluetooth/ble_slave; the code can be found directly in the local SDK.
Open a terminal and enter the official ble_slave example project directory:
cd ~/Ai-Thinker-WB2/applications/bluetooth/ble_slave
Note:
cdis the “change directory” command — entering the slave example project directory; all subsequentmakebuild andmake flashflash commands must run in this directory.
📌 Note: this project consists of multiple source files (
main.candble_interface.c); the GATT service registration and characteristic read/write callbacks are implemented inble_interface.c. This page only showsmain.c— see the official project for the rest.
Project structure:
| File | Purpose |
|---|---|
ble_slave/main.c |
Main program: starting the slave, serial forwarding logic — the main file this tutorial looks at |
ble_slave/ble_interface.c/.h |
Slave service implementation: registers the UUID1 service, the UUID1_SendNotify notification interface, and the apps_ble_start startup entry |
Open ble_slave/main.c — this example needs no parameter changes. It does just two things:
apps_ble_start(): starts the slave — registers the UUID1 service (inbox/outbox) and starts advertising (implemented inble_interface.c).- The
TaskUarttask: polls the serial port non-stop; whatever characters the serial receives are sent verbatim to the connected client viaUUID1_SendNotify.
💡 Want to see what “service/characteristic” actually looks like? Connect this device with nRF Connect on your phone and open the GATT page — you’ll see the characteristics under the UUID1 service. That’s the Bluetooth-world “rules” made visible on a real device.
Open ble_slave/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/bluetooth/ble_slave/ble_slave/main.c).
Code highlights:
| API | Purpose |
|---|---|
apps_ble_start() |
Starts the slave: registers the UUID1 service and starts advertising (implemented in ble_interface.c); without it the phone can’t find this device |
UUID1_SendNotify(len, data) |
Sends serial data to the connected client as a “notification”; the app must enable notifications to receive it |
hosal_uart_receive(&uart_dev_log, ...) |
Polls the serial port and forwards received data over Bluetooth — the “serial → Bluetooth” direction |
xTaskCreate(TaskUart, ...) |
Runs the serial listener in its own task; without it nobody watches the serial input for you |
uart_dev_log struct |
Serial parameters (uart0, TX=16, RX=7, baud rate 115200) — the serial assistant baud rate must match it |
Build in the project directory:
make -j8
Note:
makeis the “build” command, turning code into firmware (the program flashed into the board) the board can run;-j8builds with 8 parallel CPU cores, faster.
On success a firmware build_out/ble_slave.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, 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=921600is the flash baud rate (serial transfer speed), keep the default.
⏳ 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 and runs. Scan with nRF Connect on your phone and connect to this device (or use another board flashed with the BLE Master example).
First check the serial log — note! This example configures the serial at baud rate 115200; the serial assistant must be set to 115200:
[OS] proc_main_entry task...
Verify two-way transparent transfer with the phone:
- Phone → board: after connecting in nRF Connect, open the GATT page, find the characteristic under the UUID1 service, write any characters (e.g.
hello) to it — the board’s serial should print them. - Board → phone: type characters on the board’s serial; the phone app should receive a notification of that data (only after enabling the “Notify” subscription on the characteristic).
The serial printing [OS] proc_main_entry task... and the phone being able to connect and exchange data both ways means success; if the phone can’t find or connect to the device, see the FAQ at the end; if the board → phone direction receives nothing, it’s usually because the notification subscription wasn’t enabled in the app.
💡 Advanced verification: combine with the BLE Master tutorial — flash one board as master and one as slave; typing characters on either serial completes a “board-to-board transparent transfer”, which is how many real products work.
API Summary for This Tutorial
Note:
apps_ble_startandUUID1_SendNotifyare custom interfaces of this official project (implemented inble_interface.c);hosal_uart_*andbl_sys_initare SDK platform-layer interfaces.
bl_sys_init
Initializes basic resources such as the system clock and peripherals; must be called before using BLE (called in main() in this example).
Return: 0 on success; negative error code on failure
hosal_uart_init(uart)
Initializes the serial port per the struct config (this example uart_id=0, TX=GPIO16, RX=GPIO7, baud rate 115200).
Parameters:
uart:hosal_uart_dev_tstruct pointer, required, holding the serial number, pins, baud rate, etc.
Return: 0 on success; negative error code on failure
hosal_uart_receive(uart, data, expect_size)
Polls the serial port for data (called in a loop here, as the entry point for "serial → Bluetooth notification").
Parameters:
uart:hosal_uart_dev_tstruct pointerdata: receive buffer pointer, requiredexpect_size: expected bytes to receive (buffer size)
Return: bytes actually received (> 0) on success; negative error code on failure
apps_ble_start
Starts the slave: registers the UUID1 service (with characteristics) and starts advertising (project-custom API, implemented in ble_interface.c).
Return: none
UUID1_SendNotify(len, data)
Sends data to the connected client as a "notification" (project-custom API, implemented in ble_interface.c).
Parameters:
len: data length (uint16_t), requireddata: data pointer, required
Return: none
vTaskDelay(ms)
Suspends the current task for the given milliseconds, yielding the CPU to other tasks.
Parameters:
ms: delay in milliseconds; this example50(lowering the serial polling frequency)
Return: none
xTaskCreate(task, name, stack, param, prio, handle)
Creates a task and adds it to the ready queue; the scheduler runs it by priority.
Parameters:
task: task entry function pointer, shapedvoid task(void *arg), requiredname: task name string (for debugging); this example"TaskUart"/"main_entry"stack: task stack size (in words); this example2048(serial task) /1024(main task)param: parameter pointer passed to the entry function; passNULLif noneprio: task priority, values:0(lowest) ~19(highest, per SDK config); this example15handle: task handle output pointer; passNULLif not needed
Return: pdPASS on success; pdFAIL on failure (e.g. out of memory)
Full Code
Below is the complete ble_slave/main.c source, identical to the official example (applications/bluetooth/ble_slave/ble_slave/main.c). This project consists of main.c and ble_interface.c; this page only shows main.c — the GATT service registration and callback logic are in the official project's ble_interface.c:
📜 Click to expand the full main.c code
#include <FreeRTOS.h>
#include <task.h>
#include <timers.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <aos/kernel.h>
#include <aos/yloop.h>
#include <event_device.h>
#include <bl_sys.h>
#include "ble_lib_api.h"
#include <hosal_uart.h>
#include <blog.h>
#include <hosal_uart.h>
#include <hosal_timer.h>
#include "gatt.h"
#include "bluetooth.h"
#include "ble_interface.h"
hosal_uart_dev_t uart_dev_log = {
.config = {
.uart_id = 0,
.tx_pin = 16, // TXD GPIO
.rx_pin = 7, // RXD GPIO
.cts_pin = 255,
.rts_pin = 255,
.baud_rate = 115200,
.data_width = HOSAL_DATA_WIDTH_8BIT,
.parity = HOSAL_NO_PARITY,
.stop_bits = HOSAL_STOP_BITS_1,
.mode = HOSAL_UART_MODE_POLL,
},
};
void TaskUart(void *param)
{
uint8_t data[32] = {0};
int ret;
hosal_uart_init(&uart_dev_log);
while (1)
{
/* Uart receive poll */
ret = hosal_uart_receive(&uart_dev_log, data, sizeof(data));
if (ret > 0)
{
UUID1_SendNotify(strlen((char *)data), data);
}
vTaskDelay(50);
}
}
static void proc_main_entry(void *pvParameters)
{
apps_ble_start();
vTaskDelete(NULL);
}
void main()
{
bl_sys_init();
puts("[OS] proc_main_entry task...\r\n");
xTaskCreate(TaskUart, "TaskUart", 2048, NULL, 15, NULL);
xTaskCreate(proc_main_entry, (char *)"main_entry", 1024, NULL, 15, NULL);
}FAQ & Troubleshooting
⚠️ The phone can't find the device / can't connect
Cause: advertising not started, distance too far, or the phone's Bluetooth/location permission is off
Fix: confirm the serial printed [OS] proc_main_entry task...; Android phones must enable the "location permission" to scan BLE devices; move the phone close to the board (within 1 m) and scan again; if the app caches results, kill and reopen it
⚠️ Phone connected, but writing data doesn't show on the board's serial
Cause: wrote to the wrong characteristic, or the serial assistant baud rate is wrong (this example is 115200)
Fix: in nRF Connect's GATT page, confirm you're writing the characteristic under the UUID1 service; change the serial assistant baud rate to 115200; confirm a "write successful" prompt appears on the characteristic after writing
⚠️ Board sends data on the serial, but the phone receives nothing
Cause: the characteristic's "Notify" subscription wasn't enabled — notifications aren't on by default
Fix: tap "Enable Notifications" on the characteristic page in nRF Connect, then send data from the serial
⚠️ Build fails: ble_interface.h or other headers not found
Cause: the build command ran in the wrong directory, or the project files are incomplete
Fix: confirm make -j8 runs inside ~/Ai-Thinker-WB2/applications/bluetooth/ble_slave; the project's ble_interface.c/.h live in that directory — don't copy only main.c
⚠️ 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
⚠️ Serial device not found / can't open
Cause: USB-to-serial driver not installed, insufficient permission, or wrong device number
Fix: on Linux check with lsusb/dmesg; if permission denied run sudo usermod -aG dialout $USER and log back in; on Windows check the COM port in Device Manager
Self-Check
The serial prints [OS] proc_main_entry task...; nRF Connect on the phone finds and connects to this device, characters written to the UUID1 service characteristic print on the board's serial, and with "Notify" enabled the characters typed on the serial arrive in the app — the slave transparent transfer is verified.

