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