国产免费AV|泡泡玛特欧洲总部将设在伦敦|中文天堂网www新版资源在线|一本久道综合在线中文|国精产品一二三产区的使用方法|香蕉鱼在线观看|www.27eee

 找回密碼
 注冊
搜索

ENC28J60學習筆記——AVRNET項目

[復制鏈接]
樓主
山海致遠 發表于 2013-7-13 11:18:56 | 只看該作者 |倒序瀏覽 |閱讀模式
1前言
嵌入式以太網開發,可以分為兩個部分,一個是以太網收發芯片的使用,一個是嵌入式以太網協議棧的實現。以太網收發芯片的使用要比串口收發芯片的使用復雜的多,市面上流通比較廣泛的以太網收發芯片種類還不少,有SPI接口的ENC28J60,也有并口形式的RTL8019S,CS8900A等。嵌入式以太網協議棧有著名的uIP協議棧,Lwip協議棧,還有其他嵌入式高手開發的協議棧。無論是硬件還是軟件,都無法分出高下,適合項目需求的才是最好的。
1.1 寫作理由
        在前言的最后,再說明一下我寫作的理由。以前從淘寶上購買過ENC28J60,店家信誓旦旦地說能提供51 AVR LPC STM32等多個平臺的代碼,可以實現一個網頁控制LED。頭腦一熱買了回來,買回來才發現,店家提供的資料零零散散,非常難懂,雖然不貴僅僅需要40多元,現在只需要20多元。但是總感覺有欺騙的嫌疑,這也可以映射出中國人做技術買賣的原則,產品多是實物而非服務。幾經周轉,發現原來這些ENC28J60的代碼都出自一個地方——AVRNET,源自老外的一個開源項目。把最原始的代碼拿來細細品味,以太網協議就不那么神秘了。在這里說一下ENC28J60的使用,熟悉了ENC28J60的驅動可以分幾步走。第一步,通過ENC28J60移植uIP或者lwIP協議棧,實現TCP或是UDP通信,第二,順著AVRNET項目走,實現一個簡單的web服務器,運行靜態或者動態網頁。嵌入式以太網和計算機以太網開發不同,對于TCP通信而言沒有windwos socke用,對于網頁編程而言也沒有ISS或PHP,所示實現起來會比較麻煩,但是也非常有樂趣。
1.2 平臺說明
硬件平臺 Atmega32 + proteus 7.10+WinPcap
編譯平臺 AVR Studio 6
        關于硬件平臺,由于AVRNET項目采用ATmega32,分析的時候也采用Atmega32。就ENC28J60而言,對于其他的平臺,例如STM32或是MSP而言只需要修改SPI操作即可。由于沒有硬件平臺,所以使用proteus仿真,注意仿真以太網是proteus需要安裝WinPcap。
        關于編譯平臺,AVRNET項目使用的是AVR Stdui 4.XX。這個版本稍顯老舊,我就進行了相關修改,在AVR Studio 6中重新編譯,并修正了幾個錯誤。當然其他的編譯平臺也適用。
        總結一句,平臺選用原則——“求同存異”。
1.3 資料準備
        以太網開發是非常復雜的工作,在開始之前最好先大致瀏覽一些ENC28J60的使用手冊,MICROCHIP可以下載,中文版本閱讀非常方便。除此之外,需要認真閱讀TCP IP相關知識,推薦一本圖書《嵌入式Internet TCP/IP基礎、實現和應用》。
        嵌入式開發總是一個反復借鑒的過程。該部分代碼參考了AVRNET項目和奮斗開發板的相關范例。AVRNET項目網址鏈接http://www.avrportal.com/?page=avrnet
2 寄存器和寄存器操作
       ENC28J60的寄存器很多,操作這些寄存器需要一個良好的代碼組織工作。在AVRNET項目中,把ENC28J60的驅動分解成ENC28J60.h文件和ENC28J60.c文件。H文件中主要描述ENC28J60寄存器的基本定義,而C文件主要實現了這些寄存器的操作。
