Skip to content

由 WT_0213 贡献,Ai-Thinker 进行整理

目前实现上电后自动打开AP模式,连接无线AP后。打开网页提交要连接的WIFI名称 和 WIFI密码 点击提交。相应POST提交的WIFI名称和密码。

以下代码添加了关键点注释,方便大家理解。

发帖积分恢复了,又充满了动力。😄

演示

首先设备上电,上电后在电脑或手机上可以看到如下热点

点击连接

然后会让你输入密码

这里密码是:1234567879

然后通过浏览器输入:

192.168.169.1

可以看到如下界面,简单写了个配置页面效果:

输入要连接的WIFI名称与密码,点击提交。

成功后返回提交的WIFI名和密码;

介绍一下实现方式和思路

打开AP模式

html
static void start_ap(void)
{
    wifi_mgmr_ap_params_t config = { 0 };

    config.channel = 3;
    config.key = USER_AP_PASSWORD;
    config.ssid = USER_AP_NAME;
    config.use_dhcpd = 1;

    if (wifi_mgmr_conf_max_sta(2) != 0) {
        return 5;
    }
    if (wifi_mgmr_ap_start(&config) == 0) {
        return 0;
    }
}

启动http_server

html
void mhttp_server_init()
{
    //常用变量
    int ss, sc;
    struct sockaddr_in server_addr;
    struct sockaddr_in client_addr;
    int snd_size = 0; /* 发送缓冲区大小 */
    socklen_t optlen; /* 选项值长度 */
    // int optlen;
    int err;
    socklen_t addrlen;
    // int addrlen;

    //建立套接字
    ss = socket(AF_INET, SOCK_STREAM, 0);

    if (ss < 0)
    {
        printf("socket error\n");
    }

    /*设置服务器地址*/
    bzero(&server_addr, sizeof(server_addr));

    /*清零*/
    server_addr.sin_family = AF_INET;
    /*协议族*/
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY); /*本地地址*/
    server_addr.sin_port = htons(80);
    /*服务器端口*/

    /*绑定地址结构到套接字描述符*/
    err = bind(ss, (struct sockaddr *)&server_addr, sizeof(server_addr));

    if (err < 0)
    {
        printf("bind error\n");
        return -1;
    }

    /*设置侦听*/
    err = listen(ss, 7);

    if (err < 0)
    {
        printf("listen error\n");
        return -1;
    }

    addrlen = sizeof(struct sockaddr_in);
    MYPARM parm11;
    while (1)
    {
        printf("accept start\r\n");
        sc = accept(ss, (struct sockaddr *)&client_addr, &addrlen);
        if ((sc < 0) || (mysemaphoreflag > 0))
        {
            printf("accept fail sc is:%d  semaphore is:%d\r\n", sc, mysemaphoreflag);
            if (sc > 0)
            {
                close(sc);
            }
            continue;
        }

        parm11.sc = sc;
        parm11.buf = NULL;

        mysemaphoreflag++;
        http_server_thread(&parm11);
        vTaskDelay(1);
    }
}

等待客户端连接即可。为了使设备启动状态更直观,增加了开机亮灯操作。启动后自动打开绿灯。

代码如下:

html
    gpio = bflb_device_get_by_name("gpio");
    bflb_gpio_init(gpio, GPIO_PIN_14, GPIO_OUTPUT| GPIO_PULLUP | GPIO_SMT_EN | GPIO_DRV_0);
    bflb_gpio_set(gpio, GPIO_PIN_14);

主要代码讲解

main.c

c
int main(void)
{
    //中开启时钟
    board_init();
    // 亮绿灯
    gpio = bflb_device_get_by_name("gpio");
    bflb_gpio_init(gpio, GPIO_PIN_14, GPIO_OUTPUT| GPIO_PULLUP | GPIO_SMT_EN | GPIO_DRV_0);
    bflb_gpio_set(gpio, GPIO_PIN_14);

    //设置中断分组
    bflb_irq_set_nlbits(4);
    //设置中断优先级
    bflb_irq_set_priority(37, 3, 0);
    bflb_irq_set_priority(WIFI_IRQn, 1, 0);

    // 拿到 gpio
    gpio = bflb_device_get_by_name("gpio");
    // 拿到 uart
    uart0 = bflb_device_get_by_name("uart0");
    shell_init_with_task(uart0);

    // 初始化tcp ip
    tcpip_init(NULL, NULL);
    wifi_start_firmware_task();

    // 创建http服务
    create_http_server_task();
    vTaskStartScheduler();
    while (1) {
    }
}

创建http服务

html
void create_http_server_task(void)
{
    MuxSem_Handle = xSemaphoreCreateMutex();
    if (NULL != MuxSem_Handle)
    {
        printf("MuxSem_Handle creat success!\r\n");
    }

    xTaskCreate(http_server_task, (char*)"fw", WIFI_HTTP_SERVER_STACK_SIZE, NULL, HTTP_SERVERTASK_PRIORITY, &http_server_task_hd);
}

