轉自:http://www.cnblogs.com/xilentz/archive/2010/07/12/1775604.html
單片機驅動DM9000網卡晶片(詳細調試過程)【上】
單片機驅動DM9000網卡晶片(詳細調試過程)【上】
2009-03-04 11:13
和其它網卡晶片不同,DM9000系列網卡晶片在嵌入式開發闆上很常見,尤其是有關ARM-Linux的開發闆上的網絡連接配接部分幾乎都是采用該晶片完成的。當然,其它網卡晶片,如RTL8019的應用也很常見,在很多開發闆上得到應用然而RTL8019的介紹在網上可以找到非常詳細的介紹,尤其是用單片機對其做底層驅動的介紹非常豐富。下面的網站就介紹了用AVR驅動RTL8019網卡晶片的非常詳細的過程,有興趣的朋友可以參考一下。
http://members.home.nl/bzijlstra/software/examples/RTL8019as.htm AVR驅動RTL8019網卡晶片的詳細介紹。
言歸正傳。在網上也能找到許多關于DM9000網卡晶片的介紹,然而這些介紹大多是關于Linux或WinCE下的驅動程式或移植,很少有介紹單片機驅動DM9000的例子。是以我在這裡把我調試DM9000E的過程詳細說明一下,僅供參考。
本文主要介紹單片機驅動DM9000E網卡晶片的詳細過程。從網卡電路的連接配接,到網卡初始化相關程式調試,再到ARP協定的實作,一步一步詳細介紹調試過程。如果有時間也會把UDP和TCP通訊實驗過程寫出來。當然,會用單片機編寫DM9000的驅動,再想編寫ARM下的Linux的驅動就容易的多了。在調試之前,應該先參考兩份技術文檔,可以從下面網站中下載下傳。
DM9000E.pdf(晶片資料資料)和 DM9000 Application Notes Ver 1_22 061104.pdf(應用手冊)
http://www.davicom.com.tw
或者
DM9000 Datasheet VF03:
http://www.davicom.com.tw/userfile/24247/DM9000-DS-F03-041906_1.pdf
DM9000A Datasheet:
http://www.davicom.com.tw/userfile/24247/DM9000A-DS-F01-101906.pdf
DM9000 Application Notes V1.22
http://www.davicom.com.tw/big5/download/Data%20Sheet/DM9000_Application_Notes_Ver_1_22%20061104.pdf
一、電路連接配接
DM9000E網卡晶片支援8位、16位、32位模式的處理器,通過晶片引腳EEDO(65腳)和WAKEUP(79腳)的複位值設定支援的處理器類型,如16位處理器隻需将這兩個引腳接低電平即可,其中WAKEUP内部有60K下拉電阻,是以可懸空該引腳,或作為網卡晶片喚醒輸出用。其它型号請參考相應的資料手冊。
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyZwpmLi1WdoR3XxADMldWYtl2XwlGbj9CX0YTO58VZzITZ4ETO0QGN5MzLcJXZ0lmcXVmdpx0c39GZul2Vvwle05WZslGevwVbvN2Xzd2bsJmbj9CXt92YuM3ZvxmYuNmLzV2Zh1Wavw1LcpDc0RHaiojIsJye.jpg)
圖1 DM9000引腳
如圖所示,對處理器驅動網卡晶片來說,我們比較關心的有以下幾個引腳:IOR、IOW、AEN、CMD(SA2)、INT、RST,以及資料引腳SD0-SD15-SD31和位址引腳SA4-SA9。其中,位址引腳配合AEN引腳來選通該網卡晶片,對于大多數的應用來說沒有意義,因為在我們的應用中一般隻用一個網卡晶片,而這些位址引腳主要用于在多網卡晶片環境下選擇其中之一。DM9000工作的預設基位址為0x300,這裡我們按照預設位址選擇,将SA9、SA8接高電平,SA7-DA4接低電平。多網卡環境可以根據TXD0-TXD3配置SA4-SA7來選擇不同的網卡,這裡不做介紹,有興趣的朋友請參考應用手冊和資料手冊。資料引腳SD0-SD31則根據前面所講的配置處理器模式與處理器的資料總線進行選擇連接配接即可,沒用到的引腳懸空。那麼,除了位址、資料引腳外,剩下的與處理器有關引腳對我們來說及其重要了,而與處理器無關的引腳,隻需按照應用手冊連接配接即可。
IOR和IOW是DM9000的讀寫選擇引腳,低電平有效,即低電平時進行讀(IOR)寫(IOW)操作;AEN是晶片選通引腳,低電平有效,該引腳為低時才能進行讀寫操作;CMD的指令/資料切換引腳,低電平時讀寫指令操作,高電平時讀寫資料操作。
圖2 讀時序
圖3 寫時序
這些引腳接口和其它單片機外圍器件的引腳接口基本相同,其使用也一樣。對于有總線接口的單片機來說,如51系列,ARM等直接連接配接即可。對于沒有總線接口的來說,如AVR mega32等可以直接用I/O引腳模拟總線時序進行連接配接。連接配接時要參考讀寫時序,如上圖所示。具體連接配接電路,有時間我再畫出來,暫時先略了。
二、編寫驅動程式
在這,我使用C語言編寫驅動程式,這需要非常注意一點,即處理器所用的C編譯器使用“大端格式”還是“小端格式”,這可以在相應處理器的C編譯器說明上找到。一般比較常見的是小端格式。而對于8位處理器來說,在編寫驅動程式時,可以不考慮,但是在編寫網絡協定的時候,一定好考慮,因為網絡協定的格式是大端格式,而大部分編譯器或者我們習慣的是小端格式,這一點需要注意。
在DM9000中,隻有兩個可以直接被處理器通路的寄存器,這裡命名為CMD端口和DATA端口。事實上,DM9000中有許多控制和狀态寄存器(這些寄存器在上一篇文章中有詳細的使用說明),但它們都不能直接被處理器通路,通路這些控制、狀态寄存器的方法是:
(1)、将寄存器的位址寫到CMD端口;
(2)、從DATA端口讀寫寄存器中的資料;
1、讀、寫寄存器
其實,INDEX端口和DATA端口的就是由晶片上的CMD引腳來區分的。低電平為INDEX端口,高電平為DATA端口。是以,要想實作讀寫寄存器,就必須先控制好CMD引腳。
若使用總線接口連接配接DM9000的話,假設總線連接配接後晶片的基位址為0x800300(24根位址總線),隻需如下方法:
#define DM_ADD (*((volatile unsigned int *) 0x8000300))
#define DM_CMD (*((volatile unsigned int *) 0x8000304))
//向DM9000寄存器寫資料
void dm9000_reg_write(unsigned char reg, unsigned char data)
{
udelay(20);//之前定義的微妙級延時函數,這裡延時20us
DM_ADD = reg;//将寄存器位址寫到INDEX端口
udelay(20);
DM_CMD = data;//将資料寫到DATA端口,即寫進寄存器
}
//從DM9000寄存器讀資料
unsigned int dm9000_reg_read(unsigned char reg)
{
udelay(20);
DM_ADD = reg;
udelay(20);
return DM_CMD;//将資料從寄存器中讀出
}
隻得注意的是前面的兩個宏定義DM_ADD和DM_CMD,定義的内容表示指向無符号整形變量的指針,在這裡0x800300是DM9000指令端口的位址,對它的指派操作就相當于把資料寫到該位址中,即把資料寫到DM9000的指令端口中。讀的道理也一樣。這是一種很常見的宏定義,一般在處理器中定義通用寄存器也是這樣定義的。
若沒有總線接口的話,可以使用IO口模拟總線時序的方法實作寄存器的讀寫。這裡隻說明實作步驟。首先将處理器的I/O端口與DM9000的IOR等引腳直接相連(電平比對的情況下),又假設已經有宏定義“IOR”I/O端口控制DM9000的IOR引腳,其它端口控制DM9000引腳的命名相同,“PIO1”(根據處理器情況,可以是8位、16位或32位的I/O端口組成)控制資料端口。這樣宏命名更直覺些。寫寄存器的函數如下:
void dm9000_reg_write(unsigned char reg, unsigned char data)
{
PIO1 = reg;
AEN = 0;
CMD = 0;
IOR = 1;
IOW = 0;
udelay(1);
AEN = 1;
IOW = 1;
udelay(20);
PIO1 = data;
AEN = 0;
CMD = 0;
IOR = 1;
IOW = 0;
udelay(1);
AEN = 1;
IOW = 1;
}
讀寄存器的寫法類似,這裡就略一下了。這一過程看上去有些複雜,呵呵,其實執行起來也蠻有效率的,執行時間差不多。這種模拟總線時序的方式實際并不複雜,隻是把總線方式下自動執行的過程手動的執行了一遍而已。
在DM9000中,還有一些PHY寄存器,也稱之為媒體無關接口MII(Media Independent Interface)寄存器。對這些寄存器的操作會影響網卡晶片的初始化和網絡連接配接,這裡不對其進行操作,是以對這些寄存器的通路方法這裡也略了(在上篇文章中有介紹)。操作不當反而使網卡不能連接配接到網絡。
至此,我們已經寫好了兩個最基本的函數:dm9000_reg_write()和dm9000_reg_read(),以及前面的宏定義DM_ADD和DM_CMD。下面将一直用到。
2、初始化DM9000網卡晶片。
初始化DM9000網卡晶片的過程,實質上就是填寫、設定DM9000的控制寄存器的過程,這裡以程式為例進行說明。其中寄存器的名稱宏定義在DM9000.H中已定義好。
注:一下函數中unsigned char為一個位元組unsigned int為兩個位元組
//DM9000初始化
void DM9000_init(void)
{
unsigned int i;
IO0DIR |= 1 << 8;
IO1CLR |= 1 << 8;
udelay(500000);
IO2SET |= 1 << 8;
udelay(500000);
IO1CLR |= 1 << 8;
udelay(500000);
dm9000_reg_write(GPCR, 0x01);//設定 GPCR(1EH) bit[0]=1,使DM9000的GPIO3為輸出。
dm9000_reg_write(GPR, 0x00);//GPR bit[0]=0 使DM9000的GPIO3輸出為低以激活内部PHY。
udelay(5000);//延時2ms以上等待PHY上電。
dm9000_reg_write(NCR, 0x03);//軟體複位
udelay(30);//延時20us以上等待軟體複位完成
dm9000_reg_write(NCR, 0x00);//複位完成,設定正常工作模式。
dm9000_reg_write(NCR, 0x03);//第二次軟體複位,為了確定軟體複位完全成功。此步驟是必要的。
udelay(30);
dm9000_reg_write(NCR, 0x00);
dm9000_reg_write(NSR, 0x2c);//清除各種狀态标志位
dm9000_reg_write(ISR, 0x3f);//清除所有中斷标志位
dm9000_reg_write(RCR, 0x39);//接收控制
dm9000_reg_write(TCR, 0x00);//發送控制
dm9000_reg_write(BPTR, 0x3f);
dm9000_reg_write(FCTR, 0x3a);
dm9000_reg_write(RTFCR, 0xff);
dm9000_reg_write(SMCR, 0x00);
for(i=0; i<6; i++)
dm9000_reg_write(PAR + i, mac_addr[i]);//mac_addr[]自己定義一下吧,6個位元組的MAC位址
dm9000_reg_write(NSR, 0x2c);
dm9000_reg_write(ISR, 0x3f);
dm9000_reg_write(IMR, 0x81);
}
這樣就對DM9000初始化完成了,怎麼樣,挺簡單的吧。
3、發送、接收資料包
同樣,以程式為例,通過注釋說明。
//發送資料包
//參數:datas為要發送的資料緩沖區(以位元組為機關),length為要發送的資料長度(兩個位元組)。
void sendpacket(unsigned char *datas, unsigned int length)
{
unsigned int len, i;
dm9000_reg_write(IMR, 0x80);//先禁止網卡中斷,防止在發送資料時被中斷幹擾
len = length;
dm9000_reg_write(TXPLH, (len>>8) & 0x0ff);
dm9000_reg_write(TXPLL, len & 0x0ff);
DM_ADD = MWCMD;//這裡的寫法是針對有總線接口的處理器,沒有總線接口的處理器要注意加上時序。
for(i=0; i<len; i+=2)//16 bit mode
{
udelay(20);
DM_CMD = datas[i] | (datas[i+1]<<8);
}
dm9000_reg_write(TCR, 0x01);//發送資料到以太網上
while((dm9000_reg_read(NSR) & 0x0c) == 0);//等待資料發送完成
udelay(20);
dm9000_reg_write(NSR, 0x2c);//清除狀态寄存器,由于發送資料沒有設定中斷,是以不必進行中斷标志位
dm9000_reg_write(IMR, 0x81);//DM9000網卡的接收中斷使能
}
以上是發送資料包,過程很簡單。而接收資料包确需要些說明了。DM9000從網絡中接到一個資料包後,會在資料包前面加上4個位元組,分别為“01H”、“status”(同RSR寄存器的值)、“LENL”(資料包長度低8位)、“LENH”(資料包長度高8位)。是以首先要讀取這4個位元組來确定資料包的狀态,第一個位元組“01H”表示接下來的是有效資料包,若為“00H”則表示沒有資料包,若為其它值則表示網卡沒有正确初始化,需要從新初始化。
如果接收到的資料包長度小于60位元組,則DM9000會自動為不足的位元組補上0,使其達到60位元組。同時,在接收到的資料包後DM9000還會自動添加4個CRC校驗位元組。可以不予處理。于是,接收到的資料包的最小長度也會是64位元組。當然,可以根據TCP/IP協定從首部位元組中出有效位元組數,這部分在後面講解。下面為接收資料包的函數。
//接收資料包
//參數:datas為接收到是資料存儲位置(以位元組為機關)
//傳回值:接收成功傳回資料包類型,不成功傳回0
unsigned int receivepacket(unsigned char *datas)
{
unsigned int i, tem;
unsigned int status, len;
unsigned char ready;
ready = 0;//希望讀取到“01H”
status = 0;//資料包狀态
len = 0; //資料包長度
if(dm9000_reg_read(ISR) & 0x01)
{
dm9000_reg_write(ISR, 0x01);
}
ready = dm9000_reg_read(MRCMDX); // 第一次讀取,一般讀取到的是 00H
if((ready & 0x0ff) != 0x01)
{
ready = dm9000_reg_read(MRCMDX); // 第二次讀取,總能擷取到資料
if((ready & 0x01) != 0x01)
{
if((ready & 0x01) != 0x00) //若第二次讀取到的不是 01H 或 00H ,則表示沒有初始化成功
{
dm9000_reg_write(IMR, 0x80);//螢幕網卡中斷
DM9000_init();//重新初始化
dm9000_reg_write(IMR, 0x81);//打開網卡中斷
}
retrun 0;
}
}
status = dm9000_reg_read(MRCMD);
udelay(20);
len = DM_CMD;
if(!(status & 0xbf00) && (len < 1522))
{
for(i=0; i<len; i+=2)// 16 bit mode
{
udelay(20);
tem = DM_CMD;
datas[i] = tem & 0x0ff;
datas[i + 1] = (tem >> 8) & 0x0ff;
}
}
else
{
return 0;
}
if(len > 1000) return 0;
if( (HON( ETHBUF->type ) != ETHTYPE_ARP) &&
(HON( ETHBUF->type ) != ETHTYPE_IP) )
{
return 0;
}
packet_len = len;
return HON( ETHBUF->type ); //傳回資料包的類型,這裡隻選擇是ARP或IP兩種類型
}
注意:上面的函數用到了一些宏定義,已經在頭檔案中定義過,這裡說明一下:其中uint16定義為兩個位元組的變量,根據C編譯器進行定義。
unsigned char Buffer[1000];//定義了一個1000位元組的接收發送緩沖區
uint16 packet_len;//接收、發送資料包的長度,以位元組為機關。
struct eth_hdr //以太網頭部結構,為了以後使用友善
{
unsigned char d_mac[6]; //目的位址
unsigned char s_mac[6]; //源位址
uint16 type; //協定類型
};
struct arp_hdr //以太網頭部+ARP首部結構
{
struct eth_hdr ethhdr; //以太網首部
uint16 hwtype; //硬體類型(1表示傳輸的是以太網MAC位址)
uint16 protocol; //協定類型(0x0800表示傳輸的是IP位址)
unsigned char hwlen; //硬體位址長度(6)
unsigned char protolen; //協定位址長度(4)
uint16 opcode; //操作(1表示ARP請求,2表示ARP應答)
unsigned char smac[6]; //發送端MAC位址
unsigned char sipaddr[4]; //發送端IP位址
unsigned char dmac[6]; //目的端MAC位址
unsigned char dipaddr[4]; //目的端IP位址
};
struct ip_hdr //以太網頭部+IP首部結構
{
struct eth_hdr ethhdr; //以太網首部
unsigned char vhl, //4位版本号4位首部長度(0x45)
tos; //服務類型(0)
uint16 len, //整個IP資料報總位元組長度
ipid, //IP辨別
ipoffset; //3位辨別13位偏移
unsigned char ttl, //生存時間(32或64)
proto; //協定(1表示ICMP,2表示IGMP,6表示TCP,17表示UDP)
uint16 ipchksum; //首部校驗和
unsigned char srcipaddr[4], //源IP
destipaddr[4]; //目的IP
};
以上定義的三種首部結構,是根據TCP/IP協定的相關規範定義的,後面會對ARP協定進行詳細講解。
單片機驅動DM9000網卡晶片(詳細調試過程)【下】
http://hi.baidu.com/mcu8031/blog/item/c95903138671c625dc540171.html
單片機驅動DM9000網卡晶片(詳細調試過程)【下】
4、驗證初始化中的各個函數。
下面我們來看一下,上面所寫的初始化函數是否可用。以上我們寫好了三個函數,分别為
DM9000_init(),sendpacket()和receivepacket(),儲存并命名為dm9000.c。既然我們要進行調試,當
然要有結果輸出,根據自己的處理器的情況寫一個序列槽程式,這些函數是學某個單片機的基礎,這裡不
做詳細介紹,用到是時候會在函數裡注釋一下。
接下來我們來寫個主函數,建立C檔案,命名為mian.c,填寫如下函數:
void main(void)
{
unsigned int i;
unsigned char c;
uart0_init();//初始化序列槽,調試時用到
DM9000_init();//初始化網卡
print_regs();
}
函數寫好,儲存檔案,連接配接硬體,連接配接網線到電腦上或區域網路上,運作結果如下圖所示:
圖4 顯示寄存器值
這裡首先檢查,各個控制寄存器是否是自己寫進去的值,在檢查狀态寄存器是否正确,其中主要要
看NSR寄存器的bit[6]是否為“1”,該位表示是否連接配接成功。本例中NSR的值為40H,括号裡的數為對應
的十進制數。
下面我們将主函數改進一下,增加個中斷接收函數,檢視是否能接收到資料。
void main(void)
{
unsigned int i;
unsigned char c;
uart0_init();//初始化序列槽,調試時用到
DM9000_init();//初始化網卡
sendpacket(60);
while(1);//等待中斷
}
void int_issue(void) //中斷處理函數,需要根據自己的處理器進行設定
{
unsigned int i;
i = receivepacket(Buffer);//将資料讀取到Buffer中。
int_again :
if(i == 0)
{
return;
}
else
{
print_buffer();//将接收到的所有資料列印出來
while(1);//停止在這裡等待觀察,注意:實際應用中是不允許停止在中斷中的。
}
i = receivepacket(Buffer);
if(i != 0)
{
goto int_again;
}
}
編譯調試,運作結果如下:
圖5 接收資料包中的資料
這是一個ARP應答包,包含了我電腦上的MAC位址和區域網路内的IP位址。反正我也不是啥重要人物,
這裡就不保密了,呵呵。
如果一些順利,到這裡對DM9000網卡晶片的初始化工作就完成了。如果出現問題,首先要
檢查寄存器的值是否正确。可以将DM9000中的寄存器列印出來,檢視到底是哪裡的問題。如果列印出的
值很混亂,在確定序列槽程式無誤的前提下,檢視硬體連接配接,以及寄存器讀寫時序是否正确,重複調試幾
次查找原因。
三、ARP協定的實作
1、ARP協定原理簡述
ARP協定(Address Resolution Protocol 位址解析協定),在區域網路中,網絡中實際傳輸的是“
幀”,幀裡面有目标主機的MAC位址。在以太網中,一個注意要和另一個主機進行直接通信,必須要知
道目标主機的MAC位址。這個MAC位址就是辨別我們的網卡晶片唯一性的位址。但這個目标MAC位址是如
何獲得的呢?這就用到了我們這裡講到的位址解析協定。所有“位址解析”,就是主機在發送幀前将目
标IP位址轉換成MAC位址的過程。ARP協定的基本功能就是通過目标裝置的IP位址,查詢目标裝置的MAC
位址,以保證通信的順利進行。是以在第一次通信前,我們知道目标機的IP位址,想要獲知目标機的
MAC位址,就要發送ARP封包(即ARP資料包)。它的傳輸過程簡單的說就是:我知道目标機的IP位址,
那麼我就向網絡中所有的機器發送一個ARP請求,請求中有目标機的IP位址,請求的意思是目标機要是
收到了此請求,就把你的MAC位址告訴我。如果目标機不存在,那麼此請求自然不會有人回應。若目标
機接收到了此請求,它就會發送一個ARP應答,這個應答是明确發給請求者的,應答中有MAC位址。我接
到了這個應答,我就知道了目标機的MAC位址,就可以進行以後的通信了。因為每次通信都要用到MAC地
址。
ARP封包被封裝在以太網幀頭部中傳輸,如圖為ARP請求封包的頭部格式。
圖6 用于以太網的ARP請求或應答分組格式
注意,以太網的傳輸存儲是“大端格式”,即先發送高位元組後發送低位元組。例如,兩個位元組的資料
,先發送高8位後發送低8位。是以接收資料的時候要注意存儲順序。
整個封包分成兩部分,以太網首部和ARP請求/應答。下面挑重點講述。
“以太網目的位址”字段:若是發送ARP請求,應填寫廣播類型的MAC位址FF-FF-FF-FF-FF-FF,意思是
讓網絡上的所有機器接收到;
“幀類型”字段:填寫08-06表示次封包是ARP協定;
“硬體類型”字段:填寫00-01表示以太網位址,即MAC位址;
“協定類型”字段:填寫08-00表示IP,即通過IP位址查詢MAC位址;
“硬體位址長度”字段:MAC位址長度為6(以位元組為機關);
“協定位址長度”字段:IP位址長度為4(以位元組為機關);
“操作類型”字段:ARP資料包類型,0表示ARP請求,1表示ARP應答;
“目的以太網位址”字段:若是發送ARP請求,這裡是需要目标機填充的。
2、ARP的處理程式
ARP協定原理很簡單,下面我們來編寫ARP協定的處理函數。建立檔案命名為arp.c,填寫如下函數
:
unsigned char mac_addr[6] = {*,*,*,*,*,*};
unsigned char ip_addr[4] = { 192, 168, *, * };
unsigned char host_ip_addr[4] = { 192, 168, *, * };
unsigned char host_mac_addr[6]={ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
unsigned char Buffer[1000];
uint16 packet_len;
#define HON(n) ((((uint16)((n) & 0xff)) << 8) | (((n) & 0xff00) >> 8))
void arp_request(void) //發送ARP請求資料包
{
//以太網首部
memcpy(ARPBUF->ethhdr.d_mac, host_mac_addr, 6);
memcpy(ARPBUF->ethhdr.s_mac, mac_addr, 6);
ARPBUF->ethhdr.type = HON( 0x0806 );
//ARP首部
ARPBUF->hwtype = HON( 1 );
ARPBUF->protocol = HON( 0x0800 );
ARPBUF->hwlen = 6;
ARPBUF->protolen = 4;
ARPBUF->opcode = HON( 0 );
memcpy(ARPBUF->smac, mac_addr, 6);
memcpy(ARPBUF->sipaddr, ip_addr, 4);
memcpy(ARPBUF->dipaddr, host_ip_addr, 4);
packet_len = 42;//14+28=42
sendpacket( Buffer, packet_len );
}
注釋:ARPBUF的宏定義和ARP首部結構,在前面已經講過。同時注意執行該函數時中斷的處理。這裡沒
作處理。
看上去很easy吧,下面函數實作接收ARP請求或接收ARP應答的處理。
unsigned char arp_process(void)//ARP接收函數,成功傳回1,否則傳回0
{
//簡單判斷ARP資料包有無損壞,有損壞則丢棄,不予處理
if( packet_len < 28 )//ARP資料長度為28位元組為無效資料
{
return 0;
}
switch ( HON( ARPBUF->opcode ) )
{
case 0 : //處理ARP請求
if( ARPBUF->dipaddr[0] == ip_addr[0] &&
ARPBUF->dipaddr[1] == ip_addr[1] &&
ARPBUF->dipaddr[2] == ip_addr[2] &&
ARPBUF->dipaddr[3] == ip_addr[3] )//判斷是否是自己的IP,是否向自己詢問MAC位址
。
{
ARPBUF->opcode = HON( 2 );//設定為ARP應答
memcpy(ARPBUF->dmac, ARPBUF->smac, 6);
memcpy(ARPBUF->ethhdr.d_mac, ARPBUF->smac, 6);
memcpy(ARPBUF->smac, mac_addr, 6);
memcpy(ARPBUF->ethhdr.s_mac, mac_addr, 6);
memcpy(ARPBUF->dipaddr, ARPBUF->sipaddr, 4);
memcpy(ARPBUF->sipaddr, ip_addr, 4);
ARPBUF->ethhdr.type = HON( 0x0806 );
packet_len = 42;
sendpacket( Buffer, packet_len );//發送ARP資料包
return 1;
}
else
{
return 0;
}
break;
case 1 : //處理ARP應答
if( ARPBUF->dipaddr[0] == ip_addr[0] &&
ARPBUF->dipaddr[1] == ip_addr[1] &&
ARPBUF->dipaddr[2] == ip_addr[2] &&
ARPBUF->dipaddr[3] == ip_addr[3] )//再次判斷IP,是否是給自己的應答
{
memcpy(host_mac_addr, ARPBUF->smac, 6);//儲存伺服器MAC位址
return 1;
}
else
{
return 0;
}
break;
default ://不是ARP協定
return 0;
}
}
根據ARP協定格式看這兩個函數并不困難。于是我們又得到兩個函數:arp_request()和
arp_process()。
3、ARP程式調試
下面我們修改主函數和中斷處理函數。
将mian()函數中的“sendpacket(60);”語句換成“arp_request();”語句。
void int_issue(void) //中斷處理函數,需要根據自己的處理器進行設定
{
unsigned int i;
i = receivepacket(Buffer);//将資料讀取到Buffer中。
if(i == 0)
{
return;
}
else
{
i = arp_process();
if(i == 1)//判斷是否是ARP協定
print_hostmacaddr();//列印目标機的MAC位址,就是用序列槽列印host_mac_addr[]中的6
個位元組
}
}
儲存運作調試。
圖7 主機MAC位址
至此,關于DM9000的調試過程就完成了。之後我還調試了UDP通訊、TCP通訊等,主要是關于協定的
處理了,這裡就不介紹了。有興趣的朋友可以參看《TCP/IP協定》第一卷,将會有很大幫助。希望這些
調試過程能為讀者或多或少的提供些有用的資訊,也歡迎大家和我一起讨論。