1、WLAN管理框架简介

随着物联网快速发展,愈来愈多的嵌入式设备上搭载了 WIFI 无线网络设备,为了可以管理 WIFI 网络设备,RT-Thread 引入了 WLAN 设备管理框架。这套框架是 RT-Thread 开发的一套用于管理 WIFI 的中间件:对下链接具体的 WIFI 驱动,控制 WIFI 的链接、断开、扫描等操做;对上承载不一样的应用,为应用提供 WIFI 控制、事件、数据导流等操做,为上层应用提供统一的 WIFI 控制接口。html

WLAN 框架主要由四个部分组成:Device 驱动接口层,为 WLAN 框架提供统一的调用接口;Manage 管理层为用户提供 WIFI 扫描、链接、断线重连等具体功能;Protocol 协议负责处理 WIFI 上产生的网络数据流,可根据不一样的使用场景挂载不一样网络协议栈(好比 LWIP );Config配置层能够保存 WIFI 配置参数,为用户提供自动链接服务(可从Flash读取曾经链接过的热点配置信息)。WIFI 框架层次图示以下:

WLAN管理框架各层功能简介以下:git

APP应用层:是基于 WLAN 框架的具体应用,如 WiFi 相关的 Shell 命令;

Airkiss / Voice 配网层:提供无线配网和声波配网等功能;

WLAN Manager 管理层:可以对 WLAN 设备进行控制和管理,具有设置模式、链接热点、断开热点、启动热点、扫描热点等 WLAN 控制相关的功能,还提供断线重连、自动切换热点等管理功能;

WLAN Protocol 协议层:将数据流递交给具体网络协议进行解析,用户能够指定使用不一样的协议进行通讯(本文使用LwIP协议);

WLAN Config 参数管理层:管理链接成功的热点信息及密码,并写入非易失的存储介质中,能够为用户提供自动链接曾连热点的服务;

WLAN Device 驱动接口层:对接具体 WLAN 硬件(本文使用AP6181 WIFI 模块),为管理层提供统一的调用接口。

在WLAN Protocol 与 APP 层之间还应包含网络协议层(好比LwIP),甚至是套接字抽象层SAL(包括网络设备无关层netdev),这些并无表如今上面的WLAN 框架图中,下文介绍LwIP协议栈移植时再详说。github

2、WLAN Device实现与AP6181 WLAN驱动移植

2.1 WLAN Device驱动接口层

WLAN设备数据结构

// rt-thread-4.0.1\components\drivers\wlan\wlan_dev.h

struct rt_wlan_device

{

struct rt_device device;

rt_wlan_mode_t mode;

struct rt_mutex lock;

struct rt_wlan_dev_event_desc handler_table[RT_WLAN_DEV_EVT_MAX][RT_WLAN_DEV_EVENT_NUM];

rt_wlan_pormisc_callback_t pormisc_callback;

const struct rt_wlan_dev_ops *ops;

rt_uint32_t flags;

void *prot;

void *user_data;

};

typedef enum

{

RT_WLAN_NONE,

RT_WLAN_STATION,

RT_WLAN_AP,

RT_WLAN_MODE_MAX

} rt_wlan_mode_t;

struct rt_wlan_dev_event_desc

{

rt_wlan_dev_event_handler handler;

void *parameter;

};

typedef void (*rt_wlan_dev_event_handler)(struct rt_wlan_device *device, rt_wlan_dev_event_t event, struct rt_wlan_buff *buff, void *parameter);

typedef void (*rt_wlan_pormisc_callback_t)(struct rt_wlan_device *device, void *data, int len);

struct rt_wlan_dev_ops

{

rt_err_t (*wlan_init)(struct rt_wlan_device *wlan);

rt_err_t (*wlan_mode)(struct rt_wlan_device *wlan, rt_wlan_mode_t mode);

rt_err_t (*wlan_scan)(struct rt_wlan_device *wlan, struct rt_scan_info *scan_info);

rt_err_t (*wlan_join)(struct rt_wlan_device *wlan, struct rt_sta_info *sta_info);

rt_err_t (*wlan_softap)(struct rt_wlan_device *wlan, struct rt_ap_info *ap_info);

rt_err_t (*wlan_disconnect)(struct rt_wlan_device *wlan);

rt_err_t (*wlan_ap_stop)(struct rt_wlan_device *wlan);

rt_err_t (*wlan_ap_deauth)(struct rt_wlan_device *wlan, rt_uint8_t mac[]);

rt_err_t (*wlan_scan_stop)(struct rt_wlan_device *wlan);

int (*wlan_get_rssi)(struct rt_wlan_device *wlan);

rt_err_t (*wlan_set_powersave)(struct rt_wlan_device *wlan, int level);

int (*wlan_get_powersave)(struct rt_wlan_device *wlan);

rt_err_t (*wlan_cfg_promisc)(struct rt_wlan_device *wlan, rt_bool_t start);

rt_err_t (*wlan_cfg_filter)(struct rt_wlan_device *wlan, struct rt_wlan_filter *filter);

rt_err_t (*wlan_set_channel)(struct rt_wlan_device *wlan, int channel);

int (*wlan_get_channel)(struct rt_wlan_device *wlan);

rt_err_t (*wlan_set_country)(struct rt_wlan_device *wlan, rt_country_code_t country_code);

rt_country_code_t (*wlan_get_country)(struct rt_wlan_device *wlan);

rt_err_t (*wlan_set_mac)(struct rt_wlan_device *wlan, rt_uint8_t mac[]);

rt_err_t (*wlan_get_mac)(struct rt_wlan_device *wlan, rt_uint8_t mac[]);

int (*wlan_recv)(struct rt_wlan_device *wlan, void *buff, int len);

int (*wlan_send)(struct rt_wlan_device *wlan, void *buff, int len);

};

结构体 rt_wlan_device 继承自设备基类 rt_device,天然须要将其注册到 I/O 设备管理层。rt_wlan_device 成员还包括WLAN设备工做模式(Access Point模式仍是Station模式)、WLAN设备访问互斥锁、WLAN事件回调函数组、WLAN混杂模式回调函数、须要底层驱动实现并注册的WLAN接口函数集合rt_wlan_dev_ops、WLAN标识位(用于标识工做模式或自动链接状态等)、WLAN设备使用的网络协议栈信息、私有数据等。web

WLAN接口函数及设备注册过程

WLAN设备驱动(这里指的是AP6181 WLAN驱动)须要向WLAN管理框架注册接口函数集合rt_wlan_dev_ops,以便WLAN管理框架对外提供的接口能正常工做,这个函数集合rt_wlan_dev_ops是如何注册到WLAN管理框架的呢?算法

// rt-thread-4.0.1\components\drivers\wlan\wlan_dev.c

rt_err_t rt_wlan_dev_register(struct rt_wlan_device *wlan, const char *name, const struct rt_wlan_dev_ops *ops, rt_uint32_t flag, void *user_data)

{

rt_err_t err = RT_EOK;

if ((wlan == RT_NULL) || (name == RT_NULL) || (ops == RT_NULL))

......

rt_memset(wlan, 0, sizeof(struct rt_wlan_device));

#ifdef RT_USING_DEVICE_OPS

wlan->device.ops = &wlan_ops;

#else

......

#endif

wlan->device.user_data = RT_NULL;

wlan->device.type = RT_Device_Class_NetIf;

wlan->ops = ops;

wlan->user_data = user_data;

wlan->flags = flag;

err = rt_device_register(&wlan->device, name, RT_DEVICE_FLAG_RDWR);

return err;

}

#ifdef RT_USING_DEVICE_OPS

const static struct rt_device_ops wlan_ops =

{

_rt_wlan_dev_init,

RT_NULL,

RT_NULL,

RT_NULL,

RT_NULL,

_rt_wlan_dev_control

};

#endif

从函数rt_wlan_dev_register 的代码能够看出,该函数不只完成了将函数集合rt_wlan_dev_ops注册到WLAN管理框架的工做(经过参数传递),还完成了将函数集合wlan_ops(经过调用rt_wlan_dev_ops接口实现的rt_device_ops接口)注册到 I/O 设备管理框架的工做,注册的WLAN设备类型为网络接口设备RT_Device_Class_NetIf。编程

完成WLAN设备向WLAN管理框架和 I/O 设备管理框架的注册后,就可使用 I/O 设备管理层接口或WLAN Device层提供的接口访问WLAN设备了,咱们先看下WLAN设备向 I/O 设备管理层注册的函数集合 wlan_ops 的实现代码:api

// rt-thread-4.0.1\components\drivers\wlan\wlan_dev.c

static rt_err_t _rt_wlan_dev_init(rt_device_t dev)

{

struct rt_wlan_device *wlan = (struct rt_wlan_device *)dev;

rt_err_t result = RT_EOK;

rt_mutex_init(&wlan->lock, "wlan_dev", RT_IPC_FLAG_FIFO);

if (wlan->ops->wlan_init)

result = wlan->ops->wlan_init(wlan);

......

return result;

}

static rt_err_t _rt_wlan_dev_control(rt_device_t dev, int cmd, void *args)

{

struct rt_wlan_device *wlan = (struct rt_wlan_device *)dev;

rt_err_t err = RT_EOK;

WLAN_DEV_LOCK(wlan);

switch (cmd)

{

case RT_WLAN_CMD_MODE:

{

rt_wlan_mode_t mode = *((rt_wlan_mode_t *)args);

if (wlan->ops->wlan_mode)

err = wlan->ops->wlan_mode(wlan, mode);

break;

}

case RT_WLAN_CMD_SCAN:

{

struct rt_scan_info *scan_info = args;

if (wlan->ops->wlan_scan)

err = wlan->ops->wlan_scan(wlan, scan_info);

break;

}

case RT_WLAN_CMD_JOIN:

{

struct rt_sta_info *sta_info = args;

if (wlan->ops->wlan_join)

err = wlan->ops->wlan_join(wlan, sta_info);

break;

}

case RT_WLAN_CMD_SOFTAP:

{

struct rt_ap_info *ap_info = args;

if (wlan->ops->wlan_softap)

err = wlan->ops->wlan_softap(wlan, ap_info);

break;

}

case RT_WLAN_CMD_DISCONNECT:

{

if (wlan->ops->wlan_disconnect)

err = wlan->ops->wlan_disconnect(wlan);

break;

}

case RT_WLAN_CMD_AP_STOP:

{

if (wlan->ops->wlan_ap_stop)

err = wlan->ops->wlan_ap_stop(wlan);

break;

}

case RT_WLAN_CMD_AP_DEAUTH:

{

if (wlan->ops->wlan_ap_deauth)

err = wlan->ops->wlan_ap_deauth(wlan, args);

break;

}

case RT_WLAN_CMD_SCAN_STOP:

{

if (wlan->ops->wlan_scan_stop)

err = wlan->ops->wlan_scan_stop(wlan);

break;

}

case RT_WLAN_CMD_GET_RSSI:

{

int *rssi = args;

if (wlan->ops->wlan_get_rssi)

*rssi = wlan->ops->wlan_get_rssi(wlan);

break;

}

case RT_WLAN_CMD_SET_POWERSAVE:

{

int level = *((int *)args);

if (wlan->ops->wlan_set_powersave)

err = wlan->ops->wlan_set_powersave(wlan, level);

break;

}

case RT_WLAN_CMD_GET_POWERSAVE:

{

int *level = args;

if (wlan->ops->wlan_get_powersave)

*level = wlan->ops->wlan_get_powersave(wlan);

break;

}

case RT_WLAN_CMD_CFG_PROMISC:

{

rt_bool_t start = *((rt_bool_t *)args);

if (wlan->ops->wlan_cfg_promisc)

err = wlan->ops->wlan_cfg_promisc(wlan, start);

break;

}

case RT_WLAN_CMD_CFG_FILTER:

{

struct rt_wlan_filter *filter = args;

if (wlan->ops->wlan_cfg_filter)

err = wlan->ops->wlan_cfg_filter(wlan, filter);

break;

}

case RT_WLAN_CMD_SET_CHANNEL:

{

int channel = *(int *)args;

if (wlan->ops->wlan_set_channel)

err = wlan->ops->wlan_set_channel(wlan, channel);

break;

}

case RT_WLAN_CMD_GET_CHANNEL:

{

int *channel = args;

if (wlan->ops->wlan_get_channel)

*channel = wlan->ops->wlan_get_channel(wlan);

break;

}

case RT_WLAN_CMD_SET_COUNTRY:

{

rt_country_code_t country = *(rt_country_code_t *)args;

if (wlan->ops->wlan_set_country)

err = wlan->ops->wlan_set_country(wlan, country);

break;

}

case RT_WLAN_CMD_GET_COUNTRY:

{

rt_country_code_t *country = args;

if (wlan->ops->wlan_get_country)

*country = wlan->ops->wlan_get_country(wlan);

break;

}

case RT_WLAN_CMD_SET_MAC:

{

rt_uint8_t *mac = args;

if (wlan->ops->wlan_set_mac)

err = wlan->ops->wlan_set_mac(wlan, mac);

break;

}

case RT_WLAN_CMD_GET_MAC:

{

rt_uint8_t *mac = args;

if (wlan->ops->wlan_get_mac)

err = wlan->ops->wlan_get_mac(wlan, mac);

break;

}

default:

break;

}

WLAN_DEV_UNLOCK(wlan);

return err;

}

函数集合 wlan_ops 的实现最终都是靠调用WLAN设备驱动提供的函数集合rt_wlan_dev_ops,并且WLAN设备的管理配置主要靠函数rt_device_control 经过发送不一样的命令码和参数实现。WLAN Device层提供的接口函数又是经过调用函数集合 wlan_ops 实现的,下面给出WLAN Device层对外提供的接口函数声明:数组

// rt-thread-4.0.1\components\drivers\wlan\wlan_dev.h

/* wlan device init */

rt_err_t rt_wlan_dev_init(struct rt_wlan_device *device, rt_wlan_mode_t mode);

/* wlan device station interface */

rt_err_t rt_wlan_dev_connect(struct rt_wlan_device *device, struct rt_wlan_info *info, const char *password, int password_len);

rt_err_t rt_wlan_dev_disconnect(struct rt_wlan_device *device);

int rt_wlan_dev_get_rssi(struct rt_wlan_device *device);

/* wlan device ap interface */

rt_err_t rt_wlan_dev_ap_start(struct rt_wlan_device *device, struct rt_wlan_info *info, const char *password, int password_len);

rt_err_t rt_wlan_dev_ap_stop(struct rt_wlan_device *device);