2.1 寄存器定義
首先分析一下ENC28J60頭文件。閱讀數據手冊之后,會發現ENC28J60寄存器數量較多,通過分析和整理,操作ENC28J60的寄存器需要注意以下3點。
(1)   共有三種不同形式的寄存器——控制寄存器,以太網寄存器 和PHY寄存器,不同的寄存器以不同的字母開頭,以E、 MA和MI加以區分。操作這三種不同的寄存器需要不同的組合命令。
(2)   寄存器被分布在4個不同的bank中,也就是說存在地址相同的寄存器,但是這些寄存器卻位于不同的分區中,在操作寄存器之前必須選中正確的bank。
(3)  雖然存在4個bank,但是有5個寄存器在4個bank的位置相同,它們是EIE、 EIR、ESTAT、ECON1、ECON2。不言而喻,這5個寄存器將會非常重要。
AVRNET項目中,寄存器被定義成8位長度,而這8位長度包含了三個部分,地址bit7(最高位)用以區分PHY和MAC寄存器;地址bit6和bit5用以區分BANK,2位空間正好區分4個BANK;地址的最后5位才是寄存器的地址。通過這種方式就可以區分所有的寄存器了。列舉了幾行代碼。由于頭文件很長,所以不全部列出。
// bank0 寄存器
#define ERDPTL            (0x00|0x00)
#define ERDPTH            (0x01|0x00)
#define EWRPTL            (0x02|0x00)
// bank1 寄存器
#define EHT0              (0x00|0x20)
#define EHT1              (0x01|0x20)
#define EHT2              (0x02|0x20)
// bank2 寄存器
#define MACON1           (0x00|0x40|0x80)
#define MACON2           (0x01|0x40|0x80)
#define MACON3           (0x02|0x40|0x80)
//bank3 寄存器
#define MAADR1           (0x00|0x60|0x80)
#define MAADR0           (0x01|0x60|0x80)
#define MAADR3           (0x02|0x60|0x80)
       例如ERDPTH為位于BANK0的以太網寄存器,第一個數字0x01代表BANK0中的地址,該地址為0x01,第二個數字0x00代表BANK編號,該編號為0,意味第0個BANK;EHT1為位于BANK1中的控制寄存器,第二個0x20代表BANK地址為1,請注意由于BANK編號被保存在bit6和bit5,所以此處為0x20,絕不是0x10;MACON2為位于bank2的以太網寄存器,第一個數字0x01代表在該BANK中的寄存器地址,第二個數字0x40代表BANK編號,而第三個數字0x80代表該寄存器為以太網寄存器或是PHY寄存器,這些寄存器的操作和控制寄存器有區別。
       為了方便寄存器操作,h文件中還定義了寄存器地址操作的掩碼,簡單而言就是需要查看哪些位,不需要查看哪些位。
/* 寄存器地址掩碼 */
#defineADDR_MASK        0x1F
/* 存儲區域掩碼 */
#defineBANK_MASK        0x60
/* MAC和MII寄存器掩碼*/
#defineSPRD_MASK        0x80
       另外還有比較特殊的5個控制寄存器,EIE,EIR,ESTAT,ECON2和ECON1
/* 關鍵寄存器 */
#defineEIE                     0x1B
#defineEIR                     0x1C
#defineESTAT                   0x1D
#defineECON2                  0x1E
#defineECON1                  0x1F
2.2 寄存器操作命令
       寄存器操作命令也可稱為寄存器操作碼。為了實現寄存器的操作,ENC28J60定義了6+1個寄存器操作命令(操作碼)。操作相關寄存器至少有讀寄存器命令,寫寄存器命令;發送或接收以太網數據則必有寫緩沖區命令或讀緩沖區命令;為了加快操作,對于某些控制寄存器而言還可以有置位或者清零某位的命令;最后加上一個軟件復位命令,錦上添花。
