本项目的目的是构建一个基于STM32F103ZET6微控制器的嵌入式Web服务器,以满足远程监控和控制嵌入式设备的需求。随着物联网技术的快速发展,远程监控和控制嵌入式设备变得越来越重要。本项目通过选择STM32F103ZET6作为主控芯片,结合ENC28J60网卡实现网络通信,并移植UIP协议栈来构建轻量级的Web服务器。项目还集成了DS18B20温度传感器、LED灯模块和高电平触发的有源蜂鸣器,以实现远程监控和控制STM32设备端的功能,如LED灯和蜂鸣器的控制,以及设备端温度和RTC时间的显示。这种设计使得用户能够通过浏览器访问服务器,实时查看和控制嵌入式设备,为物联网应用提供了一种灵活、高效的解决方案。
(1)网络通信搭建:通过STM32F103ZET6微控制器与ENC28J60以太网控制器的集成,利用SPI接口实现数据传输,成功移植UIP轻量级TCP/IP协议栈,从而在嵌入式平台上搭建起一个功能完备的Web服务器。
(2)网页服务:在STM32内部存储一个简易的网页文件,该网页设计用于用户界面展示及交互。当用户使用任何标准的Web浏览器访问此服务器的IP地址时,即可加载并显示该网页内容。
(3)远程控制功能:
(4)环境监测与显示:
项目不仅实现了从硬件选型、网络配置到软件开发的全过程,还展示了物联网技术在实际应用中的一个小而完整的案例,即通过简单的Web界面远程监控和控制物理设备,体现了STM32平台在物联网领域的灵活性与强大功能。
(1)主控制器模块:
(2)网络通信模块:
(3)温度监测模块:
(4)输出控制模块:
(5)电源模块:为确保所有硬件组件稳定工作,需要一个合适的电源供应模块,为STM32微控制器、ENC28J60网卡、传感器及外围电路提供稳定的电压和电流。
ENC28J60是一款集成MAC(Media Access Control,媒体访问控制)和10BASE-T PHY(物理层)的以太网控制器,特别适合于嵌入式系统和微控制器应用。
以下是ENC28J60网卡的一些关键特性与介绍:
(1)接口类型:它使用SPI(Serial Peripheral Interface,串行外设接口)作为与外部微控制器通信的主要方式,这使得它能够以较少的引脚数(通常为4或5条线)与诸如STM32、Arduino等微控制器连接,降低了硬件设计的复杂度。
(2)兼容性:ENC28J60兼容IEEE 802.3标准,这意味着它可以无缝地融入标准以太网网络环境中。它支持10Mbps的传输速率,适用于不需要高速网络连接的应用场景。
(3)集成功能:除了MAC和PHY层之外,ENC28J60还集成了其他一些功能,如缓冲区管理、DMA(Direct Memory Access,直接内存访问)支持、以及对多种网络帧类型的支持,包括广播、多播和单播。
(4)网络功能:能够实现完整的以太网数据包的发送和接收,支持IP、TCP、UDP等网络协议栈。开发者通常会结合LwIP(Lightweight IP)这样的轻量级TCP/IP协议栈来实现网络通信。
(5)物理层特性:支持10BASE-T标准,可以通过RJ45接口连接双绞线(UTP),支持自动极性和交叉检测,可工作在全双工或半双工模式下。
(6)功耗与封装:ENC28J60设计考虑到了低功耗应用的需求,适合电池供电设备。它通常采用SSOP(Shrink Small Outline Package)或QFN(Quad Flat No-Leads)等小型封装形式,便于在空间受限的设计中使用。
(7)灵活性与应用:由于其SPI接口和相对较低的成本,ENC28J60被广泛应用于各种嵌入式项目中,如物联网设备、智能家居、工业控制、远程监控系统等。
ENC28J60以其集成度高、接口灵活、成本效益好等特点,成为了许多嵌入式系统设计中实现网络连接的优选解决方案。
UIP(Micro IP)协议栈是一种专门为资源受限的嵌入式系统设计的轻量级TCP/IP协议栈。它最初由Adam Dunkels在SICS(瑞典计算机科学研究所)开发,目的是使即便是8位微控制器也能轻松实现网络通信,而不必负担全尺寸TCP/IP协议栈的开销。下面是UIP协议栈的详细介绍:
这是判断网页下发的请求。
这个是写好的网页模版:
代码如下:
- <!DOCTYPE html>
- <html>
- <head>
- <meta charset="UTF-8">
- <title>WEB服务器设计</title>
- <style>
- .LED{
- margin-left: 10px;
- }
- .font{
- font-size:30px;
- width:130px;
- height:60px;
- }
- .LED:hover{
- background: red;
- }
- .LED value{
- background: blue;
- }
- </style>
- </head>
- <body>
- <div style="border:1px solid black;text-align: center;width:600px;height:500px;margin:auto;">
- <h1>基于STM32的WEB服务器设计</h1>
- <h1>微信公众号:DS小龙哥嵌入式技术资讯</h1>
- <h1>这是基于ENC28J60+UIP协议栈设计的WEB服务器</h1>
- <div class="paren_LED ">
- <button class="LED font" v="on1" num = "1">LED1</button>
- <button class="LED font" v="on2" num = "2">LED2</button>
- <button class="LED font" v="on3" num = "3">LED3</button>
- <button class="LED font" v="on4" num = "4">LED4</button>
- <button class="LED font" v="on5" num = "5" style="display: block;margin-left:23px;margin-top:5px;">蜂鸣器</button>
- </div>
- <div style="text-align: left;">
- <span class="font" style="margin-left:22px">温度:</span><input style="font-size: 30px" value="23℃" id="wendu"/>
- </div>
- <div style="text-align: left;">
- <span class="font" style="margin-left:22px">时间:</span><input style="font-size: 30px" value="1s" id="time"/>
- </div>
- </div>
- <script>
- var LED = document.getElementsByClassName("paren_LED")[0];
- var wendu = document.getElementById("wendu");
- var time = document.getElementById("time");
- var xhr = new XMLHttpRequest();
- var xhr2 = new XMLHttpRequest();
- //控制灯的按钮
- LED.onclick = function(e){
- var status = e.target.getAttribute("v");
- var paren_LED = LED.children;
- xhr.open("GET","test?data="+status,true);
- //xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
- xhr.send();
- xhr.onreadystatechange = function () {
- if(xhr.readyState==4&&xhr.status==200){
- //获取后台的数据
- var data = xhr.responseText;
- var sta = e.target.getAttribute("num");
- e.target.setAttribute("v",data+sta);
- for(var i=0;i<paren_LED.length;i++){
- var str = paren_LED[i].getAttribute("v");
- var st = str.substring(0,str.length-1);
- if("off"==st){
- paren_LED[i].style.background="blue";
- }else if("on"==st){
- paren_LED[i].style.background="";
- }else if("Beep_on"==st){
- paren_LED[i].style.background="";
- }else if("Beep_off"==st){
- paren_LED[i].style.background="blue";
- }
- }
- }
- }
- }
- //定时器
- var wd = function(){
- xhr2.open("GET","test?data=temp",true);
- xhr2.send();
- xhr2.onreadystatechange = function () {
- var data = xhr2.responseText.split("&");
- wendu.value= data[0]+"℃";
- time.value = data[1]+"s";
- }
- };
- setInterval("wd()",1000);
- </script>
- </body>
- </html>
-
将这个文件转为C语言数组放到单片机代码工程里就行了。
如何转换? 用winhex 这个工具。
替换这个数组就行了:
因为板子是静态IP,为了方便通信,ENC28J60通过网线直接与电脑网口连接。 设置固定的IP地址。
- #include "delay.h"
- #include <stdio.h>
- #include "enc28j60.h"
-
- /*
- 以下是ENC28J60驱动移植接口:
- MISO--->PA6----主机输入
- MOSI--->PA7----主机输出
- SCLK--->PA5----时钟信号
- CS----->PA4----片选
- RESET-->PG15---复位
- */
- #define ENC28J60_CS PAout(4) //ENC28J60片选信号
- #define ENC28J60_RST PGout(15) //ENC28J60复位信号
- #define ENC28J60_MOSI PAout(7) //输出
- #define ENC28J60_MISO PAin(6) //输入
- #define ENC28J60_SCLK PAout(5) //时钟线
-
- static u8 ENC28J60BANK;
- static u32 NextPacketPtr;
-
-
- /*
- 函数功能:底层SPI接口收发一个字节
- 说 明:模拟SPI时序,ENC28J60时钟线空闲电平为低电平,在第一个下降沿采集数据
- */
- u8 ENC28J60_SPI_ReadWriteOneByte(u8 tx_data)
- {
- u16 cnt=0;
- while((SPI1->SR&1<<1)==0) //等待发送区空--等待发送缓冲为空
- {
- cnt++;
- if(cnt>=65530)return 0; //超时退出 u16=2个字节
- }
- SPI1->DR=tx_data; //发送一个byte
- cnt=0;
- while((SPI1->SR&1<<0)==0) //等待接收完一个byte
- {
- cnt++;
- if(cnt>=65530)return 0; //超时退出
- }
- return SPI1->DR; //返回收到的数据
- }
-
-
- /*
- 函数功能:复位ENC28J60,包括SPI初始化/IO初始化等
- MISO--->PA6----主机输入
- MOSI--->PA7----主机输出
- SCLK--->PA5----时钟信号
- CS----->PA4----片选
- RESET-->PG15---复位
- */
- void ENC28J60_Reset(void)
- {
- /*开启时钟*/
- RCC->APB2ENR|=1<<12; //开启SPI1时钟
- RCC->APB2ENR|=1<<2; //PA
- GPIOA->CRL&=0X0000FFFF; //清除寄存器
- GPIOA->CRL|=0XB8B30000;
- GPIOA->ODR|=0XF<<4; // 上拉--输出高电平
- GPIOA->ODR&=~(1<<5);
-
- RCC->APB2ENR|=1<<8; //2 3 4 5 6 7 8
- GPIOG->CRH&=0x0FFFFFFF;
- GPIOG->CRH|=0x30000000;
-
- /*SPI2基本配置*/
- SPI1->CR1=0X0; //清空寄存器
- SPI1->CR1|=0<<15; //选择“双线双向”模式
- SPI1->CR1|=0<<11; //使用8位数据帧格式进行发送/接收;
- SPI1->CR1|=0<<10; //全双工(发送和接收);
- SPI1->CR1|=1<<9; //启用软件从设备管理
- SPI1->CR1|=1<<8; //NSS
- SPI1->CR1|=0<<7; //帧格式,先发送高位
- SPI1->CR1|=0x1<<3;//当总线频率为36MHZ时,SPI速度为18MHZ,高速。
- SPI1->CR1|=1<<2; //配置为主设备
- SPI1->CR1|=1<<1; //空闲状态时, SCK保持高电平。
- SPI1->CR1|=1<<0; //数据采样从第二个时钟边沿开始。
- SPI1->CR1|=1<<6; //开启SPI设备。
-
- //针对ENC28J60的特点(SCK空闲为低电平)修改SPI的设置
- SPI1->CR1&=~(1<<6); //SPI设备失能
- SPI1->CR1&=~(1<<1); //空闲模式下SCK为0 CPOL=0
- SPI1->CR1&=~(1<<0); //数据采样从第1个时间边沿开始,CPHA=0
- SPI1->CR1|=1<<6; //SPI设备使能
-
- ENC28J60_RST=0; //复位ENC28J60
- DelayMs(10);
- ENC28J60_RST=1; //复位结束
- DelayMs(10);
- }
-
-
- /*
- 函数功能:读取ENC28J60寄存器(带操作码)
- 参 数:op:操作码
- addr:寄存器地址/参数
- 返 回 值:读到的数据
- */
- u8 ENC28J60_Read_Op(u8 op,u8 addr)
- {
- u8 dat=0;
- ENC28J60_CS=0;
- dat=op|(addr&ADDR_MASK);
- ENC28J60_SPI_ReadWriteOneByte(dat);
- dat=ENC28J60_SPI_ReadWriteOneByte(0xFF);
- //如果是读取MAC/MII寄存器,则第二次读到的数据才是正确的,见手册29页
- if(addr&0x80)dat=ENC28J60_SPI_ReadWriteOneByte(0xFF);
- ENC28J60_CS=1;
- return dat;
- }
-
-
-
- /*
- 函数功能:读取ENC28J60寄存器(带操作码)
- 参 数:
- op:操作码
- addr:寄存器地址
- data:参数
- */
- void ENC28J60_Write_Op(u8 op,u8 addr,u8 data)
- {
- u8 dat = 0;
- ENC28J60_CS=0;
- dat=op|(addr&ADDR_MASK);
- ENC28J60_SPI_ReadWriteOneByte(dat);
- ENC28J60_SPI_ReadWriteOneByte(data);
- ENC28J60_CS=1;
- }
-
-
-
- /*
- 函数功能:读取ENC28J60接收缓存数据
- 参 数:
- len:要读取的数据长度
- data:输出数据缓存区(末尾自动添加结束符)
- */
- void ENC28J60_Read_Buf(u32 len,u8* data)
- {
- ENC28J60_CS=0;
- ENC28J60_SPI_ReadWriteOneByte(ENC28J60_READ_BUF_MEM);
- while(len)
- {
- len--;
- *data=(u8)ENC28J60_SPI_ReadWriteOneByte(0);
- data++;
- }
- *data='\0';
- ENC28J60_CS=1;
- }
-
-
- /*
- 函数功能:向ENC28J60写发送缓存数据
- 参 数:
- len:要写入的数据长度
- data:数据缓存区
- */
- void ENC28J60_Write_Buf(u32 len,u8* data)
- {
- ENC28J60_CS=0;
- ENC28J60_SPI_ReadWriteOneByte(ENC28J60_WRITE_BUF_MEM);
- while(len)
- {
- len--;
- ENC28J60_SPI_ReadWriteOneByte(*data);
- data++;
- }
- ENC28J60_CS=1;
- }
-
- /*
- 函数功能:设置ENC28J60寄存器Bank
- 参 数:
- ban:要设置的bank
- */
- void ENC28J60_Set_Bank(u8 bank)
- {
- if((bank&BANK_MASK)!=ENC28J60BANK)//和当前bank不一致的时候,才设置
- {
- ENC28J60_Write_Op(ENC28J60_BIT_FIELD_CLR,ECON1,(ECON1_BSEL1|ECON1_BSEL0));
- ENC28J60_Write_Op(ENC28J60_BIT_FIELD_SET,ECON1,(bank&BANK_MASK)>>5);
- ENC28J60BANK=(bank&BANK_MASK);
- }
- }
-
-
- /*
- 函数功能:读取ENC28J60指定寄存器
- 参 数:addr:寄存器地址
- 返 回 值:读到的数据
- */
- u8 ENC28J60_Read(u8 addr)
- {
- ENC28J60_Set_Bank(addr);//设置BANK
- return ENC28J60_Read_Op(ENC28J60_READ_CTRL_REG,addr);
- }
-
-
- /*
- 函数功能:向ENC28J60指定寄存器写数据
- 参 数:
- addr:寄存器地址
- data:要写入的数据
- */
- void ENC28J60_Write(u8 addr,u8 data)
- {
- ENC28J60_Set_Bank(addr);
- ENC28J60_Write_Op(ENC28J60_WRITE_CTRL_REG,addr,data);
- }
-
-
- /*
- 函数功能:向ENC28J60的PHY寄存器写入数据
- 参 数:
- addr:寄存器地址
- data:要写入的数据
- */
- void ENC28J60_PHY_Write(u8 addr,u32 data)
- {
- u16 retry=0;
- ENC28J60_Write(MIREGADR,addr); //设置PHY寄存器地址
- ENC28J60_Write(MIWRL,data); //写入数据
- ENC28J60_Write(MIWRH,data>>8);
- while((ENC28J60_Read(MISTAT)&MISTAT_BUSY)&&retry<0XFFF)retry++;//等待写入PHY结束
- }
-
-
- /*
- 函数功能:初始化ENC28J60
- 参 数:macaddr:MAC地址
- 返 回 值:
- 0,初始化成功;
- 1,初始化失败;
- */
- u8 ENC28J60_Init(u8* macaddr)
- {
- u16 retry=0;
- ENC28J60_Reset(); //复位底层引脚接口
- ENC28J60_Write_Op(ENC28J60_SOFT_RESET,0,ENC28J60_SOFT_RESET);//软件复位
- while(!(ENC28J60_Read(ESTAT)&ESTAT_CLKRDY)&&retry<500)//等待时钟稳定
- {
- retry++;
- DelayMs(1);
- };
- if(retry>=500)return 1;//ENC28J60初始化失败
- // do bank 0 stuff
- // initialize receive buffer
- // 16-bit transfers,must write low byte first
- // set receive buffer start address 设置接收缓冲区地址 8K字节容量
- NextPacketPtr=RXSTART_INIT;
- // Rx start
- //接收缓冲器由一个硬件管理的循环FIFO 缓冲器构成。
- //寄存器对ERXSTH:ERXSTL 和ERXNDH:ERXNDL 作
- //为指针,定义缓冲器的容量和其在存储器中的位置。
- //ERXST和ERXND指向的字节均包含在FIFO缓冲器内。
- //当从以太网接口接收数据字节时,这些字节被顺序写入
- //接收缓冲器。 但是当写入由ERXND 指向的存储单元
- //后,硬件会自动将接收的下一字节写入由ERXST 指向
- //的存储单元。 因此接收硬件将不会写入FIFO 以外的单
- //元。
- //设置接收起始字节
- ENC28J60_Write(ERXSTL,RXSTART_INIT&0xFF);
- ENC28J60_Write(ERXSTH,RXSTART_INIT>>8);
- //ERXWRPTH:ERXWRPTL 寄存器定义硬件向FIFO 中
- //的哪个位置写入其接收到的字节。 指针是只读的,在成
- //功接收到一个数据包后,硬件会自动更新指针。 指针可
- //用于判断FIFO 内剩余空间的大小 8K-1500。
- //设置接收读指针字节
- ENC28J60_Write(ERXRDPTL,RXSTART_INIT&0xFF);
- ENC28J60_Write(ERXRDPTH,RXSTART_INIT>>8);
- //设置接收结束字节
- ENC28J60_Write(ERXNDL,RXSTOP_INIT&0xFF);
- ENC28J60_Write(ERXNDH,RXSTOP_INIT>>8);
- //设置发送起始字节
- ENC28J60_Write(ETXSTL,TXSTART_INIT&0xFF);
- ENC28J60_Write(ETXSTH,TXSTART_INIT>>8);
- //设置发送结束字节
- ENC28J60_Write(ETXNDL,TXSTOP_INIT&0xFF);
- ENC28J60_Write(ETXNDH,TXSTOP_INIT>>8);
- // do bank 1 stuff,packet filter:
- // For broadcast packets we allow only ARP packtets
- // All other packets should be unicast only for our mac (MAADR)
- //
- // The pattern to match on is therefore
- // Type ETH.DST
- // ARP BROADCAST
- // 06 08 -- ff ff ff ff ff ff -> ip checksum for theses bytes=f7f9
- // in binary these poitions are:11 0000 0011 1111
- // This is hex 303F->EPMM0=0x3f,EPMM1=0x30
- //接收过滤器
- //UCEN:单播过滤器使能位
- //当ANDOR = 1 时:
- //1 = 目标地址与本地MAC 地址不匹配的数据包将被丢弃
- //0 = 禁止过滤器
- //当ANDOR = 0 时:
- //1 = 目标地址与本地MAC 地址匹配的数据包会被接受
- //0 = 禁止过滤器
- //CRCEN:后过滤器CRC 校验使能位
- //1 = 所有CRC 无效的数据包都将被丢弃
- //0 = 不考虑CRC 是否有效
- //PMEN:格式匹配过滤器使能位
- //当ANDOR = 1 时:
- //1 = 数据包必须符合格式匹配条件,否则将被丢弃
- //0 = 禁止过滤器
- //当ANDOR = 0 时:
- //1 = 符合格式匹配条件的数据包将被接受
- //0 = 禁止过滤器
- ENC28J60_Write(ERXFCON,ERXFCON_UCEN|ERXFCON_CRCEN|ERXFCON_PMEN);
- ENC28J60_Write(EPMM0,0x3f);
- ENC28J60_Write(EPMM1,0x30);
- ENC28J60_Write(EPMCSL,0xf9);
- ENC28J60_Write(EPMCSH,0xf7);
- // do bank 2 stuff
- // enable MAC receive
- //bit 0 MARXEN:MAC 接收使能位
- //1 = 允许MAC 接收数据包
- //0 = 禁止数据包接收
- //bit 3 TXPAUS:暂停控制帧发送使能位
- //1 = 允许MAC 发送暂停控制帧(用于全双工模式下的流量控制)
- //0 = 禁止暂停帧发送
- //bit 2 RXPAUS:暂停控制帧接收使能位
- //1 = 当接收到暂停控制帧时,禁止发送(正常操作)
- //0 = 忽略接收到的暂停控制帧
- ENC28J60_Write(MACON1,MACON1_MARXEN|MACON1_TXPAUS|MACON1_RXPAUS);
- // bring MAC out of reset
- //将MACON2 中的MARST 位清零,使MAC 退出复位状态。
- ENC28J60_Write(MACON2,0x00);
- // enable automatic padding to 60bytes and CRC operations
- //bit 7-5 PADCFG2:PACDFG0:自动填充和CRC 配置位
- //111 = 用0 填充所有短帧至64 字节长,并追加一个有效的CRC
- //110 = 不自动填充短帧
- //101 = MAC 自动检测具有8100h 类型字段的VLAN 协议帧,并自动填充到64 字节长。如果不
- //是VLAN 帧,则填充至60 字节长。填充后还要追加一个有效的CRC
- //100 = 不自动填充短帧
- //011 = 用0 填充所有短帧至64 字节长,并追加一个有效的CRC
- //010 = 不自动填充短帧
- //001 = 用0 填充所有短帧至60 字节长,并追加一个有效的CRC
- //000 = 不自动填充短帧
- //bit 4 TXCRCEN:发送CRC 使能位
- //1 = 不管PADCFG如何,MAC都会在发送帧的末尾追加一个有效的CRC。 如果PADCFG规定要
- //追加有效的CRC,则必须将TXCRCEN 置1。
- //0 = MAC不会追加CRC。 检查最后4 个字节,如果不是有效的CRC 则报告给发送状态向量。
- //bit 0 FULDPX:MAC 全双工使能位
- //1 = MAC工作在全双工模式下。 PHCON1.PDPXMD 位必须置1。
- //0 = MAC工作在半双工模式下。 PHCON1.PDPXMD 位必须清零。
- ENC28J60_Write_Op(ENC28J60_BIT_FIELD_SET,MACON3,MACON3_PADCFG0|MACON3_TXCRCEN|MACON3_FRMLNEN|MACON3_FULDPX);
- // set inter-frame gap (non-back-to-back)
- //配置非背对背包间间隔寄存器的低字节
- //MAIPGL。 大多数应用使用12h 编程该寄存器。
- //如果使用半双工模式,应编程非背对背包间间隔
- //寄存器的高字节MAIPGH。 大多数应用使用0Ch
- //编程该寄存器。
- ENC28J60_Write(MAIPGL,0x12);
- ENC28J60_Write(MAIPGH,0x0C);
- // set inter-frame gap (back-to-back)
- //配置背对背包间间隔寄存器MABBIPG。当使用
- //全双工模式时,大多数应用使用15h 编程该寄存
- //器,而使用半双工模式时则使用12h 进行编程。
- ENC28J60_Write(MABBIPG,0x15);
- // Set the maximum packet size which the controller will accept
- // Do not send packets longer than MAX_FRAMELEN:
- // 最大帧长度 1500
- ENC28J60_Write(MAMXFLL,MAX_FRAMELEN&0xFF);
- ENC28J60_Write(MAMXFLH,MAX_FRAMELEN>>8);
- // do bank 3 stuff
- // write MAC address
- // NOTE: MAC address in ENC28J60 is byte-backward
- //设置MAC地址
- ENC28J60_Write(MAADR5,macaddr[0]);
- ENC28J60_Write(MAADR4,macaddr[1]);
- ENC28J60_Write(MAADR3,macaddr[2]);
- ENC28J60_Write(MAADR2,macaddr[3]);
- ENC28J60_Write(MAADR1,macaddr[4]);
- ENC28J60_Write(MAADR0,macaddr[5]);
- //配置PHY为全双工 LEDB为拉电流
- ENC28J60_PHY_Write(PHCON1,PHCON1_PDPXMD);
- // no loopback of transmitted frames 禁止环回
- //HDLDIS:PHY 半双工环回禁止位
- //当PHCON1.PDPXMD = 1 或PHCON1.PLOOPBK = 1 时:
- //此位可被忽略。
- //当PHCON1.PDPXMD = 0 且PHCON1.PLOOPBK = 0 时:
- //1 = 要发送的数据仅通过双绞线接口发出
- //0 = 要发送的数据会环回到MAC 并通过双绞线接口发出
- ENC28J60_PHY_Write(PHCON2,PHCON2_HDLDIS);
- // switch to bank 0
- //ECON1 寄存器
- //寄存器3-1 所示为ECON1 寄存器,它用于控制
- //ENC28J60 的主要功能。 ECON1 中包含接收使能、发
- //送请求、DMA 控制和存储区选择位。
- ENC28J60_Set_Bank(ECON1);
- // enable interrutps
- //EIE: 以太网中断允许寄存器
- //bit 7 INTIE: 全局INT 中断允许位
- //1 = 允许中断事件驱动INT 引脚
- //0 = 禁止所有INT 引脚的活动(引脚始终被驱动为高电平)
- //bit 6 PKTIE: 接收数据包待处理中断允许位
- //1 = 允许接收数据包待处理中断
- //0 = 禁止接收数据包待处理中断
- ENC28J60_Write_Op(ENC28J60_BIT_FIELD_SET,EIE,EIE_INTIE|EIE_PKTIE);
- // enable packet reception
- //bit 2 RXEN:接收使能位
- //1 = 通过当前过滤器的数据包将被写入接收缓冲器
- //0 = 忽略所有接收的数据包
- ENC28J60_Write_Op(ENC28J60_BIT_FIELD_SET,ECON1,ECON1_RXEN);
- if(ENC28J60_Read(MAADR5)== macaddr[0])return 0;//初始化成功
- else return 1;
-
- }
-
- /*
- 函数功能:读取EREVID
- 参 数:
- */
- u8 ENC28J60_Get_EREVID(void)
- {
- //在EREVID 内也存储了版本信息。 EREVID 是一个只读控
- //制寄存器,包含一个5 位标识符,用来标识器件特定硅片
- //的版本号
- return ENC28J60_Read(EREVID);
- }
-
-
-
- /*
- 函数功能:通过ENC28J60发送数据包到网络
- 参 数:
- len :数据包大小
- packet:数据包
- */
- void ENC28J60_Packet_Send(u32 len,u8* packet)
- {
- //设置发送缓冲区地址写指针入口
- ENC28J60_Write(EWRPTL,TXSTART_INIT&0xFF);
- ENC28J60_Write(EWRPTH,TXSTART_INIT>>8);
- //设置TXND指针,以对应给定的数据包大小
- ENC28J60_Write(ETXNDL,(TXSTART_INIT+len)&0xFF);
- ENC28J60_Write(ETXNDH,(TXSTART_INIT+len)>>8);
- //写每包控制字节(0x00表示使用macon3的设置)
- ENC28J60_Write_Op(ENC28J60_WRITE_BUF_MEM,0,0x00);
- //复制数据包到发送缓冲区
- //printf("len:%d\r\n",len); //监视发送数据长度
- ENC28J60_Write_Buf(len,packet);
- //发送数据到网络
- ENC28J60_Write_Op(ENC28J60_BIT_FIELD_SET,ECON1,ECON1_TXRTS);
- //复位发送逻辑的问题。参见Rev. B4 Silicon Errata point 12.
- if((ENC28J60_Read(EIR)&EIR_TXERIF))ENC28J60_Write_Op(ENC28J60_BIT_FIELD_CLR,ECON1,ECON1_TXRTS);
- }
-
-
- /*
- 函数功能:从网络获取一个数据包内容
- 函数参数:
- maxlen:数据包最大允许接收长度
- packet:数据包缓存区
- 返 回 值:收到的数据包长度(字节)
- */
- u32 ENC28J60_Packet_Receive(u32 maxlen,u8* packet)
- {
- u32 rxstat;
- u32 len;
- if(ENC28J60_Read(EPKTCNT)==0)return 0; //是否收到数据包?
- //设置接收缓冲器读指针
- ENC28J60_Write(ERDPTL,(NextPacketPtr));
- ENC28J60_Write(ERDPTH,(NextPacketPtr)>>8);
- // 读下一个包的指针
- NextPacketPtr=ENC28J60_Read_Op(ENC28J60_READ_BUF_MEM,0);
- NextPacketPtr|=ENC28J60_Read_Op(ENC28J60_READ_BUF_MEM,0)<<8;
- //读包的长度
- len=ENC28J60_Read_Op(ENC28J60_READ_BUF_MEM,0);
- len|=ENC28J60_Read_Op(ENC28J60_READ_BUF_MEM,0)<<8;
- len-=4; //去掉CRC计数
- //读取接收状态
- rxstat=ENC28J60_Read_Op(ENC28J60_READ_BUF_MEM,0);
- rxstat|=ENC28J60_Read_Op(ENC28J60_READ_BUF_MEM,0)<<8;
- //限制接收长度
- if (len>maxlen-1)len=maxlen-1;
- //检查CRC和符号错误
- // ERXFCON.CRCEN为默认设置,一般我们不需要检查.
- if((rxstat&0x80)==0)len=0;//无效
- else ENC28J60_Read_Buf(len,packet);//从接收缓冲器中复制数据包
- //RX读指针移动到下一个接收到的数据包的开始位置
- //并释放我们刚才读出过的内存
- ENC28J60_Write(ERXRDPTL,(NextPacketPtr));
- ENC28J60_Write(ERXRDPTH,(NextPacketPtr)>>8);
- //递减数据包计数器标志我们已经得到了这个包
- ENC28J60_Write_Op(ENC28J60_BIT_FIELD_SET,ECON2,ECON2_PKTDEC);
- return(len);
- }
-