生活物联网平台SDK(含AliOS Things)是阿里云IoT针对生活物联网平台所提供的设备端SDK,其中包含AliOS Things物联网操作系统(基于AliOS Things V1.3.4)和Link Kit V2.3.0。生活物联网平台SDK含AliOS Things版本包含了无AliOS Things的版本,本文档以移植含AliOS Things版本为例,介绍WiFi芯片如何适配生活物联网平台SDK。

架构和移植概要

如果WiFi芯片还没有移植AliOS Things,请单击生活物联网平台SDK(含AliOS Things)下载代码。

AliOS Things的架构可适用于分层架构和组件化架构。从底部到顶部的内容如下。

  • 板级支持包(BSP):主要由SoC供应商开发和维护。
  • 硬件抽象层(HAL):例如WiFi和UART。
  • 内核:包括Rhino实时操作系统内核、Yloop、VFS、KV 存储。
  • 协议栈:包括TCP/IP协议栈(LwIP)、uMesh网络协议栈。
  • 安全:安全传输层协议(TLS)、可信服务框架(TFS)、可信运行环境(TEE)。
  • AOS API:提供可供应用软件和中间件使用的API。
  • 中间件:包括常见的物联网组件和阿里巴巴增值服务中间件。
  • 示例应用:阿里云自主开发的示例代码,以及通过了完备测试的应用程序(例如Link Kit App)。
架构图+分层

如上图所示,AliOS Things是一个分层加组件的架构,可以较简单的移植到WiFi芯片上。在WiFi芯片上移植AliOS Things的主要工作包括:

  • 内核移植
  • 硬件抽象层HAL移植
  • WiFi HAL和配网移植
  • LwIP协议栈移植
  • OTA移植

内核移植

  • 概要

    AliOS Things中使用的内核为Rhino。关于Rhino内核移植的介绍,请参见Rhino内核移植

  • 系统移植

    关于Rhino内核移植的具体内容,请参见Rhino系统移植

  • 移植示例

    本章节介绍如何将Rhino最小系统移植到STM32平台。

    • 目标开发板:STM32L496G-Discovery,Cotex-m4架构。
    • 移植目标:基本任务运行,tick时钟实现krhino_task_sleep,基本串口打印。

    具体移植步骤,请参见Rhino基于Keil最小内核移植示例

  • 验收测试

    系统移植完成后,验收方法如下。

    • 系统能启动一个任务并调用krhino_task_sleep函数实现定时打印,例如每秒打印一次日志。
    • 运行Helloworld程序。

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中设计了一套简单使用的永久存储机制KV(Key-Value),该机制依赖Flash HAL。因此平台移植时,需要对Flash HAL进行移植,以实现KV功能。

    关于Flash HAL和KV移植的具体细节,请参见Flash和KV移植

  • 参考实现

    以RDA5981平台为例,硬件抽象相关的移植代码请参见RDA5981 HAL参考实现

