ALIENTEK 战舰ENC28J60 LWIP和UIP补充例程(LWIP WEB有惊喜)
前面的话:自从接触网络模块,到现在有一阵子时间了,未来必定是网络的世界。学一些网络方面的知识是有必要的。我们ALINTEK 推出的ENC28J60网络模块块作为入门还是不错的。详细见此贴:
http://www.openedv.com/posts/list/9355.htm。时间对于一个开发人员是很宝贵的,如何快速应用是我们做技术的,都想要的。废话不多说了。因为主要集中在怎么应用所以有些细节可能不是正确的,
这个需要大家去质疑,去验证。
一、LWIP的应用
1.什么是LWIP?
lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。
2.哪里可以下载源码?
在这里可以下载到最新的应用:http://savannah.nongnu.org/projects/lwip/
3.更多详细介绍?
在这里有详细的:http://lwip.wikia.com/wiki/LwIP_Wiki
4.如何移植?
LWIP 有三种应用模式 RAW API,Netconn API,Socket API.这里我们主要简单讲解如何移植RAW API。移植LWIP的过程也是一个了解LWIP大概结构的过程。
当你把所有的错误,和警告干掉的后就差不多了,呵呵。
①首先到官网上下载好源码和应用例程。
<ignore_js_op>
lwip-1.4.1中src文件夹下的源码文件结构:
<ignore_js_op> ②按以下这个目录结构将源码加入到工程
<ignore_js_op>
前面三个就不用解释了,看看上面的源码文档结构就知道了,LWIP_ARCH文件夹是什么东东呢?它是一个操作系统与处理器平台配置有关的代码,你要问我这个东西哪里来的,
嘿嘿,从ST的官方固件库中偷来滴。里面包含了大小端的配置,类型定义,和操作系统有关的部分,不过这里没有用到操作系统,所以这个sys_arch.c文件需要改装下。
LWIP_APP顾名思义就是LWIP的应用程序了,这里面编写了,TCP服务器,TCP客户端,UDP服务器,UDP客户端,Webserver(应用程序和服务器的接口技术(CGI),动态网页技术(SSI),和ajax技术,做异步提交,获取数据)。的方法和例程。接下来请看我如何修改移植。
③底层驱动的移植。
在LWIP-NETIF文件加下,我们需要对ethernetif.c文件修改。该文件是一个底层网络接口框架。LWIP的作者已经为我们搭好了框架,其中有些代码是伪代码,我们只需要去实现就好了。
A.修改底层硬件初始化函数low_level_init(struct netif *netif)函数,在函数中设置好,网络的MAC地址,最大传输单元,然后再初始化ENC28J60,如果初始化失败则返回错误给上层调用的函数。
<ignore_js_op>
B.修改底层发送包的函数low_level_output(struct netif *netif, struct pbuf *p),在这个函数中将LWIP数据包中的缓冲区pbuf 复制到待发送的缓冲区lwip_buf中来,
然后利用ENC28J60的发包函数发出去。
<ignore_js_op>
C.修改底层接收包的函数low_level_input(struct netif *netif),这个过程就是把接收缓冲区lwip_buf中的数据复制到LWIP的pbuf中,如果收到了包, 我们就可以在lwip_buf中读取数据了。注:lwip_buf作为接收和发送数据的缓冲。
<ignore_js_op>
移植完这三个函数就差不多啦,简单吧,嘿嘿。
④在主函数main.c初始化LWIP。
lwip_init()是用来LWIP初始化的,但是其中的netif_init()初始化的是回环网络接口,我们想把数据发出去必须初始化ENC28J60。所以我们先调用lwip_init()来初始化LWIP的各个模块。
再利用netif_add函数添加网络接口就可以。这个函数会调用底层的 low_level_init函数,如果返回值为空,则说明ENC28J60初始化失败了。然后再注册,建立一下这个网络接口就OK了。
<ignore_js_op> 注意到上面的有一个函数 init_lwip_timer(); 这个是什么呢,这个是用来初始化LWIP的定时器,用来计时用的,相当于LWIP的心脏。
⑤在主函数main.c中编写轮训协议栈函数
LWIP需要周期性的处理一些函数,这些函数是是根据你使能的协议和功能模块来调用的。比如你要用到TCP协议,你就必须周期性的调用tcp_tmr()。
注意到有个timer_expired函数,这个函数就在sys_arch.c文件中,这个文件本来与操作系统有关,现在改装了。用来初始化LWIP的时钟,提供超时检测的功能,相当于定时器了。
<ignore_js_op>
⑥配置LWIP
LWIP有一个标准配置文件在opt.h中,这个头文件又包含了lwipopts.h,通常我们配置LWIP是通过这个文件来配置的。
这个头文件可以覆盖opt.h中任何你需要的配置。这样做的好处,是源码更加健壮了,而且能够防止我们错误的配置,而改不回去了。
有几个配置是很重要的,NO_SYS 1-----无操作系统 MEM_ALIGNMENT 4 --- 分配内存是四字节对齐,这个很重要,曾经莫名其妙的出现hardfault
TCP_MSS 1460 --TCP最大段大小,这个是极限值了,我们可以传更多的数据。
<ignore_js_op>
⑦在main函数中加入LWIP_Polling(),基本上就移植完毕了。这时可以使用ping 命令。
比如我设置的IP地址为19.168.1.10则,通过cmd命令进入dos环境。通过 ping 192.168.1.10 -t 就可以不断查询网络是否联通了。
5.如何应用? RAW API中文译为原始API可以说比较接近底层了,玩过socket编程的人都知道,socket编程用起来,是比较简单了,不像RAW API的模式,用起来比较麻烦,需要应用者对TCP,UDP这些协议,有一个 稍微深入的了解。这里我推荐一款抓包软件Wireshark。这个软件可以帮助你分析这些协议是怎么工作的。不过在用这个软件的,分析协议的时候最好不要连接到外网,会干扰的哦。 ①TCP服务器和客户端 A.TCP通信简介 TCP是面向连接的,在进行通信前需要建立连接。下面以TCP服务器模式为例简单介绍下这个过程。 建立连接:(截图看不清见附件)建立连接.jpg发送数据: 发送数据.jpg 关闭连接: 服务器关闭连接.jpg B.TCP服务器模式 RAW API大部分都是基于回调函数的API,我们需要按照一定的规范去实现这个回调函数。作为TCP服务器,必须要一个本地IP和端口。 处于服务器模式,是不需要设置远程主机IP和端口,因为远程主机在连接到服务器的过程当中,服务器会把它的IP地址等信息记录下来。就可以双向传输了。 tcp_bind(tcp_server_pcb,IP_ADDR_ANY,TCP_SERVER_PORT);//绑定本地所有IP地址和端口号 tcp_listen(tcp_server_pcb); //开始监听端口 tcp_accept(tcp_server_pcb,tcp_server_accept); //指定监听状态的连接联通之后将要调用的回调函数 tcp_recv(newpcb, tcp_server_recv); //指定连接接收到新的数据之后将要调用的回调函数tcp_err(newpcb, tcp_server_error); //指定连接出错将要调用的函数tcp_poll(newpcb, tcp_server_poll, 0); //指定轮询时将要调用的回调函数对于不熟悉这种习惯的,开始可能有点迷糊糊,不过看多了就好了。上面四个函数,都是用来指定回调函数的。 他们指定的回调函数在指定事件(比如建立了连接,接收到了数据,连接空闲等等)发生的时候将会被调用。 C.TCP客户端模式 对于TCP客户端模式,我们需要指定连接的远程服务器的IP地址和端口号,其他的函数就不多说了,详细见代码 ip_addr_t ipaddr;IP4_ADDR(&ipaddr, 192, 168, 1, 101); //设置本地ip地址tcp_connect(tcp_client_pcb,&ipaddr,TCP_CLIENT_PORT,tcp_client_connect); //连接到远程服务器 ②UDP的服务器和客户端 相对于TCP来说UDP则就简单了许多,他不需要像TCP一样需要建立连接通道才可以进行通信。没有重发机制。所以它是不可靠的通行。但是速度快,是他的一大亮点。 A:UDP服务器模式 udp_bind(udp_server_pcb,IP_ADDR_ANY,UDP_SERVER_PORT); //帮顶本地IP地址和端口udp_recv(udp_server_pcb,udp_server_rev,NULL); //指定收到数据包时的回调函数不过需要注意在接受数据的udp_server_rev回调函数中需要将客户端的IP地址和端口记下来,方便UDP服务器发送数据给客户端。 udp_server_pcb->remote_ip = *addr; //记录远程主机的IPudp_server_pcb->remote_port = port;//记录远程主机的端口号 B:UDP客户端模式 udp_bind(udp_client_pcb,IP_ADDR_ANY,UDP_CLIENT_PORT); //这里可以绑定任意的端口,不一定是服务器的端口号,作为客户端主要关心的是要连接的端口和IPudp_connect(udp_client_pcb,&ipaddr,UDP_CLIENT_PORT); //设置连接到远程主机 ③web服务器 现在那种browse+server的模式很流行,在PC机中。这种模式,客户端只需要一个浏览器就OK了,不需要下载专门的客户端,现在的网页游戏这么流行就证明了这一点。你想想在家里, 我的手机通过WIFI连接到路由器,你的嵌入式服务器也连接到路由器,只要有手机,不需要携带笨重的电脑,装麻烦软件,就可以控制家里的电器,多爽啊,这就是物联网的应用。 如何减少嵌入式服务器的开发难度,是很多人研究的重点。感觉LWIP的CGI和SSI接口用起来还是比较爽的,结合一些流行的web前端技术,我们可以开发比较漂亮的应用。 A.什么是CGI? CGI是Common Gateway Interface通用网关接口的简称。百度一下解释多多。其实,他的作用是用来让服务器和应用程序打交道的。当我们从浏览器获取参数和对应的值后,然后与应用程序 进行交互的这个函数,就是CGI函数了。对应的URLl路径就写一个对应的CGI函数。这样不同的请求,就有不同的处理,这个函数将会返回,你想发送给浏览器的文件的文件名路径。 B.CGI函数介绍 typedef char *(*tCGIHandler)(int iIndex, int iNumParams, char *pcParam[], char *pcValue[]);参数说明: iIndex :表示在ppcURLs数组中的索引号码, ppcURLs 是个什么玩意呢? ppcURLs 的类型是这个东东。 typedef struct{ const char *pcCGIName; //浏览器请求的URL tCGIHandler pfnCGIHandler; //定义的CGI函数指针} tCGI;那么这个东东有什么作用呢?,他是用来注册URL路径和对应的函数,void http_set_cgi_handlers(const tCGI *pCGIs, int iNumHandlers),这个函数会对它进行设置。当浏览器发出一个请求URL时,这个URL路径对应的CGI函数就会执行。 lwip已经帮我们做好了,我只修需要注册URL路径和对应的CGI函数就可以了,爽吧。 iNumParams :表示URL中你请求参数和其参数对应值的个数,比如http://192.168.1.16/login.cgi?name=onetree&password=12345,参数是name和password,值是onetree和12345,个数就是2.pcParam :参数的数组,包含所有的参数 pcValue: 对应参数的值的数组 C.CGI应用举例 比如。浏览器,采用get的方式,发了一个请求。http://192.168.1.16/login.cgi?/name=onetree&password=12345 我们的应用程序怎么接收我们的用户名name和密码password呢? 1.注册URL路径和对应的函数 static const tCGI ppcURLs[] ={ { "/login.cgi", Login_CGIHandler }, }; 2.设置请求的URL的路径和CGI函数 #define NUM_CONFIG_CGI_URIS (sizeof(ppcURLs ) / sizeof(tCGI)) http_set_cgi_handlers(ppcURLs , NUM_CONFIG_CGI_URIS); 3.编写CGI函数 static char * Login_CGIHandler ( int iIndex, int iNumParams, char *pcParam[], char *pcValue[] ){ int index; index = FindCGIParameter ( "name", pcParam, iNumParams ); if(index!=-1){ name对应的参数值= pcValue[index]; } index = FindCGIParameter ( " password", pcParam, iNumParams ); if(index!=-1){ password对应的参数值 =pcValue[index]; } return "/index.html"; //这里返回你要发给浏览器的文件名路径 } 注意到有个 FindCGIParameter 的函数,这个函数是用来查找你想要的参数。 static int FindCGIParameter(const char *pcToFind, char *pcParam[], int iNumParams){ int iLoop; for(iLoop = 0; iLoop < iNumParams; iLoop++) { if(strcmp(pcToFind, pcParam[iLoop]) == 0) { return(iLoop); } } return(-1);}到此为止,我们就完成了,浏览器和应用程序的交互了。 D.什么是SSI? SSI是Server Side Include服务器嵌入端的简称。是一种类似于ASP的基于服务器的网页制作技术。 工作原理:LWIP对于.shtml,.ssi,.shtm后缀的文件,会检测文件中<!--#name-->格式的TAG标志。 然后再这个标记后面添加你想要的字符串。并不是替换,不过这个方法在脚本中不行, <!--#name--> 是html文件的注释,但是在 <script>...</script>中就不是注释了,所以在添加js代码的时候必须把整个 JS脚本添加进来。 E.SSI函数简介 typedef int (*tSSIHandler)(int iIndex, char *pcInsert, int iInsertLen)这个函数除了这三个参数外,还有其他三个可选的参数,这里没写出来,具体怎么用交给大家研究啦。 参数说明: iIndex:在ppcTags数组中的索引号,ppcTags是一个字符串数组,用来标记tag名字的,比如<!--#name-->,“name”就是一个tag名 pcInsert:在tag后面插入的字符串 iInsertLen:在tag后面插入字符串的长度 F.SSI应用举例 1.新建一个index.shtml的文件,在文件中输入一下代码: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><head><title>welcom</title><meta http-equiv="Content-Type" content="text/html; charset=gb2312" /><meta http-equiv= "Cache-Control" content= "no-cache" /> </head><body><form id="login" name="login" method="get" action="login.cgi"><table width="200" border="1" align="center"><tr><td colspan="2" align="center">请按F5进行刷新</td></tr> <tr><td width="50">内容:</td><td width="134"><label><!--#alientek--></label></td></tr></table></form></body></html>注意到上面有一个 <!--#alientek--> 的TAG,,利用LWIP的SSI的功能可以将我们想插入的字符串插入到后面。 2.编写SSI处理函数 static const char *ppcTags[] = //TAG数组{ " alientek ", };enum ssi_index_s //索引号{ SSI_INDEX_ALIENTEK_GET = 0, //该表对应ppcTags[]的排序} ; static int SSIHandler ( int iIndex, char *pcInsert, int iInsertLen ){ switch(iIndex) { case SSI_INDEX_ALIENTEK_GET: pcInsert = "alentek"; iInsertLen = strlen(pcInsert ); break; default: strcpy( pcInsert , "" ); } return iInsertLen ; //返回字符串的长度} 最后重点介绍下ajax做异步提交,更新数据: 一、如何将静态页面转换为16进制的c语言数组? 前面说了这么多都没提这个重要的问题,希望大家别见怪,嘿嘿。 这里我谢谢 zhangpisces网友提供的资料。他的资料给了我很大的参考价值,喝水不忘,挖井人。 步骤: 1.首先将网页源文件编写好,如工程中atk文件夹下的文件。2.将makefsfile工具和atk放在一个文件夹内.3.运行cmd,进入到makefsfile工具的目录。4.使用makefsfile -i atk -o fsdata.h -r -h 命令生成一个 fsdata.h文件 注意:在fs.c文件中需要注释掉//#include "fsdata.c"这个,我们不用。 二,添加/response.ssi文件 #define RESPONSE_BUF_SIZE 512 //http响应缓存大小 unsigned char data_response_ssi[RESPONSE_BUF_SIZE+14] ={ /* /response.ssi */ 0x2F, 0x72, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x2E, 0x73, 0x73, 0x69, 0x00, };struct fsdata_file file_response_ssi[] ={ { NULL, data_response_ssi, data_response_ssi + 14, sizeof(data_response_ssi) - 14 }};makefsfile 生成的都是ROM文件,如果我们想要改变返回给浏览器的响应内容必须定义一个可以变化的文件。 在CGI函数中我们就可以把想发送的数据写入到这个文件当中,return "/response.ssi"就OK了。 三、编写基于ajax的JS代码 我把战舰原来uip的web例程(不过这个例程并没有用到SSI,是综合例程)改成了ajax异步获取数据,参考JS源码如下: <script type="text/javascript">var xmlhttp;function loadXMLDoc(url,cfunc){ if (window.XMLHttpRequest) { xmlhttp=new XMLHttpRequest(); } else { xmlhttp=new ActiveXObject("Microsoft.XMLHTTP"); } xmlhttp.onreadystatechange=cfunc; xmlhttp.open("GET",url,true); xmlhttp.send();} function led0(){ loadXMLDoc("/led_red.cgi?red=1&t="+ Math.random(),function() { if (xmlhttp.readyState==4 && xmlhttp.status==200) { document.getElementById("red").src=xmlhttp.responseText; } });} function led1(){ loadXMLDoc("/led_green.cgi?green=1&t="+ Math.random(),function() { if (xmlhttp.readyState==4 && xmlhttp.status==200) { document.getElementById("green").src=xmlhttp.responseText; } });}var text; function update(){ loadXMLDoc("/orther.cgi?t="+ Math.random(),function() { if (xmlhttp.readyState==4 && xmlhttp.status==200) { text = xmlhttp.responseText; text = text.split(";"); document.getElementById("temperature").innerHTML=text[0]+"℃"; document.getElementById("time").innerHTML=text[1]; } }); }function init(){ setInterval(update,1000); } </script> ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ 到现在为止基本上讲完了,第一次发这么长的帖子,各位看官慢慢看。有错误请多多包容哦^_^ 附件中还有战舰的UIP补充例程,添加了UDP的服务器和客户端模式,web没怎么搞。 还有LWIP例程。包括了TCP服务器,TCP客户端,UDP服务器,UDP客户端,WEBSEVER。这些功能,配合我们的ALIENTEK的ENC28J60模块就可以 直接在战舰开发板上运行了。 -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- LWIP例程说明: KEY_UP:选择实验(TCP服务器,TCP客户端,UDP服务器,UDP客户端,WEBSEVER)KEY_DOWN:发送数据 建议大家先学习UIP,然后再学LWIP可能效果要好些,毕竟UIP还是要简单得多。 |
-
20.68 KB, 下载次数: 4854
-
53.25 KB, 下载次数: 4647
-
494.66 KB, 下载次数: 7081
-
转载自:http://www.openedv.com/forum.php?mod=viewthread&tid=25178&extra=&page=1