欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

【程序】Marvell 88W8686 WiFi模块(WM-G-MR-09)创建或连接热点,并使用lwip2.0.2建立http服务器(20171030版)

程序员文章站 2022-03-02 14:52:43
本程序所用的单片机型号为:STM32F103REPB12端口为外接的WiFi模块电源开关,当PB12输出低电平时接通电源。WiFi模块的电源引脚VCC不可直接连接到电源上,必须要串联一组PNP三极管(或场效应管),并把基极接到PB12端口上,基极要接限流电阻。注意:WM-G-MR-09模块的芯片组(Chip Set)就是Marvell 88W8686。该程序目前暂不支持WPA和WPA2加密方式!8......

该程序是旧版本!最新版本为20180706版:

https://blog.csdn.net/ZLK1214/article/details/80941657

本程序所用的单片机型号为:STM32F103RE

PB12端口为外接的WiFi模块电源开关,当PB12输出低电平时接通电源。WiFi模块的电源引脚VCC不可直接连接到电源上,必须要串联一组PNP三极管(或场效应管),并把基极接到PB12端口上,基极要接限流电阻。


【程序】Marvell 88W8686 WiFi模块(WM-G-MR-09)创建或连接热点,并使用lwip2.0.2建立http服务器(20171030版)

注意:WM-G-MR-09模块的芯片组(Chip Set)就是Marvell 88W8686。

该程序目前暂不支持WPA和WPA2加密方式!

88W8686 WiFi模块的固件(Firmware)内容:http://blog.csdn.net/zlk1214/article/details/76166140


【勘误】

2018年2月3日:while (WiFi_GetPacketLength() == 0xfedc);中的==应该改为!=


【程序】Marvell 88W8686 WiFi模块(WM-G-MR-09)创建或连接热点,并使用lwip2.0.2建立http服务器(20171030版)

注意必须把STM32启动文件*.s中的函数堆栈大小Stack_Size改大,否则函数中无法创建大数组!

Stack_Size      EQU     0x00004000


连上(或创建)热点后请最好不要再调用WiFi_SendCommand和WiFi_ReceiveResponse函数,因为收到的回应既可能是数据包也可能是命令回应。

另外,虽然SDIO标准规定可以总线上可以接多张SD卡,但STM32单片机的SDIO接口只支持接一张卡,STM32F103芯片手册Datasheet(不是参考手册)中有声明:

The current version supports only one SD/SDIO/MMC4.2 card at any one time and a stack of MMC4.1 or previous.

如果想要同时使用WiFi模块和SD内存卡,建议SD内存卡采用SPI总线通信。

【main.c(寄存器版)】

#include <stdio.h>
#include <stm32f10x.h>
#include "lwip/init.h" // lwip_init函数所在的头文件
#include "lwip/timeouts.h" // sys_check_timeouts函数所在的头文件
#include "netif/ethernet.h" // ethernet_input函数所在头文件
#include "WiFi.h"

// 这两个函数位于ethernetif.c中, 但没有头文件声明
err_t ethernetif_init(struct netif *netif);
void ethernetif_input(struct netif *netif);

// 一个很简单的http服务器
void init_http(void);

// printf函数的实现, 必须在项目属性中勾选Use MicroLIB后才能用
int fputc(int ch, FILE *fp)
{
  if (fp == stdout)
  {
    if (ch == '\n')
    {
      while ((USART1->SR & USART_SR_TXE) == 0);
      USART1->DR = '\r';
    }
    while ((USART1->SR & USART_SR_TXE) == 0); // 等待发送寄存器变为空
    USART1->DR = ch;
  }
  return ch;
}

// RTC时间转化为毫秒数
uint32_t sys_now(void)
{
  uint32_t sec = (RTC->CNTH << 16) | RTC->CNTL; // 秒
  uint32_t milli = (39999 - RTC->DIVL) * 1000 / 40000; // 毫秒 (DIV寄存器的范围是0~PRL-1)
  return sec * 1000 + milli;
}

// WiFi事件回调函数
void WiFi_EventHandler(const WiFi_Event *event)
{
  printf("[Event %d] size=%d", event->event_id, event->header.length);
  if (event->header.length >= sizeof(WiFi_Event) - sizeof(event->mac_addr))
    printf(", reason=%d", event->reason_code);
  if (event->header.length >= sizeof(WiFi_Event))
    printf(", MAC: %02X:%02X:%02X:%02X:%02X:%02X", event->mac_addr[0], event->mac_addr[1], event->mac_addr[2], event->mac_addr[3], event->mac_addr[4], event->mac_addr[5]);
  printf("\n");
  
  switch (event->event_id)
  {
    case 3:
      // 收不到信号 (例如和手机热点建立连接后, 把手机拿走), WiFi模块不会自动重连
      printf("Beacon Loss/Link Loss\n");
      break;
    case 4:
      // Ad-Hoc网络中不止1个结点, 且连接数发生了变化
      printf("The number of stations in this ad hoc newtork has changed!\n");
      break;
    case 8:
      // 认证已解除 (例如手机关闭了热点, 或者是连接路由器后一直没有输入密码而自动断开连接)
      printf("Deauthenticated!\n");
      break;
    case 9:
      printf("Disassociated!\n");
      break;
    case 17:
      // Ad-Hoc网络中只剩本结点
      printf("All other stations have been away from this ad hoc network!\n");
      break;
  }
  
  if (event->header.length > sizeof(WiFi_Event))
    dump_data(event + 1, event->header.length - sizeof(WiFi_Event));
}

int main(void)
{
  struct ip4_addr ipaddr, netmask, gw;
  struct netif wifi_88w8686;
  uint32_t last_check = 0;
  
  // HCLK=PCLK2=72MHz, PCLK1=36MHz
  RCC->AHBENR |= RCC_AHBENR_SDIOEN; // 打开SDIO外设的时钟, 注意AHBENR寄存器有初值
#ifdef WIFI_USEDMA
  RCC->AHBENR |= RCC_AHBENR_DMA2EN;
#endif
  RCC->APB1ENR = RCC_APB1ENR_PWREN | RCC_APB1ENR_TIM6EN; // TIM6为延时用的定时器
  RCC->APB2ENR = RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN | RCC_APB2ENR_IOPCEN | RCC_APB2ENR_IOPDEN | RCC_APB2ENR_USART1EN;
  
  // GPIO寄存器的初值: GPIOA->CRH=0x88844444, GPIOA->ODR=0xa000, GPIOB->CRL=0x44484444, GPIOB->ODR=0x0010
  GPIOA->CRH = 0x888444b4; // 串口1发送引脚PA9设为复用推挽50MHz输出(b), 接收引脚PA10设为浮空输入(4), 调试用引脚PA13~15为默认的带上/下拉电阻输入
  // WiFi模块的VCC引脚不可直接连接电源, 必须要串联一个PNP型三极管, 并把基极接到PB12上, 否则STM32复位时WiFi模块无法自动复位
  // 复位时PB12输出高阻态, 三极管截止, WiFi模块断电, 配置完CRH寄存器后立即输出低电平, WiFi模块通电
  GPIOB->CRH = 0x44434444; // PB12设为推挽50MHz输出(3)
  GPIOC->CRH = 0x444bbbbb; // PC8~11为SDIO的4位数据引脚, PC12为SDIO时钟引脚, 设为复用推挽50MHz输出(b)
  GPIOD->CRL = 0x44444b44; // PD2为SDIO命令引脚, 设为复用推挽50MHz输出(b)
  
  USART1->BRR = 625; // 72000000/115200=625, 波特率设为115200
  USART1->CR1 = USART_CR1_UE | USART_CR1_TE | USART_CR1_RE; // 打开串口1, 允许发送和接收
  
  PWR->CR = PWR_CR_DBP; // 允许写后备寄存器(如RCC->BDCR)
  RCC->CSR |= RCC_CSR_LSION; // 因为板上没有LSE晶振, 所以RTC时钟选LSI
  while ((RCC->CSR & RCC_CSR_LSIRDY) == 0); // 等待LSI启动
  
  // 若RTC未打开, 则初始化RTC
  if ((RCC->BDCR & RCC_BDCR_RTCEN) == 0)
  {
    // 必须要先选择时钟, 然后再开启RTC时钟
    RCC->BDCR |= RCC_BDCR_RTCSEL_1 | RCC_BDCR_RTCEN; // 选LSI作为RTC时钟, 并开启RTC时钟, RTC开始走时
    
    RTC->CRL |= RTC_CRL_CNF; // 进入RTC配置模式
    RTC->PRLH = 0; // 设置分频系数
    RTC->PRLL = 39999; // LSI晶振的频率是40kHz, 定时1s, 注意PRLH/L寄存器只能写不能读
    //RTC->CNTH = 0; // 设置初始时间
    //RTC->CNTL = 50; // STM32F1系列的RTC没有年月日、小时等寄存器, 只有一个32位的计数器, 要想实现日期和时间的功能必须调用C库<time.h>中的mktime函数, 用软件来实现这些功能
    RTC->CRL &= ~RTC_CRL_CNF; // 保存设置
    while ((RTC->CRL & RTC_CRL_RTOFF) == 0); // 等待设置生效
  }
  else
  {
    // 等待RTC与APB1时钟同步
    RTC->CRL &= ~RTC_CRL_RSF;
    while ((RTC->CRL & RTC_CRL_RSF) == 0);
  }
  
  WiFi_Init();
  WiFi_Scan(); // 显示附近的热点信息 (可以注释掉)
  
  WiFi_SetWEP(WIFI_ACT_ADD, "Hello, World!"); // 设置热点WEP密码 (若不想设置密码请删除掉这句话)
  WiFi_StartADHOC("Marvell 88W8686"); // 创建Ad-Hoc热点
  
  //WiFi_SetWEP(WIFI_ACT_ADD, "48656c6c6f2c20576f726c6421"); // 设置热点WEP密码(第二种方式): Hello, World!
  //WiFi_Connect("Octopus-WEP", BSS_INDEPENDENT); // 连接Ad-Hoc热点 (Win7电脑可以创建WEP加密的Ad-Hoc型热点)
  
  //WiFi_Connect("10507", BSS_INFRASTRUCTURE); // 连接路由器建立的AP热点
  
  //WiFi_Connect("vivo Y29L", BSS_INFRASTRUCTURE); // 连接手机开的没有密码的AP热点
  
  lwip_init();
  IP4_ADDR(&ipaddr, 192, 168, 43, 10); // IP地址
  IP4_ADDR(&netmask, 255, 255, 255, 0); // 子网掩码
  IP4_ADDR(&gw, 192, 168, 43, 1); // 网关
  
  netif_add(&wifi_88w8686, &ipaddr, &netmask, &gw, NULL, ethernetif_init, ethernet_input);
  netif_set_default(&wifi_88w8686); // 设为默认网卡
  netif_set_up(&wifi_88w8686);
  
  init_http();
  while (1)
  {
    if (WiFi_PacketArrived())
      ethernetif_input(&wifi_88w8686);
    
    // sys_check_timeouts函数千万不能调用的太频繁, 否则会出错!(例如开机后要等1~2分钟DHCP才能分配到IP地址)
    if (sys_now() - last_check > 200)
    {
      last_check = sys_now();
      sys_check_timeouts();
    }
  }
}
【WiFi.h(寄存器版)】

#ifndef _BV
#define _BV(n) (1u << (n))
#endif

#define CMD52_WRITE _BV(31)
#define CMD52_READAFTERWRITE _BV(27)

#define CMD53_WRITE _BV(31)
#define CMD53_BLOCKMODE _BV(27)
#define CMD53_INCREMENTING _BV(26)

/* 6.9 Card Common Control Registers (CCCR) */
#define SDIO_CCCR_IOEN 0x02
#define SDIO_CCCR_IOEN_IOE1 _BV(1)

#define SDIO_CCCR_IORDY 0x03
#define SDIO_CCCR_IORDY_IOR1 _BV(1)

#define SDIO_CCCR_INTEN 0x04
#define SDIO_CCCR_INTEN_IENM _BV(0)
#define SDIO_CCCR_INTEN_IEN1 _BV(1)

#define SDIO_CCCR_BUSIFCTRL 0x07 // Bus Interface Control
#define SDIO_CCCR_BUSIFCTRL_BUSWID_1Bit 0
#define SDIO_CCCR_BUSIFCTRL_BUSWID_4Bit 0x02
#define SDIO_CCCR_BUSIFCTRL_BUSWID_8Bit 0x03

#define SDIO_SUCCEEDED() ((SDIO->STA & ~SDIO_STA_SDIOIT) == 0)

// 16.5 SDIO Card Metaformat
#define CISTPL_NULL 0x00 // Null tuple
#define CISTPL_VERS_1 0x15 // Level 1 version/product-information
#define CISTPL_MANFID 0x20 // Manufacturer Identification String Tuple
#define CISTPL_FUNCID 0x21 // Function Identification Tuple
#define CISTPL_FUNCE 0x22 // Function Extensions
#define CISTPL_END 0xff // The End-of-chain Tuple

/* WiFi配置 */
#define WIFI_DEFBUFSIZE 256 // WiFi命令默认的缓冲区大小(兼容不支持字节流传输的SDIO高速模式)
#define WIFI_DEFTIMEOUT 1500 // WiFi命令回应的超时时间(ms)
#define WIFI_HIGHSPEED // 采用SDIO高速模式
#define WIFI_USEDMA // SDIO采用DMA方式收发数据(高速模式必须采用DMA)

/* WiFi寄存器 */
#define WIFI_IOPORT0 0x00
#define WIFI_IOPORT1 0x01
#define WIFI_IOPORT2 0x02

#define WIFI_INTMASK 0x04 // Host Interrupt Mask
#define WIFI_INTMASK_HOSTINTMASK 0x0f // enable/disable SDU to SD host interrupt

#define WIFI_INTSTATUS 0x05 // Host Interrupt Status
#define WIFI_INTSTATUS_UPLD _BV(0) // Upload Host Interrupt Status (可随时手动清除, 无论UPLDCARDRDY是否为1)

#define WIFI_SQREADBASEADDR0 0x10
#define WIFI_SQREADBASEADDR1 0x11
#define WIFI_SQREADBASEADDR2 0x12
#define WIFI_SQREADBASEADDR3 0x13

#define WIFI_CARDSTATUS 0x20 // Card Status
#define WIFI_CARDSTATUS_IOREADY _BV(3) // I/O Ready Indicator
#define WIFI_CARDSTATUS_CISCARDRDY _BV(2) // Card Information Structure Card Ready
#define WIFI_CARDSTATUS_UPLDCARDRDY _BV(1) // Upload Card Ready (CMD53读写命令均会清除该位)
#define WIFI_CARDSTATUS_DNLDCARDRDY _BV(0) // Download Card Ready

#define WIFI_SCRATCHPAD4_0 0x34
#define WIFI_SCRATCHPAD4_1 0x35

/* Capability information */
#define WIFI_CAPABILITY_BSS _BV(0)
#define WIFI_CAPABILITY_IBSS _BV(1)
#define WIFI_CAPABILITY_CF_POLLABLE _BV(2)
#define WIFI_CAPABILITY_CF_POLL_REQUEST _BV(3)
#define WIFI_CAPABILITY_PRIVACY _BV(4)
#define WIFI_CAPABILITY_SHORT_PREAMBLE _BV(5)
#define WIFI_CAPABILITY_PBCC _BV(6)
#define WIFI_CAPABILITY_CHANNEL_AGILITY _BV(7)
#define WIFI_CAPABILITY_SPECTRUM_MGMT _BV(8)
#define WIFI_CAPABILITY_QOS _BV(9)
#define WIFI_CAPABILITY_SHORT_SLOT _BV(10)
#define WIFI_CAPABILITY_DSSS_OFDM _BV(13)

#define WIFI_SDIOFRAME_DATA 0x00
#define WIFI_SDIOFRAME_COMMAND 0x01
#define WIFI_SDIOFRAME_EVENT 0x03

/* Command List */
#define CMD_802_11_SCAN 0x0006 // Starts the scan process
#define CMD_802_11_ASSOCIATE 0x0012 // Initiate an association with the AP
#define CMD_802_11_SET_WEP 0x0013 // Configures the WEP keys
#define CMD_MAC_CONTROL 0x0028 // Controls hardware MAC
#define CMD_802_11_AD_HOC_START 0x002b // Starts an Ad-Hoc network
#define CMD_802_11_AD_HOC_JOIN 0x002c // Join an Ad-Hoc network
#define CMD_802_11_MAC_ADDR 0x004d // WLAN MAC address
#define CMD_802_11_KEY_MATERIAL 0x005e // Gets/sets key material used to do Tx encryption or Rx decryption
#define CMD_802_11_BG_SCAN_CONFIG 0x006b // Gets/sets background scan configuration
#define CMD_802_11_BG_SCAN_QUERY 0x006c // Gets background scan results
#define CMD_802_11_SUBSCRIBE_EVENT 0x0075 // Subscribe to events and set thresholds

/* Command Result Codes */
#define CMD_STATUS_SUCCESS 0x0000 // No error
#define CMD_STATUS_ERROR 0x0001 // Command failed
#define CMD_STATUS_UNSUPPORTED 0x0002 // Command is not supported

#define WIFI_ACT_GET 0
#define WIFI_ACT_SET 1
#define WIFI_ACT_ADD 2
#define WIFI_ACT_BITWISE_SET 2
#define WIFI_ACT_BITWISE_CLR 3
#define WIFI_ACT_REMOVE 4

/* Authentication Type to be used to authenticate with AP */
#define AUTH_MODE_OPEN 0x00
#define AUTH_MODE_SHARED 0x01
#define AUTH_MODE_NETWORK_EAP 0x80

/* WiFi_Associate return value */
#define WIFI_ASSOCIATION_NOTFOUND 0xfffe
#define WIFI_ASSOCIATION_ERROR 0xffff
#define WIFI_ASSOCIATION_SUCCESS 0x0000 // 连接成功
#define WIFI_ASSOCIATION_INTERNALERROR 0x0101
#define WIFI_ASSOCIATION_AUTHUNHANDLED(ret) (((ret) & 0xff00) == 0x200) // 未处理的认证帧
#define WIFI_ASSOCIATION_UNSUPPORTEDAUTHALG 0x0213
#define WIFI_ASSOCIATION_INVALIDSEQUENCENUMBER 0x0214
#define WIFI_ASSOCIATION_AUTHREFUSED(ret) (((ret) & 0xff00) == 0x300) // 认证失败
#define WIFI_ASSOCIATION_TIMEOUT(ret) (((ret) & 0xff00) == 0x400) // 超时
#define WIFI_ASSOCIATION_ASSOCTIMEOUT 0x0401 // 连接超时
#define WIFI_ASSOCIATION_AUTHTIMEOUT 0x402 // 认证超时
#define WIFI_ASSOCIATION_NETWORKJOINTIMEOUT 0x403 // 加入网络时超时

#define WIFI_KEYTYPE_WEP 0
#define WIFI_KEYTYPE_TKIP 1
#define WIFI_KEYTYPE_AES 2

#define WIFI_KEYINFO_KEYENABLED _BV(2)
#define WIFI_KEYINFO_UNICASTKEY _BV(1)
#define WIFI_KEYINFO_MULTICASTKEY _BV(0)

#define WIFI_MACCTRL_RX _BV(0)
#define WIFI_MACCTRL_TX _BV(1) // 此位必须要置1才能发送数据!!!
#define WIFI_MACCTRL_LOOPBACK _BV(2)
#define WIFI_MACCTRL_WEP _BV(3)
#define WIFI_MACCTRL_ETHERNET2 _BV(4)
#define WIFI_MACCTRL_PROMISCUOUS _BV(7)
#define WIFI_MACCTRL_ALLMULTICAST _BV(8)
#define WIFI_MACCTRL_ENFORCEPROTECTION _BV(10) // strict protection
#define WIFI_MACCTRL_ADHOCGPROTECTIONMODE _BV(13) // 802.11g protection mode

#define WIFI_WEPKEYTYPE_40BIT 1
#define WIFI_WEPKEYTYPE_104BIT 2

/* BSS type */
#define BSS_INFRASTRUCTURE 0x01
#define BSS_INDEPENDENT 0x02
#define BSS_ANY 0x03

/* Table 45: IEEE 802.11 Standard IE Translated to Marvell IE */
/* PDF中的表45有一些拼写错误, MRVIIE应该改为MRVLIE */
#define MRVLIETYPES_SSIDPARAMSET 0x0000
#define MRVLIETYPES_RATESPARAMSET 0x0001
#define MRVLIETYPES_DSPARAMSET 0x0003
#define MRVLIETYPES_CFPARAMSET 0x0004
#define MRVLIETYPES_IBSSPARAMSET 0x0006
#define MRVLIETYPES_RSNPARAMSET 0x0030
#define MRVLIETYPES_VENDORPARAMSET 0x00dd

#define MRVLIETYPES_KEYPARAMSET 0x0100
#define MRVLIETYPES_CHANLISTPARAMSET 0x0101
#define MRVLIETYPES_TSFTIMESTAMP 0x0113
#define MRVLIETYPES_AUTHTYPE 0x011f

/* 已知结构体大小sizeof(tlv), 求数据域的大小, 一般用于给header.length赋值 */
// 例如定义一个MrvlIETypes_CfParamSet_t param变量, 赋值param.header.length=TLV_PAYLOADLEN(param)
#define TLV_PAYLOADLEN(tlv) (sizeof(tlv) - sizeof((tlv).header))

/* 已知数据域大小, 求整个结构体的大小 */
// 例如定义一个很大的buffer, 然后定义一个IEEEType *的指针p指向该buffer
// buffer接收到数据后, 要求出接收到的IEEEType数据的实际大小显然不能用sizeof(IEEEType), 因为定义IEEEType结构体时data的长度定义的是1
// 此时就应该使用TLV_STRUCTLEN(*p)
#define TLV_STRUCTLEN(tlv) (sizeof((tlv).header) + (tlv).header.length)

// 已知本TLV的地址和大小, 求下一个TLV的地址
#define TLV_NEXT(tlv) ((uint8_t *)tlv + TLV_STRUCTLEN(*tlv))

/* TLV (Tag Length Value) of IEEE IE Type Format */
typedef __packed struct
{
	uint8_t type;
	uint8_t length; // 数据域的大小
} IEEEHeader;

// information element parameter
// 所有IEEETypes_*类型的基类型
typedef __packed struct
{
	IEEEHeader header;
	uint8_t data[1];
} IEEEType;

typedef __packed struct
{
	IEEEHeader header;
	uint8_t channel;
} IEEETypes_DsParamSet_t;

typedef __packed struct
{
	IEEEHeader header;
	uint16_t atim_window;
} IEEETypes_IbssParamSet_t;

/* TLV (Tag Length Value) of MrvllEType Format */
typedef __packed struct
{
	uint16_t type;
	uint16_t length;
} MrvlIEHeader;

// 所有MrvlIETypes_*类型的基类型
typedef __packed struct
{
	MrvlIEHeader header;
	uint8_t data[1];
} MrvlIEType;

typedef __packed struct
{
	MrvlIEHeader header;
	uint16_t auth_type;
} MrvlIETypes_AuthType_t;

typedef __packed struct
{
	MrvlIEHeader header;
	uint8_t count;
	uint8_t period;
	uint16_t max_duration;
	uint16_t duration_remaining;
} MrvlIETypes_CfParamSet_t;

/*typedef __packed struct
{
	MrvlIEHeader header;
	__packed struct
	{
		uint8_t band_config_type;
		uint8_t chan_number;
	} channels[1];
} MrvlIETypes_ChanBandList_t;*/

typedef __packed struct
{
	MrvlIEHeader header;
	__packed struct
	{
		uint8_t band_config_type;
		uint8_t chan_number;
		uint8_t scan_type;
		uint16_t min_scan_time;
		uint16_t max_scan_time;
	} channels[1];
} MrvlIETypes_ChanListParamSet_t;

typedef __packed struct
{
	MrvlIEHeader header;
	uint8_t channel;
} MrvlIETypes_PhyParamDSSet_t;

typedef __packed struct
{
	MrvlIEHeader header;
	uint16_t key_type_id;
	uint16_t key_info;
	uint16_t key_len;
	uint8_t key[32];
} MrvlIETypes_KeyParamSet_t;

typedef __packed struct
{
	MrvlIEHeader header;
	uint8_t rates[14];
} MrvlIETypes_RatesParamSet_t;

typedef __packed struct
{
	MrvlIEHeader header;
	uint8_t rsn[64];
} MrvlIETypes_RsnParamSet_t;

