1. 程式人生 > >S3C2440裸機程式【3】DM9000A

S3C2440裸機程式【3】DM9000A

原文地址:http://my.oschina.net/u/174242/blog/71688

網路對於嵌入式系統來說必不可少。可是s3c2440沒有整合乙太網介面,所以要想使s3c2440具備乙太網的功能,就必須擴充套件網絡卡介面。在這裡,我們外接DM9000,使其可以與乙太網相連線。

DM9000可以直接與ISA匯流排相連,也可以與大多數CPU相連。在這裡,我們當然是要讓DM9000與s3c2440相連線了。DM9000對 外來說只有兩個埠——地址口和資料口,地址口用於輸入內部暫存器的地址,而資料口則完成對某一暫存器的讀寫。DM9000的CMD引腳用來區分這兩個端 口,當CMD引腳為0時,DM9000的資料線上傳輸的是暫存器地址,當CMD引腳為1時,傳輸的是讀寫資料。我們把DM9000的A8和A9接為高電 平,把A4~A7接為低電平,並且把DM9000的AEN接到s3c2440的nGCS4引腳上,則DM9000的埠基址為0x20000300,如果 再把DM9000的CMD引腳接到s3c2440的ADDR2引腳上,則我們就可以定義DM9000的這兩個埠地址,它們分別為:

#define DM_ADDR_PORT (*((volatile unsigned short *) 0x20000300)) //地址口

#define DM_DATA_PORT (*((volatile unsigned short *) 0x20000304)) //資料口

如果要寫入DM9000中的某個暫存器,則先把該暫存器的地址賦予DM_ADDR_PORT,然後再把要寫入的資料賦予DM_DATA_PORT即可。讀取DM9000中的某個暫存器也類似。下面的函式的作用分別是DM9000的讀、寫暫存器操作:

  1. //寫DM9000暫存器   
  2. void __inline dm_reg_write(unsigned 
    char reg, unsigned char data)   
  3. {   
  4.        DM_ADDR_PORT = reg;            //將暫存器地址寫到地址埠   
  5.        DM_DATA_PORT = data;            
    //將資料寫到資料埠   
  6. }   
  7.     
  8. //讀DM9000暫存器   
  9. unsigned char __inline dm_reg_read(unsigned char reg)   
  10. {   
  11.        DM_ADDR_PORT = reg;              
  12.        return DM_DATA_PORT;             //將資料從資料埠讀出   
  13. }  

完成了對DM9000暫存器的讀寫函式的編寫,下面我們就可以初始化DM9000,它的過程就是適當配置DM9000暫存器的過程。DM9000的內部暫存器在這裡就不做介紹,而且DM9000的應用資料手冊也有如何初始化DM9000的步驟,我們這裡只給出具體的程式:

  1. void dm_init(void)   
  2. {   
  3.        dm_reg_write(DM9000_NCR,1);         //軟體復位DM9000   
  4.        delay(30);              //延時至少20μs   
  5.        dm_reg_write(DM9000_NCR,0);         //清除復位位   
  6.     
  7.        dm_reg_write(DM9000_NCR,1);         //為了確保復位正確,再次復位   
  8.        delay(30);   
  9.        dm_reg_write(DM9000_NCR,0);   
  10.          
  11.        dm_reg_write(DM9000_GPCR,1);       //設定GPIO0為輸出   
  12.        dm_reg_write(DM9000_GPR,0);         //啟用內部PHY   
  13.          
  14.        dm_reg_write(DM9000_NSR,0x2c);           //清TX狀態   
  15.        dm_reg_write(DM9000_ISR,0xf);                     //清中斷狀態   
  16.          
  17.        dm_reg_write(DM9000_RCR,0x39);           //設定RX控制   
  18.        dm_reg_write(DM9000_TCR,0);                //設定TX控制   
  19.        dm_reg_write(DM9000_BPTR,0x3f);            
  20.        dm_reg_write(DM9000_FCTR,0x3a);   
  21.        dm_reg_write(DM9000_FCR,0xff);   
  22.        dm_reg_write(DM9000_SMCR,0x00);   
  23.       
  24.        dm_reg_write(DM9000_PAR1,0x00);         //設定MAC地址:00-01-02-03-04-05   
  25.        dm_reg_write(DM9000_PAR2,0x01);           
  26.        dm_reg_write(DM9000_PAR3,0x02);   
  27.        dm_reg_write(DM9000_PAR4,0x03);   
  28.        dm_reg_write(DM9000_PAR5,0x04);   
  29.        dm_reg_write(DM9000_PAR6,0x05);   
  30.       
  31.        dm_reg_write(DM9000_NSR,0x2c);           //再次清TX狀態   
  32.        dm_reg_write(DM9000_ISR,0xf);                     //再次清中斷狀態   
  33.     
  34.        dm_reg_write(DM9000_IMR,0x81);           //開啟接受資料中斷   
  35. }  