rt_err_t rt_wlan_dev_ap_deauth(struct rt_wlan_device *device, rt_uint8_t mac[6]);

/* wlan device scan interface */

rt_err_t rt_wlan_dev_scan(struct rt_wlan_device *device, struct rt_wlan_info *info);

rt_err_t rt_wlan_dev_scan_stop(struct rt_wlan_device *device);

/* wlan device mac interface */

rt_err_t rt_wlan_dev_get_mac(struct rt_wlan_device *device, rt_uint8_t mac[6]);

rt_err_t rt_wlan_dev_set_mac(struct rt_wlan_device *device, rt_uint8_t mac[6]);

/* wlan device powersave interface */

rt_err_t rt_wlan_dev_set_powersave(struct rt_wlan_device *device, int level);

int rt_wlan_dev_get_powersave(struct rt_wlan_device *device);

/* wlan device event interface */

rt_err_t rt_wlan_dev_register_event_handler(struct rt_wlan_device *device, rt_wlan_dev_event_t event, rt_wlan_dev_event_handler handler, void *parameter);

rt_err_t rt_wlan_dev_unregister_event_handler(struct rt_wlan_device *device, rt_wlan_dev_event_t event, rt_wlan_dev_event_handler handler);

void rt_wlan_dev_indicate_event_handle(struct rt_wlan_device *device, rt_wlan_dev_event_t event, struct rt_wlan_buff *buff);

/* wlan device promisc interface */

rt_err_t rt_wlan_dev_enter_promisc(struct rt_wlan_device *device);

rt_err_t rt_wlan_dev_exit_promisc(struct rt_wlan_device *device);

rt_err_t rt_wlan_dev_set_promisc_callback(struct rt_wlan_device *device, rt_wlan_pormisc_callback_t callback);

void rt_wlan_dev_promisc_handler(struct rt_wlan_device *device, void *data, int len);

/* wlan device filter interface */

rt_err_t rt_wlan_dev_cfg_filter(struct rt_wlan_device *device, struct rt_wlan_filter *filter);

/* wlan device channel interface */

rt_err_t rt_wlan_dev_set_channel(struct rt_wlan_device *device, int channel);

int rt_wlan_dev_get_channel(struct rt_wlan_device *device);

/* wlan device country interface */

rt_err_t rt_wlan_dev_set_country(struct rt_wlan_device *device, rt_country_code_t country_code);

rt_country_code_t rt_wlan_dev_get_country(struct rt_wlan_device *device);

/* wlan device datat transfer interface */

rt_err_t rt_wlan_dev_report_data(struct rt_wlan_device *device, void *buff, int len);

/* wlan device register interface */

rt_err_t rt_wlan_dev_register(struct rt_wlan_device *wlan, const char *name,

const struct rt_wlan_dev_ops *ops, rt_uint32_t flag, void *user_data);

WLAN Device层提供的这些接口函数咱们虽然能够在应用程序中直接调用,但函数参数有不少结构体类型,在调用这些接口函数前,须要先构造接口函数参数须要的结构体,这就给函数调用带来了不便。WLAN Device层上面的WLAN Manager 层则对这些接口函数进行了再次封装,使用一些全局变量保存必要的信息,简化了参数的构造,咱们直接调用WLAN Manager 层提供的接口函数更加方便友好,这些接口函数在下文介绍。缓存

2.2 AP6181 WLAN驱动移植

Pandora开发板的程序源码包并无为咱们提供AP6181 WLAN驱动的源码,而是以库文件的形式给出的,因此这里也无法分析AP6181 WLAN驱动的实现原理,只能根据 SDIO 设备管理框架与WLAN 管理框架对WLAN设备驱动的要求推测一些AP6181 WLAN驱动移植时应实现或调用的函数。这里忍不住吐槽一下提供Pandora开发板 AP6181 WLAN驱动库文件的同窗,起码应该给出一些关于AP6181 WLAN驱动库文件如何使用、须要为其实现哪些接口函数、对外提供哪些接口函数、简单的实现原理之类的说明文档,如今缺乏这些信息为WLAN驱动移植和调试带来了很大的不便。sass

AP6181 WLAN固件配置

从前篇博客:SDIO设备对象管理 + AP6181(BCM43362) WiFi模块了解到,AP6181 WIFI 模组内部是须要运行WLAN固件程序的,AP6181 内部可能没有ROM空间,这就须要咱们将AP6181 内运行的WLAN固件程序存放到主控端的Flash 空间内。在使用WLAN设备前,由WLAN驱动程序负责将Host 端Flash内存放的WLAN固件读取并传送到AP6181 模组内,以便AP6181 WIFI 模组能正常工做(好比完成WIFI数据帧与以太网数据帧之间的转换)。

这里提醒一点,本文使用的AP6181的WLAN固件与驱动都是从Pandora开发板提供的源码包中得到的,且因为WLAN固件与驱动都是以库文件的形式提供的,对运行环境(好比RT-Thread版本)变动比较敏感,所以最好选择与本身使用的RT-Thread版本一致的 Pandora IOT 源码包。好比我使用的是RT-Thread 4.0.1,正点原子官网给的Pandora IOT 源码包默认的基于RT-Thread 4.0.0开发的,我就须要到GitHub 下载基于RT-Thread 4.0.1 版本的Pandora IOT 源码包(本文使用的是Release 1.2.0版本)。下文中使用的AP6181 WLAN固件与驱动都是从Pandora IOT Board Release 1.2.0版本源码包拷贝来的。

AP6181 WLAN固件所在路径:

.\IoT_Board\examples\16_iot_wifi_manager\bin\wifi_image_1.0.rbl

咱们须要先将该WLAN固件放入Flash(Pandora上的W25Q128芯片)的 wifi_image 分区,本文使用的工程文件是基于博客:FAL分区管理与easyflash变量管理中完成FAL与Easyflash组件移植后的工程文件为基础的。在上面的博客中已经FAL(Flash Abstraction Layer)的实现原理及接口函数,并且在移植FAL组件时配置到分区表也包括wifi_image 分区,这里能够直接该分区存储 AP6181 WLAN 固件镜像文件。

咱们如何将AP6181 WLAN固件(wifi_image_1.0.rbl)放到W25Q128 Flash内的wifi_image 分区呢?能够参考下面的文档:

.\IoT_Board\docs\UM3001-RT-Thread-IoT Board WIFI 模块固件下载手册.pdf

比较简单的方法是先将WLAN固件放到SD卡以下目录中:

/SYSTEM/WIFI/wifi_image_1.0.rbl

而后将SD卡插入到Pandora开发板的SD卡插槽,将综合例程文件(以下路径)烧录到Pandora开发板中:

.\IoT_Board\examples\30_iot_board_demo\bin\all.bin

综合例程文件烧录完成后,Pandora开发板检测到WLAN固件,会自动执行读取、校验、升级WLAN固件的操做,Pandora开发板的LCD也会显示相应的升级信息(若是wifi_image

分区已存在WLAN固件,且与放入SD卡中的WLAN固件版本一致,则不会有相应的加载或升级操做)。

接下来就是AP6181 WLAN驱动负责将存储在W25Q128 Flash wifi_image 分区的WLAN固件读取出来,并经过SDIO总线传输到AP6181 模组内。因为WLAN驱动是以库文件的形式提供的,咱们直接从Pandora源码包将WLAN驱动库文件和WLAN驱动移植文件复制到咱们的工程中使用,这些文件在Pandora源码包中的路径和复制到咱们工程目录的路径以下:

// Pandora IOT Board Release 1.2.0中WLAN驱动库文件和WLAN驱动移植文件路径

.\IoT_Board\libraries\wifi\libwifi_6181_0.2.5_armcm4_gcc.a

.\IoT_Board\libraries\wifi\libwifi_6181_0.2.5_armcm4_iar.a

.\IoT_Board\libraries\wifi\libwifi_6181_0.2.5_armcm4_keil.lib

.\IoT_Board\libraries\wifi\SConscript

.\IoT_Board\drivers\drv_wlan.h

.\IoT_Board\drivers\drv_wlan.c

// WLAN驱动库文件和WLAN驱动移植文件拷贝到咱们工程中的目标路径

.\RT-Thread_Projects\libraries\wifi\libwifi_6181_0.2.5_armcm4_gcc.a

.\RT-Thread_Projects\libraries\wifi\libwifi_6181_0.2.5_armcm4_iar.a

.\RT-Thread_Projects\libraries\wifi\libwifi_6181_0.2.5_armcm4_keil.lib

.\RT-Thread_Projects\libraries\wifi\SConscript

.\RT-Thread_Projects\libraries\HAL_Drivers\drv_wlan.h

.\RT-Thread_Projects\libraries\HAL_Drivers\drv_wlan.c

WLAN驱动库文件和WLAN驱动移植文件复制到咱们工程中后,须要能编译进咱们的工程,所以须要修改SConscript文件和SConstruct文件,将咱们拷贝过来的文件添加进编译脚本,新增编译代码以下:

// .\RT-Thread_Projects\libraries\HAL_Drivers\SConscript

......

# add wlan driver code

if GetDepend(['BSP_USING_WIFI']):

src += ['drv_wlan.c']

src += ['drv_common.c']

......

// .\RT-Thread_Projects\projects\stm32l475_wifi_sample\SConstruct

......

# include drivers

objs.extend(SConscript(os.path.join(libraries_path_prefix, 'HAL_Drivers', 'SConscript')))

# include wifi_libraries

objs.extend(SConscript(os.path.join(libraries_path_prefix, 'wifi', 'SConscript')))

# make a building

DoBuilding(TARGET, objs)

到这里WLAN驱动库文件和WLAN驱动移植文件就添加到咱们的工程中了,接下来看WLAN驱动是如何读取WLAN固件镜像文件的:

// .\RT-Thread_Projects\libraries\HAL_Drivers\drv_wlan.c

#define WIFI_IMAGE_PARTITION_NAME "wifi_image"

static const struct fal_partition *partition = RT_NULL;

int wiced_platform_resource_size(int resource)

{

int size = 0;

/* Download firmware */

if (resource == 0)

{

/* initialize fal */

fal_init();

partition = fal_partition_find(WIFI_IMAGE_PARTITION_NAME);

if (partition == RT_NULL)

return size;

if ((rt_ota_init() >= 0) && (rt_ota_part_fw_verify(partition) >= 0))

size = rt_ota_get_raw_fw_size(partition);

}

return size;

}

int wiced_platform_resource_read(int resource, uint32_t offset, void *buffer, uint32_t buffer_size)

{

int transfer_size = 0;

if (partition == RT_NULL)

return 0;

/* read RF firmware from partition */

transfer_size = fal_partition_read(partition, offset, buffer, buffer_size);

return transfer_size;

}

咱们只须要为WLAN驱动实现两个接口函数:函数wiced_platform_resource_size得到Flash wifi_image分区存储的WLAN固件(wifi_image_1.0.rbl)所占空间的大小size;函数wiced_platform_resource_read则从Flash wifi_image分区读取size大小的数据(实际就是WLAN固件代码),并保存到指针buffer 所指向的内存空间。WLAN固件代码后续的处理(好比经过SDIO总线将其传输到AP6181 模组内)则由WLAN驱动程序完成,不须要咱们操心了。

函数wiced_platform_resource_size获取WLAN固件大小的函数rt_ota_get_raw_fw_size(包括函数rt_ota_init与rt_ota_part_fw_verify)均由OTA(Over-The-Air programming)库文件提供,咱们还须要将OTA库文件添加进咱们的工程中,跟添加WLAN驱动库文件方法同样,以下所示:

// Pandora IOT Board Release 1.2.0中OTA库文件路径

.\IoT_Board\libraries\rt_ota\inc\rt_ota.h

.\IoT_Board\libraries\rt_ota\libs\librt_ota_noalgo_0.1.2_stm32l4_gcc.a

.\IoT_Board\libraries\rt_ota\libs\librt_ota_noalgo_0.1.2_stm32l4_iar.a

.\IoT_Board\libraries\rt_ota\libs\librt_ota_noalgo_0.1.2_stm32l4_keil.lib

.\IoT_Board\libraries\rt_ota\SConscript

// OTA库文件拷贝到咱们工程中的目标路径

.\RT-Thread_Projects\libraries\rt_ota\inc\rt_ota.h

.\RT-Thread_Projects\libraries\rt_ota\libs\librt_ota_noalgo_0.1.2_stm32l4_gcc.a

.\RT-Thread_Projects\libraries\rt_ota\libs\librt_ota_noalgo_0.1.2_stm32l4_iar.a

.\RT-Thread_Projects\libraries\rt_ota\libs\librt_ota_noalgo_0.1.2_stm32l4_keil.lib

.\RT-Thread_Projects\libraries\rt_ota\SConscript

// 将拷贝来的rt_ota库文件添加进编译脚本的代码

.\RT-Thread_Projects\projects\stm32l475_wifi_sample\SConstruct

......

# include wifi_libraries

objs.extend(SConscript(os.path.join(libraries_path_prefix, 'wifi', 'SConscript')))

# include ota_libraries

objs.extend(SConscript(os.path.join(libraries_path_prefix, 'rt_ota', 'SConscript')))

# make a building

DoBuilding(TARGET, objs)

咱们已经将WLAN驱动库文件、WLAN驱动移植文件、OTA库文件都添加进咱们的工程中了,但想要将其编译进咱们的工程中,还须要配置相应的宏,咱们先看看这些组件依赖哪些宏定义:

// .\RT-Thread_Projects\libraries\wifi\SConscript

......

group = DefineGroup('wifi', src, depend = ['RT_USING_WIFI_6181_LIB'], CPPPATH = path, LIBS = LIBS, LIBPATH = LIBPATH)

......

// .\RT-Thread_Projects\libraries\HAL_Drivers\SConscript

......

if GetDepend(['BSP_USING_WIFI']):

src += ['drv_wlan.c']

......

// .\RT-Thread_Projects\libraries\rt_ota\SConscript

......

group = DefineGroup('rt_ota', src, depend = ['RT_USING_OTA_LIB'], CPPPATH = path, LIBS = libs, LIBPATH = libpath)

......

从上面的编译脚本中能够看到三个依赖宏定义须要咱们配置,咱们在Kconfig中配置这三个宏编译选项的代码以下:

// .\RT-Thread_Projects\projects\stm32l475_wifi_sample\board\Kconfig

......

menu "Onboard Peripheral Drivers"

......

config BSP_USING_WIFI

