AliOS Things WiFi模组移植指南
更新时间:2019-05-31 11:01:02
1. AliOS Things架构和移植概要
AliOS Things是一款由阿里巴巴开发的轻量级物联网操作系统。它在2017年杭州云栖大会中问世,并在同年10月20号于github:https://github.com/alibaba/AliOS-Things开源。
AliOS Things的架构可以适用于分层架构和组件化架构。从底部到顶部,AliOS Things包括:
板级支持包(BSP):主要由SoC供应商开发和维护。
硬件抽象层(HAL):比如WiFi和UART。
内核:包括Rhino实时操作系统内核、Yloop、VFS、KV 存储。
协议栈:包括TCP/IP协议栈(LwIP)、uMesh网络协议栈。
安全:安全传输层协议(TLS)、可信服务框架(TFS)、可信运行环境(TEE)。
AOS API:提供可供应用软件和中间件使用的API。
中间件:包括常见的物联网组件和阿里巴巴增值服务中间件。
示例应用:阿里自主开发的示例代码,以及通过了完备测试的应用程序(比如linkkitapp)。
如上图所示,AliOS Things是一个分层+组件架构,可以较简单的移植到WiFi模组上。WiFi模组上,移植AliOS Things的主要工作包括:
内核移植。
硬件抽象层HAL移植。
WiFi HAL和配网移植。
LwIP协议栈移植。
OTA移植。
2. 内核移植
概要
AliOS Things中使用的内核为Rhino
。关于Rhino内核移植的介绍,请参考Rhino内核移植。
系统移植
关于Rhino内核移植的具体内容,请参考Rhino系统移植。
移植示例
本章节介绍如何将Rhino最小系统移植到STM32平台。
目标开发板:STM32L496G-Discovery,Cotex-m4架构。
移植目标:基本任务运行,tick时钟实现krhino_task_sleep,基本串口打印。
具体移植步骤,请参考:Rhino基于Keil最小内核移植示例。
验收测试
系统移植完成后的验收:
系统能启动一个任务并调用krhino_task_sleep函数实现定时打印,例如每秒打印一次日志。
运行Helloworld程序。
3. HAL移植
概要
硬件抽象层HAL抽象层普遍存在于各个操作系统之中,最主要的目的是为了屏蔽不同芯片平台的差异,从而使上面的应用软件不会随芯片而改变。目前AliOS Things定义了全面的HAL抽象层,只要对接相应的HAL接口就能控制芯片的控制器,从而达到控制硬件外设的目的。
硬件抽象层移植
AliOS Things中定义的硬件HAL包括:
ADC
GPIO
I2C
NAND/NOR
PWM
RNG
RTC
SD
SPI
Timer
UART
Watchdog
DAC
关于AliOS Things硬件HAL移植的具体内容,请参考:硬件抽象层移植。
Flash HAL及KV移植
AliOS Things中设计了一套简单使用的永久存储机制(Key-Value),该机制需要Flash HAL的支持。平台移植时,需要对Flash HAL进行移植,以支持KV功能。
关于Flash HAL和KV移植的具体细节,请参考:Flash和KV移植。
参考实现
以ESP32平台为例,硬件抽象相关的移植代码位于:ESP32 HAL参考实现。
4. WiFi和配网移植
概要
对于WiFi模组,AliOS Things中的WiFi配网功能需要WiFi HAL的支持。平台对接过程中,需要对这些WiFi HAL接口进行移植。
WiFi HAL移植
WiFi HAL相关的数据结构和接口定义在wifi.h文件中。
WiFi模块结构体
首先,先了解一下AliOS Things中跟WiFi HAL相关的一个重要结构体 - hal_wifi_module_t
。WiFi相关的操作和接口都封装在hal_wifi_module_t
这个结构体中,相关定义在文件wifi.h中。
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);
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);
};
在具体的平台移植过程中,用户需要分别实现WiFi模块结构体中对应的接口函数。
AliOS Things对WiFi HAL层接口有一层封装,参见kernel/hal/wifi.c
文件。具体的接口实现,一般在platform/mcu/xxx/hal/wifi_port.c
中。参考实现:platform/mcu/esp32/hal/wifi_port.c
。关于WiFi HAL接口的说明,可以参照:WiFi HAL。
下面对每个接口作一些说明:
WiFi接口说明
init
该接口需要对wifi进行初始化,使wifi达到可以准备进行连接工作的状态,如分配wifi资源、初始化硬件模块等操作。
get_mac_addr
通过该接口可以获取到WiFi的物理硬件地址。注意:回传的mac地址格式为6个字节二进制值(不含:
号),如 uint8_t mac[6] = {0xd8,0x96,0xe0,0x03,0x04,0x01};
。
set_mac_addr
start
通过该接口可以启动WiFi,根据启动参数不同来区分启动station模式还是AP模式,如station模式下进行连接AP的操作、AP模式下根据参数启动AP功能。在station模式下,该函数触发AP连接操作后即返回。后续底层处理过程中,拿到IP信息后,需要调用ip_got
回调函数来通知上层获取IP事件。注意:(1) station模式下启动WiFi连接时,传入的SSID长度不超过32位;(2) 在连接AP后,WiFi底层需要维护自动重连操作。
start_adv
get_ip_stat
通过该接口可以获取WiFi工作状态下的IP信息,如IP、网关、子网掩码、MAC地址等信息。
get_link_stat
通过该接口可以获取WiFi工作状态下的链路层信息,如连接信号强度、信道、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
power_on
suspend
该接口断开WiFi所有连接,同时支持station模式和soft ap模式。
suspend_station
suspend_soft_ap
set_channel
wifi_monitor
该接口启动监听模式,并且在收到任何数据帧(包括beacon、probe request等)时,调用monitor_cb回调函数进行处理。注意,回调函数是上层通过register_monitor_cb
进行注册的。注意:监听模式下,上层cb函数期望处理的包不带FCS域,所以底层的数据包如果带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格式的数据帧。
参考ESP32平台实现:platform/mcu/esp32/hal/wifi_port.c
。
WiFi事件回调
WiFi移植过程中,WiFi事件及回调函数也是很重要的一个内容。AliOS Things中WiFi事件的回调函数在netmgr模块中定义,请参照network/netmgr/
。在配网过程中,netmgr负责定义和注册WiFi事件回调函数;而在WiFi启动和运行的过程中,通过调用回调函数来通知上层应用,以执行相应的动作。这些WiFi事件的回调函数,应该在WiFi网络驱动(通常是HAL层实现或更底层的代码)的任务上下文中被触发。例如:
在WiFi底层拿到IP后,应执行
ip_got
回调,并将IP信息传递给上层;在WiFi完成信道扫描后,应调用
scan_compeleted
或者scan_adv_compeleted
回调,将扫描结果传递给上层;在在WiFi状态改变时,应调用
stat_chg
回调。
需要再次强调的是,这些事件回调函数由netmgr配网模块定义并注册,在WiFi底层(如HAL)里面触发调用。
下面是AliOS Things中定义的WiFi事件回调函数和接口,相关定义在文件wifi.h中。
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;
回调函数的定义,请参照netmgr中的g_wifi_hal_event
。
WiFi模块注册和初始化
在完成WiFi接口和事件回调的实现后,定义一个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
接口对WiFi模块进行注册,然后调用hal_wifi_init
接口对WiFi硬件模块进行初始化。至此,WiFi HAL模块就完成了初始化,可以使用。
void hal_wifi_register_module(hal_wifi_module_t *m);
int hal_wifi_init(void);
参考实现:platform/mcu/esp32/bsp/entry.c
。
参考实现
以ESP32平台为例:
WiFi HAL接口实现代码位于:
platform/mcu/esp32/hal/wifi_port.c;
WiFi模组注册和初始化代码位于:
platform/mcu/esp32/bsp/entry.c;
验收测试
在完成WiFi的移植后,可以通过WiFi APP来验证移植工作的有效性。WiFi APP位于app/example/wifihalapp/
目录下,验证步骤如下:
编译和运行wifihalapp。
在cli输入
testwifihal all <AP SSID> <AP password>
命令。预期结果:所有测试子项都通过(搜索log中“failed”关键字,如有,则说明有测试项失败了)。
5. LwIP移植
5.1 概要
WiFi模组上使用AliOS Things提供的LwIP协议栈需要五步:网卡驱动对接、平台相关移植、协议栈配置定制、与OS对接、编译配置。其中,与OS对接默认是已经完成的,无须额外工作量。
5.2 网卡驱动对接
图5-1 驱动对接示例
网卡驱动对接需要完成底层初始化、输出对接、输入对接。参考代码见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);
表5-1 对接函数说明
函数名 | 作用 | 描述 | |
---|---|---|---|
1 | low_level_init | 底层初始化 | 网卡(netif)信息填充,例如MAC地址、MTU大小、flag标示设置等 |
2 | low_level_output | 输出对接 | 调用驱动层发送函数;low_level_output函数指针将赋给netif->linkoutput |
3 | low_level_input | 输入对接 | 该函数调用将被WiFi模组驱动函数调用,用于向上交付数据包。 |
修改完成后,源代码需要存放在对应的平台platform/mcu/xxxx下面。
示例
以庆科mk3060为例,其相关修改在platform/mcu/moc108/mx108/mx378/func/mxchip/lwip-2.0.2/port/ethernetif.c中,
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调用驱动层发送函数
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,而是直接在驱动函数里调用netif->linkinput
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;
…
}
5.3 平台相关移植
平台相关的移植工作,主要定义包括类型定义,大小端设置,内存对齐等,如表所示。
表5-2 平台相关定义
平台相关定义 | 示例 | |
---|---|---|
1 | 类型定义 | typedef unsigned char u8_t; |
2 | 大小端设置 | #define BYTE_ORDER LITTLE_ENDIAN |
3 | 内存对齐 | #define PACKSTRUCTSTRUCT__attribute((packed)) |
如果参考实现与开发者实现一致,可以直接拷贝存放在对应的平台(platform)下面。
示例
以庆科mk3060为例,其相关修改在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 */
...
5.4 协议栈配置定制
LwIP配置一般放在lwipopts.h,该头文件放置在platform/mcu/xxxx相关目录下。
示例
以庆科mk3060为例,其相关修改在
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
5.5 与OS对接
LwIP与OS的对接AliOSThings已经默认完成,开发者可以直接使用。
5.6 编译配置
以庆科mk3060为例,其相关修改在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
6. OTA移植
概要
OTA升级作为物联网设备的一项基础功能,可以快速修复软件漏洞,更新系统,对于快速迭代的物联网产品是刚性需求;目前IOT设备种类繁多,但并未提供统一的OTA升级方案,针对日益发展的IOT设备,开发者迫切需要一套云端一体化的OTA升级方案来满足快速迭代的产品开发周期,同时降低产品开发和部署的成本。
对于接入AliOS Things的物联网设备,阿里巴巴云端和设备端OTA组件为用户提供云端一体化的OTA升级服务; 对于使用OTA升级服务的用户只需按照此文档实现移植层接口后就可以轻松使用阿里巴巴物联网OTA升级服务。
使用AliOS Things 的开发者或用户,代码路径:https://github.com/alibaba/AliOS-Things.git 分支:rel_2.0.0。
平台移植
OTA相关的移植详情,请参考:OTA移植文档。
参考实现
以ESP32平台为例,OTA相关的平台移植实现代码位于:platform/mcu/esp32/hal/ota_port.c
。
移植验证
参考:OTA移植demo。