鸿蒙OS 的核心在于万物互联,Wifi 是最基础的连接通道,也是 Hi3861 的主要功能。

        前面所实现的外设驱动是单片机的基础功能,而 Wifi 才是 Hi3861的特色。因我那个年代,单片机内置 Wifi 的芯片很少,也就是到了 ESP8266、ESP32 的出现,才使得 Wifi 在单片机上得以普及。可那时我已退休,故基本没有接触过 Wifi 编程,这次算是首次。

        在上面的尝试过程中,我发现了使用海思的基础库有较全的底层驱动,在后续的系统消化过程中,发现海思还提供了类似于 STM32 Hal 库中类似的外设应用示例,基本覆盖了这款芯片的所有功能:

        初步尝试 Wifi,准备用 Hi3861 做一个Station(STA),因为后续做 IoT 设备,多数情况下 Hi3861是作为 STA 存在。

        继续按前述方式,我提要求,让豆包帮我编写。

        这次为了避免豆包走弯路,将海思的 Demo 程序 Wifi_Sta.c 喂给它,让它参考:


#include <unistd.h>
#include "hi_wifi_api.h"
#include "wifi_sta.h"
#include "lwip/ip_addr.h"
#include "lwip/netifapi.h"

#define APP_INIT_VAP_NUM    2
#define APP_INIT_USR_NUM    2

static struct netif *g_lwip_netif = NULL;

/* clear netif's ip, gateway and netmask */
void hi_sta_reset_addr(struct netif *pst_lwip_netif)
{
    ip4_addr_t st_gw;
    ip4_addr_t st_ipaddr;
    ip4_addr_t st_netmask;

    if (pst_lwip_netif == NULL) {
        printf("hisi_reset_addr::Null param of netdev\r\n");
        return;
    }

    IP4_ADDR(&st_gw, 0, 0, 0, 0);
    IP4_ADDR(&st_ipaddr, 0, 0, 0, 0);
    IP4_ADDR(&st_netmask, 0, 0, 0, 0);

    netifapi_netif_set_addr(pst_lwip_netif, &st_ipaddr, &st_netmask, &st_gw);
}

void wifi_wpa_event_cb(const hi_wifi_event *hisi_event)
{
    if (hisi_event == NULL)
        return;

    switch (hisi_event->event) {
        case HI_WIFI_EVT_SCAN_DONE:
            printf("WiFi: Scan results available\n");
            break;
        case HI_WIFI_EVT_CONNECTED:
            printf("WiFi: Connected\n");
            netifapi_dhcp_start(g_lwip_netif);
            break;
        case HI_WIFI_EVT_DISCONNECTED:
            printf("WiFi: Disconnected\n");
            netifapi_dhcp_stop(g_lwip_netif);
            hi_sta_reset_addr(g_lwip_netif);
            break;
        case HI_WIFI_EVT_WPS_TIMEOUT:
            printf("WiFi: wps is timeout\n");
            break;
        default:
            break;
    }
}

int hi_wifi_start_connect(void)
{
    int ret;
    errno_t rc;
    hi_wifi_assoc_request assoc_req = {0};

    /* copy SSID to assoc_req */
    rc = memcpy_s(assoc_req.ssid, HI_WIFI_MAX_SSID_LEN + 1, "Hisilicon", 9); /* 9:ssid length */
    if (rc != EOK) {
        return -1;
    }

    /*
     * OPEN mode
     * for WPA2-PSK mode:
     * set assoc_req.auth as HI_WIFI_SECURITY_WPA2PSK,
     * then memcpy(assoc_req.key, "12345678", 8).
     */
    assoc_req.auth = HI_WIFI_SECURITY_OPEN;

    ret = hi_wifi_sta_connect(&assoc_req);
    if (ret != HISI_OK) {
        return -1;
    }

    return 0;
}