typedef __packed struct
{
	MrvlIEHeader header;
	uint8_t ssid[32];
} MrvlIETypes_SSIDParamSet_t;

typedef __packed struct
{
	MrvlIEHeader header;
	uint64_t tsf_table[1];
} MrvlIETypes_TsfTimestamp_t;

// 整个结构体的最大大小为256字节
typedef __packed struct
{
	MrvlIEHeader header;
	uint8_t vendor[64]; // 通常情况下64字节已足够
} MrvlIETypes_VendorParamSet_t;

/* WiFi模块所有类型的帧的头部 */
typedef __packed struct
{
	uint16_t length; // 大小包括此成员本身
	uint16_t type;
} WiFi_SDIOFrameHeader;

/* WiFi模块命令帧的头部 */
typedef __packed struct
{
	WiFi_SDIOFrameHeader frame_header; 
	uint16_t cmd_code;
	uint16_t size;
	uint16_t seq_num;
	uint16_t result;
} WiFi_CommandHeader;

/* WiFi模块接收的数据帧 */
/* Table 2: Fields in Receive Packet Descriptor */
typedef __packed struct
{
	WiFi_SDIOFrameHeader header;
	uint16_t reserved1;
	uint8_t snr; // Signal to noise ratio for this packet (dB)
	uint8_t reserved2;
	uint16_t rx_packet_length; // Number of bytes in the payload
	uint8_t nf; // Noise floor for this packet (dBm). Noise floor is always negative. The absolute value is passed.
	uint8_t rx_rate; // Rate at which this packet is received
	uint32_t rx_packet_offset; // Offset from the start of the packet to the beginning of the payload data packet
	uint32_t reserved3;
	uint8_t priority; // Specifies the user priority of received packet
	uint8_t reserved4[3];
	uint8_t payload[1]; // 数据链路层上的帧
} WiFi_DataRx;

/* WiFi模块发送的数据帧 */
/* Table 3: Fields in Transmit Packet Descriptor */
typedef __packed struct
{
	WiFi_SDIOFrameHeader header;
	uint32_t reserved1;
	uint32_t tx_control; // See 3.2.1 Per-Packet Settings
	uint32_t tx_packet_offset; // Offset of the beginning of the payload data packet (802.3 or 802.11 frames) from the beginning of the packet (bytes)
	uint16_t tx_packet_length; // Number of bytes in the payload data frame
	uint16_t tx_dest_addr_high; // Destination MAC address bytes 4 to 5
	uint32_t tx_dest_addr_low; // Destination MAC address bytes 0 to 3
	uint8_t priority; // Specifies the user priority of transmit packet
	uint8_t flags;
	uint8_t pkt_delay_2ms; // Amount of time the packet has been queued in the driver layer for WMM implementations
	uint8_t reserved2;
	uint8_t payload[1]; // 数据链路层上的帧
} WiFi_DataTx;

/* WiFi模块事件帧 */
typedef __packed struct
{
  WiFi_SDIOFrameHeader header;
  uint32_t event_id; // Enumerated identifier for the event
  uint16_t reason_code; // IEEE Reason Code as described in the 802.11 standard
  uint8_t mac_addr[6]; // Peer STA Address
} WiFi_Event;

/* WiFi模块中的各种命令帧 */
typedef __packed struct
{
	WiFi_CommandHeader header;
	uint16_t action;
} WiFi_Cmd_KeyMaterial;

typedef __packed struct
{
	WiFi_CommandHeader header;
	uint16_t action;
	uint8_t mac_addr[6];
} WiFi_Cmd_MACAddr;

typedef __packed struct
{
	WiFi_CommandHeader header;
	uint16_t action;
	uint16_t reserved;
} WiFi_Cmd_MACCtrl;

typedef __packed struct
{
  WiFi_CommandHeader header;
  uint16_t action;
  uint16_t tx_key_index; // Key set being used for transmit (0~3)
  uint8_t wep_types[4]; // use 40 or 104 bits
  uint8_t keys[4][16];
} WiFi_Cmd_SetWEP;

typedef __packed struct
{
  WiFi_CommandHeader header;
  uint16_t action;
  uint16_t events;
} WiFi_Cmd_SubscribeEvent;

typedef __packed struct
{
	WiFi_CommandHeader header;
	uint8_t ssid[32];
	uint8_t bss_type;
	uint16_t bcn_period;
	uint8_t reserved1;
	IEEETypes_IbssParamSet_t ibss_param_set; // ATIM window length in TU
	uint32_t reserved2;
	IEEETypes_DsParamSet_t ds_param_set; // The channel for ad-hoc network
	uint16_t reserved3[3];
	uint16_t cap_info; // Capability information
	uint8_t data_rate[14];
} WiFi_CmdRequest_ADHOCStart;

typedef __packed struct
{
  WiFi_CommandHeader header;
  uint8_t bssid[6]; // MAC address
  uint8_t ssid[32];
  uint8_t bss_type;
  uint16_t bcn_period;
  uint8_t dtim_period; // Specify DTIM period (TBTTs)
  uint8_t timestamp[8];
  uint8_t start_ts[8]; // Starting timestamp
  IEEETypes_DsParamSet_t ds_param_set; // IEEE DS parameter set element
  uint32_t reserved1;
  IEEETypes_IbssParamSet_t ibss_param_set; // IEEE IBSS parameter set
  uint32_t reserved2;
  uint16_t cap_info;
  uint8_t data_rates[14];
  uint32_t reserved3;
} WiFi_CmdRequest_ADHOCJoin;

typedef __packed struct
{
	WiFi_CommandHeader header;
	uint8_t peer_sta_addr[6]; // Peer MAC address
	uint16_t cap_info; // Capability information
	uint16_t listen_interval; // Listen interval
	uint16_t bcn_period; // Beacon period
	uint8_t dtim_period; // DTIM period
} WiFi_CmdRequest_Associate;

typedef __packed struct
{
	WiFi_CommandHeader header;
	uint8_t bss_type;
	uint8_t bss_id[6];
} WiFi_CmdRequest_Scan;

typedef __packed struct
{
	WiFi_CommandHeader header;
	uint16_t capability;
	uint16_t status_code;
	uint16_t association_id;
	IEEEType ie_buffer;
} WiFi_CmdResponse_Associate;

typedef __packed struct
{
	WiFi_CommandHeader header;
	uint16_t buf_size;
	uint8_t num_of_set;
} WiFi_CmdResponse_Scan;

typedef __packed struct
{
	uint16_t ie_length; // Total information element length (不含sizeof(ie_length))
	uint8_t bssid[6]; // BSSID
	uint8_t rssi; // RSSI value as received from peer
	
	/* Probe Response/Beacon Payload */
	uint64_t pkt_time_stamp; // Timestamp
	uint16_t bcn_interval; // Beacon interval
	uint16_t cap_info; // Capabilities information
	IEEEType ie_parameters; // 存放的是一些IEEE类型的数据
} WiFi_BssDescSet;

typedef __packed struct
{
	uint8_t oui[3];
	uint8_t oui_type;
	uint8_t oui_subtype;
	uint8_t version;
} WiFi_Vendor;

/* WiFi热点信息 */
typedef __packed struct
{
	MrvlIETypes_SSIDParamSet_t ssid;
	uint8_t mac_addr[6];
	uint16_t cap_info;
	uint16_t bcn_period;
	uint8_t channel;
	MrvlIETypes_RatesParamSet_t rates;
	MrvlIETypes_RsnParamSet_t rsn;
	MrvlIETypes_VendorParamSet_t wpa;
	MrvlIETypes_VendorParamSet_t wwm;
	MrvlIETypes_VendorParamSet_t wps;
} WiFi_SSIDInfo;

void delay(uint16_t nms);
void dump_data(const void *data, uint32_t len);
void timeout(uint16_t nms);

#define WiFi_DropPacket() WiFi_ReceivePacket(0, 0)
#define WiFi_GetSDIOBlockSize() _BV((SDIO->DCTRL & SDIO_DCTRL_DBLOCKSIZE) >> 4)
#define WiFi_ResendCommand(cmd, bufsize) WiFi_SendCommand(0, (cmd), 0, bufsize)

uint16_t WiFi_Associate(const char *ssid);
uint16_t WiFi_Connect(const char *ssid, uint8_t type);
uint8_t WiFi_DownloadFirmware(void);
uint16_t WiFi_GetPacketLength(void);
void WiFi_Init(void);
uint16_t WiFi_JoinADHOC(const char *ssid);
uint16_t WiFi_KeyMaterial(MrvlIETypes_KeyParamSet_t *keys, uint16_t size, uint8_t action);
uint16_t WiFi_MACAddr(uint8_t addr[6], uint8_t action);
uint16_t WiFi_MACControl(uint16_t action);
uint8_t WiFi_PacketArrived(void);
uint8_t WiFi_Read(uint8_t func, uint32_t addr);
uint8_t WiFi_ReadData(uint8_t func, uint32_t addr, void *data, uint16_t count, uint16_t bufsize, uint32_t flags);
uint8_t WiFi_ReadPort(void *data, uint16_t size, uint16_t bufsize);
uint8_t WiFi_ReceivePacket(void *buf, uint16_t bufsize);
uint8_t WiFi_ReceiveResponse(void *buf, uint16_t bufsize);
void WiFi_Scan(void);
uint8_t WiFi_ScanSSID(const char *ssid, WiFi_SSIDInfo *info, uint8_t *buffer, uint16_t bufsize);
void WiFi_SendCMD52(uint8_t func, uint32_t addr, uint8_t data, uint32_t flags);
void WiFi_SendCMD53(uint8_t func, uint32_t addr, uint16_t count, uint32_t flags);
uint8_t WiFi_SendCommand(uint16_t com_code, void *data, uint16_t size, uint16_t bufsize);
uint8_t WiFi_SendPacket(WiFi_DataTx *packet, uint16_t packet_len, uint16_t bufsize);
void WiFi_SetBlockSize(uint8_t func);
uint8_t WiFi_SetKeyMaterial(uint16_t type, uint16_t info, const uint8_t *key, uint16_t len);
uint16_t WiFi_SetWEP(uint8_t action, const char *key);
void WiFi_ShowCIS(uint8_t func);
void WiFi_ShowKeyMaterials(void);
uint8_t WiFi_StartADHOC(const char *ssid);
uint16_t WiFi_SubscribeEvent(uint16_t action, uint16_t events);
uint8_t WiFi_TranslateTLV(MrvlIEType *mrvlie_tlv, const IEEEType *ieee_tlv, uint16_t mrvlie_payload_size);
uint8_t WiFi_Wait(uint8_t status);
uint8_t WiFi_Write(uint8_t func, uint32_t addr, uint8_t value);
uint8_t WiFi_WriteData(uint8_t func, uint32_t addr, const void *data, uint16_t count, uint32_t bufsize, uint32_t flags);
uint8_t WiFi_WritePort(const void *data, uint16_t size, uint32_t bufsize);

// 外部自定义回调函数
void WiFi_EventHandler(const WiFi_Event *event);
【WiFi.c(寄存器版)】

#include <stdio.h>
#include <stm32f10x.h>
#include <string.h>
#include "WiFi.h"

extern const unsigned char firmware_helper_sd[2516];
extern const unsigned char firmware_sd8686[122916];

//const uint8_t wifi_mac_addr[] = {0x62, 0x1d, 0x2f, 0x00, 0x4e, 0x2d}; // MAC地址的第一个字节必须为偶数! 否则为多播地址
static uint16_t wifi_macctrl = WIFI_MACCTRL_ETHERNET2 | WIFI_MACCTRL_TX | WIFI_MACCTRL_RX;
static uint16_t wifi_timeout = WIFI_DEFTIMEOUT;
static uint32_t io_addr;

// 默认情况下startup_stm32f10x_hd.s设定的栈大小Stack_Size为0x400, 函数内最多只能创建1KB的变量
// 为了创建大缓冲区, 栈大小应该改大, 比如0x4000, 即16KB

// 延时n毫秒
void delay(uint16_t nms)
{
  timeout(nms);
  while ((TIM6->SR & TIM_SR_UIF) == 0); // 等待定时器溢出
  TIM6->SR &= ~TIM_SR_UIF; // 清除溢出标志
}

// 显示数据内容
void dump_data(const void *data, uint32_t len)
{
  const uint8_t *p = data;
  while (len--)
    printf("%02X", *p++);
  printf("\n");
}

// 启动定时器, 超时时间设为n毫秒
void timeout(uint16_t nms)
{
  TIM6->ARR = 10 * nms - 1;
  TIM6->PSC = 7199; // APB1总线上的定时器时钟为72MHz
  TIM6->CR1 = TIM_CR1_OPM | TIM_CR1_URS; // OPM=1: UIF置位后自动关闭定时器
  TIM6->EGR = TIM_EGR_UG; // 应用上面的设置, 当URS=1时UIF保持0
  TIM6->CR1 |= TIM_CR1_CEN; // 开始计时
}

/* 关联一个热点 */
uint16_t WiFi_Associate(const char *ssid)
{
  uint8_t buffer[2048];
  WiFi_SSIDInfo info;
  WiFi_CmdRequest_Associate *cmd = (WiFi_CmdRequest_Associate *)buffer;
  WiFi_CmdResponse_Associate *resp = (WiFi_CmdResponse_Associate *)buffer;
  MrvlIETypes_PhyParamDSSet_t *ds;
  MrvlIETypes_CfParamSet_t *cf;
  MrvlIETypes_AuthType_t *auth;
  MrvlIETypes_RsnParamSet_t *rsn;

  if (!WiFi_ScanSSID(ssid, &info, buffer, sizeof(buffer)))
  {
    printf("Cannot find AP: %s!\n", ssid);
    return WIFI_ASSOCIATION_NOTFOUND;
  }
  
  memcpy(cmd->peer_sta_addr, info.mac_addr, sizeof(info.mac_addr));
  cmd->cap_info = info.cap_info;
  cmd->listen_interval = 10;
  cmd->bcn_period = info.bcn_period;
  cmd->dtim_period = 1;
  memcpy(cmd + 1, &info.ssid, TLV_STRUCTLEN(info.ssid));
  
  ds = (MrvlIETypes_PhyParamDSSet_t *)((uint8_t *)(cmd + 1) + TLV_STRUCTLEN(info.ssid));
  ds->header.type = MRVLIETYPES_DSPARAMSET;
  ds->header.length = 1;
  ds->channel = info.channel;
  
  cf = (MrvlIETypes_CfParamSet_t *)(ds + 1);
  memset(cf, 0, sizeof(MrvlIETypes_CfParamSet_t));
  cf->header.type = MRVLIETYPES_CFPARAMSET;
  cf->header.length = TLV_PAYLOADLEN(*cf);
  
  memcpy(cf + 1, &info.rates, TLV_STRUCTLEN(info.rates));
  auth = (MrvlIETypes_AuthType_t *)((uint8_t *)(cf + 1) + TLV_STRUCTLEN(info.rates));
  auth->header.type = MRVLIETYPES_AUTHTYPE;
  auth->header.length = TLV_PAYLOADLEN(*auth);
  auth->auth_type = AUTH_MODE_OPEN;
  
  rsn = (MrvlIETypes_RsnParamSet_t *)(auth + 1);
  if (info.rsn.header.type)
  {
    // WPA2网络必须在命令中加入RSN参数才能成功连接
    memcpy(rsn, &info.rsn, TLV_STRUCTLEN(info.rsn));
    WiFi_SendCommand(CMD_802_11_ASSOCIATE, buffer, (uint8_t *)rsn - buffer + TLV_STRUCTLEN(info.rsn), sizeof(buffer));
  }
  else
    WiFi_SendCommand(CMD_802_11_ASSOCIATE, buffer, (uint8_t *)rsn - buffer, sizeof(buffer)); // 其余网络不需要RSN参数
  
  if (!WiFi_ReceiveResponse(buffer, sizeof(buffer)))
  {
    printf("Association with %s failed!\n", ssid);
    return WIFI_ASSOCIATION_ERROR;
  }
  
  //printf("capability=0x%04x, status_code=0x%04x, aid=0x%04x\n", resp->capability, resp->status_code, resp->association_id);
  if (resp->association_id == 0xffff)
    return ((-resp->capability) << 8) | resp->status_code;
  return WIFI_ASSOCIATION_SUCCESS;
}

/* 连接WiFi热点, 超时自动重连 */
uint16_t WiFi_Connect(const char *ssid, uint8_t type)
{
  uint16_t ret;
  do
  {
    if (type == BSS_INDEPENDENT) // 连接Ad-Hoc型热点
      ret = WiFi_JoinADHOC(ssid);
    else if (type == BSS_INFRASTRUCTURE) // 连接普通热点
      ret = WiFi_Associate(ssid);
    else
    {
      printf("WiFi_Connect: incorrect network type!\n");
      return WIFI_ASSOCIATION_ERROR;
    }
    if (ret != WIFI_ASSOCIATION_SUCCESS)
    {
      printf("%s returned 0x%04x\n", (type == BSS_INDEPENDENT) ? "WiFi_JoinADHOC" : "WiFi_Associate", ret);
      delay(2000); // 等待一段时间后重连
    }
  } while (WIFI_ASSOCIATION_TIMEOUT(ret) || ret == WIFI_ASSOCIATION_NOTFOUND); // 若连接超时, 或未扫描到热点, 则重连
  
  if (ret != WIFI_ASSOCIATION_SUCCESS)
    return ret;
  
  printf("Connected to %s!\n", ssid);
  return ret;
}

/* 下载固件 */
// 参考文档: marvell-88w8686-固件下载程序说明.doc
uint8_t WiFi_DownloadFirmware(void)
{
  uint8_t helper_buf[64];
  const uint8_t *data;
  uint16_t size;
  uint32_t len;
  
  // 块大小设为32
  SDIO->DCTRL = (SDIO->DCTRL & ~SDIO_DCTRL_DBLOCKSIZE) | SDIO_DCTRL_DBLOCKSIZE_2 | SDIO_DCTRL_DBLOCKSIZE_0;
  WiFi_SetBlockSize(1); // 应用到Function 1
  
  // 下载helper
  io_addr = WiFi_Read(1, WIFI_IOPORT0) | (WiFi_Read(1, WIFI_IOPORT1) << 8) | (WiFi_Read(1, WIFI_IOPORT2) << 16);
  data = firmware_helper_sd;
  len = sizeof(firmware_helper_sd);
  while (len)
  {
    // 每次下载64字节, 其中前4字节为本次下载的数据量
    size = (len > 60) ? 60 : len;
    *(uint32_t *)helper_buf = size;
    memcpy(helper_buf + 4, data, size);
    
    WiFi_Wait(WIFI_CARDSTATUS_DNLDCARDRDY);
    WiFi_WritePort(helper_buf, sizeof(helper_buf), sizeof(helper_buf));
    len -= size;
    data += size;
  }
  *(uint32_t *)helper_buf = 0;
  WiFi_WritePort(helper_buf, sizeof(helper_buf), sizeof(helper_buf)); // 以空数据包结束
  
  // 下载固件
  data = firmware_sd8686;
  len = sizeof(firmware_sd8686);
  while (len)
  {
    WiFi_Wait(WIFI_CARDSTATUS_DNLDCARDRDY);
    while ((size = WiFi_Read(1, WIFI_SQREADBASEADDR0) | (WiFi_Read(1, WIFI_SQREADBASEADDR1) << 8)) == 0); // 获取本次下载的字节数
    //printf("Required: %d bytes, Remaining: %d bytes\n", size, len);
    
    if (size & 1)
    {
      // 若size为奇数(如17), 则说明接收端有CRC校验错误, 应重新传送上一次的内容(这部分代码省略)
      printf("Error: an odd size is invalid!\n");
      return 0;
    }
    if (size > len)
      size = len;
    
    // len为缓冲区剩余大小
#ifdef WIFI_HIGHSPEED // 高速模式下不能使用多字节传输模式, 只能使用块传输模式, 因此缓冲区要足够大
    if (len < 32)
    {
      // 若缓冲区空间不足一个数据块, 则借用helper_buf
      memcpy(helper_buf, data, size);
      WiFi_WritePort(helper_buf, size, sizeof(helper_buf));
    }
    else
#endif
      WiFi_WritePort(data, size, len);
    
    if (!SDIO_SUCCEEDED())
    {
      printf("Data transfer error! SDIO->STA=0x%08x\n", SDIO->STA);
      return 0;
    }
    
    len -= size;
    data += size;
  }
  
  // 等待Firmware启动
  while (WiFi_GetPacketLength() == 0xfedc);
  printf("Firmware is successfully downloaded!\n");
  return 1;
}

/* 获取数据帧大小 */
uint16_t WiFi_GetPacketLength(void)
{
  // 读Scratch pad 4寄存器的低16位
  return WiFi_Read(1, WIFI_SCRATCHPAD4_0) | (WiFi_Read(1, WIFI_SCRATCHPAD4_1) << 8);
}

/* 初始化WiFi模块 */
// SDIO Simplified Specification Version 3.00: 3. SDIO Card Initialization
void WiFi_Init(void)
{
  uint16_t rca; // 虽然SDIO标准规定SDIO接口上可以接多张SD卡, 但是STM32的SDIO接口只能接一张卡(芯片手册上有说明), 软件片选RCA无意义
  SDIO->POWER = SDIO_POWER_PWRCTRL; // 打开SDIO外设
  SDIO->CLKCR = SDIO_CLKCR_CLKEN | 178; // 初始化时最高允许的频率: 72MHz/(178+2)=400kHz
  SDIO->DCTRL = SDIO_DCTRL_SDIOEN; // 开启SDIO模式
  delay(10); // 延时可防止CMD5重发
  
  // 不需要发送CMD0, 因为SD I/O card的初始化命令是CMD52
  // An I/O only card or the I/O portion of a combo card is NOT reset by CMD0. (See 4.4 Reset for SDIO)
  
  /* 发送CMD5: IO_SEND_OP_COND */
  SDIO->ARG = 0;
  SDIO->CMD = SDIO_CMD_CPSMEN | SDIO_CMD_WAITRESP_0 | 5;
  while (SDIO->STA & SDIO_STA_CMDACT);
  while (SDIO->STA & SDIO_STA_CTIMEOUT)
  {
    SDIO->ICR = SDIO_ICR_CTIMEOUTC; // 清除标志
    printf("Timeout! Resend CMD%d\n", SDIO->CMD & SDIO_CMD_CMDINDEX);
    delay(5);
    SDIO->CMD = SDIO->CMD; // 重发
    while (SDIO->STA & SDIO_STA_CMDACT);
  }
  if (SDIO->STA & SDIO_STA_CMDREND)
  {
    SDIO->ICR = SDIO_ICR_CMDRENDC;
    printf("Command response received: CMD%d, RESP_%08x\n", SDIO->RESPCMD, SDIO->RESP1);
  }
  
  /* 设置参数VDD Voltage Window: 3.2~3.4V, 并再次发送CMD5 */
  SDIO->ARG = 0x300000;
  SDIO->CMD = SDIO->CMD;
  while (SDIO->STA & SDIO_STA_CMDACT);
  if (SDIO->STA & SDIO_STA_CMDREND)
  {
    SDIO->ICR = SDIO_ICR_CMDRENDC;
    printf("Command response received: CMD%d, RESP_%08x\n", SDIO->RESPCMD, SDIO->RESP1);
    if (SDIO->RESP1 & _BV(31))
    {
      // Card is ready to operate after initialization
      printf("Number of I/O Functions: %d\n", (SDIO->RESP1 >> 28) & 7);
      printf("Memory Present: %d\n", (SDIO->RESP1 & _BV(27)) != 0);
    }
  }
  
  /* 获取WiFi模块地址 (CMD3: SEND_RELATIVE_ADDR, Ask the card to publish a new relative address (RCA)) */
  SDIO->ARG = 0;
  SDIO->CMD = SDIO_CMD_CPSMEN | SDIO_CMD_WAITRESP_0 | 3;
  while (SDIO->STA & SDIO_STA_CMDACT);
  if (SDIO->STA & SDIO_STA_CMDREND)
  {
    SDIO->ICR = SDIO_ICR_CMDRENDC;
    rca = SDIO->RESP1 >> 16;
    printf("Relative card address: 0x%04x\n", rca);
  }
  
  /* 选中WiFi模块 (CMD7: SELECT/DESELECT_CARD) */
  SDIO->ARG = rca << 16;
  SDIO->CMD = SDIO_CMD_CPSMEN | SDIO_CMD_WAITRESP_0 | 7;
  while (SDIO->STA & SDIO_STA_CMDACT);
  if (SDIO->STA & SDIO_STA_CMDREND)
  {
    SDIO->ICR = SDIO_ICR_CMDRENDC;
    printf("Card selected! status=0x%08x\n", SDIO->RESP1);
  }
  
  /* 提高时钟频率, 超时时间为0.1s */
#ifdef WIFI_HIGHSPEED
  SDIO->CLKCR = (SDIO->CLKCR & ~SDIO_CLKCR_CLKDIV) | 1; // 72MHz/(1+2)=24MHz
  SDIO->DTIMER = 2400000;
  printf("SDIO Clock: 24MHz\n");
#else
  SDIO->CLKCR = (SDIO->CLKCR & ~SDIO_CLKCR_CLKDIV) | 70; // 72MHz/(70+2)=1MHz
  SDIO->DTIMER = 100000;
  printf("SDIO Clock: 1MHz\n");
#endif
  
  /* 选择总线宽度 (Wide Bus Selection) */
  // For an SDIO card a write to the CCCR using CMD52 is used to select bus width. (See 4.5 Bus Width)
  // CMD52: IO_RW_DIRECT, CCCR: Card Common Control Registers
  WiFi_Write(0, SDIO_CCCR_BUSIFCTRL, WiFi_Read(0, SDIO_CCCR_BUSIFCTRL) | SDIO_CCCR_BUSIFCTRL_BUSWID_4Bit); // Bus Width: 4-bit bus
  SDIO->CLKCR |= SDIO_CLKCR_WIDBUS_0;
  
  // 初始化Function 1
  WiFi_Write(0, SDIO_CCCR_IOEN, SDIO_CCCR_IOEN_IOE1); // IOE1=1 (Enable Function)
  while ((WiFi_Read(0, SDIO_CCCR_IORDY) & SDIO_CCCR_IORDY_IOR1) == 0); // 等到IOR1=1 (I/O Function Ready)
  WiFi_Write(0, SDIO_CCCR_INTEN, SDIO_CCCR_INTEN_IENM | SDIO_CCCR_INTEN_IEN1); // 打开SDIO中断请求
  WiFi_Write(1, WIFI_INTMASK, WIFI_INTMASK_HOSTINTMASK); // 利用中断标志位来判定是否有数据要读取, 可靠性更高
  
  // 显示CIS信息(SDIO卡信息)
  WiFi_ShowCIS(0);
  WiFi_ShowCIS(1);
  
#ifdef WIFI_USEDMA
  // 必须在DPSM禁用的时候开关DMA请求
  SDIO->DCTRL |= SDIO_DCTRL_DMAEN;
#endif
  
  // 下载固件
  if (!WiFi_DownloadFirmware())
    while (1);
  
  // 设置数据块大小为256字节
  SDIO->DCTRL = (SDIO->DCTRL & ~SDIO_DCTRL_DBLOCKSIZE) | SDIO_DCTRL_DBLOCKSIZE_3;
  WiFi_SetBlockSize(0);
  WiFi_SetBlockSize(1);
  
  // 允许发送和接收
  // 不使用带有LLC子层的802.2SNAP帧格式, 这样LLC和SNAP这8个字节的内容就不会夹在数据链路层的源地址字段与数据字段之间
  WiFi_MACControl(wifi_macctrl);
}