<font size="3">/* 讀控制寄存器 */
#define ENC28J60_READ_CTRL_REG          0x00
/* 讀緩沖區 */
#define ENC28J60_READ_BUF_MEM          0x3A
/* 寫控制寄存器 */
#define ENC28J60_WRITE_CTRL_REG          0x40
/* 寫緩沖區 */
#define ENC28J60_WRITE_BUF_MEM          0x7A
/* 位域置位 */
#define ENC28J60_BIT_FIELD_SET              0x80
/* 位域清零 */
#define ENC28J60_BIT_FIELD_CLR                     0xA0
/* 系統復位 */
#define ENC28J60_SOFT_RESET                        0xFF</font>
復制代碼
2.3 接收和發送緩沖區分配
       以太網數據的接收和發送離不開驅動芯片內部的RAM,也可稱之為硬件緩沖區。ENC28J60包括8K 的硬件緩沖區,該硬件緩沖區一部分被接收緩沖區使用,另一部分為發送緩沖區使用。操作ENC28J60的最終目的為操作該硬件緩沖區。執行以太網發送命令時,向發送緩沖區中填充數據,并觸發相關寄存器發送以太網數據;執行以太網接收命令時,通過查詢相關寄存器或者外部中斷的方式獲得以太網數據輸入事件,接著從接收緩沖區中讀取相關數據。
(1)   把緩沖區劃分為兩個部分。把8K的硬件緩沖區劃分為兩個部分至少需要四個參數,接收緩沖區需要一個起始地址和一個結束地址加以描述,發送緩沖區也需要一個起始地址和一個結束地址加以描述。最理想的方式,兩個緩沖區完全占據了8K的硬件緩沖區,完美地利用這一空間。由于ENC28J60的寄存器長度為8位,而硬件緩沖區的大小為8K,所以前面提到的4個地址需要8個寄存器才可以完全描述,需要把單個地址分為高8位和低8位。在AVRNET項目中,接收緩沖區較大,而發送緩沖區較小。在以太網協議中,最大的報文長度為1518字節,而最小報文長度為60字節。發送緩沖區等于或略大于1518字節,剩余的部分全部分配給接收緩沖區。接收緩沖區較大也是考慮到AVR的處理能力有限,若某個時間點收到多個以太網報文,可以先把報文閑置與硬件緩沖區中,待空閑時再從緩沖區中取出。
/* 接收緩沖區起始地址 */
#define RXSTART_INIT                0x00
/* 接收緩沖區停止地址 */
#define RXSTOP_INIT                 (0x1FFF - 0x0600 - 1)
/* 發送緩沖區起始地址 發送緩沖區大小約1500字節*/
#define TXSTART_INIT                (0x1FFF - 0x0600)
/* 發送緩沖區停止地址 */
#define TXSTOP_INIT                 0x1FFF



圖硬件緩沖區結構
(2)   對于發送緩沖區而言,需要指定發送緩沖區寫指針,使用寫緩沖區命令操作該部分緩沖區,寫指針的地址會不斷增長,若遇到結束地址會重新返回起始地址。對于接收緩沖區而言就稍微復雜一點,每次讀取之前必須明確該次操作時的讀指針位置,根據前文的代碼,緩沖區讀指針的起始地址為0,在第一次讀操作發生之后需要立即設置下次讀操作的讀指針地址。ENC28J60讀緩沖區時,讀取的數據并不全是以太網的數據,在以太網數據之前還有下一個數據包的地址指針占兩個字節,接收狀態向量占4個字節,接著才是以太網數據包,該數據包包括目標MAC地址,源MAC地址,數據包類型等等;最后為CRC校驗和。在接收狀態向量的起始2個字節為該以太網數據包的長度,該參數也是非常有用的參數。



圖接收數據包結構
對于發送緩沖區而言,需要指定發送緩沖區寫指針,使用寫緩沖區命令操作該部分緩沖區,寫指針的地址會不斷增長,若遇到結束地址會重新返回起始地址。對于接收緩沖區而言就稍微復雜一點,每次讀取之前必須明確該次操作時的讀指針位置,根據前文的代碼,緩沖區讀指針的起始地址為0,在第一次讀操作發生之后需要立即設置下次讀操作的讀指針地址。ENC28J60讀緩沖區時,讀取的數據并不全是以太網的數據,在以太網數據之前還有下一個數據包的地址指針占兩個字節,接收狀態向量占4個字節,接著才是以太網數據包,該數據包包括目標MAC地址,源MAC地址,數據包類型等等;最后為CRC校驗和。在接收狀態向量的起始2個字節為該以太網數據包的長度,該參數也是非常有用的參數。
3 寄存器操作實現
      ENC28j60的寄存器操作分為2+2+2部分,分別為寫寄存器和讀寄存器部分,讀緩沖區和寫緩沖區部分,寫PHY寄存器和讀PHY寄存器部分。
