【ALIENTEK 战舰STM32开发板例程系列连载+教学】第五十七章 ENC28J60网络实验

2019-08-16 18:48发布

第五十七章 ENC28J60网络实验

  本章,我们将向大家介绍ALIENTEK ENC28J60网络模块及其使用。本章,我们将使用ALIENTEK ENC28J60网络模块和uIP 1.0实现:TCP服务器、TCP客服端以及WEB服务器等三个功能。本章分为如下几个部分: 57.1 ENC28J60以及uIP简介 57.2 硬件设计 57.3 软件设计 57.4 下载验证
57.1 ENC28J60以及uIP简介 本章我们需要用到ENC28J60以太网控制器和uIP 1.0以太网协议栈。接下来分别介绍这两个部分。

57.1.1 ENC28J60简介

ENC28J60 是带有行业标准串行外设接口(Serial Peripheral InterfaceSPI)的独立以太网控制器。它可作为任何配备有SPI 的控制器的以太网接口。ENC28J60 符合IEEE 802.3 的全部规范,采用了一系列包过滤机制以对传入数据包进行限制。 它还提供了一个内部DMA 模块,以实现快速数据吞吐和硬件支持的IP校验和计算。 与主控制器的通信通过两个中断引脚和 SPI 实现,数据传输速率高达10 Mb/s。两个专用的引脚用于连接LED,进行网络活动状态指示。        ENC28J60的主要特点如下: l  兼容IEEE802.3协议的以太网控制器 l  集成MAC10 BASE-T物理层 l  支持全双工和半双工模式 l  数据冲突时可编程自动重发 l  SPI接口速度可达10Mbps l  8K数据接收和发送双端口RAM l  提供快速数据移动的内部DMA控制器 l  可配置的接收和发送缓冲区大小 l  两个可编程LED输出 l  7个中断源的两个中断引脚 l  TTL电平输入 l  提供多种封装:SOIC/SSOP/SPDIP/QFN ENC28J60的典型应用电路如图57.1.1.1所示:

57.1.1.1 ENC28J60典型应用电路 ENC28J60 由七个主要功能模块组成: 1)  SPI 接口,充当主控制器和ENC28J60 之间通信通道。 2)  控制寄存器,用于控制和监视ENC28J60 3)  双端口RAM缓冲器,用于接收和发送数据包。 4)  判优器,当DMA、发送和接收模块发出请求时对RAM 缓冲器的访问进行控制。 5)  总线接口,对通过SPI 接收的数据和命令进行解析。 6)  MAC(Medium Access Control)模块,实现符合IEEE 802.3 标准的MAC 逻辑。 7)  PHY(物理层)模块,对双绞线上的模拟数据进行编码和译码。 ENC28J60还包括其他支持模块,诸如振荡器、片内稳压器、电平变换器(提供可以接受5V 电压的I/O 引脚)和系统控制逻辑。       ENC28J60的功能框图如图57.1.1.2所示:
57.1.1.2 ENC28J60功能框图        ALIENTEK ENC28J60网络模块采用ENC28J60作为主芯片,单芯片即可实现以太网接入,利用该模块,基本上只要是个单片机,就可以实现以太网连接。ALIENTEK ENC28J60网络模块原理图如图57.1.1.3所示:
57.1.1.3 ALIENTEK ENC28J60网络模块原理图        ALIENTEK ENC28J60网络模块外观图如图57.1.1.4所示:
57.1.1.4 ALIENTEK ENC28J60网络模块外观图        该模块通过一个8个引脚的排针与外部电路连接,这8个引脚分别是:GNDRSTMISOSCKMOSIINTCSV3.3。其中GNDV3.3用于给模块供电,MISO/MOSI/SCK用于SPI通信,CS是片选信号,INT为中断输出引脚,RST为模块复位信号。

57.1.2 uIP简介

