Skip to content

Concepts First

  • Soft-AP (software hotspot): the module itself creates a Wi-Fi hotspot without dedicated router hardware.
  • Router mode / AP+STA bridge: the module joins the router as STA while running an AP; phones joining the hotspot get their traffic bridged to the router, so they can go online. This "connection sharing" is the core of router mode.
  • Bridge interface: the STA and AP network interfaces are logically merged into one br3, so packets can be forwarded between them.
  • DHCP ownership: -d 0 lets the router (through the bridge) assign IPs instead of the AP, putting the phone and the module on the same subnet for easy mutual access.

Example Overview

This page is based on the ap_sta_bridge example in the official Bouffalo SDK (examples/wifi/ap_sta_bridge), which demonstrates router mode (AP+STA bridge):

  • board_init() / shell / rfparam_init() basic initialization;
  • additionally calls netifd_nano_init() to support bridge interfaces;
  • the rest matches the STA skeleton: register events → start Wi-Fi firmware and fhost;
  • actual operations are shell commands: wifi_sta_connect -Dwifi_ap_startnetifd bridge create/add;
  • once bridged, phones on the hotspot share the router's Internet.
  • Sibling example: examples/wifi/sta/smartconfig_softap (auto-start AP with web-based provisioning; its README primarily targets BL602).

Operation Steps

1
Enter the Example Directory

Open a terminal and enter the AP+STA bridge example directory:

cd examples/wifi/ap_sta_bridge
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
Connect the STA to the Router

Serial tool at 2000000 baud. First connect the module’s STA interface to your router (-D disables DHCP on STA, which the bridge handles later):

wifi_sta_connect -D Your_SSID 12345678
5
Start the AP Hotspot

Start the AP: -s sets the hotspot name, -d 0 disables AP-side DHCP (the bridge handles it), -c sets the channel (match the STA’s channel):

wifi_ap_start -s tiny_ap -d 0 -c 6
6
Create the Bridge and Add Both Interfaces

Create a bridge interface br3 and add the STA interface (wl1) and the AP interface (wl2) to it; the module now behaves like a router:

netifd bridge create -b br3
netifd bridge add -b br3 -i wl1
netifd bridge add -b br3 -i wl2
7
Run and Verify

Connect a phone to the tiny_ap hotspot. The phone gets an IP from the router and can access the Internet; the bridge transparently forwards packets between the STA and AP interfaces (mat shows forwarding statistics).

Code Execution Flow

The complete flow from boot to bridged forwarding:

APIs Used by the Example

netifd_nano_init()

Initializes the lightweight network interface daemon (netifd) that provides the netifd bridge commands.

Parameters: none

Return: none

wifi_mgmr_sta_connect / wifi_mgmr_ap_start(params) / (if, ssid, ch, pwd, channel)

Connect the STA interface to the router and start the AP (via wifi_ap_start shell command).

Parameters:

  • -D: do not run DHCP on STA (bridge assigns later)
  • -s: AP SSID, -d 0: no AP DHCP, -c: channel

Return: none (see event logs)

netifd bridge create / add(-b br3) / (-b br3 -i wlX)

Create a bridge interface and attach network interfaces (wl1 STA, wl2 AP) so packets flow between them.

Parameters:

  • -b br3: bridge name
  • -i wlX: interface to attach

Return: none

Complete Code

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

📜 Click to expand ap_sta_bridge/main.c full code
c
/****************************************************************************
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.  The
 * ASF licenses this file to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the
 * License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include "FreeRTOS.h"
#include "task.h"
#include "timers.h"

#include <lwip/tcpip.h>
#include <lwip/sockets.h>
#include <lwip/netdb.h>

#include "bl_fw_api.h"
#include "fhost_api.h"
#include "wifi_mgmr_ext.h"
#include "wifi_mgmr.h"

#include "bflb_irq.h"
#include "bflb_uart.h"

#include "rfparam_adapter.h"
#include "async_event.h"

#include "board.h"
#include "shell.h"

#include "mm.h"
#include "netifd_nano.h"

#define DBG_TAG "MAIN"
#include "log.h"

struct bflb_device_s *gpio;

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

#define WIFI_STACK_SIZE  (1536)
#define TASK_PRIORITY_FW (16)

/****************************************************************************
 * Private Types
 ****************************************************************************/

/****************************************************************************
 * Private Data
 ****************************************************************************/

static struct bflb_device_s *uart0;


extern void shell_init_with_task(struct bflb_device_s *shell);
extern void wifi_event_handler(async_input_event_t ev, void *priv);

