NC28j60是帶有行業標準串行外設接口(serial peripheral interface spi)的獨立以太網控制器 它符合IEEE 802.3的全部規范,采用了一系列包過濾機制以對傳入數據包進行限制; 它還提供了一個內部DMA模塊,以實現快速數據吞吐和硬件支持的IP校驗和計算。與主控制器的通信通過兩個中斷引腳和SPI 實現,數據傳輸速率高達10 Mb/s。兩個專用的引腳用于連接LED,進行網絡活動狀態指示。 1、支持全雙工和半雙工模式。 2、SPI接口可達10Mbps。 3、可配置的接收和發送緩沖區大小。 4、TTL電平輸入。
ENC28J60在發送或接收數據包時由以下幾點值得關注: 首先,ENC28J60具有一個接收過濾器可以丟棄或接收具有組播、廣播或單播目標地址的數據包。 其次,在數據字段處: 以太網數據字段的長度可以在0-1500字節之間變換,超過這一范圍的數據包是違反以太網標準的,這些包將會被大多數以太網節點丟棄。若設置ENC28J60的巨大幀使能位為1,可以發送和接收超大規格數據包。 在數據域中的填充字段是在數據字段小于46字節時起填充作用。ENC28J60在發送數據包時,會自動填充0。ENC28J60在接收時自動拒絕小于18字節的數據包。數據填充亦可由主控芯片來配置。 最后,在CRC處: ENC28J60在接收數據包時將檢查每個傳入數據包的CRC,通過檢測ERXFCON.CRCEN位來判斷輸入數據包的CRC是否正確。ENC28J60在發送數據包時,將自動生成一個有效的CRC并發送它。發送數據包的CRC亦可由主控芯片來提供。
ENC28J60存儲器構成
ENC28J60的存儲器分為三種:控制寄存器、以太網緩沖寄存器、PHY寄存器。 控制寄存器用于進行ENC28J60的配置、控制和狀態獲取,可通過SPI接口直接讀寫控制寄存器。
ENC28J60的控制寄存器通常分為ETH、MAC和MII三組寄存器,其中“E”開頭的為ETH組,“MA”開頭的寄存器屬于MAC組,“MI”開頭的屬于MII組。
以太網緩沖寄存器包含一個供以太網控制器使用的發送和接收存儲空間,可通過SPI接口對該存儲空間的容量進行編程,只可通過讀寫緩沖器的SPI指令來訪問以太網緩沖器。 PHY寄存器用于進行PHY模塊的配置、控制和狀態獲取,不可以通過SPI接口直接訪問這些寄存器,只可通過MAC中的MII訪問這些寄存器。
ENC28J60存儲器結構圖如下:
ENC28J60的寄存器很多,操作這些寄存器需要一個良好的代碼組織工作。在AVRNET項目中,把ENC28J60的驅動分解成ENC28J60.h文件和ENC28J60.c文件。H文件中主要描述ENC28J60寄存器的基本定義,而C文件主要實現了這些寄存器的操作。 控制寄存器被分布在4個不同的bank中,也就是說存在地址相同的寄存器,但是這些寄存器卻位于不同的分區中,在操作寄存器之前必須選中正確的bank。 雖然存在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個寄存器操作命令(操作碼)。操作相關寄存器至少有讀寄存器命令,寫寄存器命令;發送或接收以太網數據則必有寫緩沖區命令或讀緩沖區命令;為了加快操作,對于某些控制寄存器而言還可以有置位或者清零某位的命令;最后加上一個軟件復位命令,錦上添花。 1. <font size="3">/* 讀控制寄存器 */ 2. #define ENC28J60_READ_CTRL_REG 0x00 3. /* 讀緩沖區 */ 4. #define ENC28J60_READ_BUF_MEM 0x3A 5. /* 寫控制寄存器 */ 6. #define ENC28J60_WRITE_CTRL_REG 0x40 7. /* 寫緩沖區 */ 8. #define ENC28J60_WRITE_BUF_MEM 0x7A 9. /* 位域置位 */ 10. #define ENC28J60_BIT_FIELD_SET 0x80 11. /* 位域清零 */ 12. #define ENC28J60_BIT_FIELD_CLR 0xA0 13. /* 系統復位 */ 14. #define ENC28J60_SOFT_RESET 0xFF</font> 本帖最后由 xukai871105 于2013-2-10 19:34 編輯
1前言
嵌入式以太網開發,可以分為兩個部分,一個是以太網收發芯片的使用,一個是嵌入式以太網協議棧的實現。以太網收發芯片的使用要比串口收發芯片的使用復雜的多,市面上流通比較廣泛的以太網收發芯片種類還不少,有SPI接口的ENC28J60,也有并口形式的RTL8019S,CS8900A等。嵌入式以太網協議棧有著名的uIP協議棧,Lwip協議棧,還有其他嵌入式高手開發的協議棧。無論是硬件還是軟件,都無法分出高下,適合項目需求的才是最好的。
1.1 寫作理由
在前言的最后,再說明一下我寫作的理由。以前從淘寶上購買過ENC28J60,店家信誓旦旦地說能提供51 AVRLPC STM32等多個平臺的代碼,可以實現一個網頁控制LED。頭腦一熱買了回來,買回來才發現,店家提供的資料零零散散,非常難懂,雖然不貴僅僅需要40多元,現在只需要20多元。但是總感覺有欺騙的嫌疑,這也可以映射出中國人做技術買賣的原則,產品多是實物而非服務。幾經周轉,發現原來這些ENC28J60的代碼都出自一個地方——AVRNET,源自老外的一個開源項目。把最原始的代碼拿來細細品味,以太網協議就不那么神秘了。在這里說一下ENC28J60的使用,熟悉了ENC28J60的驅動可以分幾步走。第一步,通過ENC28J60移植uIP或者lwIP協議棧,實現TCP或是UDP通信,第二,順著AVRNET項目走,實現一個簡單的web服務器,運行靜態或者動態網頁。嵌入式以太網和計算機以太網開發不同,對于TCP通信而言沒有windwossocke用,對于網頁編程而言也沒有ISS或PHP,所示實現起來會比較麻煩,但是也非常有樂趣。
1.2 平臺說明
硬件平臺Atmega32 + proteus 7.10+WinPcap
編譯平臺 AVRStudio 6
關于硬件平臺,由于AVRNET項目采用ATmega32,分析的時候也采用Atmega32。就ENC28J60而言,對于其他的平臺,例如STM32或是MSP而言只需要修改SPI操作即可。由于沒有硬件平臺,所以使用proteus仿真,注意仿真以太網是proteus需要安裝WinPcap。
關于編譯平臺,AVRNET項目使用的是AVRStdui 4.XX。這個版本稍顯老舊,我就進行了相關修改,在AVRStudio 6中重新編譯,并修正了幾個錯誤。當然其他的編譯平臺也適用。
總結一句,平臺選用原則——“求同存異”。
1.3 資料準備
以太網開發是非常復雜的工作,在開始之前最好先大致瀏覽一些ENC28J60的使用手冊,MICROCHIP可以下載,中文版本閱讀非常方便。除此之外,需要認真閱讀TCPIP相關知識,推薦一本圖書《嵌入式InternetTCP/IP基礎、實現和應用》。
嵌入式開發總是一個反復借鑒的過程。該部分代碼參考了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個寄存器操作命令(操作碼)。操作相關寄存器至少有讀寄存器命令,寫寄存器命令;發送或接收以太網數據則必有寫緩沖區命令或讀緩沖區命令;為了加快操作,對于某些控制寄存器而言還可以有置位或者清零某位的命令;最后加上一個軟件復位命令,錦上添花。 1. <fontsize="3">/* 讀控制寄存器 */ 2. #define ENC28J60_READ_CTRL_REG 0x00 3. /* 讀緩沖區 */ 4. #defineENC28J60_READ_BUF_MEM 0x3A 5. /* 寫控制寄存器 */ 6. #defineENC28J60_WRITE_CTRL_REG 0x40 7. /* 寫緩沖區 */ 8. #defineENC28J60_WRITE_BUF_MEM 0x7A 9. /* 位域置位 */ 10. #defineENC28J60_BIT_FIELD_SET 0x80 11. /* 位域清零 */ 12. #defineENC28J60_BIT_FIELD_CLR 0xA0 13. /* 系統復位 */ 14. #defineENC28J60_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的處理能力有限,若某個時間點收到多個以太網報文,可以先把報文閑置與硬件緩沖區中,待空閑時再從緩沖區中取出。 /* 接收緩沖區起始地址 */ #defineRXSTART_INIT 0x00 /* 接收緩沖區停止地址 */ #defineRXSTOP_INIT (0x1FFF - 0x0600 - 1) /* 發送緩沖區起始地址 發送緩沖區大小約1500字節*/ #defineTXSTART_INIT (0x1FFF - 0x0600) /* 發送緩沖區停止地址 */ #defineTXSTOP_INIT 0x1FFF file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image005.jpg
圖硬件緩沖區結構 (2) 對于發送緩沖區而言,需要指定發送緩沖區寫指針,使用寫緩沖區命令操作該部分緩沖區,寫指針的地址會不斷增長,若遇到結束地址會重新返回起始地址。對于接收緩沖區而言就稍微復雜一點,每次讀取之前必須明確該次操作時的讀指針位置,根據前文的代碼,緩沖區讀指針的起始地址為0,在第一次讀操作發生之后需要立即設置下次讀操作的讀指針地址。ENC28J60讀緩沖區時,讀取的數據并不全是以太網的數據,在以太網數據之前還有下一個數據包的地址指針占兩個字節,接收狀態向量占4個字節,接著才是以太網數據包,該數據包包括目標MAC地址,源MAC地址,數據包類型等等;最后為CRC校驗和。在接收狀態向量的起始2個字節為該以太網數據包的長度,該參數也是非常有用的參數。 file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image007.jpg
3 寄存器操作實現 ENC28j60的寄存器操作分為2+2+2部分,分別為寫寄存器和讀寄存器部分,讀緩沖區和寫緩沖區部分,寫PHY寄存器和讀PHY寄存器部分。 3.1 讀寫寄存器 讀或寫寄存器的函數如下 1. <font size="3">unsignedchar enc28j60Read(unsigned char address) 2. { 3. /* 設定寄存器地址區域 */ 4. enc28j60SetBank(address); 5. /* 讀取寄存器值 發送讀寄存器命令和地址 */ 6. returnenc28j60ReadOp(ENC28J60_READ_CTRL_REG, address); 7. } 8. void enc28j60Write(unsigned charaddress, unsigned char data) 9. { 10. /* 設定寄存器地址區域 */ 11. enc28j60SetBank(address); 12. /* 寫寄存器值 發送寫寄存器命令和地址 */ 13. enc28j60WriteOp(ENC28J60_WRITE_CTRL_REG, address, data); 14. }</font> 復制代碼 讀寫寄存器的分為兩步,第一步為選定寄存器的BANK編號,第二步為使用寫命令或讀命令,操作指定地址的寄存器。在ENC28J60中,由ECON1中的某兩位保存BANK編號,ECON1是比較特殊的控制寄存器,在4個BANK中具有該寄存器且該寄存器的地址相同。Enc28j60Bank為全局變量,用于保存當前的BANK編號,如果兩次操作控制寄存器在同一個BANK時,該變量保持不變,若兩次操作的控制寄存器位于不同的BANK,那么BANK的值會變為新的BANK編號。 1. <font size="3">voidenc28j60SetBank(unsigned char address) 2. { 3. /* 計算本次寄存器地址在存取區域的位置 */ 4. if((address& BANK_MASK) != Enc28j60Bank) 5. { 6. /* 清除ECON1的BSEL1 BSEL0 詳見數據手冊15頁 */ 7. enc28j60WriteOp(ENC28J60_BIT_FIELD_CLR, ECON1, (ECON1_BSEL1|ECON1_BSEL0)); 8. /* 請注意寄存器地址的宏定義,bit6 bit5代碼寄存器存儲區域位置 */ 9. enc28j60WriteOp(ENC28J60_BIT_FIELD_SET, ECON1, (address &BANK_MASK)>>5); 10. /* 重新確定當前寄存器存儲區域 */ 11. Enc28j60Bank = (address& BANK_MASK); 12. } 13. }</font> 復制代碼 1. <font size="3">unsignedchar enc28j60ReadOp(unsigned char op, unsigned char address) 2. { 3. unsigned chardat = 0; 4. 5. /* CS拉低 使能ENC28J60 */ 6. ENC28J60_CSL(); 7. /* 操作碼和地址 */ 8. dat = op |(address & ADDR_MASK); 9. /* 通過SPI寫數據*/ 10. spi_sendbyte(dat); 11. /* 通過SPI讀出數據 */ 12. dat =spi_sendbyte(0xFF); 13. 14. /* 如果是MAC和MII寄存器,第一個讀取的字節無效,該信息包含在地址的最高位 */ 15. if(address& 0x80) 16. { 17. /* 再次通過SPI讀取數據 */ 18. dat = spi_sendbyte(0xFF); 19. } 20. 21. /* CS拉高 禁止ENC28J60 */ 22. ENC28J60_CSH(); 23. 24. /* 返回數據 */ 25. return dat; 26. }</font> 復制代碼 讀控制寄存器實際上就是嚴格遵守數據手冊的操作要求,一次編寫程序。在這里由于讀MAC和MII寄存器時,第一個接收到的字節為無效字節,第二個字節才為有效字節。程序通過寄存器地址的最高位來判斷是否為MAC或MII寄存器。寫寄存器函數較為簡單,第一次字節包括操作碼和寄存器地址,第二個字節則為數據。在這兩個函數中參數op為ENC28J60的指令,或稱之為操作碼,該指令占據了SPI第一個字節的前3位,參數address為寄存器地址,參數data為寄存器的具體值。 這兩個函數和硬件發生某些關系,ENC28J60_CSL()和ENC28J60_CSH()為操作CS端口的操作宏,而spi_sendbyte()可通過SPI發送一個字節。修改這些函數即可在其他平臺上使用ENC28J60。不過請特別注意,在使用其他開發板時由于SPI總線上可能掛載多個設備,單獨使用ENC28J60時需要把其他設備的CS端口拉高,或安裝一個上拉電阻。 1. <font size="3">unsignedchar enc28j60ReadOp(unsigned char op, unsigned char address) 2. { 3. unsigned chardat = 0; 4. 5. /* CS拉低 使能ENC28J60 */ 6. ENC28J60_CSL(); 7. /* 操作碼和地址 */ 8. dat = op |(address & ADDR_MASK); 9. /* 通過SPI寫數據*/ 10. spi_sendbyte(dat); 11. /* 通過SPI讀出數據 */ 12. dat =spi_sendbyte(0xFF); 13. 14. /* 如果是MAC和MII寄存器,第一個讀取的字節無效,該信息包含在地址的最高位 */ 15. if(address &0x80) 16. { 17. /* 再次通過SPI讀取數據 */ 18. dat = spi_sendbyte(0xFF); 19. } 20. 21. /* CS拉高 禁止ENC28J60 */ 22. ENC28J60_CSH(); 23. 24. /* 返回數據 */ 25. return dat; 26. } 27. void enc28j60WriteOp(unsigned char op,unsigned char address, unsigned char data) 28. { 29. unsigned chardat = 0; 30. /* 使能ENC28J60 */ 31. ENC28J60_CSL(); 32. /* 通過SPI發送 操作碼和寄存器地址 */ 33. dat = op |(address & ADDR_MASK); 34. /* 通過SPI1發送數據 */ 35. spi_sendbyte(dat); 36. /* 準備寄存器數值 */ 37. dat = data; 38. /* 通過SPI發送數據 */ 39. spi_sendbyte(dat); 40. /* 禁止ENC28J60 */ 41. ENC28J60_CSH(); 42. }</font> 復制代碼 3.2 讀寫緩沖區 讀寫緩沖區的操作也是易于理解的。需要說明的是,兩個函數具有相同的輸入參數,參數len代表被操作數據的長度,pdata為被操作數據的指針。和寄存器讀寫函數相似,發送或接收數據之前需要發送特定的操作碼。 1. <font size="3">voidenc28j60ReadBuffer(unsigned int len, unsigned char* pdata) 2. { 3. /* 使能ENC28J60 */ 4. ENC28J60_CSL(); 5. /* 通過SPI發送讀取緩沖區命令*/ 6. spi_sendbyte(ENC28J60_READ_BUF_MEM); 7. /* 循環讀取 */ 8. while(len) 9. { 10. len--; 11. /* 讀取數據 */ 12. *pdata = (unsignedchar)spi_sendbyte(0); 13. /* 地址指針累加 */ 14. pdata++; 15. } 16. /* 增加字符串結尾 便于操作 */ 17. *pdata='\0'; 18. /* 禁止ENC28J60 */ 19. ENC28J60_CSH(); 20. } 21. void enc28j60WriteBuffer(unsigned intlen, unsigned char* pdata) 22. { 23. /* 使能ENC28J60 */ 24. ENC28J60_CSL(); 25. /* 通過SPI發送寫取緩沖區命令*/ 26. spi_sendbyte(ENC28J60_WRITE_BUF_MEM); 27. 28. /* 循環發送 */ 29. while(len) 30. { 31. len--; 32. /* 發送數據 */ 33. spi_sendbyte(*pdata); 34. /* 地址指針累加 */ 35. pdata++; 36. } 37. 38. /* 禁止ENC28J60 */ 39. ENC28J60_CSH(); 40. }</font> 復制代碼 3.3 讀寫PHY寄存器 PHY寄存器和由ENC28J60控制的LED指示燈有關,控制這些寄存器可以控制這兩個LED的驅動方式,和發生相應事件時LED的顯示方式。一般情況下,一個LED指示燈常亮,顯示接收和發送活動,另一個LED指示燈顯示接收活動,有數據輸入時產生一個點亮脈沖。PHY是比較特殊的寄存器,先要想一個控制寄存器寫入PHY寄存器的地址,再向兩個控制寄存器依次寫入PHY寄存器的具體數據的高8位和低8位,最后等待PHY寄存器操作完成。 1. <font size="3">voidenc28j60PhyWrite(unsigned char address, unsigned int data) 2. { 3. /* 向MIREGADR寫入地址 詳見數據手冊19頁*/ 4. enc28j60Write(MIREGADR, address); 5. /* 寫入低8位數據 */ 6. enc28j60Write(MIWRL, data); 7. /* 寫入高8位數據 */ 8. enc28j60Write(MIWRH, data>>8); 9. /* 等待PHY寄存器寫入完成 */ 10. while(enc28j60Read(MISTAT) & MISTAT_BUSY); 11. }</font> 復制代碼 4 ENC28J60寫操作 ENC28J60的寄存器操作時ENC28J60初始化,發送以太網數據和接收以太網數據的基礎。通過ENC28J60進行以太網發送數據操作,本質上為操作硬件緩沖區的發送緩沖區部分。每次發送時總是從發送緩沖區的起始地址開始填充數據,數據填充的結束地址和數據的輸入長度有關。操作完發送緩沖區的大小之后可向發送緩沖區填充數據,即調用ENC28J60_WRITE_BUF_MEM操作碼,接著置位ECON1中的 ECON1_TXRTS位啟動發送,并使用等待法不斷查詢是否發送完畢。基本的思路還是和SPI或UART發送數據相似,即填充數據,啟動發送,查詢發送完成。寫操作的輸入參數為數據包的長度len和數據包指針packet,該參數正好和uIP的網絡層操作函數相對應。若是LwIP協議,輸入參數將會是pBuf這種自定義數據結構,需要經過適當的修改才應用于lwIP協議棧。 1. <font size="3">voidenc28j60PacketSend(unsigned int len, unsigned char* packet) 2. { 3. /* 查詢發送邏輯復位位 */ 4. while((enc28j60Read(ECON1)& ECON1_TXRTS)!= 0); 5. 6. /* 設置發送緩沖區起始地址 */ 7. enc28j60Write(EWRPTL, TXSTART_INIT & 0xFF); 8. enc28j60Write(EWRPTH, TXSTART_INIT >> 8); 9. 10. /* 設置發送緩沖區結束地址 該值對應發送數據包長度 */ 11. enc28j60Write(ETXNDL, (TXSTART_INIT + len) & 0xFF); 12. enc28j60Write(ETXNDH, (TXSTART_INIT + len) >>8); 13. 14. /* 發送之前發送控制包格式字 */ 15. enc28j60WriteOp(ENC28J60_WRITE_BUF_MEM, 0, 0x00); 16. 17. /* 通過ENC28J60發送數據包 */ 18. enc28j60WriteBuffer(len, packet); 19. 20. /* 開始發送 */ 21. enc28j60WriteOp(ENC28J60_BIT_FIELD_SET,ECON1, ECON1_TXRTS); 22. 23. /* 復位發送邏輯的問題 */ 24. if((enc28j60Read(EIR) & EIR_TXERIF) ) 25. { 26. enc28j60SetBank(ECON1); 27. enc28j60WriteOp(ENC28J60_BIT_FIELD_CLR, ECON1, ECON1_TXRTS); 28. } 29. }</font> 復制代碼 5 ENC28J60讀操作 讀操作要略比寫操作復雜。寫操作時每次總是從硬件發送緩沖區的起始地址開始操作,而讀操作時需要不斷修改接收緩沖區的讀指針地址,該參數需要通過NextPacketPtr完成,該變量為長度為16的全局變量。讀操作時,先通過寄存器查看是否存在以太網數據包,讀EPKTCNT寄存器便可返回以太網數據包的個數;若存在以太網數據包則設定讀指針的地址,執行讀緩沖區操作,ENC28J60的以太網數據包中前兩個字節為下一個以太網數據包的起始地址,立即保存該參數至NextPacketPtr全局變量中;以太網數據包中的后兩個字節為該數據包的長度,該長度只從目標MAC地址開始的數據包的長度,進行處理時還應該舍棄最后的4字節CRC校驗結果;最重要的事情便是通過讀緩沖區操作碼把len長度的以太網數據讀出,讀出的目標應為軟件緩沖區,例如定義在程序中的rxtx_buf。最后根據NextPacketPtr移動讀指針以便下次操作,并通過操作ECON2的ECON2_PKTDEC位遞減了以太網數據包。 1. <font size="3">unsignedint enc28j60PacketReceive(unsigned int maxlen, unsigned char* packet) 2. { 3. unsigned intrxstat; 4. unsigned intlen; 5. 6. /* 是否收到以太網數據包 */ 7. if(enc28j60Read(EPKTCNT) == 0 ) 8. { 9. return(0); 10. } 11. 12. /* 設置接收緩沖器讀指針 */ 13. enc28j60Write(ERDPTL, (NextPacketPtr)); 14. enc28j60Write(ERDPTH, (NextPacketPtr)>>8); 15. 16. /* 接收數據包結構示例 數據手冊43頁 */ 17. 18. /* 讀下一個包的指針 */ 19. NextPacketPtr = enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0); 20. NextPacketPtr|= enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0)<<8; 21. 22. /* 讀包的長度 */ 23. len = enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0); 24. len |=enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0)<<8; 25. 26. /* 去除CRC校驗部分 */ 27. len-= 4; 28. 29. /* 讀取接收狀態 */ 30. rxstat = enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0); 31. rxstat |=enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0) << 8; 32. 33. /* 限制檢索的長度 */ 34. if (len > maxlen-1) 35. { 36. len = maxlen-1; 37. } 38. /* 檢查CRC和符號錯誤 */ 39. /* ERXFCON.CRCEN是默認設置。通常我們不需要檢查 */ 40. if ((rxstat & 0x80)==0) 41. { 42. //無效的 43. len = 0; 44. } 45. else 46. { 47. /* 從接收緩沖器中復制數據包 */ 48. enc28j60ReadBuffer(len,packet); 49. } 50. 51. /* 移動接收緩沖區 讀指針*/ 52. enc28j60Write(ERXRDPTL, (NextPacketPtr)); 53. enc28j60Write(ERXRDPTH, (NextPacketPtr)>>8); 54. 55. /* 數據包遞減 */ 56. enc28j60WriteOp(ENC28J60_BIT_FIELD_SET, ECON2, ECON2_PKTDEC); 57. 58. /* 返回長度 */ 59. return(len); 60. }</font> 復制代碼 6 ENC28J60初始化操作 ENC28J60的操作比較瑣碎。第一,進行CS端口的相關配置,即把該端口設置為輸出狀態,該部分代碼可以出現在任何硬件初始化代碼中,例如可以把所有的IO操作放入gpio_config中;第二,進行軟件復位,并通過查詢ESTAT的ESTAT_CLKRDY標志位確定是否復位完成;第二,初始化NextPacketPtr變量,該變量的初值為發送緩沖區的起始地址;第三,配置發送和接收緩沖區的區間;第四,若干參數配置,請看代碼注釋部分,ENC28J60具有自動填充0 的功能,即發送報文長度低于以太網最小報文長度時可以填充0至最小長度;第五,寫入MAC地址,由于ENC28J60內部沒有全球唯一的MAC地址,所以該地址需要軟件填寫。但是這種軟件填寫方式存在缺陷,實際應用中可以含有全球唯一的MAC地址的EEPROM,從EERPOM讀取MAC地址并用該地址初始化ENC28J60;第六,初始化中斷,并使能接收,ENC28J60含有多個中斷,最重要的有全局中斷和數據包帶接收中斷。 1. <font size="3">voidenc28j60Init(unsigned char* macaddr) 2. { 3. /* CS端口為輸出 */ 4. DDRB |= (1<<4); 5. 6. /* 禁止ENC28J60 */ 7. ENC28J60_CSH(); 8. /* ENC28J60軟件復位 該函數可以改進*/ 9. enc28j60WriteOp(ENC28J60_SOFT_RESET, 0, ENC28J60_SOFT_RESET); 10. /*查詢ESTAT.CLKRDY位*/ 11. while(!(enc28j60Read(ESTAT) & ESTAT_CLKRDY)); 12. 13. /* 設置接收緩沖區起始地址 該變量用于每次讀取緩沖區時保留下一個包的首地址 */ 14. NextPacketPtr= RXSTART_INIT; 15. 16. /* 設置接收緩沖區 起始指針*/ 17. enc28j60Write(ERXSTL, RXSTART_INIT & 0xFF); 18. enc28j60Write(ERXSTH, RXSTART_INIT >> 8); 19. 20. /* 設置接收緩沖區 讀指針*/ 21. enc28j60Write(ERXRDPTL, RXSTART_INIT&0xFF); 22. enc28j60Write(ERXRDPTH, RXSTART_INIT>>8); 23. 24. /* 設置接收緩沖區 結束指針 */ 25. enc28j60Write(ERXNDL,RXSTOP_INIT&0xFF); 26. enc28j60Write(ERXNDH, RXSTOP_INIT>>8); 27. 28. /* 設置發送緩沖區 起始指針 */ 29. enc28j60Write(ETXSTL, TXSTART_INIT&0xFF); 30. enc28j60Write(ETXSTH, TXSTART_INIT>>8); 31. /* 設置發送緩沖區 結束指針 */ 32. enc28j60Write(ETXNDL,TXSTOP_INIT&0xFF); 33. enc28j60Write(ETXNDH, TXSTOP_INIT>>8); 34. 35. /* 使能單播過濾 使能CRC校驗 使能 格式匹配自動過濾*/ 36. enc28j60Write(ERXFCON, ERXFCON_UCEN|ERXFCON_CRCEN|ERXFCON_PMEN); 37. enc28j60Write(EPMM0, 0x3f); 38. enc28j60Write(EPMM1, 0x30); 39. enc28j60Write(EPMCSL, 0xf9); 40. enc28j60Write(EPMCSH, 0xf7); 41. 42. /* 使能MAC接收 允許MAC發送暫停控制幀 當接收到暫停控制幀時停止發送*/ 43. /* 數據手冊34頁 */ 44. enc28j60Write(MACON1, MACON1_MARXEN|MACON1_TXPAUS|MACON1_RXPAUS); 45. 46. /* 退出復位狀態 */ 47. enc28j60Write(MACON2, 0x00); 48. 49. /* 用0填充所有短幀至60字節長 并追加一個CRC 發送CRC使能 幀長度校驗使能 MAC全雙工使能*/ 50. /* 提示 由于ENC28J60不支持802.3的自動協商機制, 所以對端的網絡卡需要強制設置為全雙工 */ 51. enc28j60WriteOp(ENC28J60_BIT_FIELD_SET, MACON3,MACON3_PADCFG0|MACON3_TXCRCEN|MACON3_FRMLNEN|MACON3_FULDPX); 52. 53. /* 填入默認值 */ 54. enc28j60Write(MAIPGL,0x12); 55. /* 填入默認值 */ 56. enc28j60Write(MAIPGH, 0x0C); 57. /* 填入默認值 */ 58. enc28j60Write(MABBIPG, 0x15); 59. 60. /* 最大幀長度 */ 61. enc28j60Write(MAMXFLL, MAX_FRAMELEN & 0xFF); 62. enc28j60Write(MAMXFLH, MAX_FRAMELEN >> 8); 63. 64. /* 寫入MAC地址 */ 65. enc28j60Write(MAADR5, macaddr[0]); 66. enc28j60Write(MAADR4, macaddr[1]); 67. enc28j60Write(MAADR3, macaddr[2]); 68. enc28j60Write(MAADR2, macaddr[3]); 69. enc28j60Write(MAADR1, macaddr[4]); 70. enc28j60Write(MAADR0, macaddr[5]); 71. 72. /* 配置PHY為全雙工 LEDB為拉電流 */ 73. enc28j60PhyWrite(PHCON1, PHCON1_PDPXMD); 74. 75. /* LED狀態 */ 76. enc28j60PhyWrite(PHLCON,0x0476); 77. 78. /* 半雙工回環禁止 */ 79. enc28j60PhyWrite(PHCON2, PHCON2_HDLDIS); 80. 81. /* 返回BANK0 */ 82. enc28j60SetBank(ECON1); 83. 84. /* 使能中斷 全局中斷 接收中斷 接收錯誤中斷 */ 85. enc28j60WriteOp(ENC28J60_BIT_FIELD_SET, EIE, EIE_INTIE|EIE_PKTIE|EIE_RXERIE); 86. 87. /* 接收使能位 */ 88. enc28j60WriteOp(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_RXEN); 89. }</font> 復制代碼
7 總結
ENC28J60的驅動編寫算是比較復雜的。但是回過頭來看看,其他的以太網驅動芯片的操作和ENC28J60的操作類似,其操作的核心即時數KB的硬件緩沖區。本例不能給出合適的運行范例,因為以太網驅動芯片要配合以太網協議棧來實現,而以太網協議棧內容很多,即使通過uIP或是lwIP也必須面對繁多的基礎知識。ENC28J60的驅動是以太網協議棧實現的基礎,通過ENC28J60還將會分析uIP協議棧,lwIP協議棧的應用。在實現TCP通信之后,還將會結合AVRNET或uIP,lwIP協議棧實現web服務器,通過網頁交換數據。
|