3.1 讀寫寄存器
       讀或寫寄存器的函數如下
unsigned char enc28j60Read
       /* 設定寄存器地址區域 */
       enc28j60SetBank(address);
       /* 讀取寄存器值 發送讀寄存器命令和地址 */
       return enc28j60ReadOp(ENC28J60_READ_CTRL_REG, address);
}
void enc28j60Write(unsigned char address, unsigned char data)
{
       /* 設定寄存器地址區域 */
       enc28j60SetBank(address);
       /* 寫寄存器值 發送寫寄存器命令和地址 */
       enc28j60WriteOp(ENC28J60_WRITE_CTRL_REG, address, data);
}</font>
復制代碼
       讀寫寄存器的分為兩步,第一步為選定寄存器的BANK編號,第二步為使用寫命令或讀命令,操作指定地址的寄存器。在ENC28J60中,由ECON1中的某兩位保存BANK編號,ECON1是比較特殊的控制寄存器,在4個BANK中具有該寄存器且該寄存器的地址相同。Enc28j60Bank為全局變量,用于保存當前的BANK編號,如果兩次操作控制寄存器在同一個BANK時,該變量保持不變,若兩次操作的控制寄存器位于不同的BANK,那么BANK的值會變為新的BANK編號。
<font size="3">void enc28j60SetBank(unsigned char address)
{
       /* 計算本次寄存器地址在存取區域的位置 */
       if((address & BANK_MASK) != Enc28j60Bank)
       {
    /* 清除ECON1的BSEL1 BSEL0 詳見數據手冊15頁 */
    enc28j60WriteOp(ENC28J60_BIT_FIELD_CLR, ECON1, (ECON1_BSEL1|ECON1_BSEL0));
    /* 請注意寄存器地址的宏定義,bit6 bit5代碼寄存器存儲區域位置 */
    enc28j60WriteOp(ENC28J60_BIT_FIELD_SET, ECON1, (address & BANK_MASK)>>5);
    /* 重新確定當前寄存器存儲區域 */
    Enc28j60Bank = (address & BANK_MASK);
       }
}</font>
復制代碼
<font size="3">unsigned char enc28j60ReadOp(unsigned char op, unsigned char address)
{
       unsigned char dat = 0;

       /* CS拉低 使能ENC28J60 */
       ENC28J60_CSL();
       /* 操作碼和地址 */
       dat = op | (address & ADDR_MASK);
  /* 通過SPI寫數據*/
       spi_sendbyte(dat);
  /* 通過SPI讀出數據 */
       dat = spi_sendbyte(0xFF);

  /* 如果是MAC和MII寄存器,第一個讀取的字節無效,該信息包含在地址的最高位 */
       if(address & 0x80)
       {
              /* 再次通過SPI讀取數據 */
    dat = spi_sendbyte(0xFF);
       }

  /* CS拉高 禁止ENC28J60 */
       ENC28J60_CSH();

  /* 返回數據 */
       return dat;
}</font>
復制代碼
讀控制寄存器實際上就是嚴格遵守數據手冊的操作要求,一次編寫程序。在這里由于讀MAC和MII寄存器時,第一個接收到的字節為無效字節,第二個字節才為有效字節。程序通過寄存器地址的最高位來判斷是否為MAC或MII寄存器。寫寄存器函數較為簡單,第一次字節包括操作碼和寄存器地址,第二個字節則為數據。在這兩個函數中參數op為ENC28J60的指令,或稱之為操作碼,該指令占據了SPI第一個字節的前3位,參數address為寄存器地址,參數data為寄存器的具體值。
這兩個函數和硬件發生某些關系,ENC28J60_CSL()和ENC28J60_CSH()為操作CS端口的操作宏,而spi_sendbyte()可通過SPI發送一個字節。修改這些函數即可在其他平臺上使用ENC28J60。不過請特別注意,在使用其他開發板時由于SPI總線上可能掛載多個設備,單獨使用ENC28J60時需要把其他設備的CS端口拉高,或安裝一個上拉電阻。
<font size="3">unsigned char enc28j60ReadOp(unsigned char op, unsigned char address)
{
       unsigned char dat = 0;

       /* CS拉低 使能ENC28J60 */
       ENC28J60_CSL();
       /* 操作碼和地址 */
       dat = op | (address & ADDR_MASK);
  /* 通過SPI寫數據*/
       spi_sendbyte(dat);
  /* 通過SPI讀出數據 */
       dat = spi_sendbyte(0xFF);

  /* 如果是MAC和MII寄存器,第一個讀取的字節無效,該信息包含在地址的最高位 */
       if(address & 0x80)
       {
              /* 再次通過SPI讀取數據 */
    dat = spi_sendbyte(0xFF);
       }

  /* CS拉高 禁止ENC28J60 */
       ENC28J60_CSH();

  /* 返回數據 */
       return dat;
}
void enc28j60WriteOp(unsigned char op, unsigned char address, unsigned char data)
{
       unsigned char dat = 0;
  /* 使能ENC28J60 */                                                   
       ENC28J60_CSL();        
  /* 通過SPI發送 操作碼和寄存器地址 */                        
       dat = op | (address & ADDR_MASK);
  /* 通過SPI1發送數據 */
       spi_sendbyte(dat);
  /* 準備寄存器數值 */                             
       dat = data;
  /* 通過SPI發送數據 */
       spi_sendbyte(dat);
  /* 禁止ENC28J60 */                     
       ENC28J60_CSH();   
}</font>
復制代碼
3.2 讀寫緩沖區
       讀寫緩沖區的操作也是易于理解的。需要說明的是,兩個函數具有相同的輸入參數,參數len代表被操作數據的長度,pdata為被操作數據的指針。和寄存器讀寫函數相似,發送或接收數據之前需要發送特定的操作碼。
