單片機(jī)驅(qū)動DM9000網(wǎng)卡芯片詳細(xì)調(diào)試過程
http://members.home.nl/bzijlstra/software/examples/RTL8019as.htm AVR驅(qū)動RTL8019網(wǎng)卡芯片的詳細(xì)介紹。
本文引用地址:http://butianyuan.cn/article/201610/310907.htm言歸正傳。在網(wǎng)上也能找到許多關(guān)于DM9000網(wǎng)卡芯片的介紹,然而這些介紹大多是關(guān)于Linux或WinCE下的驅(qū)動程序或移植,很少有介紹單片機(jī)驅(qū)動DM9000的例子。因此我在這里把我調(diào)試DM9000E的過程詳細(xì)說明一下,僅供參考。
本文主要介紹單片機(jī)驅(qū)動DM9000E網(wǎng)卡芯片的詳細(xì)過程。從網(wǎng)卡電路的連接,到網(wǎng)卡初始化相關(guān)程序調(diào)試,再到ARP協(xié)議的實現(xiàn),一步一步詳細(xì)介紹調(diào)試過程。如果有時間也會把UDP和TCP通訊實驗過程寫出來。當(dāng)然,會用單片機(jī)編寫DM9000的驅(qū)動,再想編寫ARM下的Linux的驅(qū)動就容易的多了。在調(diào)試之前,應(yīng)該先參考兩份技術(shù)文檔,可以從下面網(wǎng)站中下載。
DM9000E.pdf(芯片數(shù)據(jù)資料)和 DM9000 Application Notes Ver 1_22 061104.pdf(應(yīng)用手冊)
http://www.davicom.com.tw
一、電路連接
DM9000E網(wǎng)卡芯片支持8位、16位、32位模式的處理器,通過芯片引腳EEDO(65腳)和WAKEUP(79腳)的復(fù)位值設(shè)置支持的處理器類型,如16位處理器只需將這兩個引腳接低電平即可,其中WAKEUP內(nèi)部有60K下拉電阻,因此可懸空該引腳,或作為網(wǎng)卡芯片喚醒輸出用。其它型號請參考相應(yīng)的數(shù)據(jù)手冊。
圖1 DM9000引腳
如圖所示,對處理器驅(qū)動網(wǎng)卡芯片來說,我們比較關(guān)心的有以下幾個引腳:IOR、IOW、AEN、CMD(SA2)、INT、RST,以及數(shù)據(jù)引腳SD0-SD15-SD31和地址引腳SA4-SA9。其中,地址引腳配合AEN引腳來選通該網(wǎng)卡芯片,對于大多數(shù)的應(yīng)用來說沒有意義,因為在我們的應(yīng)用中一般只用一個網(wǎng)卡芯片,而這些地址引腳主要用于在多網(wǎng)卡芯片環(huán)境下選擇其中之一。DM9000工作的默認(rèn)基地址為0x300,這里我們按照默認(rèn)地址選擇,將SA9、SA8接高電平,SA7-DA4接低電平。多網(wǎng)卡環(huán)境可以根據(jù)TXD0-TXD3配置SA4-SA7來選擇不同的網(wǎng)卡,這里不做介紹,有興趣的朋友請參考應(yīng)用手冊和數(shù)據(jù)手冊。數(shù)據(jù)引腳SD0-SD31則根據(jù)前面所講的配置處理器模式與處理器的數(shù)據(jù)總線進(jìn)行選擇連接即可,沒用到的引腳懸空。那么,除了地址、數(shù)據(jù)引腳外,剩下的與處理器有關(guān)引腳對我們來說及其重要了,而與處理器無關(guān)的引腳,只需按照應(yīng)用手冊連接即可。
IOR和IOW是DM9000的讀寫選擇引腳,低電平有效,即低電平時進(jìn)行讀(IOR)寫(IOW)操作;AEN是芯片選通引腳,低電平有效,該引腳為低時才能進(jìn)行讀寫操作;CMD的命令/數(shù)據(jù)切換引腳,低電平時讀寫命令操作,高電平時讀寫數(shù)據(jù)操作。
圖2 讀時序
圖3 寫時序
這些引腳接口和其它單片機(jī)外圍器件的引腳接口基本相同,其使用也一樣。對于有總線接口的單片機(jī)來說,如51系列,ARM等直接連接即可。對于沒有總線接口的來說,如AVR mega32等可以直接用I/O引腳模擬總線時序進(jìn)行連接。連接時要參考讀寫時序,如上圖所示。具體連接電路,有時間我再畫出來,暫時先略了。
二、編寫驅(qū)動程序
在這,我使用C語言編寫驅(qū)動程序,這需要非常注意一點,即處理器所用的C編譯器使用“大端格式”還是“小端格式”,這可以在相應(yīng)處理器的C編譯器說明上找到。一般比較常見的是小端格式。而對于8位處理器來說,在編寫驅(qū)動程序時,可以不考慮,但是在編寫網(wǎng)絡(luò)協(xié)議的時候,一定好考慮,因為網(wǎng)絡(luò)協(xié)議的格式是大端格式,而大部分編譯器或者我們習(xí)慣的是小端格式,這一點需要注意。
在DM9000中,只有兩個可以直接被處理器訪問的寄存器,這里命名為CMD端口和DATA端口。事實上,DM9000中有許多控制和狀態(tài)寄存器(這些寄存器在上一篇文章中有詳細(xì)的使用說明),但它們都不能直接被處理器訪問,訪問這些控制、狀態(tài)寄存器的方法是:
(1)、將寄存器的地址寫到CMD端口;
(2)、從DATA端口讀寫寄存器中的數(shù)據(jù);
1、讀、寫寄存器
其實,INDEX端口和DATA端口的就是由芯片上的CMD引腳來區(qū)分的。低電平為INDEX端口,高電平為DATA端口。所以,要想實現(xiàn)讀寫寄存器,就必須先控制好CMD引腳。
若使用總線接口連接DM9000的話,假設(shè)總線連接后芯片的基地址為0x800300(24根地址總線),只需如下方法:
#define DM_ADD (*((volatile unsigned int *) 0x8000300))
#define DM_CMD (*((volatile unsigned int *) 0x8000304))
//向DM9000寄存器寫數(shù)據(jù)
void dm9000_reg_write(unsigned char reg, unsigned char data)
{
udelay(20);//之前定義的微妙級延時函數(shù),這里延時20us
DM_ADD = reg;//將寄存器地址寫到INDEX端口
udelay(20);
DM_CMD = data;//將數(shù)據(jù)寫到DATA端口,即寫進(jìn)寄存器
}
//從DM9000寄存器讀數(shù)據(jù)
unsigned int dm9000_reg_read(unsigned char reg)
{
udelay(20);
DM_ADD = reg;
udelay(20);
return DM_CMD;//將數(shù)據(jù)從寄存器中讀出
}
只得注意的是前面的兩個宏定義DM_ADD和DM_CMD,定義的內(nèi)容表示指向無符號整形變量的指針,在這里0x800300是DM9000命令端口的地址,對它的賦值操作就相當(dāng)于把數(shù)據(jù)寫到該地址中,即把數(shù)據(jù)寫到DM9000的命令端口中。讀的道理也一樣。這是一種很常見的宏定義,一般在處理器中定義通用寄存器也是這樣定義的。
若沒有總線接口的話,可以使用IO口模擬總線時序的方法實現(xiàn)寄存器的讀寫。這里只說明實現(xiàn)步驟。首先將處理器的I/O端口與DM9000的IOR等引腳直接相連(電平匹配的情況下),又假設(shè)已經(jīng)有宏定義“IOR”I/O端口控制DM9000的IOR引腳,其它端口控制DM9000引腳的命名相同,“PIO1”(根據(jù)處理器情況,可以是8位、16位或32位的I/O端口組成)控制數(shù)據(jù)端口。這樣宏命名更直觀些。寫寄存器的函數(shù)如下:
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;
}
讀寄存器的寫法類似,這里就略一下了。這一過程看上去有些復(fù)雜,呵呵,其實執(zhí)行起來也蠻有效率的,執(zhí)行時間差不多。這種模擬總線時序的方式實際并不復(fù)雜,只是把總線方式下自動執(zhí)行的過程手動的執(zhí)行了一遍而已。
在DM9000中,還有一些PHY寄存器,也稱之為介質(zhì)無關(guān)接口MII(Media Independent Interface)寄存器。對這些寄存器的操作會影響網(wǎng)卡芯片的初始化和網(wǎng)絡(luò)連接,這里不對其進(jìn)行操作,所以對這些寄存器的訪問方法這里也略了(在上篇文章中有介紹)。操作不當(dāng)反而使網(wǎng)卡不能連接到網(wǎng)絡(luò)。
至此,我們已經(jīng)寫好了兩個最基本的函數(shù):dm9000_reg_write()和dm9000_reg_read(),以及前面的宏定義DM_ADD和DM_CMD。下面將一直用到。
2、初始化DM9000網(wǎng)卡芯片。
初始化DM9000網(wǎng)卡芯片的過程,實質(zhì)上就是填寫、設(shè)置DM9000的控制寄存器的過程,這里以程序為例進(jìn)行說明。其中寄存器的名稱宏定義在DM9000.H中已定義好。
注:一下函數(shù)中unsigned char為一個字節(jié)unsigned int為兩個字節(jié)
//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引腳,使其復(fù)位。這一步可以省略,可以用下面的軟件復(fù)位代替*/
dm9000_reg_write(GPCR, 0x01);//設(shè)置 GPCR(1EH) bit[0]=1,使DM9000的GPIO3為輸出。
dm9000_reg_write(GPR, 0x00);//GPR bit[0]=0 使DM9000的GPIO3輸出為低以激活內(nèi)部PHY。
udelay(5000);//延時2ms以上等待PHY上電。
dm9000_reg_write(NCR, 0x03);//軟件復(fù)位
udelay(30);//延時20us以上等待軟件復(fù)位完成
dm9000_reg_write(NCR, 0x00);//復(fù)位完成,設(shè)置正常工作模式。
dm9000_reg_write(NCR, 0x03);//第二次軟件復(fù)位,為了確保軟件復(fù)位完全成功。此步驟是必要的。
udelay(30);
dm9000_reg_write(NCR, 0x00);
/*以上完成了DM9000的復(fù)位操作*/
dm9000_reg_write(NSR, 0x2c);//清除各種狀態(tài)標(biāo)志位
dm9000_reg_write(ISR, 0x3f);//清除所有中斷標(biāo)志位
/*以上清除標(biāo)志位*/
dm9000_reg_write(RCR, 0x39);//接收控制
dm9000_reg_write(TCR, 0x00);//發(fā)送控制
dm9000_reg_write(BPTR, 0x3f);
dm9000_reg_write(FCTR, 0x3a);
dm9000_reg_write(RTFCR, 0xff);
dm9000_reg_write(SMCR, 0x00);
/*以上是功能控制,具體功能參考上一篇文章中的說明,或參考數(shù)據(jù)手冊的介紹*/
for(i=0; i<6; i++)
dm9000_reg_write(PAR + i, mac_addr[i]);//mac_addr[]自己定義一下吧,6個字節(jié)的MAC地址
/*以上存儲MAC地址(網(wǎng)卡物理地址)到芯片中去,這里沒有用EEPROM,所以需要自己寫進(jìn)去*/
/*關(guān)于MAC地址的說明,要參考網(wǎng)絡(luò)相關(guān)書籍或資料*/
dm9000_reg_write(NSR, 0x2c);
dm9000_reg_write(ISR, 0x3f);
/*為了保險,上面有清除了一次標(biāo)志位*/
dm9000_reg_write(IMR, 0x81);
/*中斷使能(或者說中斷屏蔽),即開啟我們想要的中斷,關(guān)閉不想要的,這里只開啟的一個接收中斷*/
/*以上所有寄存器的具體含義參考上一篇文章,或參考數(shù)據(jù)手冊*/
}
這樣就對DM9000初始化完成了,怎么樣,挺簡單的吧。
3、發(fā)送、接收數(shù)據(jù)包
同樣,以程序為例,通過注釋說明。
//發(fā)送數(shù)據(jù)包
//參數(shù):datas為要發(fā)送的數(shù)據(jù)緩沖區(qū)(以字節(jié)為單位),length為要發(fā)送的數(shù)據(jù)長度(兩個字節(jié))。
void sendpacket(unsigned char *datas, unsigned int length)
{
unsigned int len, i;
dm9000_reg_write(IMR, 0x80);//先禁止網(wǎng)卡中斷,防止在發(fā)送數(shù)據(jù)時被中斷干擾
len = length;
dm9000_reg_write(TXPLH, (len>>8) & 0x0ff);
dm9000_reg_write(TXPLL, len & 0x0ff);
/*這兩句是將要發(fā)送數(shù)據(jù)的長度告訴DM9000的寄存器*/
DM_ADD = MWCMD;//這里的寫法是針對有總線接口的處理器,沒有總線接口的處理器要注意加上時序。
for(i=0; i
{
udelay(20);
DM_CMD = datas[i] | (datas[i+1]<<8);
}
/*上面是將要發(fā)送的數(shù)據(jù)寫到DM9000的內(nèi)部SRAM中的寫FIFO中,注意沒有總線接口的處理器要加上適當(dāng)?shù)臅r序*/
/*只需要向這個寄存器中寫數(shù)據(jù)即可,MWCMD是DM9000內(nèi)部SRAM的DMA指針,根據(jù)處理器模式,寫后自動增加*/
dm9000_reg_write(TCR, 0x01);//發(fā)送數(shù)據(jù)到以太網(wǎng)上
while((dm9000_reg_read(NSR) & 0x0c) == 0);//等待數(shù)據(jù)發(fā)送完成
udelay(20);
dm9000_reg_write(NSR, 0x2c);//清除狀態(tài)寄存器,由于發(fā)送數(shù)據(jù)沒有設(shè)置中斷,因此不必處理中斷標(biāo)志位
dm9000_reg_write(IMR, 0x81);//DM9000網(wǎng)卡的接收中斷使能
}
以上是發(fā)送數(shù)據(jù)包,過程很簡單。而接收數(shù)據(jù)包確需要些說明了。DM9000從網(wǎng)絡(luò)中接到一個數(shù)據(jù)包后,會在數(shù)據(jù)包前面加上4個字節(jié),分別為“01H”、“status”(同RSR寄存器的值)、“LENL”(數(shù)據(jù)包長度低8位)、“LENH”(數(shù)據(jù)包長度高8位)。所以首先要讀取這4個字節(jié)來確定數(shù)據(jù)包的狀態(tài),第一個字節(jié)“01H”表示接下來的是有效數(shù)據(jù)包,若為“00H”則表示沒有數(shù)據(jù)包,若為其它值則表示網(wǎng)卡沒有正確初始化,需要從新初始化。
如果接收到的數(shù)據(jù)包長度小于60字節(jié),則DM9000會自動為不足的字節(jié)補上0,使其達(dá)到60字節(jié)。同時,在接收到的數(shù)據(jù)包后DM9000還會自動添加4個CRC校驗字節(jié)??梢圆挥杼幚?。于是,接收到的數(shù)據(jù)包的最小長度也會是64字節(jié)。當(dāng)然,可以根據(jù)TCP/IP協(xié)議從首部字節(jié)中出有效字節(jié)數(shù),這部分在后面講解。下面為接收數(shù)據(jù)包的函數(shù)。
//接收數(shù)據(jù)包
//參數(shù):datas為接收到是數(shù)據(jù)存儲位置(以字節(jié)為單位)
//返回值:接收成功返回數(shù)據(jù)包類型,不成功返回0
unsigned int receivepacket(unsigned char *datas)
{
unsigned int i, tem;
unsigned int status, len;
unsigned char ready;
ready = 0;//希望讀取到“01H”
status = 0;//數(shù)據(jù)包狀態(tài)
len = 0; //數(shù)據(jù)包長度
/*以上為有效數(shù)據(jù)包前的4個狀態(tài)字節(jié)*/
if(dm9000_reg_read(ISR) & 0x01)
{
dm9000_reg_write(ISR, 0x01);
}
/*清除接收中斷標(biāo)志位*/
/***********************************************************************************/
/*這個地方遇到了問題,下面的黑色字體語句應(yīng)該替換成成紅色字體,也就是說MRCMDX寄存器如果第一次讀不到數(shù)據(jù),還要讀一次才能確定完全沒有數(shù)據(jù)。
在做 PING 實驗時證明:每個數(shù)據(jù)包都是通過第二次的讀取MRCMDX寄存器操作而獲知為有效數(shù)據(jù)包的,對初始化的寄存器做了多次修改依然是此結(jié)果,但是用如下方法來實現(xiàn),絕不會漏掉數(shù)據(jù)包。*/
ready = dm9000_reg_read(MRCMDX); // 第一次讀取,一般讀取到的是 00H
if((ready & 0x0ff) != 0x01)
{
ready = dm9000_reg_read(MRCMDX); // 第二次讀取,總能獲取到數(shù)據(jù)
if((ready & 0x01) != 0x01)
{
if((ready & 0x01) != 0x00) //若第二次讀取到的不是 01H 或 00H ,則表示沒有初始化成功
{
dm9000_reg_write(IMR, 0x80);//屏幕網(wǎng)卡中斷
DM9000_init();//重新初始化
dm9000_reg_write(IMR, 0x81);//打開網(wǎng)卡中斷
}
retrun 0;
}
}
/* ready = dm9000_reg_read(MRCMDX); // read a byte without pointer increment
if(!(ready & 0x01))
{
return 0;
}*/
/***********************************************************************************/
/*以上表示若接收到的第一個字節(jié)不是“01H”,則表示沒有數(shù)據(jù)包,返回0*/
status = dm9000_reg_read(MRCMD);
udelay(20);
len = DM_CMD;
if(!(status & 0xbf00) && (len < 1522))
{
for(i=0; i
{
udelay(20);
tem = DM_CMD;
datas[i] = tem & 0x0ff;
datas[i + 1] = (tem >> 8) & 0x0ff;
}
}
else
{
return 0;
}
/*以上接收數(shù)據(jù)包,注意的地方與發(fā)送數(shù)據(jù)包的地方相同*/
if(len > 1000) return 0;
if( (HON( ETHBUF->type ) != ETHTYPE_ARP) &&
(HON( ETHBUF->type ) != ETHTYPE_IP) )
{
return 0;
}
packet_len = len;
/*以上對接收到的數(shù)據(jù)包作一些必要的限制,去除大數(shù)據(jù)包,去除非ARP或IP的數(shù)據(jù)包*/
return HON( ETHBUF->type ); //返回數(shù)據(jù)包的類型,這里只選擇是ARP或IP兩種類型
}
注意:上面的函數(shù)用到了一些宏定義,已經(jīng)在頭文件中定義過,這里說明一下:其中uint16定義為兩個字節(jié)的變量,根據(jù)C編譯器進(jìn)行定義。
unsigned char Buffer[1000];//定義了一個1000字節(jié)的接收發(fā)送緩沖區(qū)
uint16 packet_len;//接收、發(fā)送數(shù)據(jù)包的長度,以字節(jié)為單位。
struct eth_hdr //以太網(wǎng)頭部結(jié)構(gòu),為了以后使用方便
{
unsigned char d_mac[6]; //目的地址
unsigned char s_mac[6]; //源地址
uint16 type; //協(xié)議類型
};
struct arp_hdr //以太網(wǎng)頭部+ARP首部結(jié)構(gòu)
{
struct eth_hdr ethhdr; //以太網(wǎng)首部
uint16 hwtype; //硬件類型(1表示傳輸?shù)氖且蕴W(wǎng)MAC地址)
uint16 protocol; //協(xié)議類型(0x0800表示傳輸?shù)氖荌P地址)
unsigned char hwlen; //硬件地址長度(6)
unsigned char protolen; //協(xié)議地址長度(4)
uint16 opcode; //操作(1表示ARP請求,2表示ARP應(yīng)答)
unsigned char smac[6]; //發(fā)送端MAC地址
unsigned char sipaddr[4]; //發(fā)送端IP地址
unsigned char dmac[6]; //目的端MAC地址
unsigned char dipaddr[4]; //目的端IP地址
};
struct ip_hdr //以太網(wǎng)頭部+IP首部結(jié)構(gòu)
{
struct eth_hdr ethhdr; //以太網(wǎng)首部
unsigned char vhl, //4位版本號4位首部長度(0x45)
tos; //服務(wù)類型(0)
uint16 len, //整個IP數(shù)據(jù)報總字節(jié)長度
ipid, //IP標(biāo)識
ipoffset; //3位標(biāo)識13位偏移
unsigned char ttl, //生存時間(32或64)
proto; //協(xié)議(1表示ICMP,2表示IGMP,6表示TCP,17表示UDP)
uint16 ipchksum; //首部校驗和
unsigned char srcipaddr[4], //源IP
destipaddr[4]; //目的IP
};
以上定義的三種首部結(jié)構(gòu),是根據(jù)TCP/IP協(xié)議的相關(guān)規(guī)范定義的,后面會對ARP協(xié)議進(jìn)行詳細(xì)講解。
【上半部分完】
評論