/* 加入Ad-Hoc网络 */
uint16_t WiFi_JoinADHOC(const char *ssid)
{
  uint8_t buffer[2048];
  WiFi_SSIDInfo info;
  WiFi_CmdRequest_ADHOCJoin *cmd = (WiFi_CmdRequest_ADHOCJoin *)buffer;
  
  if (!WiFi_ScanSSID(ssid, &info, buffer, sizeof(buffer)))
  {
    printf("Cannot find AP: %s!\n", ssid);
    return WIFI_ASSOCIATION_NOTFOUND;
  }
  
  memcpy(cmd->bssid, info.mac_addr, sizeof(cmd->bssid));
  memset(cmd->ssid, 0, sizeof(cmd->ssid));
  strncpy((char *)cmd->ssid, ssid, sizeof(cmd->ssid));
  cmd->bss_type = BSS_ANY;
  cmd->bcn_period = info.bcn_period;
  cmd->dtim_period = 1;
  memset(cmd->timestamp, 0, sizeof(cmd->timestamp) + sizeof(cmd->start_ts));
  cmd->ds_param_set.header.type = MRVLIETYPES_DSPARAMSET;
  cmd->ds_param_set.header.length = TLV_PAYLOADLEN(cmd->ds_param_set);
  cmd->ds_param_set.channel = info.channel;
  cmd->reserved1 = 0;
  cmd->ibss_param_set.header.type = MRVLIETYPES_IBSSPARAMSET;
  cmd->ibss_param_set.header.length = TLV_PAYLOADLEN(cmd->ibss_param_set);
  cmd->ibss_param_set.atim_window = 0;
  cmd->reserved2 = 0;
  cmd->cap_info = info.cap_info;
  memcpy(cmd->data_rates, info.rates.rates, sizeof(cmd->data_rates));
  cmd->reserved3 = 0;
  
  WiFi_SendCommand(CMD_802_11_AD_HOC_JOIN, buffer, sizeof(WiFi_CmdRequest_ADHOCJoin), sizeof(buffer));
  if (!WiFi_ReceiveResponse(buffer, sizeof(buffer)))
    return WIFI_ASSOCIATION_ERROR;
  return cmd->header.result;
}

/* 获取或设置密钥 */
uint16_t WiFi_KeyMaterial(MrvlIETypes_KeyParamSet_t *keys, uint16_t size, uint8_t action)
{
  uint8_t buffer[WIFI_DEFBUFSIZE];
  uint8_t ret_size;
  WiFi_Cmd_KeyMaterial *cmd = (WiFi_Cmd_KeyMaterial *)buffer;
  cmd->action = action;
  if (action == WIFI_ACT_SET)
  {
    memcpy(cmd + 1, keys, size);
    WiFi_SendCommand(CMD_802_11_KEY_MATERIAL, buffer, sizeof(WiFi_Cmd_KeyMaterial) + size, sizeof(buffer));
  }
  else
    WiFi_SendCommand(CMD_802_11_KEY_MATERIAL, buffer, sizeof(WiFi_Cmd_KeyMaterial), sizeof(buffer));
  
  if (!WiFi_ReceiveResponse(buffer, sizeof(buffer)))
    return (action == WIFI_ACT_GET) ? 0 : CMD_STATUS_ERROR;
  
  if (action == WIFI_ACT_GET)
  {
    ret_size = cmd->header.size - sizeof(cmd->header) - sizeof(cmd->action);
    if (ret_size <= size)
      memcpy(keys, cmd + 1, ret_size);
    else
      printf("WiFi_KeyMaterial: Buffer size is too small! %d bytes required!\n", ret_size);
    return ret_size; // action=get时返回读取的数据大小
  }
  else
    return cmd->header.result; // action=set时返回命令执行结果值
}

/* 获取或设置MAC地址 */
uint16_t WiFi_MACAddr(uint8_t addr[6], uint8_t action)
{
  uint8_t buffer[WIFI_DEFBUFSIZE];
  WiFi_Cmd_MACAddr *cmd = (WiFi_Cmd_MACAddr *)buffer;
  cmd->action = action;
  if (action == WIFI_ACT_SET)
    memcpy(cmd->mac_addr, addr, 6);
  
  WiFi_SendCommand(CMD_802_11_MAC_ADDR, buffer, sizeof(WiFi_Cmd_MACAddr), sizeof(buffer));
  if (!WiFi_ReceiveResponse(buffer, sizeof(buffer)))
    return CMD_STATUS_ERROR;
  if (action == WIFI_ACT_GET)
    memcpy(addr, cmd->mac_addr, 6);
  return cmd->header.result;
}

/* 配置MAC */
uint16_t WiFi_MACControl(uint16_t action)
{
  uint8_t buffer[WIFI_DEFBUFSIZE];
  WiFi_Cmd_MACCtrl *cmd = (WiFi_Cmd_MACCtrl *)buffer;
  cmd->action = action;
  cmd->reserved = 0;
  WiFi_SendCommand(CMD_MAC_CONTROL, buffer, sizeof(WiFi_Cmd_MACCtrl), sizeof(buffer));
  if (!WiFi_ReceiveResponse(buffer, sizeof(buffer)))
    return CMD_STATUS_ERROR;
  return cmd->header.result;
}

/* 判断是否有新数据包到来 */
uint8_t WiFi_PacketArrived(void)
{
	//return (WiFi_Read(1, WIFI_CARDSTATUS) & WIFI_CARDSTATUS_UPLDCARDRDY) != 0; // 可靠性差, 标志位会被CMD53命令清除, 不建议使用
  return (WiFi_Read(1, WIFI_INTSTATUS) & WIFI_INTSTATUS_UPLD) != 0; // 可靠性高, 标志位需要手动清除, 必须打开中断后才能使用
}

/* 读寄存器 */
uint8_t WiFi_Read(uint8_t func, uint32_t addr)
{
  WiFi_SendCMD52(func, addr, NULL, NULL);
  if (SDIO->STA & SDIO_STA_CMDREND)
  {
    SDIO->ICR = SDIO_ICR_CMDRENDC;
    return SDIO->RESP1 & 0xff;
  }
  else
  {
    printf("WiFi_Read failed, SDIO->STA=0x%08x!\n", SDIO->STA);
    return 0;
  }
}

/* 接收数据 */
// count为要接收的字节数或块数, bufsize为data缓冲区的大小
// bufsize=0时, 只读取数据不存入缓冲区
uint8_t WiFi_ReadData(uint8_t func, uint32_t addr, void *data, uint16_t count, uint16_t bufsize, uint32_t flags)
{
  uint32_t dctrl = SDIO->DCTRL | SDIO_DCTRL_DTDIR;
  uint32_t len; // 实际要接收的字节数
#ifdef WIFI_USEDMA
  uint32_t temp; // 丢弃数据用的变量
#else
  uint32_t *p = data;
#endif
  if (flags & CMD53_BLOCKMODE)
  {
    // 块传输模式 (DTMODE=0)
    dctrl &= ~SDIO_DCTRL_DTMODE;
    len = count * WiFi_GetSDIOBlockSize(); // 块数*块大小
  }
  else
  {
    // 多字节传输模式 (DTMODE=1, SDIO的频率低于16MHz时才支持)
    dctrl |= SDIO_DCTRL_DTMODE;
    len = count;
    if (len % 4 != 0)
    {
      len += 4 - len % 4; // WiFi模块要求字节数必须为4的整数倍
      count = len; // 传入CMD53的数据大小参数也应该是4的倍数
    }
  }
  
  if (bufsize > 0 && bufsize < len)
  {
    printf("WiFi_ReadData: a buffer of at least %d bytes is required! bufsize=%d\n", len, bufsize);
    return 0;
  }
  
#ifdef WIFI_USEDMA
  DMA2_Channel4->CPAR = (uint32_t)&SDIO->FIFO;
  DMA2_Channel4->CNDTR = len / 4;
  DMA2_Channel4->CCR = DMA_CCR4_PL | DMA_CCR4_MSIZE_1 | DMA_CCR4_PSIZE_1;
  if (bufsize > 0)
  {
    DMA2_Channel4->CMAR = (uint32_t)data;
    DMA2_Channel4->CCR |= DMA_CCR4_MINC;
  }
  else
    DMA2_Channel4->CMAR = (uint32_t)&temp; // 数据丢弃模式
  DMA2_Channel4->CCR |= DMA_CCR4_EN;
#endif
  
  SDIO->DLEN = len;
  SDIO->DCTRL = dctrl | SDIO_DCTRL_DTEN;
  
  WiFi_SendCMD53(func, addr, count, flags);
#ifdef WIFI_USEDMA
  while ((DMA2->ISR & DMA_ISR_TCIF4) == 0);
  DMA2->IFCR = DMA_IFCR_CTCIF4;
  DMA2_Channel4->CCR &= ~DMA_CCR4_EN;
#else
  while (len)
  {
    // 如果有数据到来就读取数据
    if (SDIO->STA & SDIO_STA_RXDAVL)
    {
      len -= 4;
      if (bufsize > 0)
        *p++ = SDIO->FIFO;
      else
        SDIO->FIFO; // 读寄存器, 但不保存数据
    }
    
    if (SDIO->STA & SDIO_STA_DTIMEOUT)
    {
      printf("Data timeout!\n");
      break;
    }
    else if (SDIO->STA & SDIO_STA_DCRCFAIL)
    {
      printf("Data CRC check failed! %d bytes are lost\n", len);
      break;
    }
  }
#endif
  
  while (SDIO->STA & (SDIO_STA_CMDACT | SDIO_STA_RXACT));
  SDIO->DCTRL &= ~SDIO_DCTRL_DTEN;
  
  SDIO->ICR = SDIO_STA_DATAEND | SDIO_ICR_CMDRENDC;
  if (flags & CMD53_BLOCKMODE)
    SDIO->ICR = SDIO_ICR_DBCKENDC;
  
  SDIO->STA; // 读状态寄存器后标志位才能真正清除
  return SDIO_SUCCEEDED();
}

uint8_t WiFi_ReadPort(void *data, uint16_t size, uint16_t bufsize)
{
  uint16_t block_num, block_size;
  block_size = WiFi_GetSDIOBlockSize();
  
  WiFi_Wait(0); // 发送CMD53前必须IOReady=1
  WiFi_Write(1, WIFI_INTSTATUS, WiFi_Read(1, WIFI_INTSTATUS) & ~WIFI_INTSTATUS_UPLD); // 读端口前先清除UPLD中断标志位
#ifndef WIFI_HIGHSPEED
  if (size > 512 || size % block_size == 0) // 若size为数据块大小的整数倍, 则采用数据块方式接收
  {
#endif
    block_num = size / block_size;
    if (size % block_size != 0)
      block_num++; // 大于512字节时不能采用字节流方式接收(如712字节), 仍采用数据块方式
    
    return WiFi_ReadData(1, io_addr, data, block_num, bufsize, CMD53_BLOCKMODE);
#ifndef WIFI_HIGHSPEED
  }
  else
    return WiFi_ReadData(1, io_addr, data, size, bufsize, 0); // 否则采用字节流方式接收
#endif
}

/* 接收数据帧, 返回是否成功 */
uint8_t WiFi_ReceivePacket(void *buf, uint16_t bufsize)
{
  uint8_t ret;
  uint16_t size = WiFi_GetPacketLength();
  WiFi_SDIOFrameHeader *hdr = (WiFi_SDIOFrameHeader *)buf;
  
  ret = WiFi_ReadPort(buf, size, bufsize);
  if (ret && hdr->type != WIFI_SDIOFRAME_DATA)
  {
    if (WIFI_SDIOFRAME_EVENT)
      WiFi_EventHandler(buf);
    ret = 0;
  }
  return ret;
}

/* 接收WiFi命令的回应 */
uint8_t WiFi_ReceiveResponse(void *buf, uint16_t bufsize)
{
  uint8_t ret;
  uint8_t wait = 0;
  uint16_t size;
  WiFi_CommandHeader *cmd = (WiFi_CommandHeader *)buf;
  
  // 等待数据准备好
  // 固件下载完后发送的第一个WiFi命令偶尔会收不到回应
  // 为了保证系统的可靠性, 命令的超时重传非常重要
  while (!WiFi_Wait(WIFI_CARDSTATUS_UPLDCARDRDY))
  {
    // 若WiFi_Wait返回0, 则说明超时
    wait++;
    if (wait >= 5)
    {
      printf("No response!\n");
      return 0;
    }
    if (cmd->frame_header.type == WIFI_SDIOFRAME_COMMAND)
    {
      // 若超时后还没收到数据, 则重发命令, 然后再次执行WiFi_Wait
      printf("Resend WiFi command 0x%04x! size=%d\n", cmd->cmd_code, cmd->frame_header.length);
      WiFi_ResendCommand(buf, bufsize);
    }
    else
      return 0; // 若buf中的内容不是命令, 则不重发直接退出
  }
  
  size = WiFi_GetPacketLength();
  ret = WiFi_ReadPort(buf, size, bufsize);
  if (ret && cmd->frame_header.type != WIFI_SDIOFRAME_COMMAND)
  {
    ret = 0; // buf中的内容已被接收到的数据覆盖, 无法重发命令
    if (cmd->frame_header.type == WIFI_SDIOFRAME_EVENT)
      WiFi_EventHandler(buf);
  }
  return ret;
}

/* 扫描全部热点 (仅显示) */
void WiFi_Scan(void)
{
  // 必须把STM32启动文件*.s中的Stack_Size改大, 否则函数中无法创建大数组
  uint8_t buffer[2048]; // 用于接收返回的WiFi接入点信息, 需要较大的内存空间来存放
  uint8_t i, j, n;
  uint8_t ssid[33], channel, wpa;
  uint16_t ie_size;
  
  WiFi_CmdRequest_Scan *cmd = (WiFi_CmdRequest_Scan *)buffer; // 用buffer空间来存放要发送的命令
  MrvlIETypes_ChanListParamSet_t *chanlist = (MrvlIETypes_ChanListParamSet_t *)(cmd + 1); // 这里的+1指的是前进sizeof(指针类型)个地址单元, 而非只前进1个地址单元
  
  WiFi_CmdResponse_Scan *resp = (WiFi_CmdResponse_Scan *)buffer;
  WiFi_BssDescSet *bss_desc_set;
  WiFi_Vendor *vendor;
  IEEEType *ie_params;
  //MrvlIETypes_TsfTimestamp_t *tft_table;
  
  // 分4次扫描14个通道
  for (i = 0; i < 4; i++)
  {
    cmd->bss_type = BSS_ANY;
    memset(cmd->bss_id, 0, sizeof(cmd->bss_id));
    
    // 通道的基本参数
    n = (i == 3) ? 2 : 4; // 本次要扫描的通道数
    chanlist->header.type = MRVLIETYPES_CHANLISTPARAMSET;
    chanlist->header.length = n * sizeof(chanlist->channels);
    for (j = 0; j < n; j++)
    {
      chanlist->channels[j].band_config_type = 0;
      chanlist->channels[j].chan_number = 4 * i + j + 1; // 通道号
      chanlist->channels[j].scan_type = 0;
      chanlist->channels[j].min_scan_time = 0;
      chanlist->channels[j].max_scan_time = 100;
    }
    
    // 发送命令并接收数据
    WiFi_SendCommand(CMD_802_11_SCAN, buffer, sizeof(WiFi_CmdRequest_Scan) + TLV_STRUCTLEN(*chanlist), sizeof(buffer));
    if (!WiFi_ReceiveResponse(buffer, sizeof(buffer))) // 接收的数据会将cmd和chanlist中的内容覆盖掉
    {
      printf("WiFi_Scan: no response!\n");
      continue;
    }
    
    // 显示热点信息, num_of_set为热点数
    if (resp->num_of_set > 0)
    {
      bss_desc_set = (WiFi_BssDescSet *)(resp + 1);
      for (j = 0; j < resp->num_of_set; j++)
      {
        wpa = 0;
        ie_params = &bss_desc_set->ie_parameters;
        ie_size = bss_desc_set->ie_length - (sizeof(WiFi_BssDescSet) - sizeof(bss_desc_set->ie_length) - sizeof(bss_desc_set->ie_parameters));
        while (ie_size > 0)
        {
          switch (ie_params->header.type)
          {
            case MRVLIETYPES_SSIDPARAMSET:
              // SSID名称
              memcpy(ssid, ie_params->data, ie_params->header.length);
              ssid[ie_params->header.length] = '\0';
              break;
            case MRVLIETYPES_DSPARAMSET:
              // 通道号
              channel = ie_params->data[0];
              break;
            case MRVLIETYPES_RSNPARAMSET:
              wpa = 2; // 表示WPA版本号(0表示没有使用WPA)
              break;
            case MRVLIETYPES_VENDORPARAMSET:
              if (wpa == 0)
              {
                vendor = (WiFi_Vendor *)ie_params->data;
                if (vendor->oui[0] == 0x00 && vendor->oui[1] == 0x50 && vendor->oui[2] == 0xf2 && vendor->oui_type == 0x01)
                  wpa = 1;
              }
              break;
          }
          ie_size -= TLV_STRUCTLEN(*ie_params);
          ie_params = (IEEEType *)TLV_NEXT(ie_params);
        }
        if (ie_size != 0)
          printf("ie_parameters error!\n");
        
        printf("SSID '%s', ", ssid); // 热点名称
        printf("MAC %02X:%02X:%02X:%02X:%02X:%02X, ", bss_desc_set->bssid[0], bss_desc_set->bssid[1], bss_desc_set->bssid[2], bss_desc_set->bssid[3], bss_desc_set->bssid[4], bss_desc_set->bssid[5]); // MAC地址
        printf("RSSI %d, Channel %d\n", bss_desc_set->rssi, channel); // 信号强度和通道号
        //printf("  Timestamp %lld, Beacon interval %d\n", bss_desc_set->pkt_time_stamp, bss_desc_set->bcn_interval);
        
        printf("  Capability: 0x%04x (Security: ", bss_desc_set->cap_info);
        if (bss_desc_set->cap_info & WIFI_CAPABILITY_PRIVACY)
        {
          if (wpa == 1)
            printf("WPA");
          else if (wpa == 2)
            printf("WPA2");
          else
            printf("WEP");
        }
        else
          printf("unsecured");
        
        printf(", Mode: ");
        if (bss_desc_set->cap_info & WIFI_CAPABILITY_IBSS)
          printf("Ad-Hoc");
        else
          printf("Infrastructure");
        printf(")\n");
        
        // 转向下一个热点信息
        bss_desc_set = (WiFi_BssDescSet *)((uint8_t *)bss_desc_set + sizeof(bss_desc_set->ie_length) + bss_desc_set->ie_length);
      }
      
      // resp->buf_size就是bss_desc_set的总大小
      // 因此tft_table == buffer + sizeof(WiFi_CmdResponse_Scan) + resp->buf_size
      /*
      tft_table = (MrvlIETypes_TsfTimestamp_t *)bss_desc_set;
      if (tft_table->header.type == MRVLIETYPES_TSFTIMESTAMP && tft_table->header.length == resp->num_of_set * sizeof(uint64_t))
      {
        printf("Timestamps: ");
        for (j = 0; j < resp->num_of_set; j++)
          printf("%lld ", tft_table->tsf_table[j]);
        printf("\n");
      }
      */
      
      // TSF timestamp table是整个数据的末尾, 后面没有Channel/band table
      //if (((uint8_t *)tft_table - buffer) + TLV_STRUCTLEN(*tft_table) == cmd->header.frame_header.length)
      //  printf("data end!\n");
    }
  }
}

