文章目录
一、WLAN管理框架简介
随着物联网快速发展,越来越多的嵌入式设备上搭载了 WIFI 无线网络设备,为了能够管理 WIFI 网络设备,RT-Thread 引入了 WLAN 设备管理框架。这套框架是 RT-Thread 开发的一套用于管理 WIFI 的中间件:对下连接具体的 WIFI 驱动,控制 WIFI 的连接、断开、扫描等操作;对上承载不同的应用,为应用提供 WIFI 控制、事件、数据导流等操作,为上层应用提供统一的 WIFI 控制接口。
WLAN 框架主要由四个部分组成:Device 驱动接口层,为 WLAN 框架提供统一的调用接口;Manage 管理层为用户提供 WIFI 扫描、连接、断线重连等具体功能;Protocol 协议负责处理 WIFI 上产生的网络数据流,可根据不同的使用场景挂载不同网络协议栈(比如 LWIP );Config配置层可以保存 WIFI 配置参数,为用户提供自动连接服务(可从Flash读取曾经连接过的热点配置信息)。WIFI 框架层次图示如下:
WLAN管理框架各层功能简介如下:
- 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协议栈移植时再详说。
二、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设备使用的网络协议栈信息、私有数据等。
- 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 的实现代码:
// 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驱动移植和调试带来了很大的不便。
- 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 =