bool "Enable WiFi"

select BSP_USING_SDIO

select BSP_USING_QSPI_FLASH

select PKG_USING_FAL

select RT_USING_WIFI

select RT_USING_WIFI_6181_LIB

select RT_USING_OTA_LIB

select RT_USING_LIBC

select RT_USING_DFS

default n

endmenu

......

menu "External Libraries"

config RT_USING_WIFI_6181_LIB

bool "Using Wifi(AP6181) Library"

default n

config RT_USING_OTA_LIB

bool "Using RT-Thrad OTA Library"

default n

endmenu

endmenu

第一个宏选项BSP_USING_WIFI 依赖项比较多,首先是依赖于BSP_USING_SDIO、BSP_USING_QSPI_FLASH 和 PKG_USING_FAL,前者是使用SDIO外设,后两个是用于管理W25Q128 Flash wifi_image 分区的(用于存储WLAN固件镜像文件)。接下来三个依赖宏RT_USING_WIFI、RT_USING_WIFI_6181_LIB和RT_USING_OTA_LIB 则是使用WLAN驱动库文件和OTA库文件;依赖宏RT_USING_LIBC则是使用C标准库文件(WLAN驱动库文件和OTA库文件内有使用C标准库文件);依赖宏RT_USING_DFS是使用虚拟文件系统,这个主要是为SD Memory Card做为块设备挂载文件系统存在的。BSP_USING_WIFI 的依赖项尚未列举彻底,随着后面介绍会逐渐完善。

后面两个宏选项RT_USING_WIFI_6181_LIB和RT_USING_OTA_LIB比较简单,若是在menuconfig中被选中则相应的宏被定义。

AP6181 WLAN驱动初始化

WLAN固件与WLAN驱动(包括OTA组件)都已经添加到咱们的工程中,接下来看看WLAN驱动初始化过程:

// .\RT-Thread_Projects\libraries\HAL_Drivers\drv_wlan.c

extern int wifi_hw_init(void);

extern void wwd_thread_notify_irq(void);

static rt_uint32_t init_flag = 0;

int rt_hw_wlan_init(void)

{

if (init_flag == 1)

return RT_EOK;

#ifdef BSP_USING_WIFI_THREAD_INIT

rt_thread_t tid = RT_NULL;

tid = rt_thread_create("wifi_init", wifi_init_thread_entry, RT_NULL, WIFI_INIT_THREAD_STACK_SIZE, WIFI_INIT_THREAD_PRIORITY, 20);

if (tid)

rt_thread_startup(tid);

else

return -RT_ERROR;

#else

wifi_init_thread_entry(RT_NULL);

init_flag = 1;

#endif

return RT_EOK;

}

#ifdef BSP_USING_WIFI_AUTO_INIT

INIT_APP_EXPORT(rt_hw_wlan_init);

#endif

static void wifi_init_thread_entry(void *parameter)

{

/* set wifi irq handle, must be initialized first */

#define PIN_WIFI_IRQ GET_PIN(C, 5)

rt_pin_mode(PIN_WIFI_IRQ, PIN_MODE_INPUT_PULLUP);

rt_pin_attach_irq(PIN_WIFI_IRQ, PIN_IRQ_MODE_RISING_FALLING, _wiced_irq_handler, RT_NULL);

rt_pin_irq_enable(PIN_WIFI_IRQ, PIN_IRQ_ENABLE);

/* initialize low level wifi(ap6181) library */

wifi_hw_init();

/* waiting for sdio bus stability */

rt_thread_delay(WIFI_INIT_WAIT_TIME);

/* set wifi work mode */

rt_wlan_set_mode(RT_WLAN_DEVICE_STA_NAME, RT_WLAN_STATION);

init_flag = 1;

}

static void _wiced_irq_handler(void *param)

{

wwd_thread_notify_irq();

}

WLAN初始化函数rt_hw_wlan_init 建立并启动了一个WIFI 初始化线程 wifi_init_thread_entry(是否使用线程取决于宏BSP_USING_WIFI_THREAD_INIT是否被定义),在WIFI 初始化线程中先在PIN_WIFI_IRQ 引脚(也即前篇博客:SDIO设备对象管理 + AP6181(BCM43362) WiFi模块中介绍的WIFI_INT 引脚)上绑定中断处理函数_wiced_irq_handler(实际绑定的是WLAN驱动库文件中实现的函数wwd_thread_notify_irq),而后执行WIFI 硬件初始化函数 wifi_hw_init (由WLAN驱动库文件实现)完成 AP6181 WIFI 模块的初始化。最后,设置WIFI 工做模式,这里设置为Station 模式,让Pandora 开发板链接周围的WIFI 热点。

这里提醒一点,得到PIN_WIFI_IRQ引脚编号的宏定义是我新增的,Pandora源码包中的头文件drv_gpio.h 定义了开发板上几乎全部的引脚编号,咱们的工程使用的通用模板并无给出太多的引脚定义,所以须要在用到某引脚时再获取引脚编号。

WLAN初始化函数rt_hw_wlan_init 也是能够被自动初始化组件调用的,是否能够自动完成初始化取决于宏BSP_USING_WIFI_AUTO_INIT是否被定义,咱们再在宏选项BSP_USING_WIFI 下新增BSP_USING_WIFI_AUTO_INIT与BSP_USING_WIFI_THREAD_INIT的宏配置代码以下:

// .\RT-Thread_Projects\projects\stm32l475_wifi_sample\board\Kconfig

menu "Hardware Drivers Config"

......

menu "Onboard Peripheral Drivers"

......

config BSP_USING_WIFI

bool "Enable WiFi"

......

select RT_USING_DFS

default n

if BSP_USING_WIFI

config BSP_USING_WIFI_THREAD_INIT

bool "Using Thread Initialize WiFi"

default n

config BSP_USING_WIFI_AUTO_INIT

bool "Using WiFi Automatically Initialization"

depends on RT_USING_COMPONENTS_INIT

default y

endif

endmenu

......

WLAN初始化须要必定的时间,WLAN驱动适配层为咱们提供了接口函数 rt_hw_wlan_wait_init_done,咱们能够在应用中调用该函数等待WLAN设备初始化完成,该函数的实现代码以下:

// .\RT-Thread_Projects\libraries\HAL_Drivers\drv_wlan.c

static rt_uint32_t init_flag = 0;

/**

* wait milliseconds for wifi low level initialize complete

* time_ms: timeout in milliseconds

*/

int rt_hw_wlan_wait_init_done(rt_uint32_t time_ms)

{

rt_uint32_t time_cnt = 0;

/* wait wifi low level initialize complete */

while (time_cnt <= (time_ms / 100))

{

time_cnt++;

rt_thread_mdelay(100);

if (rt_hw_wlan_get_initialize_status() == 1)

break;

}

if (time_cnt > (time_ms / 100))

return -RT_ETIMEOUT;

return RT_EOK;

}

int rt_hw_wlan_get_initialize_status(void)

{

return init_flag;// 1 initialize done;0 not initialize

}

3、WLAN Protocol实现与LwIP协议栈移植

3.1 WLAN Protocol 网络协议层

WLAN协议层数据结构描述

// rt-thread-4.0.1\components\drivers\wlan\wlan_prot.h

struct rt_wlan_prot

{

char name[RT_WLAN_PROT_NAME_LEN];

rt_uint32_t id;

const struct rt_wlan_prot_ops *ops;

};

#define RT_LWAN_ID_PREFIX (0x5054)

struct rt_wlan_prot_ops

{

rt_err_t (*prot_recv)(struct rt_wlan_device *wlan, void *buff, int len);

struct rt_wlan_prot *(*dev_reg_callback)(struct rt_wlan_prot *prot, struct rt_wlan_device *wlan);

void (*dev_unreg_callback)(struct rt_wlan_prot *prot, struct rt_wlan_device *wlan);

};

结构体rt_wlan_prot 包含协议名name、协议ID(由前缀和编号共同构成)、网络协议层应实现并向WLAN管理框架注册的接口函数集合rt_wlan_prot_ops 等成员。

WLAN协议层须要的接口函数rt_wlan_prot_ops 是如何被注册的呢?

// rt-thread-4.0.1\components\drivers\wlan\wlan_prot.c

static struct rt_wlan_prot *_prot[RT_WLAN_PROT_MAX];

static struct rt_wlan_prot_event_des prot_event_tab[RT_WLAN_PROT_EVT_MAX][RT_WLAN_PROT_MAX];

rt_err_t rt_wlan_prot_regisetr(struct rt_wlan_prot *prot)

{

int i;

rt_uint32_t id;

static rt_uint8_t num;

/* Parameter checking */

if ((prot == RT_NULL) || (prot->ops->prot_recv == RT_NULL) ||

(prot->ops->dev_reg_callback == RT_NULL))

return -RT_EINVAL;

/* save prot */

for (i = 0; i < RT_WLAN_PROT_MAX; i++)

{

if (_prot[i] == RT_NULL)

{

id = (RT_LWAN_ID_PREFIX << 16) | num;

prot->id = id;

_prot[i] = prot;

num ++;

break;

}

else if (rt_strcmp(_prot[i]->name, prot->name) == 0)

break;

}

/* is full */

if (i >= RT_WLAN_PROT_MAX)

return -RT_ERROR;

return RT_EOK;

}

向WLAN协议层注册网络协议栈 rt_wlan_prot,实际上就是将实现的结构体对象 rt_wlan_prot 赋值给WLAN协议层的全局变量(被static修饰,仅该源文件内有效)_prot[i],WLAN协议层就能够调用注册来的接口函数 rt_wlan_prot_ops,来实现本层对外提供的接口函数了。

WLAN协议层接口函数

// rt-thread-4.0.1\components\drivers\wlan\wlan_prot.h

/* 网络协议绑定/解绑到WLAN设备 */

rt_err_t rt_wlan_prot_attach(const char *dev_name, const char *prot_name);

rt_err_t rt_wlan_prot_attach_dev(struct rt_wlan_device *wlan, const char *prot_name);

rt_err_t rt_wlan_prot_detach(const char *dev_name);

rt_err_t rt_wlan_prot_detach_dev(struct rt_wlan_device *wlan);

/* rt_wlan_prot协议注册 */

rt_err_t rt_wlan_prot_regisetr(struct rt_wlan_prot *prot);

/* WLAN协议层向设备层发送/接收数据 */

rt_err_t rt_wlan_prot_transfer_dev(struct rt_wlan_device *wlan, void *buff, int len);

rt_err_t rt_wlan_dev_transfer_prot(struct rt_wlan_device *wlan, void *buff, int len);

/* WLAN协议层事件回调函数注册/注销 */

rt_err_t rt_wlan_prot_event_register(struct rt_wlan_prot *prot, rt_wlan_prot_event_t event, rt_wlan_prot_event_handler handler);

rt_err_t rt_wlan_prot_event_unregister(struct rt_wlan_prot *prot, rt_wlan_prot_event_t event);

typedef void (*rt_wlan_prot_event_handler)(struct rt_wlan_prot *port, struct rt_wlan_device *wlan, int event);

/* 执行注册的RT_WLAN_EVT_READY事件回调函数 */

int rt_wlan_prot_ready(struct rt_wlan_device *wlan, struct rt_wlan_buff *buff);

/* 打印全部向WLAN协议层注册的rt_wlan_prot信息 */

void rt_wlan_prot_dump(void);

WLAN协议层向设备层发送/接收数据也是经过调用WLAN驱动库文件提供的rt_wlan_dev_ops实现的;WLAN协议层事件回调函数的注册/注销与与WLAN协议结构体rt_wlan_prot 的注册相似,也是将经过参数传入的事件回调函数指针与参数赋值给全局变量prot_event_tab。这里重点看下WLAN网络协议绑定到WLAN设备的过程:

// rt-thread-4.0.1\components\drivers\wlan\wlan_prot.c

rt_err_t rt_wlan_prot_attach(const char *dev_name, const char *prot_name)

{

struct rt_wlan_device *wlan;

wlan = rt_wlan_prot_find_by_name(dev_name);

......

return rt_wlan_prot_attach_dev(wlan, prot_name);

}

rt_err_t rt_wlan_prot_attach_dev(struct rt_wlan_device *wlan, const char *prot_name)

{

int i = 0;

struct rt_wlan_prot *prot = wlan->prot;

rt_wlan_dev_event_t event;

/* Parameter checking */

......

/* if prot not NULL */

if (prot != RT_NULL)

rt_wlan_prot_detach_dev(wlan);

#ifdef RT_WLAN_PROT_LWIP_PBUF_FORCE

if (rt_strcmp(RT_WLAN_PROT_LWIP, prot_name) != 0)

return -RT_ERROR;

#endif

/* find prot */

for (i = 0; i < RT_WLAN_PROT_MAX; i++)

{

if ((_prot[i] != RT_NULL) && (rt_strcmp(_prot[i]->name, prot_name) == 0))

{

/* attach prot */

wlan->prot = _prot[i]->ops->dev_reg_callback(_prot[i], wlan);

break;

}

}

if (i >= RT_WLAN_PROT_MAX)

return -RT_ERROR;

for (event = RT_WLAN_DEV_EVT_INIT_DONE; event < RT_WLAN_DEV_EVT_MAX; event ++)

{

if (rt_wlan_dev_register_event_handler(wlan, event, rt_wlan_prot_event_handle, RT_NULL) != RT_EOK)

LOG_E("prot register event filed:%d", event);

}

return RT_EOK;

}

函数rt_wlan_prot_attach 主要完成两个操做:一是调用接口函数 rt_wlan_prot_ops->dev_reg_callback完成网络协议适配;二是向WLAN设备层注册事件处理函数 rt_wlan_prot_event_handle,当WLAN设备层有事件发生,WLAN协议层就跟根据发生的事件类型完成相应的事件处理。

WLAN协议层事件状态机

WLAN运行过程当中可能出现的状态或事件比较多,这些状态或事件使用有限状态机模型(能够参考博客:有限状态机)管理。当WLAN设备时发生了相应的事件(好比设备链接、断开等),能经过执行WLAN协议层向WLAN设备层注册的事件回调函数rt_wlan_prot_event_handle,让WLAN协议层针对发生的事件及时作出相应的处理。下面看看WLAN协议层是如何处理WLAN设备层发生事件的:

// rt-thread-4.0.1\components\drivers\wlan\wlan_prot.c

struct rt_wlan_prot_event_des

{

rt_wlan_prot_event_handler handler;

struct rt_wlan_prot *prot;

};