/* 获取指定名称的热点的信息 */
// buffer用来存放返回的全部结果, 因此bufsize应该足够大
// info用来存放从buffer中提取出来的一些常用信息
uint8_t WiFi_ScanSSID(const char *ssid, WiFi_SSIDInfo *info, uint8_t *buffer, uint16_t bufsize)
{
  uint8_t i, ret;
  uint16_t ie_size;
  WiFi_CmdRequest_Scan *cmd = (WiFi_CmdRequest_Scan *)buffer;
  WiFi_CmdResponse_Scan *resp = (WiFi_CmdResponse_Scan *)buffer;
  WiFi_BssDescSet *bss_desc_set = (WiFi_BssDescSet *)(resp + 1);
  MrvlIETypes_ChanListParamSet_t *chan_list;
  IEEEType *ie_params;
  WiFi_Vendor *vendor;
  
  cmd->bss_type = BSS_ANY;
  memset(cmd->bss_id, 0, sizeof(cmd->bss_id));
  
  // 添加ssid参数
  info->ssid.header.type = MRVLIETYPES_SSIDPARAMSET;
  info->ssid.header.length = strlen(ssid);
  memcpy(info->ssid.ssid, ssid, info->ssid.header.length);
  memcpy(cmd + 1, &info->ssid, TLV_STRUCTLEN(info->ssid));
  
  chan_list = (MrvlIETypes_ChanListParamSet_t *)((uint8_t *)(cmd + 1) + TLV_STRUCTLEN(info->ssid));
  chan_list->header.type = MRVLIETYPES_CHANLISTPARAMSET;
  chan_list->header.length = 14 * sizeof(chan_list->channels); // 一次性扫描14个通道
  for (i = 0; i < 14; i++)
  {
    chan_list->channels[i].band_config_type = 0;
    chan_list->channels[i].chan_number = i + 1;
    chan_list->channels[i].scan_type = 0;
    chan_list->channels[i].min_scan_time = 0;
    chan_list->channels[i].max_scan_time = 100;
  }
  
  WiFi_SendCommand(CMD_802_11_SCAN, buffer, ((uint8_t *)chan_list - buffer) + TLV_STRUCTLEN(*chan_list), bufsize);
  wifi_timeout = 3000; // 延长超时时间
  ret = WiFi_ReceiveResponse(buffer, bufsize);
  wifi_timeout = WIFI_DEFTIMEOUT;
  if (!ret || resp->num_of_set == 0)
    return 0; // 失败
  
  // bss_desc_set以扫描到的第一个信息项为准
  memcpy(info->mac_addr, bss_desc_set->bssid, sizeof(info->mac_addr));
  info->cap_info = bss_desc_set->cap_info;
  info->bcn_period = bss_desc_set->bcn_interval;
  
  // 若type=0, 则表明没有该项的信息 (除SSID结构体外, 因为SSID的type=MRVLIETYPES_SSIDPARAMSET=0)
  info->rates.header.type = 0;
  info->rsn.header.type = 0;
  info->wpa.header.type = 0;
  info->wwm.header.type = 0;
  info->wps.header.type = 0;
  
  ie_params = &bss_desc_set->ie_parameters;
  ie_size = bss_desc_set->ie_length - (sizeof(WiFi_BssDescSet) - sizeof(bss_desc_set->ie_length) - sizeof(bss_desc_set->ie_parameters)); // 所有IEEE_Type数据的总大小
  while (ie_size > 0)
  {
    switch (ie_params->header.type)
    {
      case MRVLIETYPES_RATESPARAMSET:
        // 速率
        WiFi_TranslateTLV((MrvlIEType *)&info->rates, ie_params, sizeof(info->rates.rates));
        break;
      case MRVLIETYPES_DSPARAMSET:
        // 通道号
        info->channel = ie_params->data[0];
        break;
      case MRVLIETYPES_RSNPARAMSET:
        // 通常只有一个RSN信息 (与WPA2相关)
        // printf("RSN len=%d\n", ie_params->length);
        WiFi_TranslateTLV((MrvlIEType *)&info->rsn, ie_params, sizeof(info->rsn.rsn));
        break;
      case MRVLIETYPES_VENDORPARAMSET:
        // 通常会有多项VENDOR信息 (与WPA相关)
        vendor = (WiFi_Vendor *)ie_params->data;
        if (vendor->oui[0] == 0x00 && vendor->oui[1] == 0x50 && vendor->oui[2] == 0xf2)
        {
          switch (vendor->oui_type)
          {
            case 0x01:
              // wpa_oui
              WiFi_TranslateTLV((MrvlIEType *)&info->wpa, ie_params, sizeof(info->wpa.vendor));
              break;
            case 0x02:
              // wmm_oui
              if (ie_params->header.length == 24) // 合法大小
                WiFi_TranslateTLV((MrvlIEType *)&info->wwm, ie_params, sizeof(info->wwm.vendor));
              break;
            case 0x04:
              // wps_oui
              WiFi_TranslateTLV((MrvlIEType *)&info->wps, ie_params, sizeof(info->wps.vendor));
              break;
          }
        }
        break;
    }
    
    // 转向下一个TLV
    ie_size -= TLV_STRUCTLEN(*ie_params);
    ie_params = (IEEEType *)TLV_NEXT(ie_params);
  }
  
  return 1; // 成功
}

void WiFi_SendCMD52(uint8_t func, uint32_t addr, uint8_t data, uint32_t flags)
{
  SDIO->ARG = (func << 28) | (addr << 9) | data | flags;
  SDIO->CMD = SDIO_CMD_CPSMEN | SDIO_CMD_WAITRESP_0 | 52;
  while (SDIO->STA & SDIO_STA_CMDACT);
}

void WiFi_SendCMD53(uint8_t func, uint32_t addr, uint16_t count, uint32_t flags)
{
  // 当count=512时, 和0x1ff相与后为0, 符合要求
  SDIO->ARG = (func << 28) | (addr << 9) | (count & 0x1ff) | flags;
  SDIO->CMD = SDIO_CMD_CPSMEN | SDIO_CMD_WAITRESP_0 | 53;
}

/* 发送WiFi命令 */
uint8_t WiFi_SendCommand(uint16_t com_code, void *data, uint16_t size, uint16_t bufsize)
{
  static uint16_t seq_num = 0;
  WiFi_CommandHeader *cmdhdr = (WiFi_CommandHeader *)data;
  
  if (size != 0)
  {
    cmdhdr->frame_header.length = size;
    cmdhdr->frame_header.type = WIFI_SDIOFRAME_COMMAND;
    cmdhdr->cmd_code = com_code;
    cmdhdr->size = size - sizeof(WiFi_SDIOFrameHeader); // 命令大小包括命令头部, 但不包括SDIO帧头部
    cmdhdr->seq_num = seq_num++;
    cmdhdr->result = 0;
  }
  else
    size = cmdhdr->frame_header.length; // 重发命令时不填写cmdhdr
  
  return WiFi_WritePort(data, size, bufsize);
}

/* 发送数据帧 */
uint8_t WiFi_SendPacket(WiFi_DataTx *packet, uint16_t packet_len, uint16_t bufsize)
{
  uint8_t ret;
  
  // 有关发送数据包的细节, 请参考Firmware Specification PDF的Chapter 3: Data Path
  packet->header.length = sizeof(WiFi_DataTx) - sizeof(packet->payload) + packet_len;
  packet->header.type = WIFI_SDIOFRAME_DATA;
  
  packet->reserved1 = 0;
  packet->tx_control = 0; // 控制信息的格式请参考3.2.1 Per-Packet Settings
  packet->tx_packet_offset = sizeof(WiFi_DataTx) - sizeof(packet->payload) - sizeof(packet->header); // 不包括SDIOFrameHeader
  packet->tx_packet_length = packet_len;
  memcpy((void *)&packet->tx_dest_addr_high, packet->payload, 6);
  packet->priority = 0;
  packet->flags = 0;
  packet->pkt_delay_2ms = 0;
  packet->reserved2 = 0;
  
  ret = WiFi_WritePort(packet, packet->header.length, bufsize);
  if (ret)
    ret &= WiFi_Wait(WIFI_CARDSTATUS_DNLDCARDRDY); // WiFi模块收到数据后, 会将该位置1
  return ret;
}

/* 将SDIO->DCTRL中的块大小信息应用到WiFi模块指定的功能区上 */
void WiFi_SetBlockSize(uint8_t func)
{
  // Part E1: 6.9 Card Common Control Registers (CCCR), 6.10 Function Basic Registers (FBR)
  uint16_t size = WiFi_GetSDIOBlockSize();
  WiFi_Write(0, (func << 8) | 0x10, size & 0xff);
  WiFi_Write(0, (func << 8) | 0x11, size >> 8);
}

/* 设置密钥 */
uint8_t WiFi_SetKeyMaterial(uint16_t type, uint16_t info, const uint8_t *key, uint16_t len)
{
  MrvlIETypes_KeyParamSet_t key_param;
  if (len > sizeof(key_param.key))
    return CMD_STATUS_ERROR;
  key_param.header.type = MRVLIETYPES_KEYPARAMSET;
  key_param.header.length = (key_param.key - (uint8_t *)&key_param) - sizeof(key_param.header) + len;
  key_param.key_type_id = type;
  key_param.key_info = info;
  key_param.key_len = len; // 当len=0时可保留key的值, 只更新key_info
  if (len)
    memcpy(key_param.key, key, len);
  return WiFi_KeyMaterial(&key_param, TLV_STRUCTLEN(key_param), WIFI_ACT_SET);
}

/* 设置WEP密钥 (长度必须为5或13个字符) */
// action: WIFI_ACT_ADD / WIFI_ACT_REMOVE
uint16_t WiFi_SetWEP(uint8_t action, const char *key)
{
  uint8_t buffer[WIFI_DEFBUFSIZE];
  uint8_t i, len;
  uint16_t ret;
  uint32_t temp;
  WiFi_Cmd_SetWEP *cmd = (WiFi_Cmd_SetWEP *)buffer;
  
  cmd->action = action;
  cmd->tx_key_index = 0;
  
  memset(cmd->wep_types, 0, sizeof(cmd->wep_types) + sizeof(cmd->keys));
  if (action == WIFI_ACT_ADD)
  {
    len = strlen(key);
    if (len == 5 || len == 13)
    {
      // 5个或13个ASCII密钥字符
      if (len == 5)
        cmd->wep_types[0] = WIFI_WEPKEYTYPE_40BIT;
      else if (len == 13)
        cmd->wep_types[0] = WIFI_WEPKEYTYPE_104BIT;
      memcpy(cmd->keys[0], key, len);
    }
    else if (len == 10 || len == 26)
    {
      // 10个或26个16进制密钥字符
      if (len == 10)
        cmd->wep_types[0] = WIFI_WEPKEYTYPE_40BIT;
      else if (len == 26)
        cmd->wep_types[0] = WIFI_WEPKEYTYPE_104BIT;
      
      for (i = 0; i < len; i++)
      {
        if (!(key[i] >= '0' && key[i] <= '9') && !(key[i] >= 'a' && key[i] <= 'f') && !(key[i] >= 'A' && key[i] <= 'F'))
        {
          printf("WiFi_SetWEP: The hex key contains invalid characters!\n");
          return 0xffff;
        }
        if (i % 2 == 0)
        {
          sscanf(key + i, "%02x", &temp);
          cmd->keys[0][i / 2] = temp;
        }
      }
    }
    else
    {
      printf("WiFi_SetWEP: The key length is invalid!\n");
      return 0xffff;
    }
    wifi_macctrl |= WIFI_MACCTRL_WEP;
  }
  else
    wifi_macctrl &= ~WIFI_MACCTRL_WEP;
  
  ret = WiFi_MACControl(wifi_macctrl);
  if (ret != CMD_STATUS_SUCCESS)
    return ret;
  
  WiFi_SendCommand(CMD_802_11_SET_WEP, buffer, sizeof(WiFi_Cmd_SetWEP), sizeof(buffer));
  if (!WiFi_ReceiveResponse(buffer, sizeof(buffer)))
    return CMD_STATUS_ERROR;
  return cmd->header.result;
}

/* 显示WiFi模块信息 */
void WiFi_ShowCIS(uint8_t func)
{
  uint8_t data[255];
  uint8_t i, len;
  uint8_t tpl_code, tpl_link; // 16.2 Basic Tuple Format and Tuple Chain Structure
  uint32_t cis_ptr;
  printf("-------------- CIS of Function %d ----------------\n", func);
  
  // 获取CIS的地址
  cis_ptr = (func << 8) | 0x9;
  cis_ptr  = WiFi_Read(0, cis_ptr) | (WiFi_Read(0, cis_ptr + 1) << 8) | (WiFi_Read(0, cis_ptr + 2) << 16);
  printf("Pointer to Function %d Card Information Structure (CIS): 0x%08x\n", func, cis_ptr);
  
  // 遍历CIS, 直到尾节点
  while ((tpl_code = WiFi_Read(0, cis_ptr++)) != CISTPL_END)
  {
    if (tpl_code == CISTPL_NULL)
      continue;
    
    tpl_link = WiFi_Read(0, cis_ptr++); // 本结点数据的大小
    for (i = 0; i < tpl_link; i++)
      data[i] = WiFi_Read(0, cis_ptr + i);
    
    printf("[CIS Tuple 0x%02x] addr=0x%08x size=%d\n", tpl_code, cis_ptr - 2, tpl_link);
    dump_data(data, tpl_link);
    switch (tpl_code)
    {
      case CISTPL_VERS_1:
        i = 2;
        while (data[i] != 0xff)
        {
          len = strlen((char *)&data[i]);
          if (len != 0)
            printf("%s\n", data + i);
          i += len + 1;
        }
        break;
      case CISTPL_MANFID:
        // 16.6 CISTPL_MANFID: Manufacturer Identification String Tuple
        printf("SDIO Card manufacturer code: 0x%04x\n", *(uint16_t *)data); // TPLMID_MANF
        printf("manufacturer information (Part Number and/or Revision): 0x%04x\n", *(uint16_t *)(data + 2)); // TPLMID_CARD
        break;
      case CISTPL_FUNCID:
        // 16.7.1 CISTPL_FUNCID: Function Identification Tuple
        printf("Card function code: 0x%02x\n", data[0]); // TPLFID_FUNCTION
        printf("System initialization bit mask: 0x%02x\n", data[1]); // TPLFID_SYSINIT
        break;
      case CISTPL_FUNCE:
        // 16.7.2 CISTPL_FUNCE: Function Extension Tuple
        if (data[0] == 0)
        {
          // 16.7.3 CISTPL_FUNCE Tuple for Function 0 (Extended Data 00h)
          printf("maximum block size: %d\n", *(uint16_t *)(data + 1));
          printf("maximum transfer rate code: 0x%02x\n", data[3]);
        }
        else
        {
          // 16.7.4 CISTPL_FUNCE Tuple for Function 1-7 (Extended Data 01h)
          printf("maximum block size: %d\n", *(uint16_t *)(data + 0x0c)); // TPLFE_MAX_BLK_SIZE
        }
    }
    
    cis_ptr += tpl_link;
    if (tpl_link == 0xff)
      break; // 当TPL_LINK为0xff时说明当前结点为尾节点
  }
  printf("--------------------------------------------------\n");
}

/* 显示所有密钥 */
void WiFi_ShowKeyMaterials(void)
{
  uint8_t buffer[WIFI_DEFBUFSIZE];
  uint16_t size;
  MrvlIETypes_KeyParamSet_t *key = (MrvlIETypes_KeyParamSet_t *)buffer;
  
  size = WiFi_KeyMaterial(key, sizeof(buffer), WIFI_ACT_GET);
  while (size)
  {
    printf("Type %d, Info 0x%04x, Len %d\n", key->key_type_id, key->key_info, key->key_len);
    dump_data(key->key, key->key_len);
    size -= TLV_STRUCTLEN(*key);
    key = (MrvlIETypes_KeyParamSet_t *)((uint8_t *)key + TLV_STRUCTLEN(*key));
  }
  if (size == 0)
    printf("data end!\n");
}

/* 创建一个Ad-Hoc型的WiFi热点 */
uint8_t WiFi_StartADHOC(const char *ssid)
{
  uint8_t buffer[WIFI_DEFBUFSIZE] = {0};
  WiFi_CmdRequest_ADHOCStart *cmd = (WiFi_CmdRequest_ADHOCStart *)buffer;
  
  strncpy((char *)cmd->ssid, ssid, sizeof(cmd->ssid));
  cmd->bss_type = BSS_INDEPENDENT;
  cmd->bcn_period = 100;
  cmd->ibss_param_set.header.type = MRVLIETYPES_IBSSPARAMSET;
  cmd->ibss_param_set.header.length = TLV_PAYLOADLEN(cmd->ibss_param_set);
  cmd->ibss_param_set.atim_window = 0;
  cmd->ds_param_set.header.type = MRVLIETYPES_DSPARAMSET;
  cmd->ds_param_set.header.length = TLV_PAYLOADLEN(cmd->ds_param_set);
  cmd->ds_param_set.channel = 1;
  cmd->cap_info = WIFI_CAPABILITY_IBSS;
  if (wifi_macctrl & WIFI_MACCTRL_WEP)
    cmd->cap_info |= WIFI_CAPABILITY_PRIVACY;
  *(uint32_t *)cmd->data_rate = 0x968b8482;
  
  WiFi_SendCommand(CMD_802_11_AD_HOC_START, buffer, sizeof(WiFi_CmdRequest_ADHOCStart), sizeof(buffer));
  if (!WiFi_ReceiveResponse(buffer, sizeof(buffer)))
    return CMD_STATUS_ERROR;
  return cmd->header.result;
}

/* 设置WiFi事件 */
uint16_t WiFi_SubscribeEvent(uint16_t action, uint16_t events)
{
  uint8_t buffer[WIFI_DEFBUFSIZE];
  WiFi_Cmd_SubscribeEvent *cmd = (WiFi_Cmd_SubscribeEvent *)buffer;
  cmd->action = action;
  if (action == WIFI_ACT_GET)
    cmd->events = 0;
  else
    cmd->events = events;
  
  WiFi_SendCommand(CMD_802_11_SUBSCRIBE_EVENT, buffer, sizeof(WiFi_Cmd_SubscribeEvent), sizeof(buffer));
  if (!WiFi_ReceiveResponse(buffer, sizeof(buffer)))
    return CMD_STATUS_ERROR;
  return cmd->events;
}

/* 将IEEE型的TLV转换成MrvlIE型的TLV */
uint8_t WiFi_TranslateTLV(MrvlIEType *mrvlie_tlv, const IEEEType *ieee_tlv, uint16_t mrvlie_payload_size)
{
  mrvlie_tlv->header.type = ieee_tlv->header.type;
  if (ieee_tlv->header.length > mrvlie_payload_size)
    mrvlie_tlv->header.length = mrvlie_payload_size; // 若源数据大小超过缓冲区最大容量, 则丢弃剩余数据
  else
    mrvlie_tlv->header.length = ieee_tlv->header.length;
  memcpy(mrvlie_tlv->data, ieee_tlv->data, mrvlie_tlv->header.length); // 复制数据
  return mrvlie_tlv->header.length == ieee_tlv->header.length; // 返回值表示缓冲区大小是否足够
}

/* 在规定的超时时间内, 等待指定的卡状态位置位(自动包括IO Ready位), 若成功则返回1 */
uint8_t WiFi_Wait(uint8_t status)
{
  status |= WIFI_CARDSTATUS_IOREADY;
  timeout(wifi_timeout);
  while ((WiFi_Read(1, WIFI_CARDSTATUS) & status) != status)
  {
    if (TIM6->SR & TIM_SR_UIF)
    {
      // 若超时时间已到
      TIM6->SR &= ~TIM_SR_UIF;
      printf("WiFi_Wait(0x%02x): timeout!\n", status);
      return 0;
    }
  }
  TIM6->CR1 &=~ TIM_CR1_CEN; // 关闭定时器
  return 1;
}

/* 写寄存器, 返回写入后寄存器的实际内容 */
uint8_t WiFi_Write(uint8_t func, uint32_t addr, uint8_t value)
{
  WiFi_SendCMD52(func, addr, value, CMD52_WRITE | CMD52_READAFTERWRITE);
  if (SDIO->STA & SDIO_STA_CMDREND)
  {
    SDIO->ICR = SDIO_ICR_CMDRENDC;
    return SDIO->RESP1 & 0xff;
  }
  else
  {
    printf("WiFi_Write failed, SDIO->STA=0x%08x!\n", SDIO->STA);
    return 0;
  }
}

/* 发送数据 */
// count为要发送的字节数或块数, bufsize为data缓冲区的大小
uint8_t WiFi_WriteData(uint8_t func, uint32_t addr, const void *data, uint16_t count, uint32_t bufsize, uint32_t flags)
{
  uint32_t dctrl = SDIO->DCTRL & ~SDIO_DCTRL_DTDIR;
  uint32_t len; // 实际要发送的字节数
#ifndef WIFI_USEDMA
  const uint32_t *p = data;
#endif
  if (flags & CMD53_BLOCKMODE)
  {
    // 块传输模式 (DTMODE=0)
    dctrl &= ~SDIO_DCTRL_DTMODE;
    len = count * WiFi_GetSDIOBlockSize(); // 块数*块大小
  }
  else
  {
    // 多字节传输模式 (DTMODE=1, SDIO的频率低于16MHz时才支持)
    dctrl |= SDIO_DCTRL_DTMODE;
    len = count;
    if (len % 4 != 0)
    {
      len += 4 - len % 4; // WiFi模块要求写入的字节数必须为4的整数倍
      count = len; // 传入CMD53的数据大小参数也应该是4的倍数
    }
  }
  
  if (bufsize < len)
    printf("WiFi_WriteData: a buffer of at least %d bytes is required! bufsize=%d\n", len, bufsize); // 只读缓冲区越界不会影响数据传输, 所以这只是一个警告
  
  WiFi_SendCMD53(func, addr, count, flags | CMD53_WRITE);
  while (SDIO->STA & SDIO_STA_CMDACT);
  if ((SDIO->STA & SDIO_STA_CMDREND) == 0)
  {
    printf("WiFi_WriteData: CMD53 no response!\n");
    return 0;
  }
  SDIO->ICR = SDIO_ICR_CMDRENDC;
  
  // 开始发送数据
#ifdef WIFI_USEDMA
  DMA2_Channel4->CMAR = (uint32_t)data;
  DMA2_Channel4->CPAR = (uint32_t)&SDIO->FIFO;
  DMA2_Channel4->CNDTR = len / 4;
  DMA2_Channel4->CCR = DMA_CCR4_PL | DMA_CCR4_MSIZE_1 | DMA_CCR4_PSIZE_1 | DMA_CCR4_MINC | DMA_CCR4_DIR | DMA_CCR4_EN;
#endif
  
  SDIO->DLEN = len;
  SDIO->DCTRL = dctrl | SDIO_DCTRL_DTEN;
  
#ifdef WIFI_USEDMA
  while ((DMA2->ISR & DMA_ISR_TCIF4) == 0);
  DMA2->IFCR = DMA_IFCR_CTCIF4;
  DMA2_Channel4->CCR &= ~DMA_CCR4_EN;
#else
  while (len)
  {
    len -= 4;
    SDIO->FIFO = *p++; // 向FIFO送入4字节数据
    while (SDIO->STA & SDIO_STA_TXFIFOF); // 如果FIFO已满则等待
  }
#endif
  
  while (SDIO->STA & SDIO_STA_TXACT); // 等待发送完毕
  SDIO->DCTRL &= ~SDIO_DCTRL_DTEN; // 数据传输完毕后DTEN应及时清零, 防止后续对DCTRL寄存器操作后误启动数据传输导致超时或CRC校验错误
  
  // 清除相关标志位
  SDIO->ICR = SDIO_ICR_DATAENDC;
  if (flags & CMD53_BLOCKMODE)
    SDIO->ICR = SDIO_ICR_DBCKENDC;
  
  SDIO->STA; // 读状态寄存器后标志位才能真正清除
  return SDIO_SUCCEEDED();
}

/* 向WiFi缓冲区发送数据 */
uint8_t WiFi_WritePort(const void *data, uint16_t size, uint32_t bufsize)
{
  uint16_t block_num, block_size;
  block_size = WiFi_GetSDIOBlockSize();
  
  WiFi_Wait(0); // 发送CMD53前必须IOReady=1
#ifndef WIFI_HIGHSPEED
  if (size > 512 || size % block_size == 0) // 若size为数据块大小的整数倍, 则采用数据块方式发送
  {
#endif
    block_num = size / block_size;
    if (size % block_size != 0)
      block_num++; // 大于512字节时不能采用字节流方式发送(如712字节), 仍采用数据块方式
    
    return WiFi_WriteData(1, io_addr, data, block_num, bufsize, CMD53_BLOCKMODE);
#ifndef WIFI_HIGHSPEED
  }
  else
    return WiFi_WriteData(1, io_addr, data, size, bufsize, 0); // 否则采用字节流方式发送
#endif
}
【httptest.c】

#include <string.h>
#include "lwip/tcp.h" // 一般情况下需要包含的头文件

#define STR_AND_SIZE(str) (str), strlen(str)

static err_t http_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err)
{
  char len[10]; // 存放HTML内容的长度
  char str[200]; // 存放HTML内容
  
  char name[100];
  char *pstr;
  uint8_t i = 0;
  if (p != NULL)
  {
    // 提取页面名称
    pstr = (char *)p->payload;
    while (*pstr++ != ' ');
    while (*pstr != ' ')
      name[i++] = *pstr++;
    name[i] = '\0'; // 不要忘了结束name字符串
    tcp_recved(tpcb, p->tot_len);
    
    // 生成HTML内容
    sprintf(str, "<meta charset=\"gb2312\"><title>获取网页名称</title><div style=\"font-family:Arial\"><b>请求的网页文件名称是: </b>%s</div>", name);
    
    sprintf(len, "%d", strlen(str)); // HTML内容的长度
    tcp_write(tpcb, STR_AND_SIZE("HTTP/1.1 200 OK\r\nContent-Length: "), TCP_WRITE_FLAG_MORE);
    tcp_write(tpcb, STR_AND_SIZE(len), TCP_WRITE_FLAG_COPY | TCP_WRITE_FLAG_MORE);
    tcp_write(tpcb, STR_AND_SIZE("\r\nKeep-Alive: timeout=5, max=100\r\nConnection: Keep-Alive\r\nContent-Type: text/html\r\n\r\n"), TCP_WRITE_FLAG_MORE);
    tcp_write(tpcb, STR_AND_SIZE(str), TCP_WRITE_FLAG_COPY); // 发送HTML内容
    pbuf_free(p);
  }
  else
    tcp_close(tpcb);
  return ERR_OK;
}

