随着物联网快速发展,越来越多的嵌入式设备上搭载了 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管理框架各层功能简介如下:
在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 = 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))
return -RT_ETIMEOUT;
return RT_EOK;
}
int rt_hw_wlan_get_initialize_status(void)
{
return init_flag; // 1 initialize done;0 not initialize
}
三、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 <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 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 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
......
四、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
五、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 在连接前扫描周围的热点信息也是