Skip to content

Overview

The Master is the active side of BLE communication: it scans (looks for) nearby advertising devices, initiates a connection once it finds its target, and after connecting the two sides can exchange data freely. This tutorial makes the Ai-WB2 play the master role, connecting directionally to a slave by MAC address (the Bluetooth device's "house number"), and transparently forwarding data received on the serial port (UART — the channel that passes data bit by bit between the computer and the board) over BLE to the slave — "serial ⇄ Bluetooth" two-way transparent transfer. Ideal for apps like "phone/gateway reading sensors".

In plain words: the master is like you holding a phone "looking for friends": first prick up your ears and listen for who's advertising (shouting: "I'm here!"), then walk up and "add as friend" (connect) once you hear the target; after becoming friends you can pass notes back and forth (transfer data). The board in this tutorial is that "friend-finder": it precisely locates the agreed slave by house number (MAC address), and after adding it as a friend, whatever you type into its serial port on the computer is delivered verbatim into the slave's hands.

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_master; the code can be found directly in the local SDK.

🎯Page GoalMake the Ai-WB2 a BLE master that automatically connects to the slave by MAC address with auto-reconnect enabled, achieving two-way transparent transfer between serial and Bluetooth.
🧰Prerequisites① Two Ai-WB2 boards (this one as master; the other flashed with the [BLE Slave](./ble_slave) example as slave) ② Two Type-C data cables ③ Development environment set up per [SDK Installation](../../sdk/sdk_intro).
🔗RelatedMaster/slave role concepts: [BLE Introduction](./ble_intro); the slave example: [BLE Slave](./ble_slave).

Enter the Example Project

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

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

Note: cd is the “change directory” command — entering the master 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, ble_interface.c, ble_common_api.c, ble_central_api.c); the BLE scan/connect implementation is spread across ble_common_api.c and ble_central_api.c. This page only shows main.c — see the official project for the rest.

Project structure:

File Purpose
ble_master/main.c Main program: init, scan/connect, serial transparent transfer logic — the main file this tutorial looks at
ble_master/ble_interface.c/.h Project’s custom interface layer (BLE function declarations for main.c to call)
ble_master/ble_common_api.c Common BLE API implementation (including axk_HalBleInit)
ble_master/ble_central_api.c Master-role API implementation (scan/connect/send, including the connection status variable)
Modify the Slave's MAC Address

Open ble_master/main.c and find the MAC address array at the top (placeholder = sample content pre-written in the code, to be replaced with your real value):

/*填写从机mac地址*/
static uint8_t slave_mac[6] = {0x88, 0x88, 0x88, 0x88, 0x88, 0x88};

Change {0x88, 0x88, 0x88, 0x88, 0x88, 0x88} to your slave board’s actual MAC address. How to find the slave’s MAC: scan with a phone Bluetooth app (e.g. nRF Connect) — the slave’s advertised device info shows its MAC (shaped 88:88:88:88:88:88).

💡 The third parameter BLE_MASTER_AUTOCONN_ENABLE enables auto-reconnect: after the connection drops, the master automatically looks for the slave again. If the MAC is wrong or the slave is off, it keeps printing no ble connect!.

Write the Code

Open ble_master/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_master/ble_master/main.c).

Code highlights:

API Purpose
axk_HalBleInit() Initializes the BLE master feature (defined in ble_common_api.c); without it the scan/connect below won’t work
axk_HalBleCentralStartScan() Starts scanning nearby advertising devices; without scanning you can’t discover the slave
axk_HalBleCentralConnect(slave_mac, NULL, 1) Directionally connects to the slave by MAC with auto-reconnect; a wrong MAC can never connect
bleuart_connect_status == 1 Connection status flag (1 = connected); sending data while disconnected prints no ble connect!
axk_HalBleCentralTTWrite(ret, data) Sends the serial-received data to the slave over BLE; negative returns correspond to different error reasons
hosal_uart_receive(&ble_uart_dev, ...) Polls the serial port — the entry point for data when you type in the serial assistant
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_master.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 master 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. Wire and flash the two boards separately.

Run and Verify

Power on both the master and slave boards (the slave should already be flashed with the BLE Slave example). Open two serial assistant windows on the master: one to watch logs, one to send data.

Open the serial assistant (baud rate 115200 — this example sets the serial to 115200 in the hosal_uart_dev_t config); on boot the master prints:

MY BLE MASTER

After the connection succeeds, type any character on the master’s serial and the slave’s serial should receive it verbatim; conversely, characters sent on the slave’s serial arrive at the master’s serial — that’s “two-way transparent transfer”.

The master printing MY BLE MASTER and establishing a connection with the slave (no more no ble connect!) means success; if it keeps printing no ble connect!, the slave wasn’t connected (MAC address / slave powered on? / distance), see the FAQ at the end.