DM9000內部有0x3FF大小的SRAM用於接受和傳送資料快取。在傳送或接收資料包之前,資料是暫存在這個SRAM中的。當需要連續傳送或接 收資料時,我們需要分別把DM9000暫存器MWCMD或MRCMD賦予資料埠,這樣就指定了SRAM中的某個地址,並且在傳輸完一個數據後,指標會指 向SRAM中的下一個地址,從而完成了連續訪問資料的目的。但當我們在傳送或接受一個數據後,指向SRAM的資料指標不需要變化時,則要把MWCMDX或 MRCMDX賦予資料埠。下面的程式為DM9000傳送資料的函式,它的兩個輸入引數分別為要傳送資料陣列首地址和資料陣列長度。在這裡我們已經知道數 據的寬為16位,它是由DM9000的硬體引腳設定實現的。

  1. void dm_tran_packet(unsigned char *datas, int length)   
  2. {   
  3.        int i;   
  4.          
  5.        dm_reg_write(DM9000_IMR, 0x80);          //在傳送資料過程中禁止網絡卡中斷   
  6.     
  7.        dm_reg_write(DM9000_TXPLH, (length>>8) & 0x0ff);           //設定傳送資料長度   
  8.        dm_reg_write(DM9000_TXPLL, length & 0x0ff);   
  9.       
  10.        DM_ADDR_PORT = DM9000_MWCMD;                 //傳送資料快取賦予資料埠   
  11.          
  12.        //傳送資料   
  13.        for(i=0;i<length;i+=2)   
  14.        {   
  15.               delay(50);   
  16.               DM_DATA_PORT = datas[i]|(datas[i+1]<<8);            //8位資料轉換為16位資料輸出   
  17.        }       
  18.          
  19.        dm_reg_write(DM9000_TCR, 0x01);          //把資料傳送到乙太網上   
  20.     
  21.        while((dm_reg_read(DM9000_NSR) & 0x0c) == 0)   
  22.        ;                           //等待資料傳送完成   
  23.          
  24.        delay(50);   
  25.     
  26.        dm_reg_write(DM9000_NSR, 0x2c);          //清除TX狀態   
  27.        dm_reg_write(DM9000_IMR, 0x81);          //開啟DM9000接收資料中斷   
  28. }  

