含AliOS Things的生活物联网平台SDK即包含AliOS Things物联网操作系统(基于AliOS Things V1.3.4)和Link Kit V2.3.0。本文档基于含AliOS Things版本SDK,介绍Wi-Fi芯片的移植过程。
前提条件
概述
AliOS Things的架构可适用于分层架构和组件化架构,如下图所示。

结构图从底部到顶部的内容如下。
- 板级支持包(BSP):主要由SoC供应商开发和维护。
- 硬件抽象层(HAL):例如Wi-Fi和UART。
- 内核:包括Rhino实时操作系统内核、Yloop、VFS、KV 存储。
- 协议栈:包括TCP/IP协议栈(LwIP)、uMesh网络协议栈。
- 安全:安全传输层协议(TLS)、可信服务框架(TFS)、可信运行环境(TEE)。
- AOS API:提供可供应用软件和中间件使用的API。
- 中间件:生活物联网平台提供了常用的增值服务中间件。
- 示例应用:生活物联网平台提供了自主开发的示例代码,以及完备测试通过了的应用程序(例如Link Kit App)。
在Wi-Fi芯片上移植含AliOS Things的SDK主要包括以下工作:
- 内核移植
- HAL移植
- Wi-Fi HAL和配网移植
- LwIP协议栈移植
- OTA移植
内核移植
AliOS Things中使用的内核为Rhino,详细介绍请参见Rhino内核移植。
本文以移植Rhino最小系统到STM32平台为例。
- 目标开发板:STM32L496G-Discovery,Cortex-m4架构。
- 移植目标:基本任务运行,tick时钟实现krhino_task_sleep,基本串口打印。
- 配置环境和系统初始化。请参见Rhino系统移植。
- Rhino基于Keil最小内核移植。
- 验收测试。
系统移植完成后,验收方法如下。
- 系统能启动一个任务并调用
krhino_task_sleep
函数实现定时打印,例如每秒打印一次日志。 - 运行Helloworld程序。
- 系统能启动一个任务并调用
HAL移植
硬件抽象层HAL(Hardware Abstraction Layer)普遍存在于各个操作系统之中,用于屏蔽不同芯片平台的差异,使上层的应用软件不会随芯片改变而改变。目前AliOS Things定义了全面的HAL抽象层,您只需对接相应的HAL接口,就能控制芯片的控制器,从而达到控制硬件外设的目的。
AliOS Things中定义的硬件HAL包括以下内容。AliOS Things硬件HAL移植请参见硬件抽象层移植。
- ADC
- GPIO
- I2C总线
- Flash(NAND、NOR)
- PWM
- RNG
- RTC
- SD
- SPI
- Timer
- UART
- Watchdog
- DAC
AliOS Things中设计了一套简单使用的永久存储机制KV(Key-Value),该机制依赖Flash HAL。因此平台移植时,需要对Flash HAL进行移植,以实现KV功能。详细请参见Flash和KV移植。
以RDA5981平台为例,硬件抽象相关的移植代码请参见RDA5981 HAL参考实现。
Wi-Fi和配网移植
- 移植Wi-Fi HAL。
对于Wi-Fi芯片,AliOS Things的Wi-Fi配网功能需要Wi-Fi HAL的支持。因此在平台对接过程中,需要对这些Wi-Fi HAL接口进行移植。如果是同时支持Wi-Fi和BLE的Combo芯片,您还需额外对接蓝牙能力,请参见蓝牙对接。
- 移植与Wi-Fi HAL相关联的结构体。
在AliOS Things中,与Wi-Fi HAL相关联的结构体为
hal_wifi_module_t
。Wi-Fi相关的操作和接口都封装在hal_wifi_module_t
结构体中,Wi-Fi HAL相关的数据结构和接口定义请参见wifi.h。您可以根据以下代码示例调用对应的接口函数来实现Wi-Fi模块结构体。详细的接口函数介绍如下表所示。
struct hal_wifi_module_s { hal_module_base_t base; const hal_wifi_event_cb_t *ev_cb; int (*init)(hal_wifi_module_t *m); void (*get_mac_addr)(hal_wifi_module_t *m, uint8_t *mac); void (*set_mac_addr)(hal_wifi_module_t *m, const uint8_t *mac); int (*start)(hal_wifi_module_t *m, hal_wifi_init_type_t *init_para); int (*start_adv)(hal_wifi_module_t *m, hal_wifi_init_type_adv_t *init_para_adv); int (*get_ip_stat)(hal_wifi_module_t *m, hal_wifi_ip_stat_t *out_net_para, hal_wifi_type_t wifi_type); int (*get_link_stat)(hal_wifi_module_t *m, hal_wifi_link_stat_t *out_stat); void (*start_scan)(hal_wifi_module_t *m); void (*start_scan_adv)(hal_wifi_module_t *m); int (*power_off)(hal_wifi_module_t *m); int (*power_on)(hal_wifi_module_t *m); int (*suspend)(hal_wifi_module_t *m); int (*suspend_station)(hal_wifi_module_t *m); int (*suspend_soft_ap)(hal_wifi_module_t *m); int (*set_channel)(hal_wifi_module_t *m, int ch); void (*start_monitor)(hal_wifi_module_t *m); void (*stop_monitor)(hal_wifi_module_t *m); void (*register_monitor_cb)(hal_wifi_module_t *m, monitor_data_cb_t fn); void (*register_wlan_mgnt_monitor_cb)(hal_wifi_module_t *m, monitor_data_cb_t fn); int (*wlan_send_80211_raw_frame)(hal_wifi_module_t *m, uint8_t *buf, int len); };
表 1. Wi-Fi接口函数说明 接口名称 接口说明 init 通过该接口可以对Wi-Fi进行初始化,使Wi-Fi达到可以准备进行连接工作的状态,如分配Wi-Fi资源、初始化硬件模块等操作。
get_mac_addr 通过该接口可以获取到Wi-Fi的物理硬件地址。
说明 回传的MAC地址格式为6个字节(不含英文逗号)二进制值,例如uint8_t mac[6] = {0xd8,0x96,0xe0,0x03,0x04,0x01};
。set_mac_addr 通过该接口可以设置Wi-Fi的物理硬件地址。
start 通过该接口可以启动Wi-Fi,根据启动参数wifi_mode来区分启动模式(0表示AP模式,1表示station模式)。
- station模式:去连接AP。
在station模式下,该函数触发AP连接操作后即返回。后续底层处理过程中,拿到IP信息后,需要调用
ip_got
回调函数来通知上层获取IP事件。 - AP模式:自身为AP,只需根据参数启动AP功能即可。
重要- station模式下启动Wi-Fi连接时,传入的SSID长度不超过32位。
- 在连接AP后,Wi-Fi底层需要维护自动重连操作。
start_adv 该接口类似
start
,但启动的参数更丰富,为可选接口。get_ip_stat 通过该接口可以获取Wi-Fi工作状态下的IP信息,如IP、网关、子网掩码、MAC地址等信息。
get_link_stat 通过该接口可以获取Wi-Fi工作状态下的链路层信息,如连接信号强度、信道、SSID等信息。
start_scan 通过该接口启动station模式下的信道扫描。扫描结束后,调用
scan_compeleted
回调函数,将各个信道上扫描到的AP信息通知给上层。需要得到的扫描信息在hal_wifi_scan_result_t
中定义。说明 扫描结果存储所需要的内存在底层实现中分配,回调函数返回后再将该内存释放。start_scan_adv 该接口与
hal_wifi_start_scan
类似,但扫描的信息更多,例如bssid、channel信息等。在hal_wifi_scan_result_adv_t
中定义通过扫描想得到的信息。扫描结束后,通过调用scan_adv_compeleted
回调函数通知上层。说明 扫描结果存储所需要的内存在底层实现中分配,回调函数返回后再将该内存释放。power_off 通过该接口对Wi-Fi硬件进行断电操作。
power_on 通过该接口对Wi-Fi硬件进行上电操作。
suspend 通过该接口断开Wi-Fi的所有连接,同时支持station模式和soft AP模式。
suspend_station 通过该接口断开Wi-Fi的所有连接,仅支持station模式。
suspend_soft_ap 该接口断开Wi-Fi的所有连接,仅支持soft AP模式。
set_channel 通过该接口可以设置信道。
wifi_monitor 通过该接口启动监听模式,并且在收到任何数据帧(包括beacon、probe request等)时,调用
monitor_cb
回调函数进行处理。说明 回调函数是上层通过register_monitor_cb
注册的。监听模式下,上层cb
函数期望处理的数据包不带帧校验序列FCS(Frame Check Sequence),所以如果底层的数据包带FCS,应当先剥离再往上层传递。stop_wifi_monitor 通过该接口关闭侦听模式。
register_monitor_cb 通过该接口注册侦听模式回调函数,回调函数在底层接收到任何数据帧时被调用。
register_wlan_mgnt_monitor_cb 通过该接口注册管理帧回调函数(非监听模式下),该回调函数在底层接收到管理帧时被调用。
start_debug_mode 通过该接口进入调试模式,可选(需模块支持)。
stop_debug_mode 通过该接口退出调试模式,可选。
wlan_send_80211_raw_frame 该接口可以用于发送802.11格式的数据帧。
您可以参考RDA5981平台实现:platform/mcu/rda5981x/hal/wifi_port.c。
- station模式:去连接AP。
- 移植Wi-Fi事件回调。
在配网过程中,netmgr模块(请参见network/netmgr/目录)负责定义和注册Wi-Fi事件回调函数。而在Wi-Fi启动和运行的过程中,通过调用回调函数来通知上层应用,以执行相应的动作。这些Wi-Fi事件的回调函数,应该在Wi-Fi网络驱动(通常是HAL层实现或更底层的代码)的任务上下文中被触发,示例如下。
- 在Wi-Fi底层拿到IP后,应执行
ip_got
回调,并将IP信息传递给上。 - 在Wi-Fi完成信道扫描后,应调用
scan_compeleted
或scan_adv_compeleted
回调,将扫描结果传递给上层。 - 在Wi-Fi状态改变时,应调用
stat_chg
回调。
说明 这些事件回调函数由netmgr配网模块定义并注册,在Wi-Fi底层(如HAL)里面触发调用。下面是AliOS Things中定义的Wi-Fi事件回调函数和接口,相关定义请参见wifi.h。
回调函数的定义,请参考netmgr模块中的
g_wifi_hal_event
函数。typedef struct { void (*connect_fail)(hal_wifi_module_t *m, int err, void *arg); void (*ip_got)(hal_wifi_module_t *m, hal_wifi_ip_stat_t *pnet, void *arg); void (*stat_chg)(hal_wifi_module_t *m, hal_wifi_event_t stat, void *arg); void (*scan_compeleted)(hal_wifi_module_t *m, hal_wifi_scan_result_t *result, void *arg); void (*scan_adv_compeleted)(hal_wifi_module_t *m, hal_wifi_scan_result_adv_t *result, void *arg); void (*para_chg)(hal_wifi_module_t *m, hal_wifi_ap_info_adv_t *ap_info, char *key, int key_len, void *arg); void (*fatal_err)(hal_wifi_module_t *m, void *arg); } hal_wifi_event_cb_t;
- 在Wi-Fi底层拿到IP后,应执行
- 注册和初始化Wi-Fi模块。
在完成Wi-Fi接口和事件回调的实现后,定义一个
hal_wifi_module_t
的结构体,将各个接口和回调的实现地址赋值给结构体中对应的域,示例如下。hal_wifi_module_t sim_aos_wifi_vendor = { .base.name = "alios_wifi_vender_name", .init = wifi_init, .get_mac_addr = wifi_get_mac_addr, .start = wifi_start, .start_adv = wifi_start_adv, .get_ip_stat = get_ip_stat, .get_link_stat = get_link_stat, .start_scan = start_scan, .start_scan_adv = start_scan_adv, .power_off = power_off, .power_on = power_on, .suspend = suspend, .suspend_station = suspend_station, .suspend_soft_ap = suspend_soft_ap, .set_channel = set_channel, .start_monitor = start_monitor, .stop_monitor = stop_monitor, .register_monitor_cb = register_monitor_cb, .register_wlan_mgnt_monitor_cb = register_wlan_mgnt_monitor_cb, .wlan_send_80211_raw_frame = wlan_send_80211_raw_frame, };
一般在板级初始化的过程中,先通过
hal_wifi_register_module
接口对Wi-Fi模块进行注册,再调用hal_wifi_init
接口对Wi-Fi硬件模块进行初始化。Wi-Fi HAL模块完成初始化后才可以使用。参考实现:platform/mcu/rda5981x/bsp/entry.c。void hal_wifi_register_module(hal_wifi_module_t *m); int hal_wifi_init(void);
- 参考实现。
以RDA5981平台为例:
- Wi-Fi HAL接口实现代码位于platform/mcu/rda5981x/hal/wifi_port.c。
- Wi-Fi芯片注册和初始化代码位于platform/mcu/rda5981x/bsp/entry.c。
- 验收测试。
在完成W-iFi的移植后,可以通过Wi-Fi APP来验证移植工作的有效性。Wi-Fi App位于app/example/wifihalapp/目录下,验证流程如下。
- 编译和运行Wi-Fi HAL App。
- 在CLI输入
testwifihal all <AP SSID> <AP password>
命令。正常情况下所有测试子项都通过。若测试不通过,可在Log中搜索
failed
关键字,查看具体失败的测试子项,并根据日志排查。
LwIP移植
- 对接网卡与驱动。
对接网卡与驱动需要完成底层初始化、输出对接、输入对接。Wi-Fi芯片驱动的原理图如下。
- 进入network/lwip/netif/ethernet.c文件。
- 修改以下示例代码。
static void low_level_init(struct netif *netif); static err_t low_level_output(struct netif *netif, struct pbuf *p); static struct pbuf *low_level_input(struct netif *netif);
其中详细的函数说明如下。
- low_level_init
主要作用:底层初始化。主要工作是填充网卡(netif)信息,例如设置MAC地址、MTU大小、flag标识等。
static void low_level_init(struct netif *netif) { u8_t wireless_mac[NETIF_MAX_HWADDR_LEN]; wifi_get_mac_address((char *)wireless_mac); /* set MAC hardware address length */ netif->hwaddr_len = ETHARP_HWADDR_LEN; os_memcpy(netif->hwaddr, wireless_mac, ETHARP_HWADDR_LEN); /* maximum transfer unit */ netif->mtu = 1500; /* device capabilities */ /* don't set NETIF_FLAG_ETHARP if this device is not an ethernet one */ netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_LINK_UP | NETIF_FLAG_IGMP; ETH_INTF_PRT("leave low level!\r\n"); }
low_level_output
调用驱动层发送函数。主要作用:输出对接。调用驱动层发送函数,该函数指针将赋给
netif->output
。static err_t low_level_output(struct netif *netif, struct pbuf *p) { int ret; ret = bmsg_tx_sender(p); if (ret == 0) return ERR_OK; else return ERR_TIMEOUT; }
- low_level_input
主要作用:输入对接。该函数调用Wi-Fi芯片驱动的函数,用于向上交付数据包。
未实现low_level_input时,即直接在驱动函数里调用
netif->input
。void ethernetif_input(int iface, struct pbuf *p) { … netif = (struct netif *)net_get_netif_handle(); … switch (htons(ethhdr->type)) { … case ETHTYPE_IP: /* full packet send to tcpip_thread to process */ if (netif->input(p, netif) != ERR_OK) // ethernet_input { LWIP_DEBUGF(NETIF_DEBUG, ("ethernetif_input: IP input error\r\n")); pbuf_free(p); p = NULL; } break; … }
- low_level_init
- 存放源代码。
修改完成后,源代码需要存放在对应的平台目录platform/mcu/xxxx下。以某一设备为例,若其相关修改项在platform/mcu/moc108/mx108/mx378/func/mxchip/lwip-2.0.2/port/ethernetif.c文件中。
- 移植平台相关内容。
平台相关的移植工作,主要定义包括类型定义,大小端设置,内存对齐等(如下表所示)。如果参考实现与开发者实现一致,可以直接拷贝存放在对应的平台
platform
下面。平台相关定义 示例 类型定义 typedef unsigned char u8_t;
大小端设置 #define BYTE_ORDER LITTLE_ENDIAN
内存对齐 #define PACKSTRUCTSTRUCT_attribute((packed))
以某一设备为例,若其相关修改项在platform/mcu/moc108/mx108/mx378/func/mxchip/lwip-2.0.2/port/cc.h目录中。
/* * Typedefs for the types used by lwip - * u8_t, s8_t, u16_t, s16_t, u32_t, s32_t, mem_ptr_t */ typedef unsigned char u8_t; /* Unsigned 8 bit quantity */ typedef signed char s8_t; /* Signed 8 bit quantity */ typedef unsigned short u16_t; /* Unsigned 16 bit quantity */ typedef signed short s16_t; /* Signed 16 bit quantity */ typedef unsigned long u32_t; /* Unsigned 32 bit quantity */ typedef signed long s32_t; /* Signed 32 bit quantity */ ...
- 定制协议栈配置。
LwIP配置通常在lwipopts.h文件中,该头文件放置在平台platform/mcu/xxxx目录下。以某设备为例,其相关修改项在platform/mcu/moc108/mx108/mx378/func/mxchip/lwip-2.0.2/lwipopts.h目录中,具体配置项如下所示。
#define IP_DEBUG LWIP_DBG_OFF #define ETHARP_DEBUG LWIP_DBG_OFF #define NETIF_DEBUG LWIP_DBG_OFF #define PBUF_DEBUG LWIP_DBG_OFF #define MEMP_DEBUG LWIP_DBG_OFF #define API_LIB_DEBUG LWIP_DBG_OFF #define API_MSG_DEBUG LWIP_DBG_OFF #define ICMP_DEBUG LWIP_DBG_OFF #define IGMP_DEBUG LWIP_DBG_OFF #define INET_DEBUG LWIP_DBG_OFF
- 编译配置。
LwIP与OS的对接AliOS Things已经默认完成,您无需关注,只需编译配置即可。
以某一设备为例,若其相关修改项在platform/mcu/moc108/aos.mk目录中,编译配置示例如下。
GLOBAL_INCLUDES += mx108/mx378/func/mxchip/lwip-2.0.2/port $(NAME)_SOURCES += mx108/mx378/func/mxchip/lwip-2.0.2/port/ethernetif.c \ mx108/mx378/func/mxchip/lwip-2.0.2/port/net.c
OTA移植
远程空中OTA(Over The Air)升级作为物联网设备的一项基础功能,可以快速修复软件漏洞、更新系统,对于快速迭代的物联网产品是刚性需求。生活物联网平台为您提供一套云端一体化的OTA升级方案,以满足产品功能快速迭代,同时降低产品开发和部署的成本。您只需按照此文档实现移植层接口后,就可以轻松使用生活物联网平台的OTA升级服务。OTA移植的更多介绍请参见OTA移植文档。
以RTL8710平台为例,OTA相关的平台移植实现代码位于platform/mcu/rtl8710bn/hal/ota_port.c目录下。详细移植验证请参见OTA移植demo。