static struct rt_wlan_prot_event_des prot_event_tab[RT_WLAN_PROT_EVT_MAX][RT_WLAN_PROT_MAX];

static void rt_wlan_prot_event_handle(struct rt_wlan_device *wlan, rt_wlan_dev_event_t event, struct rt_wlan_buff *buff, void *parameter)

{

int i;

struct rt_wlan_prot *wlan_prot;

struct rt_wlan_prot *prot;

rt_wlan_prot_event_handler handler;

rt_wlan_prot_event_t prot_event;

wlan_prot = wlan->prot;

handler = RT_NULL;

prot = RT_NULL;

switch (event)

{

case RT_WLAN_DEV_EVT_INIT_DONE:

{

prot_event = RT_WLAN_PROT_EVT_INIT_DONE;

break;

}

case RT_WLAN_DEV_EVT_CONNECT:

{

prot_event = RT_WLAN_PROT_EVT_CONNECT;

break;

}

case RT_WLAN_DEV_EVT_DISCONNECT:

{

prot_event = RT_WLAN_PROT_EVT_DISCONNECT;

break;

}

case RT_WLAN_DEV_EVT_AP_START:

{

prot_event = RT_WLAN_PROT_EVT_AP_START;

break;

}

case RT_WLAN_DEV_EVT_AP_STOP:

{

prot_event = RT_WLAN_PROT_EVT_AP_STOP;

break;

}

case RT_WLAN_DEV_EVT_AP_ASSOCIATED:

{

prot_event = RT_WLAN_PROT_EVT_AP_ASSOCIATED;

break;

}

case RT_WLAN_DEV_EVT_AP_DISASSOCIATED:

{

prot_event = RT_WLAN_PROT_EVT_AP_DISASSOCIATED;

break;

}

default:

return;

}

for (i = 0; i < RT_WLAN_PROT_MAX; i++)

{

if ((prot_event_tab[prot_event][i].handler != RT_NULL) &&

(prot_event_tab[prot_event][i].prot->id == wlan_prot->id))

{

handler = prot_event_tab[prot_event][i].handler;

prot = prot_event_tab[prot_event][i].prot;

break;

}

}

if (handler != RT_NULL)

handler(prot, wlan, prot_event);

}

函数rt_wlan_prot_event_handle 根据参数中标识的事件类型,去查询注册到WLAN协议层的事件回调函数表prot_event_tab,当查找到标识事件有注册相应的事件回调函数后,便执行对应的事件回调函数,完成WLAN事件的处理。这些事件回调函数通常是由调用者根据须要实现并注册的,WLAN管理框架只是在咱们标识的事件发生时自动执行咱们设定的处理程序而已。

3.2 LwIP协议栈移植

向WLAN协议层注册并适配LwIP协议

本文使用的网络协议时LwIP,要想让LwIP网络接口层与WLAN协议层能配合工做,首先须要为WLAN协议层实现并注册接口函数集合rt_wlan_prot_ops,下面先看这些接口函数的注册过程:

// rt-thread-4.0.1\components\drivers\wlan\wlan_lwip.c

static struct rt_wlan_prot_ops ops =

{

rt_wlan_lwip_protocol_recv,

rt_wlan_lwip_protocol_register,

rt_wlan_lwip_protocol_unregister

};

int rt_wlan_lwip_init(void)

{

static struct rt_wlan_prot prot;

rt_wlan_prot_event_t event;

rt_memset(&prot, 0, sizeof(prot));

rt_strncpy(&prot.name[0], RT_WLAN_PROT_LWIP, RT_WLAN_PROT_NAME_LEN);

prot.ops = &ops;

if (rt_wlan_prot_regisetr(&prot) != RT_EOK)

return -1;

for (event = RT_WLAN_PROT_EVT_INIT_DONE; event < RT_WLAN_PROT_EVT_MAX; event++)

rt_wlan_prot_event_register(&prot, event, rt_wlan_lwip_event_handle);

return 0;

}

INIT_PREV_EXPORT(rt_wlan_lwip_init);

函数rt_wlan_lwip_init 主要完成两个操做:一是WLAN协议结构体rt_wlan_prot 的注册(函数rt_wlan_prot_regisetr);二是向WLAN协议层注册事件处理函数rt_wlan_lwip_event_handle。在函数rt_wlan_prot_event_handle 中最后要调用执行向WLAN协议层注册的事件回调函数,也就是这里注册的事件处理函数rt_wlan_lwip_event_handle。

函数rt_wlan_lwip_init 被自动初始化组件调用执行,不须要咱们主动调用。咱们先看下LwIP协议是如何适配到WLAN管理框架的:

// rt-thread-4.0.1\components\drivers\wlan\wlan_lwip.c

struct lwip_prot_des

{

struct rt_wlan_prot prot;

struct eth_device eth;

rt_int8_t connected_flag;

struct rt_timer timer;

struct rt_work work;

};

#ifdef RT_USING_DEVICE_OPS

const static struct rt_device_ops wlan_lwip_ops =

{

RT_NULL,

RT_NULL,

RT_NULL,

RT_NULL,

RT_NULL,

rt_wlan_lwip_protocol_control

};

#endif

static struct rt_wlan_prot *rt_wlan_lwip_protocol_register(struct rt_wlan_prot *prot, struct rt_wlan_device *wlan)

{

struct eth_device *eth = RT_NULL;

static rt_uint8_t id = 0;

char eth_name[4], timer_name[16];

rt_device_t device = RT_NULL;

struct lwip_prot_des *lwip_prot;

......

do

{

/* find ETH device name */

eth_name[0] = 'w';

eth_name[1] = '0' + id++;

eth_name[2] = '\0';

device = rt_device_find(eth_name);

}

while (device);

if (id > 9)

return RT_NULL;

if (rt_device_open((rt_device_t)wlan, RT_DEVICE_OFLAG_RDWR) != RT_EOK)

return RT_NULL;

lwip_prot = rt_malloc(sizeof(struct lwip_prot_des));

if (lwip_prot == RT_NULL)

{

rt_device_close((rt_device_t)wlan);

return RT_NULL;

}

rt_memset(lwip_prot, 0, sizeof(struct lwip_prot_des));

eth = &lwip_prot->eth;

#ifdef RT_USING_DEVICE_OPS

eth->parent.ops = &wlan_lwip_ops;

#else

......

#endif

eth->parent.user_data = wlan;

eth->eth_rx = RT_NULL;

eth->eth_tx = rt_wlan_lwip_protocol_send;

/* register ETH device */

if (eth_device_init(eth, eth_name) != RT_EOK)

{

rt_device_close((rt_device_t)wlan);

rt_free(lwip_prot);

return RT_NULL;

}

rt_memcpy(&lwip_prot->prot, prot, sizeof(struct rt_wlan_prot));

if (wlan->mode == RT_WLAN_STATION)

{

rt_sprintf(timer_name, "timer_%s", eth_name);

rt_timer_init(&lwip_prot->timer, timer_name, timer_callback, wlan, rt_tick_from_millisecond(1000),

RT_TIMER_FLAG_SOFT_TIMER | RT_TIMER_FLAG_ONE_SHOT);

}

netif_set_up(eth->netif);

return &lwip_prot->prot;

}

LwIP协议适配层定义了一个全局结构体 lwip_prot_des ,包含以太网设备对象 eth_device、WLAN协议对象 rt_wlan_prot、网络链接标识connected_flag、定时器对象rt_timer、工做任务对象rt_work 等成员。

LwIP网络接口层处理的是以太网帧数据,这里直接包含了以太网设备对象 eth_device,你能够对比下ENC28J60以太网卡的数据结构描述,也是包含了eth_device。但对比发现结构体 lwip_prot_des 少了WLAN设备的MAC地址信息,为此实现了一个函数rt_wlan_lwip_protocol_control 用来获取WLAN设备的MAC地址(该函数也仅有这一个功能)。因为以太网设备eth_device 继承自设备基类 rt_device,所以也向 I/O 设备管理框架注册了一个网卡设备(函数eth_device_init,可参考博客:网络分层结构 + netdev/SAL原理)。

以太网设备 eth_device 须要实现两个接口函数:eth_rx 与 eth_tx,方便LwIP使用网卡设备完成网络数据流的发生/接收。这里注册的eth_tx 接口函数是 rt_wlan_lwip_protocol_send,最终调用的是WLAN驱动库提供的接口rt_wlan_dev_ops->wlan_send;注册的eth_rx 接口则为RT_NULL,LwIP如何经过网卡接收数据呢?

WLAN协议层接收数据的处理

再回顾下WLAN协议层须要的接口函数集合rt_wlan_prot_ops,除了前面介绍的网络协议注册和注销,还有一个就是数据接收函数rt_wlan_lwip_protocol_recv,该函数被注册到WLAN协议层后,当AP6181 WIFI 芯片接收到数据会调用该函数来处理。ENC28J60以太网卡INT 引脚绑定的中断处理函数 enc28j60_isr 实际调用的是LwIP 网络接口层的函数eth_device_ready,因此LwIP 能够处理ENC28J60网卡的中断信号并从网卡接收数据。

AP6181 WIFI 网卡的WIFI_INT 引脚绑定的中断处理函数 _wiced_irq_handler,也即AP6181 WIFI 网卡的中断信号和数据接收均由WLAN驱动负责,并不禁LwIP 网络接口层负责。要移植LwIP 协议栈,须要向WLAN协议层注册接收函数rt_wlan_lwip_protocol_recv,告诉WLAN协议层接收到的网络数据该如何处理,下面看看接收函数rt_wlan_lwip_protocol_recv 是如何处理WLAN网卡接收到数据的:

// rt-thread-4.0.1\components\drivers\wlan\wlan_lwip.c

static rt_err_t rt_wlan_lwip_protocol_recv(struct rt_wlan_device *wlan, void *buff, int len)

{

struct eth_device *eth_dev = &((struct lwip_prot_des *)wlan->prot)->eth;

struct pbuf *p = RT_NULL;

......

#ifdef RT_WLAN_PROT_LWIP_PBUF_FORCE

{

p = buff;

if ((eth_dev->netif->input(p, eth_dev->netif)) != ERR_OK)

return -RT_ERROR;

return RT_EOK;

}

#else

{

/* alloc pbuf */

......

/*copy data dat -> pbuf*/

......

}

#endif

}

AP6181 WIFI 网卡接收到数据后,会调用函数rt_wlan_lwip_protocol_recv 处理接收到的数据,实际上仍是调用LwIP网络接口层netif 的input接口(也即函数 tcpip_input)将接收到的数据传递给LwIP 上层进行处理。对 eth_device_init 和 tcpip_input 不熟悉的读者,能够参考博客:LwIP协议栈移植。

在函数rt_wlan_lwip_protocol_recv中有一个宏定义选项RT_WLAN_PROT_LWIP_PBUF_FORCE,若该宏被定义则直接强制使用LwIP的pbuf 数据包,若该宏未定义,还须要先分配pbuf 对象再拷贝数据,为了尽量保证效率与性能,咱们能够在宏配置选项BSP_USING_WIFI下新增依赖宏RT_WLAN_PROT_LWIP_PBUF_FORCE。

LwIP适配层事件处理

前面介绍的两个函数rt_wlan_lwip_init 与 rt_wlan_lwip_protocol_register 都还有个尾巴没介绍,分别是LwIP适配层事件处理函数 rt_wlan_lwip_event_handle 和 定时回调函数 timer_callback。先看看LwIP适配层如何处理WLAN协议层的事件:

// rt-thread-4.0.1\components\drivers\wlan\wlan_lwip.c

static void rt_wlan_lwip_event_handle(struct rt_wlan_prot *port, struct rt_wlan_device *wlan, int event)

{

struct lwip_prot_des *lwip_prot = (struct lwip_prot_des *)wlan->prot;

rt_bool_t flag_old;

flag_old = lwip_prot->connected_flag;

switch (event)

{

case RT_WLAN_PROT_EVT_CONNECT:

{

lwip_prot->connected_flag = RT_TRUE;

break;

}

case RT_WLAN_PROT_EVT_DISCONNECT:

{

lwip_prot->connected_flag = RT_FALSE;

break;

}

case RT_WLAN_PROT_EVT_AP_START:

{

lwip_prot->connected_flag = RT_TRUE;

break;

}

case RT_WLAN_PROT_EVT_AP_STOP:

{

lwip_prot->connected_flag = RT_FALSE;

break;

}

case RT_WLAN_PROT_EVT_AP_ASSOCIATED:

break;

case RT_WLAN_PROT_EVT_AP_DISASSOCIATED:

break;

default :

break;

}

if (flag_old != lwip_prot->connected_flag)

rt_wlan_workqueue_dowork(netif_set_connected, wlan);

}

static void netif_set_connected(void *parameter)

{

struct rt_wlan_device *wlan = parameter;

struct lwip_prot_des *lwip_prot = wlan->prot;

struct eth_device *eth_dev = &lwip_prot->eth;

if (lwip_prot->connected_flag)

{

if (wlan->mode == RT_WLAN_STATION)

{

netifapi_netif_common(eth_dev->netif, netif_set_link_up, NULL);

......

rt_timer_start(&lwip_prot->timer);

}

else if (wlan->mode == RT_WLAN_AP)

netifapi_netif_common(eth_dev->netif, netif_set_link_up, NULL);

......

}

else

{

if (wlan->mode == RT_WLAN_STATION)

{

netifapi_netif_common(eth_dev->netif, netif_set_link_down, NULL);

......

rt_timer_stop(&lwip_prot->timer);

}

else if (wlan->mode == RT_WLAN_AP)

netifapi_netif_common(eth_dev->netif, netif_set_link_down, NULL);

}

}

WLAN设备的状态或事件虽多,实际上对于LwIP协议栈来讲,主要关心的就两种:链接、断开;LwIP网络接口层作出的处理也是两种:链路层网卡打开、关闭。若是WIFI Station已链接或AP已启用,LwIP 协议则会打开或启用网络接口,并为其提供网络服务;若是WIFI Station已断开或AP已中止,LwIP 协议则会关闭或禁用网络接口,并中止为其提供网络服务。

接下来再看看向WLAN协议层注册并配置LwIP 协议后,初始化一个定时器,当定时器触发后会执行哪些操做:

// rt-thread-4.0.1\components\drivers\wlan\wlan_lwip.c

static void timer_callback(void *parameter)

{

......

workqueue = rt_wlan_get_workqueue();

if (workqueue != RT_NULL)

{

level = rt_hw_interrupt_disable();

rt_work_init(work, netif_is_ready, parameter);

rt_hw_interrupt_enable(level);

......

}

}

