Skip to content

Concepts First

  • Central (master): the device that actively scans and connects. The example scans for a target peripheral, connects, discovers services and continuously writes data.
  • GATT discovery: after connecting, the central must "discover" the peripheral's services and characteristics and obtain handles before reading/writing. The example discovers primary services → characteristics → descriptors in order.
  • Characteristic properties: the example prints each characteristic's properties (read/write/notify etc.) and automatically subscribes to notifiable characteristics.
  • Writing data: the example uses bt_gatt_write_without_response to write 244 bytes (0x00–0xFF sequence) every second, testing throughput and connectivity.

Example Overview

This page covers the central example in the official Bouffalo SDK (examples/btble/central):

  • bt_le_scan_start scans actively; the device_found callback filters by device name (CONFIG_BT_DEVICE_NAME, default BTBLE-DEV);
  • on finding the target, bt_le_scan_stop stops scanning and bt_conn_create_le initiates the connection;
  • after connecting, discovers primary services → characteristics → descriptors (bt_gatt_discover), printing UUIDs and properties;
  • creates a write task for writable characteristics (244 bytes every second) and subscribes to notifiable ones;
  • on disconnect, automatically rescans (ble_disconnectedble_start_scan).
  • Related reference: pairs with the BLE Slave (peripheral) example; iBeacon advertising in iBeacon.

Operation Steps

1
Enter the Example Directory

Open a terminal and enter the SDK BLE central (master) example directory (prerequisite: set up the environment as in Quick Start (Linux) or Windows):

cd examples/btble/central
2
Build the Project

Both Ai-M61 and Ai-M62 use bl616:

make CHIP=bl616 BOARD=bl616dk
3
Flash the Firmware

Hold BOOT, briefly press EN/RST to enter download mode, then flash:

make flash CHIP=bl616 COMX=/dev/ttyUSB0
4
Prepare a Peripheral

The example scans for a peripheral named BTBLE-DEV (CONFIG_BT_DEVICE_NAME) and connects to its test service (UUID 07af27a5-...). Flash the BLE Slave example on another board, or prepare a connectable peripheral with the same name.

5
Run and Verify

Serial tool at 2000000 baud. The log shows Start scan successfully[DEVICE FOUND] ...Connection startingConnected: ... → service/characteristic discovery; then the module writes 244 bytes of test data to the write characteristic every second, logging Write success.

Code Execution Flow

The complete central flow from boot to continuous writes:

APIs Used by the Example

bt_le_scan_start(&scan_param, device_found)

Starts BLE scanning. The example uses active scan (BT_HCI_LE_SCAN_ACTIVE), duplicate filtering disabled, fast scan intervals.

Parameters:

  • scan_param: struct bt_le_scan_param
  • device_found: device-found callback

Return: 0 on success; negative error code

bt_conn_create_le(addr, &param)

Initiates a BLE connection by address; connection parameters (interval/latency/timeout) come from struct bt_le_conn_param.

Parameters:

  • addr: target device address
  • param: connection parameters

Return: struct bt_conn *; NULL on failure

bt_gatt_discover(conn, &discover_params)

Discovers services/characteristics/descriptors. The example uses BT_GATT_DISCOVER_PRIMARYCHARACTERISTICDESCRIPTOR in sequence; results arrive in discover_func.

Parameters:

  • conn: connection object
  • discover_params: discovery type and handle range

Return: 0 on success; negative error code

bt_gatt_write_without_response(conn, handle, data, len, false)

Writes data to a characteristic handle without waiting for acknowledgement; the example writes 244 bytes every second.

Parameters:

  • conn: connection object
  • handle: characteristic value handle
  • data / len: data and length

Return: 0 on success; negative error code

bt_gatt_subscribe(conn, &subscribe_params)

Subscribes to a notifiable characteristic; the peripheral's notifications arrive via notify_func.

Parameters:

  • conn: connection object
  • subscribe_params: subscription parameters (CCC handle, value, callback)

Return: 0 on success; negative error code

Complete Code

The full main.c source, identical to the official example (examples/btble/central). Collapsed by default; click to expand:

📜 Click to expand central/main.c full code
c
#include "shell.h"
#include <FreeRTOS.h>
#include "task.h"
#include "board.h"

#include "bluetooth.h"
#include "conn.h"
#include "conn_internal.h"
#if defined(BL702) || defined(BL602)
#include "ble_lib_api.h"
#else
#include "btble_lib_api.h"
#include "rfparam_adapter.h"
#endif

#if defined(BL616)
#include "bl616_glb.h"
#elif defined(BL618DG)
#include "bl618dg_glb.h"
#elif defined(BL616CL)
#include "bl616cl_glb.h"
#endif