int hi_wifi_start_sta(void)
{
    int ret;
    char ifname[WIFI_IFNAME_MAX_SIZE + 1] = {0};
    int len = sizeof(ifname);
    const unsigned char wifi_vap_res_num = APP_INIT_VAP_NUM;
    const unsigned char wifi_user_res_num = APP_INIT_USR_NUM;
    unsigned int  num = WIFI_SCAN_AP_LIMIT;

    ret = hi_wifi_init(wifi_vap_res_num, wifi_user_res_num);
    if (ret != HISI_OK) {
        return -1;
    }

    ret = hi_wifi_sta_start(ifname, &len);
    if (ret != HISI_OK) {
        return -1;
    }

    /* register call back function to receive wifi event, etc scan results event,
     * connected event, disconnected event.
     */
    ret = hi_wifi_register_event_callback(wifi_wpa_event_cb);
    if (ret != HISI_OK) {
        printf("register wifi event callback failed\n");
    }

    /* acquire netif for IP operation */
    g_lwip_netif = netifapi_netif_find(ifname);
    if (g_lwip_netif == NULL) {
        printf("%s: get netif failed\n", __FUNCTION__);
        return -1;
    }

    /* start scan, scan results event will be received soon */
    ret = hi_wifi_sta_scan();
    if (ret != HISI_OK) {
        return -1;
    }

    sleep(5);   /* sleep 5s, waiting for scan result. */

    hi_wifi_ap_info *pst_results = malloc(sizeof(hi_wifi_ap_info) * WIFI_SCAN_AP_LIMIT);
    if (pst_results == NULL) {
        return -1;
    }

    ret = hi_wifi_sta_scan_results(pst_results, &num);
    if (ret != HISI_OK) {
        free(pst_results);
        return -1;
    }

    for (unsigned int loop = 0; (loop < num) && (loop < WIFI_SCAN_AP_LIMIT); loop++) {
        printf("SSID: %s\n", pst_results[loop].ssid);
    }
    free(pst_results);

    /* if received scan results, select one SSID to connect */
    ret = hi_wifi_start_connect();
    if (ret != 0) {
        return -1;
    }

    return 0;
}

void hi_wifi_stop_sta(void)
{
    int ret;

    ret = hi_wifi_sta_stop();
    if (ret != HISI_OK) {
        printf("failed to stop sta\n");
    }

    ret = hi_wifi_deinit();
    if (ret != HISI_OK) {
        printf("failed to deinit wifi\n");
    }

    g_lwip_netif = NULL;
}

        重新建了一个项目 wifi_sta_demo,在豆包编写的程序中,加入 LED 闪烁,以便快速判断程序是否正确执行。

        这次顺带将 GPIO 的函数全部替换为海思基础库 hi_xx.h,感觉这个逻辑更清晰。

        为了解决上一篇文章中所提到的虚拟机内存撑死的问题,在这次调试前,首先解决编译和下载问题。即不用打开工程,使用命令完成程序的编译和下载(烧录)。

        编译比较简单,豆包帮我生成了两个命令行操作:

