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的讀、寫暫存器操作:
- //寫DM9000暫存器
- void __inline dm_reg_write(unsigned
- {
- DM_ADDR_PORT = reg; //將暫存器地址寫到地址埠
- DM_DATA_PORT = data;
- }
- //讀DM9000暫存器
- unsigned char __inline dm_reg_read(unsigned char reg)
- {
- DM_ADDR_PORT = reg;
- return DM_DATA_PORT; //將資料從資料埠讀出
- }
完成了對DM9000暫存器的讀寫函式的編寫,下面我們就可以初始化DM9000,它的過程就是適當配置DM9000暫存器的過程。DM9000的內部暫存器在這裡就不做介紹,而且DM9000的應用資料手冊也有如何初始化DM9000的步驟,我們這裡只給出具體的程式:
- void dm_init(void)
- {
- dm_reg_write(DM9000_NCR,1); //軟體復位DM9000
- delay(30); //延時至少20μs
- dm_reg_write(DM9000_NCR,0); //清除復位位
- dm_reg_write(DM9000_NCR,1); //為了確保復位正確,再次復位
- delay(30);
- dm_reg_write(DM9000_NCR,0);
- dm_reg_write(DM9000_GPCR,1); //設定GPIO0為輸出
- dm_reg_write(DM9000_GPR,0); //啟用內部PHY
- dm_reg_write(DM9000_NSR,0x2c); //清TX狀態
- dm_reg_write(DM9000_ISR,0xf); //清中斷狀態
- dm_reg_write(DM9000_RCR,0x39); //設定RX控制
- dm_reg_write(DM9000_TCR,0); //設定TX控制
- dm_reg_write(DM9000_BPTR,0x3f);
- dm_reg_write(DM9000_FCTR,0x3a);
- dm_reg_write(DM9000_FCR,0xff);
- dm_reg_write(DM9000_SMCR,0x00);
- dm_reg_write(DM9000_PAR1,0x00); //設定MAC地址:00-01-02-03-04-05
- dm_reg_write(DM9000_PAR2,0x01);
- dm_reg_write(DM9000_PAR3,0x02);
- dm_reg_write(DM9000_PAR4,0x03);
- dm_reg_write(DM9000_PAR5,0x04);
- dm_reg_write(DM9000_PAR6,0x05);
- dm_reg_write(DM9000_NSR,0x2c); //再次清TX狀態
- dm_reg_write(DM9000_ISR,0xf); //再次清中斷狀態
- dm_reg_write(DM9000_IMR,0x81); //開啟接受資料中斷
- }
DM9000內部有0x3FF大小的SRAM用於接受和傳送資料快取。在傳送或接收資料包之前,資料是暫存在這個SRAM中的。當需要連續傳送或接 收資料時,我們需要分別把DM9000暫存器MWCMD或MRCMD賦予資料埠,這樣就指定了SRAM中的某個地址,並且在傳輸完一個數據後,指標會指 向SRAM中的下一個地址,從而完成了連續訪問資料的目的。但當我們在傳送或接受一個數據後,指向SRAM的資料指標不需要變化時,則要把MWCMDX或 MRCMDX賦予資料埠。下面的程式為DM9000傳送資料的函式,它的兩個輸入引數分別為要傳送資料陣列首地址和資料陣列長度。在這裡我們已經知道數 據的寬為16位,它是由DM9000的硬體引腳設定實現的。
- void dm_tran_packet(unsigned char *datas, int length)
- {
- int i;
- dm_reg_write(DM9000_IMR, 0x80); //在傳送資料過程中禁止網絡卡中斷
- dm_reg_write(DM9000_TXPLH, (length>>8) & 0x0ff); //設定傳送資料長度
- dm_reg_write(DM9000_TXPLL, length & 0x0ff);
- DM_ADDR_PORT = DM9000_MWCMD; //傳送資料快取賦予資料埠
- //傳送資料
- for(i=0;i<length;i+=2)
- {
- delay(50);
- DM_DATA_PORT = datas[i]|(datas[i+1]<<8); //8位資料轉換為16位資料輸出
- }
- dm_reg_write(DM9000_TCR, 0x01); //把資料傳送到乙太網上
- while((dm_reg_read(DM9000_NSR) & 0x0c) == 0)
- ; //等待資料傳送完成
- delay(50);
- dm_reg_write(DM9000_NSR, 0x2c); //清除TX狀態
- dm_reg_write(DM9000_IMR, 0x81); //開啟DM9000接收資料中斷
- }
傳送資料比較簡單,接收資料就略顯複雜,因為它是有一定格式要求的。在接收到的一包資料中的首位元組如果為0x01,則表示這是一個可以接收的資料 包;如果為0x0,則表示沒有可接收的資料包。因此在讀取其他位元組時,一定要先判斷首位元組是否為0x01。資料包的第二個位元組為資料包的一些資訊,它的高 位元組的格式與DM9000的暫存器RSR完全一致。第三個和第四個位元組為資料包的長度。後面的資料就是真正要接收的資料了。下面就是DM9000接收資料 的程式,其中輸入引數為存放輸入資料陣列的首地址,輸出引數為接收資料的長度。
- int dm_rec_packet(unsigned char *datas)
- {
- unsigned char int_status;
- unsigned char rx_ready;
- unsigned short rx_status;
- unsigned short rx_length;
- unsigned short temp;
- int i;
- int_status = dm_reg_read(DM9000_ISR); //讀取ISR
- if(int_status & 0x1) //判斷是否有資料要接受
- {
- rx_ready = dm_reg_read(DM9000_MRCMDX); //先讀取一個無效的資料
- rx_ready = (unsigned char)DM_DATA_PORT; //真正讀取到的資料包首位元組
- if(rx_ready == 1) //判讀首位元組是否為1或0
- {
- DM_ADDR_PORT = DM9000_MRCMD; //連續讀取資料包內容
- rx_status = DM_DATA_PORT; //狀態位元組
- rx_length = DM_DATA_PORT; //資料長度
- if(!(rx_status & 0xbf00) && (rx_length < 10000)) //判讀資料是否符合要求
- {
- for(i=0; i<rx_length; i+=2) //16位資料轉換為8位資料儲存
- {
- delay(50);
- temp = DM_DATA_PORT;
- datas[i] = temp & 0x0ff;
- datas[i + 1] = (temp >> 8) & 0x0ff;
- }
- }
- }
- else if(rx_ready !=0) //停止裝置
- {
- //dm_reg_write(DM9000_IMR,0x80); //停止中斷
- //dm_reg_write(DM9000_ISR,0x0F); //清中斷狀態
- //dm_reg_write(DM9000_RCR,0x0); //停止接收
- //還需要復位系統,這裡暫時沒有處理
- }
- }
- dm_reg_write(DM9000_ISR, 0x1); //清中斷
- return rx_length;
- }
關於DM9000的設定我們就介紹到這裡,下面就是s3c2440的設定。在這裡,網絡卡傳送資料利用的是查詢方式,接收資料利用的是中斷方式,因此 我們把DM9000的INT引腳連線到了s3c2440的EINT7上。另外我們還是用UART0介面來控制和顯示網絡卡資料。這兩個介面的初始化為:
- //uart0 port
- rGPHCON = 0x00faaa;
- rGPHUP = 0x7ff;
- rULCON0 = 0x3;
- rUCON0 = 0x5;
- rUFCON0 = 0;
- rUMCON0 = 0;
- rUBRDIV0 = 26;
- rSRCPND = (0x1<<27)|(0x1<<28);
- rSUBSRCPND = 0x1;
- rINTPND = (0x1<<27)|(0x1<<28);
- rINTSUBMSK = ~(0x1);
- rINTMSK = ~((0x1<<27)|(0x1<<28));
- pISR_UART0 = (U32)uartISR;
- //EINT7
- rGPFCON = 2<<14;
- rEXTINT0 = (rEXTINT0 & (~(0x07<<28))) | (0x01<<28);
- rEINTMASK &= ~(1<<7);
- rSRCPND = rSRCPND | (0x1<<4);
- rINTPND = rINTPND | (0x1<<4);
- rINTMSK &= ~(1<<4);
- 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的中斷復位程式為:
- void __irq uartISR(void)
- {
- char ch;
- rSUBSRCPND |= 0x1;
- rSRCPND |= 0x1<<28;
- rINTPND |= 0x1<<28;
- ch=rURXH0;
- if(ch == 0x33)
- comm=3; //表示傳送一個ARP資料請求包
- rUTXH0=ch;
- }
另外我們還要事先定義一個遵循ARP協議格式的陣列:
- unsigned char arpsendbuf[42]={
- 0xff,0xff,0xff,0xff,0xff,0xff, //乙太網目標地址,全1表示為廣播地址
- 0x00,0x01,0x02,0x03,0x04,0x05, //乙太網源地址
- 0x08,0x06, //幀型別:ARP幀
- 0x00,0x01, //硬體型別:乙太網
- 0x08,0x00, //協議型別:IP協議
- 0x06, //硬體地址長度:6位元組
- 0x04, //協議地址長度:4位元組
- 0x00,0x01, //操作碼:ARP請求
- 0x00,0x01,0x02,0x03,0x04,0x05, //傳送端乙太網硬體地址
- 192, 168, 1, 50, //傳送端IP協議地址
- 0x00,0x00,0x00,0x00,0x00,0x00, //接收端乙太網硬體地址
- 192, 168, 1, 120 //接收端IP協議地址
- };
其中傳送端硬體地址,即乙太網源地址(00-01-02-03-04-05)是我們初始化DM9000時定義的。而傳送端IP協議地址是我們任意定義的。
該測試程式的主程式為:
- void Main(void)
- {
- …… ……
- //一些必要的初始化
- comm=0; //命令
- flag=0; //傳送ARP請求包標識
- dm_init(); //DM9000初始化
- while(1)
- {
- if(comm.==3)
- {
- comm=0;
- dm_tran_packet(arpsendbuf, 42 ); //傳送ARP請求包
- flag=1; //置標識
- }
- }
- }
接收網路上的資料是通過外部中斷方式的,在這個中斷處理程式中,主要完成的是接收網絡卡資料,並把接收到的資料傳送到UART,讓其顯示到PC機上。 這裡我們還需解決一個問題,那就是當我們傳送一個ARP請求包的時候,XP系統並不會應答一個ARP資料包,而是應答一個IP協議資料包,當再多次發出 ARP請求包後,才會得到ARP應答包。因此當s3c2440傳送ARP請求包後,它首先要檢查所接收到的資料包,如果不是ARP應答包,它就要再次傳送 ARP請求包,直到得到ARP應答包為止。因此中斷處理程式為:
- void __irq DM9000ISR(void)
- {
- int i;
- rSRCPND = rSRCPND | (0x1<<4);
- rINTPND = rINTPND | (0x1<<4);
- if(rEINTPEND&(1<<7))
- {
- rEINTPEND = rEINTPEND | (0x1<<7);
- packet_len = dm_rec_packet(buffer); //接收網絡卡資料
- if((buffer[12]==0x08)&&(buffer[13]==0x06)) //是ARP協議
- {
- //通過UART顯示出來
- for(i=0;i<packet_len;i++)
- {
- while(!(rUTRSTAT0 & 0x2)) ;
- rUTXH0 = buffer[i];
- }
- flag=0; //清標誌
- }
- else if(flag==1) //如果在發出ARP請求包後,接收到的資料不是ARP協議
- {
- comm=3; //繼續傳送ARP請求包
- }
- }
- }
這樣,整個網絡卡程式就編寫完畢。為了使大家對程式的因果關係認識得更加清晰,我們再敘述一遍程式的流程:首先初始化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通我們的開發板了。