static void netif_is_ready(struct rt_work *work, void *parameter)

{

......

if (rt_wlan_prot_ready(wlan, &buff) != 0)

{

rt_timer_start(&lwip_prot->timer);

goto exit;

}

......

}

定时器触发后执行函数netif_is_ready,从名字能够看出是LwIP 网络接口配置就绪的函数,其内部调用了WLAN协议层介绍过的接口函数 rt_wlan_prot_ready,前面提到该函数执行注册的RT_WLAN_EVT_READY事件回调函数,这个事件回调函数须要咱们本身提早实现并注册,下文再详细介绍。

到这里向WLAN管理框架注册并适配 LwIP 协议栈的工做就完成了,网络设备无关层netdev 和 网络协议无关层 SAL 的移植或适配跟ENC28J60以太网卡中介绍的彻底同样,这里就再也不赘述了,能够参考博客:网络分层结构 + netdev/SAL原理。

因为适配了LwIP 协议栈,咱们须要在AP6181 WIFI 外设的宏配置选项中新增关于LwIP 的宏依赖项,新增脚本代码以下:

// projects\stm32l475_wifi_sample\board\Kconfig

menu "Hardware Drivers Config"

......

menu "Onboard Peripheral Drivers"

......

config BSP_USING_WIFI

bool "Enable WiFi"

......

select RT_USING_DFS

select RT_WLAN_PROT_LWIP_PBUF_FORCE

select RT_USING_LWIP

......

endmenu

......

4、WLAN Config 参数管理与自动链接实现

4.1 WLAN Config 参数管理层

WLAN配置信息数据结构描述

// rt-thread-4.0.1\components\drivers\wlan\wlan_cfg.c

struct rt_wlan_cfg_des

{

rt_uint32_t num;

struct rt_wlan_cfg_info *cfg_info;

};

static struct rt_wlan_cfg_des *cfg_cache;

// rt-thread-4.0.1\components\drivers\wlan\wlan_cfg.h

struct rt_wlan_cfg_info

{

struct rt_wlan_info info;

struct rt_wlan_key key;

};

// rt-thread-4.0.1\components\drivers\wlan\wlan_dev.h

struct rt_wlan_info

{

/* security type */

rt_wlan_security_t security;

/* 2.4G/5G */

rt_802_11_band_t band;

/* maximal data rate */

rt_uint32_t datarate;

/* radio channel */

rt_int16_t channel;

/* signal strength */

rt_int16_t rssi;

/* ssid */

rt_wlan_ssid_t ssid;

/* hwaddr */

rt_uint8_t bssid[RT_WLAN_BSSID_MAX_LENGTH];

rt_uint8_t hidden;

};

struct rt_wlan_ssid

{

rt_uint8_t len;

rt_uint8_t val[RT_WLAN_SSID_MAX_LENGTH + 1];

};

typedef struct rt_wlan_ssid rt_wlan_ssid_t;

struct rt_wlan_key

{

rt_uint8_t len;

rt_uint8_t val[RT_WLAN_PASSWORD_MAX_LENGTH + 1];

};

typedef struct rt_wlan_key rt_wlan_key_t;

/* Enumeration of Wi-Fi security modes */

typedef enum

{

SECURITY_OPEN = 0, /* Open security */

SECURITY_WEP_PSK = WEP_ENABLED, /* WEP Security with open authentication */

SECURITY_WEP_SHARED = (WEP_ENABLED | SHARED_ENABLED), /* WEP Security with shared authentication */

SECURITY_WPA_TKIP_PSK = (WPA_SECURITY | TKIP_ENABLED), /* WPA Security with TKIP */

SECURITY_WPA_AES_PSK = (WPA_SECURITY | AES_ENABLED), /* WPA Security with AES */

SECURITY_WPA2_AES_PSK = (WPA2_SECURITY | AES_ENABLED), /* WPA2 Security with AES */

SECURITY_WPA2_TKIP_PSK = (WPA2_SECURITY | TKIP_ENABLED), /* WPA2 Security with TKIP */

SECURITY_WPA2_MIXED_PSK = (WPA2_SECURITY | AES_ENABLED | TKIP_ENABLED), /* WPA2 Security with AES & TKIP */

SECURITY_WPS_OPEN = WPS_ENABLED, /* WPS with open security */

SECURITY_WPS_SECURE = (WPS_ENABLED | AES_ENABLED), /* WPS with AES security */

SECURITY_UNKNOWN = -1, /* May be returned by scan function if security is unknown.

Do not pass this to the join function! */

} rt_wlan_security_t;

typedef enum

{

RT_802_11_BAND_5GHZ = 0, /* Denotes 5GHz radio band */

RT_802_11_BAND_2_4GHZ = 1, /* Denotes 2.4GHz radio band */

RT_802_11_BAND_UNKNOWN = 0x7fffffff, /* unknown */

} rt_802_11_band_t;

配置信息结构体rt_wlan_cfg_info 包含的配置项有:WIFI 安全模式、WIFI 频段、最大数据传输速率、通讯信道、信号强度、WIFI热点名SSID、WIFI热点密码key、WIFI热点MAC地址、SSID是否隐藏标识等。

WLAN配置管理层接口函数

// rt-thread-4.0.1\components\drivers\wlan\wlan_cfg.h

/* WLAN配置管理层初始化 */

void rt_wlan_cfg_init(void);

/* 向WLAN配置管理层注册接口函数 */

void rt_wlan_cfg_set_ops(const struct rt_wlan_cfg_ops *ops);

/* 得到WLAN配置的WIFI热点信息数量 */

int rt_wlan_cfg_get_num(void);

/* 从缓存中读取全部的WIFI热点配置信息 */

int rt_wlan_cfg_read(struct rt_wlan_cfg_info *cfg_info, int num);

/* 从缓存中读取指定的WIFI热点配置信息 */

int rt_wlan_cfg_read_index(struct rt_wlan_cfg_info *cfg_info, int index);

/* 从Flash中读取存储的WIFI配置信息到缓存中 */

rt_err_t rt_wlan_cfg_cache_refresh(void);

/* 将WIFI配置信息保存到缓存和Flash闪存中 */

rt_err_t rt_wlan_cfg_save(struct rt_wlan_cfg_info *cfg_info);

/* 将缓存中的WIFI配置信息保存到Flash闪存中 */

rt_err_t rt_wlan_cfg_cache_save(void);

/* 从缓存中删除指定的WIFI热点配置信息 */

int rt_wlan_cfg_delete_index(int index);

/* 从缓存中删除全部的WIFI热点配置信息 */

void rt_wlan_cfg_delete_all(void);

/* 打印缓存中全部的WIFI热点配置信息 */

void rt_wlan_cfg_dump(void);

在缓存或内存中读取、保存或写入、删除数据(配置信息)比较容易理解,重点是将缓存中的数据保存到Flash闪存,或从Flash闪存中存储的配置信息读取到缓存中,须要向WLAN配置管理层提供访问Flash的接口函数。上面介绍过的函数 rt_wlan_cfg_set_ops 即可以向WLAN Config层注册接口函数集合 rt_wlan_cfg_ops,下面看WLAN Config层须要哪些接口函数,又是如何注册的:

// rt-thread-4.0.1\components\drivers\wlan\wlan_cfg.h

struct rt_wlan_cfg_ops

{

int (*read_cfg)(void *buff, int len);

int (*get_len)(void);

int (*write_cfg)(void *buff, int len);

};

// rt-thread-4.0.1\components\drivers\wlan\wlan_cfg.c

struct rt_wlan_cfg_des

{

rt_uint32_t num;

struct rt_wlan_cfg_info *cfg_info;

};

static struct rt_wlan_cfg_des *cfg_cache;

static const struct rt_wlan_cfg_ops *cfg_ops;

void rt_wlan_cfg_set_ops(const struct rt_wlan_cfg_ops *ops)

{

rt_wlan_cfg_init();

WLAN_CFG_LOCK();

/* save ops pointer */

cfg_ops = ops;

WLAN_CFG_UNLOCK();

}

须要向WLAN Config层注册的接口函数rt_wlan_cfg_ops 包含读取数据、获取数据长度、写入数据这三个成员,注册函数 rt_wlan_cfg_set_ops 则是将参数传入的接口函数集合赋值给WLAN Config层的全局变量cfg_cache。

4.2 rt_wlan_cfg_ops 实现与注册

咱们在博客:FAL分区管理与easyflash变量管理中,不只介绍了FAL分区管理(好比WLAN固件库文件存储的wifi_image 分区),还介绍了Easyflash变量管理。博客中介绍的变量管理主要用于管理环境变量,固然也能够用来管理这里的WLAN配置信息,因此咱们可使用Easyflash组件提供的接口函数来实现rt_wlan_cfg_ops。

咱们对AP6181 WLAN驱动处理WIFI配置信息的数据格式要求并不了解,仍然从Pandora源码包中复制WLAN Config移植文件:

// Pandora IOT Board Release 1.2.0中WLAN Config移植文件路径

.\IoT_Board\examples\16_iot_wifi_manager\ports\wifi\wifi_config.h

.\IoT_Board\examples\16_iot_wifi_manager\ports\wifi\wifi_config.c

.\IoT_Board\examples\16_iot_wifi_manager\ports\wifi\SConscript

// WLAN Config移植文件拷贝到咱们工程中的目标路径

.\RT-Thread_Projects\projects\stm32l475_wifi_sample\ports\wifi\wifi_config.h

.\RT-Thread_Projects\projects\stm32l475_wifi_sample\ports\wifi\wifi_config.c

.\RT-Thread_Projects\projects\stm32l475_wifi_sample\ports\wifi\SConscript

因为咱们工程的ports目录下有一个SConscript 脚本文件,能够将ports目录下的全部子目录内的SConscript 文件都添加到工程中,这里就不须要额外新增编译控制脚本代码了。

从wifi_config.c 代码中能够看出,WLAN配置信息的读取和写入涉及到了Base64编解码,Base64能够看做是一种数据加解密算法。对WLAN配置信息进行加密处理,而不是直接将ASCII明文存储到Flash中,能够防止恶意者泄露咱们的WLAN配置信息,保障网络通讯安全。固然你也能够采用其它的加解密算法,甚至不采用加解密算法。

咱们先不关心WLAN配置信息的加解密算法,看看接口函数集合 rt_wlan_cfg_ops 是如何实现并注册的:

// projects\stm32l475_wifi_sample\ports\wifi\wifi_config.c

static int read_cfg(void *buff, int len)

{

char *wlan_cfg_info = RT_NULL;

wlan_cfg_info = ef_get_env("wlan_cfg_info");

if (wlan_cfg_info != RT_NULL)

{

str_base64_decode(wlan_cfg_info, rt_strlen(wlan_cfg_info), buff);

return len;

}

else

return 0;

}

static int get_len(void)

{

int len;

char *wlan_cfg_len = RT_NULL;

wlan_cfg_len = ef_get_env("wlan_cfg_len");

if (wlan_cfg_len == RT_NULL)

len = 0;

else

len = atoi(wlan_cfg_len);

return len;

}

static int write_cfg(void *buff, int len)

{

char wlan_cfg_len[12] = {0};

char *base64_buf = RT_NULL;

base64_buf = rt_malloc(len * 4 / 3 + 4); /* 3-byte blocks to 4-byte, and the end. */

if (base64_buf == RT_NULL)

return -RT_ENOMEM;

rt_memset(base64_buf, 0, len);

/* interger to string */

sprintf(wlan_cfg_len, "%d", len);

/* set and store the wlan config lengths to Env */

ef_set_env("wlan_cfg_len", wlan_cfg_len);

str_base64_encode_len(buff, base64_buf, len);

/* set and store the wlan config information to Env */

ef_set_env("wlan_cfg_info", base64_buf);

ef_save_env();

rt_free(base64_buf);

return len;

}

static const struct rt_wlan_cfg_ops ops =

{

read_cfg,

get_len,

write_cfg

};

void wlan_autoconnect_init(void)

{

fal_init();

easyflash_init();

rt_wlan_cfg_set_ops(&ops);

rt_wlan_cfg_cache_refresh();

}

在函数wlan_autoconnect_init 中,先执行fal_init 与 easyflash_init 完成FAL组件与Easyflash组件的初始化,接着经过前面介绍的函数rt_wlan_cfg_set_ops 将这里实现的接口函数集rt_wlan_cfg_ops 注册到WLAN Config层,最后调用函数rt_wlan_cfg_cache_refresh 将Flash中存储的WLAN配置信息读取到缓存中,供WLAN管理框架使用。

函数wlan_autoconnect_init 并无被自动初始化组件调用,咱们能够在这里添加代码,将该函数的调用执行交给RT-Thread的自动初始化组件,也能够在应用中主动调用函数wlan_autoconnect_init ,本文就保持默认,在应用中根据须要主动调用吧。

这里又使用Easyflash组件,咱们须要再往宏配置选项BSP_USING_WIFI 下新增依赖宏PKG_USING_EASYFLASH。WLAN管理框架中的Device层、Protocol层和Config层的移植和适配已经完成,依赖的组件已经添加进工程;WLAN Manager 层并不依赖其它组件,主要是为咱们提供更方便友好的WLAN管理接口,WLAN Airkiss / Voice配网层算是可选功能,本文暂不介绍,因此宏配置选项BSP_USING_WIFI 下的依赖宏基本所有肯定了,下面给出该部分的完整版配置代码:

// .\RT-Thread_Projects\projects\stm32l475_wifi_sample\board\Kconfig

menu "Hardware Drivers Config"

......

menu "Onboard Peripheral Drivers"

......

config BSP_USING_WIFI

bool "Enable WiFi"

select BSP_USING_SDIO

select BSP_USING_QSPI_FLASH

select PKG_USING_FAL

select RT_USING_WIFI

select RT_USING_WIFI_6181_LIB

select RT_USING_OTA_LIB

select RT_USING_LIBC

select RT_USING_DFS

select RT_WLAN_PROT_LWIP_PBUF_FORCE

select RT_USING_LWIP

select PKG_USING_EASYFLASH

default n

if BSP_USING_WIFI

config BSP_USING_WIFI_THREAD_INIT

bool "Using Thread Initialize WiFi"

default n

config BSP_USING_WIFI_AUTO_INIT

bool "Using WiFi Automatically Initialization"

depends on RT_USING_COMPONENTS_INIT

default y

endif

endmenu

