文章目录
- 一、前言
- 1.1 开发背景
- 1.2 实现的功能
- 1.3 硬件模块组成
- 1.4 ENC28J60网卡介绍
- 1.5 UIP协议栈
- 【1】目标与特点
- 【2】核心组件
- 【3】应用与优势
- 1.6 添加UIP协议栈实现创建WEB服务器步骤
- 1.7 ENC28J60添加UIP协议栈实现创建WEB客户端
- 1.8 ENC28J60移植UIP协议并编写服务器测试示例
- 1.9 ENC28J60移植UIP协议并编写客户端测试示例
- 二、硬件设计
- 2.1 接线说明
- 【1】ENC28J60接线
- 【2】LED灯接线
- 【3】蜂鸣器接线
- 【4】DS18B20接线
- 2.2 代码与服务器交互的控制
- 2.3 修改网页的页面
- 2.4 设置网卡地址
- 2.5 程序运行效果
- 【1】下载程序
- 【2】串口看效果
- 【3】ping板子IP地址
- 【4】打开网页看效果
- 2.6 ENC28J60驱动代码
一、前言
1.1 开发背景
本项目的目的是构建一个基于STM32F103ZET6微控制器的嵌入式Web服务器,以满足远程监控和控制嵌入式设备的需求。随着物联网技术的快速发展,远程监控和控制嵌入式设备变得越来越重要。本项目通过选择STM32F103ZET6作为主控芯片,结合ENC28J60网卡实现网络通信,并移植UIP协议栈来构建轻量级的Web服务器。项目还集成了DS18B20温度传感器、LED灯模块和高电平触发的有源蜂鸣器,以实现远程监控和控制STM32设备端的功能,如LED灯和蜂鸣器的控制,以及设备端温度和RTC时间的显示。这种设计使得用户能够通过浏览器访问服务器,实时查看和控制嵌入式设备,为物联网应用提供了一种灵活、高效的解决方案。
1.2 实现的功能
(1)网络通信搭建:通过STM32F103ZET6微控制器与ENC28J60以太网控制器的集成,利用SPI接口实现数据传输,成功移植UIP轻量级TCP/IP协议栈,从而在嵌入式平台上搭建起一个功能完备的Web服务器。
(2)网页服务:在STM32内部存储一个简易的网页文件,该网页设计用于用户界面展示及交互。当用户使用任何标准的Web浏览器访问此服务器的IP地址时,即可加载并显示该网页内容。
(3)远程控制功能:
- LED灯控制:网页上设有控制按钮或界面元素,允许用户远程开关连接到STM32的LED灯模块,实现远程照明控制演示。
- 蜂鸣器控制:同样通过网页界面,用户能激活或关闭STM32连接的高电平触发的有源蜂鸣器,完成远程报警或信号提示功能的测试。
(4)环境监测与显示:
- 温度监控:集成DS18B20数字温度传感器,周期性采集环境温度数据,并通过Web界面实时显示给用户,提供基本的环境监测能力。
- 实时时钟显示:利用STM32内置的RTC(实时时钟)模块,获取并准确显示当前的时间信息,增强系统的实用性和用户交互体验。
项目不仅实现了从硬件选型、网络配置到软件开发的全过程,还展示了物联网技术在实际应用中的一个小而完整的案例,即通过简单的Web界面远程监控和控制物理设备,体现了STM32平台在物联网领域的灵活性与强大功能。
1.3 硬件模块组成
(1)主控制器模块:
- STM32F103ZET6微控制器:作为系统的核心处理器,负责运行控制程序、管理外设通信、处理网络数据包及执行用户指令。它拥有丰富的外设资源,如SPI、USART、I2C等,支持高速运算和低功耗操作,是实现项目功能的基础。
(2)网络通信模块:
- ENC28J60以太网控制器:通过SPI接口与STM32连接,提供物理层和数据链路层的网络功能,实现与外部网络的连接。它是项目中实现Web服务器功能的关键组件,支持以太网数据包的收发,使嵌入式设备能够接入互联网。
(3)温度监测模块:
- DS18B20数字温度传感器:通过单总线(One-Wire)接口与STM32通信,用于精确测量环境温度。该传感器具有体积小、精度高、直接数字输出等特点,非常适合嵌入式系统中的温度监控应用。
(4)输出控制模块:
- LED灯模块:作为基本的输出设备,通过GPIO(通用输入输出端口)直接由STM32控制,用于响应用户的控制命令,如点亮或熄灭,以直观展示控制效果。
- 有源蜂鸣器:通过高电平触发的方式连接至STM32的一个GPIO引脚,根据控制信号产生声音,实现报警或状态反馈功能。
(5)电源模块:为确保所有硬件组件稳定工作,需要一个合适的电源供应模块,为STM32微控制器、ENC28J60网卡、传感器及外围电路提供稳定的电压和电流。
1.4 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以其集成度高、接口灵活、成本效益好等特点,成为了许多嵌入式系统设计中实现网络连接的优选解决方案。
1.5 UIP协议栈
UIP(Micro IP)协议栈是一种专门为资源受限的嵌入式系统设计的轻量级TCP/IP协议栈。它最初由Adam Dunkels在SICS(瑞典计算机科学研究所)开发,目的是使即便是8位微控制器也能轻松实现网络通信,而不必负担全尺寸TCP/IP协议栈的开销。下面是UIP协议栈的详细介绍:
【1】目标与特点
-
轻量化设计:UIP的核心目标是在保持TCP/IP协议核心功能的同时,尽可能减小代码大小和内存占用。其代码量通常仅为几千字节,RAM消耗最低可达几百字节,这使得它非常适合于微控制器等资源有限的环境。
-
事件驱动编程:UIP采用了事件驱动的编程模型,而不是基于多线程或中断驱动的方法。这种设计减少了对内存的需求,并且简化了程序逻辑,使得协议栈更加易于理解和维护。
-
协议支持:尽管精简,UIP仍实现了网络层(IP)、网络互联层(ARP)、传输层(TCP)和部分应用层(如ICMP ping)的基本功能。它还支持UDP,尽管可能不如TCP那样全面。对于不常用的TCP/IP特性,如窗口缩放、时间戳等,则被省略以减少资源消耗。
-
无操作系统依赖:UIP设计为可以在“裸机”环境下运行,即不需要操作系统的支持,这使得它非常灵活,可以部署在各种嵌入式平台上。
【2】核心组件
- IP层:负责数据包的路由和分片重组,实现基本的网络层功能。
- ARP层:地址解析协议,用于将IP地址转换为MAC地址,确保数据包能够正确送达物理网络层。
- ICMP层:因特网控制消息协议,主要用于网络诊断,如ping命令的实现。
- TCP层:传输控制协议,用于建立可靠的、面向连接的数据传输服务,尽管在UIP中进行了大幅简化以适应嵌入式环境。
- 内存管理:由于嵌入式系统内存资源有限,UIP实现了高效的内存块管理和缓冲区分配机制。
【3】应用与优势
- 应用广泛:UIP被广泛应用于物联网设备、智能家居、传感器网络、嵌入式Web服务器等场景。
- 易于移植:由于其代码结构清晰、依赖少,UIP很容易被移植到不同的微控制器平台上。
- 资源效率:在保证基本网络功能的同时,极大地节省了系统资源,使得低成本、低功耗的设备也能实现网络连接。
1.6 添加UIP协议栈实现创建WEB服务器步骤
1.7 ENC28J60添加UIP协议栈实现创建WEB客户端
1.8 ENC28J60移植UIP协议并编写服务器测试示例
1.9 ENC28J60移植UIP协议并编写客户端测试示例
二、硬件设计
2.1 接线说明
【1】ENC28J60接线
【2】LED灯接线
【3】蜂鸣器接线
【4】DS18B20接线
2.2 代码与服务器交互的控制
这是判断网页下发的请求。
2.3 修改网页的页面
这个是写好的网页模版:
代码如下:
<!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
这个工具。
替换这个数组就行了:
2.4 设置网卡地址
因为板子是静态IP,为了方便通信,ENC28J60通过网线直接与电脑网口连接。 设置固定的IP地址。
2.5 程序运行效果
【1】下载程序
【2】串口看效果
【3】ping板子IP地址
【4】打开网页看效果
2.6 ENC28J60驱动代码
#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; //PAGPIOA->CRL&=0X0000FFFF; //清除寄存器GPIOA->CRL|=0XB8B30000;GPIOA->ODR|=0XF<<4; // 上拉--输出高电平GPIOA->ODR&=~(1<<5);RCC->APB2ENR|=1<<8; //2 3 4 5 6 7 8GPIOG->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; //NSSSPI1->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=0SPI1->CR1&=~(1<<0); //数据采样从第1个时间边沿开始,CPHA=0 SPI1->CR1|=1<<6; //SPI设备使能ENC28J60_RST=0; //复位ENC28J60DelayMs(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:// 最大帧长度 1500ENC28J60_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);
}