static err_t http_accept(void *arg, struct tcp_pcb *newpcb, err_t err)
{
  tcp_recv(newpcb, http_recv);
  return ERR_OK;
}

void init_http(void)
{
  struct tcp_pcb *tpcb = tcp_new();
  tcp_bind(tpcb, IP_ADDR_ANY, 80);
  tpcb = tcp_listen(tpcb);
  tcp_accept(tpcb, http_accept);
}


在工程的所在文件夹创建一个lwip文件夹。然后在lwip的官方网站下载lwip-2.0.2.zip,打开压缩包中的lwip-2.0.2/src文件夹,解压以下文件夹到工程的lwip目录下。

core/
core/ipv4
include/lwip
include/netif
netif/ethernet.c
netif/ethernetif.c

解压后,将里面的c文件都添加到工程的lwip分组下。
具体添加的文件请看下图:

【程序】Marvell 88W8686 WiFi模块(WM-G-MR-09)创建或连接热点,并使用lwip2.0.2建立http服务器(20171030版)


接下来,创建lwip/include/arch/cc.h文件,内容如下:

#define PACK_STRUCT_BEGIN __packed // struct前的__packed

创建lwip/include/lwipopts.h文件,内容如下:

#define NO_SYS 1 // 无操作系统

#define LWIP_NETCONN 0
#define LWIP_SOCKET 0
#define LWIP_STATS 0

#define MEM_ALIGNMENT 4 // STM32单片机是32位的单片机, 因此是4字节对齐的

#define SYS_LIGHTWEIGHT_PROT 0 // 不进行临界区保护 (在中断中调用lwip函数时要小心)

打开lwip/netif/ethernetif.c文件,按照下面的中文提示修改代码:

/**
 * @file
 * Ethernet Interface Skeleton
 *
 */

/*
 * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
 * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGE.
 *
 * This file is part of the lwIP TCP/IP stack.
 *
 * Author: Adam Dunkels <adam@sics.se>
 *
 */

/*
 * This file is a skeleton for developing Ethernet network interface
 * drivers for lwIP. Add code to the low_level functions and do a
 * search-and-replace for the word "ethernetif" to replace it with
 * something that better describes your network interface.
 */

#include "lwip/opt.h"

#if 1 // 允许编译器编译该文件

#include "lwip/def.h"
#include "lwip/mem.h"
#include "lwip/pbuf.h"
#include "lwip/stats.h"
#include "lwip/snmp.h"
#include "lwip/ethip6.h"
#include "lwip/etharp.h"
#include "netif/ppp/pppoe.h"

/* Define those to better describe your network interface. */
#define IFNAME0 'e'
#define IFNAME1 'n'

#include <string.h> // memcpy函数所在的头文件
#include "WiFi.h" // WiFi模块驱动程序头文件

/**
 * Helper struct to hold private data used to operate your ethernet interface.
 * Keeping the ethernet address of the MAC in this struct is not necessary
 * as it is already kept in the struct netif.
 * But this is only an example, anyway...
 */
struct ethernetif {
  struct eth_addr *ethaddr;
  /* Add whatever per-interface state that is needed here. */
};

/* Forward declarations. */
// 这里必须去掉static
/*static */void  ethernetif_input(struct netif *netif);

/**
 * In this function, the hardware should be initialized.
 * Called from ethernetif_init().
 *
 * @param netif the already initialized lwip network interface structure
 *        for this ethernetif
 */
static void
low_level_init(struct netif *netif)
{
  struct ethernetif *ethernetif = netif->state;

  /* set MAC hardware address length */
  netif->hwaddr_len = ETHARP_HWADDR_LEN;

  /* set MAC hardware address */
  WiFi_MACAddr(netif->hwaddr, WIFI_ACT_GET); // 获取网卡的默认MAC地址
  printf("MAC Addr: %02X:%02X:%02X:%02X:%02X:%02X\n", netif->hwaddr[0], netif->hwaddr[1], netif->hwaddr[2], netif->hwaddr[3], netif->hwaddr[4], netif->hwaddr[5]);

  /* maximum transfer unit */
  netif->mtu = 1500;

  /* device capabilities */
  /* don't set NETIF_FLAG_ETHARP if this device is not an ethernet one */
  netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_LINK_UP;

#if LWIP_IPV6 && LWIP_IPV6_MLD
  /*
   * For hardware/netifs that implement MAC filtering.
   * All-nodes link-local is handled by default, so we must let the hardware know
   * to allow multicast packets in.
   * Should set mld_mac_filter previously. */
  if (netif->mld_mac_filter != NULL) {
    ip6_addr_t ip6_allnodes_ll;
    ip6_addr_set_allnodes_linklocal(&ip6_allnodes_ll);
    netif->mld_mac_filter(netif, &ip6_allnodes_ll, NETIF_ADD_MAC_FILTER);
  }
#endif /* LWIP_IPV6 && LWIP_IPV6_MLD */

  /* Do whatever else is needed to initialize interface. */
}

/**
 * This function should do the actual transmission of the packet. The packet is
 * contained in the pbuf that is passed to the function. This pbuf
 * might be chained.
 *
 * @param netif the lwip network interface structure for this ethernetif
 * @param p the MAC packet to send (e.g. IP packet including MAC addresses and type)
 * @return ERR_OK if the packet could be sent
 *         an err_t value if the packet couldn't be sent
 *
 * @note Returning ERR_MEM here if a DMA queue of your MAC is full can lead to
 *       strange results. You might consider waiting for space in the DMA queue
 *       to become available since the stack doesn't retry to send a packet
 *       dropped because of memory failure (except for the TCP timers).
 */

static err_t
low_level_output(struct netif *netif, struct pbuf *p)
{
  struct ethernetif *ethernetif = netif->state;
  struct pbuf *q;
  
  // 添加的变量
  uint8_t buffer[1792];
  WiFi_DataTx *packet = (WiFi_DataTx *)buffer;
  uint8_t *bufptr = packet->payload;

  //initiate transfer();

#if ETH_PAD_SIZE
  pbuf_header(p, -ETH_PAD_SIZE); /* drop the padding word */
#endif

  for (q = p; q != NULL; q = q->next) {
    /* Send the data from the pbuf to the interface, one pbuf at a
       time. The size of the data in each pbuf is kept in the ->len
       variable. */
    //send data from(q->payload, q->len);
    memcpy(bufptr, q->payload, q->len); // 复制数据包内容
    bufptr += q->len; // 指针前移
  }

  //signal that packet should be sent();
  WiFi_SendPacket(packet, bufptr - packet->payload, sizeof(buffer)); // 发送数据包
  
  // 打印数据包内容
  printf("[Send] size=%d\n", packet->tx_packet_length);
  //dump_data(packet->payload, packet->tx_packet_length);

  MIB2_STATS_NETIF_ADD(netif, ifoutoctets, p->tot_len);
  if (((u8_t*)p->payload)[0] & 1) {
    /* broadcast or multicast packet*/
    MIB2_STATS_NETIF_INC(netif, ifoutnucastpkts);
  } else {
    /* unicast packet */
    MIB2_STATS_NETIF_INC(netif, ifoutucastpkts);
  }
  /* increase ifoutdiscards or ifouterrors on error */

#if ETH_PAD_SIZE
  pbuf_header(p, ETH_PAD_SIZE); /* reclaim the padding word */
#endif

  LINK_STATS_INC(link.xmit);

  return ERR_OK;
}

/**
 * Should allocate a pbuf and transfer the bytes of the incoming
 * packet from the interface into the pbuf.
 *
 * @param netif the lwip network interface structure for this ethernetif
 * @return a pbuf filled with the received packet (including MAC header)
 *         NULL on memory error
 */
static struct pbuf *
low_level_input(struct netif *netif)
{
  struct ethernetif *ethernetif = netif->state;
  struct pbuf *p, *q;
  u16_t len;
  
  // 添加的变量
  uint8_t buffer[1792]; // 由于WiFi模块本身不支持使用多个CMD53命令读取数据包, 所以必须建立一个缓冲区, 一次性读取完
  WiFi_DataRx *packet = (WiFi_DataRx *)buffer;
  uint8_t *bufptr = packet->payload;

  /* Obtain the size of the packet and put it into the "len"
     variable. */
  // 读取整个数据包
  if (!WiFi_ReceivePacket(buffer, sizeof(buffer)))
  {
    // 若读取失败, 则不分配pbuf, 退出本函数
    p = NULL;
    goto after_alloc;
  }
  len = packet->rx_packet_length; // 获取数据包的大小
  
  // 打印数据包内容
  printf("[Recv] size=%d\n", len);
  //dump_data(packet->payload, len);

#if ETH_PAD_SIZE
  len += ETH_PAD_SIZE; /* allow room for Ethernet padding */
#endif

  /* We allocate a pbuf chain of pbufs from the pool. */
  p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL);
  
after_alloc: // 添加此标记

  if (p != NULL) {

#if ETH_PAD_SIZE
    pbuf_header(p, -ETH_PAD_SIZE); /* drop the padding word */
#endif

    /* We iterate over the pbuf chain until we have read the entire
     * packet into the pbuf. */
    for (q = p; q != NULL; q = q->next) {
      /* Read enough bytes to fill this pbuf in the chain. The
       * available data in the pbuf is given by the q->len
       * variable.
       * This does not necessarily have to be a memcpy, you can also preallocate
       * pbufs for a DMA-enabled MAC and after receiving truncate it to the
       * actually received size. In this case, ensure the tot_len member of the
       * pbuf is the sum of the chained pbuf len members.
       */
      //read data into(q->payload, q->len);
      memcpy(q->payload, bufptr, q->len); // 复制数据包内容
      bufptr += q->len; // 指针前移
    }
    //acknowledge that packet has been read(); // 无需确认

    MIB2_STATS_NETIF_ADD(netif, ifinoctets, p->tot_len);
    if (((u8_t*)p->payload)[0] & 1) {
      /* broadcast or multicast packet*/
      MIB2_STATS_NETIF_INC(netif, ifinnucastpkts);
    } else {
      /* unicast packet*/
      MIB2_STATS_NETIF_INC(netif, ifinucastpkts);
    }
#if ETH_PAD_SIZE
    pbuf_header(p, ETH_PAD_SIZE); /* reclaim the padding word */
#endif

    LINK_STATS_INC(link.recv);
  } else {
    //drop packet(); // 注释掉此行
    LINK_STATS_INC(link.memerr);
    LINK_STATS_INC(link.drop);
    MIB2_STATS_NETIF_INC(netif, ifindiscards);
  }

  return p;
}

/**
 * This function should be called when a packet is ready to be read
 * from the interface. It uses the function low_level_input() that
 * should handle the actual reception of bytes from the network
 * interface. Then the type of the received packet is determined and
 * the appropriate input function is called.
 *
 * @param netif the lwip network interface structure for this ethernetif
 */
// 必须去掉static
/*static */void
ethernetif_input(struct netif *netif)
{
  struct ethernetif *ethernetif;
  struct eth_hdr *ethhdr;
  struct pbuf *p;

  ethernetif = netif->state;

  /* move received packet into a new pbuf */
  p = low_level_input(netif);
  /* if no packet could be read, silently ignore this */
  if (p != NULL) {
    /* pass all packets to ethernet_input, which decides what packets it supports */
    if (netif->input(p, netif) != ERR_OK) {
      LWIP_DEBUGF(NETIF_DEBUG, ("ethernetif_input: IP input error\n"));
      pbuf_free(p);
      p = NULL;
    }
  }
}

/**
 * Should be called at the beginning of the program to set up the
 * network interface. It calls the function low_level_init() to do the
 * actual setup of the hardware.
 *
 * This function should be passed as a parameter to netif_add().
 *
 * @param netif the lwip network interface structure for this ethernetif
 * @return ERR_OK if the loopif is initialized
 *         ERR_MEM if private data couldn't be allocated
 *         any other err_t on error
 */
err_t
ethernetif_init(struct netif *netif)
{
  struct ethernetif *ethernetif;

  LWIP_ASSERT("netif != NULL", (netif != NULL));

  ethernetif = mem_malloc(sizeof(struct ethernetif));
  if (ethernetif == NULL) {
    LWIP_DEBUGF(NETIF_DEBUG, ("ethernetif_init: out of memory\n"));
    return ERR_MEM;
  }

#if LWIP_NETIF_HOSTNAME
  /* Initialize interface hostname */
  netif->hostname = "lwip";
#endif /* LWIP_NETIF_HOSTNAME */

  /*
   * Initialize the snmp variables and counters inside the struct netif.
   * The last argument should be replaced with your link speed, in units
   * of bits per second.
   */
  MIB2_INIT_NETIF(netif, snmp_ifType_ethernet_csmacd, LINK_SPEED_OF_YOUR_NETIF_IN_BPS);

  netif->state = ethernetif;
  netif->name[0] = IFNAME0;
  netif->name[1] = IFNAME1;
  /* We directly use etharp_output() here to save a function call.
   * You can instead declare your own function an call etharp_output()
   * from it if you have to do some checks before sending (e.g. if link
   * is available...) */
  netif->output = etharp_output;
#if LWIP_IPV6
  netif->output_ip6 = ethip6_output;
#endif /* LWIP_IPV6 */
  netif->linkoutput = low_level_output;

  ethernetif->ethaddr = (struct eth_addr *)&(netif->hwaddr[0]);

  /* initialize the hardware */
  low_level_init(netif);

  return ERR_OK;
}

#endif /* 0 */


在项目属性中,设置Include Paths为:.;.\lwip\include

其中点表示工程根目录。
【程序】Marvell 88W8686 WiFi模块(WM-G-MR-09)创建或连接热点,并使用lwip2.0.2建立http服务器(20171030版)

另外,为了在程序中使用printf函数,Target选项卡下的Use MicroLIB复选框也要勾选上。


编译并下载程序。

【电脑连接WiFi模块创建的带WEP密码的热点】

WiFi_SetWEP(WIFI_ACT_ADD, "Hello, World!"); // 设置热点WEP密码 (若不想设置密码请删除掉这句话)
WiFi_StartADHOC("Marvell 88W8686"); // 创建Ad-Hoc热点
【程序】Marvell 88W8686 WiFi模块(WM-G-MR-09)创建或连接热点,并使用lwip2.0.2建立http服务器(20171030版) 【程序】Marvell 88W8686 WiFi模块(WM-G-MR-09)创建或连接热点,并使用lwip2.0.2建立http服务器(20171030版)

此时电脑的IP地址要填写192.168.43.1,子网掩码为255.255.255.0,网关为空(电脑作网关)。

【程序】Marvell 88W8686 WiFi模块(WM-G-MR-09)创建或连接热点,并使用lwip2.0.2建立http服务器(20171030版)

此时可用浏览器访问WiFi模块上的网页:

【程序】Marvell 88W8686 WiFi模块(WM-G-MR-09)创建或连接热点,并使用lwip2.0.2建立http服务器(20171030版)

程序运行结果:

Command response received: CMD63, RESP_90ff8000
Command response received: CMD63, RESP_90300000
Number of I/O Functions: 1
Memory Present: 0
Relative card address: 0x0001
Card selected! status=0x00001e00
SDIO Clock: 24MHz
-------------- CIS of Function 0 ----------------
Pointer to Function 0 Card Information Structure (CIS): 0x00008000
[CIS Tuple 0x15] addr=0x00008000 size=31
01004D617276656C6C003830322E3131205344494F2049443A2030420000FF
Marvell
802.11 SDIO ID: 0B
[CIS Tuple 0x20] addr=0x00008021 size=4
DF020391
SDIO Card manufacturer code: 0x02df
manufacturer information (Part Number and/or Revision): 0x9103
[CIS Tuple 0x21] addr=0x00008027 size=2
0C00
Card function code: 0x0c
System initialization bit mask: 0x00
[CIS Tuple 0x22] addr=0x0000802b size=4
00000132
maximum block size: 256
maximum transfer rate code: 0x32
--------------------------------------------------
-------------- CIS of Function 1 ----------------
Pointer to Function 1 Card Information Structure (CIS): 0x00008080
[CIS Tuple 0x21] addr=0x00008080 size=2
0C00
Card function code: 0x0c
System initialization bit mask: 0x00
[CIS Tuple 0x22] addr=0x00008084 size=28
01011000000000000000000000010000000000000000000000000000
maximum block size: 256
--------------------------------------------------
Firmware is successfully downloaded!
WiFi_Wait(0x0a): timeout!
Resend WiFi command 0x0028! size=16
SSID 'Marvell 88W8686', MAC 02:22:E2:81:E9:80, RSSI 50, Channel 1
  Capability: 0x0012 (Security: WEP, Mode: Ad-Hoc)
SSID 'TP-LINK_ORANGE', MAC B0:95:8E:05:82:CA, RSSI 56, Channel 1
  Capability: 0x0431 (Security: WPA2, Mode: Infrastructure)
SSID 'CDU_Free', MAC D4:61:FE:71:36:D0, RSSI 72, Channel 6
  Capability: 0x8421 (Security: unsecured, Mode: Infrastructure)
SSID 'CDU', MAC D4:61:FE:71:36:D1, RSSI 71, Channel 6
  Capability: 0x0431 (Security: WPA2, Mode: Infrastructure)
SSID 'TP-LINK_PLC', MAC 30:FC:68:38:6E:2C, RSSI 72, Channel 6
  Capability: 0x0431 (Security: WPA2, Mode: Infrastructure)
SSID '10507', MAC B0:95:8E:04:2A:06, RSSI 49, Channel 11
  Capability: 0x0431 (Security: WPA2, Mode: Infrastructure)
SSID 'CMCC-Young', MAC D6:14:4B:6F:A6:0F, RSSI 68, Channel 11
  Capability: 0x0621 (Security: unsecured, Mode: Infrastructure)
SSID 'CMCC-EDU', MAC D6:14:4B:6F:A6:0E, RSSI 68, Channel 11
  Capability: 0x0621 (Security: unsecured, Mode: Infrastructure)
MAC Addr: 00:1A:6B:A4:AA:B4
[Send] size=42
[Event 30] size=8
[Event 4] size=8
The number of stations in this ad hoc newtork has changed!
[Event 17] size=8
All other stations have been away from this ad hoc network!
[Event 4] size=8
The number of stations in this ad hoc newtork has changed!
[Recv] size=42
[Recv] size=42
[Recv] size=42
[Recv] size=42
[Recv] size=42
[Recv] size=42
[Recv] size=42
[Recv] size=42
[Recv] size=42
[Recv] size=42
[Recv] size=42
[Recv] size=42
[Event 17] size=8
All other stations have been away from this ad hoc network!
[Event 4] size=8
The number of stations in this ad hoc newtork has changed!
[Event 17] size=8
All other stations have been away from this ad hoc network!
[Recv] size=42
[Event 4] size=8
The number of stations in this ad hoc newtork has changed!
[Recv] size=42
[Recv] size=42
[Event 17] size=8
All other stations have been away from this ad hoc network!
[Recv] size=42
[Recv] size=42
[Event 4] size=8
The number of stations in this ad hoc newtork has changed!
[Recv] size=42
[Recv] size=42
[Recv] size=42
[Recv] size=42
[Recv] size=42
【电脑和WiFi模块同时连接手机开的热点】

WiFi_Connect("vivo Y29L", BSS_INFRASTRUCTURE); // 连接手机开的没有密码的AP热点

此时开热点的手机为网关,电脑的IP地址为自动获取。用电脑查看一下获取到的IP地址和网关,确保网关和程序里面设置的是一致的。

笔者的手机IP地址为192.168.43.1,因此程序里面网关就写的是192.168.43.1,IP地址前三个要一样,最后一个数随便写。

配置完成后,即可用电脑或手机上的浏览器访问开发板上面的HTTP服务器。

【程序】Marvell 88W8686 WiFi模块(WM-G-MR-09)创建或连接热点,并使用lwip2.0.2建立http服务器(20171030版)

程序运行结果:

Command response received: CMD63, RESP_90ff8000
Command response received: CMD63, RESP_90300000
Number of I/O Functions: 1
Memory Present: 0
Relative card address: 0x0001
Card selected! status=0x00001e00
SDIO Clock: 24MHz
-------------- CIS of Function 0 ----------------
Pointer to Function 0 Card Information Structure (CIS): 0x00008000
[CIS Tuple 0x15] addr=0x00008000 size=31
01004D617276656C6C003830322E3131205344494F2049443A2030420000FF
Marvell
802.11 SDIO ID: 0B
[CIS Tuple 0x20] addr=0x00008021 size=4
DF020391
SDIO Card manufacturer code: 0x02df
manufacturer information (Part Number and/or Revision): 0x9103
[CIS Tuple 0x21] addr=0x00008027 size=2
0C00
Card function code: 0x0c
System initialization bit mask: 0x00
[CIS Tuple 0x22] addr=0x0000802b size=4
00000132
maximum block size: 256
maximum transfer rate code: 0x32
--------------------------------------------------
-------------- CIS of Function 1 ----------------
Pointer to Function 1 Card Information Structure (CIS): 0x00008080
[CIS Tuple 0x21] addr=0x00008080 size=2
0C00
Card function code: 0x0c
System initialization bit mask: 0x00
[CIS Tuple 0x22] addr=0x00008084 size=28
01011000000000000000000000010000000000000000000000000000
maximum block size: 256
--------------------------------------------------
Firmware is successfully downloaded!
WiFi_Wait(0x0a): timeout!
Resend WiFi command 0x0028! size=16
SSID 'CMCC-Young', MAC E6:14:4B:57:40:00, RSSI 75, Channel 1
  Capability: 0x0621 (Security: unsecured, Mode: Infrastructure)
SSID 'TP-LINK_ORANGE', MAC B0:95:8E:05:82:CA, RSSI 50, Channel 1
  Capability: 0x0431 (Security: WPA2, Mode: Infrastructure)
SSID 'CMCC-EDU', MAC E6:14:4B:57:40:0F, RSSI 74, Channel 1
  Capability: 0x0621 (Security: unsecured, Mode: Infrastructure)
SSID 'CMCC-EDU', MAC 96:14:4B:6F:A5:DA, RSSI 73, Channel 1
  Capability: 0x0621 (Security: unsecured, Mode: Infrastructure)
SSID 'diansai505', MAC 50:FA:84:53:5B:8E, RSSI 75, Channel 2
  Capability: 0x0411 (Security: WPA2, Mode: Infrastructure)
SSID 'vivo Y29L', MAC F4:29:81:98:F3:78, RSSI 47, Channel 6
  Capability: 0x8421 (Security: unsecured, Mode: Infrastructure)
SSID 'TP-LINK_PLC', MAC 30:FC:68:38:6E:2C, RSSI 76, Channel 6
  Capability: 0x0431 (Security: WPA2, Mode: Infrastructure)
SSID 'xgxy666', MAC DC:FE:18:67:76:14, RSSI 73, Channel 6
  Capability: 0x0431 (Security: WPA2, Mode: Infrastructure)
SSID 'CDU_Free', MAC D4:61:FE:71:36:D0, RSSI 75, Channel 6
  Capability: 0x8421 (Security: unsecured, Mode: Infrastructure)
SSID 'CDU', MAC D4:61:FE:71:36:D1, RSSI 76, Channel 6
  Capability: 0x0431 (Security: WPA2, Mode: Infrastructure)
SSID 'CMCC-EDU', MAC D6:14:4B:6F:A6:0E, RSSI 69, Channel 11
  Capability: 0x0621 (Security: unsecured, Mode: Infrastructure)
SSID '10507', MAC B0:95:8E:04:2A:06, RSSI 47, Channel 11
  Capability: 0x0431 (Security: WPA2, Mode: Infrastructure)
SSID 'CMCC-Young', MAC D6:14:4B:6F:A6:0F, RSSI 69, Channel 11
  Capability: 0x0621 (Security: unsecured, Mode: Infrastructure)
