Skip to content

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

🎯Page GoalMake the Ai-WB2 a BLE slave that registers the UUID1 transparent transfer service; a phone app or BLE master can connect and exchange data both ways.
🧰Prerequisites① An Ai-WB2 development board ② Development environment set up per [SDK Installation](../../sdk/sdk_intro) ③ A phone (with a Bluetooth debugging app such as nRF Connect) or another board flashed with the [BLE Master](./ble_master) example.
🔗RelatedGATT service/characteristic concepts: [BLE Introduction](./ble_intro); the master that actively connects to it: [BLE Master](./ble_master).

Enter the Example Project

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

cd ~/Ai-Thinker-WB2/applications/bluetooth/ble_slave

Note: cd is the “change directory” command — entering the slave example project directory; all subsequent make build and make flash flash commands must run in this directory.

📌 Note: this project consists of multiple source files (main.c and ble_interface.c); the GATT service registration and characteristic read/write callbacks are implemented in ble_interface.c. This page only shows main.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
Understand How the Slave Works

Open ble_slave/main.c — this example needs no parameter changes. It does just two things:

  1. apps_ble_start(): starts the slave — registers the UUID1 service (inbox/outbox) and starts advertising (implemented in ble_interface.c).
  2. The TaskUart task: polls the serial port non-stop; whatever characters the serial receives are sent verbatim to the connected client via UUID1_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.

Write the Code

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

Build in the project directory:

make -j8

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

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 one — check with ls /dev/ttyUSB*), b=921600 is 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.

Run and Verify

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:

  1. 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.
  2. 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_start and UUID1_SendNotify are custom interfaces of this official project (implemented in ble_interface.c); hosal_uart_* and bl_sys_init are 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_t struct 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_t struct pointer
  • data: receive buffer pointer, required
  • expect_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), required
  • data: 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 example 50 (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, shaped void task(void *arg), required
  • name: task name string (for debugging); this example "TaskUart" / "main_entry"
  • stack: task stack size (in words); this example 2048 (serial task) / 1024 (main task)
  • param: parameter pointer passed to the entry function; pass NULL if none
  • prio: task priority, values: 0 (lowest) ~ 19 (highest, per SDK config); this example 15
  • handle: task handle output pointer; pass NULL if 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
c

#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.

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