dsp控制DM9000實現802.3數據收發第一篇,文章轉載

單片機驅動DM9000

和其它網卡芯片不同,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/24 ... M9000-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/downlo ... tes_Ver_1_22%20061104.pdf

一、電路連接

     DM9000E網卡芯片支持8位、16位、32位模式的處理器,通過芯片引腳EEDO(65腳)和WAKEUP(79腳)的復位值設置支持的處理器類型,如16位處理器只需將這兩個引腳接低電平即可,其中WAKEUP內部有60K下拉電阻,因此可懸空該引腳,或作爲網卡芯片喚醒輸出用。其它型號請參考相應的數據手冊。

clip_image001

圖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的命令/數據切換引腳,低電平時讀寫命令操作,高電平時讀寫數據操作。 clip_image002

圖2 讀時序

clip_image003

圖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 (*[1]

#define DM_CMD (*[2]

//向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協議進行詳細講解。

附註

(volatile unsigned int *) 0x8000300

(volatile unsigned int *) 0x8000304

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章