Connected to vivo Y29L!
MAC Addr: 00:1A:6B:A4:AA:B4
[Send] size=42
[Recv] size=42
[Send] size=42
[Recv] size=74
[Send] size=58
[Recv] size=74
[Send] size=58
[Recv] size=74
[Send] size=58
[Recv] size=54
[Recv] size=522
[Send] size=322
[Recv] size=54
[Recv] size=54
[Recv] size=54
[Recv] size=368
[Send] size=303
[Recv] size=54
[Recv] size=502
[Send] size=42
[Recv] size=502
[Recv] size=502
[Recv] size=42
[Send] size=54
[Send] size=322
[Recv] size=54
[Recv] size=368
[Send] size=303
[Recv] size=54
[Recv] size=502
[Send] size=322
[Recv] size=54
[Recv] size=368
[Send] size=42
[Recv] size=368
[Recv] size=368
[Recv] size=522
[Recv] size=42
[Send] size=54
[Recv] size=522
[Send] size=54
[Send] size=322
[Recv] size=54
[Send] size=303
[Recv] size=54
[Recv] size=368
[Send] size=42
[Recv] size=368
[Recv] size=42
[Send] size=54
[Recv] size=54
[Send] size=54
[Recv] size=54
[Send] size=42
[Recv] size=42
[Send] size=303
[Recv] size=54
[Event 8] size=16, reason=3, MAC: F4:29:81:98:F3:78
Deauthenticated!
此外,程序还可以连接电脑创建的带有WEP密码的ad hoc热点。


WiFi.h中的两个重要选项:
//#define WIFI_HIGHSPEED // 采用SDIO高速模式
//#define WIFI_USEDMA // SDIO采用DMA方式收发数据(高速模式必须采用DMA)
在main.c中,WiFi_Scan()扫描并显示附近的WiFi热点信息,可注释掉。WiFi.c中的WiFi_Init函数中调用的WiFi_ShowCIS函数用于显示WiFi模块的SDIO信息,也可以注释掉,减少屏幕显示内容。


以下为STM32标准库函数版本的程序。
【WiFi.h(库函数版)】
把寄存器版中的WiFi_GetSDIOBlockSize的定义改为:
#define WiFi_GetSDIOBlockSize() _BV(sdio_data.SDIO_DataBlockSize >> 4)
【main.c(库函数版)】
#include <stdio.h>
#include <stm32f10x.h>
#include "lwip/init.h" // lwip_init函数所在的头文件
#include "lwip/timeouts.h" // sys_check_timeouts函数所在的头文件
#include "netif/ethernet.h" // ethernet_input函数所在头文件
#include "WiFi.h"

// 这两个函数位于ethernetif.c中, 但没有头文件声明
err_t ethernetif_init(struct netif *netif);
void ethernetif_input(struct netif *netif);

// 一个很简单的http服务器
void init_http(void);

// printf函数的实现, 必须在项目属性中勾选Use MicroLIB后才能用
int fputc(int ch, FILE *fp)
{
  if (fp == stdout)
  {
    if (ch == '\n')
    {
      while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
      USART_SendData(USART1, '\r');
    }
    while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
    USART_SendData(USART1, ch);
  }
  return ch;
}

// RTC时间转化为毫秒数
uint32_t sys_now(void)
{
  uint32_t sec = RTC_GetCounter(); // 秒
  uint32_t milli = (39999 - RTC_GetDivider()) * 1000 / 40000; // 毫秒 (DIV寄存器的范围是0~PRL-1)
  return sec * 1000 + milli;
}

// WiFi事件回调函数
void WiFi_EventHandler(const WiFi_Event *event)
{
  printf("[Event %d] size=%d", event->event_id, event->header.length);
  if (event->header.length >= sizeof(WiFi_Event) - sizeof(event->mac_addr))
    printf(", reason=%d", event->reason_code);
  if (event->header.length >= sizeof(WiFi_Event))
    printf(", MAC: %02X:%02X:%02X:%02X:%02X:%02X", event->mac_addr[0], event->mac_addr[1], event->mac_addr[2], event->mac_addr[3], event->mac_addr[4], event->mac_addr[5]);
  printf("\n");
  
  switch (event->event_id)
  {
    case 3:
      // 收不到信号 (例如和手机热点建立连接后, 把手机拿走), WiFi模块不会自动重连
      printf("Beacon Loss/Link Loss\n");
      break;
    case 4:
      // Ad-Hoc网络中不止1个结点, 且连接数发生了变化
      printf("The number of stations in this ad hoc newtork has changed!\n");
      break;
    case 8:
      // 认证已解除 (例如手机关闭了热点, 或者是连接路由器后一直没有输入密码而自动断开连接)
      printf("Deauthenticated!\n");
      break;
    case 9:
      printf("Disassociated!\n");
      break;
    case 17:
      // Ad-Hoc网络中只剩本结点
      printf("All other stations have been away from this ad hoc network!\n");
      break;
  }
  
  if (event->header.length > sizeof(WiFi_Event))
    dump_data(event + 1, event->header.length - sizeof(WiFi_Event));
}

int main(void)
{
  struct ip4_addr ipaddr, netmask, gw;
  struct netif wifi_88w8686;
  uint32_t last_check = 0;
  
  GPIO_InitTypeDef gpio;
  USART_InitTypeDef usart;
  
  // HCLK=PCLK2=72MHz, PCLK1=36MHz
  RCC_AHBPeriphClockCmd(RCC_AHBPeriph_SDIO, ENABLE); // 打开SDIO外设的时钟
#ifdef WIFI_USEDMA
  RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA2, ENABLE);
#endif
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_TIM6, ENABLE); // TIM6为延时用的定时器
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOD | RCC_APB2Periph_USART1, ENABLE);
  
  // 串口1发送引脚PA9设为复用推挽50MHz输出, 接收引脚PA10为默认的浮空输入
  gpio.GPIO_Mode = GPIO_Mode_AF_PP;
  gpio.GPIO_Pin = GPIO_Pin_9;
  gpio.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GPIOA, &gpio);
  
  // PB12设为推挽50MHz输出
  // WiFi模块的VCC引脚不可直接连接电源, 必须要串联一个PNP型三极管, 并把基极接到PB12上, 否则STM32复位时WiFi模块无法自动复位
  // 复位时PB12输出高阻态, 三极管截止, WiFi模块断电, 配置完CRH寄存器后立即输出低电平, WiFi模块通电
  gpio.GPIO_Mode = GPIO_Mode_Out_PP;
  gpio.GPIO_Pin = GPIO_Pin_12;
  GPIO_Init(GPIOB, &gpio);
  
  // PC8~11为SDIO的4位数据引脚, PC12为SDIO时钟引脚, 设为复用推挽50MHz输出
  gpio.GPIO_Mode = GPIO_Mode_AF_PP;
  gpio.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_11 | GPIO_Pin_12;
  GPIO_Init(GPIOC, &gpio);
  
  // PD2为SDIO命令引脚, 设为复用推挽50MHz输出
  gpio.GPIO_Pin = GPIO_Pin_2;
  GPIO_Init(GPIOD, &gpio);
  
  // 打开串口1, 允许发送和接收
  USART_StructInit(&usart);
  usart.USART_BaudRate = 115200; // 波特率
  USART_Init(USART1, &usart);
  USART_Cmd(USART1, ENABLE);
  
  PWR_BackupAccessCmd(ENABLE); // 允许写后备寄存器(如RCC->BDCR)
  RCC_LSICmd(ENABLE); // 因为板上没有LSE晶振, 所以RTC时钟选LSI
  while (RCC_GetFlagStatus(RCC_FLAG_LSIRDY) == RESET); // 等待LSI启动
  
  // 若RTC未打开, 则初始化RTC
  if ((RCC->BDCR & RCC_BDCR_RTCEN) == 0) // 没有库函数可以判断RTC是否已打开. 只能使用寄存器
  {
    // 必须要先选择时钟, 然后再开启RTC时钟
    RCC_RTCCLKConfig(RCC_RTCCLKSource_LSI); // 选LSI作为RTC时钟
    RCC_RTCCLKCmd(ENABLE); // 开启RTC时钟, RTC开始走时
    
    RTC_SetPrescaler(39999); // 设置分频系数: LSI晶振的频率是40kHz, 定时1s
    RTC_WaitForLastTask(); // 等待设置生效
    //RTC_SetCounter(50); // 设置初始时间; STM32F1系列的RTC没有年月日、小时等寄存器, 只有一个32位的计数器, 要想实现日期和时间的功能必须调用C库<time.h>中的mktime函数, 用软件来实现这些功能
    //RTC_WaitForLastTask();
  }
  else
    RTC_WaitForSynchro(); // 等待RTC与APB1时钟同步
  
  WiFi_Init();
  WiFi_Scan(); // 显示附近的热点信息 (可以注释掉)
  
  WiFi_SetWEP(WIFI_ACT_ADD, "Hello, World!"); // 设置热点WEP密码 (若不想设置密码请删除掉这句话)
  WiFi_StartADHOC("Marvell 88W8686"); // 创建Ad-Hoc热点
  
  //WiFi_SetWEP(WIFI_ACT_ADD, "48656c6c6f2c20576f726c6421"); // 设置热点WEP密码(第二种方式): Hello, World!
  //WiFi_Connect("Octopus-WEP", BSS_INDEPENDENT); // 连接Ad-Hoc热点 (Win7电脑可以创建WEP加密的Ad-Hoc型热点)
  
  //WiFi_Connect("10507", BSS_INFRASTRUCTURE); // 连接路由器建立的AP热点
  
  //WiFi_Connect("vivo Y29L", BSS_INFRASTRUCTURE); // 连接手机开的没有密码的AP热点
  
  lwip_init();
  IP4_ADDR(&ipaddr, 192, 168, 43, 10); // IP地址
  IP4_ADDR(&netmask, 255, 255, 255, 0); // 子网掩码
  IP4_ADDR(&gw, 192, 168, 43, 1); // 网关
  
  netif_add(&wifi_88w8686, &ipaddr, &netmask, &gw, NULL, ethernetif_init, ethernet_input);
  netif_set_default(&wifi_88w8686); // 设为默认网卡
  netif_set_up(&wifi_88w8686);
  
  init_http();
  while (1)
  {
    if (WiFi_PacketArrived())
      ethernetif_input(&wifi_88w8686);
    
    // sys_check_timeouts函数千万不能调用的太频繁, 否则会出错!(例如开机后要等1~2分钟DHCP才能分配到IP地址)
    if (sys_now() - last_check > 200)
    {
      last_check = sys_now();
      sys_check_timeouts();
    }
  }
}
【WiFi.c(库函数版)】
#include <stdio.h>
#include <stm32f10x.h>
#include <string.h>
#include "WiFi.h"

extern const unsigned char firmware_helper_sd[2516];
extern const unsigned char firmware_sd8686[122916];

//const uint8_t wifi_mac_addr[] = {0x62, 0x1d, 0x2f, 0x00, 0x4e, 0x2d}; // MAC地址的第一个字节必须为偶数! 否则为多播地址
static uint16_t wifi_macctrl = WIFI_MACCTRL_ETHERNET2 | WIFI_MACCTRL_TX | WIFI_MACCTRL_RX;
static uint16_t wifi_timeout = WIFI_DEFTIMEOUT;
static uint32_t io_addr;
static SDIO_CmdInitTypeDef sdio_cmd;
static SDIO_DataInitTypeDef sdio_data;

// 默认情况下startup_stm32f10x_hd.s设定的栈大小Stack_Size为0x400, 函数内最多只能创建1KB的变量
// 为了创建大缓冲区, 栈大小应该改大, 比如0x4000, 即16KB

// 延时n毫秒
void delay(uint16_t nms)
{
  timeout(nms);
  while (TIM_GetFlagStatus(TIM6, TIM_FLAG_Update) == RESET); // 等待定时器溢出
  TIM_ClearFlag(TIM6, TIM_FLAG_Update); // 清除溢出标志
}

// 显示数据内容
void dump_data(const void *data, uint32_t len)
{
  const uint8_t *p = data;
  while (len--)
    printf("%02X", *p++);
  printf("\n");
}

// 启动定时器, 超时时间设为n毫秒
void timeout(uint16_t nms)
{
  TIM_TimeBaseInitTypeDef tim;
  TIM_SelectOnePulseMode(TIM6, TIM_OPMode_Single); // UIF置位后自动关闭定时器
  TIM_UpdateRequestConfig(TIM6, TIM_UpdateSource_Regular); // 初始化时保持UIF=0
  tim.TIM_ClockDivision = TIM_CKD_DIV1;
  tim.TIM_CounterMode = TIM_CounterMode_Up;
  tim.TIM_Period = 10 * nms - 1;
  tim.TIM_Prescaler = 7199; // APB1总线上的定时器时钟为72MHz
  TIM_TimeBaseInit(TIM6, &tim);
  TIM_Cmd(TIM6, ENABLE); // 开始计时
}

/* 关联一个热点 */
uint16_t WiFi_Associate(const char *ssid)
{
  uint8_t buffer[2048];
  WiFi_SSIDInfo info;
  WiFi_CmdRequest_Associate *cmd = (WiFi_CmdRequest_Associate *)buffer;
  WiFi_CmdResponse_Associate *resp = (WiFi_CmdResponse_Associate *)buffer;
  MrvlIETypes_PhyParamDSSet_t *ds;
  MrvlIETypes_CfParamSet_t *cf;
  MrvlIETypes_AuthType_t *auth;
  MrvlIETypes_RsnParamSet_t *rsn;

  if (!WiFi_ScanSSID(ssid, &info, buffer, sizeof(buffer)))
  {
    printf("Cannot find AP: %s!\n", ssid);
    return WIFI_ASSOCIATION_NOTFOUND;
  }
  
  memcpy(cmd->peer_sta_addr, info.mac_addr, sizeof(info.mac_addr));
  cmd->cap_info = info.cap_info;
  cmd->listen_interval = 10;
  cmd->bcn_period = info.bcn_period;
  cmd->dtim_period = 1;
  memcpy(cmd + 1, &info.ssid, TLV_STRUCTLEN(info.ssid));
  
  ds = (MrvlIETypes_PhyParamDSSet_t *)((uint8_t *)(cmd + 1) + TLV_STRUCTLEN(info.ssid));
  ds->header.type = MRVLIETYPES_DSPARAMSET;
  ds->header.length = 1;
  ds->channel = info.channel;
  
  cf = (MrvlIETypes_CfParamSet_t *)(ds + 1);
  memset(cf, 0, sizeof(MrvlIETypes_CfParamSet_t));
  cf->header.type = MRVLIETYPES_CFPARAMSET;
  cf->header.length = TLV_PAYLOADLEN(*cf);
  
  memcpy(cf + 1, &info.rates, TLV_STRUCTLEN(info.rates));
  auth = (MrvlIETypes_AuthType_t *)((uint8_t *)(cf + 1) + TLV_STRUCTLEN(info.rates));
  auth->header.type = MRVLIETYPES_AUTHTYPE;
  auth->header.length = TLV_PAYLOADLEN(*auth);
  auth->auth_type = AUTH_MODE_OPEN;
  
  rsn = (MrvlIETypes_RsnParamSet_t *)(auth + 1);
  if (info.rsn.header.type)
  {
    // WPA2网络必须在命令中加入RSN参数才能成功连接
    memcpy(rsn, &info.rsn, TLV_STRUCTLEN(info.rsn));
    WiFi_SendCommand(CMD_802_11_ASSOCIATE, buffer, (uint8_t *)rsn - buffer + TLV_STRUCTLEN(info.rsn), sizeof(buffer));
  }
  else
    WiFi_SendCommand(CMD_802_11_ASSOCIATE, buffer, (uint8_t *)rsn - buffer, sizeof(buffer)); // 其余网络不需要RSN参数
  
  if (!WiFi_ReceiveResponse(buffer, sizeof(buffer)))
  {
    printf("Association with %s failed!\n", ssid);
    return WIFI_ASSOCIATION_ERROR;
  }
  
  //printf("capability=0x%04x, status_code=0x%04x, aid=0x%04x\n", resp->capability, resp->status_code, resp->association_id);
  if (resp->association_id == 0xffff)
    return ((-resp->capability) << 8) | resp->status_code;
  return WIFI_ASSOCIATION_SUCCESS;
}

/* 连接WiFi热点, 超时自动重连 */
uint16_t WiFi_Connect(const char *ssid, uint8_t type)
{
  uint16_t ret;
  do
  {
    if (type == BSS_INDEPENDENT) // 连接Ad-Hoc型热点
      ret = WiFi_JoinADHOC(ssid);
    else if (type == BSS_INFRASTRUCTURE) // 连接普通热点
      ret = WiFi_Associate(ssid);
    else
    {
      printf("WiFi_Connect: incorrect network type!\n");
      return WIFI_ASSOCIATION_ERROR;
    }
    if (ret != WIFI_ASSOCIATION_SUCCESS)
    {
      printf("%s returned 0x%04x\n", (type == BSS_INDEPENDENT) ? "WiFi_JoinADHOC" : "WiFi_Associate", ret);
      delay(2000); // 等待一段时间后重连
    }
  } while (WIFI_ASSOCIATION_TIMEOUT(ret) || ret == WIFI_ASSOCIATION_NOTFOUND); // 若连接超时, 或未扫描到热点, 则重连
  
  if (ret != WIFI_ASSOCIATION_SUCCESS)
    return ret;
  
  printf("Connected to %s!\n", ssid);
  return ret;
}

/* 下载固件 */
// 参考文档: marvell-88w8686-固件下载程序说明.doc
uint8_t WiFi_DownloadFirmware(void)
{
  uint8_t helper_buf[64];
  const uint8_t *data;
  uint16_t size;
  uint32_t len;
  
  // 块大小设为32
  sdio_data.SDIO_DataBlockSize = SDIO_DataBlockSize_32b;
  WiFi_SetBlockSize(1); // 应用到Function 1
  
  // 下载helper
  io_addr = WiFi_Read(1, WIFI_IOPORT0) | (WiFi_Read(1, WIFI_IOPORT1) << 8) | (WiFi_Read(1, WIFI_IOPORT2) << 16);
  data = firmware_helper_sd;
  len = sizeof(firmware_helper_sd);
  while (len)
  {
    // 每次下载64字节, 其中前4字节为本次下载的数据量
    size = (len > 60) ? 60 : len;
    *(uint32_t *)helper_buf = size;
    memcpy(helper_buf + 4, data, size);
    
    WiFi_Wait(WIFI_CARDSTATUS_DNLDCARDRDY);
    WiFi_WritePort(helper_buf, sizeof(helper_buf), sizeof(helper_buf));
    len -= size;
    data += size;
  }
  *(uint32_t *)helper_buf = 0;
  WiFi_WritePort(helper_buf, sizeof(helper_buf), sizeof(helper_buf)); // 以空数据包结束
  
  // 下载固件
  data = firmware_sd8686;
  len = sizeof(firmware_sd8686);
  while (len)
  {
    WiFi_Wait(WIFI_CARDSTATUS_DNLDCARDRDY);
    while ((size = WiFi_Read(1, WIFI_SQREADBASEADDR0) | (WiFi_Read(1, WIFI_SQREADBASEADDR1) << 8)) == 0); // 获取本次下载的字节数
    //printf("Required: %d bytes, Remaining: %d bytes\n", size, len);
    
    if (size & 1)
    {
      // 若size为奇数(如17), 则说明接收端有CRC校验错误, 应重新传送上一次的内容(这部分代码省略)
      printf("Error: an odd size is invalid!\n");
      return 0;
    }
    if (size > len)
      size = len;
    
    // len为缓冲区剩余大小
#ifdef WIFI_HIGHSPEED // 高速模式下不能使用多字节传输模式, 只能使用块传输模式, 因此缓冲区要足够大
    if (len < 32)
    {
      // 若缓冲区空间不足一个数据块, 则借用helper_buf
      memcpy(helper_buf, data, size);
      WiFi_WritePort(helper_buf, size, sizeof(helper_buf));
    }
    else
#endif
      WiFi_WritePort(data, size, len);
    
    if (!SDIO_SUCCEEDED())
    {
      printf("Data transfer error! SDIO->STA=0x%08x\n", SDIO->STA);
      return 0;
    }
    
    len -= size;
    data += size;
  }
  
  // 等待Firmware启动
  while (WiFi_GetPacketLength() == 0xfedc);
  printf("Firmware is successfully downloaded!\n");
  return 1;
}

/* 获取数据帧大小 */
uint16_t WiFi_GetPacketLength(void)
{
  // 读Scratch pad 4寄存器的低16位
  return WiFi_Read(1, WIFI_SCRATCHPAD4_0) | (WiFi_Read(1, WIFI_SCRATCHPAD4_1) << 8);
}

/* 初始化WiFi模块 */
// SDIO Simplified Specification Version 3.00: 3. SDIO Card Initialization
void WiFi_Init(void)
{
  uint16_t rca; // 虽然SDIO标准规定SDIO接口上可以接多张SD卡, 但是STM32的SDIO接口只能接一张卡(芯片手册上有说明), 软件片选RCA无意义
  uint32_t resp;
  SDIO_InitTypeDef sdio;
  
  SDIO_SetPowerState(SDIO_PowerState_ON); // 打开SDIO外设
  SDIO_StructInit(&sdio);
  sdio.SDIO_BusWide = SDIO_BusWide_4b;
  sdio.SDIO_ClockDiv = 178; // 初始化时最高允许的频率: 72MHz/(178+2)=400kHz
  SDIO_Init(&sdio);
  SDIO_ClockCmd(ENABLE);
  SDIO_SetSDIOOperation(ENABLE); // 开启SDIO模式
  delay(10); // 延时可防止CMD5重发
  
  // 不需要发送CMD0, 因为SD I/O card的初始化命令是CMD52
  // An I/O only card or the I/O portion of a combo card is NOT reset by CMD0. (See 4.4 Reset for SDIO)
  
  /* 发送CMD5: IO_SEND_OP_COND */
  sdio_cmd.SDIO_Argument = 0;
  sdio_cmd.SDIO_CmdIndex = 5;
  sdio_cmd.SDIO_CPSM = SDIO_CPSM_Enable;
  sdio_cmd.SDIO_Response = SDIO_Response_Short;
  sdio_cmd.SDIO_Wait = SDIO_Wait_No;
  SDIO_SendCommand(&sdio_cmd);
  while (SDIO_GetFlagStatus(SDIO_FLAG_CMDACT) == SET);
  while (SDIO_GetFlagStatus(SDIO_FLAG_CTIMEOUT) == SET)
  {
    // 为了保险起见还是要检查一下是否要重发命令
    SDIO_ClearFlag(SDIO_FLAG_CTIMEOUT); // 清除标志
    printf("Timeout! Resend CMD%d\n", sdio_cmd.SDIO_CmdIndex);
    delay(5);
    SDIO_SendCommand(&sdio_cmd); // 重发
    while (SDIO_GetFlagStatus(SDIO_FLAG_CMDACT) == SET);
  }
  if (SDIO_GetFlagStatus(SDIO_FLAG_CMDREND) == SET)
  {
    SDIO_ClearFlag(SDIO_FLAG_CMDREND);
    printf("Command response received: CMD%d, RESP_%08x\n", SDIO_GetCommandResponse(), SDIO_GetResponse(SDIO_RESP1));
  }
  
  /* 设置参数VDD Voltage Window: 3.2~3.4V, 并再次发送CMD5 */
  sdio_cmd.SDIO_Argument = 0x300000;
  SDIO_SendCommand(&sdio_cmd);
  while (SDIO_GetFlagStatus(SDIO_FLAG_CMDACT) == SET);
  if (SDIO_GetFlagStatus(SDIO_FLAG_CMDREND) == SET)
  {
    SDIO_ClearFlag(SDIO_FLAG_CMDREND);
    resp = SDIO_GetResponse(SDIO_RESP1);
    printf("Command response received: CMD%d, RESP_%08x\n", SDIO_GetCommandResponse(), resp);
    if (resp & _BV(31))
    {
      // Card is ready to operate after initialization
      printf("Number of I/O Functions: %d\n", (resp >> 28) & 7);
      printf("Memory Present: %d\n", (resp & _BV(27)) != 0);
    }
  }
  
  /* 获取WiFi模块地址 (CMD3: SEND_RELATIVE_ADDR, Ask the card to publish a new relative address (RCA)) */
  sdio_cmd.SDIO_Argument = 0;
  sdio_cmd.SDIO_CmdIndex = 3;
  SDIO_SendCommand(&sdio_cmd);
  while (SDIO_GetFlagStatus(SDIO_FLAG_CMDACT) == SET);
  if (SDIO_GetFlagStatus(SDIO_FLAG_CMDREND) == SET)
  {
    SDIO_ClearFlag(SDIO_FLAG_CMDREND);
    rca = SDIO_GetResponse(SDIO_RESP1) >> 16;
    printf("Relative card address: 0x%04x\n", rca);
  }
  
  /* 选中WiFi模块 (CMD7: SELECT/DESELECT_CARD) */
  sdio_cmd.SDIO_Argument = rca << 16;
  sdio_cmd.SDIO_CmdIndex = 7;
  SDIO_SendCommand(&sdio_cmd);
  while (SDIO_GetFlagStatus(SDIO_FLAG_CMDACT) == SET);
  if (SDIO_GetFlagStatus(SDIO_FLAG_CMDREND) == SET)
  {
    SDIO_ClearFlag(SDIO_FLAG_CMDREND);
    printf("Card selected! status=0x%08x\n", SDIO_GetResponse(SDIO_RESP1));
  }
  
  /* 提高时钟频率, 超时时间为0.1s */
#ifdef WIFI_HIGHSPEED
  sdio.SDIO_ClockDiv = 1; // 72MHz/(1+2)=24MHz
  SDIO_Init(&sdio);
  sdio_data.SDIO_DataTimeOut = 2400000;
  printf("SDIO Clock: 24MHz\n");
#else
  sdio.SDIO_ClockDiv = 70; // 72MHz/(70+2)=1MHz
  SDIO_Init(&sdio);
  sdio_data.SDIO_DataTimeOut = 100000;
  printf("SDIO Clock: 1MHz\n");
#endif
  
  /* 选择总线宽度 (Wide Bus Selection) */
  // For an SDIO card a write to the CCCR using CMD52 is used to select bus width. (See 4.5 Bus Width)
  // CMD52: IO_RW_DIRECT, CCCR: Card Common Control Registers
  WiFi_Write(0, SDIO_CCCR_BUSIFCTRL, WiFi_Read(0, SDIO_CCCR_BUSIFCTRL) | SDIO_CCCR_BUSIFCTRL_BUSWID_4Bit); // Bus Width: 4-bit bus
  
  // 初始化Function 1
  WiFi_Write(0, SDIO_CCCR_IOEN, SDIO_CCCR_IOEN_IOE1); // IOE1=1 (Enable Function)
  while ((WiFi_Read(0, SDIO_CCCR_IORDY) & SDIO_CCCR_IORDY_IOR1) == 0); // 等到IOR1=1 (I/O Function Ready)
  WiFi_Write(0, SDIO_CCCR_INTEN, SDIO_CCCR_INTEN_IENM | SDIO_CCCR_INTEN_IEN1); // 打开SDIO中断请求
  WiFi_Write(1, WIFI_INTMASK, WIFI_INTMASK_HOSTINTMASK); // 利用中断标志位来判定是否有数据要读取, 可靠性更高
  
  // 显示CIS信息(SDIO卡信息)
  WiFi_ShowCIS(0);
  WiFi_ShowCIS(1);
  
#ifdef WIFI_USEDMA
  // 必须在DPSM禁用的时候开关DMA请求
  SDIO_DMACmd(ENABLE);
#endif
  
  // 下载固件
  if (!WiFi_DownloadFirmware())
    while (1);
  
  // 设置数据块大小为256字节
  sdio_data.SDIO_DataBlockSize = SDIO_DataBlockSize_256b;
  WiFi_SetBlockSize(0);
  WiFi_SetBlockSize(1);
  
  // 允许发送和接收
  // 不使用带有LLC子层的802.2SNAP帧格式, 这样LLC和SNAP这8个字节的内容就不会夹在数据链路层的源地址字段与数据字段之间
  WiFi_MACControl(wifi_macctrl);
}

