鸿蒙OS IoT 设备开发之基础 —— Wifi 连接
鸿蒙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 功能类似,完成设备间的信息互联。它是智能家居的基础。
————————————————
更多推荐


所有评论(0)