#include "hci_driver.h"
#include "hci_core.h"
#include "gatt.h"
#include "bt_log.h"

#include "bflb_mtd.h"
#include "easyflash.h"

typedef struct{
    TaskHandle_t handle;
    uint32_t wr_hdl;
}wr_arg_t;

const char* discovery_str[] = {
    "primary",
    "secondary",
    "include",
    "characteristic",
    "descriptor",
    "attribute",
};

static u16_t gatt_mtu_size;
static wr_arg_t wr_arg;
static struct bflb_device_s *uart0;
static struct bt_conn *default_conn;
static struct bt_gatt_discover_params discover_params;
static struct bt_gatt_subscribe_params subscribe_params;
static void ble_start_scan(void);
static void start_discovery(u8_t type);

extern void shell_init_with_task(struct bflb_device_s *shell);

static void ble_write_task(void *arg)
{
    wr_arg_t* args = (wr_arg_t*)arg;
    uint8_t data[244] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09};

    for(int i = 0; i < sizeof(data); ++i){
        data[i] = i & 0xff;
    }

    while(args->handle){
        int err = bt_gatt_write_without_response(default_conn, (uint32_t)arg & 0xffff, data,
            (gatt_mtu_size - 3) > sizeof(data) ? sizeof(data) : (gatt_mtu_size - 3), false);
        if(err){
            printf("Write without response (err %d)\r\n", err);
        }
        printf("Write success\r\n");
        vTaskDelay(pdMS_TO_TICKS(1000));
    }

    printf("Deleted blewrite task\r\n");
    vTaskDelete(NULL);
}

void ble_write_task_set(bool enable, uint16_t handle)
{
    wr_arg.wr_hdl = handle + 0;
    if(!enable){
        if(wr_arg.handle){
            wr_arg.handle = NULL;
        }
        return;
    }

    if(xTaskCreate(ble_write_task, (char*)"blewrite", 256, (void*)&wr_arg, configMAX_PRIORITIES - 5, &wr_arg.handle) == pdPASS){
        printf("Create ble_write_task success, start write\r\n");
    }
    else{
        printf("Create ble_write_task failed\r\n");
    }
}

static u8_t notify_func(struct bt_conn *conn,
			struct bt_gatt_subscribe_params *params,
			const void *data, u16_t length)
{
    if (!params->value) {
        printf("Unsubscribed\r\n");
        params->value_handle = 0U;
        return BT_GATT_ITER_STOP;
    }
    printf("Subscribed\r\n");
    return BT_GATT_ITER_CONTINUE;
}

static void do_subscribe(uint16_t ccc_handle, uint16_t val)
{
    subscribe_params.ccc_handle = ccc_handle;
    subscribe_params.value = val;
    subscribe_params.notify = notify_func;

    int err = bt_gatt_subscribe(default_conn, &subscribe_params);
    if (err) {
        printf("Subscribe failed (err %d)\r\n", err);
    } else {
        printf("Subscribed starting\r\n");
    }
}

static void print_chrc_props(u8_t properties)
{
    printf("Properties: ");

    if (properties & BT_GATT_CHRC_BROADCAST) {
        printf("[bcast]\r\n");
    }
    if (properties & BT_GATT_CHRC_READ) {
        printf("[read]\r\n");
    }
    if (properties & BT_GATT_CHRC_WRITE) {
        printf("[write]\r\n");
    }
    if (properties & BT_GATT_CHRC_WRITE_WITHOUT_RESP) {
        printf("[write w/w rsp]\r\n");
    }
    if (properties & BT_GATT_CHRC_NOTIFY) {
        printf("[notify]\r\n");
    }
    if (properties & BT_GATT_CHRC_INDICATE) {
        printf("[indicate]\r\n");
    }
    if (properties & BT_GATT_CHRC_AUTH) {
        printf("[auth]\r\n");
    }
    if (properties & BT_GATT_CHRC_EXT_PROP) {
        printf("[ext prop]\r\n");
    }
}

