含AliOS Things的生活物联网平台SDK即包含AliOS Things物联网操作系统(基于AliOS Things V1.3.4)和Link Kit V2.3.0。本文档基于含AliOS Things版本SDK,介绍Wi-Fi芯片的移植过程。

前提条件

已下载含AliOS Things的生活物联网平台SDK,请参见获取SDK

概述

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,基本串口打印。
  1. 配置环境和系统初始化。请参见Rhino系统移植
  2. Rhino基于Keil最小内核移植
  3. 验收测试。

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

    • 系统能启动一个任务并调用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和配网移植

  1. 移植Wi-Fi HAL。

    对于Wi-Fi芯片,AliOS Things的Wi-Fi配网功能需要Wi-Fi HAL的支持。因此在平台对接过程中,需要对这些Wi-Fi HAL接口进行移植。如果是同时支持Wi-Fi和BLE的Combo芯片,您还需额外对接蓝牙能力,请参见蓝牙对接

  2. 移植与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类似,但扫描的信息更多,例如bssidchannel信息等。在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

  3. 移植Wi-Fi事件回调。

    在配网过程中,netmgr模块(请参见network/netmgr/目录)负责定义和注册Wi-Fi事件回调函数。而在Wi-Fi启动和运行的过程中,通过调用回调函数来通知上层应用,以执行相应的动作。这些Wi-Fi事件的回调函数,应该在Wi-Fi网络驱动(通常是HAL层实现或更底层的代码)的任务上下文中被触发,示例如下。

    • 在Wi-Fi底层拿到IP后,应执行ip_got回调,并将IP信息传递给上。
    • 在Wi-Fi完成信道扫描后,应调用scan_compeletedscan_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;
  4. 注册和初始化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);
  5. 参考实现。

    以RDA5981平台为例:

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

    在完成W-iFi的移植后,可以通过Wi-Fi APP来验证移植工作的有效性。Wi-Fi App位于app/example/wifihalapp/目录下,验证流程如下。

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

      正常情况下所有测试子项都通过。若测试不通过,可在Log中搜索failed关键字,查看具体失败的测试子项,并根据日志排查。

LwIP移植

  1. 对接网卡与驱动。

    对接网卡与驱动需要完成底层初始化、输出对接、输入对接。Wi-Fi芯片驱动的原理图如下。

    网卡驱动对接
    1. 进入network/lwip/netif/ethernet.c文件。
    2. 修改以下示例代码。
      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;
            …
        }
    3. 存放源代码。

      修改完成后,源代码需要存放在对应的平台目录platform/mcu/xxxx下。以某一设备为例,若其相关修改项在platform/mcu/moc108/mx108/mx378/func/mxchip/lwip-2.0.2/port/ethernetif.c文件中。

  2. 移植平台相关内容。

    平台相关的移植工作,主要定义包括类型定义,大小端设置,内存对齐等(如下表所示)。如果参考实现与开发者实现一致,可以直接拷贝存放在对应的平台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        */
    ...
  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. 编译配置。

    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