新聞中心

EEPW首頁 > 嵌入式系統(tǒng) > 設(shè)計應(yīng)用 > 單片機(jī)驅(qū)動DM9000網(wǎng)卡芯片詳細(xì)調(diào)試過程

單片機(jī)驅(qū)動DM9000網(wǎng)卡芯片詳細(xì)調(diào)試過程

作者: 時間:2016-10-07 來源:網(wǎng)絡(luò) 收藏
和其它網(wǎng)卡不同,系列網(wǎng)卡在嵌入式開發(fā)板上很常見,尤其是有關(guān)ARM-Linux的開發(fā)板上的網(wǎng)絡(luò)連接部分幾乎都是采用該完成的。當(dāng)然,其它網(wǎng)卡芯片,如RTL8019的應(yīng)用也很常見,在很多開發(fā)板上得到應(yīng)用然而RTL8019的介紹在網(wǎng)上可以找到非常詳細(xì)的介紹,尤其是用單片機(jī)對其做底層驅(qū)動的介紹非常豐富。下面的網(wǎng)站就介紹了用AVR驅(qū)動RTL8019網(wǎng)卡芯片的非常詳細(xì)的過程,有興趣的朋友可以參考一下。

  http://members.home.nl/bzijlstra/software/examples/RTL8019as.htm AVR驅(qū)動RTL8019網(wǎng)卡芯片的詳細(xì)介紹。

本文引用地址:http://m.butianyuan.cn/article/201610/310907.htm

  言歸正傳。在網(wǎng)上也能找到許多關(guān)于網(wǎng)卡芯片的介紹,然而這些介紹大多是關(guān)于Linux或WinCE下的驅(qū)動程序或移植,很少有介紹單片機(jī)驅(qū)動的例子。因此我在這里把我調(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é)補(bǔ)上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ì)講解。

  【上半部分完】


上一頁 1 2 下一頁

關(guān)鍵詞: DM9000 芯片

評論


相關(guān)推薦

技術(shù)專區(qū)

關(guān)閉