static u8_t discover_func(struct bt_conn *conn, const struct bt_gatt_attr *attr, struct bt_gatt_discover_params *params)
{
    struct bt_gatt_service_val *gatt_service;
    struct bt_gatt_chrc *gatt_chrc;
    struct bt_gatt_include *gatt_include;
    char str[37];

    if (!attr) {
        printf( "Discover %s complete\r\n", discovery_str[discover_params.type]);
        if(discover_params.type == BT_GATT_DISCOVER_PRIMARY){
            start_discovery(BT_GATT_DISCOVER_CHARACTERISTIC);
        }
        else if(discover_params.type == BT_GATT_DISCOVER_CHARACTERISTIC){
            start_discovery(BT_GATT_DISCOVER_DESCRIPTOR);
        }
        else{
            memset(params, 0, sizeof(*params));
        }
        return BT_GATT_ITER_STOP;
    }

    switch (params->type) {
    case BT_GATT_DISCOVER_SECONDARY:
    case BT_GATT_DISCOVER_PRIMARY:
        gatt_service = attr->user_data;
        bt_uuid_to_str(gatt_service->uuid, str, sizeof(str));
        printf("Service %s found: start handle %x, end_handle %x\r\n", str, attr->handle, gatt_service->end_handle);
        break;
    case BT_GATT_DISCOVER_CHARACTERISTIC:
        gatt_chrc = attr->user_data;
        bt_uuid_to_str(gatt_chrc->uuid, str, sizeof(str));
        printf("Characteristic %s found: attr->handle %x  chrc->handle %x \r\n", str, attr->handle,gatt_chrc->value_handle);
        print_chrc_props(gatt_chrc->properties);
        if(gatt_chrc->properties & BT_GATT_CHRC_NOTIFY){
            do_subscribe(attr->handle + 2, BT_GATT_CCC_NOTIFY);
        }
        if(!wr_arg.handle){
            ble_write_task_set(true, gatt_chrc->value_handle);
        }
        break;
    case BT_GATT_DISCOVER_INCLUDE:
        gatt_include = attr->user_data;
        bt_uuid_to_str(gatt_include->uuid, str, sizeof(str));
        printf("Include %s found: handle %x, start %x, end %x\r\n", str, attr->handle,
            gatt_include->start_handle, gatt_include->end_handle);
        break;
    default:
        bt_uuid_to_str(attr->uuid, str, sizeof(str));
        printf("Descriptor %s found: handle %x\r\n", str, attr->handle);
        break;
    }

    return BT_GATT_ITER_CONTINUE;
}

static void start_discovery(u8_t type)
{
    int err;

    discover_params.func = discover_func;
    discover_params.start_handle = 0x0001;
    discover_params.end_handle = 0xffff;
    discover_params.type = type;
    discover_params.uuid = NULL;

    err = bt_gatt_discover(default_conn, &discover_params);
    if (err) {
        printf("Discover %s failed (err %d)\r\n", discovery_str[discover_params.type], err);
    } 
}

static void ble_connected(struct bt_conn *conn, u8_t err)
{
    char addr[BT_ADDR_LE_STR_LEN];

    if(conn->type != BT_CONN_TYPE_LE) {
        return;
    }

    bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
    if (err || conn->type != BT_CONN_TYPE_LE) {
        printf("Failed to connect to %s (%u) \r\n", addr, err);
        return;
    }

    printf("Connected: %s \r\n", addr);

    if (!default_conn) {
        default_conn = conn;
    }

    gatt_mtu_size = bt_gatt_get_mtu(default_conn);

    start_discovery(BT_GATT_DISCOVER_PRIMARY);
}

static void ble_disconnected(struct bt_conn *conn, u8_t reason)
{
    char addr[BT_ADDR_LE_STR_LEN];

    if(conn->type != BT_CONN_TYPE_LE){
        return;
    }

    bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
    printf("Disconnected: %s (reason %u) \r\n", addr, reason);

    if (default_conn == conn) {
        default_conn = NULL;
    }

    // restart scan
    ble_start_scan();

    // stop write task
    ble_write_task_set(false, 0);
}

static struct bt_conn_cb ble_conn_callbacks = {
    .connected	=   ble_connected,
    .disconnected	=   ble_disconnected,
};

static bool data_cb(struct bt_data *data, void *user_data)
{
    char *name = user_data;
    u8_t len;

    switch (data->type) {
    case BT_DATA_NAME_SHORTENED:
    case BT_DATA_NAME_COMPLETE:
        len = (data->data_len > 30 - 1)?(30 - 1):(data->data_len);
        memcpy(name, data->data, len);
        return false;		
    default:
        return true;
    }
}
static void device_found(const bt_addr_le_t *addr, s8_t rssi, u8_t evtype,
			 struct net_buf_simple *buf)
{
    char le_addr[BT_ADDR_LE_STR_LEN];
    char name[30];
    int ret;
    struct bt_conn* conn = NULL;
    struct bt_le_conn_param param = {
        .interval_min =  BT_GAP_INIT_CONN_INT_MIN,
        .interval_max =  BT_GAP_INIT_CONN_INT_MAX,
        .latency = 0,
        .timeout = 400,
    };

    (void)memset(name, 0, sizeof(name));
    bt_data_parse(buf, data_cb, name);

