基于STM32的轻量级Web服务器设计

举报
DS小龙哥 发表于 2024/06/12 15:59:20 2024/06/12
【摘要】 本项目的目的是构建一个基于STM32F103ZET6微控制器的嵌入式Web服务器,以满足远程监控和控制嵌入式设备的需求。

一、前言

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 修改网页的页面

这个是写好的网页模版:

代码如下:


html
<!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驱动代码


cpp
#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);
}
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。