一、前言
STM32+ENC28J60+UIP協(xié)議棧實(shí)現(xiàn)WEB服務(wù)器
1.1 開發(fā)背景
本項(xiàng)目的目的是構(gòu)建一個(gè)基于STM32F103ZET6微控制器的嵌入式Web服務(wù)器,以滿足遠(yuǎn)程監(jiān)控和控制嵌入式設(shè)備的需求。隨著物聯(lián)網(wǎng)技術(shù)的快速發(fā)展,遠(yuǎn)程監(jiān)控和控制嵌入式設(shè)備變得越來越重要。本項(xiàng)目通過選擇STM32F103ZET6作為主控芯片,結(jié)合ENC28J60網(wǎng)卡實(shí)現(xiàn)網(wǎng)絡(luò)通信,并移植UIP協(xié)議棧來構(gòu)建輕量級的Web服務(wù)器。
項(xiàng)目還集成了DS18B20溫度傳感器、LED燈模塊和高電平觸發(fā)的有源蜂鳴器,以實(shí)現(xiàn)遠(yuǎn)程監(jiān)控和控制STM32設(shè)備端的功能,如LED燈和蜂鳴器的控制,以及設(shè)備端溫度和RTC時(shí)間的顯示。這種設(shè)計(jì)使得用戶能夠通過瀏覽器訪問服務(wù)器,實(shí)時(shí)查看和控制嵌入式設(shè)備,為物聯(lián)網(wǎng)應(yīng)用提供了一種靈活、高效的解決方案。
1.2 實(shí)現(xiàn)的功能
(1)網(wǎng)絡(luò)通信搭建:通過STM32F103ZET6微控制器與ENC28J60以太網(wǎng)控制器的集成,利用SPI接口實(shí)現(xiàn)數(shù)據(jù)傳輸,成功移植UIP輕量級TCP/IP協(xié)議棧,從而在嵌入式平臺(tái)上搭建起一個(gè)功能完備的Web服務(wù)器。
(2)網(wǎng)頁服務(wù):在STM32內(nèi)部存儲(chǔ)一個(gè)簡易的網(wǎng)頁文件,該網(wǎng)頁設(shè)計(jì)用于用戶界面展示及交互。當(dāng)用戶使用任何標(biāo)準(zhǔn)的Web瀏覽器訪問此服務(wù)器的IP地址時(shí),即可加載并顯示該網(wǎng)頁內(nèi)容。
(3)遠(yuǎn)程控制功能:
- LED燈控制:網(wǎng)頁上設(shè)有控制按鈕或界面元素,允許用戶遠(yuǎn)程開關(guān)連接到STM32的LED燈模塊,實(shí)現(xiàn)遠(yuǎn)程照明控制演示。
- 蜂鳴器控制:同樣通過網(wǎng)頁界面,用戶能激活或關(guān)閉STM32連接的高電平觸發(fā)的有源蜂鳴器,完成遠(yuǎn)程報(bào)警或信號提示功能的測試。
(4)環(huán)境監(jiān)測與顯示:
- 溫度監(jiān)控:集成DS18B20數(shù)字溫度傳感器,周期性采集環(huán)境溫度數(shù)據(jù),并通過Web界面實(shí)時(shí)顯示給用戶,提供基本的環(huán)境監(jiān)測能力。
- 實(shí)時(shí)時(shí)鐘顯示:利用STM32內(nèi)置的RTC(實(shí)時(shí)時(shí)鐘)模塊,獲取并準(zhǔn)確顯示當(dāng)前的時(shí)間信息,增強(qiáng)系統(tǒng)的實(shí)用性和用戶交互體驗(yàn)。
項(xiàng)目不僅實(shí)現(xiàn)了從硬件選型、網(wǎng)絡(luò)配置到軟件開發(fā)的全過程,還展示了物聯(lián)網(wǎng)技術(shù)在實(shí)際應(yīng)用中的一個(gè)小而完整的案例,即通過簡單的Web界面遠(yuǎn)程監(jiān)控和控制物理設(shè)備,體現(xiàn)了STM32平臺(tái)在物聯(lián)網(wǎng)領(lǐng)域的靈活性與強(qiáng)大功能。
1.3 硬件模塊組成
(1)主控制器模塊:
- STM32F103ZET6微控制器:作為系統(tǒng)的核心處理器,負(fù)責(zé)運(yùn)行控制程序、管理外設(shè)通信、處理網(wǎng)絡(luò)數(shù)據(jù)包及執(zhí)行用戶指令。它擁有豐富的外設(shè)資源,如SPI、USART、I2C等,支持高速運(yùn)算和低功耗操作,是實(shí)現(xiàn)項(xiàng)目功能的基礎(chǔ)。
(2)網(wǎng)絡(luò)通信模塊:
- ENC28J60以太網(wǎng)控制器:通過SPI接口與STM32連接,提供物理層和數(shù)據(jù)鏈路層的網(wǎng)絡(luò)功能,實(shí)現(xiàn)與外部網(wǎng)絡(luò)的連接。它是項(xiàng)目中實(shí)現(xiàn)Web服務(wù)器功能的關(guān)鍵組件,支持以太網(wǎng)數(shù)據(jù)包的收發(fā),使嵌入式設(shè)備能夠接入互聯(lián)網(wǎng)。
(3)溫度監(jiān)測模塊:
- DS18B20數(shù)字溫度傳感器:通過單總線(One-Wire)接口與STM32通信,用于精確測量環(huán)境溫度。該傳感器具有體積小、精度高、直接數(shù)字輸出等特點(diǎn),非常適合嵌入式系統(tǒng)中的溫度監(jiān)控應(yīng)用。
(4)輸出控制模塊:
- LED燈模塊:作為基本的輸出設(shè)備,通過GPIO(通用輸入輸出端口)直接由STM32控制,用于響應(yīng)用戶的控制命令,如點(diǎn)亮或熄滅,以直觀展示控制效果。
- 有源蜂鳴器:通過高電平觸發(fā)的方式連接至STM32的一個(gè)GPIO引腳,根據(jù)控制信號產(chǎn)生聲音,實(shí)現(xiàn)報(bào)警或狀態(tài)反饋功能。
(5)電源模塊:為確保所有硬件組件穩(wěn)定工作,需要一個(gè)合適的電源供應(yīng)模塊,為STM32微控制器、ENC28J60網(wǎng)卡、傳感器及外圍電路提供穩(wěn)定的電壓和電流。
1.4 ENC28J60網(wǎng)卡介紹
ENC28J60是一款集成MAC(Media Access Control,媒體訪問控制)和10BASE-T PHY(物理層)的以太網(wǎng)控制器,特別適合于嵌入式系統(tǒng)和微控制器應(yīng)用。
以下是ENC28J60網(wǎng)卡的一些關(guān)鍵特性與介紹:
(1)接口類型:它使用SPI(Serial Peripheral Interface,串行外設(shè)接口)作為與外部微控制器通信的主要方式,這使得它能夠以較少的引腳數(shù)(通常為4或5條線)與諸如STM32、Arduino等微控制器連接,降低了硬件設(shè)計(jì)的復(fù)雜度。
(2)兼容性:ENC28J60兼容IEEE 802.3標(biāo)準(zhǔn),這意味著它可以無縫地融入標(biāo)準(zhǔn)以太網(wǎng)網(wǎng)絡(luò)環(huán)境中。它支持10Mbps的傳輸速率,適用于不需要高速網(wǎng)絡(luò)連接的應(yīng)用場景。
(3)集成功能:除了MAC和PHY層之外,ENC28J60還集成了其他一些功能,如緩沖區(qū)管理、DMA(Direct Memory Access,直接內(nèi)存訪問)支持、以及對多種網(wǎng)絡(luò)幀類型的支持,包括廣播、多播和單播。
(4)網(wǎng)絡(luò)功能:能夠?qū)崿F(xiàn)完整的以太網(wǎng)數(shù)據(jù)包的發(fā)送和接收,支持IP、TCP、UDP等網(wǎng)絡(luò)協(xié)議棧。開發(fā)者通常會(huì)結(jié)合LwIP(Lightweight IP)這樣的輕量級TCP/IP協(xié)議棧來實(shí)現(xiàn)網(wǎng)絡(luò)通信。
(5)物理層特性:支持10BASE-T標(biāo)準(zhǔn),可以通過RJ45接口連接雙絞線(UTP),支持自動(dòng)極性和交叉檢測,可工作在全雙工或半雙工模式下。
(6)功耗與封裝:ENC28J60設(shè)計(jì)考慮到了低功耗應(yīng)用的需求,適合電池供電設(shè)備。它通常采用SSOP(Shrink Small Outline Package)或QFN(Quad Flat No-Leads)等小型封裝形式,便于在空間受限的設(shè)計(jì)中使用。
(7)靈活性與應(yīng)用:由于其SPI接口和相對較低的成本,ENC28J60被廣泛應(yīng)用于各種嵌入式項(xiàng)目中,如物聯(lián)網(wǎng)設(shè)備、智能家居、工業(yè)控制、遠(yuǎn)程監(jiān)控系統(tǒng)等。
ENC28J60以其集成度高、接口靈活、成本效益好等特點(diǎn),成為了許多嵌入式系統(tǒng)設(shè)計(jì)中實(shí)現(xiàn)網(wǎng)絡(luò)連接的優(yōu)選解決方案。
1.5 UIP協(xié)議棧
UIP(Micro IP)協(xié)議棧是一種專門為資源受限的嵌入式系統(tǒng)設(shè)計(jì)的輕量級TCP/IP協(xié)議棧。它最初由Adam Dunkels在SICS(瑞典計(jì)算機(jī)科學(xué)研究所)開發(fā),目的是使即便是8位微控制器也能輕松實(shí)現(xiàn)網(wǎng)絡(luò)通信,而不必負(fù)擔(dān)全尺寸TCP/IP協(xié)議棧的開銷。下面是UIP協(xié)議棧的詳細(xì)介紹:
【1】目標(biāo)與特點(diǎn)
- 輕量化設(shè)計(jì):UIP的核心目標(biāo)是在保持TCP/IP協(xié)議核心功能的同時(shí),盡可能減小代碼大小和內(nèi)存占用。其代碼量通常僅為幾千字節(jié),RAM消耗最低可達(dá)幾百字節(jié),這使得它非常適合于微控制器等資源有限的環(huán)境。
- 事件驅(qū)動(dòng)編程:UIP采用了事件驅(qū)動(dòng)的編程模型,而不是基于多線程或中斷驅(qū)動(dòng)的方法。這種設(shè)計(jì)減少了對內(nèi)存的需求,并且簡化了程序邏輯,使得協(xié)議棧更加易于理解和維護(hù)。
- 協(xié)議支持:盡管精簡,UIP仍實(shí)現(xiàn)了網(wǎng)絡(luò)層(IP)、網(wǎng)絡(luò)互聯(lián)層(ARP)、傳輸層(TCP)和部分應(yīng)用層(如ICMP ping)的基本功能。它還支持UDP,盡管可能不如TCP那樣全面。對于不常用的TCP/IP特性,如窗口縮放、時(shí)間戳等,則被省略以減少資源消耗。
- 無操作系統(tǒng)依賴:UIP設(shè)計(jì)為可以在“裸機(jī)”環(huán)境下運(yùn)行,即不需要操作系統(tǒng)的支持,這使得它非常靈活,可以部署在各種嵌入式平臺(tái)上。
【2】核心組件
- IP層:負(fù)責(zé)數(shù)據(jù)包的路由和分片重組,實(shí)現(xiàn)基本的網(wǎng)絡(luò)層功能。
- ARP層:地址解析協(xié)議,用于將IP地址轉(zhuǎn)換為MAC地址,確保數(shù)據(jù)包能夠正確送達(dá)物理網(wǎng)絡(luò)層。
- ICMP層:因特網(wǎng)控制消息協(xié)議,主要用于網(wǎng)絡(luò)診斷,如ping命令的實(shí)現(xiàn)。
- TCP層:傳輸控制協(xié)議,用于建立可靠的、面向連接的數(shù)據(jù)傳輸服務(wù),盡管在UIP中進(jìn)行了大幅簡化以適應(yīng)嵌入式環(huán)境。
- 內(nèi)存管理:由于嵌入式系統(tǒng)內(nèi)存資源有限,UIP實(shí)現(xiàn)了高效的內(nèi)存塊管理和緩沖區(qū)分配機(jī)制。
【3】應(yīng)用與優(yōu)勢
- 應(yīng)用廣泛:UIP被廣泛應(yīng)用于物聯(lián)網(wǎng)設(shè)備、智能家居、傳感器網(wǎng)絡(luò)、嵌入式Web服務(wù)器等場景。
- 易于移植:由于其代碼結(jié)構(gòu)清晰、依賴少,UIP很容易被移植到不同的微控制器平臺(tái)上。
- 資源效率:在保證基本網(wǎng)絡(luò)功能的同時(shí),極大地節(jié)省了系統(tǒng)資源,使得低成本、低功耗的設(shè)備也能實(shí)現(xiàn)網(wǎng)絡(luò)連接。
1.6 添加UIP協(xié)議棧實(shí)現(xiàn)創(chuàng)建WEB服務(wù)器步驟
1.7 ENC28J60添加UIP協(xié)議棧實(shí)現(xiàn)創(chuàng)建WEB客戶端
1.8 ENC28J60移植UIP協(xié)議并編寫服務(wù)器測試示例
1.9 ENC28J60移植UIP協(xié)議并編寫客戶端測試示例
二、硬件設(shè)計(jì)
2.1 接線說明
【1】ENC28J60接線
【2】LED燈接線
【3】蜂鳴器接線
【4】DS18B20接線
2.2 代碼與服務(wù)器交互的控制
這是判斷網(wǎng)頁下發(fā)的請求。
2.3 修改網(wǎng)頁的頁面
這個(gè)是寫好的網(wǎng)頁模版:
代碼如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>WEB服務(wù)器設(shè)計(jì)</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服務(wù)器設(shè)計(jì)</h1>
<h1>微信公眾號:DS小龍哥嵌入式技術(shù)資訊</h1>
<h1>這是基于ENC28J60+UIP協(xié)議棧設(shè)計(jì)的WEB服務(wù)器</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">時(shí)間:</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){
//獲取后臺(tái)的數(shù)據(jù)
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";
}
}
}
}
}
//定時(shí)器
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>
將這個(gè)文件轉(zhuǎn)為C語言數(shù)組放到單片機(jī)代碼工程里就行了。
如何轉(zhuǎn)換? 用winhex
這個(gè)工具。
替換這個(gè)數(shù)組就行了:
2.4 設(shè)置網(wǎng)卡地址
因?yàn)榘遄邮庆o態(tài)IP,為了方便通信,ENC28J60通過網(wǎng)線直接與電腦網(wǎng)口連接。 設(shè)置固定的IP地址。
2.5 程序運(yùn)行效果
【1】下載程序
【2】串口看效果
【3】ping板子IP地址
【4】打開網(wǎng)頁看效果
2.6 ENC28J60驅(qū)動(dòng)代碼
#include "delay.h"
#include <stdio.h>
#include "enc28j60.h"
/*
以下是ENC28J60驅(qū)動(dòng)移植接口:
MISO--->PA6----主機(jī)輸入
MOSI--->PA7----主機(jī)輸出
SCLK--->PA5----時(shí)鐘信號
CS----->PA4----片選
RESET-->PG15---復(fù)位
*/
#define ENC28J60_CS PAout(4) //ENC28J60片選信號
#define ENC28J60_RST PGout(15) //ENC28J60復(fù)位信號
#define ENC28J60_MOSI PAout(7) //輸出
#define ENC28J60_MISO PAin(6) //輸入
#define ENC28J60_SCLK PAout(5) //時(shí)鐘線
static u8 ENC28J60BANK;
static u32 NextPacketPtr;
/*
函數(shù)功能:底層SPI接口收發(fā)一個(gè)字節(jié)
說 明:模擬SPI時(shí)序,ENC28J60時(shí)鐘線空閑電平為低電平,在第一個(gè)下降沿采集數(shù)據(jù)
*/
u8 ENC28J60_SPI_ReadWriteOneByte(u8 tx_data)
{
u16 cnt=0;
while((SPI1->SR&1<<1)==0) //等待發(fā)送區(qū)空--等待發(fā)送緩沖為空
{
cnt++;
if(cnt>=65530)return 0; //超時(shí)退出 u16=2個(gè)字節(jié)
}
SPI1->DR=tx_data; //發(fā)送一個(gè)byte
cnt=0;
while((SPI1->SR&1<<0)==0) //等待接收完一個(gè)byte
{
cnt++;
if(cnt>=65530)return 0; //超時(shí)退出
}
return SPI1->DR; //返回收到的數(shù)據(jù)
}
/*
函數(shù)功能:復(fù)位ENC28J60,包括SPI初始化/IO初始化等
MISO--->PA6----主機(jī)輸入
MOSI--->PA7----主機(jī)輸出
SCLK--->PA5----時(shí)鐘信號
CS----->PA4----片選
RESET-->PG15---復(fù)位
*/
void ENC28J60_Reset(void)
{
/*開啟時(shí)鐘*/
RCC->APB2ENR|=1<<12; //開啟SPI1時(shí)鐘
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位數(shù)據(jù)幀格式進(jìn)行發(fā)送/接收;
SPI1->CR1|=0<<10; //全雙工(發(fā)送和接收);
SPI1->CR1|=1<<9; //啟用軟件從設(shè)備管理
SPI1->CR1|=1<<8; //NSS
SPI1->CR1|=0<<7; //幀格式,先發(fā)送高位
SPI1->CR1|=0x1<<3;//當(dāng)總線頻率為36MHZ時(shí),SPI速度為18MHZ,高速。
SPI1->CR1|=1<<2; //配置為主設(shè)備
SPI1->CR1|=1<<1; //空閑狀態(tài)時(shí), SCK保持高電平。
SPI1->CR1|=1<<0; //數(shù)據(jù)采樣從第二個(gè)時(shí)鐘邊沿開始。
SPI1->CR1|=1<<6; //開啟SPI設(shè)備。
//針對ENC28J60的特點(diǎn)(SCK空閑為低電平)修改SPI的設(shè)置
SPI1->CR1&=~(1<<6); //SPI設(shè)備失能
SPI1->CR1&=~(1<<1); //空閑模式下SCK為0 CPOL=0
SPI1->CR1&=~(1<<0); //數(shù)據(jù)采樣從第1個(gè)時(shí)間邊沿開始,CPHA=0
SPI1->CR1|=1<<6; //SPI設(shè)備使能
ENC28J60_RST=0; //復(fù)位ENC28J60
DelayMs(10);
ENC28J60_RST=1; //復(fù)位結(jié)束
DelayMs(10);
}
/*
函數(shù)功能:讀取ENC28J60寄存器(帶操作碼)
參 數(shù):op:操作碼
addr:寄存器地址/參數(shù)
返 回 值:讀到的數(shù)據(jù)
*/
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寄存器,則第二次讀到的數(shù)據(jù)才是正確的,見手冊29頁
if(addr&0x80)dat=ENC28J60_SPI_ReadWriteOneByte(0xFF);
ENC28J60_CS=1;
return dat;
}
/*
函數(shù)功能:讀取ENC28J60寄存器(帶操作碼)
參 數(shù):
op:操作碼
addr:寄存器地址
data:參數(shù)
*/
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;
}
/*
函數(shù)功能:讀取ENC28J60接收緩存數(shù)據(jù)
參 數(shù):
len:要讀取的數(shù)據(jù)長度
data:輸出數(shù)據(jù)緩存區(qū)(末尾自動(dòng)添加結(jié)束符)
*/
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='?';
ENC28J60_CS=1;
}
/*
函數(shù)功能:向ENC28J60寫發(fā)送緩存數(shù)據(jù)
參 數(shù):
len:要寫入的數(shù)據(jù)長度
data:數(shù)據(jù)緩存區(qū)
*/
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;
}
/*
函數(shù)功能:設(shè)置ENC28J60寄存器Bank
參 數(shù):
ban:要設(shè)置的bank
*/
void ENC28J60_Set_Bank(u8 bank)
{
if((bank&BANK_MASK)!=ENC28J60BANK)//和當(dāng)前bank不一致的時(shí)候,才設(shè)置
{
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);
}
}
/*
函數(shù)功能:讀取ENC28J60指定寄存器
參 數(shù):addr:寄存器地址
返 回 值:讀到的數(shù)據(jù)
*/
u8 ENC28J60_Read(u8 addr)
{
ENC28J60_Set_Bank(addr);//設(shè)置BANK
return ENC28J60_Read_Op(ENC28J60_READ_CTRL_REG,addr);
}
/*
函數(shù)功能:向ENC28J60指定寄存器寫數(shù)據(jù)
參 數(shù):
addr:寄存器地址
data:要寫入的數(shù)據(jù)
*/
void ENC28J60_Write(u8 addr,u8 data)
{
ENC28J60_Set_Bank(addr);
ENC28J60_Write_Op(ENC28J60_WRITE_CTRL_REG,addr,data);
}
/*
函數(shù)功能:向ENC28J60的PHY寄存器寫入數(shù)據(jù)
參 數(shù):
addr:寄存器地址
data:要寫入的數(shù)據(jù)
*/
void ENC28J60_PHY_Write(u8 addr,u32 data)
{
u16 retry=0;
ENC28J60_Write(MIREGADR,addr); //設(shè)置PHY寄存器地址
ENC28J60_Write(MIWRL,data); //寫入數(shù)據(jù)
ENC28J60_Write(MIWRH,data>>8);
while((ENC28J60_Read(MISTAT)&MISTAT_BUSY)&&retry<0XFFF)retry++;//等待寫入PHY結(jié)束
}
/*
函數(shù)功能:初始化ENC28J60
參 數(shù):macaddr:MAC地址
返 回 值:
0,初始化成功;
1,初始化失敗;
*/
u8 ENC28J60_Init(u8* macaddr)
{
u16 retry=0;
ENC28J60_Reset(); //復(fù)位底層引腳接口
ENC28J60_Write_Op(ENC28J60_SOFT_RESET,0,ENC28J60_SOFT_RESET);//軟件復(fù)位
while(!(ENC28J60_Read(ESTAT)&ESTAT_CLKRDY)&&retry<500)//等待時(shí)鐘穩(wěn)定
{
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 設(shè)置接收緩沖區(qū)地址 8K字節(jié)容量
NextPacketPtr=RXSTART_INIT;
// Rx start
//接收緩沖器由一個(gè)硬件管理的循環(huán)FIFO 緩沖器構(gòu)成。
//寄存器對ERXSTH:ERXSTL 和ERXNDH:ERXNDL 作
//為指針,定義緩沖器的容量和其在存儲(chǔ)器中的位置。
//ERXST和ERXND指向的字節(jié)均包含在FIFO緩沖器內(nèi)。
//當(dāng)從以太網(wǎng)接口接收數(shù)據(jù)字節(jié)時(shí),這些字節(jié)被順序?qū)懭?
//接收緩沖器。 但是當(dāng)寫入由ERXND 指向的存儲(chǔ)單元
//后,硬件會(huì)自動(dòng)將接收的下一字節(jié)寫入由ERXST 指向
//的存儲(chǔ)單元。 因此接收硬件將不會(huì)寫入FIFO 以外的單
//元。
//設(shè)置接收起始字節(jié)
ENC28J60_Write(ERXSTL,RXSTART_INIT&0xFF);
ENC28J60_Write(ERXSTH,RXSTART_INIT>>8);
//ERXWRPTH:ERXWRPTL 寄存器定義硬件向FIFO 中
//的哪個(gè)位置寫入其接收到的字節(jié)。 指針是只讀的,在成
//功接收到一個(gè)數(shù)據(jù)包后,硬件會(huì)自動(dòng)更新指針。 指針可
//用于判斷FIFO 內(nèi)剩余空間的大小 8K-1500。
//設(shè)置接收讀指針字節(jié)
ENC28J60_Write(ERXRDPTL,RXSTART_INIT&0xFF);
ENC28J60_Write(ERXRDPTH,RXSTART_INIT>>8);
//設(shè)置接收結(jié)束字節(jié)
ENC28J60_Write(ERXNDL,RXSTOP_INIT&0xFF);
ENC28J60_Write(ERXNDH,RXSTOP_INIT>>8);
//設(shè)置發(fā)送起始字節(jié)
ENC28J60_Write(ETXSTL,TXSTART_INIT&0xFF);
ENC28J60_Write(ETXSTH,TXSTART_INIT>>8);
//設(shè)置發(fā)送結(jié)束字節(jié)
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:單播過濾器使能位
//當(dāng)ANDOR = 1 時(shí):
//1 = 目標(biāo)地址與本地MAC 地址不匹配的數(shù)據(jù)包將被丟棄
//0 = 禁止過濾器
//當(dāng)ANDOR = 0 時(shí):
//1 = 目標(biāo)地址與本地MAC 地址匹配的數(shù)據(jù)包會(huì)被接受
//0 = 禁止過濾器
//CRCEN:后過濾器CRC 校驗(yàn)使能位
//1 = 所有CRC 無效的數(shù)據(jù)包都將被丟棄
//0 = 不考慮CRC 是否有效
//PMEN:格式匹配過濾器使能位
//當(dāng)ANDOR = 1 時(shí):
//1 = 數(shù)據(jù)包必須符合格式匹配條件,否則將被丟棄
//0 = 禁止過濾器
//當(dāng)ANDOR = 0 時(shí):
//1 = 符合格式匹配條件的數(shù)據(jù)包將被接受
//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 接收數(shù)據(jù)包
//0 = 禁止數(shù)據(jù)包接收
//bit 3 TXPAUS:暫??刂茙l(fā)送使能位
//1 = 允許MAC 發(fā)送暫??刂茙ㄓ糜谌p工模式下的流量控制)
//0 = 禁止暫停幀發(fā)送
//bit 2 RXPAUS:暫停控制幀接收使能位
//1 = 當(dāng)接收到暫??刂茙瑫r(shí),禁止發(fā)送(正常操作)
//0 = 忽略接收到的暫??刂茙?
ENC28J60_Write(MACON1,MACON1_MARXEN|MACON1_TXPAUS|MACON1_RXPAUS);
// bring MAC out of reset
//將MACON2 中的MARST 位清零,使MAC 退出復(fù)位狀態(tài)。
ENC28J60_Write(MACON2,0x00);
// enable automatic padding to 60bytes and CRC operations
//bit 7-5 PADCFG2:PACDFG0:自動(dòng)填充和CRC 配置位
//111 = 用0 填充所有短幀至64 字節(jié)長,并追加一個(gè)有效的CRC
//110 = 不自動(dòng)填充短幀
//101 = MAC 自動(dòng)檢測具有8100h 類型字段的VLAN 協(xié)議幀,并自動(dòng)填充到64 字節(jié)長。如果不
//是VLAN 幀,則填充至60 字節(jié)長。填充后還要追加一個(gè)有效的CRC
//100 = 不自動(dòng)填充短幀
//011 = 用0 填充所有短幀至64 字節(jié)長,并追加一個(gè)有效的CRC
//010 = 不自動(dòng)填充短幀
//001 = 用0 填充所有短幀至60 字節(jié)長,并追加一個(gè)有效的CRC
//000 = 不自動(dòng)填充短幀
//bit 4 TXCRCEN:發(fā)送CRC 使能位
//1 = 不管PADCFG如何,MAC都會(huì)在發(fā)送幀的末尾追加一個(gè)有效的CRC。 如果PADCFG規(guī)定要
//追加有效的CRC,則必須將TXCRCEN 置1。
//0 = MAC不會(huì)追加CRC。 檢查最后4 個(gè)字節(jié),如果不是有效的CRC 則報(bào)告給發(fā)送狀態(tài)向量。
//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)
//配置非背對背包間間隔寄存器的低字節(jié)
//MAIPGL。 大多數(shù)應(yīng)用使用12h 編程該寄存器。
//如果使用半雙工模式,應(yīng)編程非背對背包間間隔
//寄存器的高字節(jié)MAIPGH。 大多數(shù)應(yīng)用使用0Ch
//編程該寄存器。
ENC28J60_Write(MAIPGL,0x12);
ENC28J60_Write(MAIPGH,0x0C);
// set inter-frame gap (back-to-back)
//配置背對背包間間隔寄存器MABBIPG。當(dāng)使用
//全雙工模式時(shí),大多數(shù)應(yīng)用使用15h 編程該寄存
//器,而使用半雙工模式時(shí)則使用12h 進(jìn)行編程。
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
//設(shè)置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 禁止環(huán)回
//HDLDIS:PHY 半雙工環(huán)回禁止位
//當(dāng)PHCON1.PDPXMD = 1 或PHCON1.PLOOPBK = 1 時(shí):
//此位可被忽略。
//當(dāng)PHCON1.PDPXMD = 0 且PHCON1.PLOOPBK = 0 時(shí):
//1 = 要發(fā)送的數(shù)據(jù)僅通過雙絞線接口發(fā)出
//0 = 要發(fā)送的數(shù)據(jù)會(huì)環(huán)回到MAC 并通過雙絞線接口發(fā)出
ENC28J60_PHY_Write(PHCON2,PHCON2_HDLDIS);
// switch to bank 0
//ECON1 寄存器
//寄存器3-1 所示為ECON1 寄存器,它用于控制
//ENC28J60 的主要功能。 ECON1 中包含接收使能、發(fā)
//送請求、DMA 控制和存儲(chǔ)區(qū)選擇位。
ENC28J60_Set_Bank(ECON1);
// enable interrutps
//EIE: 以太網(wǎng)中斷允許寄存器
//bit 7 INTIE: 全局INT 中斷允許位
//1 = 允許中斷事件驅(qū)動(dòng)INT 引腳
//0 = 禁止所有INT 引腳的活動(dòng)(引腳始終被驅(qū)動(dòng)為高電平)
//bit 6 PKTIE: 接收數(shù)據(jù)包待處理中斷允許位
//1 = 允許接收數(shù)據(jù)包待處理中斷
//0 = 禁止接收數(shù)據(jù)包待處理中斷
ENC28J60_Write_Op(ENC28J60_BIT_FIELD_SET,EIE,EIE_INTIE|EIE_PKTIE);
// enable packet reception
//bit 2 RXEN:接收使能位
//1 = 通過當(dāng)前過濾器的數(shù)據(jù)包將被寫入接收緩沖器
//0 = 忽略所有接收的數(shù)據(jù)包
ENC28J60_Write_Op(ENC28J60_BIT_FIELD_SET,ECON1,ECON1_RXEN);
if(ENC28J60_Read(MAADR5)== macaddr[0])return 0;//初始化成功
else return 1;
}
/*
函數(shù)功能:讀取EREVID
參 數(shù):
*/
u8 ENC28J60_Get_EREVID(void)
{
//在EREVID 內(nèi)也存儲(chǔ)了版本信息。 EREVID 是一個(gè)只讀控
//制寄存器,包含一個(gè)5 位標(biāo)識(shí)符,用來標(biāo)識(shí)器件特定硅片
//的版本號
return ENC28J60_Read(EREVID);
}
/*
函數(shù)功能:通過ENC28J60發(fā)送數(shù)據(jù)包到網(wǎng)絡(luò)
參 數(shù):
len :數(shù)據(jù)包大小
packet:數(shù)據(jù)包
*/
void ENC28J60_Packet_Send(u32 len,u8* packet)
{
//設(shè)置發(fā)送緩沖區(qū)地址寫指針入口
ENC28J60_Write(EWRPTL,TXSTART_INIT&0xFF);
ENC28J60_Write(EWRPTH,TXSTART_INIT>>8);
//設(shè)置TXND指針,以對應(yīng)給定的數(shù)據(jù)包大小
ENC28J60_Write(ETXNDL,(TXSTART_INIT+len)&0xFF);
ENC28J60_Write(ETXNDH,(TXSTART_INIT+len)>>8);
//寫每包控制字節(jié)(0x00表示使用macon3的設(shè)置)
ENC28J60_Write_Op(ENC28J60_WRITE_BUF_MEM,0,0x00);
//復(fù)制數(shù)據(jù)包到發(fā)送緩沖區(qū)
//printf("len:%drn",len); //監(jiān)視發(fā)送數(shù)據(jù)長度
ENC28J60_Write_Buf(len,packet);
//發(fā)送數(shù)據(jù)到網(wǎng)絡(luò)
ENC28J60_Write_Op(ENC28J60_BIT_FIELD_SET,ECON1,ECON1_TXRTS);
//復(fù)位發(fā)送邏輯的問題。參見Rev. B4 Silicon Errata point 12.
if((ENC28J60_Read(EIR)&EIR_TXERIF))ENC28J60_Write_Op(ENC28J60_BIT_FIELD_CLR,ECON1,ECON1_TXRTS);
}
/*
函數(shù)功能:從網(wǎng)絡(luò)獲取一個(gè)數(shù)據(jù)包內(nèi)容
函數(shù)參數(shù):
maxlen:數(shù)據(jù)包最大允許接收長度
packet:數(shù)據(jù)包緩存區(qū)
返 回 值:收到的數(shù)據(jù)包長度(字節(jié))
*/
u32 ENC28J60_Packet_Receive(u32 maxlen,u8* packet)
{
u32 rxstat;
u32 len;
if(ENC28J60_Read(EPKTCNT)==0)return 0; //是否收到數(shù)據(jù)包?
//設(shè)置接收緩沖器讀指針
ENC28J60_Write(ERDPTL,(NextPacketPtr));
ENC28J60_Write(ERDPTH,(NextPacketPtr)>>8);
// 讀下一個(gè)包的指針
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計(jì)數(shù)
//讀取接收狀態(tài)
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和符號錯(cuò)誤
// ERXFCON.CRCEN為默認(rèn)設(shè)置,一般我們不需要檢查.
if((rxstat&0x80)==0)len=0;//無效
else ENC28J60_Read_Buf(len,packet);//從接收緩沖器中復(fù)制數(shù)據(jù)包
//RX讀指針移動(dòng)到下一個(gè)接收到的數(shù)據(jù)包的開始位置
//并釋放我們剛才讀出過的內(nèi)存
ENC28J60_Write(ERXRDPTL,(NextPacketPtr));
ENC28J60_Write(ERXRDPTH,(NextPacketPtr)>>8);
//遞減數(shù)據(jù)包計(jì)數(shù)器標(biāo)志我們已經(jīng)得到了這個(gè)包
ENC28J60_Write_Op(ENC28J60_BIT_FIELD_SET,ECON2,ECON2_PKTDEC);
return(len);
}