这里使用了xTaskCreate,FreeRTOS的任务。

不了解的可以看下Ai-M61-32S AP 配网学习 之 FreeRTOS任务 https://bbs.ai-thinker.com/forum.php?mod=viewthread&tid=43670

接下来是 http_server_task

html
void http_server_task(void* param)
{
    // 打开AP
    start_ap();
    // 启动http服务,也就是响应网页的服务
    mhttp_server_init();
}

这里比较简单。

打开ap代码

html
static void start_ap(void)
{
    wifi_mgmr_ap_params_t config = { 0 };
    // 通道
    config.channel = 3;
    // 设置AP热点名称
    config.ssid = USER_AP_NAME;
    // 设置密码
    config.key = USER_AP_PASSWORD;
    // 启动dhcp
    config.use_dhcpd = 1;

    // 设置最大连接数
    if (wifi_mgmr_conf_max_sta(2) != 0) {
        return 5;
    }
    // 启动AP模式
    if (wifi_mgmr_ap_start(&config) == 0) {
        return 0;
    }
}

WIFI信息设置

html
#define USER_AP_NAME "Ai-M61-32s"
#define USER_AP_PASSWORD "123456789"

然后是http服务的启动

mlwip_https.c

html
void mhttp_server_init()
{
    //常用变量
    int ss, sc;
    struct sockaddr_in server_addr;
    struct sockaddr_in client_addr;
    int snd_size = 0; /* 发送缓冲区大小 */
    socklen_t optlen; /* 选项值长度 */
    // int optlen;
    int err;
    socklen_t addrlen;
    // int addrlen;

    //建立套接字
    ss = socket(AF_INET, SOCK_STREAM, 0);

    if (ss < 0)
    {
        printf("socket error\n");
    }

    /*设置服务器地址*/
    bzero(&server_addr, sizeof(server_addr));

    /*清零*/
    server_addr.sin_family = AF_INET;
    /*协议族*/
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY); /*本地地址*/
    server_addr.sin_port = htons(80);
    /*服务器端口*/

    /*绑定地址结构到套接字描述符*/
    err = bind(ss, (struct sockaddr *)&server_addr, sizeof(server_addr));

    if (err < 0)
    {
        printf("bind error\n");
        return -1;
    }

    /*设置侦听*/
    err = listen(ss, 7);

    if (err < 0)
    {
        printf("listen error\n");
        return -1;
    }

    addrlen = sizeof(struct sockaddr_in);
    MYPARM parm11;
    while (1)
    {
        printf("accept start\r\n");
        sc = accept(ss, (struct sockaddr *)&client_addr, &addrlen);
        if ((sc < 0) || (mysemaphoreflag > 0))
        {
            printf("accept fail sc is:%d  semaphore is:%d\r\n", sc, mysemaphoreflag);
            if (sc > 0)
            {
                close(sc);
            }
            continue;
        }

        parm11.sc = sc;
        parm11.buf = NULL;

        mysemaphoreflag++;
        http_server_thread(&parm11);
        vTaskDelay(1);
    }
}

接下来就是响应请求