WiFi和配网移植

  • 概要

    对于WiFi芯片,AliOS Things的WiFi配网功能需要WiFi HAL的支持。平台对接过程中,需要对这些WiFi HAL接口进行移植。

    当Wi-Fi和BLE Combo芯片需要蓝牙辅助配网能力的时候,除了对接WiFi HAL之外,请参见蓝牙对接内容。

  • WiFi HAL移植

    WiFi HAL相关的数据结构和接口定义在wifi.h文件中。

  • WiFi模块结构体

    在AliOS Things中与WiFi HAL相关联的结构体为 - hal_wifi_module_t。WiFi相关的操作和接口都封装在hal_wifi_module_t结构体中,WiFi的相关定义在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);
        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);
    };

    在具体的平台移植过程中,用户需要分别实现WiFi模块结构体中对应的接口函数。

    AliOS Things对WiFi HAL层接口有一层封装,参见kernel/hal/wifi.c文件。具体的接口实现,一般在platform/mcu/xxx/hal/wifi_port.c中。参考实现请查看platform/mcu/rda5981x/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

      通过该接口可以设置WiFi的物理硬件地址。

    • start

      通过该接口可以启动WiFi,根据启动参数不同来区分启动station模式还是AP模式。

      • station模式:连接AP

        在station模式下,该函数触发AP连接操作后即返回。后续底层处理过程中,拿到IP信息后,需要调用ip_got回调函数来通知上层获取IP事件。

      • AP模式:根据参数启动AP功能
      注意
      • station模式下启动WiFi连接时,传入的SSID长度不超过32位。
      • 在连接AP后,WiFi底层需要维护自动重连操作。
    • start_adv

      该接口类似start,但启动的参数更丰富,为可选接口。

    • 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类似,但扫描的信息更多,例如bssidchannel信息等,需要扫描得到的信息在hal_wifi_scan_result_adv_t中定义。扫描结束后,通过调用scan_adv_compeleted回调函数通知上层。
      说明 扫描结果存储所需要的内存在底层实现中分配,回调函数返回后再将该内存释放。
    • power_off

      该接口对WiFi硬件进行断电操作。

    • power_on

      该接口对WiFi硬件进行上电操作。

    • suspend

      该接口断开WiFi所有连接,同时支持station模式和soft ap模式。

    • suspend_station

      该接口断开WiFi所有连接,支持station模式。

    • suspend_soft_ap

      该接口断开WiFi所有连接,支持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格式的数据帧。

      参考RDA5981平台实现:platform/mcu/rda5981x/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_compeletedscan_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/rda5981x/bsp/entry.c

  • 参考实现

    以RDA5981平台为例:

    • WiFi HAL接口实现代码位于platform/mcu/rda5981x/hal/wifi_port.c
    • WiFi芯片注册和初始化代码位于platform/mcu/rda5981x/bsp/entry.c
  • 验收测试

    在完成WiFi的移植后,可以通过WiFi APP来验证移植工作的有效性。WiFi App位于app/example/wifihalapp/目录下,验证步骤如下。

    1. 编译和运行WiFi HAL App。
    2. 在CLI输入testwifihal all <AP SSID> <AP password>命令。

      正常情况下所有测试子项都通过。

      说明 若失败,可在Log中搜索failed关键字,查看具体失败的测试子项。

LwIP移植

WiFi芯片上使用提供的LwIP协议栈需要如下五步操作。其中,与OS对接默认是已经完成的,无须额外操作。

  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);
    函数名 作用 描述
    low_level_init 底层初始化 网卡(netif)信息填充,例如MAC地址、MTU大小、flag标示设置等
    low_level_output 输出对接 调用驱动层发送函数;low_level_output函数指针将赋给netif->linkoutput
    low_level_input 输入对接 该函数调用将被WiFi芯片驱动函数调用,用于向上交付数据包。

    修改完成后,源代码需要存放在对应的平台目录platform/mcu/xxxx下。

    以某一设备为例,若其相关修改项在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;
        …
    }
  2. 移植平台相关内容。

    平台相关的移植工作,主要定义包括类型定义,大小端设置,内存对齐等,如表所示。

    平台相关定义 示例
    类型定义 typedef unsigned char u8_t;
    大小端设置 #define BYTE_ORDER LITTLE_ENDIAN
    内存对齐 #define PACKSTRUCTSTRUCT__attribute((packed))

    如果参考实现与开发者实现一致,可以直接拷贝存放在对应的平台platform下面。

    以某一设备为例,若其相关修改项在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        */
    ...
  3. 定制协议栈配置。

    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
  4. 与OS对接。

    LwIP与OS的对接AliOS Things已经默认完成,开发者可以直接使用。

  5. 编译配置。

    以某一设备为例,若其相关修改项在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升级作为物联网设备的一项基础功能,可以快速修复软件漏洞、更新系统,对于快速迭代的物联网产品是刚性需求。目前IOT设备种类繁多,但并未提供统一的OTA升级方案,针对日益发展的IOT设备,开发者迫切需要一套云端一体化的OTA升级方案来满足快速迭代的产品开发周期,同时降低产品开发和部署的成本。

    对于接入生活物联网平台的物联网设备,阿里巴巴云端和设备端OTA组件为用户提供云端一体化的OTA升级服务; 对于使用OTA升级服务的用户只需按照此文档实现移植层接口后就可以轻松使用阿里巴巴物联网OTA升级服务。

  • 平台移植

    OTA相关的移植详情,请参见OTA移植文档

  • 参考实现

    以RTL8710平台为例,OTA相关的平台移植实现代码位于platform/mcu/rtl8710bn/hal/ota_port.c目录下。

  • 移植验证

    请参见OTA移植demo