傳送資料比較簡單,接收資料就略顯複雜,因為它是有一定格式要求的。在接收到的一包資料中的首位元組如果為0x01,則表示這是一個可以接收的資料 包;如果為0x0,則表示沒有可接收的資料包。因此在讀取其他位元組時,一定要先判斷首位元組是否為0x01。資料包的第二個位元組為資料包的一些資訊,它的高 位元組的格式與DM9000的暫存器RSR完全一致。第三個和第四個位元組為資料包的長度。後面的資料就是真正要接收的資料了。下面就是DM9000接收資料 的程式,其中輸入引數為存放輸入資料陣列的首地址,輸出引數為接收資料的長度。

  1. int dm_rec_packet(unsigned char *datas)   
  2. {   
  3.        unsigned char int_status;   
  4.        unsigned char rx_ready;   
  5.        unsigned short rx_status;   
  6.        unsigned short rx_length;   
  7.        unsigned short temp;   
  8.        int i;   
  9.     
  10.        int_status = dm_reg_read(DM9000_ISR);           //讀取ISR   
  11.        if(int_status & 0x1)                     //判斷是否有資料要接受   
  12.        {   
  13.               rx_ready = dm_reg_read(DM9000_MRCMDX);         //先讀取一個無效的資料   
  14.               rx_ready = (unsigned char)DM_DATA_PORT;            //真正讀取到的資料包首位元組   
  15.                 
  16.               if(rx_ready == 1)                 //判讀首位元組是否為1或0   
  17.               {   
  18.                      DM_ADDR_PORT = DM9000_MRCMD;           //連續讀取資料包內容   
  19.     
  20.                      rx_status = DM_DATA_PORT;                           //狀態位元組   
  21.                        
  22.                      rx_length = DM_DATA_PORT;                          //資料長度   
  23.                        
  24.                      if(!(rx_status & 0xbf00) && (rx_length < 10000))     //判讀資料是否符合要求   
  25.                      {   
  26.                             for(i=0; i<rx_length; i+=2)          //16位資料轉換為8位資料儲存   
  27.                             {   
  28.                                    delay(50);   
  29.                                    temp = DM_DATA_PORT;   
  30.                                    datas[i] = temp & 0x0ff;   
  31.                                    datas[i + 1] = (temp >> 8) & 0x0ff;   
  32.                             }   
  33.                      }   
  34.               }   
  35.               else if(rx_ready !=0)      //停止裝置   
  36.               {   
  37.                      //dm_reg_write(DM9000_IMR,0x80);  //停止中斷   
  38.                      //dm_reg_write(DM9000_ISR,0x0F);   //清中斷狀態   
  39.                      //dm_reg_write(DM9000_RCR,0x0);    //停止接收   
  40.                      //還需要復位系統,這裡暫時沒有處理   
  41.               }   
  42.        }   
  43.        dm_reg_write(DM9000_ISR, 0x1);             //清中斷   
  44.        return rx_length;   
  45. }  

關於DM9000的設定我們就介紹到這裡,下面就是s3c2440的設定。在這裡,網絡卡傳送資料利用的是查詢方式,接收資料利用的是中斷方式,因此 我們把DM9000的INT引腳連線到了s3c2440的EINT7上。另外我們還是用UART0介面來控制和顯示網絡卡資料。這兩個介面的初始化為:

  1. //uart0 port   
  2. rGPHCON = 0x00faaa;   
  3. rGPHUP  = 0x7ff;   
  4. rULCON0 = 0x3;   
  5. rUCON0 = 0x5;   
  6. rUFCON0 = 0;   
  7. rUMCON0 = 0;   
  8. rUBRDIV0 = 26;   
  9.        
  10. rSRCPND = (0x1<<27)|(0x1<<28);   
  11. rSUBSRCPND = 0x1;   
  12. rINTPND = (0x1<<27)|(0x1<<28);   
  13. rINTSUBMSK = ~(0x1);   
  14. rINTMSK = ~((0x1<<27)|(0x1<<28));   
  15. pISR_UART0 = (U32)uartISR;   
  16.     
  17. //EINT7   
  18. rGPFCON = 2<<14;   
  19. rEXTINT0 = (rEXTINT0 & (~(0x07<<28))) | (0x01<<28);   
  20. rEINTMASK &= ~(1<<7);   
  21. rSRCPND = rSRCPND | (0x1<<4);   
  22. rINTPND = rINTPND | (0x1<<4);   
  23. rINTMSK &= ~(1<<4);   
  24. pISR_EINT4_7 = (U32)DM9000ISR;  