5、WLAN Manager 实现原理

WLAN管理层直接向用户提供WLAN设备的访问接口,可以对 WLAN 设备进行控制和管理,该层接口函数的实现多数都是对WLAN Device设备层接口函数的再封装,但比WLAN Device层提供的接口函数更方便友好。

WLAN管理结构描述

// rt-thread-4.0.1\components\drivers\wlan\wlan_mgnt.c

struct rt_wlan_mgnt_des

{

struct rt_wlan_device *device;

struct rt_wlan_info info;

struct rt_wlan_key key;

rt_uint8_t state;

rt_uint8_t flags;

};

static struct rt_wlan_mgnt_des _sta_mgnt;

static struct rt_wlan_mgnt_des _ap_mgnt;

static struct rt_wlan_scan_result scan_result;

// rt-thread-4.0.1\components\drivers\wlan\wlan_mgnt.h

struct rt_wlan_scan_result

{

rt_int32_t num;

struct rt_wlan_info *info;

};

/*state fot station*/

#define RT_WLAN_STATE_CONNECT (1UL << 0)

#define RT_WLAN_STATE_CONNECTING (1UL << 1)

#define RT_WLAN_STATE_READY (1UL << 2)

#define RT_WLAN_STATE_POWERSAVE (1UL << 3)

/*flags fot station*/

#define RT_WLAN_STATE_AUTOEN (1UL << 0)

/*state fot ap*/

#define RT_WLAN_STATE_ACTIVE (1UL << 0)

WLAN管理结构体rt_wlan_mgnt_des 包含WLAN设备对象指针 *device、WLAN信息结构体 info、WIFI热点密码key、WLAN所处的状态state(CONNECT / CONNECTING / READY / POWERSAVE)、WLAN标识位(Station AUTOEN / AP ACTIVE)等成员,而且Station和AP各建立一个WLAN管理对象(全局变量 _sta_mgnt、_ap_mgnt)。

WLAN信息结构体rt_wlan_info 和WIFI 热点密码结构体 rt_wlan_key,在前面WLAN Config层介绍WLAN配置信息结构体rt_wlan_cfg_info 时都简单介绍过了。WLAN 在链接前扫描周围的热点信息也是常常遇到的场景,所以WLAN管理框架为便于保存扫描出的周围WIFI 热点信息,提供了一个结构体 rt_wlan_scan_result,用来保存每一个WIFI 热点信息结构体指针 *info 和扫描到的WIFI 热点数量num。

WLAN管理层接口函数

// rt-thread-4.0.1\components\drivers\wlan\wlan_mgnt.h

/* wifi init interface */

int rt_wlan_init(void);

rt_err_t rt_wlan_set_mode(const char *dev_name, rt_wlan_mode_t mode);

rt_wlan_mode_t rt_wlan_get_mode(const char *dev_name);

/* wifi station mode interface */

rt_err_t rt_wlan_connect(const char *ssid, const char *password);

rt_err_t rt_wlan_connect_adv(struct rt_wlan_info *info, const char *password);

rt_err_t rt_wlan_disconnect(void);

rt_bool_t rt_wlan_is_connected(void);

rt_bool_t rt_wlan_is_ready(void);

rt_err_t rt_wlan_set_mac(rt_uint8_t *mac);

rt_err_t rt_wlan_get_mac(rt_uint8_t *mac);

rt_err_t rt_wlan_get_info(struct rt_wlan_info *info);

int rt_wlan_get_rssi(void);

/* wifi ap mode interface */

rt_err_t rt_wlan_start_ap(const char *ssid, const char *password);

rt_err_t rt_wlan_start_ap_adv(struct rt_wlan_info *info, const char *password);

rt_bool_t rt_wlan_ap_is_active(void);

rt_err_t rt_wlan_ap_stop(void);

rt_err_t rt_wlan_ap_get_info(struct rt_wlan_info *info);

int rt_wlan_ap_get_sta_num(void);

int rt_wlan_ap_get_sta_info(struct rt_wlan_info *info, int num);

rt_err_t rt_wlan_ap_deauth_sta(rt_uint8_t *mac);

rt_err_t rt_wlan_ap_set_country(rt_country_code_t country_code);

rt_country_code_t rt_wlan_ap_get_country(void);

/* wifi scan interface */

rt_err_t rt_wlan_scan(void);

struct rt_wlan_scan_result *rt_wlan_scan_sync(void);

struct rt_wlan_scan_result *rt_wlan_scan_with_info(struct rt_wlan_info *info);

int rt_wlan_scan_get_info_num(void);

int rt_wlan_scan_get_info(struct rt_wlan_info *info, int num);

struct rt_wlan_scan_result *rt_wlan_scan_get_result(void);

void rt_wlan_scan_result_clean(void);

int rt_wlan_scan_find_cache(struct rt_wlan_info *info, struct rt_wlan_info *out_info, int num);

rt_bool_t rt_wlan_find_best_by_cache(const char *ssid, struct rt_wlan_info *info);

/* wifi auto connect interface */

void rt_wlan_config_autoreconnect(rt_bool_t enable);

rt_bool_t rt_wlan_get_autoreconnect_mode(void);

/* wifi power management interface */

rt_err_t rt_wlan_set_powersave(int level);

int rt_wlan_get_powersave(void);

/* wifi event management interface */

rt_err_t rt_wlan_register_event_handler(rt_wlan_event_t event, rt_wlan_event_handler handler, void *parameter);

rt_err_t rt_wlan_unregister_event_handler(rt_wlan_event_t event);

/* wifi management lock interface */

void rt_wlan_mgnt_lock(void);

void rt_wlan_mgnt_unlock(void);

从上面WLAN管理层的接口函数声明与前面介绍的WLAN设备接口层的接口函数声明对比能够看出,WLAN管理层的接口函数参数比较简单友好,没有那么多结构体须要构造,并且参数个数也减小了,这是如何作到的呢?

WLAN管理层定义了很多全局变量(虽被static 修饰,在WLAN管理层内部也即本文档内不受限制),将WLAN管理层经常使用到的信息(好比管理结构体rt_wlan_mgnt_des)保存到全局变量中,WLAN管理层内部函数就能够直接使用这些全局变量存储的信息,而不须要再经过参数传入。这些全局变量的初始化能够在WLAN管理层初始化函数中看到:

// rt-thread-4.0.1\components\drivers\wlan\wlan_mgnt.c

int rt_wlan_init(void)

{

static rt_int8_t _init_flag = 0;

/* Execute only once */

if (_init_flag == 0)

{

rt_memset(&_sta_mgnt, 0, sizeof(struct rt_wlan_mgnt_des));

rt_memset(&_ap_mgnt, 0, sizeof(struct rt_wlan_mgnt_des));

rt_memset(&scan_result, 0, sizeof(struct rt_wlan_scan_result));

rt_memset(&sta_info, 0, sizeof(struct rt_wlan_sta_des));

rt_mutex_init(&mgnt_mutex, "mgnt", RT_IPC_FLAG_FIFO);

rt_mutex_init(&scan_result_mutex, "scan", RT_IPC_FLAG_FIFO);

rt_mutex_init(&sta_info_mutex, "sta", RT_IPC_FLAG_FIFO);

rt_mutex_init(&complete_mutex, "complete", RT_IPC_FLAG_FIFO);

rt_timer_init(&reconnect_time, "wifi_tim", rt_wlan_cyclic_check, RT_NULL, DISCONNECT_RESPONSE_TICK, RT_TIMER_FLAG_PERIODIC | RT_TIMER_FLAG_SOFT_TIMER);

rt_timer_start(&reconnect_time);

_init_flag = 1;

}

return 0;

}

INIT_PREV_EXPORT(rt_wlan_init);

函数rt_wlan_init 被自动初始化组件调用,除了初始化WLAN管理层用到的全局变量和互斥量,还初始化并启动了一个重连定时器,当定时器超时触发会执行注册到的超时回调函数rt_wlan_cyclic_check,这个函数作了哪些操做呢?

// rt-thread-4.0.1\components\drivers\wlan\wlan_mgnt.c

static void rt_wlan_cyclic_check(void *parameter)

{

struct rt_workqueue *workqueue;

static struct rt_work work;

rt_base_t level;

if ((_is_do_connect() == RT_TRUE) && (work.work_func == RT_NULL))

{

workqueue = rt_wlan_get_workqueue();

if (workqueue != RT_NULL)

{

level = rt_hw_interrupt_disable();

rt_work_init(&work, rt_wlan_auto_connect_run, RT_NULL);

rt_hw_interrupt_enable(level);

if (rt_workqueue_dowork(workqueue, &work) != RT_EOK)

......

}

}

}

static void rt_wlan_auto_connect_run(struct rt_work *work, void *parameter)

{

static rt_uint32_t id = 0;

struct rt_wlan_cfg_info cfg_info;

char *password = RT_NULL;

rt_base_t level;

......

/* auto connect status is disable or wifi is connect or connecting, exit */

......

/* Read the next configuration */

rt_memset(&cfg_info, 0, sizeof(struct rt_wlan_cfg_info));

if (rt_wlan_cfg_read_index(&cfg_info, id ++) == 0)

{

id = 0;

goto exit;

}

if (id >= rt_wlan_cfg_get_num()) id = 0;

if ((cfg_info.key.len > 0) && (cfg_info.key.len < RT_WLAN_PASSWORD_MAX_LENGTH))

{

cfg_info.key.val[cfg_info.key.len] = '\0';

password = (char *)(&cfg_info.key.val[0]);

}

rt_wlan_connect_adv(&cfg_info.info, password);

exit:

......

}

重连定时器reconnect_time 的回调函数rt_wlan_cyclic_check 实际至关于调用执行函数rt_wlan_auto_connect_run,该函数从缓存中读取WLAN配置信息,并逐个尝试使用读取的WIFI 热点配置信息链接周围的热点(经过函数rt_wlan_connect_adv),若是链接失败会尝试下一个WIFI 热点配置信息(重连定时器reconnect_time 为周期定时器)。

前面介绍过的WLAN驱动移植适配层(源文件drv_wlan.c)初始化函数 rt_hw_wlan_init 中,在完成WLAN设备驱动初始化(函数 wifi_hw_init)后,会调用WLAN管理层的函数 rt_wlan_set_mode,该函数除了设置WLAN工做模式还执行了什么操做呢?

// rt-thread-4.0.1\components\drivers\wlan\wlan_mgnt.c

rt_err_t rt_wlan_set_mode(const char *dev_name, rt_wlan_mode_t mode)

{

rt_device_t device = RT_NULL;

rt_err_t err;

rt_int8_t up_event_flag = 0;

rt_wlan_dev_event_handler handler = RT_NULL;

......

/* find device */

device = rt_device_find(dev_name);

......

/* device == sta and change to ap, should deinit; device == ap and change to sta, should deinit */

if (((mode == RT_WLAN_STATION) && (RT_WLAN_DEVICE(device) == AP_DEVICE())) ||

((mode == RT_WLAN_AP) && (RT_WLAN_DEVICE(device) == STA_DEVICE())))

{

err = rt_wlan_set_mode(dev_name, RT_WLAN_NONE);

......

}

/* init device */

err = rt_wlan_dev_init(RT_WLAN_DEVICE(device), mode);

......

/* the mode is none */

if (mode == RT_WLAN_NONE)

{

up_event_flag = 1;

handler = RT_NULL;

......

}

/* save sta device */

else if (mode == RT_WLAN_STATION)

{

up_event_flag = 1;

handler = rt_wlan_event_dispatch;

_sta_mgnt.device = RT_WLAN_DEVICE(device);

}

/* save ap device */

else if (mode == RT_WLAN_AP)

{

up_event_flag = 1;

handler = rt_wlan_event_dispatch;

_ap_mgnt.device = RT_WLAN_DEVICE(device);

}

/* update dev event handle */

if (up_event_flag == 1)

{

rt_wlan_dev_event_t event;

for (event = RT_WLAN_DEV_EVT_INIT_DONE; event < RT_WLAN_DEV_EVT_MAX; event++)

{

if (handler)

rt_wlan_dev_register_event_handler(RT_WLAN_DEVICE(device), event, handler, RT_NULL);

else

rt_wlan_dev_unregister_event_handler(RT_WLAN_DEVICE(device), event, handler);

}

}

MGNT_UNLOCK();

/* Mount protocol */

#ifdef RT_WLAN_DEFAULT_PROT

rt_wlan_prot_attach(dev_name, RT_WLAN_DEFAULT_PROT);

#endif

return err;

}

函数rt_wlan_set_mode 不只完成WLAN工做模式的配置(函数 rt_wlan_dev_init 中经过调用rt_wlan_dev_ops->wlan_mode 实现),还完成了WLAN设备事件回调函数 rt_wlan_event_dispatch 的注册。当WLAN Device有事件发生时,会调用WLAN管理层注册的事件处理函数rt_wlan_event_dispatch 完成对相应事件的处理。

函数rt_wlan_set_mode最后调用了WLAN协议层介绍过的协议绑附适配函数rt_wlan_prot_attach,在该函数中也是向WLAN设备接口层注册了事件回调函数 rt_wlan_prot_event_handle。当WLAN Device有事件发生时,也会调用WLAN协议层注册的事件处理函数rt_wlan_prot_attach(最终是调用LwIP适配层向WLAN协议层注册的事件处理函数rt_wlan_lwip_event_handle)完成对相应事件的处理。

WLAN管理层与WLAN协议层都向WLAN设备接口层注册了事件处理回调函数,两者会发生冲突吗?回头看WLAN设备接口层对事件回调函数列表的定义,向WLAN设备接口层注册事件回调函数以及该层执行注册的回调函数的过程以下:

// rt-thread-4.0.1\components\drivers\wlan\wlan_dev.h

#define RT_WLAN_DEV_EVENT_NUM (2)/* EVENT GROUP MAX NUM */

struct rt_wlan_device

{

......

struct rt_wlan_dev_event_desc handler_table[RT_WLAN_DEV_EVT_MAX][RT_WLAN_DEV_EVENT_NUM];

......

};

// rt-thread-4.0.1\components\drivers\wlan\wlan_dev.c

rt_err_t rt_wlan_dev_register_event_handler(struct rt_wlan_device *device, rt_wlan_dev_event_t event, rt_wlan_dev_event_handler handler, void *parameter)

{

......

for (i = 0; i < RT_WLAN_DEV_EVENT_NUM; i++)

{

if (device->handler_table[event][i].handler == RT_NULL)

{

device->handler_table[event][i].handler = handler;

device->handler_table[event][i].parameter = parameter;

rt_hw_interrupt_enable(level);

return RT_EOK;

}

}

......

}