<font size="3">void enc28j60ReadBuffer(unsigned int len, unsigned char* pdata)
{
  /* 使能ENC28J60 */
  ENC28J60_CSL();
       /* 通過SPI發送讀取緩沖區命令*/
       spi_sendbyte(ENC28J60_READ_BUF_MEM);
  /* 循環讀取 */
       while(len)
       {
    len--;
    /* 讀取數據 */
    *pdata = (unsigned char)spi_sendbyte(0);
    /* 地址指針累加 */
    pdata++;
       }
  /* 增加字符串結尾 便于操作 */
       *pdata='\0';
  /* 禁止ENC28J60 */
       ENC28J60_CSH();
}
void enc28j60WriteBuffer(unsigned int len, unsigned char* pdata)
{
  /* 使能ENC28J60 */
  ENC28J60_CSL();
       /* 通過SPI發送寫取緩沖區命令*/
       spi_sendbyte(ENC28J60_WRITE_BUF_MEM);

  /* 循環發送 */
       while(len)
       {
              len--;
    /* 發送數據 */
              spi_sendbyte(*pdata);
    /* 地址指針累加 */
              pdata++;
       }

  /* 禁止ENC28J60 */
       ENC28J60_CSH();
}</font>
復制代碼
3.3 讀寫PHY寄存器
       PHY寄存器和由ENC28J60控制的LED指示燈有關,控制這些寄存器可以控制這兩個LED的驅動方式,和發生相應事件時LED的顯示方式。一般情況下,一個LED指示燈常亮,顯示接收和發送活動,另一個LED指示燈顯示接收活動,有數據輸入時產生一個點亮脈沖。PHY是比較特殊的寄存器,先要想一個控制寄存器寫入PHY寄存器的地址,再向兩個控制寄存器依次寫入PHY寄存器的具體數據的高8位和低8位,最后等待PHY寄存器操作完成。
