標籤:
和其它網卡晶片不同,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下拉電阻,因此可懸空該引腳,或作為網卡晶片喚醒輸出用。其它型號請參考相應的資料手冊。
圖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);
/*以上部分是利用一個IO口控制DM9000的RST引腳,使其複位。這一步可以省略,可以用下面的軟體複位代替*/
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的複位操作*/
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地址
/*以上儲存MAC地址(網卡物理地址)到晶片中去,這裡沒有用EEPROM,所以需要自己寫進去*/
/*關於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);
/*這兩句是將要發送資料的長度告訴DM9000的寄存器*/
DM_ADD = MWCMD;//這裡的寫法是針對有匯流排介面的處理器,沒有匯流排介面的處理器要注意加上時序。
for(i=0; i<len; i+=2)//16 bit mode
{
udelay(20);
DM_CMD = datas[i] | (datas[i+1]<<8);
}
/*上面是將要發送的資料寫到DM9000的內部SRAM中的寫FIFO中,注意沒有匯流排介面的處理器要加上適當的時序*/
/*只需要向這個寄存器中寫資料即可,MWCMD是DM9000內部SRAM的DMA指標,根據處理器模式,寫後自動增加*/
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; //資料包長度
/*以上為有效資料包前的4個狀態位元組*/
if(dm9000_reg_read(ISR) & 0x01)
{
dm9000_reg_write(ISR, 0x01);
}
/*清除接收中斷標誌位*/
/***********************************************************************************/
/*這個地方遇到了問題,下面的黑色字型語句應該替換成成紅色字型,也就是說MRCMDX寄存器如果第一次讀不到資料,還要讀一次才能確定完全沒有資料。
在做 PING 實驗時證明:每個資料包都是通過第二次的讀取MRCMDX寄存器操作而獲知為有效資料包的,對初始化的寄存器做了多次修改依然是此結果,但是用如下方法來實現,絕不會漏掉資料包。*/
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;
}
}
/* ready = dm9000_reg_read(MRCMDX); // read a byte without pointer increment
if(!(ready & 0x01))
{
return 0;
}*/
/***********************************************************************************/
/*以上表示若接收到的第一個位元組不是“01H”,則表示沒有資料包,返回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;
/*以上對接收到的資料包作一些必要的限制,去除大資料包,去除非ARP或IP的資料包*/ 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協議進行詳細講解。
[轉]DM9000A調試(上)