    if(!strcmp(name,  CONFIG_BT_DEVICE_NAME)){
        bt_addr_le_to_str(addr, le_addr, sizeof(le_addr));
        printf("[DEVICE FOUND]: %s, AD evt type %u, RSSI %i %s \r\n",le_addr, evtype, rssi, name);
        // Stop scan 
        ret = bt_le_scan_stop();
        if(ret){
            printf("Stop scan fail err = %d\r\n", ret);
            return;
        }

        // Do connect
        conn = bt_conn_create_le(addr, /*BT_LE_CONN_PARAM_DEFAULT*/&param);

        if(!conn){
            printf("Connection failed\r\n");
        }else{
            printf("Connection starting\r\n");
        }
    }
}

static void ble_start_scan(void)
{
    struct bt_le_scan_param scan_param;
    int err;

    memset(&scan_param, 0, sizeof(scan_param));

    scan_param.type = BT_HCI_LE_SCAN_ACTIVE;
    scan_param.filter_dup = BT_HCI_LE_SCAN_FILTER_DUP_DISABLE;
    scan_param.interval = BT_GAP_SCAN_FAST_INTERVAL;
    scan_param.window = BT_GAP_SCAN_FAST_WINDOW;

    err = bt_le_scan_start(&scan_param, device_found);

    if(err){
        printf("Failed to start scan (err %d) \r\n", err);
    }else{
        printf("Start scan successfully \r\n");
    }
}

static void bt_gatt_mtu_changed_cb(struct bt_conn *conn, int mtu)
{
    if(conn == default_conn){
        gatt_mtu_size = mtu;
    }
}

static void ble_notification_all_cb_t(struct bt_conn *conn, u16_t handle,const void *data, u16_t length)
{
    printf("handle %x, %s\r\n", handle, bt_hex(data, length));
}

void bt_enable_cb(int err)
{
    if (!err) {
        bt_addr_le_t bt_addr;
        bt_get_local_public_address(&bt_addr);
        printf("BD_ADDR:(MSB)%02x:%02x:%02x:%02x:%02x:%02x(LSB) \r\n",
            bt_addr.a.val[5], bt_addr.a.val[4], bt_addr.a.val[3], bt_addr.a.val[2], bt_addr.a.val[1], bt_addr.a.val[0]);

        bt_conn_cb_register(&ble_conn_callbacks);
        #if defined(BFLB_BLE_MTU_CHANGE_CB)
        bt_gatt_register_mtu_callback(bt_gatt_mtu_changed_cb);
        #endif

        // start advertising
        ble_start_scan();

        // Register notification callback
        bt_gatt_register_notification_callback(ble_notification_all_cb_t);
    }
}

static TaskHandle_t app_start_handle;

static void app_start_task(void *pvParameters)
{
    // Initialize BLE controller
    #if defined(BL702) || defined(BL602)
    ble_controller_init(configMAX_PRIORITIES - 1);
    #else
    btble_controller_init(configMAX_PRIORITIES - 1);
    #endif
    // Initialize BLE Host stack
    hci_driver_init();
    bt_enable(bt_enable_cb);

    vTaskDelete(NULL);
}

int main(void)
{
    board_init();

    configASSERT((configMAX_PRIORITIES > 4));

    uart0 = bflb_device_get_by_name("uart0");
    shell_init_with_task(uart0);

    bflb_mtd_init();
    /* ble stack need easyflash kv */
    easyflash_init();

    /* Init rf */
    if (0 != rfparam_init(0, NULL, 0)) {
        printf("PHY RF init failed!\r\n");
        return 0;
    }

    #if defined(BL618DG)
    #if defined(CONFIG_BTBLE_USE_STANDALONE_PATH)
    printf("cmd_set_btble_standalone\r\n");
    extern void cmd_set_btble_standalone(int argc, char **argv);
    cmd_set_btble_standalone(0, 0);
    #else
    printf("cmd_set_btble_combo\r\n");
    extern void cmd_set_btble_combo(int argc, char **argv);
    cmd_set_btble_combo(0, 0);
    #endif
    #endif

    xTaskCreate(app_start_task, (char *)"app_start", 1024, NULL, configMAX_PRIORITIES - 2, &app_start_handle);

    vTaskStartScheduler();

    while (1) {
    }
}

FAQ

Keeps scanning but no [DEVICE FOUND]

Confirm the peripheral is actually advertising and its name exactly matches CONFIG_BT_DEVICE_NAME (default BTBLE-DEV); keep the two devices close; the peripheral (e.g. the BLE Slave example) must be in a discoverable advertising state.

Write success always fails after connecting

The write length is min(gatt_mtu_size - 3, 244); if the peripheral has no writable (write/write-without-response) characteristic, writes fail — use the matching peripheral example to verify.

Have questions?

For any other questions, visit the unified Q&A and discussion board: Ai-Thinker Discussions

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