<font size="3">void enc28j60PhyWrite(unsigned char address, unsigned int data)
{
       /* 向MIREGADR寫入地址 詳見數據手冊19頁*/
       enc28j60Write(MIREGADR, address);
       /* 寫入低8位數據 */
       enc28j60Write(MIWRL, data);
  /* 寫入高8位數據 */
       enc28j60Write(MIWRH, data>>8);
       /* 等待PHY寄存器寫入完成 */
       while(enc28j60Read(MISTAT) & MISTAT_BUSY);
}</font>
復制代碼
4 ENC28J60寫操作
       ENC28J60的寄存器操作時ENC28J60初始化,發送以太網數據和接收以太網數據的基礎。通過ENC28J60進行以太網發送數據操作,本質上為操作硬件緩沖區的發送緩沖區部分。每次發送時總是從發送緩沖區的起始地址開始填充數據,數據填充的結束地址和數據的輸入長度有關。操作完發送緩沖區的大小之后可向發送緩沖區填充數據,即調用ENC28J60_WRITE_BUF_MEM操作碼,接著置位ECON1中的 ECON1_TXRTS位啟動發送,并使用等待法不斷查詢是否發送完畢。基本的思路還是和SPI或UART發送數據相似,即填充數據,啟動發送,查詢發送完成。寫操作的輸入參數為數據包的長度len和數據包指針packet,該參數正好和uIP的網絡層操作函數相對應。若是LwIP協議,輸入參數將會是pBuf這種自定義數據結構,需要經過適當的修改才應用于lwIP協議棧。
<font size="3">void enc28j60PacketSend(unsigned int len, unsigned char* packet)
{
       /* 查詢發送邏輯復位位 */
       while((enc28j60Read(ECON1) & ECON1_TXRTS)!= 0);

  /* 設置發送緩沖區起始地址 */   
       enc28j60Write(EWRPTL, TXSTART_INIT & 0xFF);
       enc28j60Write(EWRPTH, TXSTART_INIT >> 8);

       /* 設置發送緩沖區結束地址 該值對應發送數據包長度 */  
       enc28j60Write(ETXNDL, (TXSTART_INIT + len) & 0xFF);
       enc28j60Write(ETXNDH, (TXSTART_INIT + len) >>8);

       /* 發送之前發送控制包格式字 */
       enc28j60WriteOp(ENC28J60_WRITE_BUF_MEM, 0, 0x00);

       /* 通過ENC28J60發送數據包 */
       enc28j60WriteBuffer(len, packet);

       /* 開始發送 */
       enc28j60WriteOp(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_TXRTS);

  /* 復位發送邏輯的問題 */
       if( (enc28j60Read(EIR) & EIR_TXERIF) )
       {
              enc28j60SetBank(ECON1);
    enc28j60WriteOp(ENC28J60_BIT_FIELD_CLR, ECON1, ECON1_TXRTS);
  }
}</font>
復制代碼
5 ENC28J60讀操作
       讀操作要略比寫操作復雜。寫操作時每次總是從硬件發送緩沖區的起始地址開始操作,而讀操作時需要不斷修改接收緩沖區的讀指針地址,該參數需要通過NextPacketPtr完成,該變量為長度為16的全局變量。讀操作時,先通過寄存器查看是否存在以太網數據包,讀EPKTCNT寄存器便可返回以太網數據包的個數;若存在以太網數據包則設定讀指針的地址,執行讀緩沖區操作,ENC28J60的以太網數據包中前兩個字節為下一個以太網數據包的起始地址,立即保存該參數至NextPacketPtr全局變量中;以太網數據包中的后兩個字節為該數據包的長度,該長度只從目標MAC地址開始的數據包的長度,進行處理時還應該舍棄最后的4字節CRC校驗結果;最重要的事情便是通過讀緩沖區操作碼把len長度的以太網數據讀出,讀出的目標應為軟件緩沖區,例如定義在程序中的rxtx_buf。最后根據NextPacketPtr移動讀指針以便下次操作,并通過操作ECON2的ECON2_PKTDEC位遞減了以太網數據包。