void rt_wlan_dev_indicate_event_handle(struct rt_wlan_device *device, rt_wlan_dev_event_t event, struct rt_wlan_buff *buff)

{

void *parameter[RT_WLAN_DEV_EVENT_NUM];

rt_wlan_dev_event_handler handler[RT_WLAN_DEV_EVENT_NUM];

int i;

rt_base_t level;

......

/* get callback handle */

level = rt_hw_interrupt_disable();

for (i = 0; i < RT_WLAN_DEV_EVENT_NUM; i++)

{

handler[i] = device->handler_table[event][i].handler;

parameter[i] = device->handler_table[event][i].parameter;

}

rt_hw_interrupt_enable(level);

/* run callback */

for (i = 0; i < RT_WLAN_DEV_EVENT_NUM; i++)

{

if (handler[i] != RT_NULL)

handler[i](device, event, buff, parameter[i]);

}

}

WLAN设备接口层定义的事件回调函数表是一个二维数组,第二个维度存储的是组数,按照注册的前后顺序,WLAN管理层注册的事件回调函数是第一组,WLAN协议层注册的事件回调函数是第二组。当WLAN设备层有事件发生时,注册的全部事件回调函数都会被依序调用执行。

WLAN管理层事件状态机

下面看WLAN管理层的事件调度处理函数实现过程:

// rt-thread-4.0.1\components\drivers\wlan\wlan_mgnt.c

static void rt_wlan_event_dispatch(struct rt_wlan_device *device, rt_wlan_dev_event_t event, struct rt_wlan_buff *buff, void *parameter)

{

rt_base_t level;

void *user_parameter;

rt_wlan_event_handler handler = RT_NULL;

rt_err_t err = RT_NULL;

rt_wlan_event_t user_event = RT_WLAN_EVT_MAX;

int i;

struct rt_wlan_buff user_buff = { 0 };

if (buff)

user_buff = *buff;

/* Event Handle */

switch (event)

{

case RT_WLAN_DEV_EVT_CONNECT:

{

_sta_mgnt.state |= RT_WLAN_STATE_CONNECT;

_sta_mgnt.state &= ~RT_WLAN_STATE_CONNECTING;

user_event = RT_WLAN_EVT_STA_CONNECTED;

TIME_STOP();

rt_wlan_send_msg(event, RT_NULL, 0);

user_buff.data = &_sta_mgnt.info;

user_buff.len = sizeof(struct rt_wlan_info);

break;

}

case RT_WLAN_DEV_EVT_CONNECT_FAIL:

{

_sta_mgnt.state &= ~RT_WLAN_STATE_CONNECT;

_sta_mgnt.state &= ~RT_WLAN_STATE_CONNECTING;

_sta_mgnt.state &= ~RT_WLAN_STATE_READY;

user_event = RT_WLAN_EVT_STA_CONNECTED_FAIL;

user_buff.data = &_sta_mgnt.info;

user_buff.len = sizeof(struct rt_wlan_info);

TIME_START();

break;

}

case RT_WLAN_DEV_EVT_DISCONNECT:

{

_sta_mgnt.state &= ~RT_WLAN_STATE_CONNECT;

_sta_mgnt.state &= ~RT_WLAN_STATE_READY;

user_event = RT_WLAN_EVT_STA_DISCONNECTED;

user_buff.data = &_sta_mgnt.info;

user_buff.len = sizeof(struct rt_wlan_info);

TIME_START();

break;

}

case RT_WLAN_DEV_EVT_AP_START:

{

_ap_mgnt.state |= RT_WLAN_STATE_ACTIVE;

user_event = RT_WLAN_EVT_AP_START;

user_buff.data = &_ap_mgnt.info;

user_buff.len = sizeof(struct rt_wlan_info);

break;

}

case RT_WLAN_DEV_EVT_AP_STOP:

{

_ap_mgnt.state &= ~RT_WLAN_STATE_ACTIVE;

user_event = RT_WLAN_EVT_AP_STOP;

err = rt_wlan_sta_info_del_all(RT_WAITING_FOREVER);

......

user_buff.data = &_ap_mgnt.info;

user_buff.len = sizeof(struct rt_wlan_info);

break;

}

case RT_WLAN_DEV_EVT_AP_ASSOCIATED:

{

user_event = RT_WLAN_EVT_AP_ASSOCIATED;

if (user_buff.len != sizeof(struct rt_wlan_info))

break;

err = rt_wlan_sta_info_add(user_buff.data, RT_WAITING_FOREVER);

......

break;

}

case RT_WLAN_DEV_EVT_AP_DISASSOCIATED:

{

user_event = RT_WLAN_EVT_AP_DISASSOCIATED;

if (user_buff.len != sizeof(struct rt_wlan_info))

break;

err = rt_wlan_sta_info_del(user_buff.data, RT_WAITING_FOREVER);

......

break;

}

case RT_WLAN_DEV_EVT_AP_ASSOCIATE_FAILED:

break;

case RT_WLAN_DEV_EVT_SCAN_REPORT:

{

user_event = RT_WLAN_EVT_SCAN_REPORT;

if (user_buff.len != sizeof(struct rt_wlan_info))

break;

rt_wlan_scan_result_cache(user_buff.data, 0);

break;

}

case RT_WLAN_DEV_EVT_SCAN_DONE:

{

user_buff.data = &scan_result;

user_buff.len = sizeof(scan_result);

user_event = RT_WLAN_EVT_SCAN_DONE;

break;

}

default :

return;

}

/* send event */

COMPLETE_LOCK();

for (i = 0; i < sizeof(complete_tab) / sizeof(complete_tab[0]); i++)

{

if ((complete_tab[i] != RT_NULL))

{

complete_tab[i]->event_flag |= 0x1 << event;

rt_event_send(&complete_tab[i]->complete, 0x1 << event);

}

}

COMPLETE_UNLOCK();

/* Get user callback */

if (user_event < RT_WLAN_EVT_MAX)

{

level = rt_hw_interrupt_disable();

handler = event_tab[user_event].handler;

user_parameter = event_tab[user_event].parameter;

rt_hw_interrupt_enable(level);

}

/* run user callback fun */

if (handler)

handler(user_event, &user_buff, user_parameter);

}

当WLAN设备层有事件发生时,调用WLAN管理层注册的事件调度函数rt_wlan_event_dispatch,该函数根据事件类型对WLAN管理层的全局变量进行相应的配置,最后运行用户注册到WLAN管理层的事件回调函数(支持传入参数)。

在函数rt_wlan_event_dispatch中除了根据发生的事件类型调用相应的事件回调函数外,还向complete_tab[i]发送了事件,这是何意呢?再回头看WLAN管理层接口函数中名字和功能相近的几对儿:

// rt-thread-4.0.1\components\drivers\wlan\wlan_mgnt.h

/* 阻塞式链接热点,链接成功或失败后才会返回 */

rt_err_t rt_wlan_connect(const char *ssid, const char *password);

/* 非阻塞链接热点(advance),返回值仅表示链接动做是否开始执行,是否链接成功须要主动查询或设置回调通知 */

rt_err_t rt_wlan_connect_adv(struct rt_wlan_info *info, const char *password);

/* 阻塞式启动热点,返回值表示是否启动成功 */

rt_err_t rt_wlan_start_ap(const char *ssid, const char *password);

/* 非阻塞启动热点,热点是否启动须要手动查询或回调通知 */

rt_err_t rt_wlan_start_ap_adv(struct rt_wlan_info *info, const char *password);

/* 异步扫描函数,扫描完成须要经过回调进行通知 */

rt_err_t rt_wlan_scan(void);

/* 同步扫描函数,扫描所有热点信息,完成过直接返回扫描结果 */

struct rt_wlan_scan_result *rt_wlan_scan_sync(void);

WLAN管理层提供的链接热点、启动热点、扫描热点等都有阻塞/非阻塞、异步/同步两个版本,WLAN设备层或驱动库文件提供的接口函数都不负责同步或阻塞等待,而是直接异步/非阻塞执行。WLAN管理层提供的同步或阻塞等待接口须要该层本身实现,WLAN管理层也正是经过一个全局事件对象complete_tab实现同步/阻塞等待功能的。当调用同步或阻塞等待接口函数时,会先建立一个等待事件complete,接着执行对应的异步/非阻塞函数,阻塞等待接收事件complete;当设备层发生指望的事件后,调用执行函数rt_wlan_event_dispatch,在该函数中发送事件complete,而后释放事件complete资源并返回,实现同步或阻塞等待的目的。

6、WIFI与Socket网络开发示例

6.1 WLAN框架使用示例

在编写WIFI 链接示例程序前,先把以前介绍DFS文件系统和Sensor传感器管理框架时使用的例程整理下,只保留须要系统自动初始化的部分驱动代码,好比QSPI Flash驱动初始化、AHT10 Sensor初始化等。

QSPI Flash驱动初始化代码

// projects\stm32l475_wifi_sample\applications\drv_qspi_flash.c

#include

#include

#include

#include

#include

#ifdef BSP_USING_QSPI_FLASH

#include "spi_flash.h"

#include "spi_flash_sfud.h"

//#define DRV_DEBUG

#define LOG_TAG "drv.qspi"

#include

char w25qxx_read_status_register2(struct rt_qspi_device *device)

{

/* 0x35 read status register2 */

char instruction = 0x35, status;

rt_qspi_send_then_recv(device, &instruction, 1, &status, 1);

return status;

}

void w25qxx_write_enable(struct rt_qspi_device *device)

{

/* 0x06 write enable */

char instruction = 0x06;

rt_qspi_send(device, &instruction, 1);

}

void w25qxx_enter_qspi_mode(struct rt_qspi_device *device)

{

char status = 0;

/* 0x38 enter qspi mode */

char instruction = 0x38;

char write_status2_buf[2] = {0};

/* 0x31 write status register2 */

write_status2_buf[0] = 0x31;

status = w25qxx_read_status_register2(device);

if (!(status & 0x02))

{

status |= 1 << 1;

w25qxx_write_enable(device);

write_status2_buf[1] = status;

rt_qspi_send(device, &write_status2_buf, 2);

rt_qspi_send(device, &instruction, 1);

LOG_I("flash already enter qspi mode.");

rt_thread_mdelay(10);

}

}

static int rt_hw_qspi_flash_with_sfud_init(void)

{

stm32_qspi_bus_attach_device("qspi1", "qspi10", RT_NULL, 4, w25qxx_enter_qspi_mode, RT_NULL);

/* init w25q128 */

if (RT_NULL == rt_sfud_flash_probe("W25Q128", "qspi10"))

{

return -RT_ERROR;

}

return RT_EOK;

}

INIT_COMPONENT_EXPORT(rt_hw_qspi_flash_with_sfud_init);

......

#endif/* BSP_USING_QSPI_FLASH */

AHT10 Sensor 初始化代码

// projects\stm32l475_wifi_sample\applications\sensor_port.c

#include

#ifdef PKG_USING_AHT10

#include "sensor_asair_aht10.h"

#define AHT10_I2C_BUS "i2c1"

int rt_hw_aht10_port(void)

{

struct rt_sensor_config cfg;

cfg.intf.dev_name = AHT10_I2C_BUS;

cfg.intf.user_data = (void *)AHT10_I2C_ADDR;

rt_hw_aht10_init("aht10", &cfg);

return RT_EOK;

}

INIT_ENV_EXPORT(rt_hw_aht10_port);

#endif

只保留上面这些代码,删除以前示例遗留的源文件,咱们就能够开始编写WLAN 示例工程代码了。

WLAN事件回调函数

在编写WLAN工程代码前,咱们再回顾下WLAN协议层的函数rt_wlan_prot_ready,该函数被LwIP 适配层的函数netif_is_ready调用,实际执行的是WLAN管理层的函数rt_wlan_prot_ready_event,也即当LwIP协议网络接口完成初始化后会调用执行函数rt_wlan_prot_ready_event,该函数实现代码以下:

// rt-thread-4.0.1\components\drivers\wlan\wlan_mgnt.c

int rt_wlan_prot_ready_event(struct rt_wlan_device *wlan, struct rt_wlan_buff *buff)

{

rt_base_t level;

void *user_parameter;

rt_wlan_event_handler handler = RT_NULL;

if ((wlan == RT_NULL) || (_sta_mgnt.device != wlan) || (!(_sta_mgnt.state & RT_WLAN_STATE_CONNECT)))

return -1;

if (_sta_mgnt.state & RT_WLAN_STATE_READY)

return 0;

level = rt_hw_interrupt_disable();

_sta_mgnt.state |= RT_WLAN_STATE_READY;

handler = event_tab[RT_WLAN_EVT_READY].handler;

user_parameter = event_tab[RT_WLAN_EVT_READY].parameter;

rt_hw_interrupt_enable(level);

if (handler)

handler(RT_WLAN_EVT_READY, buff, user_parameter);

return 0;

}

函数rt_wlan_prot_ready_event 内部调用的是事件RT_WLAN_EVT_READY的回调处理函数,该函数须要咱们在工程代码中自行实现并注册。当WLAN链接不稳定或发生故障断开链接时,也应能及时给出反馈信息提醒咱们WIFI 已断开,所以也为事件RT_WLAN_EVT_STA_DISCONNECTED实现并注册一个回调函数。WLAN管理层支持的事件种类以下:

// rt-thread-4.0.1\components\drivers\wlan\wlan_mgnt.h

typedef enum

{

RT_WLAN_EVT_READY = 0, /* connect and prot is ok, You can send data*/

RT_WLAN_EVT_SCAN_DONE, /* Scan a info */

RT_WLAN_EVT_SCAN_REPORT, /* Scan end */

RT_WLAN_EVT_STA_CONNECTED, /* connect success */

RT_WLAN_EVT_STA_CONNECTED_FAIL, /* connection failed */

RT_WLAN_EVT_STA_DISCONNECTED, /* disconnect */

RT_WLAN_EVT_AP_START, /* AP start */

RT_WLAN_EVT_AP_STOP, /* AP stop */

RT_WLAN_EVT_AP_ASSOCIATED, /* sta associated */

RT_WLAN_EVT_AP_DISASSOCIATED, /* sta disassociated */

RT_WLAN_EVT_MAX

} rt_wlan_event_t;