/****************************************************************************
 * Private Function Prototypes
 ****************************************************************************/

/****************************************************************************
 * Functions
 ****************************************************************************/

void wifi_start_firmware_task(void *param)
{
    LOG_I("Starting wifi ...\r\n");
    async_register_event_filter(EV_WIFI, wifi_event_handler, NULL);
    wifi_task_create();

    LOG_I("Starting fhost ...\r\n");
    fhost_init();

    vTaskDelete(NULL);
}

void wifi_event_handler(async_input_event_t ev, void *priv)
{
    uint32_t code = ev->code;

    switch (code) {
        case CODE_WIFI_ON_INIT_DONE: {
            LOG_I("[APP] [EVT] %s, CODE_WIFI_ON_INIT_DONE\r\n", __func__);
            wifi_mgmr_task_start();
        } break;
        case CODE_WIFI_ON_MGMR_DONE: {
            LOG_I("[APP] [EVT] %s, CODE_WIFI_ON_MGMR_DONE\r\n", __func__);
        } break;
        case CODE_WIFI_ON_SCAN_DONE: {
            LOG_I("[APP] [EVT] %s, CODE_WIFI_ON_SCAN_DONE\r\n", __func__);
            wifi_mgmr_sta_scanlist();
        } break;
        case CODE_WIFI_ON_CONNECTED: {
            LOG_I("[APP] [EVT] %s, CODE_WIFI_ON_CONNECTED\r\n", __func__);
            void mm_sec_keydump();
            mm_sec_keydump();
        } break;
        #ifdef CODE_WIFI_ON_GOT_IP_ABORT
        case CODE_WIFI_ON_GOT_IP_ABORT: {
            LOG_I("[APP] [EVT] %s, CODE_WIFI_ON_GOT_IP_ABORT\r\n", __func__);
        } break;
        #endif
        #ifdef CODE_WIFI_ON_GOT_IP_TIMEOUT
        case CODE_WIFI_ON_GOT_IP_TIMEOUT: {
            LOG_I("[APP] [EVT] %s, CODE_WIFI_ON_GOT_IP_TIMEOUT\r\n", __func__);
        } break;
        #endif
        case CODE_WIFI_ON_GOT_IP: {
            LOG_I("[APP] [EVT] %s, CODE_WIFI_ON_GOT_IP\r\n", __func__);
            LOG_I("[SYS] Memory left is %d Bytes\r\n", kfree_size(0));
        } break;
        case CODE_WIFI_ON_DISCONNECT: {
            LOG_I("[APP] [EVT] %s, CODE_WIFI_ON_DISCONNECT\r\n", __func__);
        } break;
        case CODE_WIFI_ON_AP_STARTED: {
            LOG_I("[APP] [EVT] %s, CODE_WIFI_ON_AP_STARTED\r\n", __func__);
        } break;
        case CODE_WIFI_ON_AP_STOPPED: {
            LOG_I("[APP] [EVT] %s, CODE_WIFI_ON_AP_STOPPED\r\n", __func__);
        } break;
        case CODE_WIFI_ON_AP_STA_ADD: {
            LOG_I("[APP] [EVT] [AP] [ADD] %lld\r\n", xTaskGetTickCount());
        } break;
        case CODE_WIFI_ON_AP_STA_DEL: {
            LOG_I("[APP] [EVT] [AP] [DEL] %lld\r\n", xTaskGetTickCount());
        } break;
        default: {
            LOG_I("[APP] [EVT] Unknown code %u \r\n", code);
        }
    }
}

int main(void)
{
    board_init();

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

    if (0 != rfparam_init(0, NULL, 0)) {
        LOG_I("PHY RF init failed!\r\n");
        return 0;
    }

    LOG_I("PHY RF init success!\r\n");

    netifd_nano_init();
    tcpip_init(NULL, NULL);
    xTaskCreate(wifi_start_firmware_task, "wifi init", 1024, NULL, 10, NULL);

    vTaskStartScheduler();

    while (1) {
    }
}

FAQ

Phone connects to the hotspot but cannot get an IP

Confirm the STA interface is connected to a working router first (wifi_sta_connect -D), the bridge br3 contains both wl1 and wl2, and the router's DHCP is enabled; also make sure the AP channel matches the STA channel.

Only AP or only STA seems to work

Check the interface names with netifd / ifconfig; the bridge commands must use the real STA (wl1) and AP (wl2) interface names printed by the SDK.

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