/* 加入Ad-Hoc网络 */
uint16_t WiFi_JoinADHOC(const char *ssid)
{
  uint8_t buffer[2048];
  WiFi_SSIDInfo info;
  WiFi_CmdRequest_ADHOCJoin *cmd = (WiFi_CmdRequest_ADHOCJoin *)buffer;
  
  if (!WiFi_ScanSSID(ssid, &info, buffer, sizeof(buffer)))
  {
    printf("Cannot find AP: %s!\n", ssid);
    return WIFI_ASSOCIATION_NOTFOUND;
  }
  
  memcpy(cmd->bssid, info.mac_addr, sizeof(cmd->bssid));
  memset(cmd->ssid, 0, sizeof(cmd->ssid));
  strncpy((char *)cmd->ssid, ssid, sizeof(cmd->ssid));
  cmd->bss_type = BSS_ANY;
  cmd->bcn_period = info.bcn_period;
  cmd->dtim_period = 1;
  memset(cmd->timestamp, 0, sizeof(cmd->timestamp) + sizeof(cmd->start_ts));
  cmd->ds_param_set.header.type = MRVLIETYPES_DSPARAMSET;
  cmd->ds_param_set.header.length = TLV_PAYLOADLEN(cmd->ds_param_set);
  cmd->ds_param_set.channel = info.channel;
  cmd->reserved1 = 0;
  cmd->ibss_param_set.header.type = MRVLIETYPES_IBSSPARAMSET;
  cmd->ibss_param_set.header.length = TLV_PAYLOADLEN(cmd->ibss_param_set);
  cmd->ibss_param_set.atim_window = 0;
  cmd->reserved2 = 0;
  cmd->cap_info = info.cap_info;
  memcpy(cmd->data_rates, info.rates.rates, sizeof(cmd->data_rates));
  cmd->reserved3 = 0;
  
  WiFi_SendCommand(CMD_802_11_AD_HOC_JOIN, buffer, sizeof(WiFi_CmdRequest_ADHOCJoin), sizeof(buffer));
  if (!WiFi_ReceiveResponse(buffer, sizeof(buffer)))
    return WIFI_ASSOCIATION_ERROR;
  return cmd->header.result;
}

/* 获取或设置密钥 */
uint16_t WiFi_KeyMaterial(MrvlIETypes_KeyParamSet_t *keys, uint16_t size, uint8_t action)
{
  uint8_t buffer[WIFI_DEFBUFSIZE];
  uint8_t ret_size;
  WiFi_Cmd_KeyMaterial *cmd = (WiFi_Cmd_KeyMaterial *)buffer;
  cmd->action = action;
  if (action == WIFI_ACT_SET)
  {
    memcpy(cmd + 1, keys, size);
    WiFi_SendCommand(CMD_802_11_KEY_MATERIAL, buffer, sizeof(WiFi_Cmd_KeyMaterial) + size, sizeof(buffer));
  }
  else
    WiFi_SendCommand(CMD_802_11_KEY_MATERIAL, buffer, sizeof(WiFi_Cmd_KeyMaterial), sizeof(buffer));
  
  if (!WiFi_ReceiveResponse(buffer, sizeof(buffer)))
    return (action == WIFI_ACT_GET) ? 0 : CMD_STATUS_ERROR;
  
  if (action == WIFI_ACT_GET)
  {
    ret_size = cmd->header.size - sizeof(cmd->header) - sizeof(cmd->action);
    if (ret_size <= size)
      memcpy(keys, cmd + 1, ret_size);
    else
      printf("WiFi_KeyMaterial: Buffer size is too small! %d bytes required!\n", ret_size);
    return ret_size; // action=get时返回读取的数据大小
  }
  else
    return cmd->header.result; // action=set时返回命令执行结果值
}

/* 获取或设置MAC地址 */
uint16_t WiFi_MACAddr(uint8_t addr[6], uint8_t action)
{
  uint8_t buffer[WIFI_DEFBUFSIZE];
  WiFi_Cmd_MACAddr *cmd = (WiFi_Cmd_MACAddr *)buffer;
  cmd->action = action;
  if (action == WIFI_ACT_SET)
    memcpy(cmd->mac_addr, addr, 6);
  
  WiFi_SendCommand(CMD_802_11_MAC_ADDR, buffer, sizeof(WiFi_Cmd_MACAddr), sizeof(buffer));
  if (!WiFi_ReceiveResponse(buffer, sizeof(buffer)))
    return CMD_STATUS_ERROR;
  if (action == WIFI_ACT_GET)
    memcpy(addr, cmd->mac_addr, 6);
  return cmd->header.result;
}

/* 配置MAC */
uint16_t WiFi_MACControl(uint16_t action)
{
  uint8_t buffer[WIFI_DEFBUFSIZE];
  WiFi_Cmd_MACCtrl *cmd = (WiFi_Cmd_MACCtrl *)buffer;
  cmd->action = action;
  cmd->reserved = 0;
  WiFi_SendCommand(CMD_MAC_CONTROL, buffer, sizeof(WiFi_Cmd_MACCtrl), sizeof(buffer));
  if (!WiFi_ReceiveResponse(buffer, sizeof(buffer)))
    return CMD_STATUS_ERROR;
  return cmd->header.result;
}

/* 判断是否有新数据包到来 */
uint8_t WiFi_PacketArrived(void)
{
	//return (WiFi_Read(1, WIFI_CARDSTATUS) & WIFI_CARDSTATUS_UPLDCARDRDY) != 0; // 可靠性差, 标志位会被CMD53命令清除, 不建议使用
  return (WiFi_Read(1, WIFI_INTSTATUS) & WIFI_INTSTATUS_UPLD) != 0; // 可靠性高, 标志位需要手动清除, 必须打开中断后才能使用
}

/* 读寄存器 */
uint8_t WiFi_Read(uint8_t func, uint32_t addr)
{
  WiFi_SendCMD52(func, addr, NULL, NULL);
  if (SDIO_GetFlagStatus(SDIO_FLAG_CMDREND) == SET)
  {
    SDIO_ClearFlag(SDIO_FLAG_CMDREND);
    return SDIO_GetResponse(SDIO_RESP1) & 0xff;
  }
  else
  {
    printf("WiFi_Read failed, SDIO->STA=0x%08x!\n", SDIO->STA);
    return 0;
  }
}

/* 接收数据 */
// count为要接收的字节数或块数, bufsize为data缓冲区的大小
// bufsize=0时, 只读取数据不存入缓冲区
uint8_t WiFi_ReadData(uint8_t func, uint32_t addr, void *data, uint16_t count, uint16_t bufsize, uint32_t flags)
{
  uint32_t len; // 实际要接收的字节数
#ifdef WIFI_USEDMA
  uint32_t temp; // 丢弃数据用的变量
  DMA_InitTypeDef dma;
#else
  uint32_t *p = data;
#endif
  if (flags & CMD53_BLOCKMODE)
  {
    // 块传输模式 (DTMODE=0)
    sdio_data.SDIO_TransferMode = SDIO_TransferMode_Block;
    len = count * WiFi_GetSDIOBlockSize(); // 块数*块大小
  }
  else
  {
    // 多字节传输模式 (DTMODE=1, SDIO的频率低于16MHz时才支持)
    sdio_data.SDIO_TransferMode = SDIO_TransferMode_Stream;
    len = count;
    if (len % 4 != 0)
    {
      len += 4 - len % 4; // WiFi模块要求字节数必须为4的整数倍
      count = len; // 传入CMD53的数据大小参数也应该是4的倍数
    }
  }
  
  if (bufsize > 0 && bufsize < len)
  {
    printf("WiFi_ReadData: a buffer of at least %d bytes is required! bufsize=%d\n", len, bufsize);
    return 0;
  }
  
#ifdef WIFI_USEDMA
  dma.DMA_BufferSize = len / 4;
  dma.DMA_DIR = DMA_DIR_PeripheralSRC;
  dma.DMA_M2M = DMA_M2M_Disable;
  dma.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
  dma.DMA_Mode = DMA_Mode_Normal;
  dma.DMA_PeripheralBaseAddr = (uint32_t)&SDIO->FIFO;
  dma.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
  dma.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
  dma.DMA_Priority = DMA_Priority_VeryHigh;
  if (bufsize > 0)
  {
    dma.DMA_MemoryBaseAddr = (uint32_t)data;
    dma.DMA_MemoryInc = DMA_MemoryInc_Enable;
  }
  else
  {
    dma.DMA_MemoryBaseAddr = (uint32_t)&temp; // 数据丢弃模式
    dma.DMA_MemoryInc = DMA_MemoryInc_Disable;
  }
  DMA_Init(DMA2_Channel4, &dma);
  DMA_Cmd(DMA2_Channel4, ENABLE);
#endif
  
  sdio_data.SDIO_DataLength = len;
  sdio_data.SDIO_DPSM = SDIO_DPSM_Enable;
  sdio_data.SDIO_TransferDir = SDIO_TransferDir_ToSDIO;
  SDIO_DataConfig(&sdio_data);
  
  WiFi_SendCMD53(func, addr, count, flags);
#ifdef WIFI_USEDMA
  while (DMA_GetFlagStatus(DMA2_FLAG_TC4) == RESET);
  DMA_ClearFlag(DMA2_FLAG_TC4);
  DMA_Cmd(DMA2_Channel4, DISABLE);
#else
  while (len)
  {
    // 如果有数据到来就读取数据
    if (SDIO_GetFlagStatus(SDIO_FLAG_RXDAVL) == SET)
    {
      len -= 4;
      if (bufsize > 0)
        *p++ = SDIO_ReadData();
      else
        SDIO_ReadData(); // 读寄存器, 但不保存数据
    }
    
    if (SDIO_GetFlagStatus(SDIO_FLAG_DTIMEOUT) == SET)
    {
      printf("Data timeout!\n");
      break;
    }
    else if (SDIO_GetFlagStatus(SDIO_FLAG_DCRCFAIL) == SET)
    {
      printf("Data CRC check failed! %d bytes are lost\n", len);
      break;
    }
  }
#endif
  while (SDIO_GetFlagStatus(SDIO_FLAG_CMDACT) == SET || SDIO_GetFlagStatus(SDIO_FLAG_RXACT) == SET);
  
  SDIO_ClearFlag(SDIO_FLAG_CMDREND | SDIO_FLAG_DATAEND);
  if (flags & CMD53_BLOCKMODE)
    SDIO_ClearFlag(SDIO_FLAG_DBCKEND);
  return SDIO_SUCCEEDED();
}

uint8_t WiFi_ReadPort(void *data, uint16_t size, uint16_t bufsize)
{
  uint16_t block_num, block_size;
  block_size = WiFi_GetSDIOBlockSize();
  
  WiFi_Wait(0); // 发送CMD53前必须IOReady=1
  WiFi_Write(1, WIFI_INTSTATUS, WiFi_Read(1, WIFI_INTSTATUS) & ~WIFI_INTSTATUS_UPLD); // 读端口前先清除UPLD中断标志位
#ifndef WIFI_HIGHSPEED
  if (size > 512 || size % block_size == 0) // 若size为数据块大小的整数倍, 则采用数据块方式接收
  {
#endif
    block_num = size / block_size;
    if (size % block_size != 0)
      block_num++; // 大于512字节时不能采用字节流方式接收(如712字节), 仍采用数据块方式
    
    return WiFi_ReadData(1, io_addr, data, block_num, bufsize, CMD53_BLOCKMODE);
#ifndef WIFI_HIGHSPEED
  }
  else
    return WiFi_ReadData(1, io_addr, data, size, bufsize, 0); // 否则采用字节流方式接收
#endif
}

/* 接收数据帧, 返回是否成功 */
uint8_t WiFi_ReceivePacket(void *buf, uint16_t bufsize)
{
  uint8_t ret;
  uint16_t size = WiFi_GetPacketLength();
  WiFi_SDIOFrameHeader *hdr = (WiFi_SDIOFrameHeader *)buf;
  
  ret = WiFi_ReadPort(buf, size, bufsize);
  if (ret && hdr->type != WIFI_SDIOFRAME_DATA)
  {
    if (WIFI_SDIOFRAME_EVENT)
      WiFi_EventHandler(buf);
    ret = 0;
  }
  return ret;
}

/* 接收WiFi命令的回应 */
uint8_t WiFi_ReceiveResponse(void *buf, uint16_t bufsize)
{
  uint8_t ret;
  uint8_t wait = 0;
  uint16_t size;
  WiFi_CommandHeader *cmd = (WiFi_CommandHeader *)buf;
  
  // 等待数据准备好
  // 固件下载完后发送的第一个WiFi命令偶尔会收不到回应
  // 为了保证系统的可靠性, 命令的超时重传非常重要
  while (!WiFi_Wait(WIFI_CARDSTATUS_UPLDCARDRDY))
  {
    // 若WiFi_Wait返回0, 则说明超时
    wait++;
    if (wait >= 5)
    {
      printf("No response!\n");
      return 0;
    }
    if (cmd->frame_header.type == WIFI_SDIOFRAME_COMMAND)
    {
      // 若超时后还没收到数据, 则重发命令, 然后再次执行WiFi_Wait
      printf("Resend WiFi command 0x%04x! size=%d\n", cmd->cmd_code, cmd->frame_header.length);
      WiFi_ResendCommand(buf, bufsize);
    }
    else
      return 0; // 若buf中的内容不是命令, 则不重发直接退出
  }
  
  size = WiFi_GetPacketLength();
  ret = WiFi_ReadPort(buf, size, bufsize);
  if (ret && cmd->frame_header.type != WIFI_SDIOFRAME_COMMAND)
  {
    ret = 0; // buf中的内容已被接收到的数据覆盖, 无法重发命令
    if (cmd->frame_header.type == WIFI_SDIOFRAME_EVENT)
      WiFi_EventHandler(buf);
  }
  return ret;
}

/* 扫描全部热点 (仅显示) */
void WiFi_Scan(void)
{
  // 必须把STM32启动文件*.s中的Stack_Size改大, 否则函数中无法创建大数组
  uint8_t buffer[2048]; // 用于接收返回的WiFi接入点信息, 需要较大的内存空间来存放
  uint8_t i, j, n;
  uint8_t ssid[33], channel, wpa;
  uint16_t ie_size;
  
  WiFi_CmdRequest_Scan *cmd = (WiFi_CmdRequest_Scan *)buffer; // 用buffer空间来存放要发送的命令
  MrvlIETypes_ChanListParamSet_t *chanlist = (MrvlIETypes_ChanListParamSet_t *)(cmd + 1); // 这里的+1指的是前进sizeof(指针类型)个地址单元, 而非只前进1个地址单元
  
  WiFi_CmdResponse_Scan *resp = (WiFi_CmdResponse_Scan *)buffer;
  WiFi_BssDescSet *bss_desc_set;
  WiFi_Vendor *vendor;
  IEEEType *ie_params;
  //MrvlIETypes_TsfTimestamp_t *tft_table;
  
  // 分4次扫描14个通道
  for (i = 0; i < 4; i++)
  {
    cmd->bss_type = BSS_ANY;
    memset(cmd->bss_id, 0, sizeof(cmd->bss_id));
    
    // 通道的基本参数
    n = (i == 3) ? 2 : 4; // 本次要扫描的通道数
    chanlist->header.type = MRVLIETYPES_CHANLISTPARAMSET;
    chanlist->header.length = n * sizeof(chanlist->channels);
    for (j = 0; j < n; j++)
    {
      chanlist->channels[j].band_config_type = 0;
      chanlist->channels[j].chan_number = 4 * i + j + 1; // 通道号
      chanlist->channels[j].scan_type = 0;
      chanlist->channels[j].min_scan_time = 0;
      chanlist->channels[j].max_scan_time = 100;
    }
    
    // 发送命令并接收数据
    WiFi_SendCommand(CMD_802_11_SCAN, buffer, sizeof(WiFi_CmdRequest_Scan) + TLV_STRUCTLEN(*chanlist), sizeof(buffer));
    if (!WiFi_ReceiveResponse(buffer, sizeof(buffer))) // 接收的数据会将cmd和chanlist中的内容覆盖掉
    {
      printf("WiFi_Scan: no response!\n");
      continue;
    }
    
    // 显示热点信息, num_of_set为热点数
    if (resp->num_of_set > 0)
    {
      bss_desc_set = (WiFi_BssDescSet *)(resp + 1);
      for (j = 0; j < resp->num_of_set; j++)
      {
        wpa = 0;
        ie_params = &bss_desc_set->ie_parameters;
        ie_size = bss_desc_set->ie_length - (sizeof(WiFi_BssDescSet) - sizeof(bss_desc_set->ie_length) - sizeof(bss_desc_set->ie_parameters));
        while (ie_size > 0)
        {
          switch (ie_params->header.type)
          {
            case MRVLIETYPES_SSIDPARAMSET:
              // SSID名称
              memcpy(ssid, ie_params->data, ie_params->header.length);
              ssid[ie_params->header.length] = '\0';
              break;
            case MRVLIETYPES_DSPARAMSET:
              // 通道号
              channel = ie_params->data[0];
              break;
            case MRVLIETYPES_RSNPARAMSET:
              wpa = 2; // 表示WPA版本号(0表示没有使用WPA)
              break;
            case MRVLIETYPES_VENDORPARAMSET:
              if (wpa == 0)
              {
                vendor = (WiFi_Vendor *)ie_params->data;
                if (vendor->oui[0] == 0x00 && vendor->oui[1] == 0x50 && vendor->oui[2] == 0xf2 && vendor->oui_type == 0x01)
                  wpa = 1;
              }
              break;
          }
          ie_size -= TLV_STRUCTLEN(*ie_params);
          ie_params = (IEEEType *)TLV_NEXT(ie_params);
        }
        if (ie_size != 0)
          printf("ie_parameters error!\n");
        
        printf("SSID '%s', ", ssid); // 热点名称
        printf("MAC %02X:%02X:%02X:%02X:%02X:%02X, ", bss_desc_set->bssid[0], bss_desc_set->bssid[1], bss_desc_set->bssid[2], bss_desc_set->bssid[3], bss_desc_set->bssid[4], bss_desc_set->bssid[5]); // MAC地址
        printf("RSSI %d, Channel %d\n", bss_desc_set->rssi, channel); // 信号强度和通道号
        //printf("  Timestamp %lld, Beacon interval %d\n", bss_desc_set->pkt_time_stamp, bss_desc_set->bcn_interval);
        
        printf("  Capability: 0x%04x (Security: ", bss_desc_set->cap_info);
        if (bss_desc_set->cap_info & WIFI_CAPABILITY_PRIVACY)
        {
          if (wpa == 1)
            printf("WPA");
          else if (wpa == 2)
            printf("WPA2");
          else
            printf("WEP");
        }
        else
          printf("unsecured");
        
        printf(", Mode: ");
        if (bss_desc_set->cap_info & WIFI_CAPABILITY_IBSS)
          printf("Ad-Hoc");
        else
          printf("Infrastructure");
        printf(")\n");
        
        // 转向下一个热点信息
        bss_desc_set = (WiFi_BssDescSet *)((uint8_t *)bss_desc_set + sizeof(bss_desc_set->ie_length) + bss_desc_set->ie_length);
      }
      
      // resp->buf_size就是bss_desc_set的总大小
      // 因此tft_table == buffer + sizeof(WiFi_CmdResponse_Scan) + resp->buf_size
      /*
      tft_table = (MrvlIETypes_TsfTimestamp_t *)bss_desc_set;
      if (tft_table->header.type == MRVLIETYPES_TSFTIMESTAMP && tft_table->header.length == resp->num_of_set * sizeof(uint64_t))
      {
        printf("Timestamps: ");
        for (j = 0; j < resp->num_of_set; j++)
          printf("%lld ", tft_table->tsf_table[j]);
        printf("\n");
      }
      */
      
      // TSF timestamp table是整个数据的末尾, 后面没有Channel/band table
      //if (((uint8_t *)tft_table - buffer) + TLV_STRUCTLEN(*tft_table) == cmd->header.frame_header.length)
      //  printf("data end!\n");
    }
  }
}