1、清除上次编译的结果:

        rm -rf out/*

2、编译:

        hb build -f
        在 Vscode 中的远程终端  ~/OpenHarmonu303/OpenHarmony/ 目录下先后执行上述两条命令即可。

        程序烧录有点麻烦,首先需要下载一个海思芯片的烧录软件:HiBurn.exe

        下载链接(豆包帮我找的):https://gitee.com/talkweb_oh/niobe/blob/master/applications/docs/tools/HiBurn.exe

        此软件直接可以运行,无需安装。

程序下载(烧录)的操作步骤如下

        首先在 Vscode 中使用 Download 命令将编译后的 xxx_allinone.bin 文件拷贝到 win 系统中。
在 win 环境下使用 HiBurn 软件烧入芯片。
HiBurn 操作步骤:
        1)启动软件
        2)选择串口
        3)导入 Bin 文件
        4)点选 Auto Burn
        5)将 Hi3861 的 GPIO0 引脚接 GND 
        6)  按住 Hi3861 板的 Reset 键
        7)点击 Hiburn 上的 connect
        8)松开 Reset 键
        9)在 Hiburn 下部提示窗开始有显示内容后,断开 GPIO0 的接地。
        此时可以看到 HiBurn 下部有烧录的进度条,直到显示完成( success),点击 disconnect,完成烧录。

        这样虽然没有原来打开工程、使用IDE环境方便,但彻底消除了虚拟机内存满导致死机的问题。

        为了能更方便的在 Vscode 环境中浏览相应的头文件和 Demo 程序,以及快速消除 Bug,我将打开的目录调整为:

1)~/OpenHarmony303/OpenHarmony/applications/sample/wifi-iot

(这是应用程序目录,自己编写的代码均在此)

2)~/OpenHarmony303/OpenHarmony/out

(这是输出目录,为了拷贝 bin,以及看 build.log 除错)

3)~/OpenHarmony303/OpenHarmony/device/hisilicon/hispark_pegasus/sdk_liteos

(这是 Hi3861 芯片的资源目录,查看头文件、示例程序均在此)

        打开这三个目录,用 htop 观察虚拟机内存,最高在 2 ~ 3G 之间,没有再出现过死机现象。

        豆包按照示例编写的程序基本没有出错,只是遇到一个小问题:

        原有的编译配置中,默认打开了测试功能,日志输出很多,导致我观察 Wifi 程序执行结果困难。于是请教豆包,在它的指导下,虽然遇到点挫折,但总算搞定了。要修改的配置文件在:

~/OpenHarmony303/OpenHarmony/vendor/hisilicon/hispark_pegasus/config.json

        这里面定义了应用程序编译时所包含的模块,将 Test 去掉后编译,测试输出没有了,只剩我程序中所打印的内容。

        最终程序如下:

#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include "hi_types_base.h"
#include "hi_wifi_api.h"
#include "hi_io.h"
#include "hi_gpio.h"
#include "lwip/ip_addr.h"
#include "lwip/netifapi.h"
#include "los_task.h"
#include "ohos_init.h"

#define LED_INTERVAL_TIME_MS 1000
#define LED_TEST_GPIO HI_GPIO_IDX_9 // for hispark_pegasus
#define LED_TEST_GPIO_NAME HI_IO_NAME_GPIO_9 // for hispark_taurus

/* ========= 修改为你的2.4G WiFi信息 ========= */
#define WIFI_SSID       "www.embedream.com_2.4G"
#define WIFI_PASSWD     "embedream"

/* ========= 路由器上显示的设备主机名 ========= */
#define WIFI_HOSTNAME   "Hi3861_01"

#define APP_INIT_VAP_NUM    2
#define APP_INIT_USR_NUM    2

static struct netif *g_lwip_netif = NULL;
static hi_u32 g_wifi_task_id = 0;

/**
 * @brief 设置DHCP Hostname(路由器后台显示的设备名)
 */
static void Wifi_SetHostName(const char *name)
{
    err_t ret;
    ret = netifapi_set_hostname(g_lwip_netif, name, strlen(name));
    if(g_lwip_netif == NULL || name == NULL)
    {
        printf("Wifi_SetHostName: netif or name is NULL\r\n");
        return;
    }
    printf("✅ Hostname set:\"%s\", ret=%d\r\n", name, ret);
}

/* 清空netif的ip地址 */
static void hi_sta_reset_addr(struct netif *pst_lwip_netif)
{
    ip4_addr_t st_gw;
    ip4_addr_t st_ipaddr;
    ip4_addr_t st_netmask;

    if (pst_lwip_netif == NULL) {
        printf("hisi_reset_addr::Null param of netdev\r\n");
        return;
    }
    IP4_ADDR(&st_gw, 0, 0, 0, 0);
    IP4_ADDR(&st_ipaddr, 0, 0, 0, 0);
    IP4_ADDR(&st_netmask, 0, 0, 0, 0);
    netifapi_netif_set_addr(pst_lwip_netif, &st_ipaddr, &st_netmask, &st_gw);
}