<font size="3">unsigned int enc28j60PacketReceive(unsigned int maxlen, unsigned char* packet)
{
       unsigned int rxstat;
       unsigned int len;

       /* 是否收到以太網數據包 */
       if( enc28j60Read(EPKTCNT) == 0 )
       {
              return(0);
    }

       /* 設置接收緩沖器讀指針 */
       enc28j60Write(ERDPTL, (NextPacketPtr));
       enc28j60Write(ERDPTH, (NextPacketPtr)>>8);

  /* 接收數據包結構示例 數據手冊43頁 */

       /* 讀下一個包的指針 */
       NextPacketPtr  = enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0);
       NextPacketPtr |= enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0)<<8;

       /* 讀包的長度 */
       len  = enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0);
       len |= enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0)<<8;

   /* 去除CRC校驗部分 */
   len-= 4;

       /* 讀取接收狀態 */
       rxstat  = enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0);
       rxstat |= enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0) << 8;

       /* 限制檢索的長度  */
  if (len > maxlen-1)
       {
    len = maxlen-1;
  }
  /* 檢查CRC和符號錯誤 */
  /* ERXFCON.CRCEN是默認設置。通常我們不需要檢查 */
  if ((rxstat & 0x80)==0)
       {
          //無效的
          len = 0;
       }
       else
       {
    /* 從接收緩沖器中復制數據包 */
    enc28j60ReadBuffer(len, packet);
  }

  /* 移動接收緩沖區 讀指針*/
       enc28j60Write(ERXRDPTL, (NextPacketPtr));
       enc28j60Write(ERXRDPTH, (NextPacketPtr)>>8);

       /* 數據包遞減 */
       enc28j60WriteOp(ENC28J60_BIT_FIELD_SET, ECON2, ECON2_PKTDEC);

  /* 返回長度 */
       return(len);
}</font>
復制代碼
6 ENC28J60初始化操作
       ENC28J60的操作比較瑣碎。第一,進行CS端口的相關配置,即把該端口設置為輸出狀態,該部分代碼可以出現在任何硬件初始化代碼中,例如可以把所有的IO操作放入gpio_config中;第二,進行軟件復位,并通過查詢ESTAT的ESTAT_CLKRDY標志位確定是否復位完成;第二,初始化NextPacketPtr變量,該變量的初值為發送緩沖區的起始地址;第三,配置發送和接收緩沖區的區間;第四,若干參數配置,請看代碼注釋部分,ENC28J60具有自動填充0 的功能,即發送報文長度低于以太網最小報文長度時可以填充0至最小長度;第五,寫入MAC地址,由于ENC28J60內部沒有全球唯一的MAC地址,所以該地址需要軟件填寫。但是這種軟件填寫方式存在缺陷,實際應用中可以含有全球唯一的MAC地址的EEPROM,從EERPOM讀取MAC地址并用該地址初始化ENC28J60;第六,初始化中斷,并使能接收,ENC28J60含有多個中斷,最重要的有全局中斷和數據包帶接收中斷。
