该程序是旧版本!最新版本为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。

Keil5工程下载地址: https://pan.baidu.com/s/1Dw5skiXV5-OmDVqDFrewQA

代码说明:http://blog.csdn.net/ZLK1214/article/details/79278871
Windows下根据计算机名获取IP地址的C语言程序:http://blog.csdn.net/ZLK1214/article/details/79595245

此版本修复了上一个版本(20180129版)存在的许多问题,其中包括无法连接部分WPA/WPA2路由器的问题。添加了库函数版本的代码,还添加了SDIO错误处理代码,取消了数据帧的重传功能(因为802.11数据链路层上已经有了该功能)。该版本仍有如下问题未解决:
(1) 连接仅支持设置WPA2密码的路由器后(这类路由器通常仅支持WPA/WPA2混合模式,体现出来是扫描热点后同时拥有WPA_IE和RSN_IE信息结构), 有时路由器会进行Group Key Handshake更新GTK密钥(也就是组密钥),但程序回复的Message 2无效被路由器丢弃,最后会断开连接。解决该bug的困难在于,该类路由器往往不能在管理页面中修改组密钥更新频率,也无法通过发送EAPOL请求帧(key_info=REQUEST+MIC=0x902)请求GTK更新,并且更新频率为每天一次,给程序调试带来很大麻烦。临时解决办法:在WiFi_EventHandler中调用associate_example函数重新连接路由器,但这样会影响已连接的TCP和UDP客户,并且IP地址也有可能改变。
笔者在电脑上用Microsoft Network Monitor 3.4抓取电脑发送的Message 2认证帧,按照他的格式修改了程序发送的Message 2,经检验MIC的值是正确的,其他字段一模一样,但还是不能解决问题。说明发出去的帧内容上应该没有问题。
有一个简单的方法可以快速复现该bug。在WiFi_Associate_Callback函数中,把添加RSN参数的代码去掉,只添加WPA的Vendor参数(目的是在连接WPA/WPA2混合模式的路由器时,选WPA认证方式):

cmd_size = (uint8_t *)(auth + 1) - wifi_buffer_command;
if (security == WIFI_SECURITYTYPE_WPA || security == WIFI_SECURITYTYPE_WPA2)
{// WPA网络必须在命令中加入Vendor参数才能成功连接vendor = (MrvlIETypes_VendorParamSet_t *)(auth + 1);memcpy(vendor, &wifi_ssid_info.wpa, TLV_STRUCTLEN(wifi_ssid_info.wpa));cmd_size += TLV_STRUCTLEN(wifi_ssid_info.wpa);
}

然后在WiFi_EAPOLProcess函数中,也把添加RSN参数的代码去掉(选择WPA方式认证):

/* 在待发送的Message 2中添加Key Data信息 */
// https://community.arubanetworks.com/t5/Technology-Blog/A-closer-look-at-WiFi-Security-IE-Information-Elements/ba-p/198867
// 使用WPA的热点一定有WPA IE信息项,一定没有RSN IE信息项
// 使用WPA2的热点一定有RSN IE信息项,可能有WPA IE信息项
packet_tx = (WiFi_EAPOLKeyFrame *)WiFi_GetPacketBuffer();
kde = (WiFi_KDE *)packet_tx->key_data;
if (wifi_ssid_info.wpa.header.type)
{// 路由器提供了WPA IE信息项时可直接复制//printf("WPA IE copied!\n");kde->type = WIFI_MRVLIETYPES_VENDORPARAMSET;kde->length = wifi_ssid_info.wpa.header.length;memcpy(kde->oui, wifi_ssid_info.wpa.vendor, wifi_ssid_info.wpa.header.length);key_data_len = sizeof(kde->type) + sizeof(kde->length) + kde->length;
}
else
{printf("IE not copied!\n");key_data_len = 0;
}

这样,关联时就会强制采用WPA方式认证,每次关联时都可以产生Group key handshake,不用再等上一天了。

修改后就会发现,路由器产生了Group key handshake,程序也回应了Message 2但路由器没有接受,最后Deauthenticated!被强制断开连接。
进行WPA认证时组密钥握手失败的原因是:WiFi_EAPOLProcess函数中,CMD_802_11_KEY_MATERIAL命令帧和EAPOL回应数据帧不能同时发送。必须先发送其中一个,成功后在回调函数中发送另一个。
这个解决办法只能修复WPA认证时最开始握手失败的问题,仍不能解决WPA和WPA2认证成功过后一天内出现的组密钥更新失败的问题。

(2) 发送了很多数据帧后,有时会卡在WiFi_Wait(0x02): timeout!上,再也发不出任何数据帧了。临时解决办法:把WiFi.h中WIFI_DEFAULT_TIMEOUT_DATAACK的值改大,比如1000,10000等。
(3) 连接某些WPA的热点,卡在Waiting for authentication!上无法继续,接收不到Message 1。
(4) TCP和UDP的发送速率很慢,只有几到几十KB/s,并且UDP丢包率也很严重,大约达到了30%。临时解决办法:WiFi_SendPacket函数中将packet->tx_control的值改为0x001c(优先使用54Mbps传输速率,默认的最大重传次数2),这样能使UDP发送速率稳定在300~700KB/s左右,但不能改善UDP丢包率和TCP发送速率。
(5) 连接某些手机热点时,单片机复位后第一次扫描到的概率很大,但之后就很难再次扫描到了,有时长达好几分钟一直显示SSID not found!
(6) 长时间连接路由器后没有通信,电脑无法ping通WiFi开发板。

【勘误】

2018年4月1日(重要):DHCP长时间获取不到IP地址,是因为sys_now函数的实现有问题。该函数没有考虑到毫秒向秒进位时发生的同步问题,有时候后调用的时间值小于先调用的时间值,使sys_check_timeouts函数中的diff值为负,破坏了next_timeout链表,导致sys_check_timeouts函数不能正常工作。下面是解决方法,把代码复制到工程里就可以修复此问题:

/* 获取RTC分频计数器的值 */
uint32_t rtc_divider(void)
{uint32_t div[2];do{div[0] = RTC_GetDivider();div[1] = RTC_GetDivider();} while ((div[0] >> 16) != (div[1] >> 16));// RTC_GetDivider函数先读取DIVH再读取DIVL// 如果更新发生在第一次读取DIVH或DIVL后, 则div[0]和div[1]的高16位不相等, 两个值都会被丢弃// 如果更新发生在第二次读取DIVH后, 则div[0]和div[1]的高16位相等, 且div[0]值的高、低16位匹配,div[1]值的高、低16位不匹配return div[0]; // 所以应该取第一次读取的值
}/* RTC时间转化为毫秒数 (lwip协议栈要求实现的函数) */
// 该函数必须保证: 除非定时器溢出, 否则后获取的时间必须大于先获取的时间
uint32_t sys_now(void)
{uint32_t sec[2];uint32_t div, milli;do{time(&sec[0]); // 秒div = rtc_divider();time(&sec[1]);} while (sec[0] != sec[1]);// CNT是在DIV从P-1跳变到P-2瞬间发生更新的 (P=RTC_PRESCALER)if (div == RTC_PRESCALER - 1)milli = div;elsemilli = RTC_PRESCALER - div - 2;milli = milli * 1000 / RTC_PRESCALER; // 毫秒return sec[0] * 1000 + milli;
}/* 获取RTC秒数 */
time_t time(time_t *timer)
{// CNTH和CNTL是同时更新的// 但由于这两个寄存器不是同时读取的, 所以有可能一个读到的是更新前的值, 另一个读到的是更新后的值uint32_t now[2];do{now[0] = RTC_GetCounter();now[1] = RTC_GetCounter();} while ((now[0] >> 16) != (now[1] >> 16)); // 使用循环可以避免中断的影响// RTC_GetCounter函数先读取CNTL再读取CNTH// 如果更新发生在第一次读取CNTL后, 则now[0]和now[1]的高16位相等, 且now[0]值的高、低16位不匹配,now[1]值的高、低16位匹配// 如果更新发生在第一次读取CNTH后或第二次读取CNTL后, 则now[0]和now[1]的高16位不相等, 两个值都会被丢弃if (timer)*timer = now[1];return now[1]; // 所以应该取第二次读取的值
}

Wi-Fi模块电源引脚的连接方法:

程序支持连接无密码的热点以及WEP、WPA-PSK和WPA2-PSK认证类型的热点,加密方式支持TKIP和AES。
支持创建无密码或是带有WEP密码的ADHOC热点,ADHOC模式下不支持WPA和WPA2!

注意:虽然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总线通信。

【程序运行截图】

连上路由器后DHCP分配得到IP地址:(花的时间有时候长,有时候短。解决方法见2018年4月1日的勘误)

下面是把WiFi模块固件写入单片机芯片Flash固定区域的程序(用于减少调试主程序时下载程序的时间)的运行结果:

电脑上ping IP地址和计算机名:

通过计算机名在电脑上访问开发板上的HTTP服务器(lwip自带的httpd):

【程序运行结果(扫描热点并连接安卓手机开的WPA2热点)】

STM32F103RE SDIO 88W8686
RESPCMD63, RESP1_90ff8000
RESPCMD63, RESP1_90300000
Number of I/O Functions: 1
Memory Present: 0
Relative Card Address: 0x0001
Card selected! RESP1_00001e00
SDIO Clock: 24MHz
[CIS] func=0, ptr=0x00008000
Product Information: Marvell 802.11 SDIO ID: 0B
Manufacturer Code: 0x02df
Manufacturer Information: 0x9103
Card Function Code: 0x0c
System Initialization Bit Mask: 0x00
Maximum Block Size: 256
Maximum Transfer Rate Code: 0x32
[CIS] func=1, ptr=0x00008080
Card Function Code: 0x0c
System Initialization Bit Mask: 0x00
Maximum Block Size: 256
Firmware is successfully downloaded!
MAC Addr: 00:1A:6B:A4:AA:B4
SSID 'CMCC-EDU', MAC 96:14:4B:6F:A5:DA, RSSI 73, Channel 1Capability: 0x0621 (Security: Unsecured, Mode: Infrastructure)
SSID 'CMCC-Young', MAC 96:14:4B:6F:A5:DB, RSSI 69, Channel 1Capability: 0x0621 (Security: Unsecured, Mode: Infrastructure)
SSID '???????', MAC BC:46:99:9B:1E:E4, RSSI 71, Channel 1Capability: 0x0431 (Security: WPA2, Mode: Infrastructure)
SSID 'CMCC-EDU', MAC E6:14:4B:57:40:0F, RSSI 75, Channel 1Capability: 0x0621 (Security: Unsecured, Mode: Infrastructure)
SSID 'CMCC-Young', MAC E6:14:4B:57:40:00, RSSI 75, Channel 1Capability: 0x0621 (Security: Unsecured, Mode: Infrastructure)
SSID 'CMCC-EDU', MAC F6:14:4B:6C:19:50, RSSI 86, Channel 6Capability: 0x0621 (Security: Unsecured, Mode: Infrastructure)
SSID 'TP-LINK_PLC', MAC 30:FC:68:38:6E:2C, RSSI 66, Channel 6Capability: 0x0431 (Security: WPA2, Mode: Infrastructure)
SSID 'TP-LINK_ORANGE', MAC B0:95:8E:05:82:CA, RSSI 56, Channel 6Capability: 0x0431 (Security: WPA2, Mode: Infrastructure)
SSID 'vivo Y29L', MAC F4:29:81:98:F3:78, RSSI 49, Channel 6Capability: 0x8431 (Security: WPA2, Mode: Infrastructure)
SSID 'CDU_Free', MAC D4:61:FE:71:36:D0, RSSI 72, Channel 6Capability: 0x8421 (Security: Unsecured, Mode: Infrastructure)
SSID 'CDU', MAC D4:61:FE:71:36:D1, RSSI 73, Channel 6Capability: 0x0431 (Security: WPA2, Mode: Infrastructure)
SSID 'CMCC-EDU', MAC D6:14:4B:6F:A6:0E, RSSI 58, Channel 11Capability: 0x0621 (Security: Unsecured, Mode: Infrastructure)
SSID 'CMCC-Young', MAC D6:14:4B:6F:A6:0F, RSSI 58, Channel 11Capability: 0x0621 (Security: Unsecured, Mode: Infrastructure)
SSID 'xgxy666', MAC DC:FE:18:67:76:14, RSSI 73, Channel 11Capability: 0x0431 (Security: WPA2, Mode: Infrastructure)
SSID '10505diansai', MAC 50:FA:84:53:5B:8E, RSSI 77, Channel 13Capability: 0x0411 (Security: WPA2, Mode: Infrastructure)
Scan finished!
Waiting for authentication!
Message 1 received!
Message 2 sent!
Message 3 received!
Message 4 sent!
Authenticated!
[Send] len=350
PTK & GTK set!
[Recv] len=351
[Send] len=350
[Recv] len=351
[Send] len=42
[Send] len=42
[Send] len=42
[Send] len=42
[Send] len=42
[Send] len=42
[Send] len=42
DHCP supplied address at 0.65s!
IP address: 192.168.43.64
Subnet mask: 255.255.255.0
Default gateway: 192.168.43.1
DNS Server: 192.168.43.1
[Send] len=42
Not in cache! err=-5
[Recv] len=42
[Send] len=76
[Recv] len=92
DNS Found IP: 106.186.126.193
Connecting to 106.186.126.193...
[Send] len=58
[Recv] len=58
Connected! err=0
Connection is successfully closed!
[Send] len=54
[Send] len=42
[Recv] len=54
[Send] len=54
[Send] len=42
[Send] len=42
[Send] len=42
[Recv] len=74
[Send] len=58
[Recv] len=54
[Recv] len=524
[Send] len=590
[Send] len=590
[Recv] len=54
[Send] len=590
[Recv] len=54
[Send] len=304
[Recv] len=54
[Recv] len=54
[Send] len=54
[Recv] len=74
[Send] len=58
[Recv] len=54
[Recv] len=488
[Send] len=590
[Send] len=349
[Recv] len=54
[Recv] len=54
[Send] len=54
[Recv] len=74
[Send] len=58
[Recv] len=54
[Recv] len=419
[Send] len=590
[Send] len=202
[Recv] len=54
[Recv] len=54
[Send] len=54

【程序主要代码(库函数版本)】

main.c:

#include <lwip/apps/httpd.h> // http服务器
#include <lwip/apps/netbiosns.h> // NetBIOS服务
#include <lwip/dhcp.h> // DHCP客户端
#include <lwip/dns.h> // DNS客户端
#include <lwip/init.h> // lwip_init函数所在的头文件
#include <lwip/timeouts.h> // sys_check_timeouts函数所在的头文件
#include <netif/ethernet.h> // ethernet_input函数所在头文件
#include <stm32f10x.h>
#include <string.h>
#include "common.h"
#include "WiFi.h"// 这两个函数位于ethernetif.c中, 但没有头文件声明
err_t ethernetif_init(struct netif *netif);
void ethernetif_input(struct netif *netif);// 这两个函数位于dns_test.c中
void dns_test(void);
void display_time(void);static struct netif wifi_88w8686;
#if LWIP_DHCP
static uint32_t dhcp_start_time = 0;
#endifint 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;
}/* 通知lwip网卡的连接状态 */
static void set_netif(struct netif *netif, uint8_t up)
{if (up){if (!netif_is_up(netif)){netif_set_up(netif);
#if LWIP_DHCPdhcp_start(netif); // 路由器中显示的DHCP客户名称在ethernetif_init函数中设置dhcp_start_time = sys_now();
#endif}}else{netif_set_down(netif);
#if LWIP_DHCPdhcp_release(netif);dhcp_stop(netif);
#endif}
}/* WiFi认证成功回调函数 */
void WiFi_AuthenticationCompleteHandler(void)
{printf("Authenticated!\n");set_netif(&wifi_88w8686, 1); // 在lwip中启用WiFi网卡
}/* 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");set_netif(&wifi_88w8686, 0);break;case 4:// Ad-Hoc网络中不止1个结点, 且连接数发生了变化printf("The number of stations in this ad hoc newtork has changed!\n");set_netif(&wifi_88w8686, 1);break;case 8:// 认证已解除 (例如手机关闭了热点, 或者连接路由器后因认证失败而自动断开连接)printf("Deauthenticated!\n");set_netif(&wifi_88w8686, 0);break;case 9:// 解除了关联printf("Disassociated!\n");set_netif(&wifi_88w8686, 0);break;case 17:// Ad-Hoc网络中只剩本结点printf("All other stations have been away from this ad hoc network!\n");set_netif(&wifi_88w8686, 0);break;case 30:printf("IBSS coalescing process is finished and BSSID has changed!\n");break;}if (event->header.length > sizeof(WiFi_Event))dump_data(event + 1, event->header.length - sizeof(WiFi_Event));
}/* WiFi模块收到新的数据帧 */
void WiFi_PacketHandler(const WiFi_DataRx *data)
{ethernetif_input(&wifi_88w8686); // 交给lwip处理
}void associate_callback(void *arg, void *data, WiFi_Status status)
{switch (status){case WIFI_STATUS_OK:printf("Associated!\n");set_netif(&wifi_88w8686, 1); // 在lwip中启用WiFi网卡break;case WIFI_STATUS_NOTFOUND:printf("SSID not found!\n");break;case WIFI_STATUS_FAIL:printf("Association failed!\n");break;case WIFI_STATUS_INPROGRESS:printf("Waiting for authentication!\n");break;default:printf("Unknown error! status=%d\n", status);}
}void associate_example(void)
{WiFi_Connection conn;//WiFi_WEPKey wepkey = {0}; // 未使用的成员必须设为0/*conn.security = WIFI_SECURITYTYPE_WEP;conn.ssid = "Oct1158-2";conn.password = &wepkey;wepkey.keys[0] = "1234567890123";wepkey.index = 0; // 密钥序号必须要正确WiFi_AssociateEx(&conn, WIFI_AUTH_MODE_OPEN, -1, associate_callback, NULL); // 开放系统方式//WiFi_AssociateEx(&conn, WIFI_AUTH_MODE_SHARED, -1, associate_callback, NULL); // 共享密钥方式*/conn.security = WIFI_SECURITYTYPE_WPA; // WPA和WPA2都可以使用此选项conn.ssid = "vivo Y29L";conn.password = "2345678am"; // WPA密码直接指定, 不需要WiFi_WEPKey结构体WiFi_AssociateEx(&conn, WIFI_AUTH_MODE_OPEN, -1, associate_callback, NULL); // 必须使用WIFI_AUTH_MODE_OPEN选项
}void adhoc_callback(void *arg, void *data, WiFi_Status status)
{if (status == WIFI_STATUS_OK)printf("ADHOC %sed!\n", (char *)arg);elseprintf("Cannot %s ADHOC!\n", (char *)arg);
}void adhoc_example(void)
{WiFi_Connection conn;WiFi_WEPKey wepkey = {0}; // 未使用的成员必须设为0/*conn.security = WIFI_SECURITYTYPE_WEP;conn.ssid = "Octopus-WEP";conn.password = &wepkey;wepkey.keys[0] = "3132333435";wepkey.index = 0; // 范围: 0~3WiFi_JoinADHOCEx(&conn, -1, adhoc_callback, "join");*////*// 注意: 电脑上无论密码输入是否正确都可以连接, 但只有正确的密码才可以通信conn.security = WIFI_SECURITYTYPE_WEP;conn.ssid = "WM-G-MR-09";conn.password = &wepkey;wepkey.keys[0] = "1234567890123";wepkey.index = 0;WiFi_StartADHOCEx(&conn, adhoc_callback, "start");//*//*conn.security = WIFI_SECURITYTYPE_NONE;conn.ssid = "Octopus-WEP";WiFi_JoinADHOCEx(&conn, -1, adhoc_callback, "join");*//*conn.security = WIFI_SECURITYTYPE_NONE;conn.ssid = "WM-G-MR-09";WiFi_StartADHOCEx(&conn, adhoc_callback, "start");*/
}void scan_callback(void *arg, void *data, WiFi_Status status)
{if (status == WIFI_STATUS_OK)printf("Scan finished!\n");elseprintf("Scan failed!\n");//adhoc_example();associate_example();
}// 获取网卡MAC地址成功后, 就立即将网卡添加到lwip中, 但暂不把网卡设为"已连接"状态
void mac_address_callback(void *arg, void *data, WiFi_Status status)
{
#if !LWIP_DHCPstruct ip4_addr ipaddr, netmask, gw;
#endifif (status == WIFI_STATUS_OK){WiFi_Scan(scan_callback, NULL); // 扫描热点memcpy(wifi_88w8686.hwaddr, data, 6); // 将获得的MAC地址复制到全局变量中printf("MAC Addr: %02X:%02X:%02X:%02X:%02X:%02X\n", wifi_88w8686.hwaddr[0], wifi_88w8686.hwaddr[1], wifi_88w8686.hwaddr[2], wifi_88w8686.hwaddr[3], wifi_88w8686.hwaddr[4], wifi_88w8686.hwaddr[5]);#if LWIP_DHCPnetif_add(&wifi_88w8686, IP_ADDR_ANY, IP_ADDR_ANY, IP_ADDR_ANY, NULL, ethernetif_init, ethernet_input);
#elseIP4_ADDR(&ipaddr, 192, 168, 43, 15); // 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); // 添加WiFi模块到lwip中#if LWIP_DNSIP4_ADDR(&ipaddr, 8, 8, 8, 8); // 首选DNS服务器dns_setserver(0, &ipaddr);IP4_ADDR(&ipaddr, 8, 8, 4, 4); // 备用DNS服务器dns_setserver(1, &ipaddr);
#endif
#endifnetif_set_default(&wifi_88w8686); // 设为默认网卡}elseprintf("Cannot get MAC address!\n");
}void stop_callback(void *arg, void *data, WiFi_Status status)
{char *s1 = (char *)arg;char *s2 = s1 + strlen(s1) + 1;if (status == WIFI_STATUS_OK){set_netif(&wifi_88w8686, 0);printf("%s %s!\n", s1, s2);}elseprintf("%s not %s!\n", s1, s2);
}int main(void)
{GPIO_InitTypeDef gpio;USART_InitTypeDef usart;
#if LWIP_DHCPstruct dhcp *dhcp;
#endifuint8_t data;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE);// 串口发送引脚PA9设为复用推挽输出, 串口接收引脚PA10保持默认的浮空输入gpio.GPIO_Mode = GPIO_Mode_AF_PP;gpio.GPIO_Pin = GPIO_Pin_9;gpio.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &gpio);USART_StructInit(&usart);usart.USART_BaudRate = 115200;USART_Init(USART1, &usart);USART_Cmd(USART1, ENABLE);printf("STM32F103RE SDIO 88W8686\n");rtc_init();WiFi_Init();WiFi_GetMACAddress(mac_address_callback, NULL);lwip_init();netbiosns_init();netbiosns_set_name("STM32F103RE"); // 计算机名httpd_init();while (1){// WiFi模块中断和超时处理if (SDIO_GetFlagStatus(SDIO_FLAG_SDIOIT) == SET){SDIO_ClearFlag(SDIO_FLAG_SDIOIT);WiFi_Input();}elseWiFi_CheckTimeout();// lwip协议栈定时处理函数sys_check_timeouts();// 显示DHCP获取到的IP地址
#if LWIP_DHCPif (dhcp_supplied_address(&wifi_88w8686)){if (dhcp_start_time != 0){printf("DHCP supplied address at %.2fs!\n", (sys_now() - dhcp_start_time) / 1000.0);dhcp_start_time = 0;dhcp = netif_dhcp_data(&wifi_88w8686);printf("IP address: %s\n", ip4addr_ntoa(&dhcp->offered_ip_addr));printf("Subnet mask: %s\n", ip4addr_ntoa(&dhcp->offered_sn_mask));printf("Default gateway: %s\n", ip4addr_ntoa(&dhcp->offered_gw_addr));
#if LWIP_DNSprintf("DNS Server: %s\n", ip4addr_ntoa(dns_getserver(0)));dns_test();
#endif}}
#endif// 串口调试命令if (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == SET){data = USART_ReceiveData(USART1);switch (data){case 'A':// 离开ADHOC网络WiFi_StopADHOC(stop_callback, "ADHOC\0stopped"); // 如果当前未处于ADHOC模式, 则命令不会收到回应, 但最终回调函数肯定会调用break;case 'D':// 取消关联热点WiFi_Deauthenticate(3, stop_callback, "Connection\0closed"); // LEAVING_NETWORK_DEAUTHbreak;case 'd':// WiFi模块规定, 如果程序一直没有读取新收到的帧, 则不会接收新的帧// 这个命令用于强制丢弃WiFi缓冲区中的数据WiFi_DiscardData();break;
#if LWIP_DNScase 'n':// DNS测试dns_test();break;
#endifcase 'R':// 重新连接热点if (netif_is_up(&wifi_88w8686))printf("Please disconnect first!\n");else{printf("Reconnecting...\n");associate_example();}break;case 's':// 显示状态寄存器的值printf("SDIO->STA=0x%08x, ", SDIO->STA);printf("CARDSTATUS=%d, INTSTATUS=%d\n", WiFi_LowLevel_ReadReg(1, WIFI_CARDSTATUS), WiFi_LowLevel_ReadReg(1, WIFI_INTSTATUS));break;case 't':// 显示当前时间display_time();break;}}}
}void HardFault_Handler(void)
{printf("Hard Error!\n");while (1);
}

common.h:

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

common.c:

#include <stdio.h>
#include <stm32f10x.h>
#include <time.h>
#include "common.h"#define RTC_USELSI // 因为板上没有LSE晶振, 所以RTC时钟选LSI#ifdef RTC_USELSI
#define RTC_PRESCALER 40000 // LSI频率
#else
#define RTC_PRESCALER 32768 // LSE频率
#endif/* 延时n毫秒 (不精确) */
// 实际延迟的时间t: nms<t<=nms+1
void delay(uint16_t nms)
{uint32_t newtime = sys_now() + nms;while (sys_now() <= newtime);
}/* 显示数据内容 */
void dump_data(const void *data, uint32_t len)
{const uint8_t *p = data;while (len--)printf("%02X", *p++);printf("\n");
}/* 初始化RTC外设 */
// 如果sys_now函数不是用RTC实现的, 则可以删掉这个函数
void rtc_init(void)
{RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);PWR_BackupAccessCmd(ENABLE); // 允许写后备寄存器(如RCC->BDCR)#ifdef RTC_USELSIRCC_LSICmd(ENABLE);while (RCC_GetFlagStatus(RCC_FLAG_LSIRDY) == RESET); // 等待LSI启动
#elseif (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET){RCC_LSEConfig(RCC_LSE_ON);while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET); // 等待LSE启动}
#endifif ((RCC->BDCR & RCC_BDCR_RTCEN) == 0) // 这个操作无法用库函数代码表示{// 若RTC未打开, 则初始化RTC// 必须要先选择时钟, 然后再开启RTC时钟
#ifdef RTC_USELSIRCC_RTCCLKConfig(RCC_RTCCLKSource_LSI); // 选LSI作为RTC时钟
#elseRCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
#endifRCC_RTCCLKCmd(ENABLE); // 开启RTC时钟, RTC开始走时RTC_SetPrescaler(RTC_PRESCALER - 1); // 设置分频系数: 定时1sRTC_WaitForLastTask();//RTC_SetCounter(50); // 设置初始时间: STM32F1系列的RTC没有年月日、小时等寄存器, 只有一个32位的计数器, 要想实现日期和时间的功能必须调用C库<time.h>中的mktime函数, 用软件来实现这些功能//RTC_WaitForLastTask();}elseRTC_WaitForSynchro(); // 等待RTC与APB1时钟同步
}/* RTC时间转化为毫秒数 (lwip协议栈要求实现的函数) */
uint32_t sys_now(void)
{uint32_t sec = time(NULL); // 秒uint32_t div = RTC_GetDivider();uint32_t milli = (RTC_PRESCALER - div - 1) * 1000 / RTC_PRESCALER; // 毫秒return sec * 1000 + milli;
}/* 获取RTC秒数 */
time_t time(time_t *timer)
{time_t t = RTC_GetCounter();if (timer)*timer = t;return t;
}

WiFi.h:

/* 选项 */
#define WIFI_DEFAULT_MAXRETRY 2 // 命令帧默认最大尝试发送的次数
#define WIFI_DEFAULT_TIMEOUT_CMDACK 20 // WiFi命令帧确认的超时时间(ms)
#define WIFI_DEFAULT_TIMEOUT_CMDRESP 1000 // WiFi命令帧回应的超时时间(ms): 如果发现命令无论怎么重传都收不到回应, 就要考虑增大超时时间
#define WIFI_DEFAULT_TIMEOUT_DATAACK 20 // WiFi数据帧确认的超时时间(ms)
#define WIFI_DISPLAY_PACKET_SIZE // 显示收发的数据包的大小
//#define WIFI_DISPLAY_PACKET_RX // 显示收到的数据包内容
//#define WIFI_DISPLAY_PACKET_TX // 显示发送的数据包内容
//#define WIFI_DISPLAY_RESPTIME // 显示命令帧和数据帧从发送到收到确认和回应所经过的时间
#define WIFI_HIGHSPEED // 采用SDIO高速模式
#define WIFI_USEDMA // SDIO采用DMA方式收发数据#define WIFI_FIRMWAREAREA_ADDR 0x08061000 // 固件存储区首地址#ifdef WIFI_FIRMWAREAREA_ADDR
// 固件存储区的格式: helper固件大小(4B)+固件大小(4B)+helper固件内容+固件内容+CRC校验码(4B)
#define WIFI_HELPER_SIZE (*(const uint32_t *)WIFI_FIRMWAREAREA_ADDR)
#define WIFI_FIRMWARE_SIZE (*(const uint32_t *)(WIFI_FIRMWAREAREA_ADDR + 4))
#define WIFI_HELPER_ADDR ((const uint8_t *)WIFI_FIRMWAREAREA_ADDR + 8)
#define WIFI_FIRMWARE_ADDR (WIFI_HELPER_ADDR + WIFI_HELPER_SIZE)
#else
// 这里sd表示这两个固件是SDIO接口专用的, G-SPI接口对应的则是gspi
// 取消WIFI_FIRMWAREAREA_ADDR宏定义后, 需要把helper_sd.c和sd8686.c添加到工程中来, 并确保变量的声明已添加__attribute__((aligned))
extern const unsigned char firmware_helper_sd[2516];
extern const unsigned char firmware_sd8686[122916];
#define WIFI_HELPER_SIZE sizeof(firmware_helper_sd)
#define WIFI_FIRMWARE_SIZE sizeof(firmware_sd8686)
#define WIFI_HELPER_ADDR firmware_helper_sd
#define WIFI_FIRMWARE_ADDR firmware_sd8686
#endif/* WiFi寄存器及位定义 */
#define _BV(n) (1u << (n))// 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 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_ALL 0x0f
#define WIFI_INTSTATUS_OVERFLOW _BV(3)
#define WIFI_INTSTATUS_UNDERFLOW _BV(2)
#define WIFI_INTSTATUS_DNLD _BV(1) // Download 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/* WiFi模块用到的枚举类型 */
// Authentication Type to be used to authenticate with AP
typedef enum
{WIFI_AUTH_MODE_OPEN = 0x00,WIFI_AUTH_MODE_SHARED = 0x01,WIFI_AUTH_MODE_NETWORK_EAP = 0x80
} WiFi_AuthenticationType;// BSS type
typedef enum
{WIFI_BSS_INFRASTRUCTURE = 0x01,WIFI_BSS_INDEPENDENT = 0x02,WIFI_BSS_ANY = 0x03
} WiFi_BSSType;// 部分WiFi命令的action字段
typedef enum
{WIFI_ACT_GET = 0,WIFI_ACT_SET = 1,WIFI_ACT_ADD = 2,WIFI_ACT_BITWISE_SET = 2,WIFI_ACT_BITWISE_CLR = 3,WIFI_ACT_REMOVE = 4
} WiFi_CommandAction;// WiFi命令列表
typedef enum
{CMD_802_11_SCAN = 0x0006, // Starts the scan processCMD_802_11_ASSOCIATE = 0x0012, // Initiate an association with the APCMD_802_11_SET_WEP = 0x0013, // Configures the WEP keysCMD_802_11_DEAUTHENTICATE = 0x0024, // Starts de-authentication process with the APCMD_MAC_CONTROL = 0x0028, // Controls hardware MACCMD_802_11_AD_HOC_START = 0x002b, // Starts an Ad-Hoc networkCMD_802_11_AD_HOC_JOIN = 0x002c, // Join an Ad-Hoc networkCMD_802_11_AD_HOC_STOP = 0x0040, // Stops Ad-Hoc NetworkCMD_802_11_MAC_ADDR = 0x004d, // WLAN MAC addressCMD_802_11_KEY_MATERIAL = 0x005e, // Gets/sets key material used to do Tx encryption or Rx decryptionCMD_802_11_BG_SCAN_CONFIG = 0x006b, // Gets/sets background scan configurationCMD_802_11_BG_SCAN_QUERY = 0x006c, // Gets background scan resultsCMD_802_11_SUBSCRIBE_EVENT = 0x0075 // Subscribe to events and set thresholds
} WiFi_CommandList;// WiFi命令执行结果
typedef enum
{CMD_STATUS_SUCCESS = 0x0000, // No errorCMD_STATUS_ERROR = 0x0001, // Command failedCMD_STATUS_UNSUPPORTED = 0x0002 // Command is not supported (result=2表示WiFi模块不支持此命令)
} WiFi_CommandResult;// WiFi密钥类型
typedef enum
{WIFI_KEYTYPE_WEP = 0,WIFI_KEYTYPE_TKIP = 1,WIFI_KEYTYPE_AES = 2
} WiFi_KeyType;// Table 45: IEEE 802.11 Standard IE Translated to Marvell IE
// PDF中的表45有一些拼写错误, MRVIIE应该改为MRVLIE
typedef enum
{WIFI_MRVLIETYPES_SSIDPARAMSET = 0x0000,WIFI_MRVLIETYPES_RATESPARAMSET = 0x0001,WIFI_MRVLIETYPES_PHYPARAMDSSET = 0x0003,WIFI_MRVLIETYPES_CFPARAMSET = 0x0004,WIFI_MRVLIETYPES_IBSSPARAMSET = 0x0006,WIFI_MRVLIETYPES_RSNPARAMSET = 0x0030,WIFI_MRVLIETYPES_VENDORPARAMSET = 0x00dd,WIFI_MRVLIETYPES_KEYPARAMSET = 0x0100,WIFI_MRVLIETYPES_CHANLISTPARAMSET = 0x0101,WIFI_MRVLIETYPES_TSFTIMESTAMP = 0x0113,WIFI_MRVLIETYPES_AUTHTYPE = 0x011f
} WiFi_MrvlIETypes;// SDIO帧类型
typedef enum
{WIFI_SDIOFRAME_DATA = 0x00,WIFI_SDIOFRAME_COMMAND = 0x01,WIFI_SDIOFRAME_EVENT = 0x03
} WiFi_SDIOFrameType;// 16.5 SDIO Card Metaformat
typedef enum
{CISTPL_NULL = 0x00, // Null tupleCISTPL_VERS_1 = 0x15, // Level 1 version/product-informationCISTPL_MANFID = 0x20, // Manufacturer Identification String TupleCISTPL_FUNCID = 0x21, // Function Identification TupleCISTPL_FUNCE = 0x22, // Function ExtensionsCISTPL_END = 0xff // The End-of-chain Tuple
} WiFi_SDIOTupleCode;// 无线认证类型
typedef enum
{WIFI_SECURITYTYPE_NONE = 0,WIFI_SECURITYTYPE_WEP = 1,WIFI_SECURITYTYPE_WPA = 2,WIFI_SECURITYTYPE_WPA2 = 3
} WiFi_SecurityType;// 回调函数中的状态参数
typedef enum
{WIFI_STATUS_OK = 0, // 成功收到了回应WIFI_STATUS_FAIL = 1, // 未能完成请求的操作 (例如找到了AP热点但关联失败)WIFI_STATUS_BUSY = 2, // 之前的操作尚未完成WIFI_STATUS_NORESP = 3, // 重试了几遍都没有收到回应WIFI_STATUS_MEM = 4, // 内存不足WIFI_STATUS_INVALID = 5, // 无效的参数WIFI_STATUS_NOTFOUND = 6, // 未找到目标 (如AP热点)WIFI_STATUS_INPROGRESS = 7 // 成功执行命令, 但还需要后续的操作 (比如关联AP成功但还需要后续的认证操作)
} WiFi_Status;// WEP密钥长度
typedef enum
{WIFI_WEPKEYTYPE_40BIT = 1,WIFI_WEPKEYTYPE_104BIT = 2
} WiFi_WEPKeyType;/* 回调函数类型 */
typedef void (*WiFi_Callback)(void *arg, void *data, WiFi_Status status); // data为NULL表示没有收到任何回应/* WiFi命令字段位定义 */
// Capability information
#define WIFI_CAPABILITY_ESS _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_SHORT_SLOT _BV(10)
#define WIFI_CAPABILITY_DSSS_OFDM _BV(13)#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/* 常用的宏函数 */
// 已知结构体大小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)))// 字节序转换函数
#ifndef htons
#define htons(x) ((((x) & 0x00ffUL) << 8) | (((x) & 0xff00UL) >> 8))
#endif
#ifndef ntohs
#define ntohs htons
#endif#define WiFi_GetCommandCode(data) (((data) == NULL) ? 0 : (((const WiFi_CommandHeader *)(data))->cmd_code & 0x7fff))
#define WiFi_IsCommandResponse(data) ((data) != NULL && ((const WiFi_CommandHeader *)(data))->cmd_code & 0x8000)/* 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;uint8_t scan_type;uint16_t min_scan_time;uint16_t max_scan_time;} channels[1];
} MrvlIETypes_ChanListParamSet_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 channel;
} MrvlIETypes_PhyParamDSSet_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命令帧和数据帧格式 */
// 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;typedef __packed struct
{WiFi_CommandHeader header;uint8_t bssid[6]; // MAC addressuint8_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 timestampIEEETypes_DsParamSet_t ds_param_set; // IEEE DS parameter set elementuint32_t reserved1;IEEETypes_IbssParamSet_t ibss_param_set; // IEEE IBSS parameter setuint32_t reserved2;uint16_t cap_info;uint8_t data_rates[14];uint32_t reserved3;
} WiFi_Cmd_ADHOCJoin;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 TUuint32_t reserved2;IEEETypes_DsParamSet_t ds_param_set; // The channel for ad-hoc networkuint8_t reserved3[6];uint16_t cap_info; // Capability informationuint8_t data_rate[14];
} WiFi_Cmd_ADHOCStart;typedef __packed struct
{WiFi_CommandHeader header;uint8_t peer_sta_addr[6];uint16_t reason_code; // Reason code defined in IEEE 802.11 specification section 7.3.1.7 to indicate de-authentication reason
} WiFi_Cmd_Deauthenticate;typedef __packed struct
{WiFi_CommandHeader header;uint16_t action;MrvlIETypes_KeyParamSet_t keys;
} 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 bitsuint8_t keys[4][16];
} WiFi_Cmd_SetWEP;typedef __packed struct
{WiFi_CommandHeader header;uint8_t peer_sta_addr[6]; // Peer MAC addressuint16_t cap_info; // Capability informationuint16_t listen_interval; // Listen intervaluint16_t bcn_period; // Beacon perioduint8_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;// WiFi模块接收的数据帧
// Table 2: Fields in Receive Packet Descriptor
typedef __packed struct
{WiFi_SDIOFrameHeader header;uint16_t reserved1;int8_t snr; // Signal to noise ratio for this packet (dB)uint8_t reserved2;uint16_t rx_packet_length; // Number of bytes in the payloaduint8_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 receiveduint32_t rx_packet_offset; // Offset from the start of the packet to the beginning of the payload data packetuint32_t reserved3;uint8_t priority; // Specifies the user priority of received packetuint8_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 Settingsuint32_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 frameuint16_t tx_dest_addr_high; // Destination MAC address bytes 4 to 5uint32_t tx_dest_addr_low; // Destination MAC address bytes 0 to 3uint8_t priority; // Specifies the user priority of transmit packetuint8_t flags;uint8_t pkt_delay_2ms; // Amount of time the packet has been queued in the driver layer for WMM implementationsuint8_t reserved2;uint8_t payload[1]; // 数据链路层上的帧
} WiFi_DataTx;// EAPOL认证帧
// https://www.vocal.com/secure-communication/eapol-extensible-authentication-protocol-over-lan/
typedef __packed struct
{uint8_t dest[6];uint8_t src[6];uint16_t type;uint8_t version;uint8_t packet_type;uint16_t packet_body_length; // big endian/* packet body */// 802.11-2016.pdf: Figure 12-32 EAPOL-Key frame// http://etutorials.org/Networking/802.11+security.+wi-fi+protected+access+and+802.11i/Part+II+The+Design+of+Wi-Fi+Security/Chapter+10.+WPA+and+RSN+Key+Hierarchy/Details+of+Key+Derivation+for+WPA/uint8_t descriptor_type;uint16_t key_information;uint16_t key_length;uint8_t key_replay_counter[8];uint8_t key_nonce[32];uint8_t key_iv[16];uint8_t key_rsc[8]; // receive sequence counteruint8_t reserved[8]; // not used in WPAuint8_t key_mic[16];uint16_t key_data_length;uint8_t key_data[1];
} WiFi_EAPOLKeyFrame;// WiFi模块事件帧
typedef __packed struct
{WiFi_SDIOFrameHeader header;uint32_t event_id; // Enumerated identifier for the eventuint16_t reason_code; // IEEE Reason Code as described in the 802.11 standarduint8_t mac_addr[6]; // Peer STA Address
} WiFi_Event;/* WiFi的常用数据结构 */
typedef __packed struct
{uint16_t ie_length; // Total information element length (不含sizeof(ie_length))uint8_t bssid[6]; // BSSIDuint8_t rssi; // RSSI value as received from peer// Probe Response/Beacon Payloaduint64_t pkt_time_stamp; // Timestampuint16_t bcn_interval; // Beacon intervaluint16_t cap_info; // Capabilities informationIEEEType ie_parameters; // 存放的是一些IEEE类型的数据
} WiFi_BssDescSet;typedef struct
{WiFi_SecurityType security;char *ssid;void *password;
} WiFi_Connection;typedef __packed struct
{uint8_t TK[16];uint8_t TKIPTxMICKey[8];uint8_t TKIPRxMICKey[8];
} WiFi_GTK;typedef __packed struct
{uint8_t type;uint8_t length;uint8_t oui[3];uint8_t data_type;uint8_t data[1];
} WiFi_KDE;typedef __packed struct
{uint8_t MIC[16];uint8_t RESERVED[4]; // 用于给sha1函数留足缓冲区
} WiFi_MIC;typedef __packed struct
{uint8_t KCK[16];uint8_t KEK[16];uint8_t TK[16];uint8_t TKIPTxMICKey[8];uint8_t TKIPRxMICKey[8];
} WiFi_PTK;typedef __packed struct
{uint8_t MAC[2][6];uint8_t nonce[2][32];
} WiFi_PTKB;// WiFi热点信息
typedef 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;typedef struct
{WiFi_Callback callback;void *arg;uint8_t busy;uint8_t ready; // 数据帧缓冲区不使用这两个成员uint8_t retry; //uint32_t start_time;uint32_t timeout;
} WiFi_TxBuffer;typedef __packed struct
{uint8_t oui[3];uint8_t oui_type;uint16_t version;uint8_t multicast_oui[4];uint16_t unicast_num;uint8_t unicast_oui[1][4]; // 这里假定unicast_num=1uint16_t auth_num; // 只有当unicast_num=1时, 该成员才会在这个位置上uint8_t auth_oui[1][4];
} WiFi_Vendor;typedef struct
{char *keys[4];uint8_t index; // 0~3
} WiFi_WEPKey;/* WiFi模块底层函数 */
uint8_t WiFi_LowLevel_GetFunctionNum(void);
void WiFi_LowLevel_Init(void);
uint8_t WiFi_LowLevel_ReadData(uint8_t func, uint32_t addr, void *data, uint32_t size, uint32_t bufsize);
uint8_t WiFi_LowLevel_ReadReg(uint8_t func, uint32_t addr);
void WiFi_LowLevel_SetBlockSize(uint8_t func, uint32_t size);
uint8_t WiFi_LowLevel_WriteData(uint8_t func, uint32_t addr, const void *data, uint32_t size, uint32_t bufsize);
uint8_t WiFi_LowLevel_WriteReg(uint8_t func, uint32_t addr, uint8_t value);/* WiFi模块主要函数 */
void WiFi_Associate(const char *ssid, WiFi_AuthenticationType auth_type, WiFi_Callback callback, void *arg);
void WiFi_AssociateEx(const WiFi_Connection *conn, WiFi_AuthenticationType auth_type, int8_t max_retry, WiFi_Callback callback, void *arg);
void WiFi_CheckTimeout(void);
void WiFi_Deauthenticate(uint16_t reason, WiFi_Callback callback, void *arg);
void WiFi_DiscardData(void);
uint8_t WiFi_GetBSSID(uint8_t mac_addr[6]);
uint16_t WiFi_GetDataLength(void);
void WiFi_GetMACAddress(WiFi_Callback callback, void *arg);
uint8_t *WiFi_GetPacketBuffer(void);
const uint8_t *WiFi_GetReceivedPacket(uint16_t *len);
WiFi_SecurityType WiFi_GetSecurityType(const WiFi_SSIDInfo *info);
void WiFi_Init(void);
void WiFi_Input(void);
uint8_t WiFi_IsCommandBusy(void);
void WiFi_JoinADHOC(const char *ssid, WiFi_Callback callback, void *arg);
void WiFi_JoinADHOCEx(const WiFi_Connection *conn, int8_t max_retry, WiFi_Callback callback, void *arg);
void WiFi_KeyMaterial(WiFi_CommandAction action, MrvlIETypes_KeyParamSet_t *key, uint8_t key_count, WiFi_Callback callback, void *arg);
void WiFi_MACAddr(const uint8_t newaddr[6], WiFi_CommandAction action, WiFi_Callback callback, void *arg);
void WiFi_MACControl(uint16_t action, WiFi_Callback callback, void *arg);
void WiFi_Scan(WiFi_Callback callback, void *arg);
void WiFi_ScanSSID(const char *ssid, WiFi_SSIDInfo *info, WiFi_Callback callback, void *arg);
void WiFi_SendCommand(uint16_t code, const void *data, uint16_t size, WiFi_Callback callback, void *arg, uint32_t timeout, uint8_t max_retry);
void WiFi_SendPacket(void *data, uint16_t size, WiFi_Callback callback, void *arg, uint32_t timeout);
void WiFi_SetWEP(WiFi_CommandAction action, const WiFi_WEPKey *key, WiFi_Callback callback, void *arg);
void WiFi_SetWPA(const char *ssid, const char *password);
void WiFi_ShowCIS(void);
void WiFi_StartADHOC(const char *ssid, uint16_t cap_info, WiFi_Callback callback, void *arg);
void WiFi_StartADHOCEx(const WiFi_Connection *conn, WiFi_Callback callback, void *arg);
void WiFi_StopADHOC(WiFi_Callback callback, void *arg);
uint8_t WiFi_TranslateTLV(MrvlIEType *mrvlie_tlv, const IEEEType *ieee_tlv, uint16_t mrvlie_payload_size);
uint8_t WiFi_Wait(uint8_t status, uint32_t timeout);
void WiFi_WaitForLastTask(void);/* 外部自定义回调函数 */
void WiFi_AuthenticationCompleteHandler(void);
void WiFi_EventHandler(const WiFi_Event *event);
void WiFi_PacketHandler(const WiFi_DataRx *data);

WiFi_LowLevel.c:

// 定义与单片机寄存器操作相关的函数, 方便在不同平台间移植
#include <stdio.h>
#include <stm32f10x.h>
#include <string.h>
#include "common.h"
#include "WiFi.h"#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)// 高速模式下必须使用DMA
#if defined(WIFI_HIGHSPEED) && !defined(WIFI_USEDMA)
#error "DMA must be enabled when SDIO is in high speed mode!"
#endifstatic uint8_t WiFi_LowLevel_CheckError(const char *msg_title);
static uint16_t WiFi_LowLevel_GetBlockNum(uint8_t func, uint32_t *psize);
static void WiFi_LowLevel_GPIOInit(void);
static void WiFi_LowLevel_SDIOInit(void);
static void WiFi_LowLevel_SendCMD52(uint8_t func, uint32_t addr, uint8_t data, uint32_t flags);
static void WiFi_LowLevel_SendCMD53(uint8_t func, uint32_t addr, uint16_t count, uint32_t flags);
static void WiFi_LowLevel_SetSDIOBlockSize(uint32_t size);
#ifdef WIFI_FIRMWAREAREA_ADDR
static uint8_t WiFi_LowLevel_VerifyFirmware(void);
#endif
static void WiFi_LowLevel_WaitForResponse(const char *msg_title);static uint16_t sdio_block_size[2]; // 各功能区的块大小, 保存在此变量中避免每次都去发送CMD52命令读SDIO寄存器
static uint8_t sdio_func_num = 0; // 功能区总数 (0号功能区除外)
static uint16_t sdio_rca; // RCA相对地址: 虽然SDIO标准规定SDIO接口上可以接多张SD卡, 但是STM32的SDIO接口只能接一张卡 (芯片手册上有说明)
static SDIO_CmdInitTypeDef sdio_cmd;
static SDIO_DataInitTypeDef sdio_data;/* 检查并清除错误标志位 */
static uint8_t WiFi_LowLevel_CheckError(const char *msg_title)
{uint8_t err = 0;if (SDIO_GetFlagStatus(SDIO_FLAG_CCRCFAIL) == SET){SDIO_ClearFlag(SDIO_FLAG_CCRCFAIL);err++;printf("%s: CMD%d CRC failed!\n", msg_title, SDIO->CMD & SDIO_CMD_CMDINDEX);}if (SDIO_GetFlagStatus(SDIO_FLAG_CTIMEOUT) == SET){SDIO_ClearFlag(SDIO_FLAG_CTIMEOUT);err++;printf("%s: CMD%d timeout!\n", msg_title, SDIO->CMD & SDIO_CMD_CMDINDEX);}if (SDIO_GetFlagStatus(SDIO_FLAG_DCRCFAIL) == SET){SDIO_ClearFlag(SDIO_FLAG_DCRCFAIL);err++;printf("%s: data CRC failed!\n", msg_title);}if (SDIO_GetFlagStatus(SDIO_FLAG_DTIMEOUT) == SET){SDIO_ClearFlag(SDIO_FLAG_DTIMEOUT);err++;printf("%s: data timeout!\n", msg_title);}if (SDIO_GetFlagStatus(SDIO_FLAG_STBITERR) == SET){SDIO_ClearFlag(SDIO_FLAG_STBITERR);err++;printf("%s: start bit error!\n", msg_title);}if (SDIO_GetFlagStatus(SDIO_FLAG_TXUNDERR) == SET){SDIO_ClearFlag(SDIO_FLAG_TXUNDERR);err++;printf("%s: data underrun!\n", msg_title);}if (SDIO_GetFlagStatus(SDIO_FLAG_RXOVERR) == SET){SDIO_ClearFlag(SDIO_FLAG_RXOVERR);err++;printf("%s: data overrun!\n", msg_title);}
#ifdef WIFI_USEDMAif (DMA_GetFlagStatus(DMA2_FLAG_TE4) == SET){DMA_ClearFlag(DMA2_FLAG_TE4);err++;printf("%s: DMA transfer error!\n", msg_title);}
#endifreturn err;
}/* 判断应该采用哪种方式传输数据 */
// 返回值: 0为多字节模式, 否则表示块传输模式的数据块数
// *psize的值会做适当调整, 有可能大于原值
static uint16_t WiFi_LowLevel_GetBlockNum(uint8_t func, uint32_t *psize)
{uint16_t block_num = 0;
#ifndef WIFI_HIGHSPEEDif (*psize > 512) // 大于512字节时才用数据块方式传输{
#endif// 块传输模式 (DTMODE=0)WiFi_LowLevel_SetSDIOBlockSize(sdio_block_size[func]);block_num = *psize / sdio_block_size[func];if (*psize % sdio_block_size[func] != 0)block_num++;*psize = block_num * sdio_block_size[func]; // 块数*块大小
#ifndef WIFI_HIGHSPEED}else{// 多字节传输模式 (DTMODE=1, SDIO的频率低于16MHz时才支持)*psize = (*psize + 3) & ~3; // WiFi模块要求写入的字节数必须为4的整数倍}
#endifreturn block_num;
}/* 获取WiFi模块支持SDIO功能区个数 (0号功能区除外) */
uint8_t WiFi_LowLevel_GetFunctionNum(void)
{return sdio_func_num;
}/* 初始化WiFi模块有关的所有GPIO引脚 */
static void WiFi_LowLevel_GPIOInit(void)
{GPIO_InitTypeDef gpio;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOD, ENABLE);// WiFi模块的电源引脚是通过场效应管(相当于PNP三极管)接到VCC上的// 基极接的是单片机的PB12, 发射极接的是电源VCC, 集电极接的是WiFi模块的VCC, 基极必须串联一个限流电阻// 单片机复位时PB12输出高阻态, 三极管不导通, WiFi模块不通电// 现将PB12设为输出低电平, 三极管导通, WiFi模块上电 (这起到了复位的效果)gpio.GPIO_Mode = GPIO_Mode_Out_PP; // PB12设为推挽输出, 并立即输出默认的低电平gpio.GPIO_Pin = GPIO_Pin_12;gpio.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &gpio);// SDIO相关引脚// PC8~11: SDIO_D0~3, PC12: SDIO_CK, 设为复用推挽输出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_CMD, 设为复用推挽输出gpio.GPIO_Pin = GPIO_Pin_2;GPIO_Init(GPIOD, &gpio);
}void WiFi_LowLevel_Init(void)
{// 在此处打开WiFi模块所需要的除GPIO和SDIO外所有其他外设的时钟RCC_AHBPeriphClockCmd(RCC_AHBPeriph_CRC, ENABLE);// 检查Flash中保存的固件内容是否已被破坏
#ifdef WIFI_FIRMWAREAREA_ADDRif (!WiFi_LowLevel_VerifyFirmware()){printf("Error: The firmware stored in flash memory is corrupted!\n");while (1);}
#endifWiFi_LowLevel_GPIOInit();WiFi_LowLevel_SDIOInit();
}/* 接收数据, 自动判断采用哪种传输模式 */
// size为要接收的字节数, bufsize为data缓冲区的大小
// 若bufsize=0, 则只读取数据, 但不保存到data中, 此时data可以为NULL
uint8_t WiFi_LowLevel_ReadData(uint8_t func, uint32_t addr, void *data, uint32_t size, uint32_t bufsize)
{uint16_t block_num; // 数据块个数
#ifdef WIFI_USEDMADMA_InitTypeDef dma;uint32_t temp; // 丢弃数据用的变量
#elseuint32_t *p = data;
#endifif ((uintptr_t)data & 3)printf("WiFi_LowLevel_ReadData: data must be 4-byte aligned!\n"); // DMA每次传输多个字节时, 内存和外设地址必须要对齐, 否则将不能正确传输且不会提示错误if (size == 0){printf("WiFi_LowLevel_ReadData: size cannot be 0!\n");return 0;}block_num = WiFi_LowLevel_GetBlockNum(func, &size);if (bufsize > 0 && bufsize < size){printf("WiFi_LowLevel_ReadData: a buffer of at least %d bytes is required! bufsize=%d\n", size, bufsize);return 0;}#ifdef WIFI_USEDMAdma.DMA_BufferSize = size / 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);
#endifif (block_num){sdio_data.SDIO_TransferMode = SDIO_TransferMode_Block;WiFi_LowLevel_SendCMD53(func, addr, block_num, CMD53_BLOCKMODE);}else{sdio_data.SDIO_TransferMode = SDIO_TransferMode_Stream;WiFi_LowLevel_SendCMD53(func, addr, size, 0);}sdio_data.SDIO_DataLength = size;sdio_data.SDIO_DPSM = SDIO_DPSM_Enable;sdio_data.SDIO_TransferDir = SDIO_TransferDir_ToSDIO;SDIO_DataConfig(&sdio_data);while (SDIO_GetFlagStatus(SDIO_FLAG_RXACT) == RESET); // 等待开始接收数据
#ifdef WIFI_USEDMAwhile (DMA_GetFlagStatus(DMA2_FLAG_TC4) == RESET && SDIO_GetFlagStatus(SDIO_FLAG_RXACT) == SET && SDIO_GetFlagStatus(SDIO_FLAG_RXOVERR) == RESET); // 等待DMA读取数据完毕且没有出现错误DMA_ClearFlag(DMA2_FLAG_TC4); // 清除DMA传输完成标志位DMA_Cmd(DMA2_Channel4, DISABLE); // 关闭DMA
#elsewhile (size && SDIO_GetFlagStatus(SDIO_FLAG_RXACT) == SET && SDIO_GetFlagStatus(SDIO_FLAG_RXOVERR) == RESET){// 如果有数据到来就读取数据if (SDIO_GetFlagStatus(SDIO_FLAG_RXDAVL) == SET){size -= 4;if (bufsize > 0)*p++ = SDIO_ReadData();elseSDIO_ReadData(); // 读寄存器, 但不保存数据}}
#endifwhile ((SDIO_GetFlagStatus(SDIO_FLAG_CMDACT) == SET || SDIO_GetFlagStatus(SDIO_FLAG_RXACT) == SET) && SDIO_GetFlagStatus(SDIO_FLAG_RXOVERR) == RESET); // 等待接收完毕sdio_data.SDIO_DPSM = SDIO_DPSM_Disable;SDIO_DataConfig(&sdio_data);// 清除相关标志位SDIO_ClearFlag(SDIO_FLAG_CMDREND | SDIO_FLAG_DATAEND | SDIO_FLAG_DBCKEND);return WiFi_LowLevel_CheckError(__func__) == 0;
}/* 读SDIO寄存器 */
uint8_t WiFi_LowLevel_ReadReg(uint8_t func, uint32_t addr)
{WiFi_LowLevel_SendCMD52(func, addr, 0, 0);WiFi_LowLevel_WaitForResponse(__func__);return SDIO_GetResponse(SDIO_RESP1) & 0xff;
}/* 初始化SDIO外设并完成WiFi模块的枚举 */
// SDIO Simplified Specification Version 3.00: 3. SDIO Card Initialization
static void WiFi_LowLevel_SDIOInit(void)
{SDIO_InitTypeDef sdio;uint32_t resp;// SDIO外设拥有两个时钟: SDIOCLK=HCLK=72MHz(分频后用于产生SDIO_CK=PC12引脚时钟), AHB bus clock=HCLK/2=36MHz(用于访问寄存器)RCC_AHBPeriphClockCmd(RCC_AHBPeriph_SDIO, ENABLE);
#ifdef WIFI_USEDMARCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA2, ENABLE);
#endifSDIO_SetPowerState(SDIO_PowerState_ON); // 打开SDIO外设SDIO_StructInit(&sdio);sdio.SDIO_ClockDiv = 178; // 初始化时最高允许的频率: 72MHz/(178+2)=400kHzSDIO_Init(&sdio);SDIO_ClockCmd(ENABLE);SDIO_SetSDIOOperation(ENABLE); // 设为SDIO模式
#ifdef WIFI_USEDMASDIO_DMACmd(ENABLE); // 设为DMA传输模式
#endif// 不需要发送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)delay(10); // 延时可防止CMD5重发/* 发送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);WiFi_LowLevel_WaitForResponse(__func__);printf("RESPCMD%d, RESP1_%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);WiFi_LowLevel_WaitForResponse(__func__);resp = SDIO_GetResponse(SDIO_RESP1);printf("RESPCMD%d, RESP1_%08x\n", SDIO_GetCommandResponse(), resp);if (resp & _BV(31)){// Card is ready to operate after initializationsdio_func_num = (resp >> 28) & 7;printf("Number of I/O Functions: %d\n", sdio_func_num);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);WiFi_LowLevel_WaitForResponse(__func__);sdio_rca = SDIO_GetResponse(SDIO_RESP1) >> 16;printf("Relative Card Address: 0x%04x\n", sdio_rca);/* 选中WiFi模块 (CMD7: SELECT/DESELECT_CARD) */sdio_cmd.SDIO_Argument = sdio_rca << 16;sdio_cmd.SDIO_CmdIndex = 7;SDIO_SendCommand(&sdio_cmd);WiFi_LowLevel_WaitForResponse(__func__);printf("Card selected! RESP1_%08x\n", SDIO_GetResponse(SDIO_RESP1));/* 提高时钟频率, 并设置数据超时时间为0.1s */
#ifdef WIFI_HIGHSPEEDsdio.SDIO_ClockDiv = 1; // 72MHz/(1+2)=24MHzsdio_data.SDIO_DataTimeOut = 2400000;printf("SDIO Clock: 24MHz\n");
#elsesdio.SDIO_ClockDiv = 70; // 72MHz/(70+2)=1MHzsdio_data.SDIO_DataTimeOut = 100000;printf("SDIO Clock: 1MHz\n");
#endif/* SDIO外设的总线宽度设为4位 */sdio.SDIO_BusWide = SDIO_BusWide_4b;SDIO_Init(&sdio);WiFi_LowLevel_WriteReg(0, SDIO_CCCR_BUSIFCTRL, WiFi_LowLevel_ReadReg(0, SDIO_CCCR_BUSIFCTRL) | SDIO_CCCR_BUSIFCTRL_BUSWID_4Bit);
}static void WiFi_LowLevel_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_cmd.SDIO_CPSM = SDIO_CPSM_Enable;sdio_cmd.SDIO_Response = SDIO_Response_Short;sdio_cmd.SDIO_Wait = SDIO_Wait_No;SDIO_SendCommand(&sdio_cmd);
}static void WiFi_LowLevel_SendCMD53(uint8_t func, uint32_t addr, uint16_t count, uint32_t flags)
{// 当count=512时, 和0x1ff相与后为0, 符合SDIO标准sdio_cmd.SDIO_Argument = (func << 28) | (addr << 9) | (count & 0x1ff) | flags;sdio_cmd.SDIO_CmdIndex = 53;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);
}/* 设置WiFi模块功能区的数据块大小 */
void WiFi_LowLevel_SetBlockSize(uint8_t func, uint32_t size)
{sdio_block_size[func] = size;WiFi_LowLevel_WriteReg(0, (func << 8) | 0x10, size & 0xff);WiFi_LowLevel_WriteReg(0, (func << 8) | 0x11, size >> 8);
}/* 设置SDIO外设的数据块大小 */
static void WiFi_LowLevel_SetSDIOBlockSize(uint32_t size)
{switch (size){case 1:sdio_data.SDIO_DataBlockSize = SDIO_DataBlockSize_1b;break;case 2:sdio_data.SDIO_DataBlockSize = SDIO_DataBlockSize_2b;break;case 4:sdio_data.SDIO_DataBlockSize = SDIO_DataBlockSize_4b;break;case 8:sdio_data.SDIO_DataBlockSize = SDIO_DataBlockSize_8b;break;case 16:sdio_data.SDIO_DataBlockSize = SDIO_DataBlockSize_16b;break;case 32:sdio_data.SDIO_DataBlockSize = SDIO_DataBlockSize_32b;break;case 64:sdio_data.SDIO_DataBlockSize = SDIO_DataBlockSize_64b;break;case 128:sdio_data.SDIO_DataBlockSize = SDIO_DataBlockSize_128b;break;case 256:sdio_data.SDIO_DataBlockSize = SDIO_DataBlockSize_256b;break;case 512:sdio_data.SDIO_DataBlockSize = SDIO_DataBlockSize_512b;break;case 1024:sdio_data.SDIO_DataBlockSize = SDIO_DataBlockSize_1024b;break;case 2048:sdio_data.SDIO_DataBlockSize = SDIO_DataBlockSize_2048b;break;case 4096:sdio_data.SDIO_DataBlockSize = SDIO_DataBlockSize_4096b;break;case 8192:sdio_data.SDIO_DataBlockSize = SDIO_DataBlockSize_8192b;break;case 16384:sdio_data.SDIO_DataBlockSize = SDIO_DataBlockSize_16384b;break;}
}/* 检查Flash中保存的固件内容是否完整 */
#ifdef WIFI_FIRMWAREAREA_ADDR
static uint8_t WiFi_LowLevel_VerifyFirmware(void)
{uint32_t crc;uint32_t len = (WIFI_HELPER_SIZE + WIFI_FIRMWARE_SIZE) / 4 + 3; // 固件区(包括CRC)总大小的1/4CRC_ResetDR();crc = CRC_CalcBlockCRC((uint32_t *)WIFI_FIRMWAREAREA_ADDR, len);return crc == 0;
}
#endif/* 等待SDIO命令回应 */
static void WiFi_LowLevel_WaitForResponse(const char *msg_title)
{uint8_t first = 1;do{if (!first)SDIO_SendCommand(&sdio_cmd); // 重发命令elsefirst = 0;while (SDIO_GetFlagStatus(SDIO_FLAG_CMDACT) == SET); // 等待命令发送完毕WiFi_LowLevel_CheckError(msg_title);} while (SDIO_GetFlagStatus(SDIO_FLAG_CMDREND) == RESET); // 如果没有收到回应, 则重试SDIO_ClearFlag(SDIO_FLAG_CMDREND);
}/* 发送数据, 自动判断采用哪种传输模式 */
// size为要发送的字节数, bufsize为data缓冲区的大小
uint8_t WiFi_LowLevel_WriteData(uint8_t func, uint32_t addr, const void *data, uint32_t size, uint32_t bufsize)
{uint16_t block_num; // 数据块个数
#ifdef WIFI_USEDMADMA_InitTypeDef dma;
#elseconst uint32_t *p = data;
#endifif ((uintptr_t)data & 3)printf("WiFi_LowLevel_WriteData: data must be 4-byte aligned!\n");if (size == 0){printf("WiFi_LowLevel_WriteData: size cannot be 0!\n");return 0;}block_num = WiFi_LowLevel_GetBlockNum(func, &size);if (bufsize < size)printf("WiFi_LowLevel_WriteData: a buffer of at least %d bytes is required! bufsize=%d\n", size, bufsize); // 只读缓冲区越界不会影响数据传输, 所以这只是一个警告if (block_num){sdio_data.SDIO_TransferMode = SDIO_TransferMode_Block;WiFi_LowLevel_SendCMD53(func, addr, block_num, CMD53_WRITE | CMD53_BLOCKMODE);}else{sdio_data.SDIO_TransferMode = SDIO_TransferMode_Stream;WiFi_LowLevel_SendCMD53(func, addr, size, CMD53_WRITE);}WiFi_LowLevel_WaitForResponse(__func__); // 必须要等到CMD53收到回应后才能开始发送数据// 开始发送数据
#ifdef WIFI_USEDMAdma.DMA_BufferSize = size / 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);
#endifsdio_data.SDIO_DataLength = size;sdio_data.SDIO_DPSM = SDIO_DPSM_Enable;sdio_data.SDIO_TransferDir = SDIO_TransferDir_ToCard;SDIO_DataConfig(&sdio_data);while (SDIO_GetFlagStatus(SDIO_FLAG_TXACT) == RESET); // 等待开始发送数据
#ifdef WIFI_USEDMAwhile (DMA_GetFlagStatus(DMA2_FLAG_TC4) == RESET && SDIO_GetFlagStatus(SDIO_FLAG_TXACT) == SET && SDIO_GetFlagStatus(SDIO_FLAG_TXUNDERR) == RESET); // 等待数据送入完毕且没有出现错误DMA_ClearFlag(DMA2_FLAG_TC4); // 清除DMA传输完成标志位DMA_Cmd(DMA2_Channel4, DISABLE); // 关闭DMA
#elsewhile (size && SDIO_GetFlagStatus(SDIO_FLAG_TXACT) == SET && SDIO_GetFlagStatus(SDIO_FLAG_TXUNDERR) == RESET){size -= 4;SDIO_WriteData(*p++); // 向FIFO送入4字节数据while (SDIO_GetFlagStatus(SDIO_FLAG_TXFIFOF) == SET); // 如果FIFO已满则等待}
#endifwhile (SDIO_GetFlagStatus(SDIO_FLAG_TXACT) == SET && SDIO_GetFlagStatus(SDIO_FLAG_TXUNDERR) == RESET); // 等待发送完毕sdio_data.SDIO_DPSM = SDIO_DPSM_Disable;SDIO_DataConfig(&sdio_data);// 清除相关标志位SDIO_ClearFlag(SDIO_FLAG_DATAEND | SDIO_FLAG_DBCKEND);return WiFi_LowLevel_CheckError(__func__) == 0;
}/* 写寄存器, 返回写入后寄存器的实际内容 */
uint8_t WiFi_LowLevel_WriteReg(uint8_t func, uint32_t addr, uint8_t value)
{WiFi_LowLevel_SendCMD52(func, addr, value, CMD52_WRITE | CMD52_READAFTERWRITE);WiFi_LowLevel_WaitForResponse(__func__);return SDIO_GetResponse(SDIO_RESP1) & 0xff;
}

WiFi.c:

#include <ctype.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "common.h"
#include "WPA.h"
#include "WiFi.h"static void WiFi_Associate_Callback(void *arg, void *data, WiFi_Status status);
static void WiFi_AssociateEx_Callback(void *arg, void *data, WiFi_Status status);
static uint8_t WiFi_CheckCommandBusy(WiFi_Callback callback, void *arg);
static uint8_t WiFi_CheckMIC(WiFi_EAPOLKeyFrame *packet, uint16_t len);
static uint8_t WiFi_CheckTxBufferRetry(WiFi_TxBuffer *tbuf, void *data);
static void WiFi_Deauthenticate_Callback(void *arg, void *data, WiFi_Status status);
static void WiFi_DownloadFirmware(void);
static void WiFi_EAPOLProcess(WiFi_DataRx *data);
static void WiFi_EAPOLProcess_Callback(void *arg, void *data, WiFi_Status status);
static uint8_t WiFi_ExtractGTK(const WiFi_EAPOLKeyFrame *packet_rx);
static void WiFi_GetMACAddress_Callback(void *arg, void *data, WiFi_Status status);
static void WiFi_JoinADHOC_Callback(void *arg, void *data, WiFi_Status status);
static void WiFi_JoinADHOCEx_Callback(void *arg, void *data, WiFi_Status status);
static void WiFi_TxBufferComplete(WiFi_TxBuffer *tbuf, void *data, WiFi_Status status);
static void WiFi_Scan_Callback(void *arg, void *data, WiFi_Status status);
static void WiFi_ScanSSID_Callback(void *arg, void *data, WiFi_Status status);
static void WiFi_SendEAPOLResponse(const WiFi_EAPOLKeyFrame *packet_rx, uint16_t key_info, const void *key_data, uint16_t key_data_len, WiFi_Callback callback, void *arg);
static void WiFi_SetKeyMaterial(WiFi_KeyType key_type, uint8_t key_num, WiFi_Callback callback, void *arg);
static void WiFi_StartADHOCEx_Callback(void *arg, void *data, WiFi_Status status);static uint8_t wifi_buffer_command[256]; // 命令帧发送缓冲区
static uint8_t wifi_buffer_packet[1792]; // 数据帧发送缓冲区
static uint8_t wifi_buffer_rx[2048]; // 帧接收缓冲区
static uint8_t wifi_psk[32]; // preshared-key
static uint8_t wifi_snonce[32];
static uint32_t wifi_port;
static uint32_t wifi_input_time = 0; // 最后一次调用SDIO中断处理函数的时间
static WiFi_GTK wifi_gtk;
static WiFi_PTK wifi_ptk;
static WiFi_SSIDInfo wifi_ssid_info = {0}; // 当前连接的热点信息
static WiFi_TxBuffer wifi_tx_command = {0}; // 命令帧发送缓冲区描述符
static WiFi_TxBuffer wifi_tx_packet = {0}; // 数据帧发送缓冲区描述符/* 关联一个热点 */
// 参数mac_addr用于接收热点的MAC地址, 可以为NULL, 但是不能指向局部变量
void WiFi_Associate(const char *ssid, WiFi_AuthenticationType auth_type, WiFi_Callback callback, void *arg)
{void **p;if (WiFi_CheckCommandBusy(callback, arg))return;p = malloc(2 * sizeof(void *) + sizeof(WiFi_AuthenticationType)); // 最后一个成员不是指针, 而是实际数据if (p == NULL){printf("WiFi_Associate: malloc failed!\n");if (callback)callback(arg, NULL, WIFI_STATUS_MEM);return;}p[0] = arg;p[1] = callback;*(WiFi_AuthenticationType *)(p + 2) = auth_type;WiFi_ScanSSID(ssid, &wifi_ssid_info, WiFi_Associate_Callback, p);
}static void WiFi_Associate_Callback(void *arg, void *data, WiFi_Status status)
{void **p = (void **)arg; // 之前分配的内存void *app_arg = p[0]; // 用户指定的参数WiFi_Callback app_callback = (WiFi_Callback)p[1]; // 用户指定的回调函数WiFi_AuthenticationType *pauth = (WiFi_AuthenticationType *)(p + 2);uint16_t cmd_size;WiFi_CmdRequest_Associate *cmd;WiFi_CmdResponse_Associate *resp;WiFi_SecurityType security;MrvlIETypes_PhyParamDSSet_t *ds;MrvlIETypes_CfParamSet_t *cf;MrvlIETypes_AuthType_t *auth;MrvlIETypes_VendorParamSet_t *vendor;MrvlIETypes_RsnParamSet_t *rsn;if (status != WIFI_STATUS_OK){printf("WiFi_Associate error!\n");free(arg);if (app_callback)app_callback(app_arg, data, status);return;}security = WiFi_GetSecurityType(&wifi_ssid_info);switch (WiFi_GetCommandCode(data)){case CMD_802_11_SCAN:// WiFi_ScanSSID命令执行完毕cmd = (WiFi_CmdRequest_Associate *)wifi_buffer_command;memcpy(cmd->peer_sta_addr, wifi_ssid_info.mac_addr, sizeof(wifi_ssid_info.mac_addr));cmd->cap_info = wifi_ssid_info.cap_info;cmd->listen_interval = 10;cmd->bcn_period = wifi_ssid_info.bcn_period;cmd->dtim_period = 1;memcpy(cmd + 1, &wifi_ssid_info.ssid, TLV_STRUCTLEN(wifi_ssid_info.ssid));ds = (MrvlIETypes_PhyParamDSSet_t *)((uint8_t *)(cmd + 1) + TLV_STRUCTLEN(wifi_ssid_info.ssid));ds->header.type = WIFI_MRVLIETYPES_PHYPARAMDSSET;ds->header.length = 1;ds->channel = wifi_ssid_info.channel;cf = (MrvlIETypes_CfParamSet_t *)(ds + 1);memset(cf, 0, sizeof(MrvlIETypes_CfParamSet_t));cf->header.type = WIFI_MRVLIETYPES_CFPARAMSET;cf->header.length = TLV_PAYLOADLEN(*cf);memcpy(cf + 1, &wifi_ssid_info.rates, TLV_STRUCTLEN(wifi_ssid_info.rates));auth = (MrvlIETypes_AuthType_t *)((uint8_t *)(cf + 1) + TLV_STRUCTLEN(wifi_ssid_info.rates));auth->header.type = WIFI_MRVLIETYPES_AUTHTYPE;auth->header.length = TLV_PAYLOADLEN(*auth);auth->auth_type = *pauth;cmd_size = (uint8_t *)(auth + 1) - wifi_buffer_command;if (security == WIFI_SECURITYTYPE_WPA){// WPA网络必须在命令中加入Vendor参数才能成功连接vendor = (MrvlIETypes_VendorParamSet_t *)(auth + 1);memcpy(vendor, &wifi_ssid_info.wpa, TLV_STRUCTLEN(wifi_ssid_info.wpa));cmd_size += TLV_STRUCTLEN(wifi_ssid_info.wpa);}else if (security == WIFI_SECURITYTYPE_WPA2){// WPA2网络必须在命令中加入RSN参数才能成功连接rsn = (MrvlIETypes_RsnParamSet_t *)(auth + 1);memcpy(rsn, &wifi_ssid_info.rsn, TLV_STRUCTLEN(wifi_ssid_info.rsn));cmd_size += TLV_STRUCTLEN(wifi_ssid_info.rsn);}WiFi_SendCommand(CMD_802_11_ASSOCIATE, wifi_buffer_command, cmd_size, WiFi_Associate_Callback, arg, WIFI_DEFAULT_TIMEOUT_CMDRESP, WIFI_DEFAULT_MAXRETRY);// 保留arg内存, 等关联成功后再释放break;case CMD_802_11_ASSOCIATE:// 关联命令执行完毕并收到了回应// 现在需要检查是否关联成功free(arg);resp = (WiFi_CmdResponse_Associate *)data;//printf("capability=0x%04x, status_code=0x%04x, aid=0x%04x\n", resp->capability, resp->status_code, resp->association_id);if (app_callback){if (resp->association_id == 0xffff)app_callback(app_arg, data, WIFI_STATUS_FAIL); // 关联失败 (在回调函数的data中检查resp->capability和resp->status_code的值可获得详细原因)else if (security == WIFI_SECURITYTYPE_WPA || security == WIFI_SECURITYTYPE_WPA2)app_callback(app_arg, data, WIFI_STATUS_INPROGRESS); // 等待认证elseapp_callback(app_arg, data, WIFI_STATUS_OK); // 关联成功}break;}
}/* 关联一个热点并输入密码 */
// 连接WPA型的热点时, security成员直接赋值WIFI_SECURITYTYPE_WPA即可, 不需要明确指出WPA版本号
void WiFi_AssociateEx(const WiFi_Connection *conn, WiFi_AuthenticationType auth_type, int8_t max_retry, WiFi_Callback callback, void *arg)
{int8_t *pmax_retry;uint16_t ssid_len;void **p;WiFi_AuthenticationType *pauth;if (WiFi_CheckCommandBusy(callback, arg))return;ssid_len = strlen(conn->ssid);p = malloc(2 * sizeof(void *) + sizeof(WiFi_AuthenticationType) + sizeof(int8_t) + ssid_len + 1);if (p == NULL){printf("WiFi_AssociateEx: malloc failed!\n");if (callback)callback(arg, NULL, WIFI_STATUS_MEM);return;}p[0] = arg;p[1] = callback;pauth = (WiFi_AuthenticationType *)(p + 2);*pauth = auth_type;pmax_retry = (int8_t *)(pauth + 1);*pmax_retry = max_retry; // 最大尝试重新连接的次数, -1表示无限次数, 0表示不重试memcpy(pmax_retry + 1, conn->ssid, ssid_len + 1);if (conn->security == WIFI_SECURITYTYPE_WEP)WiFi_SetWEP(WIFI_ACT_ADD, conn->password, WiFi_AssociateEx_Callback, p);else{WiFi_MACControl(WIFI_MACCTRL_ETHERNET2 | WIFI_MACCTRL_TX | WIFI_MACCTRL_RX, WiFi_AssociateEx_Callback, p);if (conn->security == WIFI_SECURITYTYPE_WPA || conn->security == WIFI_SECURITYTYPE_WPA2)WiFi_SetWPA(conn->ssid, conn->password);}
}static void WiFi_AssociateEx_Callback(void *arg, void *data, WiFi_Status status)
{void **p = (void **)arg;void *app_arg = p[0];WiFi_Callback app_callback = (WiFi_Callback)p[1];WiFi_AuthenticationType *pauth = (WiFi_AuthenticationType *)(p + 2);int8_t *pmax_retry = (int8_t *)(pauth + 1);char *ssid = (char *)(pmax_retry + 1);uint16_t cmd_code = WiFi_GetCommandCode(data);if (cmd_code == CMD_802_11_ASSOCIATE || cmd_code == CMD_802_11_SCAN){if (cmd_code == CMD_802_11_ASSOCIATE && (status == WIFI_STATUS_OK || status == WIFI_STATUS_INPROGRESS)){// 关联成功free(arg);if (app_callback)app_callback(app_arg, data, status);return;}else{// 关联失败, 重试if (*pmax_retry != 0){if (*pmax_retry != -1)(*pmax_retry)--;cmd_code = CMD_MAC_CONTROL;status = WIFI_STATUS_OK;}}}if (status != WIFI_STATUS_OK){printf("WiFi_AssociateEx error!\n");free(arg);if (app_callback)app_callback(app_arg, data, status);return;}switch (cmd_code){case CMD_802_11_SET_WEP:WiFi_MACControl(WIFI_MACCTRL_ETHERNET2 | WIFI_MACCTRL_WEP | WIFI_MACCTRL_TX | WIFI_MACCTRL_RX, WiFi_AssociateEx_Callback, arg);break;case CMD_MAC_CONTROL:WiFi_Associate(ssid, *pauth, WiFi_AssociateEx_Callback, arg);}
}/* 如果之前的命令尚未执行完就请求执行新的命令, 则直接调用回调函数报告错误 */
static uint8_t WiFi_CheckCommandBusy(WiFi_Callback callback, void *arg)
{// 发送新命令前必须确保之前的命令已经发送完毕并收到回应// See 4.2 Protocol: The command exchange protocol is serialized, the host driver must wait until // it has received a command response for the current command request before it can send the next command request.if (WiFi_IsCommandBusy()){printf("Warning: The previous command is in progress!\n");if (callback)callback(arg, NULL, WIFI_STATUS_BUSY);return 1;}WiFi_WaitForLastTask(); // 等待之前的数据帧收到确认return 0;
}/* 验证EAPOL帧中的MIC值是否正确 */
static uint8_t WiFi_CheckMIC(WiFi_EAPOLKeyFrame *packet, uint16_t len)
{uint8_t ret;WiFi_KeyType key_type = (WiFi_KeyType)(ntohs(packet->key_information) & 0x07);WiFi_MIC mic[2];memcpy(mic[0].MIC, packet->key_mic, sizeof(packet->key_mic));memset(packet->key_mic, 0, sizeof(packet->key_mic));if (key_type == WIFI_KEYTYPE_TKIP)ret = hmac_md5(wifi_ptk.KCK, sizeof(wifi_ptk.KCK), &packet->version, len - 14, mic[1].MIC);else if (key_type == WIFI_KEYTYPE_AES)ret = hmac_sha1(wifi_ptk.KCK, sizeof(wifi_ptk.KCK), &packet->version, len - 14, mic[1].MIC);elsereturn 0;if (!ret){printf("WiFi_CheckMIC: out of memory!\n");return 0;}return memcmp(mic[0].MIC, mic[1].MIC, sizeof(mic[1].MIC)) == 0;
}/* 数据帧、命令帧发送超时处理 */
void WiFi_CheckTimeout(void)
{WiFi_CommandHeader *cmd = (WiFi_CommandHeader *)wifi_buffer_command;// 定时检查INTSTATUS寄存器, 避免因为没有检测到SDIOIT中断而导致程序卡死if (sys_now() > wifi_input_time + 2000)WiFi_Input();// 回调函数中的data参数: 失败时为发送失败的数据, 成功时为收到的回应// 发送数据帧时, data始终为发送的内容if (WiFi_CheckTxBufferRetry(&wifi_tx_command, wifi_buffer_command)){WiFi_SendCommand(0, NULL, 0, wifi_tx_command.callback, wifi_tx_command.arg, wifi_tx_command.timeout, wifi_tx_command.retry - 1);printf("WiFi Command 0x%04x Timeout! Resend...\n", cmd->cmd_code);}WiFi_CheckTxBufferRetry(&wifi_tx_packet, wifi_buffer_packet);
}/* 检查发送缓冲区是否需要重传 */
// data为报告错误时需要传给回调函数的数据
static uint8_t WiFi_CheckTxBufferRetry(WiFi_TxBuffer *tbuf, void *data)
{if (tbuf->busy && sys_now() > tbuf->start_time + tbuf->timeout) // 若超时时间到了{if (tbuf->retry != 0){tbuf->busy = 0;return 1;}elseWiFi_TxBufferComplete(tbuf, data, WIFI_STATUS_NORESP); // 超过最大重试次数, 向回调函数报告错误}return 0;
}/* 与热点断开连接 */
void WiFi_Deauthenticate(uint16_t reason, WiFi_Callback callback, void *arg)
{uint8_t ret;void **p;WiFi_Cmd_Deauthenticate *cmd = (WiFi_Cmd_Deauthenticate *)wifi_buffer_command;if (WiFi_CheckCommandBusy(callback, arg))return;ret = WiFi_GetBSSID(cmd->peer_sta_addr);if (!ret){printf("WiFi_Deauthenticate: WiFi is not connected!\n");if (callback)callback(arg, NULL, WIFI_STATUS_FAIL);return;}p = malloc(2 * sizeof(void *));if (p == NULL){printf("WiFi_Deauthenticate: malloc failed!\n");if (callback)callback(arg, NULL, WIFI_STATUS_MEM);return;}p[0] = arg;p[1] = callback;cmd->reason_code = reason;WiFi_SendCommand(CMD_802_11_DEAUTHENTICATE, wifi_buffer_command, sizeof(WiFi_Cmd_Deauthenticate), WiFi_Deauthenticate_Callback, p, WIFI_DEFAULT_TIMEOUT_CMDRESP, WIFI_DEFAULT_MAXRETRY);
}static void WiFi_Deauthenticate_Callback(void *arg, void *data, WiFi_Status status)
{void **p = (void **)arg;void *app_arg = p[0];WiFi_Callback app_callback = (WiFi_Callback)p[1];free(arg);if (status == WIFI_STATUS_OK)memset(wifi_ssid_info.mac_addr, 0, 6);elseprintf("WiFi_Deauthenticate failed!\n");if (app_callback)app_callback(app_arg, data, status);
}/* 固件下载 */
static void WiFi_DownloadFirmware(void)
{uint8_t helper_buf[64];const uint8_t *data;uint16_t curr;uint32_t len;// 下载helper固件data = WIFI_HELPER_ADDR;len = WIFI_HELPER_SIZE;while (len){// 每次最多下载60字节固件内容curr = (len > 60) ? 60 : len;memcpy(helper_buf, &curr, 4); // 前4字节为本次实际下载的数据量memcpy(helper_buf + 4, data, curr);if (len != WIFI_HELPER_SIZE) // 第一次发送数据前不需要等待WiFi_Wait(WIFI_INTSTATUS_DNLD, 0);WiFi_LowLevel_WriteData(1, wifi_port, helper_buf, sizeof(helper_buf), sizeof(helper_buf));len -= curr;data += curr;}memset(helper_buf, 0, 4);WiFi_LowLevel_WriteData(1, wifi_port, helper_buf, sizeof(helper_buf), sizeof(helper_buf)); // 以空数据包结束// 下载固件data = WIFI_FIRMWARE_ADDR; // 该地址必须是4字节对齐的, 否则将会引起传输错误len = WIFI_FIRMWARE_SIZE;while (len){// 获取本次应下载的字节数// 每次可以发送n>=curr字节的数据, 只能用一个CMD53命令发送, WiFi模块只认前curr字节的数据WiFi_Wait(WIFI_INTSTATUS_DNLD, 0); // 发送前必须等待Download Readywhile ((curr = WiFi_LowLevel_ReadReg(1, WIFI_SQREADBASEADDR0) | (WiFi_LowLevel_ReadReg(1, WIFI_SQREADBASEADDR1) << 8)) == 0);//printf("Required: %d bytes, Remaining: %d bytes\n", curr, len);if (curr & 1){// 若curr为奇数(如17), 则说明接收端出现了CRC校验错误, 应重新传送上一次的内容(这部分代码省略)printf("Error: an odd size is invalid!\n");while (1);}if (curr > len)curr = len;// 发送固件数据
#ifdef WIFI_HIGHSPEED // 高速模式下不能使用多字节传输模式, 只能使用块传输模式, 因此缓冲区要足够大if (len < 32) // len为缓冲区剩余大小{// 若缓冲区空间不足一个数据块, 则借用helper_bufmemcpy(helper_buf, data, curr);WiFi_LowLevel_WriteData(1, wifi_port, helper_buf, curr, sizeof(helper_buf));}else
#endifWiFi_LowLevel_WriteData(1, wifi_port, data, curr, len);len -= curr;data += curr;}// 等待Firmware启动WiFi_Wait(WIFI_INTSTATUS_DNLD, 0);while (WiFi_GetDataLength() != 0xfedc);printf("Firmware is successfully downloaded!\n");
}/* 丢弃收到但因程序错误一直未处理的数据或命令 */
void WiFi_DiscardData(void)
{uint16_t len = WiFi_GetDataLength();uint8_t ret = WiFi_LowLevel_ReadData(1, wifi_port, wifi_buffer_rx, len, sizeof(wifi_buffer_rx));if (ret){printf("Discarded %d bytes!\n", len);dump_data(wifi_buffer_rx, len);}elseprintf("Discarding %d bytes of data failed!\n", len);
}/* 处理EAPOL认证帧 */
static void WiFi_EAPOLProcess(WiFi_DataRx *data)
{uint8_t i, ret;uint8_t random_b[10];uint16_t key_info;uint16_t key_data_len;uint32_t random_k[16];WiFi_EAPOLKeyFrame *packet_rx = (WiFi_EAPOLKeyFrame *)data->payload;WiFi_EAPOLKeyFrame *packet_tx;WiFi_KDE *kde;WiFi_KeyType key_type;WiFi_PTKB ptkb;key_info = ntohs(packet_rx->key_information);key_type = (WiFi_KeyType)(key_info & 0x07);if (key_type != WIFI_KEYTYPE_TKIP && key_type != WIFI_KEYTYPE_AES){printf("Unsupported key descriptor version: %d\n", key_type);return;}switch (key_info & 0x23c8) // 移除与EAPOL-Key frame notation前六个参数无关的位{case 0x88:/* 4-way handshake Message 1: EAPOL-Key(0,0,1,0,P,0,...), P=1 */printf("Message 1 received!\n");/* 生成SNonce */// PRF-256(Random number, "Init Counter", Local MAC Address || Time)srand(sys_now());for (i = 0; i < sizeof(random_k) / sizeof(uint32_t); i++)random_k[i] = rand();memcpy(random_b, packet_rx->dest, sizeof(packet_rx->dest));*(uint32_t *)(random_b + sizeof(packet_rx->dest)) = sys_now();ret = PRF(random_k, sizeof(random_k), "Init Counter", random_b, sizeof(random_b), wifi_snonce, sizeof(wifi_snonce)); // PRF-256if (!ret){printf("PRF: out of memory!\n"); // 遇到内存不足的情况, 只需要把STM32启动文件(.s)中的堆空间大小Heap_Size调大就能解决问题break;}/* 生成PTK */// 较小的MAC地址在前, 较大的在后if (memcmp(packet_rx->dest, packet_rx->src, sizeof(packet_rx->src)) < 0)memcpy(ptkb.MAC, packet_rx->dest, sizeof(ptkb.MAC));else{memcpy(ptkb.MAC[0], packet_rx->src, sizeof(packet_rx->src));memcpy(ptkb.MAC[1], packet_rx->dest, sizeof(packet_rx->dest));}// 较小的随机数在前, 较大的在后if (memcmp(packet_rx->key_nonce, wifi_snonce, sizeof(wifi_snonce)) < 0){memcpy(ptkb.nonce[0], packet_rx->key_nonce, sizeof(packet_rx->key_nonce));memcpy(ptkb.nonce[1], wifi_snonce, sizeof(wifi_snonce));}else{memcpy(ptkb.nonce[0], wifi_snonce, sizeof(wifi_snonce));memcpy(ptkb.nonce[1], packet_rx->key_nonce, sizeof(packet_rx->key_nonce));}// wifi_psk是在设置密码时生成的ret = PRF(wifi_psk, sizeof(wifi_psk), "Pairwise key expansion", &ptkb, sizeof(ptkb), &wifi_ptk, sizeof(wifi_ptk)); // PRF-512if (!ret){printf("PRF: out of memory!\n");break;}/* 在待发送的Message 2中添加Key Data信息 */// https://community.arubanetworks.com/t5/Technology-Blog/A-closer-look-at-WiFi-Security-IE-Information-Elements/ba-p/198867// 使用WPA的热点一定有WPA IE信息项,一定没有RSN IE信息项// 使用WPA2的热点一定有RSN IE信息项,可能有WPA IE信息项packet_tx = (WiFi_EAPOLKeyFrame *)WiFi_GetPacketBuffer();kde = (WiFi_KDE *)packet_tx->key_data;if (wifi_ssid_info.rsn.header.type){// 路由器提供了RSN IE信息项时可直接复制//printf("RSN IE copied!\n");kde->type = WIFI_MRVLIETYPES_RSNPARAMSET;kde->length = wifi_ssid_info.rsn.header.length;memcpy(kde->oui, wifi_ssid_info.rsn.rsn, wifi_ssid_info.rsn.header.length);key_data_len = sizeof(kde->type) + sizeof(kde->length) + kde->length;}else if (wifi_ssid_info.wpa.header.type){// 路由器提供了WPA IE信息项时可直接复制//printf("WPA IE copied!\n");kde->type = WIFI_MRVLIETYPES_VENDORPARAMSET;kde->length = wifi_ssid_info.wpa.header.length;memcpy(kde->oui, wifi_ssid_info.wpa.vendor, wifi_ssid_info.wpa.header.length);key_data_len = sizeof(kde->type) + sizeof(kde->length) + kde->length;}else{printf("IE not copied!\n");key_data_len = 0;}/* 发送Message 2: EAPOL-Key(0,1,0,0,P,0,...) */WiFi_SendEAPOLResponse(packet_rx, 0x108 | key_type, NULL, key_data_len, WiFi_EAPOLProcess_Callback, (void *)2);break;case 0x1c8: // WPA MSG3case 0x3c8: // WPA2 MSG3/* 4-way handshake Message 3: EAPOL-Key(1,1,1,1,P,0,...) */printf("Message 3 received!\n");if (!WiFi_CheckMIC(packet_rx, data->rx_packet_length)) // prevents undetected modification of message 3{printf("Message 3 is corrupted!\n");break;}if (WiFi_IsCommandBusy()) // 如果命令发送缓冲区被占用, 则丢弃本次的Msg3不作出回应, 等待下一个Msg3break;// WPA认证时, 只将PTK发给固件// WPA2认证时, 需要获取GTK并将PTK和GTK发送给固件ret = WiFi_ExtractGTK(packet_rx);WiFi_SetKeyMaterial(key_type, ret ? 2 : 1, WiFi_EAPOLProcess_Callback, (void *)(ret ? 2 : 3));/* 发送Message 4: EAPOL-Key(1,1,0,0,P,0,...) */WiFi_SendEAPOLResponse(packet_rx, 0x308 | key_type, NULL, 0, WiFi_EAPOLProcess_Callback, (void *)4);break;case 0x380:/* Group key handshake Message 1: EAPOL-Key(1,1,1,0,G,0,...), G=0 */printf("Group key handshake!\n");if (!WiFi_CheckMIC(packet_rx, data->rx_packet_length))break;if (WiFi_IsCommandBusy())break;/* 将新的GTK发给固件 */if (!WiFi_ExtractGTK(packet_rx)){printf("Extracting GTK failed!\n");break;}WiFi_SetKeyMaterial(key_type, 2, WiFi_EAPOLProcess_Callback, (void *)1); // 同时发送PTK和GTK, 不能只发GTK, 否则固件中的密钥无法得到更新/* 发送Message 2: EAPOL-Key(1,1,0,0,G,0,...) */WiFi_SendEAPOLResponse(packet_rx, 0x300 | key_type, NULL, 0, WiFi_EAPOLProcess_Callback, (void *)2);break;default:printf("Unhandled EAPOL frame! key_info=0x%04x\n", key_info);dump_data(packet_rx, data->rx_packet_length);}
}static void WiFi_EAPOLProcess_Callback(void *arg, void *data, WiFi_Status status)
{WiFi_SDIOFrameHeader *header = (WiFi_SDIOFrameHeader *)data;if (status == WIFI_STATUS_OK){if (header->type == WIFI_SDIOFRAME_COMMAND){switch ((uint32_t)arg){case 1:printf("GTK set!\n");break;case 2:printf("PTK & GTK set!\n");break;case 3:printf("PTK set!\n");break;}}else if (header->type == WIFI_SDIOFRAME_DATA){printf("Message %d sent!\n", (uint32_t)arg);if ((uint32_t)arg == 4)WiFi_AuthenticationCompleteHandler(); // 有了PTK就可以发送广播帧了, 所以在这里调用callback比较合适}}
}/* 用KEK密钥对key_data数据解密, 并提取出GTK密钥 */
static uint8_t WiFi_ExtractGTK(const WiFi_EAPOLKeyFrame *packet_rx)
{uint16_t key_info = ntohs(packet_rx->key_information);uint16_t key_len = ntohs(packet_rx->key_length);uint16_t keydata_len = ntohs(packet_rx->key_data_length);WiFi_KDE *kde;WiFi_KeyType key_type = (WiFi_KeyType)(key_info & 0x07);// 解密key_data字段kde = (WiFi_KDE *)wifi_buffer_command;if (key_type == WIFI_KEYTYPE_TKIP)ARC4_decrypt_keydata(wifi_ptk.KEK, packet_rx->key_iv, packet_rx->key_data, keydata_len, wifi_buffer_command);else if (key_type == WIFI_KEYTYPE_AES)keydata_len = AES_unwrap(wifi_ptk.KEK, packet_rx->key_data, keydata_len, wifi_buffer_command);elsereturn 0;if (keydata_len == key_len){// 如果认证类型为WPA, 则解密之后的keydata内容就是GTKmemcpy(&wifi_gtk, wifi_buffer_command, keydata_len);return 1;}else{// 如果认证类型为WPA2, 则解密后的keydata内容是一些KDE结构的数据, GTK在其中的一个KDE里面while (kde->length != 0) // 搜索长度不为0的KDE结构{if (kde->type == 0xdd && kde->data_type == 1 && kde->length - 6 == key_len) // GTK KDE{memcpy(wifi_gtk.TK, kde->data + 2, key_len);return 1;}kde = (WiFi_KDE *)((uint8_t *)kde + kde->length + 2);if (((uint8_t *)kde - wifi_buffer_command) >= keydata_len - 1) // 保证key->length落在有效数据区域内break;}}return 0;
}/* 获取所连热点的MAC地址 */
uint8_t WiFi_GetBSSID(uint8_t mac_addr[6])
{uint8_t i;memcpy(mac_addr, wifi_ssid_info.mac_addr, 6);for (i = 0; i < 6; i++){if (mac_addr[i] != 0)break;}return i != 6; // 返回值表示MAC地址是否不全为0
}/* 获取需要接收的数据大小 */
uint16_t WiFi_GetDataLength(void)
{// 读Scratch pad 4寄存器的低16位return WiFi_LowLevel_ReadReg(1, WIFI_SCRATCHPAD4_0) | (WiFi_LowLevel_ReadReg(1, WIFI_SCRATCHPAD4_1) << 8);
}/* 获取MAC地址 */
// callback不能为NULL
void WiFi_GetMACAddress(WiFi_Callback callback, void *arg)
{void **p;if (WiFi_CheckCommandBusy(callback, arg))return;p = malloc(2 * sizeof(void *));if (p == NULL){printf("WiFi_GetMACAddress: malloc failed!\n");callback(arg, NULL, WIFI_STATUS_MEM);return;}p[0] = arg;p[1] = callback;WiFi_MACAddr(NULL, WIFI_ACT_GET, WiFi_GetMACAddress_Callback, p);
}static void WiFi_GetMACAddress_Callback(void *arg, void *data, WiFi_Status status)
{void **p = (void **)arg;void *app_arg = p[0];WiFi_Callback app_callback = (WiFi_Callback)p[1];WiFi_Cmd_MACAddr *cmd = (WiFi_Cmd_MACAddr *)data;free(arg);if (status == WIFI_STATUS_OK)app_callback(app_arg, cmd->mac_addr, status);else{printf("WiFi_GetMACAddress error!\n");app_callback(app_arg, NULL, status);}
}/* 请求发送新的数据帧 */
uint8_t *WiFi_GetPacketBuffer(void)
{WiFi_DataTx *data = (WiFi_DataTx *)wifi_buffer_packet;WiFi_WaitForLastTask(); // 使用前必须确保缓冲区未被占用return data->payload;
}/* 获取收到的数据帧的内容和大小 */
const uint8_t *WiFi_GetReceivedPacket(uint16_t *len)
{WiFi_DataRx *data = (WiFi_DataRx *)wifi_buffer_rx;if (data->header.type == WIFI_SDIOFRAME_DATA){*len = data->rx_packet_length;return data->payload;}elsereturn NULL;
}/* 获取热点的认证类型 */
WiFi_SecurityType WiFi_GetSecurityType(const WiFi_SSIDInfo *info)
{if (info->cap_info & WIFI_CAPABILITY_PRIVACY){if (info->rsn.header.type)return WIFI_SECURITYTYPE_WPA2;else if (info->wpa.header.type)return WIFI_SECURITYTYPE_WPA;elsereturn WIFI_SECURITYTYPE_WEP;}elsereturn WIFI_SECURITYTYPE_NONE;
}/* 初始化WiFi模块 */
void WiFi_Init(void)
{// 初始化底层寄存器WiFi_LowLevel_Init();WiFi_ShowCIS();// 初始化Function 1WiFi_LowLevel_WriteReg(0, SDIO_CCCR_IOEN, SDIO_CCCR_IOEN_IOE1); // IOE1=1 (Enable Function)while ((WiFi_LowLevel_ReadReg(0, SDIO_CCCR_IORDY) & SDIO_CCCR_IORDY_IOR1) == 0); // 等待IOR1=1 (I/O Function Ready)WiFi_LowLevel_WriteReg(0, SDIO_CCCR_INTEN, SDIO_CCCR_INTEN_IENM | SDIO_CCCR_INTEN_IEN1); // 打开SDIO中断请求WiFi_LowLevel_WriteReg(1, WIFI_INTMASK, WIFI_INTMASK_HOSTINTMASK); // 利用中断标志位来判定是否有数据要读取, 可靠性更高// 下载固件wifi_port = WiFi_LowLevel_ReadReg(1, WIFI_IOPORT0) | (WiFi_LowLevel_ReadReg(1, WIFI_IOPORT1) << 8) | (WiFi_LowLevel_ReadReg(1, WIFI_IOPORT2) << 16);WiFi_LowLevel_SetBlockSize(1, 32);WiFi_DownloadFirmware();WiFi_LowLevel_SetBlockSize(1, 256);
}void WiFi_Input(void)
{uint8_t ret, status;uint16_t len;WiFi_SDIOFrameHeader *rx_header = (WiFi_SDIOFrameHeader *)wifi_buffer_rx;WiFi_DataRx *rx_packet = (WiFi_DataRx *)wifi_buffer_rx;WiFi_CommandHeader *rx_cmd = (WiFi_CommandHeader *)wifi_buffer_rx;WiFi_CommandHeader *tx_cmd = (WiFi_CommandHeader *)wifi_buffer_command;wifi_input_time = sys_now();status = WiFi_LowLevel_ReadReg(1, WIFI_INTSTATUS); // 获取需要处理的中断标志位if (status == 0)return;WiFi_LowLevel_WriteReg(1, WIFI_INTSTATUS, WIFI_INTSTATUS_ALL & ~status); // 必须先清除这些标志位, 然后再进行处理, 这样可以避免清除掉处理过程中新来的中断if (status & WIFI_INTSTATUS_DNLD){// 命令帧收到确认if (wifi_tx_command.busy && wifi_tx_command.ready == 0){
#ifdef WIFI_DISPLAY_RESPTIMEprintf("CMD 0x%04x ACK at %dms\n", tx_cmd->cmd_code, sys_now() - wifi_tx_command.start_time);
#endifwifi_tx_command.ready = 1;}// 数据帧发送成功并收到确认if (wifi_tx_packet.busy){
#ifdef WIFI_DISPLAY_RESPTIMEprintf("Packet ACK at %dms\n", sys_now() - wifi_tx_packet.start_time);
#endifWiFi_TxBufferComplete(&wifi_tx_packet, wifi_buffer_packet, WIFI_STATUS_OK);}}if (status & WIFI_INTSTATUS_UPLD){len = WiFi_GetDataLength();ret = WiFi_LowLevel_ReadData(1, wifi_port, wifi_buffer_rx, len, sizeof(wifi_buffer_rx));if (ret){switch (rx_header->type){case WIFI_SDIOFRAME_DATA:// 收到以太网数据帧if (rx_packet->rx_packet_length >= 14 && rx_packet->payload[12] == 0x88 && rx_packet->payload[13] == 0x8e)WiFi_EAPOLProcess(rx_packet); // 处理0x888e类型的EAPOL认证帧elseWiFi_PacketHandler(rx_packet);break;case WIFI_SDIOFRAME_COMMAND:// 收到命令回应帧if (rx_cmd->seq_num == tx_cmd->seq_num) // 序号相符{#ifdef WIFI_DISPLAY_RESPTIMEprintf("CMDRESP 0x%04x at %dms\n", rx_cmd->cmd_code, sys_now() - wifi_tx_command.start_time);#endifWiFi_TxBufferComplete(&wifi_tx_command, wifi_buffer_rx, WIFI_STATUS_OK);}break;case WIFI_SDIOFRAME_EVENT:// 收到事件帧WiFi_EventHandler((WiFi_Event *)wifi_buffer_rx); // 调用事件处理回调函数}}}
}/* 发送命令帧前, 必须保证命令发送缓冲区为空 */
uint8_t WiFi_IsCommandBusy(void)
{return wifi_tx_command.busy;
}/* 加入Ad-Hoc网络 */
void WiFi_JoinADHOC(const char *ssid, WiFi_Callback callback, void *arg)
{void **p;if (WiFi_CheckCommandBusy(callback, arg))return;p = malloc(2 * sizeof(void *));if (p == NULL){printf("WiFi_JoinADHOC: malloc failed!\n");if (callback)callback(arg, NULL, WIFI_STATUS_MEM);return;}p[0] = arg;p[1] = callback;WiFi_ScanSSID(ssid, &wifi_ssid_info, WiFi_JoinADHOC_Callback, p);
}static void WiFi_JoinADHOC_Callback(void *arg, void *data, WiFi_Status status)
{void **p = (void **)arg;void *app_arg = p[0];WiFi_Callback app_callback = (WiFi_Callback)p[1];WiFi_Cmd_ADHOCJoin *cmd;if (status != WIFI_STATUS_OK){printf("WiFi_JoinADHOC error!\n");free(arg);if (app_callback)app_callback(app_arg, data, status);return;}switch (WiFi_GetCommandCode(data)){case CMD_802_11_SCAN:cmd = (WiFi_Cmd_ADHOCJoin *)wifi_buffer_command;memcpy(cmd->bssid, wifi_ssid_info.mac_addr, sizeof(cmd->bssid));strncpy((char *)cmd->ssid, (char *)wifi_ssid_info.ssid.ssid, sizeof(cmd->ssid)); // strncpy会将未使用的区域填充为0cmd->bss_type = WIFI_BSS_ANY; // recommended for use when joining Ad-Hoc networkscmd->bcn_period = wifi_ssid_info.bcn_period;cmd->dtim_period = 1;memset(cmd->timestamp, 0, sizeof(cmd->timestamp) + sizeof(cmd->start_ts));cmd->ds_param_set.header.type = WIFI_MRVLIETYPES_PHYPARAMDSSET;cmd->ds_param_set.header.length = TLV_PAYLOADLEN(cmd->ds_param_set);cmd->ds_param_set.channel = wifi_ssid_info.channel;cmd->reserved1 = 0;cmd->ibss_param_set.header.type = WIFI_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 = wifi_ssid_info.cap_info;memcpy(cmd->data_rates, wifi_ssid_info.rates.rates, sizeof(cmd->data_rates));cmd->reserved3 = 0;WiFi_SendCommand(CMD_802_11_AD_HOC_JOIN, wifi_buffer_command, sizeof(WiFi_Cmd_ADHOCJoin), WiFi_JoinADHOC_Callback, arg, WIFI_DEFAULT_TIMEOUT_CMDRESP, WIFI_DEFAULT_MAXRETRY);break;case CMD_802_11_AD_HOC_JOIN:free(arg);cmd = (WiFi_Cmd_ADHOCJoin *)data;if (app_callback)app_callback(app_arg, data, (cmd->header.result == 0) ? WIFI_STATUS_OK : WIFI_STATUS_FAIL);break;}
}/* 加入带有密码的Ad-Hoc网络 */
void WiFi_JoinADHOCEx(const WiFi_Connection *conn, int8_t max_retry, WiFi_Callback callback, void *arg)
{int8_t *pmax_retry;uint16_t ssid_len;void **p;if (WiFi_CheckCommandBusy(callback, arg))return;if (conn->security == WIFI_SECURITYTYPE_WPA || conn->security == WIFI_SECURITYTYPE_WPA2){// 88W8686虽然能够连上电脑创建的WPA2认证类型的Ad-Hoc热点, 也能完成EAPOL认证// 但是同时也会自己创建一个WEP型的同名Ad-Hoc热点, 使通信无法正常进行printf("WiFi_JoinADHOCEx: WPA is not supported!\n");if (callback)callback(arg, NULL, WIFI_STATUS_INVALID);return;}ssid_len = strlen(conn->ssid);p = malloc(2 * sizeof(void *) + sizeof(int8_t) + ssid_len + 1);if (p == NULL){printf("WiFi_JoinADHOCEx: malloc failed!\n");if (callback)callback(arg, NULL, WIFI_STATUS_MEM);return;}p[0] = arg;p[1] = callback;pmax_retry = (int8_t *)(p + 2);*pmax_retry = max_retry;memcpy(pmax_retry + 1, conn->ssid, ssid_len + 1);if (conn->security == WIFI_SECURITYTYPE_WEP)WiFi_SetWEP(WIFI_ACT_ADD, conn->password, WiFi_JoinADHOCEx_Callback, p);elseWiFi_MACControl(WIFI_MACCTRL_ETHERNET2 | WIFI_MACCTRL_TX | WIFI_MACCTRL_RX, WiFi_JoinADHOCEx_Callback, p);
}static void WiFi_JoinADHOCEx_Callback(void *arg, void *data, WiFi_Status status)
{void **p = (void **)arg;void *app_arg = p[0];WiFi_Callback app_callback = (WiFi_Callback)p[1];int8_t *pmax_retry = (int8_t *)(p + 2);char *ssid = (char *)(pmax_retry + 1);uint16_t cmd_code = WiFi_GetCommandCode(data);if (cmd_code == CMD_802_11_AD_HOC_JOIN || cmd_code == CMD_802_11_SCAN){if (cmd_code == CMD_802_11_AD_HOC_JOIN && status == WIFI_STATUS_OK){free(arg);if (app_callback)app_callback(app_arg, data, status);return;}else{if (*pmax_retry != 0){if (*pmax_retry != -1)(*pmax_retry)--;cmd_code = CMD_MAC_CONTROL;status = WIFI_STATUS_OK;}}}if (status != WIFI_STATUS_OK){printf("WiFi_JoinADHOCEx error!\n");free(arg);if (app_callback)app_callback(app_arg, data, status);return;}switch (cmd_code){case CMD_802_11_SET_WEP:WiFi_MACControl(WIFI_MACCTRL_ETHERNET2 | WIFI_MACCTRL_WEP | WIFI_MACCTRL_TX | WIFI_MACCTRL_RX, WiFi_JoinADHOCEx_Callback, arg);break;case CMD_MAC_CONTROL:WiFi_JoinADHOC(ssid, WiFi_JoinADHOCEx_Callback, arg);}
}/* 获取或设置WPA密钥 */
void WiFi_KeyMaterial(WiFi_CommandAction action, MrvlIETypes_KeyParamSet_t *key, uint8_t key_count, WiFi_Callback callback, void *arg)
{uint8_t i;WiFi_Cmd_KeyMaterial *cmd = (WiFi_Cmd_KeyMaterial *)wifi_buffer_command;MrvlIETypes_KeyParamSet_t *pkey = &cmd->keys;if (WiFi_CheckCommandBusy(callback, arg))return;cmd->action = action;if (action == WIFI_ACT_SET){for (i = 0; i < key_count; i++){key[i].header.type = WIFI_MRVLIETYPES_KEYPARAMSET;key[i].header.length = sizeof(MrvlIETypes_KeyParamSet_t) - sizeof(key[i].header) - sizeof(key[i].key) + key->key_len;memcpy(pkey, key + i, TLV_STRUCTLEN(key[i]));pkey = (MrvlIETypes_KeyParamSet_t *)TLV_NEXT(pkey);}}WiFi_SendCommand(CMD_802_11_KEY_MATERIAL, wifi_buffer_command, (uint8_t *)pkey - wifi_buffer_command, callback, arg, WIFI_DEFAULT_TIMEOUT_CMDRESP, WIFI_DEFAULT_MAXRETRY);
}/* 获取或设置MAC地址 */
void WiFi_MACAddr(const uint8_t newaddr[6], WiFi_CommandAction action, WiFi_Callback callback, void *arg)
{WiFi_Cmd_MACAddr *cmd = (WiFi_Cmd_MACAddr *)wifi_buffer_command;if (WiFi_CheckCommandBusy(callback, arg))return;cmd->action = action;if (action == WIFI_ACT_SET)memcpy(cmd->mac_addr, newaddr, 6);elsememset(cmd->mac_addr, 0, 6);WiFi_SendCommand(CMD_802_11_MAC_ADDR, wifi_buffer_command, sizeof(WiFi_Cmd_MACAddr), callback, arg, WIFI_DEFAULT_TIMEOUT_CMDRESP, WIFI_DEFAULT_MAXRETRY);
}/* 配置MAC */
void WiFi_MACControl(uint16_t action, WiFi_Callback callback, void *arg)
{WiFi_Cmd_MACCtrl *cmd = (WiFi_Cmd_MACCtrl *)wifi_buffer_command;if (WiFi_CheckCommandBusy(callback, arg))return;cmd->action = action;cmd->reserved = 0;WiFi_SendCommand(CMD_MAC_CONTROL, wifi_buffer_command, sizeof(WiFi_Cmd_MACCtrl), callback, arg, WIFI_DEFAULT_TIMEOUT_CMDRESP, WIFI_DEFAULT_MAXRETRY);
}/* 扫描全部热点 (仅显示) */
void WiFi_Scan(WiFi_Callback callback, void *arg)
{uint8_t i;void **p;WiFi_CmdRequest_Scan *cmd = (WiFi_CmdRequest_Scan *)wifi_buffer_command; // 要发送的命令MrvlIETypes_ChanListParamSet_t *chanlist = (MrvlIETypes_ChanListParamSet_t *)(cmd + 1); // 这里的+1指的是前进sizeof(指针类型)个地址单元, 而非只前进1个地址单元if (WiFi_CheckCommandBusy(callback, arg))return;p = malloc(2 * sizeof(void *));if (p == NULL){printf("WiFi_Scan: malloc failed!\n");if (callback)callback(arg, NULL, WIFI_STATUS_MEM);return;}p[0] = arg;p[1] = callback;cmd->bss_type = WIFI_BSS_ANY;memset(cmd->bss_id, 0, sizeof(cmd->bss_id));// 通道的基本参数chanlist->header.type = WIFI_MRVLIETYPES_CHANLISTPARAMSET;chanlist->header.length = 4 * sizeof(chanlist->channels);for (i = 0; i < 4; i++) // 先扫描前4个通道 (i作下标, i+1才是通道号){chanlist->channels[i].band_config_type = 0; // 2.4GHz band, 20MHz channel widthchanlist->channels[i].chan_number = i + 1; // 通道号chanlist->channels[i].scan_type = 0;chanlist->channels[i].min_scan_time = 0;chanlist->channels[i].max_scan_time = 100;}WiFi_SendCommand(CMD_802_11_SCAN, wifi_buffer_command, sizeof(WiFi_CmdRequest_Scan) + TLV_STRUCTLEN(*chanlist), WiFi_Scan_Callback, p, 3000, WIFI_DEFAULT_MAXRETRY);
}static void WiFi_Scan_Callback(void *arg, void *data, WiFi_Status status)
{void **p = (void **)arg;void *app_arg = p[0];WiFi_Callback app_callback = (WiFi_Callback)p[1];uint8_t i, j, n;WiFi_CmdRequest_Scan *cmd = (WiFi_CmdRequest_Scan *)wifi_buffer_command;MrvlIETypes_ChanListParamSet_t *chanlist = (MrvlIETypes_ChanListParamSet_t *)(cmd + 1);uint8_t ssid[33], channel;uint16_t ie_size;WiFi_CmdResponse_Scan *resp = (WiFi_CmdResponse_Scan *)data;WiFi_BssDescSet *bss_desc_set;WiFi_SecurityType security;WiFi_Vendor *vendor;IEEEType *ie_params;//MrvlIETypes_TsfTimestamp_t *tft_table;if (status != WIFI_STATUS_OK){printf("WiFi_Scan error!\n");free(arg);if (app_callback)app_callback(app_arg, data, status);return;}// 发送扫描接下来的4个通道的命令j = chanlist->channels[0].chan_number + 4;if (j < 14){if (j == 13)n = 2;elsen = 4;chanlist->header.length = n * sizeof(chanlist->channels);for (i = 0; i < n; i++)chanlist->channels[i].chan_number = i + j;WiFi_SendCommand(CMD_802_11_SCAN, wifi_buffer_command, sizeof(WiFi_CmdRequest_Scan) + TLV_STRUCTLEN(*chanlist), WiFi_Scan_Callback, arg, 3000, WIFI_DEFAULT_MAXRETRY);}elsen = 0;// 显示本次扫描结果, num_of_set为热点数if (resp->num_of_set > 0){bss_desc_set = (WiFi_BssDescSet *)(resp + 1);for (i = 0; i < resp->num_of_set; i++){security = WIFI_SECURITYTYPE_WEP;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 WIFI_MRVLIETYPES_SSIDPARAMSET:// SSID名称memcpy(ssid, ie_params->data, ie_params->header.length);ssid[ie_params->header.length] = '\0';break;case WIFI_MRVLIETYPES_PHYPARAMDSSET:// 通道号channel = ie_params->data[0];break;case WIFI_MRVLIETYPES_RSNPARAMSET:security = WIFI_SECURITYTYPE_WPA2;break;case WIFI_MRVLIETYPES_VENDORPARAMSET:if (security != WIFI_SECURITYTYPE_WPA2){vendor = (WiFi_Vendor *)ie_params->data;if (vendor->oui[0] == 0x00 && vendor->oui[1] == 0x50 && vendor->oui[2] == 0xf2 && vendor->oui_type == 0x01)security = WIFI_SECURITYTYPE_WPA;}break;}ie_size -= TLV_STRUCTLEN(*ie_params);ie_params = (IEEEType *)TLV_NEXT(ie_params);}if ((bss_desc_set->cap_info & WIFI_CAPABILITY_PRIVACY) == 0)security = WIFI_SECURITYTYPE_NONE;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);switch (security){case WIFI_SECURITYTYPE_NONE:printf("Unsecured");break;case WIFI_SECURITYTYPE_WEP:printf("WEP");break;case WIFI_SECURITYTYPE_WPA:printf("WPA");break;case WIFI_SECURITYTYPE_WPA2:printf("WPA2");break;}printf(", Mode: ");if (bss_desc_set->cap_info & WIFI_CAPABILITY_IBSS)printf("Ad-Hoc");elseprintf("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 == WIFI_MRVLIETYPES_TSFTIMESTAMP && tft_table->header.length == resp->num_of_set * sizeof(uint64_t)){printf("Timestamps: ");for (i = 0; i < resp->num_of_set; i++)printf("%lld ", tft_table->tsf_table[i]);printf("\n");}*/// TSF timestamp table是整个数据的末尾, 后面没有Channel/band table//if (((uint8_t *)tft_table - (uint8_t *)data) + TLV_STRUCTLEN(*tft_table) == resp->header.frame_header.length)//  printf("data end!\n");}// 扫描完毕时调用回调函数if (n == 0){free(arg);if (app_callback)app_callback(app_arg, data, status);}
}/* 获取指定名称的热点的详细信息 */
void WiFi_ScanSSID(const char *ssid, WiFi_SSIDInfo *info, WiFi_Callback callback, void *arg)
{uint8_t i;void **p;MrvlIETypes_ChanListParamSet_t *chan_list;WiFi_CmdRequest_Scan *cmd = (WiFi_CmdRequest_Scan *)wifi_buffer_command;if (WiFi_CheckCommandBusy(callback, arg))return;p = malloc(3 * sizeof(void *));if (p == NULL){printf("WiFi_ScanSSID: malloc failed!\n");if (callback)callback(arg, NULL, WIFI_STATUS_MEM);return;}p[0] = arg;p[1] = callback;p[2] = info;memset(info, 0, sizeof(WiFi_SSIDInfo)); // 将整个info结构体清零cmd->bss_type = WIFI_BSS_ANY;memset(cmd->bss_id, 0, sizeof(cmd->bss_id));// 给info->ssid成员赋值info->ssid.header.type = WIFI_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)); // 把info->ssid复制到待发送的命令内容中chan_list = (MrvlIETypes_ChanListParamSet_t *)((uint8_t *)(cmd + 1) + TLV_STRUCTLEN(info->ssid));chan_list->header.type = WIFI_MRVLIETYPES_CHANLISTPARAMSET;chan_list->header.length = 14 * sizeof(chan_list->channels); // 一次性扫描14个通道for (i = 0; i < 14; i++) // i作下标, i+1才是通道号{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, wifi_buffer_command, ((uint8_t *)chan_list - wifi_buffer_command) + TLV_STRUCTLEN(*chan_list), WiFi_ScanSSID_Callback, p, 3000, WIFI_DEFAULT_MAXRETRY);
}static void WiFi_ScanSSID_Callback(void *arg, void *data, WiFi_Status status)
{void **p = (void **)arg;void *app_arg = p[0];WiFi_Callback app_callback = (WiFi_Callback)p[1];WiFi_SSIDInfo *info = p[2];uint16_t ie_size;WiFi_CmdResponse_Scan *resp = (WiFi_CmdResponse_Scan *)data;WiFi_BssDescSet *bss_desc_set = (WiFi_BssDescSet *)(resp + 1);IEEEType *ie_params;WiFi_Vendor *vendor;free(arg);if (status != WIFI_STATUS_OK){printf("WiFi_ScanSSID error!\n");if (app_callback)app_callback(app_arg, data, status);return;}if (resp->num_of_set == 0){// 未找到指定的AP热点, 此时info结构体中除了ssid成员外, 其余的成员均为0// resp中的内容到了num_of_set成员后就没有了printf("No SSID!\n");if (app_callback)app_callback(app_arg, data, WIFI_STATUS_NOTFOUND);return;}// 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;// 若info->xxx.header.type=0, 则表明没有该项的信息 (除SSID结构体外, 因为SSID的type=WIFI_MRVLIETYPES_SSIDPARAMSET=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 WIFI_MRVLIETYPES_RATESPARAMSET:// 速率WiFi_TranslateTLV((MrvlIEType *)&info->rates, ie_params, sizeof(info->rates.rates));break;case WIFI_MRVLIETYPES_PHYPARAMDSSET:// 通道号info->channel = ie_params->data[0];break;case WIFI_MRVLIETYPES_RSNPARAMSET:// 通常只有一个RSN信息 (与WPA2相关)WiFi_TranslateTLV((MrvlIEType *)&info->rsn, ie_params, sizeof(info->rsn.rsn));break;case WIFI_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_ouiWiFi_TranslateTLV((MrvlIEType *)&info->wpa, ie_params, sizeof(info->wpa.vendor));break;case 0x02:// wmm_ouiif (ie_params->header.length == 24) // 合法大小WiFi_TranslateTLV((MrvlIEType *)&info->wwm, ie_params, sizeof(info->wwm.vendor));break;case 0x04:// wps_ouiWiFi_TranslateTLV((MrvlIEType *)&info->wps, ie_params, sizeof(info->wps.vendor));break;}}break;}// 转向下一个TLVie_size -= TLV_STRUCTLEN(*ie_params);ie_params = (IEEEType *)TLV_NEXT(ie_params);}if (app_callback)app_callback(app_arg, data, status);
}/* 发送WiFi命令, 收到回应或超时时调用callback回调函数 */
// size=0表示data包含完整的命令数据, 且code和size可直接从data中获取(用于重发)
// retry可以为0(第一次失败时就直接调用回调函数, 不再重试), 但timeout不能为0(否则收到回应前会误认为超时并调用回调函数)
//
// 无操作系统的环境下只能使用非阻塞方式执行WiFi命令, 并通过回调函数通知命令执行的结果 (回调函数应保证能够被调用并只调用一次)
// 如果有操作系统, 某个任务想要以阻塞方式执行WiFi命令, 可以在该函数里面添加发送命令前阻塞等待表示命令通道是否可用的0-1信号量的代码
// 当命令通道可用时, 使信号量的值为1并唤醒其中一个等待发送命令的任务, 发送命令后继续阻塞等待回应, 调用回调函数并根据命令执行结果(成功还是失败)决定函数的返回值
void WiFi_SendCommand(uint16_t code, const void *data, uint16_t size, WiFi_Callback callback, void *arg, uint32_t timeout, uint8_t max_retry)
{static uint16_t seq_num = 0;WiFi_CommandHeader *cmdhdr = (WiFi_CommandHeader *)wifi_buffer_command;if (WiFi_CheckCommandBusy(callback, arg)) // 发送命令前必须确保之前的命令已经发送完毕return;// 直接发送命令: WiFi_SendCommand(0, data, 0, ...)// 填充命令头并发送命令: WiFi_SendCommand(code, data, size, ...)// 重试上次命令: WiFi_SendCommand(0, NULL, 0, ...)if (data != NULL && data != wifi_buffer_command)memmove(wifi_buffer_command, data, (size != 0) ? size : cmdhdr->frame_header.length); // 将要发送的命令内容复制到缓冲区中, 以便出错时重发if (size != 0){cmdhdr->frame_header.length = size;cmdhdr->frame_header.type = WIFI_SDIOFRAME_COMMAND;cmdhdr->cmd_code = code;cmdhdr->size = size - sizeof(WiFi_SDIOFrameHeader); // 命令大小包括命令头部, 但不包括SDIO帧头部cmdhdr->seq_num = seq_num++;cmdhdr->result = 0;}elsesize = cmdhdr->frame_header.length; // 重发命令时不填写cmdhdrWiFi_LowLevel_WriteData(1, wifi_port, wifi_buffer_command, size, sizeof(wifi_buffer_command));// WriteData函数出错的概率很小, 这里简单起见就不去判断它的返回值了// 即使出错了(如CRC校验错误), 由于收不到命令回应, WiFi_CheckTimeout函数也会重传该命令wifi_tx_command.arg = arg;wifi_tx_command.busy = 1;wifi_tx_command.callback = callback;wifi_tx_command.ready = 0;wifi_tx_command.retry = max_retry;wifi_tx_command.start_time = sys_now();wifi_tx_command.timeout = timeout;
}/* 发送EAPOL回应帧 */
static void WiFi_SendEAPOLResponse(const WiFi_EAPOLKeyFrame *packet_rx, uint16_t key_info, const void *key_data, uint16_t key_data_len, WiFi_Callback callback, void *arg)
{uint8_t ret;uint16_t len;WiFi_EAPOLKeyFrame *packet_tx = (WiFi_EAPOLKeyFrame *)WiFi_GetPacketBuffer();WiFi_KeyType key_type = (WiFi_KeyType)(key_info & 0x07);WiFi_MIC mic;memcpy(packet_tx->dest, packet_rx->src, 6); // 目标MAC地址memcpy(packet_tx->src, packet_rx->dest, 6); // 源MAC地址packet_tx->type = htons(0x888e); // 大端序的0x888e: 802.1X Authenticationpacket_tx->version = packet_rx->version; // 协议版本号 (MIC从此字段开始计算)packet_tx->packet_type = packet_rx->packet_type; // 通常为3: Keypacket_tx->packet_body_length = packet_tx->key_data - &packet_tx->descriptor_type + key_data_len;packet_tx->packet_body_length = htons(packet_tx->packet_body_length);packet_tx->descriptor_type = packet_rx->descriptor_type; // 描述符类型packet_tx->key_information = htons(key_info);packet_tx->key_length = packet_rx->key_length;memcpy(packet_tx->key_replay_counter, packet_rx->key_replay_counter, sizeof(packet_rx->key_replay_counter));memcpy(packet_tx->key_nonce, wifi_snonce, sizeof(wifi_snonce));memset(packet_tx->key_iv, 0, sizeof(packet_tx->key_iv) + sizeof(packet_tx->key_rsc) + sizeof(packet_tx->reserved));packet_tx->key_data_length = htons(key_data_len);if (key_data)memcpy(packet_tx->key_data, key_data, key_data_len);// 用KCK对要发送的EAPOL帧(以太网帧去掉两个MAC地址字段和type/length=0x888e字段剩下的payload)进行运算得到MIClen = sizeof(WiFi_EAPOLKeyFrame) - sizeof(packet_tx->key_data) + key_data_len; // 帧的总长度memset(packet_tx->key_mic, 0, sizeof(packet_tx->key_mic)); // 先将MIC字段清零if (key_type == WIFI_KEYTYPE_TKIP)ret = hmac_md5(wifi_ptk.KCK, sizeof(wifi_ptk.KCK), &packet_tx->version, len - 14, mic.MIC); // 计算MICelse if (key_type == WIFI_KEYTYPE_AES)ret = hmac_sha1(wifi_ptk.KCK, sizeof(wifi_ptk.KCK), &packet_tx->version, len - 14, mic.MIC);else{printf("WiFi_SendEAPOLResponse: unsupported key type!\n");if (callback)callback(arg, packet_tx, WIFI_STATUS_INVALID);return;}if (!ret){printf("WiFi_SendEAPOLResponse: out of memory!\n");if (callback)callback(arg, packet_tx, WIFI_STATUS_MEM);return;}memcpy(packet_tx->key_mic, mic.MIC, sizeof(mic.MIC)); // 将计算结果放到MIC字段上WiFi_SendPacket(packet_tx, len, callback, arg, WIFI_DEFAULT_TIMEOUT_DATAACK);
}/* 发送数据帧 */
// data指向的是WiFi_DataTx.payload
// size=0表示再次发送上一帧, 此时data参数将被忽略
void WiFi_SendPacket(void *data, uint16_t size, WiFi_Callback callback, void *arg, uint32_t timeout)
{WiFi_DataTx *packet = (WiFi_DataTx *)wifi_buffer_packet;// 发送新数据帧: WiFi_SendPacket(data, size, ...)// 重传前一数据帧: WiFi_SendPacket(NULL, 0, ...)if (size == 0)data = packet->payload;WiFi_WaitForLastTask();if (data != packet->payload)memmove(packet->payload, data, size); // 将要发送的数据内容复制到缓冲区中if (size != 0){// 有关发送数据包的细节, 请参考Firmware Specification PDF的Chapter 3: Data Pathpacket->header.length = sizeof(WiFi_DataTx) - sizeof(packet->payload) + size;packet->header.type = WIFI_SDIOFRAME_DATA;packet->reserved1 = 0;packet->tx_control = 0; // 控制信息的格式请参考3.2.1 Per-Packet Settingspacket->tx_packet_offset = sizeof(WiFi_DataTx) - sizeof(packet->payload) - sizeof(packet->header); // 不包括SDIOFrameHeaderpacket->tx_packet_length = size;memcpy((void *)&packet->tx_dest_addr_high, packet->payload, 6);packet->priority = 0;packet->flags = 0;packet->pkt_delay_2ms = 0;packet->reserved2 = 0;}WiFi_LowLevel_WriteData(1, wifi_port, wifi_buffer_packet, packet->header.length, sizeof(wifi_buffer_packet));// 接下来需要等待Download Ready位置1, 表明数据帧发送成功wifi_tx_packet.arg = arg;wifi_tx_packet.busy = 1;wifi_tx_packet.callback = callback;wifi_tx_packet.retry = 0;wifi_tx_packet.start_time = sys_now();wifi_tx_packet.timeout = timeout;
}/* 将PTK和GTK密钥发送给固件使用 */
static void WiFi_SetKeyMaterial(WiFi_KeyType key_type, uint8_t key_num, WiFi_Callback callback, void *arg)
{MrvlIETypes_KeyParamSet_t keys[2];uint16_t key_len;if (key_type == WIFI_KEYTYPE_TKIP)key_len = sizeof(wifi_ptk.TK) + sizeof(wifi_ptk.TKIPTxMICKey) + sizeof(wifi_ptk.TKIPRxMICKey);else if (key_type == WIFI_KEYTYPE_AES)key_len = sizeof(wifi_ptk.TK);else{if (callback)callback(arg, NULL, WIFI_STATUS_INVALID);return;}keys[0].key_type_id = key_type;keys[0].key_info = WIFI_KEYINFO_KEYENABLED | WIFI_KEYINFO_UNICASTKEY; // 单播密钥keys[0].key_len = key_len;memcpy(keys[0].key, wifi_ptk.TK, sizeof(wifi_ptk.TK));if (key_type == WIFI_KEYTYPE_TKIP){// 固件中表示的MIC校验用的密钥顺序刚好和PRF函数产生的顺序相反memcpy(keys[0].key + sizeof(wifi_ptk.TK), wifi_ptk.TKIPRxMICKey, sizeof(wifi_ptk.TKIPRxMICKey));memcpy(keys[0].key + sizeof(wifi_ptk.TK) + sizeof(wifi_ptk.TKIPRxMICKey), wifi_ptk.TKIPTxMICKey, sizeof(wifi_ptk.TKIPTxMICKey));}if (key_num == 2){keys[1].key_type_id = key_type;keys[1].key_info = WIFI_KEYINFO_KEYENABLED | WIFI_KEYINFO_MULTICASTKEY; // 多播、广播密钥keys[1].key_len = key_len;memcpy(keys[1].key, wifi_gtk.TK, sizeof(wifi_gtk.TK));if (key_type == WIFI_KEYTYPE_TKIP){memcpy(keys[1].key + sizeof(wifi_gtk.TK), wifi_gtk.TKIPRxMICKey, sizeof(wifi_gtk.TKIPRxMICKey));memcpy(keys[1].key + sizeof(wifi_gtk.TK) + sizeof(wifi_gtk.TKIPRxMICKey), wifi_gtk.TKIPTxMICKey, sizeof(wifi_gtk.TKIPTxMICKey));}}WiFi_KeyMaterial(WIFI_ACT_SET, keys, key_num, callback, arg);
}/* 设置WEP密钥 (长度必须为5或13个字符) */
// action: WIFI_ACT_ADD / WIFI_ACT_REMOVE, 移除密钥时参数key可以设为NULL
void WiFi_SetWEP(WiFi_CommandAction action, const WiFi_WEPKey *key, WiFi_Callback callback, void *arg)
{uint8_t i, j, len;uint16_t cmd_size;uint32_t temp;WiFi_Cmd_SetWEP *cmd = (WiFi_Cmd_SetWEP *)wifi_buffer_command;if (WiFi_CheckCommandBusy(callback, arg))return;cmd->action = action;cmd->tx_key_index = key->index;if (action == WIFI_ACT_ADD){memset(cmd->wep_types, 0, sizeof(cmd->wep_types) + sizeof(cmd->keys));for (i = 0; i < 4; i++){if (key->keys[i] == NULL)continue;len = strlen(key->keys[i]);if (len == 5 || len == 13){// 5个或13个ASCII密钥字符if (len == 5)cmd->wep_types[i] = WIFI_WEPKEYTYPE_40BIT;else if (len == 13)cmd->wep_types[i] = WIFI_WEPKEYTYPE_104BIT;memcpy(cmd->keys[i], key->keys[i], len);}else if (len == 10 || len == 26){// 10个或26个16进制密钥字符if (len == 10)cmd->wep_types[i] = WIFI_WEPKEYTYPE_40BIT;else if (len == 26)cmd->wep_types[i] = WIFI_WEPKEYTYPE_104BIT;for (j = 0; j < len; j += 2){if (!isxdigit(key->keys[i][j]) || !isxdigit(key->keys[i][j + 1])){printf("WiFi_SetWEP: The hex key %d contains invalid characters!\n", i);if (callback)callback(arg, NULL, WIFI_STATUS_INVALID);return;}sscanf(key->keys[i] + j, "%02x", &temp);cmd->keys[i][j / 2] = temp;}}else{printf("WiFi_SetWEP: The length of key %d is invalid!\n", i);if (callback)callback(arg, NULL, WIFI_STATUS_INVALID);return;}}cmd_size = sizeof(WiFi_Cmd_SetWEP);}else if (action == WIFI_ACT_REMOVE)cmd_size = cmd->wep_types - wifi_buffer_command;else{if (callback)callback(arg, NULL, WIFI_STATUS_INVALID);return;}WiFi_SendCommand(CMD_802_11_SET_WEP, wifi_buffer_command, cmd_size, callback, arg, WIFI_DEFAULT_TIMEOUT_CMDRESP, WIFI_DEFAULT_MAXRETRY);
}/* 设置WPA密码, 并生成PSK */
void WiFi_SetWPA(const char *ssid, const char *password)
{uint8_t ret = pbkdf2_hmac_sha1(password, strlen(password), ssid, strlen(ssid), 4096, wifi_psk, sizeof(wifi_psk));if (!ret)printf("WiFi_SetWPA: out of memory!\n");
}/* 显示WiFi模块信息 */
void WiFi_ShowCIS(void)
{uint8_t data[255];uint8_t func, i, n, len;uint8_t tpl_code, tpl_link; // 16.2 Basic Tuple Format and Tuple Chain Structureuint32_t cis_ptr;n = WiFi_LowLevel_GetFunctionNum();for (func = 0; func <= n; func++){// 获取CIS的地址cis_ptr = (func << 8) | 0x9;cis_ptr  = WiFi_LowLevel_ReadReg(0, cis_ptr) | (WiFi_LowLevel_ReadReg(0, cis_ptr + 1) << 8) | (WiFi_LowLevel_ReadReg(0, cis_ptr + 2) << 16);printf("[CIS] func=%d, ptr=0x%08x\n", func, cis_ptr);// 遍历CIS, 直到尾节点while ((tpl_code = WiFi_LowLevel_ReadReg(0, cis_ptr++)) != CISTPL_END){if (tpl_code == CISTPL_NULL)continue;tpl_link = WiFi_LowLevel_ReadReg(0, cis_ptr++); // 本结点数据的大小for (i = 0; i < tpl_link; i++)data[i] = WiFi_LowLevel_ReadReg(0, cis_ptr + i);switch (tpl_code){case CISTPL_VERS_1:printf("Product Information:");for (i = 2; data[i] != 0xff; i += len + 1){// 遍历所有字符串len = strlen((char *)data + i);if (len != 0)printf(" %s", data + i);}printf("\n");break;case CISTPL_MANFID:// 16.6 CISTPL_MANFID: Manufacturer Identification String Tupleprintf("Manufacturer Code: 0x%04x\n", *(uint16_t *)data); // TPLMID_MANFprintf("Manufacturer Information: 0x%04x\n", *(uint16_t *)(data + 2)); // TPLMID_CARDbreak;case CISTPL_FUNCID:// 16.7.1 CISTPL_FUNCID: Function Identification Tupleprintf("Card Function Code: 0x%02x\n", data[0]); // TPLFID_FUNCTIONprintf("System Initialization Bit Mask: 0x%02x\n", data[1]); // TPLFID_SYSINITbreak;case CISTPL_FUNCE:// 16.7.2 CISTPL_FUNCE: Function Extension Tupleif (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}break;default:printf("[CIS Tuple 0x%02x] addr=0x%08x size=%d\n", tpl_code, cis_ptr - 2, tpl_link);dump_data(data, tpl_link);}if (tpl_link == 0xff)break; // 当TPL_LINK为0xff时说明当前结点为尾节点cis_ptr += tpl_link;}}
}/* 创建一个Ad-Hoc型的WiFi热点 */
// 创建带有WEP密码的热点时, cap_info为WIFI_CAPABILITY_PRIVACY
// 创建无密码的热点时, cap_info为0
void WiFi_StartADHOC(const char *ssid, uint16_t cap_info, WiFi_Callback callback, void *arg)
{WiFi_Cmd_ADHOCStart *cmd = (WiFi_Cmd_ADHOCStart *)wifi_buffer_command;if (WiFi_CheckCommandBusy(callback, arg))return;strncpy((char *)cmd->ssid, ssid, sizeof(cmd->ssid));cmd->bss_type = WIFI_BSS_INDEPENDENT;cmd->bcn_period = 100;cmd->reserved1 = 0;cmd->ibss_param_set.header.type = WIFI_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->ds_param_set.header.type = WIFI_MRVLIETYPES_PHYPARAMDSSET;cmd->ds_param_set.header.length = TLV_PAYLOADLEN(cmd->ds_param_set);cmd->ds_param_set.channel = 1;memset(cmd->reserved3, 0, sizeof(cmd->reserved3));cmd->cap_info = WIFI_CAPABILITY_IBSS | cap_info;memset(cmd->data_rate, 0, sizeof(cmd->data_rate));*(uint32_t *)cmd->data_rate = 0x968b8482;WiFi_SendCommand(CMD_802_11_AD_HOC_START, wifi_buffer_command, sizeof(WiFi_Cmd_ADHOCStart), callback, arg, WIFI_DEFAULT_TIMEOUT_CMDRESP, WIFI_DEFAULT_MAXRETRY);
}/* 创建一个Ad-Hoc型的WiFi热点并设置好密码 */
// conn->mac_addr成员将被忽略
void WiFi_StartADHOCEx(const WiFi_Connection *conn, WiFi_Callback callback, void *arg)
{uint16_t ssid_len;void **p;WiFi_SecurityType *psecurity;if (WiFi_CheckCommandBusy(callback, arg))return;if (conn->security == WIFI_SECURITYTYPE_WPA || conn->security == WIFI_SECURITYTYPE_WPA2){printf("WiFi_StartADHOCEx: WPA is not supported!\n");if (callback)callback(arg, NULL, WIFI_STATUS_INVALID);return;}ssid_len = strlen(conn->ssid);p = malloc(2 * sizeof(void *) + sizeof(WiFi_SecurityType) + ssid_len + 1);if (p == NULL){printf("WiFi_StartADHOCEx: malloc failed!\n");if (callback)callback(arg, NULL, WIFI_STATUS_MEM);return;}p[0] = arg;p[1] = callback;psecurity = (WiFi_SecurityType *)(p + 2);*psecurity = conn->security;memcpy(psecurity + 1, conn->ssid, ssid_len + 1);if (conn->security == WIFI_SECURITYTYPE_WEP)WiFi_SetWEP(WIFI_ACT_ADD, conn->password, WiFi_StartADHOCEx_Callback, p);elseWiFi_MACControl(WIFI_MACCTRL_ETHERNET2 | WIFI_MACCTRL_TX | WIFI_MACCTRL_RX, WiFi_StartADHOCEx_Callback, p);
}static void WiFi_StartADHOCEx_Callback(void *arg, void *data, WiFi_Status status)
{void **p = (void **)arg;void *app_arg = p[0];WiFi_Callback app_callback = (WiFi_Callback)p[1];WiFi_SecurityType *psecurity = (WiFi_SecurityType *)(p + 2);char *ssid = (char *)(psecurity + 1);if (status != WIFI_STATUS_OK){printf("WiFi_StartADHOCEx error!\n");free(arg);if (app_callback)app_callback(app_arg, data, status);return;}switch (WiFi_GetCommandCode(data)){case CMD_802_11_SET_WEP:WiFi_MACControl(WIFI_MACCTRL_ETHERNET2 | WIFI_MACCTRL_WEP | WIFI_MACCTRL_TX | WIFI_MACCTRL_RX, WiFi_StartADHOCEx_Callback, arg);break;case CMD_MAC_CONTROL:if (*psecurity == WIFI_SECURITYTYPE_NONE)WiFi_StartADHOC(ssid, 0, WiFi_StartADHOCEx_Callback, arg);elseWiFi_StartADHOC(ssid, WIFI_CAPABILITY_PRIVACY, WiFi_StartADHOCEx_Callback, arg);break;case CMD_802_11_AD_HOC_START:free(arg);if (app_callback)app_callback(app_arg, data, status);break;}
}void WiFi_StopADHOC(WiFi_Callback callback, void *arg)
{WiFi_SendCommand(CMD_802_11_AD_HOC_STOP, wifi_buffer_command, sizeof(WiFi_CommandHeader), callback, arg, WIFI_DEFAULT_TIMEOUT_CMDRESP, WIFI_DEFAULT_MAXRETRY);
}/* 释放发送缓冲区并调用回调函数 */
static void WiFi_TxBufferComplete(WiFi_TxBuffer *tbuf, void *data, WiFi_Status status)
{if (tbuf->busy){tbuf->busy = 0;if (tbuf->callback)tbuf->callback(tbuf->arg, data, status); // 调用回调函数}
}/* 将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; // 若源数据大小超过缓冲区最大容量, 则丢弃剩余数据elsemrvlie_tlv->header.length = ieee_tlv->header.length;memset(mrvlie_tlv->data, 0, mrvlie_payload_size); // 清零memcpy(mrvlie_tlv->data, ieee_tlv->data, mrvlie_tlv->header.length); // 复制数据return mrvlie_tlv->header.length == ieee_tlv->header.length; // 返回值表示缓冲区大小是否足够
}/* 在规定的超时时间内, 等待指定的卡状态位置位, 并清除相应的中断标志位 */
// 成功时返回1
uint8_t WiFi_Wait(uint8_t status, uint32_t timeout)
{if (timeout != 0)timeout += sys_now();while ((WiFi_LowLevel_ReadReg(1, WIFI_INTSTATUS) & status) != status){if (timeout != 0 && sys_now() > timeout){// 若超时时间已到printf("WiFi_Wait(0x%02x): timeout!\n", status);return 0;}}// 清除对应的中断标志位WiFi_LowLevel_WriteReg(1, WIFI_INTSTATUS, WIFI_INTSTATUS_ALL & ~status); // 不需要清除的位必须为1// 不能将SDIOIT位清除掉! 否则有可能导致该位永远不再置位return 1;
}/* 等待之前发送的命令帧或数据帧收到确认 */
void WiFi_WaitForLastTask(void)
{int32_t remaining;
#ifdef WIFI_DISPLAY_RESPTIMEWiFi_CommandHeader *tx_cmd = (WiFi_CommandHeader *)wifi_buffer_command;
#endif// 执行CMD53命令发送数据前, 必须等待之前发送的数据收到DNLDRDY的确认// 注: CMDBUSY=DATABUSY=1且CMDRDY=0的情况是不可能出现的while ((wifi_tx_command.busy && wifi_tx_command.ready == 0) || wifi_tx_packet.busy){if (wifi_tx_command.busy && wifi_tx_command.ready == 0){remaining = wifi_tx_command.start_time + WIFI_DEFAULT_TIMEOUT_CMDACK - sys_now() + 1;if (remaining > 0){if (WiFi_Wait(WIFI_INTSTATUS_DNLD, remaining)){
#ifdef WIFI_DISPLAY_RESPTIMEprintf("-- CMD 0x%04x ACK at %dms\n", tx_cmd->cmd_code, sys_now() - wifi_tx_command.start_time);
#endif}}wifi_tx_command.ready = 1;// 现在命令要么超时, 要么已收到确认, 这里不负责重传命令}if (wifi_tx_packet.busy){// 只需要等待download ready位置位, 不用管新来的数据帧(upload ready)remaining = wifi_tx_packet.start_time + wifi_tx_packet.timeout - sys_now() + 1; // 剩余时间+1 (小于等于0表示超时)if (remaining > 0 && WiFi_Wait(WIFI_INTSTATUS_DNLD, remaining)) // 在剩余时间+1内等待标志位置位, 并清除中断标志位{
#ifdef WIFI_DISPLAY_RESPTIMEprintf("-- Packet ACK at %dms\n", sys_now() - wifi_tx_packet.start_time);
#endifWiFi_TxBufferComplete(&wifi_tx_packet, wifi_buffer_packet, WIFI_STATUS_OK); // 若DNLDRDY已置位, 则表明数据帧发送成功, 将busy清零并调用相应的回调函数// 如果在回调函数中发送了新帧, 那么busy仍等于1, 需要继续等待, 所以这里不能写break}elseWiFi_CheckTxBufferRetry(&wifi_tx_packet, wifi_buffer_packet); // 通知回调函数超时}}
}

WPA.h:

#define HMAC_MD5_BLOCKSIZE 64
#define HMAC_MD5_OUTPUTSIZE 16
#define HMAC_SHA1_BLOCKSIZE 64
#define HMAC_SHA1_OUTPUTSIZE 20typedef void (*HashFunction)(unsigned char *input, int ilen, unsigned char *output);void ARC4_decrypt_keydata(const uint8_t *KEK, const uint8_t *key_iv, const uint8_t *data, uint16_t datalen, uint8_t *output);
uint16_t AES_unwrap(const uint8_t *key, const uint8_t *data, uint16_t datalen, uint8_t *output);
uint8_t hmac(const void *key, uint16_t keylen, const void *msg, uint16_t msglen, HashFunction hash, uint8_t blocksize, uint8_t *output, uint8_t outputsize);
uint8_t hmac_md5(const void *key, uint16_t keylen, const void *msg, uint16_t msglen, uint8_t output[HMAC_MD5_OUTPUTSIZE]);
uint8_t hmac_sha1(const void *key, uint16_t keylen, const void *msg, uint16_t msglen, uint8_t output[HMAC_SHA1_OUTPUTSIZE]);
uint8_t pbkdf2_hmac_sha1(const void *password, uint16_t pwdlen, const void *salt, uint16_t saltlen, uint32_t c, uint8_t *dk, uint16_t dklen);
uint8_t PRF(const void *k, uint16_t klen, const char *a, const void *b, uint16_t blen, void *output, uint8_t n);

WPA.c:

#include <aes.h>
#include <netif/ppp/polarssl/arc4.h>
#include <netif/ppp/polarssl/md5.h>
#include <netif/ppp/polarssl/sha1.h>
#include <stdlib.h>
#include <string.h>
#include "WPA.h"/* ARC4算法解密TKIP Key Data封包 */
// ARC4的加解密算法相同, 但每次都必须要调用一次arc4_setup函数
// 参考文章: http://www.fenlog.com/post/111.html
void ARC4_decrypt_keydata(const uint8_t *KEK, const uint8_t *key_iv, const uint8_t *data, uint16_t datalen, uint8_t *output)
{arc4_context ctx;uint8_t dummy[256] = {0};uint8_t newkey[2][16];memcpy(newkey[0], key_iv, sizeof(newkey[0]));memcpy(newkey[1], KEK, sizeof(newkey[1]));arc4_setup(&ctx, newkey[0], sizeof(newkey));arc4_crypt(&ctx, dummy, sizeof(dummy)); // discard the first 256 bytes of the RC4 cipher stream outputmemcpy(output, data, datalen);arc4_crypt(&ctx, output, datalen);
}/* AES Key Wrap解密算法解密AES Key Data封包 */
// 88W8686不支持CMD_802_11_CRYPTO命令, 因此必须用软件实现此算法
// RFC3394.pdf: 2.2.2 Key Unwrap
// 返回值为输出结果的数据大小
uint16_t AES_unwrap(const uint8_t *key, const uint8_t *data, uint16_t datalen, uint8_t *output)
{uint8_t a[8], b[16];uint8_t *r;uint16_t i, j, n, t;struct AES_ctx ctx;AES_init_ctx(&ctx, key);/* Initialize variables */n = (datalen / 8) - 1;memcpy(a, data, 8);r = output;memcpy(r, data + 8, datalen - 8);/* Compute intermediate values */for (j = 5; j <= 5; j--){r = output + (n - 1) * 8;for (i = n; i >= 1; i--){t = n * j + i;memcpy(b, a, 8);b[7] ^= t;memcpy(b + 8, r, 8);AES_ECB_decrypt(&ctx, b);memcpy(a, b, 8);memcpy(r, b + 8, 8);r -= 8;}}return datalen - 8;
}/* HMAC算法 */
// https://en.wikipedia.org/wiki/Hash-based_message_authentication_code
// hash函数不会改变input参数的值, 所以可以放心去掉const修饰符
uint8_t hmac(const void *key, uint16_t keylen, const void *msg, uint16_t msglen, HashFunction hash, uint8_t blocksize, uint8_t *output, uint8_t outputsize)
{uint8_t i, *p;uint8_t *obuf = (uint8_t *)malloc(blocksize + outputsize); // 用最前面的blocksize字节存放转换后的keyif (obuf == NULL)return 0; // 计算失败: 内存不足// Keys longer than blockSize are shortened by hashing themif (keylen > blocksize){hash((uint8_t *)key, keylen, obuf); // Key becomes outputSize bytes longkeylen = outputsize;}elsememcpy(obuf, key, keylen);// Keys shorter than blockSize are padded to blockSize by padding with zeros on the rightif (keylen < blocksize)memset(obuf + keylen, 0, blocksize - keylen); // pad key with zeros to make it blockSize bytes long// Inner padded keyp = (uint8_t *)malloc(blocksize + msglen);if (p == NULL){free(obuf);return 0; // 计算失败: 内存不足}for (i = 0; i < blocksize; i++)p[i] = obuf[i] ^ 0x36;memcpy(p + blocksize, msg, msglen);hash(p, blocksize + msglen, obuf + blocksize);free(p);// Outer padded keyfor (i = 0; i < blocksize; i++)obuf[i] ^= 0x5c;hash(obuf, blocksize + outputsize, (uint8_t *)output);free(obuf);return 1; // 计算成功
}/* 利用lwip提供的md5函数实现HMAC_MD5算法 */
uint8_t hmac_md5(const void *key, uint16_t keylen, const void *msg, uint16_t msglen, uint8_t output[HMAC_MD5_OUTPUTSIZE])
{return hmac(key, keylen, msg, msglen, md5, HMAC_MD5_BLOCKSIZE, output, HMAC_MD5_OUTPUTSIZE);
}/* 利用lwip提供的sha1函数实现HMAC_SHA1算法 */
uint8_t hmac_sha1(const void *key, uint16_t keylen, const void *msg, uint16_t msglen, uint8_t output[HMAC_SHA1_OUTPUTSIZE])
{return hmac(key, keylen, msg, msglen, sha1, HMAC_SHA1_BLOCKSIZE, output, HMAC_SHA1_OUTPUTSIZE);
}/* 利用hmac_sha1函数实现PBKDF2_HMAC_SHA1算法 */
// https://en.wikipedia.org/wiki/PBKDF2
uint8_t pbkdf2_hmac_sha1(const void *password, uint16_t pwdlen, const void *salt, uint16_t saltlen, uint32_t c, uint8_t *dk, uint16_t dklen)
{uint8_t curr, k, *p;uint8_t ret;uint8_t u[3][HMAC_SHA1_OUTPUTSIZE];uint16_t i;uint32_t j;p = (uint8_t *)malloc(saltlen + 4);if (p == NULL)return 0;memcpy(p, salt, saltlen);memset(p + saltlen, 0, 2);for (i = 1; dklen; i++){// INT_32_BE(i)p[saltlen + 2] = i >> 8;p[saltlen + 3] = i & 0xff;ret = hmac_sha1(password, pwdlen, p, saltlen + 4, u[1]); // 算出来的U1放到u[1]中if (!ret){free(p);return 0;}memcpy(u[2], u[1], HMAC_SHA1_OUTPUTSIZE); // F=U1 (u[2]用来存放F)for (j = 2; j <= c; j++){// F^=Uj -> F^=PRF(password, Uj-1)ret = hmac_sha1(password, pwdlen, u[1], HMAC_SHA1_OUTPUTSIZE, u[0]); // 根据Uj-1(位于u[1])算出Uj放到u[0]中if (!ret){free(p);return 0;}for (k = 0; k < HMAC_SHA1_OUTPUTSIZE; k++){u[2][k] ^= u[0][k];if (j != c)u[1][k] = u[0][k]; // 顺便把u[0]复制到u[1]中}}// u[2]为最终的结果F, 将其复制到结果缓冲区dk中curr = (dklen < HMAC_SHA1_OUTPUTSIZE) ? dklen : HMAC_SHA1_OUTPUTSIZE;memcpy(dk, u[2], curr);dk += curr;dklen -= curr;}free(p);return 1;
}/* Pseudorandom function (PRF-n) */
// http://etutorials.org/Networking/802.11+security.+wi-fi+protected+access+and+802.11i/Part+II+The+Design+of+Wi-Fi+Security/Chapter+10.+WPA+and+RSN+Key+Hierarchy/Computing+the+Temporal+Keys/
// 参数n为PRF-n中的n除以8
uint8_t PRF(const void *k, uint16_t klen, const char *a, const void *b, uint16_t blen, void *output, uint8_t n)
{uint8_t alen = strlen(a);uint8_t buf[HMAC_SHA1_OUTPUTSIZE];uint8_t curr, i, *p, *q;p = (uint8_t *)malloc(alen + blen + 2);if (p == NULL)return 0;q = (uint8_t *)output;strcpy((char *)p, a); // application-specific text (including '\0')memcpy(p + alen + 1, b, blen); // special datafor (i = 0; n; i++){p[alen + blen + 1] = i; // single byte counterhmac_sha1(k, klen, p, alen + blen + 2, buf);curr = (n < HMAC_SHA1_OUTPUTSIZE) ? n : HMAC_SHA1_OUTPUTSIZE;memcpy(q, buf, curr);q += curr;n -= curr;}free(p);return 1;
}

dns_test.c:

#include <lwip/tcp.h>
#include <lwip/dns.h>
#include <lwip/sys.h> // sys_now函数所在的头文件
#include <time.h>static err_t test_connected(void *arg, struct tcp_pcb *tpcb, err_t err)
{printf("Connected! err=%d\n", err);err = tcp_close(tpcb);if (err == ERR_OK)printf("Connection is successfully closed!\n");elseprintf("Connection cannot be closed now! err=%d\n", err);return ERR_OK;
}static void test_err(void *arg, err_t err)
{printf("Connection error! code=%d\n", err);
}void connect_test(const ip_addr_t *ipaddr)
{struct tcp_pcb *tpcb;printf("Connecting to %s...\n", ip4addr_ntoa(ipaddr));tpcb = tcp_new();tcp_connect(tpcb, ipaddr, 80, test_connected);tcp_err(tpcb, test_err);
}#if LWIP_DNS
static void dns_found(const char *name, const ip_addr_t *ipaddr, void *callback_arg)
{if (ipaddr != NULL){printf("DNS Found IP: %s\n", ip4addr_ntoa(ipaddr));connect_test(ipaddr);}elseprintf("DNS Not Found IP!\n");
}void dns_test(void)
{ip_addr_t dnsip;err_t err = dns_gethostbyname("zh.arslanbar.net", &dnsip, dns_found, NULL);if (err == ERR_OK){printf("In cache! IP: %s\n", ip4addr_ntoa(&dnsip));connect_test(&dnsip);}elseprintf("Not in cache! err=%d\n", err); // 缓存中没有时需要等待DNS解析完毕在dns_found回调函数中返回结果
}
#endif// 下面这个函数演示了如何使用C库将RTC时间值转换为日期时间格式, 不用的话可以删掉
void display_time(void)
{char str[30];struct tm *ptm;time_t sec;time(&sec);ptm = localtime(&sec);strftime(str, sizeof(str), "%Y-%m-%d %H:%M:%S", ptm);printf("%s\n", str);printf("sys_now()=%d\n", sys_now());
}

以下是lwip协议栈中添加或修改过的文件,修改过的地方都有中文注释。

lwip-2.0.3/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 // 不进行临界区保护// 配置DHCP
#define LWIP_DHCP 1
#define LWIP_NETIF_HOSTNAME 1// 配置DNS
#define LWIP_DNS 1
#define LWIP_RAND() ((u32_t)rand())// WPA/WPA2认证需要用到lwip中的arc4, md5和sha1函数
// 需要修改各文件的第42行, 注释掉条件编译宏
#define LWIP_INCLUDED_POLARSSL_ARC4 1
#define LWIP_INCLUDED_POLARSSL_MD5 1
#define LWIP_INCLUDED_POLARSSL_SHA1 1

lwip-2.0.3/include/arch/cc.h:

#define PACK_STRUCT_BEGIN __packed // struct前的__packed

lwip-2.0.3/netif/ethernetif.c:

/*** @file* Ethernet Interface Skeleton**//** Copyright (c) 2001-2004 Swedish Institute of Computer Science.* All rights reserved.** Redistribution and use in source and binary forms, with or without modification,* are permitted provided that the following conditions are met:** 1. Redistributions of source code must retain the above copyright notice,*    this list of conditions and the following disclaimer.* 2. Redistributions in binary form must reproduce the above copyright notice,*    this list of conditions and the following disclaimer in the documentation*    and/or other materials provided with the distribution.* 3. The name of the author may not be used to endorse or promote products*    derived from this software without specific prior written permission.** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT* SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY* OF SUCH DAMAGE.** This file is part of the lwIP TCP/IP stack.** Author: Adam Dunkels <adam@sics.se>**//** This file is a skeleton for developing Ethernet network interface* drivers for lwIP. Add code to the low_level functions and do a* search-and-replace for the word "ethernetif" to replace it with* something that better describes your network interface.*/#include "lwip/opt.h"//#if 0 /* don't build, this is only a skeleton, see previous comment */
#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"// 包含头文件
#include "../../common.h"
#include "../../WiFi.h"/* Define those to better describe your network interface. */
#define IFNAME0 'e'
#define IFNAME1 'n'/*** 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 */void  ethernetif_input(struct netif *netif); // 必须去掉static/*** 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 *///netif->hwaddr[0] = ;//...//netif->hwaddr[5] = ;// MAC地址已设置, 注释掉这段代码/* 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; // 添加此变量//initiate transfer();#if ETH_PAD_SIZEpbuf_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 atime. The size of the data in each pbuf is kept in the ->lenvariable. *///send data from(q->payload, q->len);//}buffer = WiFi_GetPacketBuffer(); // 获取发送缓冲区 (需要等待之前的帧发送完毕)pbuf_copy_partial(p, buffer, p->tot_len, 0); // 复制要发送的数据帧到发送缓冲区中
#ifdef WIFI_DISPLAY_PACKET_SIZEprintf("[Send] len=%hd\n", p->tot_len);
#endif
#ifdef WIFI_DISPLAY_PACKET_TXdump_data(buffer, p->tot_len);
#endif//signal that packet should be sent();WiFi_SendPacket(buffer, p->tot_len, NULL, NULL, WIFI_DEFAULT_TIMEOUT_DATAACK);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_SIZEpbuf_header(p, ETH_PAD_SIZE); /* reclaim the padding word */
#endifLINK_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;// 添加变量const uint8_t *data;/* Obtain the size of the packet and put it into the "len"variable. *///len = ;data = WiFi_GetReceivedPacket(&len); // 获取数据帧内容和大小
#ifdef WIFI_DISPLAY_PACKET_SIZEprintf("[Recv] len=%hd\n", len);
#endif
#ifdef WIFI_DISPLAY_PACKET_RXdump_data(data, len);
#endif#if ETH_PAD_SIZElen += 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);if (p != NULL) {#if ETH_PAD_SIZEpbuf_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);//}pbuf_take(p, data, len); // 将数据帧内容复制到pbuf中//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_SIZEpbuf_header(p, ETH_PAD_SIZE); /* reclaim the padding word */
#endifLINK_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 */void // 必须去掉static
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 = "STM32F103RE_SDIO"; // 路由器中显示的名称
#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_IPV6netif->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 */

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

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

    该程序是旧版本!最新版本为20180706版: https://blog.csdn.net/ZLK1214/article/details/80941657 本程序所用的单片机型号为:STM32F10 ...

  2. 【教程】Marvell 88W8686 WiFi模块驱动程序的编写(一)

    本文以STM32F1系列的单片机为例,详细讲解Marvell公司的88W8686 WiFi模块驱动程序的编写.编写程序时为了代码简短起见,直接用寄存器操作,不使用STM32库函数.IDE采用Keil ...

  3. STM32驱动Marvell 88W8686 WiFi模块代码说明(20180129版)

    一.概述 88W8686是Marvell公司2007年推出的一款SDIO Wi-Fi芯片,使用简单的SPI或SDIO协议就可以与单片机连接起来,操作方便,具有创建无密码或带有WEP密码的Ad-Hoc热 ...

  4. 【程序】Marvell 88W8801 WiFi模块连接路由器,并使用lwip2.0.3建立http服务器(20180729版)

    该程序是旧版本!最新版本为20220213版: https://blog.csdn.net/ZLK1214/article/details/122915474 本程序所用的单片机型号为:STM32F1 ...

  5. 【程序】Marvell 88W8801 WiFi模块连接路由器,并使用lwip2.0.3建立http服务器(20180807版)

    该程序是旧版本!最新版本为20220213版: https://blog.csdn.net/ZLK1214/article/details/122915474 本程序所用的单片机型号为:STM32F1 ...

  6. 【程序】Marvell 88W8801 WiFi模块连接路由器,并使用lwip2.0.3建立http服务器(20190314版)

    该程序是旧版本!最新版本为20220213版: https://blog.csdn.net/ZLK1214/article/details/122915474 本程序所用的单片机型号为:STM32F1 ...

  7. 【程序】Marvell 88W8801 WiFi模块创建或连接热点,并使用lwip2.1.2建立http服务器(20200208版)

    该程序是旧版本!最新版本为20220213版: https://blog.csdn.net/ZLK1214/article/details/122915474 本程序所用的单片机型号为:STM32F1 ...

  8. 【方法】Marvell 88W8801 WiFi模块创建能上网的热点

    源程序:Marvell 88W8801 WiFi模块创建或连接热点,并使用lwip2.1.2建立http服务器(20200208版) 本文讲解如何使88W8801创建出来的热点有网.具体的连接方式是: ...

  9. 【程序】Marvell 88W8782/88W8801 WiFi模块创建或连接热点,并使用lwip2.1.3建立http服务器(20220213版)

    本程序所用的单片机型号为:STM32F103RE.STM32F407ZG或STM32H743ZI. 复位引脚(PDN)应连接到STM32F1/F4的PA15引脚或STM32H7的PE6引脚.不连接PD ...

最新文章

  1. 把jquery的this写入选择器里(伪写入)哈哈~
  2. 增强for循环_增强for循环实际用法
  3. 【深度学习】逆卷积(Deconvolution)概述
  4. python queue 查询是否在队列中_python队列Queue的详解
  5. OpenCV-图像几何变换:旋转,缩放,斜切 .
  6. 【人脸识别】初识人脸识别
  7. 【grafana】API 遇到的问题
  8. 创建一个catkin工作空间
  9. 电力负荷事件划分(有代码)
  10. 【BZOJ】4001: [TJOI2015]概率论
  11. 漫画: 什么是外部排序?
  12. (转载)关于My97 datepicker与Angular ng-model绑定问题解决。
  13. Codeforces 914D - Bash and a Tough Math Puzzle 线段树,区间GCD
  14. c语言只能最大值不能最小值,用c语言编写输入10个无序的整数,去掉一个最大值和最小值,然后求其平均值...
  15. evernote国际版不可用
  16. Linux学习-文件操作和属性
  17. vs警告 当前源代码跟内置的版本不一致解决办法
  18. Python爬取天气数据及可视化分析(附源码)
  19. ubuntu设置开机启动程序
  20. Google是如何提供“无缝”的街景全景图的?

热门文章

  1. 微信小程序项目图片如何保存到本地的方法
  2. Mybatis时区问题
  3. 人工智能--自然演绎推理
  4. Rss Feed是什么?
  5. 基于改进正弦余弦算法的函数寻优算法
  6. for(foo(‘a‘) ; foo(‘b‘) (i<2);foo(‘c‘))的执行结果
  7. 【Arduino 连接 SD 卡模块实现数据读写】
  8. 工控安全的一些个人建议
  9. 2021年中国巴豆酸市场趋势报告、技术动态创新及2027年市场预测
  10. MySQL顺序读写和随机读写磁盘_随机读写与顺序读写的深入理解