/* WiFi事件回调,完全沿用原版示例回调原型 */
static void wifi_wpa_event_cb(const hi_wifi_event *hisi_event)
{
    if (hisi_event == NULL)
        return;

    switch (hisi_event->event) {
        case HI_WIFI_EVT_SCAN_DONE:
            printf("WiFi: Scan results available\n");
            break;
        case HI_WIFI_EVT_CONNECTED:
            printf("WiFi: ✅ AP Connected, start DHCP\n");
            if(g_lwip_netif != NULL)
            {
                netifapi_dhcp_start(g_lwip_netif);
                /* 此处不能立刻打印IP,DHCP后台异步获取 */
            }
            break;
        case HI_WIFI_EVT_DISCONNECTED:
            printf("WiFi: ❌ Disconnected\n");
            if(g_lwip_netif != NULL)
            {
                netifapi_dhcp_stop(g_lwip_netif);
                hi_sta_reset_addr(g_lwip_netif);
            }
            break;
        case HI_WIFI_EVT_WPS_TIMEOUT:
            printf("WiFi: wps is timeout\n");
            break;
        default:
            break;
    }
}

/* 发起WiFi连接,支持WPA2‑PSK密码 */
static int hi_wifi_my_connect(void)
{
    int ret;
    errno_t rc;
    hi_wifi_assoc_request assoc_req = {0};

    rc = memcpy_s(assoc_req.ssid, HI_WIFI_MAX_SSID_LEN + 1, WIFI_SSID, strlen(WIFI_SSID));
    if (rc != EOK) {
        return -1;
    }

    /* WPA2‑PSK加密模式,家用路由器绝大多数是这个 */
    assoc_req.auth = HI_WIFI_SECURITY_WPA2PSK;
    rc = memcpy_s(assoc_req.key, HI_WIFI_MAX_KEY_LEN + 1, WIFI_PASSWD, strlen(WIFI_PASSWD));
    if (rc != EOK) {
        return -1;
    }

    ret = hi_wifi_sta_connect(&assoc_req);
    if (ret != HISI_OK) {
        printf("hi_wifi_sta_connect fail\n");
        return -1;
    }
    else
    {
        printf("hi_wifi_sta_connect success\n");
    }
    return 0;
}

/* WiFi业务任务 */
static void WifiStaTask(void)
{
    int ret;
    char ifname[WIFI_IFNAME_MAX_SIZE + 1] = {0};
    int len = sizeof(ifname);
    unsigned int num = WIFI_SCAN_AP_LIMIT;
    ip4_addr_t ip,mask,gw;
    err_t err;

    LOS_Msleep(1000);

    /* 1.wifi初始化,和原版示例保持一致 */
    ret = hi_wifi_deinit();
    if (ret != HISI_OK) {
        printf("hi_wifi_deinit fail\n");
        return;
    }
    LOS_Msleep(100);

    ret = hi_wifi_init(APP_INIT_VAP_NUM, APP_INIT_USR_NUM);
    if (ret != HISI_OK) {
        printf("hi_wifi_init fail\n");
        return;
    }
    else
    {
        printf("hi_wifi_init success\n");
    }

    /* 2.启动STA */
    ret = hi_wifi_sta_start(ifname, &len);
    if (ret != HISI_OK) {
        printf("hi_wifi_sta_start fail\n");
        return;
    }
    else
    {
        printf("hi_wifi_sta_start success, ifname = %s\n", ifname);
    }


    /* 3.注册事件回调 */
    ret = hi_wifi_register_event_callback(wifi_wpa_event_cb);
    if (ret != HISI_OK) {
        printf("register wifi event callback failed\n");
    }
    else
    {
        printf("register wifi event callback success\n");
    }

    /* 获取lwip netif句柄,DHCP依赖 */
    g_lwip_netif = netifapi_netif_find(ifname);
    if (g_lwip_netif == NULL) {
        printf("get netif failed\n");
        return;
    }
    else
    {
        printf("get netif success\n");
        // ========== 新增:设置主机名(DHCP启动前) ==========
        Wifi_SetHostName(WIFI_HOSTNAME);
    }

    /* 扫描AP */
    ret = hi_wifi_sta_scan();
    if (ret != HISI_OK) {
        printf("hi_wifi_sta_scan fail\n");
    }
    else
    {
        printf("hi_wifi_sta_scan success\n");
    }

    LOS_Msleep(5000);  /*等待扫描完成*/

    hi_wifi_ap_info *pst_results = malloc(sizeof(hi_wifi_ap_info) * WIFI_SCAN_AP_LIMIT);
    if (pst_results != NULL)
    {
        ret = hi_wifi_sta_scan_results(pst_results, &num);
        for (unsigned int loop = 0; (loop < num) && (loop < WIFI_SCAN_AP_LIMIT); loop++) {
            printf("scan SSID: %s\n", pst_results[loop].ssid);
        }
        free(pst_results);
    }

    /* 发起连接 */
    hi_wifi_my_connect();

    /* 循环读取IP地址 + LED闪烁 */
    while(1)
    {
        hi_gpio_set_ouput_val(LED_TEST_GPIO, HI_GPIO_VALUE0);
        LOS_Msleep(LED_INTERVAL_TIME_MS);
        hi_gpio_set_ouput_val(LED_TEST_GPIO, HI_GPIO_VALUE1);
        LOS_Msleep(LED_INTERVAL_TIME_MS);

        /* 轮询打印已经获取到的IP */
        err = netifapi_netif_get_addr(g_lwip_netif, &ip, &mask, &gw);
        if(err == ERR_OK && !ip4_addr_isany(&ip))
        {
            printf("✅ Board IP: %s\n", ip4addr_ntoa(&ip));
        }
    }
}