下面就利用DM9000來進行簡單的網絡卡傳輸資料的測驗。由於乙太網傳輸資料都是基於某種協議的,因此要傳輸資料,必須遵循一定的協議格式。這裡我 們實現較為簡單的ARP協議。用於乙太網的ARP請求/應答分組格式為:14個位元組的乙太網首部+28個位元組ARP請求/應答。乙太網首部的格式為:6個 位元組的乙太網目標地址+6個位元組乙太網源地址+2個位元組幀型別,對於ARP來說,幀型別為0x0806。ARP請求/應答的格式為:2個位元組的硬體類 型+2個位元組的協議型別+1個位元組的硬體地址長度+1個位元組的協議地址長度+2個位元組的操作碼+6個位元組的傳送端乙太網地址+4個位元組的傳送端IP地 址+6個位元組的目標乙太網地址+4個位元組的目標IP地址。硬體型別為1表示的是乙太網,協議型別為0x0800表示的是IP地址,硬體地址長度和協議地址 長度分別為6和4,它們都是以位元組為單位的,操作碼為1表示的是ARP請求,為2表示的是ARP應答。

在下面的測試程式中,我們用交叉網線把開發板與PC機(作業系統為Windows XP,網絡卡的IP地址為192.168.1.120)相連線,我們通過UART發出一個命令,讓開發板發出一個ARP請求資料包,然後接收來自PC機的應 答,並把該應答資訊通過UART顯示出來。其中UART的中斷復位程式為:

  1. void __irq uartISR(void)   
  2. {   
  3.        char ch;   
  4.        rSUBSRCPND |= 0x1;   
  5.        rSRCPND |= 0x1<<28;   
  6.        rINTPND |= 0x1<<28;   
  7.        ch=rURXH0;   
  8.        if(ch == 0x33)   
  9.               comm=3;               //表示傳送一個ARP資料請求包   
  10.          
  11.        rUTXH0=ch;   
  12. }  

另外我們還要事先定義一個遵循ARP協議格式的陣列:

  1. unsigned char arpsendbuf[42]={   
  2.     
  3.        0xff,0xff,0xff,0xff,0xff,0xff,                     //乙太網目標地址,全1表示為廣播地址   
  4.        0x00,0x01,0x02,0x03,0x04,0x05,        //乙太網源地址   
  5.        0x08,0x06,                                        //幀型別:ARP幀   
  6.          
  7.        0x00,0x01,                                        //硬體型別:乙太網   
  8.        0x08,0x00,                                        //協議型別:IP協議   
  9.        0x06,                                                //硬體地址長度:6位元組   
  10.        0x04,                                                //協議地址長度:4位元組   
  11.        0x00,0x01,                                        //操作碼:ARP請求   
  12.          
  13.        0x00,0x01,0x02,0x03,0x04,0x05,        //傳送端乙太網硬體地址   
  14.        192, 168, 1, 50,                                 //傳送端IP協議地址   
  15.        0x00,0x00,0x00,0x00,0x00,0x00,        //接收端乙太網硬體地址   
  16.        192, 168, 1, 120                                 //接收端IP協議地址   
  17. };  

其中傳送端硬體地址,即乙太網源地址(00-01-02-03-04-05)是我們初始化DM9000時定義的。而傳送端IP協議地址是我們任意定義的。

該測試程式的主程式為:

  1. void Main(void)   
  2. {   
  3. ……   ……   
  4. //一些必要的初始化   
  5.     
  6.        comm=0;               //命令   
  7.        flag=0;                  //傳送ARP請求包標識   
  8.          
  9.        dm_init();              //DM9000初始化   
  10.                 
  11.        while(1)   
  12.        {   
  13.               if(comm.==3)   
  14.               {       
  15.                      comm=0;   
  16.                      dm_tran_packet(arpsendbuf, 42 );               //傳送ARP請求包   
  17.                      flag=1;           //置標識   
  18.               }   
  19. }   
  20. }  