/* 获取指定名称的热点的信息 */
// buffer用来存放返回的全部结果, 因此bufsize应该足够大
// info用来存放从buffer中提取出来的一些常用信息
uint8_t WiFi_ScanSSID(const char *ssid, WiFi_SSIDInfo *info, uint8_t *buffer, uint16_t bufsize)
{
  uint8_t i, ret;
  uint16_t ie_size;
  WiFi_CmdRequest_Scan *cmd = (WiFi_CmdRequest_Scan *)buffer;
  WiFi_CmdResponse_Scan *resp = (WiFi_CmdResponse_Scan *)buffer;
  WiFi_BssDescSet *bss_desc_set = (WiFi_BssDescSet *)(resp + 1);
  MrvlIETypes_ChanListParamSet_t *chan_list;
  IEEEType *ie_params;
  WiFi_Vendor *vendor;
  
  cmd->bss_type = BSS_ANY;
  memset(cmd->bss_id, 0, sizeof(cmd->bss_id));
  
  // 添加ssid参数
  info->ssid.header.type = MRVLIETYPES_SSIDPARAMSET;
  info->ssid.header.length = strlen(ssid);
  memcpy(info->ssid.ssid, ssid, info->ssid.header.length);
  memcpy(cmd + 1, &info->ssid, TLV_STRUCTLEN(info->ssid));
  
  chan_list = (MrvlIETypes_ChanListParamSet_t *)((uint8_t *)(cmd + 1) + TLV_STRUCTLEN(info->ssid));
  chan_list->header.type = MRVLIETYPES_CHANLISTPARAMSET;
  chan_list->header.length = 14 * sizeof(chan_list->channels); // 一次性扫描14个通道
  for (i = 0; i < 14; i++)
  {
    chan_list->channels[i].band_config_type = 0;
    chan_list->channels[i].chan_number = i + 1;
    chan_list->channels[i].scan_type = 0;
    chan_list->channels[i].min_scan_time = 0;
    chan_list->channels[i].max_scan_time = 100;
  }
  
  WiFi_SendCommand(CMD_802_11_SCAN, buffer, ((uint8_t *)chan_list - buffer) + TLV_STRUCTLEN(*chan_list), bufsize);
  wifi_timeout = 3000; // 延长超时时间
  ret = WiFi_ReceiveResponse(buffer, bufsize);
  wifi_timeout = WIFI_DEFTIMEOUT;
  if (!ret || resp->num_of_set == 0)
    return 0; // 失败
  
  // bss_desc_set以扫描到的第一个信息项为准
  memcpy(info->mac_addr, bss_desc_set->bssid, sizeof(info->mac_addr));
  info->cap_info = bss_desc_set->cap_info;
  info->bcn_period = bss_desc_set->bcn_interval;
  
  // 若type=0, 则表明没有该项的信息 (除SSID结构体外, 因为SSID的type=MRVLIETYPES_SSIDPARAMSET=0)
  info->rates.header.type = 0;
  info->rsn.header.type = 0;
  info->wpa.header.type = 0;
  info->wwm.header.type = 0;
  info->wps.header.type = 0;
  
  ie_params = &bss_desc_set->ie_parameters;
  ie_size = bss_desc_set->ie_length - (sizeof(WiFi_BssDescSet) - sizeof(bss_desc_set->ie_length) - sizeof(bss_desc_set->ie_parameters)); // 所有IEEE_Type数据的总大小
  while (ie_size > 0)
  {
    switch (ie_params->header.type)
    {
      case MRVLIETYPES_RATESPARAMSET:
        // 速率
        WiFi_TranslateTLV((MrvlIEType *)&info->rates, ie_params, sizeof(info->rates.rates));
        break;
      case MRVLIETYPES_DSPARAMSET:
        // 通道号
        info->channel = ie_params->data[0];
        break;
      case MRVLIETYPES_RSNPARAMSET:
        // 通常只有一个RSN信息 (与WPA2相关)
        // printf("RSN len=%d\n", ie_params->length);
        WiFi_TranslateTLV((MrvlIEType *)&info->rsn, ie_params, sizeof(info->rsn.rsn));
        break;
      case MRVLIETYPES_VENDORPARAMSET:
        // 通常会有多项VENDOR信息 (与WPA相关)
        vendor = (WiFi_Vendor *)ie_params->data;
        if (vendor->oui[0] == 0x00 && vendor->oui[1] == 0x50 && vendor->oui[2] == 0xf2)
        {
          switch (vendor->oui_type)
          {
            case 0x01:
              // wpa_oui
              WiFi_TranslateTLV((MrvlIEType *)&info->wpa, ie_params, sizeof(info->wpa.vendor));
              break;
            case 0x02:
              // wmm_oui
              if (ie_params->header.length == 24) // 合法大小
                WiFi_TranslateTLV((MrvlIEType *)&info->wwm, ie_params, sizeof(info->wwm.vendor));
              break;
            case 0x04:
              // wps_oui
              WiFi_TranslateTLV((MrvlIEType *)&info->wps, ie_params, sizeof(info->wps.vendor));
              break;
          }
        }
        break;
    }
    
    // 转向下一个TLV
    ie_size -= TLV_STRUCTLEN(*ie_params);
    ie_params = (IEEEType *)TLV_NEXT(ie_params);
  }
  
  return 1; // 成功
}

void WiFi_SendCMD52(uint8_t func, uint32_t addr, uint8_t data, uint32_t flags)
{
  sdio_cmd.SDIO_Argument = (func << 28) | (addr << 9) | data | flags;
  sdio_cmd.SDIO_CmdIndex = 52;
  SDIO_SendCommand(&sdio_cmd);
  while (SDIO_GetFlagStatus(SDIO_FLAG_CMDACT) == SET);
}

void WiFi_SendCMD53(uint8_t func, uint32_t addr, uint16_t count, uint32_t flags)
{
  // 当count=512时, 和0x1ff相与后为0, 符合要求
  sdio_cmd.SDIO_Argument = (func << 28) | (addr << 9) | (count & 0x1ff) | flags;
  sdio_cmd.SDIO_CmdIndex = 53;
  SDIO_SendCommand(&sdio_cmd);
}

/* 发送WiFi命令 */
uint8_t WiFi_SendCommand(uint16_t com_code, void *data, uint16_t size, uint16_t bufsize)
{
  static uint16_t seq_num = 0;
  WiFi_CommandHeader *cmdhdr = (WiFi_CommandHeader *)data;
  
  if (size != 0)
  {
    cmdhdr->frame_header.length = size;
    cmdhdr->frame_header.type = WIFI_SDIOFRAME_COMMAND;
    cmdhdr->cmd_code = com_code;
    cmdhdr->size = size - sizeof(WiFi_SDIOFrameHeader); // 命令大小包括命令头部, 但不包括SDIO帧头部
    cmdhdr->seq_num = seq_num++;
    cmdhdr->result = 0;
  }
  else
    size = cmdhdr->frame_header.length; // 重发命令时不填写cmdhdr
  
  return WiFi_WritePort(data, size, bufsize);
}

/* 发送数据帧 */
uint8_t WiFi_SendPacket(WiFi_DataTx *packet, uint16_t packet_len, uint16_t bufsize)
{
  uint8_t ret;
  
  // 有关发送数据包的细节, 请参考Firmware Specification PDF的Chapter 3: Data Path
  packet->header.length = sizeof(WiFi_DataTx) - sizeof(packet->payload) + packet_len;
  packet->header.type = WIFI_SDIOFRAME_DATA;
  
  packet->reserved1 = 0;
  packet->tx_control = 0; // 控制信息的格式请参考3.2.1 Per-Packet Settings
  packet->tx_packet_offset = sizeof(WiFi_DataTx) - sizeof(packet->payload) - sizeof(packet->header); // 不包括SDIOFrameHeader
  packet->tx_packet_length = packet_len;
  memcpy((void *)&packet->tx_dest_addr_high, packet->payload, 6);
  packet->priority = 0;
  packet->flags = 0;
  packet->pkt_delay_2ms = 0;
  packet->reserved2 = 0;
  
  ret = WiFi_WritePort(packet, packet->header.length, bufsize);
  if (ret)
    ret &= WiFi_Wait(WIFI_CARDSTATUS_DNLDCARDRDY); // WiFi模块收到数据后, 会将该位置1
  return ret;
}

/* 将SDIO->DCTRL中的块大小信息应用到WiFi模块指定的功能区上 */
void WiFi_SetBlockSize(uint8_t func)
{
  // Part E1: 6.9 Card Common Control Registers (CCCR), 6.10 Function Basic Registers (FBR)
  uint16_t size = WiFi_GetSDIOBlockSize();
  WiFi_Write(0, (func << 8) | 0x10, size & 0xff);
  WiFi_Write(0, (func << 8) | 0x11, size >> 8);
}

/* 设置密钥 */
uint8_t WiFi_SetKeyMaterial(uint16_t type, uint16_t info, const uint8_t *key, uint16_t len)
{
  MrvlIETypes_KeyParamSet_t key_param;
  if (len > sizeof(key_param.key))
    return CMD_STATUS_ERROR;
  key_param.header.type = MRVLIETYPES_KEYPARAMSET;
  key_param.header.length = (key_param.key - (uint8_t *)&key_param) - sizeof(key_param.header) + len;
  key_param.key_type_id = type;
  key_param.key_info = info;
  key_param.key_len = len; // 当len=0时可保留key的值, 只更新key_info
  if (len)
    memcpy(key_param.key, key, len);
  return WiFi_KeyMaterial(&key_param, TLV_STRUCTLEN(key_param), WIFI_ACT_SET);
}

/* 设置WEP密钥 (长度必须为5或13个字符) */
// action: WIFI_ACT_ADD / WIFI_ACT_REMOVE
uint16_t WiFi_SetWEP(uint8_t action, const char *key)
{
  uint8_t buffer[WIFI_DEFBUFSIZE];
  uint8_t i, len;
  uint16_t ret;
  uint32_t temp;
  WiFi_Cmd_SetWEP *cmd = (WiFi_Cmd_SetWEP *)buffer;
  
  cmd->action = action;
  cmd->tx_key_index = 0;
  
  memset(cmd->wep_types, 0, sizeof(cmd->wep_types) + sizeof(cmd->keys));
  if (action == WIFI_ACT_ADD)
  {
    len = strlen(key);
    if (len == 5 || len == 13)
    {
      // 5个或13个ASCII密钥字符
      if (len == 5)
        cmd->wep_types[0] = WIFI_WEPKEYTYPE_40BIT;
      else if (len == 13)
        cmd->wep_types[0] = WIFI_WEPKEYTYPE_104BIT;
      memcpy(cmd->keys[0], key, len);
    }
    else if (len == 10 || len == 26)
    {
      // 10个或26个16进制密钥字符
      if (len == 10)
        cmd->wep_types[0] = WIFI_WEPKEYTYPE_40BIT;
      else if (len == 26)
        cmd->wep_types[0] = WIFI_WEPKEYTYPE_104BIT;
      
      for (i = 0; i < len; i++)
      {
        if (!(key[i] >= '0' && key[i] <= '9') && !(key[i] >= 'a' && key[i] <= 'f') && !(key[i] >= 'A' && key[i] <= 'F'))
        {
          printf("WiFi_SetWEP: The hex key contains invalid characters!\n");
          return 0xffff;
        }
        if (i % 2 == 0)
        {
          sscanf(key + i, "%02x", &temp);
          cmd->keys[0][i / 2] = temp;
        }
      }
    }
    else
    {
      printf("WiFi_SetWEP: The key length is invalid!\n");
      return 0xffff;
    }
    wifi_macctrl |= WIFI_MACCTRL_WEP;
  }
  else
    wifi_macctrl &= ~WIFI_MACCTRL_WEP;
  
  ret = WiFi_MACControl(wifi_macctrl);
  if (ret != CMD_STATUS_SUCCESS)
    return ret;
  
  WiFi_SendCommand(CMD_802_11_SET_WEP, buffer, sizeof(WiFi_Cmd_SetWEP), sizeof(buffer));
  if (!WiFi_ReceiveResponse(buffer, sizeof(buffer)))
    return CMD_STATUS_ERROR;
  return cmd->header.result;
}

/* 显示WiFi模块信息 */
void WiFi_ShowCIS(uint8_t func)
{
  uint8_t data[255];
  uint8_t i, len;
  uint8_t tpl_code, tpl_link; // 16.2 Basic Tuple Format and Tuple Chain Structure
  uint32_t cis_ptr;
  printf("-------------- CIS of Function %d ----------------\n", func);
  
  // 获取CIS的地址
  cis_ptr = (func << 8) | 0x9;
  cis_ptr  = WiFi_Read(0, cis_ptr) | (WiFi_Read(0, cis_ptr + 1) << 8) | (WiFi_Read(0, cis_ptr + 2) << 16);
  printf("Pointer to Function %d Card Information Structure (CIS): 0x%08x\n", func, cis_ptr);
  
  // 遍历CIS, 直到尾节点
  while ((tpl_code = WiFi_Read(0, cis_ptr++)) != CISTPL_END)
  {
    if (tpl_code == CISTPL_NULL)
      continue;
    
    tpl_link = WiFi_Read(0, cis_ptr++); // 本结点数据的大小
    for (i = 0; i < tpl_link; i++)
      data[i] = WiFi_Read(0, cis_ptr + i);
    
    printf("[CIS Tuple 0x%02x] addr=0x%08x size=%d\n", tpl_code, cis_ptr - 2, tpl_link);
    dump_data(data, tpl_link);
    switch (tpl_code)
    {
      case CISTPL_VERS_1:
        i = 2;
        while (data[i] != 0xff)
        {
          len = strlen((char *)&data[i]);
          if (len != 0)
            printf("%s\n", data + i);
          i += len + 1;
        }
        break;
      case CISTPL_MANFID:
        // 16.6 CISTPL_MANFID: Manufacturer Identification String Tuple
        printf("SDIO Card manufacturer code: 0x%04x\n", *(uint16_t *)data); // TPLMID_MANF
        printf("manufacturer information (Part Number and/or Revision): 0x%04x\n", *(uint16_t *)(data + 2)); // TPLMID_CARD
        break;
      case CISTPL_FUNCID:
        // 16.7.1 CISTPL_FUNCID: Function Identification Tuple
        printf("Card function code: 0x%02x\n", data[0]); // TPLFID_FUNCTION
        printf("System initialization bit mask: 0x%02x\n", data[1]); // TPLFID_SYSINIT
        break;
      case CISTPL_FUNCE:
        // 16.7.2 CISTPL_FUNCE: Function Extension Tuple
        if (data[0] == 0)
        {
          // 16.7.3 CISTPL_FUNCE Tuple for Function 0 (Extended Data 00h)
          printf("maximum block size: %d\n", *(uint16_t *)(data + 1));
          printf("maximum transfer rate code: 0x%02x\n", data[3]);
        }
        else
        {
          // 16.7.4 CISTPL_FUNCE Tuple for Function 1-7 (Extended Data 01h)
          printf("maximum block size: %d\n", *(uint16_t *)(data + 0x0c)); // TPLFE_MAX_BLK_SIZE
        }
    }
    
    cis_ptr += tpl_link;
    if (tpl_link == 0xff)
      break; // 当TPL_LINK为0xff时说明当前结点为尾节点
  }
  printf("--------------------------------------------------\n");
}

/* 显示所有密钥 */
void WiFi_ShowKeyMaterials(void)
{
  uint8_t buffer[WIFI_DEFBUFSIZE];
  uint16_t size;
  MrvlIETypes_KeyParamSet_t *key = (MrvlIETypes_KeyParamSet_t *)buffer;
  
  size = WiFi_KeyMaterial(key, sizeof(buffer), WIFI_ACT_GET);
  while (size)
  {
    printf("Type %d, Info 0x%04x, Len %d\n", key->key_type_id, key->key_info, key->key_len);
    dump_data(key->key, key->key_len);
    size -= TLV_STRUCTLEN(*key);
    key = (MrvlIETypes_KeyParamSet_t *)((uint8_t *)key + TLV_STRUCTLEN(*key));
  }
  if (size == 0)
    printf("data end!\n");
}

/* 创建一个Ad-Hoc型的WiFi热点 */
uint8_t WiFi_StartADHOC(const char *ssid)
{
  uint8_t buffer[WIFI_DEFBUFSIZE] = {0};
  WiFi_CmdRequest_ADHOCStart *cmd = (WiFi_CmdRequest_ADHOCStart *)buffer;
  
  strncpy((char *)cmd->ssid, ssid, sizeof(cmd->ssid));
  cmd->bss_type = BSS_INDEPENDENT;
  cmd->bcn_period = 100;
  cmd->ibss_param_set.header.type = MRVLIETYPES_IBSSPARAMSET;
  cmd->ibss_param_set.header.length = TLV_PAYLOADLEN(cmd->ibss_param_set);
  cmd->ibss_param_set.atim_window = 0;
  cmd->ds_param_set.header.type = MRVLIETYPES_DSPARAMSET;
  cmd->ds_param_set.header.length = TLV_PAYLOADLEN(cmd->ds_param_set);
  cmd->ds_param_set.channel = 1;
  cmd->cap_info = WIFI_CAPABILITY_IBSS;
  if (wifi_macctrl & WIFI_MACCTRL_WEP)
    cmd->cap_info |= WIFI_CAPABILITY_PRIVACY;
  *(uint32_t *)cmd->data_rate = 0x968b8482;
  
  WiFi_SendCommand(CMD_802_11_AD_HOC_START, buffer, sizeof(WiFi_CmdRequest_ADHOCStart), sizeof(buffer));
  if (!WiFi_ReceiveResponse(buffer, sizeof(buffer)))
    return CMD_STATUS_ERROR;
  return cmd->header.result;
}

/* 设置WiFi事件 */
uint16_t WiFi_SubscribeEvent(uint16_t action, uint16_t events)
{
  uint8_t buffer[WIFI_DEFBUFSIZE];
  WiFi_Cmd_SubscribeEvent *cmd = (WiFi_Cmd_SubscribeEvent *)buffer;
  cmd->action = action;
  if (action == WIFI_ACT_GET)
    cmd->events = 0;
  else
    cmd->events = events;
  
  WiFi_SendCommand(CMD_802_11_SUBSCRIBE_EVENT, buffer, sizeof(WiFi_Cmd_SubscribeEvent), sizeof(buffer));
  if (!WiFi_ReceiveResponse(buffer, sizeof(buffer)))
    return CMD_STATUS_ERROR;
  return cmd->events;
}

/* 将IEEE型的TLV转换成MrvlIE型的TLV */
uint8_t WiFi_TranslateTLV(MrvlIEType *mrvlie_tlv, const IEEEType *ieee_tlv, uint16_t mrvlie_payload_size)
{
  mrvlie_tlv->header.type = ieee_tlv->header.type;
  if (ieee_tlv->header.length > mrvlie_payload_size)
    mrvlie_tlv->header.length = mrvlie_payload_size; // 若源数据大小超过缓冲区最大容量, 则丢弃剩余数据
  else
    mrvlie_tlv->header.length = ieee_tlv->header.length;
  memcpy(mrvlie_tlv->data, ieee_tlv->data, mrvlie_tlv->header.length); // 复制数据
  return mrvlie_tlv->header.length == ieee_tlv->header.length; // 返回值表示缓冲区大小是否足够
}

/* 在规定的超时时间内, 等待指定的卡状态位置位(自动包括IO Ready位), 若成功则返回1 */
uint8_t WiFi_Wait(uint8_t status)
{
  status |= WIFI_CARDSTATUS_IOREADY;
  timeout(wifi_timeout);
  while ((WiFi_Read(1, WIFI_CARDSTATUS) & status) != status)
  {
    if (TIM_GetFlagStatus(TIM6, TIM_FLAG_Update) == SET)
    {
      // 若超时时间已到
      TIM_ClearFlag(TIM6, TIM_FLAG_Update);
      printf("WiFi_Wait(0x%02x): timeout!\n", status);
      return 0;
    }
  }
  TIM_Cmd(TIM6, DISABLE); // 关闭定时器
  return 1;
}

/* 写寄存器, 返回写入后寄存器的实际内容 */
uint8_t WiFi_Write(uint8_t func, uint32_t addr, uint8_t value)
{
  WiFi_SendCMD52(func, addr, value, CMD52_WRITE | CMD52_READAFTERWRITE);
  if (SDIO_GetFlagStatus(SDIO_FLAG_CMDREND) == SET)
  {
    SDIO_ClearFlag(SDIO_FLAG_CMDREND);
    return SDIO_GetResponse(SDIO_RESP1) & 0xff;
  }
  else
  {
    printf("WiFi_Write failed, SDIO->STA=0x%08x!\n", SDIO->STA);
    return 0;
  }
}

/* 发送数据 */
// count为要发送的字节数或块数, bufsize为data缓冲区的大小
uint8_t WiFi_WriteData(uint8_t func, uint32_t addr, const void *data, uint16_t count, uint32_t bufsize, uint32_t flags)
{
  uint32_t len; // 实际要发送的字节数
#ifdef WIFI_USEDMA
  DMA_InitTypeDef dma;
#else
  const uint32_t *p = data;
#endif
  if (flags & CMD53_BLOCKMODE)
  {
    // 块传输模式 (DTMODE=0)
    sdio_data.SDIO_TransferMode = SDIO_TransferMode_Block;
    len = count * WiFi_GetSDIOBlockSize(); // 块数*块大小
  }
  else
  {
    // 多字节传输模式 (DTMODE=1, SDIO的频率低于16MHz时才支持)
    sdio_data.SDIO_TransferMode = SDIO_TransferMode_Stream;
    len = count;
    if (len % 4 != 0)
    {
      len += 4 - len % 4; // WiFi模块要求写入的字节数必须为4的整数倍
      count = len; // 传入CMD53的数据大小参数也应该是4的倍数
    }
  }
  
  if (bufsize < len)
    printf("WiFi_WriteData: a buffer of at least %d bytes is required! bufsize=%d\n", len, bufsize); // 只读缓冲区越界不会影响数据传输, 所以这只是一个警告
  
  WiFi_SendCMD53(func, addr, count, flags | CMD53_WRITE);
  while (SDIO_GetFlagStatus(SDIO_FLAG_CMDACT) == SET);
  if (SDIO_GetFlagStatus(SDIO_FLAG_CMDREND) == RESET)
  {
    printf("WiFi_WriteData: CMD53 no response!\n");
    return 0;
  }
  SDIO_ClearFlag(SDIO_FLAG_CMDREND);
  
  // 开始发送数据
#ifdef WIFI_USEDMA
  dma.DMA_BufferSize = len / 4;
  dma.DMA_DIR = DMA_DIR_PeripheralDST;
  dma.DMA_M2M = DMA_M2M_Disable;
  dma.DMA_MemoryBaseAddr = (uint32_t)data;
  dma.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
  dma.DMA_MemoryInc = DMA_MemoryInc_Enable;
  dma.DMA_Mode = DMA_Mode_Normal;
  dma.DMA_PeripheralBaseAddr = (uint32_t)&SDIO->FIFO;
  dma.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
  dma.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
  dma.DMA_Priority = DMA_Priority_VeryHigh;
  DMA_Init(DMA2_Channel4, &dma);
  DMA_Cmd(DMA2_Channel4, ENABLE);
#endif
  
  sdio_data.SDIO_DataLength = len;
  sdio_data.SDIO_DPSM = SDIO_DPSM_Enable;
  sdio_data.SDIO_TransferDir = SDIO_TransferDir_ToCard;
  SDIO_DataConfig(&sdio_data);
  
#ifdef WIFI_USEDMA
  while (DMA_GetFlagStatus(DMA2_FLAG_TC4) == RESET);
  DMA_ClearFlag(DMA2_FLAG_TC4);
  DMA_Cmd(DMA2_Channel4, DISABLE);
#else
  while (len)
  {
    len -= 4;
    SDIO_WriteData(*p++); // 向FIFO送入4字节数据
    while (SDIO_GetFlagStatus(SDIO_FLAG_TXFIFOF) == SET); // 如果FIFO已满则等待
  }
#endif
  while (SDIO_GetFlagStatus(SDIO_FLAG_TXACT) == SET); // 等待发送完毕
  
  // 清除相关标志位
  SDIO_ClearFlag(SDIO_FLAG_DATAEND);
  if (flags & CMD53_BLOCKMODE)
    SDIO_ClearFlag(SDIO_FLAG_DBCKEND);
  return SDIO_SUCCEEDED();
}

/* 向WiFi缓冲区发送数据 */
uint8_t WiFi_WritePort(const void *data, uint16_t size, uint32_t bufsize)
{
  uint16_t block_num, block_size;
  block_size = WiFi_GetSDIOBlockSize();
  
  WiFi_Wait(0); // 发送CMD53前必须IOReady=1
#ifndef WIFI_HIGHSPEED
  if (size > 512 || size % block_size == 0) // 若size为数据块大小的整数倍, 则采用数据块方式发送
  {
#endif
    block_num = size / block_size;
    if (size % block_size != 0)
      block_num++; // 大于512字节时不能采用字节流方式发送(如712字节), 仍采用数据块方式
    
    return WiFi_WriteData(1, io_addr, data, block_num, bufsize, CMD53_BLOCKMODE);
#ifndef WIFI_HIGHSPEED
  }
  else
    return WiFi_WriteData(1, io_addr, data, size, bufsize, 0); // 否则采用字节流方式发送
#endif
}

【扩展阅读】
将Marvell 88W8686 WiFi模块的固件内容写入到STM32F103RE单片机Flash末尾的固定位置,减少程序下载的时间:
http://blog.csdn.net/zlk1214/article/details/78368968

本文地址:https://blog.csdn.net/ZLK1214/article/details/75410647