💡 Advanced verification: unplug the slave’s power while connected, then restore it — the master auto-reconnects (BLE_MASTER_AUTOCONN_ENABLE kicks in), and transparent transfer resumes without restarting the master.


API Summary for This Tutorial

Note: the axk_HalBle* family are custom interfaces of this official project (defined in ble_common_api.c / ble_central_api.c, declared with extern for main.c to call) — they are not part of the SDK platform layer; 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=1, 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_send(uart, txbuf, size)

Sends a block of data over the serial (used here to print logs and error hints).

Parameters:

  • uart: hosal_uart_dev_t struct pointer
  • txbuf: pointer to the data to send, required
  • size: number of bytes to send

Return: bytes sent (> 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 "computer input → Bluetooth transparent transfer").

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

axk_HalBleInit

Initializes the BLE master feature (project-custom API, defined in ble_common_api.c) — the first step of all master operations.

Return: uint8_t; 0 on success (project convention); non-zero on failure

axk_HalBleCentralStartScan

Starts scanning nearby advertising Bluetooth devices (project-custom API, defined in ble_central_api.c).

Return: uint8_t; 0 on success; non-zero on failure

axk_HalBleCentralConnect(mac, uuid, autoConnect)

Directionally connects to the slave by MAC address (or UUID) (project-custom API, defined in ble_central_api.c).

Parameters:

  • mac: target slave MAC address array (6 bytes), required (this example slave_mac)
  • uuid: target service UUID; this example passes NULL (exact MAC match)
  • autoConnect: whether to auto-reconnect, values: BLE_MASTER_AUTOCONN_ENABLE (1, enabled) / 0 (disabled)

Return: uint8_t; 0 on success; non-zero on failure

axk_HalBleCentralTTWrite(len, data)

Sends data to the connected slave over BLE (project-custom API, defined in ble_central_api.c).

Parameters:

  • len: data length (uint16_t), required
  • data: data pointer, required

Return: int; >= 0 sent successfully; -1 connection status abnormal, -2 data length error, -3 data empty, other negatives are send failures

vTaskDelay(ms)

Suspends the current task for the given milliseconds, yielding the CPU to other tasks.

