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 -D → wifi_ap_start → netifd 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-30 17:31:25