📜 点击展开 http_server_thread 完整代码
html
void http_server_thread(void *msg)
{
    // printf("http_server_thread\r\n");
    MYPARM *parm11;
    parm11 = (MYPARM *)msg;

    int sc;
    char readbuffer[1024];
    int size = 0;
    char command[1024];
    char head_buf[1000];
    memset(command, 0, sizeof(command));
    memset(head_buf, 0, sizeof(head_buf));
    sc = parm11->sc;

    memset(readbuffer, 0, sizeof(readbuffer));

    while (1)
    {
        // printf("read stop\r\n");
        size = read(sc, readbuffer, 1024);
        // int rc = recv(sc, readbuffer, sizeof(readbuffer), 0);
        printf("read len:%d\r\n", size);
        printf("get:%s\r\n", readbuffer);

        if (size <= 0)
        {
            printf("size <= 0\r\n");
            break;
        }
        int len = get_http_command(readbuffer, command); //得到http 请求中 GET后面的字符串
        printf("get:%s len:%d\r\n", command, len);

        if (strcmp(command, "/") == 0)
        {
            printf("command1\r\n");
            streatask = 0;
            sprintf(head_buf, "HTTP/1.1 200 OK\r\nContent-Length: %d\r\nContent-Type: text/html;charset=UTF-8\r\n\r\n", sizeof(html_page));

            // head_buf
            // strlen(index_ov2640_html)
            // 返回html页面
            int ret = write(sc, head_buf, strlen(head_buf));
            if (ret == -1)
            {
                printf("send failed");
                close(sc);
                mysemaphoreflag--;
                return NULL;
            }
            ret = write(sc, html_page, sizeof(html_page));
            if (ret < 0)
            {
                printf("text write failed");
            }
            close(sc);
            mysemaphoreflag--;
            break;
        }
        else if (strstr(command, "set"))
        {
            printf("set\r\n");
            streatask = 0;
            // 获取POST提交过来的数据,拿到ssid部分
            char* wifiCfg = strstr(readbuffer, "ssid");

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

            sprintf(head_buf, "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: 433\r\nAccess-Control-Allow-Origin: *\r\n\r\n");

            int ret = write(sc, head_buf, strlen(head_buf));
            if (ret == -1)
            {
                printf("send failed");
                close(sc);
                mysemaphoreflag--;
                return NULL;
            }

            // 将post参数切开,分别拿到 ssid 和 pwd
            char* ssid = strtok(wifiCfg, "&");
            char* pwd =  strtok(NULL, "&");

            // 将ssid参数切开,分别拿到 key 和 value
            char* ssidKey = strtok(ssid, "=");
            char* ssidValue = strtok(NULL, "=");
            // 将pwd参数切开,分别拿到 key 和 value
            char* pwdKey = strtok(pwd, "=");
            char* pwdValue = strtok(NULL, "=");

            // ============================================

            // 待实现 将 wifi 信息写入 存储

            // 开启 sta 模式,连接WIFI

            // ============================================

            printf("OK ssid:%s, pwd:%s  \r\n", ssidValue, pwdValue);

            // 创建cJSON
            cJSON *json = cJSON_CreateObject();
            cJSON_AddStringToObject(json, "ssid", ssidValue);
            cJSON_AddStringToObject(json, "pwd", pwdValue);

            char data_buf[1024];

            sprintf(data_buf, "%s\r\n\r\n", cJSON_Print(json));

            printf("OK data_buf:%s  \r\n", data_buf);
            // static char data_buf[] = "{\"ssid\":1,\"pwd\":0}";
            // ret = write(sc, data_buf, 433);
            // 将获取到的数据通过json形式返回
            ret = write(sc, data_buf, sizeof(data_buf));
            if (ret < 0)
            {
                printf("text write failed");
            }
            close(sc);
            mysemaphoreflag--;
            break;
        }
        else
        {
            streatask = 0;
            close(sc);
            mysemaphoreflag--;
        }
        // write(sc,readbuffer,size);

    }
    //关中断
    // free(parm11->buf);
    // vTaskDelete(NULL);
    //开中断

}

html 代码 page.h 很简单的界面,可以做很多优化。

html
static const unsigned char html_page[] = R"(
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>WiFi 配置</title>
    <style>
        body {
            font-family: "Microsoft YaHei", Arial, sans-serif;
            background: #f5f7fa;
            display: flex;
            justify-content: center;
            align-items: center;
            min-height: 100vh;
            margin: 0;
        }
        .card {
            background: #fff;
            border-radius: 10px;
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
            padding: 32px 40px;
            width: 320px;
            text-align: center;
        }
        h1 {
            font-size: 20px;
            color: #333;
            margin: 0 0 8px 0;
        }
        p.tip {
            font-size: 13px;
            color: #888;
            margin: 0 0 24px 0;
        }
        input[type="text"] {
            width: 100%;
            box-sizing: border-box;
            padding: 10px 12px;
            margin-bottom: 14px;
            border: 1px solid #d9d9d9;
            border-radius: 6px;
            font-size: 14px;
            outline: none;
        }
        input[type="text"]:focus {
            border-color: #1677ff;
        }
        input[type="submit"] {
            width: 100%;
            padding: 10px 0;
            border: none;
            border-radius: 6px;
            background: #1677ff;
            color: #fff;
            font-size: 15px;
            cursor: pointer;
        }
        input[type="submit"]:hover {
            background: #4096ff;
        }
    </style>
</head>
<body>
    <div class="card">
        <h1>WiFi 配置</h1>
        <p class="tip">请输入路由器名称和密码,点击提交后设备将自动连接。</p>
        <form method="post" action="/set">
            <input name="ssid" type="text" placeholder="WiFi 名称 (SSID)">
            <input name="pwd" type="text" placeholder="WiFi 密码">
            <input type="submit" value="提交">
        </form>
    </div>
</body>
</html>
)";

到这里基本,就可以实现 AP 配网的一些前置条件了。

下一步就是将 WIFI信息保存到 存储。

WIFI相关参考文档。

WiFi API指南 — 安信可科技 documentation (wb2-api-web.readthedocs.io)

Wi-Fi Manager — BL IoT SDK release_bl_iot_sdk_1.6.39-238-gf5ba0a7ee 文档 (bouffalolab.github.io)

遇到问题?

如有其他问题,请到统一的提问与讨论区:Ai-Thinker Discussions

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