Parameters:

  • ms: delay in milliseconds; this example 100 (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 "ble master"
  • stack: task stack size (in words); this example 1024
  • 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_master/main.c source, identical to the official example (applications/bluetooth/ble_master/ble_master/main.c). This project consists of multiple source files (main.c, ble_interface.c, ble_common_api.c, ble_central_api.c); this page only shows main.c — see the official project for the rest:

📜 Click to expand the full main.c code
c
/*
 * Copyright (c) 2020 Bouffalolab.
 *
 * This file is part of
 *     *** Bouffalolab Software Dev Kit ***
 *      (see www.bouffalolab.com).
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *   1. Redistributions of source code must retain the above copyright notice,
 *      this list of conditions and the following disclaimer.
 *   2. Redistributions in binary form must reproduce the above copyright notice,
 *      this list of conditions and the following disclaimer in the documentation
 *      and/or other materials provided with the distribution.
 *   3. Neither the name of Bouffalo Lab nor the names of its contributors
 *      may be used to endorse or promote products derived from this software
 *      without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
#include <FreeRTOS.h>
#include <task.h>
#include <timers.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>

#include <vfs.h>
#include <aos/kernel.h>
#include <aos/yloop.h>
#include <event_device.h>
#include <cli.h>

#include <lwip/tcpip.h>
#include <lwip/sockets.h>
#include <lwip/netdb.h>
#include <lwip/tcp.h>
#include <lwip/err.h>
#include <netutils/netutils.h>

#include <bl602_glb.h>
#include <bl602_hbn.h>

#include <bl_uart.h>
#include <bl_chip.h>
#include <bl_sec.h>
#include <bl_cks.h>
#include <bl_irq.h>
#include <bl_dma.h>
#include <bl_adc.h>
#include <bl_timer.h>
#include <bl_gpio_cli.h>
#include <bl_wdt_cli.h>
// #include <hal_uart.h>
#include <hal_sys.h>
#include <hal_gpio.h>
#include <hal_boot2.h>
#include <hal_board.h>
#include <looprt.h>
#include <loopset.h>
#include <bl_sys_time.h>
#include <bl_sys_ota.h>
#include <bl_romfs.h>
#include <fdt.h>
#include <bl_sys.h>
#include <bl_timer.h>
#include <easyflash.h>
#include <bl60x_fw_api.h>
#include <utils_log.h>
#include <libfdt.h>
#include <blog.h>
// #include <ble_cli_cmds.h>
#include <hosal_uart.h>
#include "ble_interface.h"

/*填写从机mac地址*/
static uint8_t slave_mac[6] = {0x88, 0x88, 0x88, 0x88, 0x88, 0x88};

#define OS_CMP(s1, s2) (strcmp(s1, s2) == 0)
extern unsigned char bleuart_connect_status;
extern uint8_t axk_HalBleInit();
extern uint8_t axk_HalBleCentralStartScan(void);
extern uint8_t axk_HalBleCentralConnect(uint8_t *mac, uint8_t *uuid, uint8_t autoConnect);
extern int axk_HalBleCentralTTWrite(uint16_t len, uint8_t *data);
hosal_uart_dev_t ble_uart_dev = {
    .config = {
        .uart_id = 1,
        .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 bleuart_printf(char *buf)
{
  hosal_uart_send(&ble_uart_dev, buf, strlen(buf));
}

void ble_user_init(void)
{
  axk_HalBleInit();
  axk_HalBleCentralStartScan();                                          // 扫描周围的蓝牙设备
  axk_HalBleCentralConnect(slave_mac, NULL, BLE_MASTER_AUTOCONN_ENABLE); // 通过MAC地址和UUID指定连接从机,开启自动重连
}

int str2hex(char *pbuf, int len)
{
  int i = 0;
  for (i = 0; i < len; i++)
  {
    if (((pbuf[i] >= '0') && (pbuf[i] <= '9')) || ((pbuf[i] >= 'A') && (pbuf[i] <= 'F')) || ((pbuf[i] >= 'a') && (pbuf[i] <= 'f')))
    {
      if ((pbuf[i] >= '0') && (pbuf[i] <= '9'))
      {
        pbuf[i] -= '0';
      }
      else if (((pbuf[i] >= 'A') && (pbuf[i] <= 'F')))
      {
        pbuf[i] -= 'A';
        pbuf[i] += 0x0A;
      }
      else
      {
        pbuf[i] -= 'a';
        pbuf[i] += 0x0A;
      }

      if (i % 2)
      {
        pbuf[i / 2] = (pbuf[i - 1] << 4) | pbuf[i];
      }
    }
    else
    {
      return -1;
    }
  }

  return 0;
}

static void ble_loop_proc(void *pvParameters)
{
  char data[250];
  int ret, rep;
  ble_user_init();
  while (1)
  {
    /* Uart receive poll */
    ret = hosal_uart_receive(&ble_uart_dev, data, sizeof(data));
    if (ret > 0)
    { /* Uart send poll */
      // hosal_uart_send(&ble_uart_dev, data, ret);
      if (bleuart_connect_status == 1)
      {
        rep = axk_HalBleCentralTTWrite(ret, (uint8_t *)data);
        if (rep >= 0)
        {
          // hosal_uart_send(&ble_uart_dev, data, ret);//
        }
        else if (rep == -1)
        {
          bleuart_printf("ble status error!\r\n");
        }
        else if (rep == -2)
        {
          bleuart_printf("ble data len error!\r\n");
        }
        else if (rep == -3)
        {
          bleuart_printf("ble data null!\r\n");
        }
        else
        {
          bleuart_printf("ble send fail!\r\n");
        }
      }
      else
        bleuart_printf("no ble connect!\r\n");
    }
    vTaskDelay(100);
  }
  vTaskDelete(NULL);
}

static void uart_init(void)
{
  hosal_uart_init(&ble_uart_dev);
}

void main()
{
  /*Init UART In the first place*/
  // bl_uart_init(0, 16, 7, 255, 255, 115200);//2 * 1000 * 1000
  uart_init();
  bleuart_printf("MY BLE MASTER\r\n");
  bl_sys_init(); // if use ble ,must init
  xTaskCreate(ble_loop_proc, "ble master", 1024, NULL, 15, NULL);
}

FAQ & Troubleshooting

⚠️ Keeps printing no ble connect!
Cause: the MAC wasn't changed to the slave's actual one, the slave is off/not advertising, or the two boards are too far apart
Fix: check the slave's actual MAC with a phone Bluetooth app, replace slave_mac, rebuild and reflash; confirm the slave is flashed and powered; bring the boards closer (within 1 m) for testing

⚠️ Can't see the MY BLE MASTER log on the serial
Cause: wrong baud rate in the serial assistant — this example configures the serial at 115200, not the usual 921600
Fix: change the serial assistant baud rate to 115200; confirm the serial number is right (ls /dev/ttyUSB* to check)

⚠️ Connected, but the master's data doesn't reach the slave
Cause: the slave isn't flashed with the ble_slave example, or the slave's service UUID doesn't match
Fix: confirm the slave board is flashed with the BLE Slave example (serial prints [OS] proc_main_entry task...); data may drop when the two devices are far apart — test close together

⚠️ 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_master; the project's ble_interface.c etc. all 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 master's serial prints MY BLE MASTER, stops printing no ble connect! after connecting to the slave, and characters typed on the master's serial arrive at the slave's serial while characters typed on the slave's serial arrive at the master's — the two-way transparent transfer is verified.

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