uIP 由瑞典计算机科学学院(网络嵌入式系统小组)Adam Dunkels 开发。其源代码由C 语言编写,并完全公开,uIP的最新版本是1.0 版本,本指南移植和使用的版本正是此版本。 uIP 协议栈去掉了完整的TCP/IP 中不常用的功能,简化了通讯流程,但保留了网络通信必须使用的协议,设计重点放在了IP/TCP/ICMP/UDP/ARP 这些网络层和传输层协议上,保证了其代码的通用性和结构的稳定性。 由于uIP 协议栈专门为嵌入式系统而设计,因此还具有如下优越功能: 1)  代码非常少,其协议栈代码不到6K,很方便阅读和移植。 2)  占用的内存数非常少,RAM 占用仅几百字节。 3)  其硬件处理层、协议栈层和应用层共用一个全局缓存区,不存在数据的拷贝,且发送和接收都是依靠这个缓存区,极大的节省空间和时间。 4)  支持多个主动连接和被动连接并发。 5)  其源代码中提供一套实例程序:web 服务器,web 客户端,电子邮件发送程序(SMTP 客户端)Telnet 服务器, DNS 主机名解析程序等。通用性强,移植起来基本不用修改就可以通过。 6)  对数据的处理采用轮循机制,不需要操作系统的支持。 由于uIP 对资源的需求少和移植容易,大部分的8 位微控制器都使用过uIP协议栈, 而且很多的著名的嵌入式产品和项目(如卫星,Cisco 路由器,无线传感器网络)中都在使用uIP 协议栈。 uIP相当于一个代码库,通过一系列的函数实现与底层硬件和高层应用程序的通讯,对于整个系统来说它内部的协议组是透明的,从而增加了协议的通用性。uIP协议栈与系统底层和高层应用之间的关系如图57.1.2.1所示:
57.1.2.1 uIP在系统中的位置 从上图可以看出,uIP协议栈主要提供2个函数供系统底层调用:uip_inputuip_periodic。另外和应用程序联系主要是通过UIP_APPCALL函数。 当网卡驱动收到一个输入包时,将放入全局缓冲区uip_buf 中,包的大小由全局变量uip_len 约束。同时将调用uip_input()函数,这个函数将会根据包首部的协议处理这个包和需要时调用应用程序。当uip_input()返回时,一个输出包同样放在全局缓冲区uip_buf 里,大小赋给uip_len。如果uip_len 0,则说明没有包要发送。否则调用底层系统的发包函数将包发送到网络上。 uIP 周期计时是用于驱动所有的uIP 内部时钟事件。当周期计时激发,每一个TCP 连接都会调用uIP 函数uip_periodic()。类似于uip_input()函数。uip_periodic()函数返回时,输出的IP 包要放到uip_buf 中,供底层系统查询uip_len 的大小发送。 由于使用TCP/IP 的应用场景很多,因此应用程序作为单独的模块由用户实现。uIP 协议栈提供一系列接口函数供用户程序调用,其中大部分函数是作为C的宏命令实现的,主要是为了速度、代码大小、效率和堆栈的使用。用户需要将应用层入口程序作为接口提供给uIP 协议栈, 并将这个函数定义为宏UIP_APPCALL()。这样,uIP 在接受到底层传来的数据包后,在需要送到上层应用程序处理的地方,调用UIP_APPCALL( )。在不用修改协议栈的情况下可以适配不同的应用程序。 uIP协议栈提供了我们很多接口函数,这些函数在 uip.h 中定义,为了减少函数调用造成的额外支出,大部分接口函数以宏命令实现的,uIP提供的接口函数有: 1,初始化uIP协议栈:uip_init() 2.处理输入包:uip_input() 3.处理周期计时事件:uip_periodic() 4.开始监听端口:uip_listen() 5.连接到远程主机:uip_connect() 6.接收到连接请求:uip_connected() 7.主动关闭连接:uip_close() 8.连接被关闭:uip_closed() 9.发出去的数据被应答:uip_acked() 10.在当前连接发送数据:uip_send() 11.在当前连接上收到新的数据:uip_newdata() 12.告诉对方要停止连接:uip_stop() 13.连接被意外终止:uip_aborted() 接下来,我们看看uIP的移植过程。首先,uIP1.0的源码包里面有如下内容,如图57.1.2.2所示:
57.1.2.2 uIP 1.0源码包内容 其中apps文件夹里面是uip提供的各种参考代码,本章我们主要有用到里面的webserver部分。doc文件夹里面是一些uip的使用及说明文件,是学习uip的官方资料。lib文件夹里面是用于内存管理的一个代码,本章我们没有用到。uip里面就是uip 1.0的源码了,我们全盘照收。unix里面提供的是具体的应用实例,我们移植参考主要是依照这个里面的代码。 移植第一步:实现在unix/tapdev.c里面的三个函数。首先是tapdev_init函数,该函数用于初始化网卡(也就是我们的ENC28J60),通过这个函数实现网卡初始化。其次,是tapdev_read函数,该函数用于从网卡读取一包数据,将读到的数据存放在uip_buf里面,数据长度返回给uip_len。最后,是tapdev_send函数,该函数用于向网卡发送一包数据,将全局缓存区uip_buf里面的数据发送出去(长度为uip_len)。其实这三个函数就是实现最底层的网卡操作。 第二步,因为uIP协议栈需要使用时钟,为TCPARP的定时器服务,因此我们需要STM32提供一个定时器做时钟,提供10ms计时(假设clock-arch.h里面的CLOCK_CONF_SECOND100),通过clock-arch.c里面的clock_time函数返回给uIP使用。 第三步,配置uip-conf.h里面的宏定义选项。主要用于设置TCP最大连接数、TCP监听端口数、CPU大小端模式等,这个大家根据自己需要配置即可。 通过以上3步的修改,我们基本上就完成了uIP的移植。在使用uIP的时候,一般通过如下顺序: 1) 实现接口函数(回调函数)UIP_APPCALL 该函数是我们使用uIP最关键的部分,它是uIP和应用程序的接口,我们必须根据自己的需要,在该函数做各种处理,而做这些处理的触发条件,就是前面提到的uIP提供的那些接口函数,如uip_newdatauip_ackeduip_closed等等。另外,如果是UDP,那么还需要实现UIP_UDP_APPCALL回调函数。 2) 调用tapdev_init函数,先初始化网卡。 此步先初始化网卡,配置MAC地址,为uIP和网络通信做好准备。 3) 调用uip_init函数,初始化uIP协议栈。 此步主要用于uip自身的初始化,我们直接调用就是。 4) 设置IP地址、网关以及掩码 这个和电脑上网差不多,只不过我们这里是通过uip_ipaddruip_sethostaddruip_setdraddruip_setnetmask等函数实现。 5) 设置监听端口 uIP根据你设定的不同监听端口,实现不同的服务,比如我们实现Web Server就监听80端口(浏览器默认的端口是80端口),凡是发现80端口的数据,都通过Web ServerAPPCALL函数处理。根据自己的需要设置不同的监听端口。不过uIP有本地端口(lport)和远程端口(rport)之分,如果是做服务端,我们通过监听本地端口(lport)实现;如果是做客户端,则需要去连接远程端口(rport)。 6) 处理uIP事件 最后,uIP通过uip_polling函数轮询处理uIP事件。该函数必须插入到用户的主循环里面(也就是必须每隔一定时间调用一次)。   57.2 硬件设计 本节实验功能简介:开机检测ENC28J60,如果检测不成功,则提示报错。在成功检测到ENC28J60之后,初始化uIP,并设置IP地址(192.168.1.16)等,然后监听80端口和1200端口,并尝试连接远程1400端口,80端口用于实现WEB Server功能,1200端口用于实现TCP Server功能,连接1400端口实现TCP Client功能。此时,我们在电脑浏览器输入http://192.168.1.16 ,就可以登录到一个界面,该界面可以控制开发板上两个LED灯的亮灭,还会显示开发板的当前时间以及开发板STM32芯片的温度(每10秒自动刷新一次)。另外,我们通过网络调试软件(做TCP Server时,设置IP地址为:192.168.1.103,端口为1400;做TCP Client时,设置IP地址为:192.168.1.16,端口为1200)同开发板连接,即可实现开发板与网络调试软件之间的数据互发。按KEY0,由开发板的TCP Server端发送数据到电脑的TCP Client端。按KEY2,则由开发板的TCP Client端发送数据到电脑的TCP Server端。LCD显示当前连接状态。 所要用到的硬件资源如下: 1)  指示灯DS0 DS1 2)  KEY0/KEY2两个按键 3)  串口 4)  TFTLCD模块 5)  ENC28J60网络模块 前面4部分都已经详细介绍过,本章,我们重点看看ALIENTEK ENC28J60网络模块同ALIENTEK 战舰STM32开发板的连接,前面我们介绍了ALIENTEK ENC28J60网络模块的接口,我们通过杜邦线(或排线)连接网络模块和开发板的P12端口,连接关系如表56.2.1所示:
56.2.1 ENC28J60网络模块同战舰STM32开发板连接关系表 上表可以看出,其实网络模块同战舰STM32开发板的线序是一一对应的,所以如果你有一个1*8的排线,就可以直接对插即可。这里需要注意,本来开发板的P12端口是用来连接SD卡,实现SPI读写SD卡的,如果要连接网络模块,我们需要把跳线帽连接到P10P11,这样还是可以通过SDIO访问SD卡。 在开发板连接网络模块以后,我们还需要一根网线(自备),连接网络模块和路由器,这样我们才能实现和电脑的连接。 57.3 软件设计 本章,我们在第二十八章实验 (实验23 )的基础上修改,在该工程源码下面加入uIP-1.0文件夹,存放uIP1.0源码,再新建uIP-APP文件夹,存放应用部分代码,因为uIP自己有一个timer.ctimer.h的文件,所以我们还需要修改HARDWARE里面的timer.ctimer.h为不同的名字,本章我们改为timerx.ctimerx.h,我们还需要实现ENC28J60的驱动代码,存放在HARDWARE文件夹下的ENC28J60文件夹里面。详细的步骤我们就不一一阐述了,全部改好之后,工程如图57.3.1所示:
57.3.1 移植完后,MDK工程图        图中uIP-1.0文件夹里面的代码全部是uIP提供的协议栈源码,而uIP-APP里面的代码则部分是我们自己实现的,部分是uIP提供的,其中:        clock-arch.c,属于uIP协议栈,uIP通过该代码里面的clock_time函数获取时钟节拍。        tapdev.c,同样是uIP提供,用来实现uIP与网卡的接口,该文件实现tapdev_inittapdev_readtapdev_send三个重要函数。        tcp_demo.c,完成UIP_APPCALL函数的实现,即tcp_demo_appcall函数。该函数根据端口的不同,分别调用不同的appcall函数,实现不同功能。同时该文件还实现了uip_log函数,用于打印日志。        tcp_client_demo.c,完成一个简单的TCP客户端应用,实现与电脑TCP服务端的数据收发。        tcp_server_demo.c,完成一个简单的TCP服务端应用,实现与电脑TCP客户端的数据收发。        httpd.chttpd-cgi.chttpd-fs.chttpd-strings.h,属于uIP提供的WEB服务器参考代码,我们通过修改部分代码,实现一个简单的WEB服务器。        本章代码很多,我们仅挑一些重点和大家介绍。 首先是tapdev.c里面的三个函数,代码如下: //MAC地址,必须唯一 //如果你有两个战舰开发板,想连入路由器,则需要修改MAC地址不一样! const u8 mymac[6]={0x04,0x02,0x35,0x00,0x00,0x01};   //MAC地址 //配置网卡硬件,并设置MAC地址 //返回值:0,正常;1,失败; u8 tapdev_init(void) {                   u8 i,res=0;                                           res=ENC28J60_Init((u8*)mymac);      //初始化ENC28J60                                           //IP地址和MAC地址写入缓存区       for (i = 0; i < 6; i++)uip_ethaddr.addr=mymac     //指示灯状态:0x476 is PHLCON LEDA(绿)=links status, LEDB()=receive/transmit       //PHLCONPHY 模块LED 控制寄存器             ENC28J60_PHY_Write(PHLCON,0x0476);        return res;       } //读取一包数据  uint16_t tapdev_read(void) {            return  ENC28J60_Packet_Receive(MAX_FRAMELEN,uip_buf); } //发送一包数据  void tapdev_send(void) {        ENC28J60_Packet_Send(uip_len,uip_buf); }        tapdev_init函数,该函数用于初始化网卡,即初始化我们的ENC28J60,初始化工作主要通过调用ENC28J60_Init函数实现,该函数在enc28j60.c里面实现,同时该函数还用于设置MAC地址,这里请确保MAC地址的唯一性。在初始化enc28j60以后,我们设置enc28j60LED控制器工作方式,即完成对ENC28J60的全部初始化工作。该函数的返回值用于判断网卡初始化是否成功。 tapdev_read函数,该函数调用ENC28J60_Packet_Receive函数,实现从网卡(ENC28J60)读取一包数据,数据被存放在uip_buf里面,同时返回读到的包长度(包长度一般是存放在uip_len里面的)。        tapdev_send函数,该函数调用ENC28J60_Packet_Send函数,实现从网卡(ENC28J60)发送一包数据到网络,数据内容存放在uip_buf,数据长度为uip_len        再来看看tcp_demo.c里面的tcp_demo_appcall函数,该函数代码如下: //TCP应用接口函数(UIP_APPCALL) //完成TCP服务(包括serverclient)HTTP服务 void tcp_demo_appcall(void) {            switch(uip_conn->lport)//本地监听端口801200        {               case HTONS(80):                      httpd_appcall();                      break;               case HTONS(1200):                   tcp_server_demo_appcall();                      break;               default: break;               }                       switch(uip_conn->rport)       //远程连接1400端口        {            case HTONS(1400):                      tcp_client_demo_appcall();                    break;            default: break;          }   } 该函数即UIP_APPCALL函数,是uIP同应用程序的接口函数,该函数通过端口号选择不同的appcall函数,实现不同的服务。其中80端口用于实现WEB服务,通过调用httpd_appcall实现;1200端口用于实现TCP服务器,通过调用tcp_server_demo_appcall函数实现;1400是远程端口,用于实现TCP客户端,调用tcp_client_demo_appcall函数实现。 接着,我们来看看这3appcall函数,首先是WEB服务器的appcall函数:httpd_appcall,该函数在httpd.c里面实现,源码如下: //http服务(WEB)处理 void httpd_appcall(void) {        struct httpd_state *s = (struct httpd_state *)&(uip_conn->appstate);//读取连接状态        if(uip_closed() || uip_aborted() || uip_timedout())//异常处理(这里无任何处理)        else if(uip_connected())//连接成功        {               PSOCK_INIT(&s->sin, s->inputbuf, sizeof(s->inputbuf) - 1);               PSOCK_INIT(&s->sout, s->inputbuf, sizeof(s->inputbuf) - 1);               PT_INIT(&s->outputpt);               s->state = STATE_WAITING;               /*    timer_set(&s->timer, CLOCK_SECOND * 100);*/               s->timer = 0;               handle_connection(s);//处理        }else if(s!=NULL)        {               if(uip_poll())               {                      ++s->timer;                      if(s->timer >= 20)uip_abort();                     else s->timer = 0;               }               handle_connection(s);        }else uip_abort();// } 该函数在连接建立的时候,通handle_connection函数处理http数据,handle_connection函数代码如下: //分析http数据 static void handle_connection(struct httpd_state *s) {        handle_input(s);  //处理http输入数据        if(s->state==STATE_OUTPUT)handle_output(s);//输出状态,处理输出数据 } 该函数调用handle_input处理http输入数据,通过调用handle_output实现http网页输出。对我们来说最重要
友情提示: 此问题已得到解决,问题已经关闭,关闭后问题禁止继续编辑,回答。
该问题目前已经被作者或者管理员关闭, 无法添加新回复
44条回答
愤怒的工程师
1楼-- · 2019-08-18 20:11
回复【21楼】愤怒的工程师:
---------------------------------
必须的啊.
愤怒的工程师
2楼-- · 2019-08-18 21:38
回复【22楼】正点原子:
---------------------------------
如果我这边有一个服务器,那边你那边如何能够连接上我这边的服务器呢???用TCP虚拟的服务器我们可以设置IP,但如果真实的服务器,IP是固定的,那你那边如何能够连接上呢???原子哥
正点原子
3楼-- · 2019-08-19 01:38
有独立IP,就能访问。
愤怒的工程师
4楼-- · 2019-08-19 05:05
回复【24楼】正点原子:
---------------------------------
独立的IP?我是给我的以太网模块设置了一个IP呀!是不是独立IP呢?还是我理解错了
正点原子
5楼-- · 2019-08-19 08:33
 精彩回答 2  元偷偷看……
愤怒的工程师
6楼-- · 2019-08-19 12:06
回复【26楼】正点原子:
---------------------------------
原子哥,为什么以太网作为客服端连接到服务器,过一段时间后会自动断开呢,然后检测不到掉线的信息?如果检测到断开的话,能否重连呢?

一周热门 更多>