接收網路上的資料是通過外部中斷方式的,在這個中斷處理程式中,主要完成的是接收網絡卡資料,並把接收到的資料傳送到UART,讓其顯示到PC機上。 這裡我們還需解決一個問題,那就是當我們傳送一個ARP請求包的時候,XP系統並不會應答一個ARP資料包,而是應答一個IP協議資料包,當再多次發出 ARP請求包後,才會得到ARP應答包。因此當s3c2440傳送ARP請求包後,它首先要檢查所接收到的資料包,如果不是ARP應答包,它就要再次傳送 ARP請求包,直到得到ARP應答包為止。因此中斷處理程式為:

  1. void __irq DM9000ISR(void)   
  2. {   
  3.        int i;   
  4.          
  5.        rSRCPND = rSRCPND | (0x1<<4);   
  6.        rINTPND = rINTPND | (0x1<<4);   
  7.     
  8.        if(rEINTPEND&(1<<7))   
  9.        {   
  10.               rEINTPEND = rEINTPEND | (0x1<<7);   
  11.     
  12.               packet_len = dm_rec_packet(buffer);                  //接收網絡卡資料   
  13.     
  14.               if((buffer[12]==0x08)&&(buffer[13]==0x06))          //是ARP協議   
  15.               {              
  16.                      //通過UART顯示出來   
  17.                      for(i=0;i<packet_len;i++)   
  18.                      {   
  19.                             while(!(rUTRSTAT0 & 0x2)) ;   
  20.                             rUTXH0 = buffer[i];   
  21.                      }   
  22.     
  23.                      flag=0;                  //清標誌   
  24.               }   
  25.               else if(flag==1)             //如果在發出ARP請求包後,接收到的資料不是ARP協議   
  26.               {   
  27.                      comm=3;               //繼續傳送ARP請求包   
  28.               }   
  29.        }   
  30. }  

這樣,整個網絡卡程式就編寫完畢。為了使大家對程式的因果關係認識得更加清晰,我們再敘述一遍程式的流程:首先初始化UART0,使其用中斷方式接收 資料,查詢方式傳送資料;初始化EINT7,這是因為DM9000的資料中斷引腳INT是連線到s3c2440的外部中斷7引腳上的;然後初始化 DM9000,主要是配置一些它的暫存器,並使其用中斷方式接收網絡卡資料,查詢方式傳送資料,這與UART0相似,最後是死迴圈等待UART0接收中斷服 務程式中得到的傳送ARP請求包命令。當得到傳送ARP請求包命令後,呼叫DM9000傳送資料命令,傳送事先準備好的一組資料。在傳送完ARP資料 後,PC機會應答該請求,從而引發s3c2440外部中斷7中斷,在該中斷服務程式中,主要是完成接收ARP應答包的任務,並把它通過UART0顯示出 來。

當程式被執行完,並在PC機上通過串列埠除錯軟體顯示出了一個正確的ARP應答包後,我們還可以通過下列方法來進一步驗證該程式的正確性:開啟 Windows XP系統只帶的“命令提示符”小軟體,在提示符下輸入:arp –a,會出現我們所設定的開發板的MAC地址(00-01-02-03-04-05)和IP地址(192.168.1.50),則說明Windows XP系統已經把我們開發板上的網絡卡資訊新增到了它的靜態列表中。

我們對該系統進一步分析還會發現,當開發板上電並且DM9000初始化完成後,Windows XP系統會向該開發板傳送一些目標地址為廣播地址(FF-FF-FF-FF-FF-FF)的ARP資料包和IP資料包,只要我們正確讀取它們,就可以在開 發板上電後,自動知道與其相連的系統的MAC地址和IP地址了。另外,如果對這一部分感興趣,還可以編寫ICMP協議的資料包,這樣就可以讓PC機 ping通我們的開發板了。