【转】Arduino官方WIFI模块----开发记录
2012年12月24日 08:59 发布者:coocox
WIFI开发计划CooCox Cedar@Wuhan
说明:
本来计划开发SparkFun的WiFly模块,后来发现Arduino官网出了WIFI模块,就优先开发Arduino WIFI Shield。
官方的WIFI Shield资料如下:
*:原理图
*:PCB文件
*:WIFI库
*:固件代码(用于实现IP栈)
以上所有资料均可从Arduino官网下载,需要说明的是WIFI Shield的核心芯片是HDG104,进入HD官网下载资料时,
需要注册账户,注册后,不知为何,始终无法登陆,所有没有下载到任何手册。如果您有HDG104的手册,可以分享
一下,非常感谢。
因为换WIFI模块,需要重新熟悉代码和资料,现制定一份初步计划
计划如下:
阶段1: 学习Socket编程
难点:开发环境的搭建,熟悉网络概念
阶段2:用Socket实现HTTP例子(客户端和服务器)
难点:HTTP协议分析和HTML代码编写
阶段3:分析官方WIFI库代码
难点:理解代码的分层模型,从代码中分析WIFI模块的协议
阶段4:移植到Cookie
难点:Cox接口入门
阶段5:总结
总结心得体会,分享资料,链接
PS:如果有任何疑问,请跟帖或email我,3Q
Email: renjun@coocox.com
Socket软件包使用说明
综述
本Socket软件包作为Arduino WIFI shield入门指导中的一部分,用于说明如何在windows下用TCP/IP Socket编程,如何建立链接,绑定端口,收发数据,深入理解这部分,更利于后期学习HTTP客户端和服务器代码。这里用Socket写了一个简单的局域网聊天工具,在不同的机器上分别运行客户端和服务器,然后就像QQ一样聊天。
软硬件环境
操作系统: Win7
开发环境: VS2008
开发语言:C
注:所有代码在上述环境中测试通过,理论上在其它环境(如VC++6.0,windows XP)可以编译通过,但未测试
目录结构
使用时只需要重点关注红色字体标注的文件(夹)
Socket
│
├─client
│ │ client.c ------> Socket 客户端源码
│ │
│ ├─client
│ │ │ client.ncb
│ │ │ client.sln
│ │ │ client.vcproj ------> Client VC工程文件
│ │ │ client.vcproj.RENJUN-PC.RENJUN.user
│ │ │
│ │ └─Debug
│ │ BuildLog.htm
│ │ client.exe ------> VC工程生成的客户端可执行文件,在本
│ │ 机上可以直接双击运行。如需在其它电脑运行,
│ │ 请参考“发布”章节中的步骤和注意事项
│ │ client.exe.embed.manifest ------> exe文件的依赖关系,详细信息,参考“发布”章节
│ │ client.exe.embed.manifest.res
│ │ client.exe.intermediate.manifest
│ │ client.ilk
│ │ client.obj
│ │ client.pdb
│ │ mt.dep
│ │ vc90.idb
│ │ vc90.pdb
│ │
│ └─dist ------> 需要在其它没装VS2008的电脑上运行exe文件时,
│ │ 直接将dist文件夹复制过去就ok,
│ │ 详细信息,参见“发布”章节
│ │ client.exe
│ │ client.exe.embed.manifest
│ │
│ └─Microsoft.VC90.DebugCRT
│ Microsoft.VC90.DebugCRT.manifest
│ msvcm90d.dll
│ msvcp90d.dll
│ msvcr90d.dll
│
└─server
│ server.c ------> Socket 服务器源码
│
├─dist ------> 需要在其它没装VS2008的电脑上运行exe文件时,
│ │ 直接将dist文件夹复制过去就ok
│ │ 详细信息,参见“发布”章节
│ │ server.exe
│ │ server.exe.embed.manifest
│ │
│ └─Microsoft.VC90.DebugCRT
│ Microsoft.VC90.DebugCRT.manifest
│ msvcm90d.dll
│ msvcp90d.dll
│ msvcr90d.dll
│
└─server
│ server.ncb
│ server.sln
│ server.vcproj ------> Server VC工程文件
│ server.vcproj.RENJUN-PC.RENJUN.user
│
└─Debug
BuildLog.htm
mt.dep
server.exe ------> VC工程生成的客户端可执行文件,在本机上
可以直接双击运行。如需在其它电脑运行,
请参考“发布”章节中的步骤和注意事项
server.exe.embed.manifest ------> exe文件的依赖关系,详细信息,参考“发布”章节
server.exe.embed.manifest.res
server.exe.intermediate.manifest
server.ilk
server.obj
server.pdb
vc90.idb
vc90.pdb
使用方法
A:体验
1:双击打开server 目录下exe文件夹下exe文件,启动聊天服务器
2:双击打开client 目录下exe文件夹下exe文件,启动聊天客户端
3:启动客户端后,输入服务器主机IP地址,便可开始聊天
4:根据提示进行发送和接收信息,在发送状态时,如果不想发送数据,可以按回车,取消发送
B:开发
1:确保您已经正确安装VS2008
2:双击软件包中的server.vcproj和client.vcproj,打开对应的工程文件
3:编译和调试server和client代码
发布
如果想要将生成的exe文件在其他电脑上运行,只复制exe文件过去,打开时,会出现下面的错误
http://www.coocox.org/userfiles/dd.jpg
这是因为缺少对应的dll,所以我们需要同时复制dll,exe和描述文件过去,别人才能正常运行
这里以打包client为例,来说明如何正确发布软件
1:新建一个文件夹,文件夹名字随便起,这里我们将文件夹命名为dist,该文件夹用于存放exe文件和对应的依赖文件
2:打开软件包中Debug文件夹,找到 client .exe和 client .exe.embed.manifest文件
如没有这些文件,进入VS2008,rebuild一下
3:打开 client.exe.embed.manifest文件,查看exe依赖的dll
在我电脑上,client.exe.embed.manifest内容如下:
文件组织方式为标准xml结构,在此重点关注
[*]该exe为win32可执行文件
[*]依赖Microsoft.VC90.DebugCRT目录下的dll文件
[*]dll版本号为:9.0.21022.8
知道缺少了哪些dll,只需要将这些dll复制到exe文件夹下即可
4:进入VS2008安装目录,找到Microsoft.VC90.DebugCRT这个文件夹,在我的电脑上路径如下:
D:\Program Files\Microsoft Visual Studio 9.0\VC\redist\Debug_NonRedist\x86
不同电脑,路径可能不一样,但大致结构相同
5:将Microsoft.VC90.DebugCRT文件夹整体拷贝到第1步建立的dist文件夹中
6:现在所有的必须的文件都已经复制完毕,dist文件夹下目录组织结构如下
dist
|-->client.exe
|-->client.exe.embed.manifest
|---Microsoft.VC90.DebugCRT
|---> Microsoft.VC90.DebugCRT.manifest
|---> msvcm90d.dll
|---> msvcp90d.dll
|---> msvcr90d.dll
7:检查dist目录下client.exe.embed.manifest和Microsoft.VC90.DebugCRT.manifest版本号(9.0.21022.8)是否一致
8:现在您可以将dist文件夹打包,发送给朋友了 ^_^
软件包下载
所有代码可以从github网站下载:
https://github.com/cedar-renjun/Socket_server_client_chat_Example
Socket Http WebClient
说明:直接用Socket连接百度web服务器,然后发送HTTP Get请求来获取百度首页,VS2008工程在上个帖子中有链接,这次直接发代码,使用时,将代码复制到.c文件中,rebuild生成exe文件,然后点击运行,就可以看到获取的html网页了
#include
#include
#include
// Need to link with Ws2_32.lib
#pragma comment (lib, "Ws2_32.lib")
#define DEFAULT_BUFLEN 512
#define DEFAULT_PORT 80
WSADATA wsaData;
SOCKET ConnectSocket = INVALID_SOCKET;
char *HttpRequst ="GET / HTTP/1.1\r\nConnection: close\r\n\r\n";
char RecvBuf;
char *IP = "119.75.217.56";
struct sockaddr_in ServerCfg;
char WelcomeInfo[] =
{
"\t===============WIFI Shield Dirver================\r\n"
"\tName: HTTP WebClient Example\r\n"
"\tHOST : http://www.baidu.com\r\n"
"\tIP : 119.75.217.56\r\n"
"\tPORT : 80\r\n"
"\t=============== CooCox Team =====================\r\n"
};
int main(void)
{
int tmp = 0;
int cnt = 0;
int iResult = 0;
printf(WelcomeInfo);
// Initialize Winsock
iResult = WSAStartup(MAKEWORD(2,2), &wsaData);
if (iResult != 0)
{
printf("WSAStartup failed with error: %d\r\n", iResult);
return 1;
}
printf("Initial WinSock OK\r\r\n");
// Create a SOCKET for connecting to server
ConnectSocket = socket(AF_INET, SOCK_STREAM, 0);
if (ConnectSocket == INVALID_SOCKET)
{
printf("socket failed with error: %ld\r\n", WSAGetLastError());
WSACleanup();
return 1;
}
printf("Create Socket OK\r\n");
//Connect to Server
ServerCfg.sin_family = AF_INET;
ServerCfg.sin_port = htons(DEFAULT_PORT);
ServerCfg.sin_addr.s_addr = inet_addr(IP);
printf("Try to connect server\r\n");
iResult = connect(ConnectSocket, (struct sockaddr *)&ServerCfg, sizeof(struct sockaddr));
if (iResult == SOCKET_ERROR)
{
printf("Unable to connect to server!\n");
WSACleanup();
return 1;
}
printf("Connect to server!\r\n");
// Send Message to Server
printf("Send HTTP Requst\r\n%s", HttpRequst);
iResult = send( ConnectSocket, HttpRequst, strlen(HttpRequst), 0);
if (iResult == SOCKET_ERROR)
{
printf("send failed with error: %d\r\n", WSAGetLastError());
}
printf("-----------Receive Respon Message---------\r\n");
// Receive Full response message
while(1)
{
// Important,MUST NOT comment this
memset(RecvBuf, '\0', DEFAULT_BUFLEN);
iResult = recv( ConnectSocket, RecvBuf, DEFAULT_BUFLEN, 0);
if (iResult < 0)
{
printf("receive failed with error: %d\r\n", WSAGetLastError());
}
else if(iResult > 0)
{
puts(RecvBuf);
}
else
{
printf("\r\n----------------------------------------------");
printf("\r\nReceive OK\r\n");
break;
}
}
// clean
printf("Now Release resource\r\n");
closesocket(ConnectSocket);
WSACleanup();
printf("Closed! Example is over\r\n");
printf("Press Enter to exit\r\n");
getch();
//while(1);
}
Socket Http WebServer
#include
#include
#include
#include
#include
// Need to link with Ws2_32.lib
#pragma comment(lib, "Ws2_32.lib")
#define DEFAULT_BUFLEN 2048
#define DEFAULT_PORT 27013
char RecvBuf;
char SendBuf;
struct sockaddr_in ServerCfg;
SOCKET ListenSocket = INVALID_SOCKET;
SOCKET ClientSocket = INVALID_SOCKET;
char WelcomeInfo[] =
{
"\t===============WIFI Shield Dirver================\r\n"
"\tName: TCP Socket Http Webserver Example\r\n"
"\tPORT : 8080\r\n"
"\t=============== CooCox Team =====================\r\n"
};
char RespondInfo[] =
{
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/html\r\n"
"Connnection: close\r\n"
"\r\n"
"\r\n"
"\r\n"
"\r\n"
"
"
"================= WIFI Shield Dirver===============
"
"Name: TCP Socket Http Webserver Example
"
"PORT : 27013
"
"==================="
"CooCox Team=====================
"
"
""================= WIFI Shield Dirver===============
"
"Name: TCP Socket Http Webserver Example
"
"PORT : 27013
"
"==================="
"CooCox Team=====================
"
"
"\r\n"
"\r\n"
"\r\n"
};
int main(void)
{
WSADATA wsaData;
int iResult = 0;
int iSendResult = 0;
printf(WelcomeInfo);
// Initialize Winsock
iResult = WSAStartup(MAKEWORD(2,2), &wsaData);
if (iResult != 0)
{
printf("WSAStartup failed with error: %d\n", iResult);
return 1;
}
printf("Initial WinSock OK\r\n");
// Create a SOCKET
ListenSocket = socket(AF_INET, SOCK_STREAM, 0);
if (ListenSocket == INVALID_SOCKET)
{
printf("socket failed with error: %d\n", WSAGetLastError());
WSACleanup();
return 1;
}
printf("Create Socket OK\r\n");
// Setup the TCP listening socket
ServerCfg.sin_family = AF_INET;
ServerCfg.sin_port = htons(DEFAULT_PORT);
ServerCfg.sin_addr.s_addr = htonl(INADDR_ANY);
iResult = bind(ListenSocket,(struct sockaddr *)&ServerCfg,
sizeof(struct sockaddr));
if(iResult == SOCKET_ERROR)
{
printf("bind failed with error: %d\n", WSAGetLastError());
closesocket(ListenSocket);
WSACleanup();
return 1;
}
printf("Bind to PORT OK\r\n");
// Begin listen PORT
iResult = listen(ListenSocket, 5);
if (iResult == SOCKET_ERROR)
{
printf("listen failed with error: %d\n", WSAGetLastError());
closesocket(ListenSocket);
WSACleanup();
return 1;
}
printf("Start Listen PORT\r\n");
printf("Waitting Client ...\r\n");
// Accept a client socket
ClientSocket = accept(ListenSocket, NULL, NULL);
if (ClientSocket == INVALID_SOCKET)
{
printf("accept failed with error: %d\n", WSAGetLastError());
closesocket(ListenSocket);
WSACleanup();
return 1;
}
printf("Client Connect Now\r\n");
// No longer need server socket
closesocket(ListenSocket);
// Get Request
// Receive Full response message
printf("Receive Web bowser request\r\n");
memset(RecvBuf, '\0', DEFAULT_BUFLEN);
iResult = recv(ClientSocket, RecvBuf, DEFAULT_BUFLEN, 0);
if (iResult < 0)
{
printf("receive failed with error: %d\r\n", WSAGetLastError());
}
else if(iResult > 0)
{
puts(RecvBuf);
}
else
{
printf("Connect Closed!\r\n");
}
// Send html to client
//printf("Send HTTP Respond:\r\n%s\r\n",RespondInfo);
iResult = send(ClientSocket, RespondInfo, strlen(RespondInfo), 0);
if (iResult == SOCKET_ERROR)
{
printf("send failed with error: %d\r\n", WSAGetLastError());
}
printf("Send OK\r\n");
//Wait 1S to allow boswer to receive information
Sleep(1000);
// shutdown the connection since no more data will be sent
closesocket(ClientSocket);
WSACleanup();
printf("WebServer closed! Press Enter to exit\r\n");
getch();
}
阶段总结
WIFI模块本身做不了什么,只有和TCP/IP协议栈,HTTP协议结合起来,才能发挥更大的作用。从分层模型上来看,WIFI本身仅仅充当物理层和数据链路层的作用,因为各层的接口特性,上层只需要关注下层提供的接口,无需关注内部实现,所以在保证接口不变的前提下,可以直接替换内部模块。一般开发时,先在PC上调试好应用层代码,调试ok后,直接将底层代码替换成WIFI驱动,这样就可快速完成模块。所以下面的帖子会先介绍PC上常用的软件和对应的设置方法,然后用理论加实践的方式介绍TCP/IP socket编程入门,HTTP协议分析和模拟客户端,服务器。
网友评论
jlddzeec 2018年10月23日
学习学习!
学习学习!
精简版inet_addr
说明:
今天在移植代码的时候,需要一个客户端WiFiClientConnect函数,该函数的一个参数为服务器地址,第二个为端口。
其中服务器的地址可以是主机地址(如: www.baidu.com ),也可以是IP地址(如:192.168.2.71)。
官方的WIFI驱动是C++重载函数写的,可以根据不同的输入格式来调用对应的函数,C中没有重载,只有手工解析,解决方法有3个:
1:分别采用2个函数,比如提供WiFiClientConnectVia 和Host WiFiClientConnectViaIP两个函数,供用户调用
2:像Arduino WiFi驱动接口那样,使用一个函数,但增加一个入口参数,用于指明服务器地址类型
3:符合Arduino 官方WIFI的调用习惯,用一个WiFiClientConnect函数,用户只需输入HOST或IP即可,解析的问题,交给函数内部来处理如 WiFiConnect( "www.baidu.com", 80);WiFiConnect( "192.168.2.71", 80)
最终为了方便用户,采用了最后一个方案,这样用户可以更加方便的使用WIFI
我们需要在内部识别出是否是有效的IP地址,如果是的话,则转换为网络地址,这样就需要一个识别和转换函数IPToNetAddr。
IPToNetAddr将传入的IP地址解析成网络格式,比如将“192.168.2.71”分割成4个10进制的数字,存到数组里面
在Q群问了网友,有人说inet_addr符合要求,找到对应源码后,发现它考虑的情况太多了,支持各种进制的数字,而我们这里,只需要支持10进制就ok,网上搜了一下,貌似没人写过这函数,所以还是自己动手写一个吧
要求:
1:能识别出输入IP字符串是否有效,有效则返回0,无效则返回-1
2:输入错误的IP地址,函数不损坏存放结果的数组
错误的IP例子:
1:子项过大:每个子项应小于255。下面的IP地址第一个子项为1921,大于255
1921.108.2.71
2:子项过多:应有4个子项,由3个.号分隔,下面的IP地址有5个子项
192.168.2.1.2
测试代码如下:
int main(void)
{
int retv = 0;
//目标IP地址字符串
char IP_OK[] = "192.168.002.071";
char IP_ERROR_1[] = "1921.108.2.71";
char IP_ERROR_2[] = "192.168.2.1.2";
char IP_ERROR_3[] = "293.168.2.1.2";
char IP_ERROR_4[] = "193.168.2.1.300";
//存储结果数组
uint8_t result = {0, 0, 0, 0};
retv = IPToNetAddr(IP_OK, result);
if(-1 == retv)
{
printf("Test Failure\r\n");
while(1);
}
retv = IPToNetAddr(IP_ERROR_1, result);
if(0 == retv)
{
printf("Test Failure\r\n");
while(1);
}
retv = IPToNetAddr(IP_ERROR_2, result);
if(0 == retv)
{
printf("Test Failure\r\n");
while(1);
}
retv = IPToNetAddr(IP_ERROR_3, result);
if(0 == retv)
{
printf("Test Failure\r\n");
while(1);
}
retv = IPToNetAddr(IP_ERROR_3, result);
if(0 == retv)
{
printf("Test Failure\r\n");
while(1);
}
printf("Test OK, Press any key to exit\r\n");
getchar();
return (0);
}
完整代码如下:
CODE:
#include "stdafx.h"
#include
#include
//#include
#include
typedef unsigned char uint8_t;
typedef signed char int8_t;
typedef unsigned int uint32_t;
typedef signed int int32_t;
int IPToNetAddr(char * IPStr, uint8_t * NetAddr);
int main(void)
{
int retv = 0;
//目标IP地址字符串
char IP_OK[] = "192.168.002.071";
char IP_ERROR_1[] = "1921.108.2.71";
char IP_ERROR_2[] = "192.168.2.1.2";
char IP_ERROR_3[] = "293.168.2.1.2";
char IP_ERROR_4[] = "193.168.2.1.300";
//存储结果数组
uint8_t result = {0, 0, 0, 0};
retv = IPToNetAddr(IP_OK, result);
if(-1 == retv)
{
printf("Test Failure\r\n");
while(1);
}
retv = IPToNetAddr(IP_ERROR_1, result);
if(0 == retv)
{
printf("Test Failure\r\n");
while(1);
}
retv = IPToNetAddr(IP_ERROR_2, result);
if(0 == retv)
{
printf("Test Failure\r\n");
while(1);
}
retv = IPToNetAddr(IP_ERROR_3, result);
if(0 == retv)
{
printf("Test Failure\r\n");
while(1);
}
retv = IPToNetAddr(IP_ERROR_4, result);
if(0 == retv)
{
printf("Test Failure\r\n");
while(1);
}
printf("Test OK, Press any key to exit\r\n");
getchar();
return (0);
}
int IPToNetAddr(char * IPStr, uint8_t * NetAddr)
{
uint32_t _IP = {0, 0, 0, 0};
uint8_t cnt = 0;
uint8_t idx = 0;
char _str = NULL;
//检查IP和接收缓存区是否有效
if(IPStr == NULL || NetAddr == NULL)
{
return (-1);
}
while((_str = *IPStr++) != NULL)
{
if(_str == '.')
{
//清空计数器,该计数器最大值为3
cnt = 0;
if(_IP > 255)
{
return (-1);
}
idx = idx + 1;
}
else if(_str >= '0' && _str <= '9') //检查是否为有效字符 '0' --> '9'
{
if(cnt++ < 3 && idx <= 3)
{
_IP = (10 * _IP) + (_str - '0');
}
else
{
return (-1);
}
}
else
{
return (-1);
}
}
if(_IP > 255)
{
return (-1);
}
//复制数据到结果缓存区
NetAddr = (uint8_t)_IP;
NetAddr = (uint8_t)_IP;
NetAddr = (uint8_t)_IP;
NetAddr = (uint8_t)_IP;
return (0);
}