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

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

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

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

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



注意: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);中的==应该改为!=


注意必须把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 
#include 
#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库中的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 
#include 
#include 
#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 
#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, "获取网页名称
请求的网页文件名称是: %s
", 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版)_第1张图片


接下来,创建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 
 *
 */

/*
 * 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  // 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版)_第2张图片

另外,为了在程序中使用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版)_第3张图片  【程序】Marvell 88W8686 WiFi模块(WM-G-MR-09)创建或连接热点,并使用lwip2.0.2建立http服务器(20171030版)_第4张图片

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

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

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

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

程序运行结果:

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版)_第7张图片

程序运行结果:

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 
#include 
#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库中的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 
#include 
#include 
#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

你可能感兴趣的:(单片机,stm32,网卡,c语言,wi-fi)