咱们在下面的示例工程代码中实现其中的两个事件RT_WLAN_EVT_READY和RT_WLAN_EVT_STA_DISCONNECTED的回调函数,其中事件RT_WLAN_EVT_READY回调函数涉及到LwIP 网络接口初始化就绪通知,咱们使用信号量来实现LwIP初始化与网络访问间的同步顺序。当LwIP网络接口完成初始化调用执行RT_WLAN_EVT_READY事件回调函数,则释放信号量,主程序中等待LwIP网络就绪节点处得到该信号量,就能够继续执行,使用LwIP 协议栈提供的网络服务了。

WLAN框架应用示例

下面的示例工程想使用WLAN框架管理层、协议层、参数配置层这三部分提供的功能:首先是WLAN管理层提供的WIFI 扫描链接周围热点的功能;而后是WLAN协议层提供的网络访问功能,这里执行ifconfig命令和ping命令;最后是WLAN参数配置层提供的自动重连服务。按照这个业务逻辑,编写示例工程代码以下:

// projects\stm32l475_wifi_sample\applications\main.c

#include

#include

#include

#include

#include "drv_wlan.h"

#include "wifi_config.h"

#include

#include

#define DBG_TAG "main"

#define DBG_LVL DBG_LOG

#include

#define WLAN_SSID "_360WiFi_"

#define WLAN_PASSWORD "********"

#define NET_READY_TIME_OUT (rt_tick_from_millisecond(15 * 1000))

static void print_scan_result(struct rt_wlan_scan_result *scan_result);

static void print_wlan_information(struct rt_wlan_info *info);

static struct rt_semaphore net_ready;

/* WLAN网卡就绪回调函数 */

void wlan_ready_handler(int event, struct rt_wlan_buff *buff, void *parameter)

{

rt_sem_release(&net_ready);

}

/* 断开链接回调函数 */

void wlan_station_disconnect_handler(int event, struct rt_wlan_buff *buff, void *parameter)

{

LOG_I("disconnect from the network!");

}

int main(void)

{

int result = RT_EOK;

struct rt_wlan_info info;

struct rt_wlan_scan_result *scan_result;

/* 等待 1000 ms 以便 wifi 完成初始化 */

rt_hw_wlan_wait_init_done(1000);

/* 扫描热点 */

LOG_D("start to scan ap ...");

/* 执行同步扫描 */

scan_result = rt_wlan_scan_sync();

if (scan_result)

{

LOG_D("the scan is complete, results is as follows: ");

/* 打印扫描结果 */

print_scan_result(scan_result);

/* 清除扫描结果 */

rt_wlan_scan_result_clean();

}

else

{

LOG_E("not found ap information ");

return -1;

}

/* 热点链接 */

LOG_D("start to connect ap ...");

rt_sem_init(&net_ready, "net_ready", 0, RT_IPC_FLAG_FIFO);

/* 注册 wlan ready 回调函数 */

rt_wlan_register_event_handler(RT_WLAN_EVT_READY, wlan_ready_handler, RT_NULL);

/* 注册 wlan 断开回调函数 */

rt_wlan_register_event_handler(RT_WLAN_EVT_STA_DISCONNECTED, wlan_station_disconnect_handler, RT_NULL);

/* 阻塞式链接指定热点 */

result = rt_wlan_connect(WLAN_SSID, WLAN_PASSWORD);

if (result == RT_EOK)

{

rt_memset(&info, 0, sizeof(struct rt_wlan_info));

/* 获取当前链接热点信息 */

rt_wlan_get_info(&info);

LOG_D("station information:");

print_wlan_information(&info);

/* 等待成功获取 IP */

result = rt_sem_take(&net_ready, NET_READY_TIME_OUT);

if (result == RT_EOK)

{

LOG_D("networking ready!");

msh_exec("ifconfig", rt_strlen("ifconfig"));

rt_thread_mdelay(2000);

msh_exec("ping www.baidu.com", rt_strlen("ping www.baidu.com"));

}

else

{

LOG_D("wait ip got timeout!");

}

/* 回收资源 */

rt_wlan_unregister_event_handler(RT_WLAN_EVT_READY);

rt_sem_detach(&net_ready);

}

else

{

LOG_E("The AP(%s) is connect failed!", WLAN_SSID);

}

rt_thread_mdelay(5000);

LOG_D("ready to disconect from ap ...");

rt_wlan_disconnect();

/* 自动链接 */

LOG_D("start to autoconnect ...");

/* 初始化自动链接配置 */

wlan_autoconnect_init();

/* 使能 wlan 自动链接 */

rt_wlan_config_autoreconnect(RT_TRUE);

return 0;

}

为了方便在串口控制台交互WIFI 热点信息,这里实现了两个打印WIFI 热点信息的函数:print_scan_result 能够打印扫描到的全部WIFI 热点信息(能够参考函数rt_wlan_cfg_dump 的实现代码);print_wlan_information能够打印指定的 WIFI 热点信息。这两个WIFI 热点信息打印函数的实现代码以下:

static void print_scan_result(struct rt_wlan_scan_result *scan_result)

{

char *security;

int index, num;

num = scan_result->num;

/* 有规则的排列扫描到的热点 */

rt_kprintf(" SSID MAC security rssi chn Mbps\n");

rt_kprintf("------------------------------- ----------------- -------------- ---- --- ----\n");

for (index = 0; index < num; index++)

{

rt_kprintf("%-32.32s", &scan_result->info[index].ssid.val[0]);

rt_kprintf("%02x:%02x:%02x:%02x:%02x:%02x ",

scan_result->info[index].bssid[0],

scan_result->info[index].bssid[1],

scan_result->info[index].bssid[2],

scan_result->info[index].bssid[3],

scan_result->info[index].bssid[4],

scan_result->info[index].bssid[5]);

switch (scan_result->info[index].security)

{

case SECURITY_OPEN:

security = "OPEN";

break;

case SECURITY_WEP_PSK:

security = "WEP_PSK";

break;

case SECURITY_WEP_SHARED:

security = "WEP_SHARED";

break;

case SECURITY_WPA_TKIP_PSK:

security = "WPA_TKIP_PSK";

break;

case SECURITY_WPA_AES_PSK:

security = "WPA_AES_PSK";

break;

case SECURITY_WPA2_AES_PSK:

security = "WPA2_AES_PSK";

break;

case SECURITY_WPA2_TKIP_PSK:

security = "WPA2_TKIP_PSK";

break;

case SECURITY_WPA2_MIXED_PSK:

security = "WPA2_MIXED_PSK";

break;

case SECURITY_WPS_OPEN:

security = "WPS_OPEN";

break;

case SECURITY_WPS_SECURE:

security = "WPS_SECURE";

break;

default:

security = "UNKNOWN";

break;

}

rt_kprintf("%-14.14s ", security);

rt_kprintf("%-4d ", scan_result->info[index].rssi);

rt_kprintf("%3d ", scan_result->info[index].channel);

rt_kprintf("%4d\n", scan_result->info[index].datarate / 1000000);

}

rt_kprintf("\n");

}

static void print_wlan_information(struct rt_wlan_info *info)

{

LOG_D("SSID : %-.32s", &info->ssid.val[0]);

LOG_D("MAC Addr: %02x:%02x:%02x:%02x:%02x:%02x", info->bssid[0],

info->bssid[1],

info->bssid[2],

info->bssid[3],

info->bssid[4],

info->bssid[5]);

LOG_D("Channel: %d", info->channel);

LOG_D("DataRate: %dMbps", info->datarate / 1000000);

LOG_D("RSSI: %d", info->rssi);

}

到这里咱们想要实现的示例工程代码就编写完了,在生成工程以前,先经过menuconfig命令启用AP6181 WIFI 设备,配置界面以下:

启用了AP6181 WIFI 设备,因为依赖项RT_USING_LWIP,也同时启用了LwIP协议栈。咱们在示例工程中使用了两个命令ifconfig 和 ping 是由netdev组件提供的,也须要启用netdev组件;为方便后面使用BSD Socket 进行网络编程,还须要启用SAL组件,启用netdev组件与SAL组件的配置界面以下:

在env环境中执行scons --target=mdk5 命令,生成MDK5工程文件,打开project.uvprojx,编译无报错,将程序烧录到Pandora开发板中,输出信息以下:

从上面程序执行的输出结果能够看出,AP6181 WIFI 模块工做正常,咱们对WLAN管理框架各部分的移植适配也没什么问题。

WLAN框架提供的msh命令使用示例

WLAN管理框架也为咱们提供了msh命令,方便咱们进行调试,msh命令使用示例以下:

6.2 BSD Socket 网络编程示例

咱们使用博客:网络分层结构 + netdev/SAL原理中的HTTP服务应用示例程序代码,将从Pandora开发板 AHT10 Sensor读取的温湿度数据经过HTTP服务返回给远程访问的Web客户端,再次贴出HTTP服务示例代码以下:

// projects\stm32l475_wifi_sample\applications\sockapi_http_demo.c

#include

#include /* 使用BSD socket,须要包含socket.h头文件 */

#include

#include

#define DBG_TAG "Socket"

#define DBG_LVL DBG_INFO

#include

/* defined received buffer size */

#define BUFSZ 512

/* defined the number of times aht10 sensor data is sent */

#define SENDCNT 10

/* defined aht10 sensor name */

#define SENSOR_TEMP_NAME "temp_aht10"

#define SENSOR_HUMI_NAME "humi_aht10"

static rt_thread_t tid = RT_NULL;

const static char http_html_hdr[] = "HTTP/1.1 200 OK\r\nContent-type: text/html\r\n\r\n";

const static char http_index_html[] = "

Congrats!\

Welcome to LwIP 2.1.0 HTTP server!

";

static char Sensor_Data[] ="

Congrats!\

The current temperature is: %3d.%d C, humidity is: %3d.%d %.\

sockert组成部分_IOT-OS之RT-Thread(十六)--- WLAN管理框架 + AP6181(BCM43362) WiFi模块相关推荐

  1. IOT-OS之RT-Thread(十五)--- SDIO设备对象管理 + AP6181(BCM43362) WiFi模块

    文章目录 一.AP6181 Wi-Fi模块简介 1.1 AP6181 硬件接口 1.2 AP6181 驱动层级 二 SDIO设备对象管理 2.1 SDIO Bus Driver 2.1.1 Host ...

  2. 【OS学习笔记】十六 保护模式四:进入保护模式与在保护模式下访问内存的汇编代码

    本文记录的是之前四篇文章所对应的汇编代码.四篇文章分别是: [OS学习笔记]十二 现代处理器的结构和特点 [OS学习笔记]十三 保护模式一:全局描述符表(GDT) [OS学习笔记]十四 保护模式二:段 ...

  3. Mac OS使用技巧之十六:系统失去响应怎么办?

    再好的系统,再快的本本,也会在运行时因为种种原因出现卡顿或者死机等失去响应的情况.Mac用户也会时不时碰到这种情况,最常见的表现为鼠标变为七彩圆圈,通常等上一会儿系统会自己恢复.如果迟迟没有响应的话, ...

  4. python基础学习(十六)——超详细!pickle模块的使用(pickle.dump()和pickle.load())

    python的pickle模块提供了一个简答的持久化功能,可以将对象以文件的形式存放在磁盘上. pickle模块实现了基本的数据序列化和反序列化 通过pickle模块的序列化操作pickle.dump ...

  5. (十六)PDF文档处理模块(PDF格式转换+iTextSharp组件)

    首先在页面加一个textbox控件,一个button控件. 引入 using System.IO; using System.Text; using System.Collections; 之后是在后 ...

  6. 关于RT thread系统节拍时钟的配置

    关于RT thread系统节拍时钟的配置                  -----本文基于rt-thread-3.1.3版本编写 首先,使用RTthread OS时,要配置(或者明白)它的系统节拍 ...

  7. Linux运维 第二阶段(十六)OS优化(1)

    Linux运维 第二阶段(十六)OS优化(1) 一.相关概念: OS optimization 1.understanding the linux operating system: CPU(cent ...

  8. 正点原子delay函数移植到rt thread操作系统(HAL库)

    正点原子教程中涉及到的操作系统只涉及了UCOS的教程,其中例程的system文件夹中的delay.c函数只是适配了UCOS. 下面将delay.c函数移植到rt thread中,使用的bsp是rt t ...

  9. rt thread studio使用QBOOT和片外flash实现OTA升级

    我们这里要使用单片机外部flash作为OTA的下载分区,外部flash硬件连接关系 PB3-->SPI3_CLK PB4-->SPI3_MISO PB5-->SPI3_MOSI PE ...

最新文章

  1. Ubuntu环境下使用gnuplot由数据表绘制曲线图
  2. C#实现路由器断开连接,更改公网ip
  3. 简单例子解释invalidate(), requestLayout() (常用还是需要知道的)
  4. Ubuntu里的vi编辑器不好用的解决办法
  5. 计算机无法查找新硬件,电脑弹出新硬件向导怎么办_win7开机显示找到新的硬件向导的解决方法...
  6. Java并发编程之ConcurrentHashMap
  7. android json传输数据到服务器,Android中post请求传递json数据给服务端的实例
  8. Java转型(向上转型和向下转型)
  9. JMH性能测试,试试你代码的性能如何
  10. python django mysql_Python之模块、函数和缩进
  11. hdu1133-----递推+大数
  12. oracle 字段带数字,Oracle字符串中包含数字、特殊符号的排序
  13. 访问服务器显示我被拒绝,连接到服务器localhost:8080被拒绝(The connection to the server localhost:8080 was refused)...
  14. 这8大技术趋势,将塑造未来科技行业格局
  15. Ableton Live 10 Suite v10.1.42 WiN-MAC 音乐制作宿主软件
  16. JVM分化回收机制(年轻代、年老代、永久代)
  17. 马云的经典语录(转)
  18. GetElementById
  19. springboot毕设项目电影影评管理系统wej6a(java+VUE+Mybatis+Maven+Mysql)
  20. mysql事务转账_模拟数据库事务实现转账

热门文章

  1. linux开放端口权限
  2. Navicat生成数据库的模型并展示属性的中文注释
  3. Windows环境中运行.dll文件
  4. java区块链_JAVA区块链项目实战
  5. 如何做好现场英语翻译
  6. css3 flex 省略号,flex 布局下 CSS 文本超出单行宽度后显示省略号
  7. Spring之控制反转
  8. 如何爬取微博全部粉丝python_让你了解python的强大!【 登录新浪微博爬取粉丝信息】...
  9. CIL之——ilasm.exe和ildasm.exe
  10. Live555 直播性能优化