<font size="3">void enc28j60Init(unsigned char* macaddr)
{
  /* CS端口為輸出 */
  DDRB |= (1<<4);

  /* 禁止ENC28J60 */
  ENC28J60_CSH();
       /* ENC28J60軟件復位 該函數可以改進*/
       enc28j60WriteOp(ENC28J60_SOFT_RESET, 0, ENC28J60_SOFT_RESET);
  /*查詢ESTAT.CLKRDY位*/
       while(!(enc28j60Read(ESTAT) & ESTAT_CLKRDY));

       /* 設置接收緩沖區起始地址 該變量用于每次讀取緩沖區時保留下一個包的首地址 */
       NextPacketPtr = RXSTART_INIT;

  /* 設置接收緩沖區 起始指針*/
       enc28j60Write(ERXSTL, RXSTART_INIT & 0xFF);
       enc28j60Write(ERXSTH, RXSTART_INIT >> 8);

  /* 設置接收緩沖區 讀指針*/
       enc28j60Write(ERXRDPTL, RXSTART_INIT&0xFF);
       enc28j60Write(ERXRDPTH, RXSTART_INIT>>8);

  /* 設置接收緩沖區 結束指針 */
       enc28j60Write(ERXNDL, RXSTOP_INIT&0xFF);
       enc28j60Write(ERXNDH, RXSTOP_INIT>>8);

       /* 設置發送緩沖區 起始指針 */
       enc28j60Write(ETXSTL, TXSTART_INIT&0xFF);
       enc28j60Write(ETXSTH, TXSTART_INIT>>8);
       /* 設置發送緩沖區 結束指針 */
       enc28j60Write(ETXNDL, TXSTOP_INIT&0xFF);
       enc28j60Write(ETXNDH, TXSTOP_INIT>>8);

  /* 使能單播過濾 使能CRC校驗 使能 格式匹配自動過濾*/
       enc28j60Write(ERXFCON, ERXFCON_UCEN|ERXFCON_CRCEN|ERXFCON_PMEN);
       enc28j60Write(EPMM0, 0x3f);
       enc28j60Write(EPMM1, 0x30);
       enc28j60Write(EPMCSL, 0xf9);
       enc28j60Write(EPMCSH, 0xf7);

  /* 使能MAC接收 允許MAC發送暫停控制幀 當接收到暫停控制幀時停止發送*/
  /* 數據手冊34頁 */
       enc28j60Write(MACON1, MACON1_MARXEN|MACON1_TXPAUS|MACON1_RXPAUS);

  /* 退出復位狀態 */
       enc28j60Write(MACON2, 0x00);

  /* 用0填充所有短幀至60字節長 并追加一個CRC 發送CRC使能 幀長度校驗使能 MAC全雙工使能*/
       /* 提示 由于ENC28J60不支持802.3的自動協商機制, 所以對端的網絡卡需要強制設置為全雙工 */
       enc28j60WriteOp(ENC28J60_BIT_FIELD_SET, MACON3, MACON3_PADCFG0|MACON3_TXCRCEN|MACON3_FRMLNEN|MACON3_FULDPX);

  /* 填入默認值 */
       enc28j60Write(MAIPGL, 0x12);
  /* 填入默認值 */
       enc28j60Write(MAIPGH, 0x0C);
  /* 填入默認值 */
       enc28j60Write(MABBIPG, 0x15);

  /* 最大幀長度 */
       enc28j60Write(MAMXFLL, MAX_FRAMELEN & 0xFF);
       enc28j60Write(MAMXFLH, MAX_FRAMELEN >> 8);

  /* 寫入MAC地址 */
       enc28j60Write(MAADR5, macaddr[0]);     
       enc28j60Write(MAADR4, macaddr[1]);
       enc28j60Write(MAADR3, macaddr[2]);
       enc28j60Write(MAADR2, macaddr[3]);
       enc28j60Write(MAADR1, macaddr[4]);
       enc28j60Write(MAADR0, macaddr[5]);

       /* 配置PHY為全雙工  LEDB為拉電流 */
       enc28j60PhyWrite(PHCON1, PHCON1_PDPXMD);

  /* LED狀態 */
  enc28j60PhyWrite(PHLCON,0x0476);   

  /* 半雙工回環禁止 */
       enc28j60PhyWrite(PHCON2, PHCON2_HDLDIS);

  /* 返回BANK0 */     
       enc28j60SetBank(ECON1);

  /* 使能中斷 全局中斷 接收中斷 接收錯誤中斷 */
       enc28j60WriteOp(ENC28J60_BIT_FIELD_SET, EIE, EIE_INTIE|EIE_PKTIE|EIE_RXERIE);

  /* 接收使能位 */
       enc28j60WriteOp(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_RXEN);
}</font>
復制代碼

7 總結
ENC28J60的驅動編寫算是比較復雜的。但是回過頭來看看,其他的以太網驅動芯片的操作和ENC28J60的操作類似,其操作的核心即時數KB的硬件緩沖區。本例不能給出合適的運行范例,因為以太網驅動芯片要配合以太網協議棧來實現,而以太網協議棧內容很多,即使通過uIP或是lwIP也必須面對繁多的基礎知識。ENC28J60的驅動是以太網協議棧實現的基礎,通過ENC28J60還將會分析uIP協議棧,lwIP協議棧的應用。在實現TCP通信之后,還將會結合AVRNET或uIP,lwIP協議棧實現web服務器,通過網頁交換數據。

您需要登錄后才可以回帖 登錄 | 注冊

本版積分規則

手機版|小黑屋|ELEOK |網站地圖

GMT+8, 2026-5-26 03:06

Powered by Discuz! X5.0

© 2001-2026 Discuz! Team.

快速回復 返回頂部 返回列表