單片機驅動DM9000網卡芯片詳細調試過程
http://members.home.nl/bzijlstra/software/examples/RTL8019as.htm AVR驅動RTL8019網卡芯片的詳細介紹。
本文引用地址:http://www.biyoush.com/article/201610/310907.htm言歸正傳。在網上也能找到許多關于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
一、電路連接
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引腳來選通該網卡芯片,對于大多數的應用來說沒有意義,因為在我們的應用中一般只用一個網卡芯片,而這些地址引腳主要用于在多網卡芯片環(huán)境下選擇其中之一。DM9000工作的默認基地址為0x300,這里我們按照默認地址選擇,將SA9、SA8接高電平,SA7-DA4接低電平。多網卡環(huán)境可以根據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中有許多控制和狀態(tài)寄存器(這些寄存器在上一篇文章中有詳細的使用說明),但它們都不能直接被處理器訪問,訪問這些控制、狀態(tài)寄存器的方法是:
(1)、將寄存器的地址寫到CMD端口;
(2)、從DATA端口讀寫寄存器中的數據;
1、讀、寫寄存器
其實,INDEX端口和DATA端口的就是由芯片上的CMD引腳來區(qū)分的。低電平為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;
}
讀寄存器的寫法類似,這里就略一下了。這一過程看上去有些復雜,呵呵,其實執(zhí)行起來也蠻有效率的,執(zhí)行時間差不多。這種模擬總線時序的方式實際并不復雜,只是把總線方式下自動執(zhí)行的過程手動的執(zhí)行了一遍而已。
在DM9000中,還有一些PHY寄存器,也稱之為介質無關接口MII(Media Independent Interface)寄存器。對這些寄存器的操作會影響網卡芯片的初始化和網絡連接,這里不對其進行操作,所以對這些寄存器的訪問方法這里也略了(在上篇文章中有介紹)。操作不當反而使網卡不能連接到網絡。
至此,我們已經寫好了兩個最基本的函數:dm9000_reg_write()和dm9000_reg_read(),以及前面的宏定義DM_ADD和DM_CMD。下面將一直用到。
2、初始化DM9000網卡芯片。
初始化DM9000網卡芯片的過程,實質上就是填寫、設置DM9000的控制寄存器的過程,這里以程序為例進行說明。其中寄存器的名稱宏定義在DM9000.H中已定義好。
注:一下函數中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引腳,使其復位。這一步可以省略,可以用下面的軟件復位代替*/
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);//清除各種狀態(tài)標志位
dm9000_reg_write(ISR, 0x3f);//清除所有中斷標志位
/*以上清除標志位*/
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);
/*以上是功能控制,具體功能參考上一篇文章中的說明,或參考數據手冊的介紹*/
for(i=0; i<6; i++)
dm9000_reg_write(PAR + i, mac_addr[i]);//mac_addr[]自己定義一下吧,6個字節(jié)的MAC地址
/*以上存儲MAC地址(網卡物理地址)到芯片中去,這里沒有用EEPROM,所以需要自己寫進去*/
/*關于MAC地址的說明,要參考網絡相關書籍或資料*/
dm9000_reg_write(NSR, 0x2c);
dm9000_reg_write(ISR, 0x3f);
/*為了保險,上面有清除了一次標志位*/
dm9000_reg_write(IMR, 0x81);
/*中斷使能(或者說中斷屏蔽),即開啟我們想要的中斷,關閉不想要的,這里只開啟的一個接收中斷*/
/*以上所有寄存器的具體含義參考上一篇文章,或參考數據手冊*/
}
這樣就對DM9000初始化完成了,怎么樣,挺簡單的吧。
3、發(fā)送、接收數據包
同樣,以程序為例,通過注釋說明。
//發(fā)送數據包
//參數:datas為要發(fā)送的數據緩沖區(qū)(以字節(jié)為單位),length為要發(fā)送的數據長度(兩個字節(jié))。
void sendpacket(unsigned char *datas, unsigned int length)
{
unsigned int len, i;
dm9000_reg_write(IMR, 0x80);//先禁止網卡中斷,防止在發(fā)送數據時被中斷干擾
len = length;
dm9000_reg_write(TXPLH, (len>>8) & 0x0ff);
dm9000_reg_write(TXPLL, len & 0x0ff);
/*這兩句是將要發(fā)送數據的長度告訴DM9000的寄存器*/
DM_ADD = MWCMD;//這里的寫法是針對有總線接口的處理器,沒有總線接口的處理器要注意加上時序。
for(i=0; i
{
udelay(20);
DM_CMD = datas[i] | (datas[i+1]<<8);
}
/*上面是將要發(fā)送的數據寫到DM9000的內部SRAM中的寫FIFO中,注意沒有總線接口的處理器要加上適當的時序*/
/*只需要向這個寄存器中寫數據即可,MWCMD是DM9000內部SRAM的DMA指針,根據處理器模式,寫后自動增加*/
dm9000_reg_write(TCR, 0x01);//發(fā)送數據到以太網上
while((dm9000_reg_read(NSR) & 0x0c) == 0);//等待數據發(fā)送完成
udelay(20);
dm9000_reg_write(NSR, 0x2c);//清除狀態(tài)寄存器,由于發(fā)送數據沒有設置中斷,因此不必處理中斷標志位
dm9000_reg_write(IMR, 0x81);//DM9000網卡的接收中斷使能
}
以上是發(fā)送數據包,過程很簡單。而接收數據包確需要些說明了。DM9000從網絡中接到一個數據包后,會在數據包前面加上4個字節(jié),分別為“01H”、“status”(同RSR寄存器的值)、“LENL”(數據包長度低8位)、“LENH”(數據包長度高8位)。所以首先要讀取這4個字節(jié)來確定數據包的狀態(tài),第一個字節(jié)“01H”表示接下來的是有效數據包,若為“00H”則表示沒有數據包,若為其它值則表示網卡沒有正確初始化,需要從新初始化。
如果接收到的數據包長度小于60字節(jié),則DM9000會自動為不足的字節(jié)補上0,使其達到60字節(jié)。同時,在接收到的數據包后DM9000還會自動添加4個CRC校驗字節(jié)??梢圆挥杼幚?。于是,接收到的數據包的最小長度也會是64字節(jié)。當然,可以根據TCP/IP協議從首部字節(jié)中出有效字節(jié)數,這部分在后面講解。下面為接收數據包的函數。
//接收數據包
//參數:datas為接收到是數據存儲位置(以字節(jié)為單位)
//返回值:接收成功返回數據包類型,不成功返回0
unsigned int receivepacket(unsigned char *datas)
{
unsigned int i, tem;
unsigned int status, len;
unsigned char ready;
ready = 0;//希望讀取到“01H”
status = 0;//數據包狀態(tài)
len = 0; //數據包長度
/*以上為有效數據包前的4個狀態(tài)字節(jié)*/
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;
}*/
/***********************************************************************************/
/*以上表示若接收到的第一個字節(jié)不是“01H”,則表示沒有數據包,返回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;
}
/*以上接收數據包,注意的地方與發(fā)送數據包的地方相同*/
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定義為兩個字節(jié)的變量,根據C編譯器進行定義。
unsigned char Buffer[1000];//定義了一個1000字節(jié)的接收發(fā)送緩沖區(qū)
uint16 packet_len;//接收、發(fā)送數據包的長度,以字節(jié)為單位。
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]; //發(fā)送端MAC地址
unsigned char sipaddr[4]; //發(fā)送端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數據報總字節(jié)長度
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協議的相關規(guī)范定義的,后面會對ARP協議進行詳細講解。
【上半部分完】
評論