/* SYS_RUN应用入口 */
void WifiDemoEntry(void)
{
    hi_u32 ret;

    ret = hi_gpio_init();
    if (ret != HI_ERR_SUCCESS) {
        printf("===== ERROR ===== gpio -> hi_gpio_init ret:%d\r\n", ret);
        return;
    }
    printf("----- gpio init success-----\r\n");

    ret = hi_io_set_func(LED_TEST_GPIO_NAME, HI_IO_FUNC_GPIO_9_GPIO);
    if (ret != HI_ERR_SUCCESS) {
        printf("===== ERROR ===== gpio -> hi_io_set_func ret:%d\r\n", ret);
        return;
    }
    printf("----- io set func success-----\r\n");

    hi_gpio_set_dir(LED_TEST_GPIO, HI_GPIO_DIR_OUT);
    hi_gpio_set_ouput_val(LED_TEST_GPIO, HI_GPIO_VALUE1);

    TSK_INIT_PARAM_S taskParam = {0};
    taskParam.pfnTaskEntry = (TSK_ENTRY_FUNC)WifiStaTask;
    taskParam.uwStackSize = 4096;   /*用到lwip/malloc,栈调大一点*/
    taskParam.pcName = "WifiStaTask";
    taskParam.usTaskPrio = 6;
    LOS_TaskCreate(&g_wifi_task_id, &taskParam);
}

SYS_RUN(WifiDemoEntry);

        这里面有两处修改:

        1、原来程序没有设置模块名称(Hostname)的功能,豆包帮我添加了。

        2、原来程序中用全局变量 g_lwip_netif 获取 IP 地址,但由于是跨线程的(WiFi有自己的任务),故在 DHCP 完成 IP 地址分配后,不会刷新 g_lwip_netif ,需要显性获取。修改后正常。

        在编写代码中发现,现在的 Vscode 编辑器智能补代码的能力十分了得,几乎只敲一、二个字符,它就能根据上下文给出推荐语句,准确度很高,这估计是其内置 AI — Copilot  的功劳吧? 

        用 ping 测试,丢包率低于1%,Wifi 功能 OK。

        下一步尝试我最关注的功能:软总线

        之所以学习鸿蒙OS,就在于其软总线设计,我理解应该和 ROS 中的Topic、Service、Action 功能类似,完成设备间的信息互联。它是智能家居的基础

————————————————

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