mini2440裸机编程---网卡

我自己作的试验有问题没有解决:

dm9000 发完之后,不产生中断,为什么?而且通过判断nsr,也读不到到发送完毕的标志。
另一个问题是:一旦启动发送,程序就会不断地产生recv的中断。这个问题经过写入isr寄存器得到解决。但依然不明白。

一、dm9000和ARM的连线。

     DM9000可以直接与ISA总线相连,也可以与大多数CPU相连。在这里,我们当然是要让DM9000与s3c2440相连接了。dm9000与arm之间的连线主要有三部分: 

    1. 地址线,主要是AEN和CMD两根线。

DM9000对外来说只有两个端口——地址口和数据口,地址口用于输入内部寄存器的地址,而数据口则完成对某一寄存器的读写。DM9000的CMD引脚用来区分这两个端口,当CMD引脚为0时,DM9000的数据线上传输的是寄存器地址,当CMD引脚为1时,传输的是读写数据。我们把DM9000的A8和A9接为高电平,把A4~A7接为低电平,并且把DM9000的AEN接到s3c2440的nGCS4引脚上,则DM9000的端口基址为0x2xxxx300,如果再把DM9000的CMD引脚接到s3c2440的ADDR2引脚上,则我们就可以定义DM9000的这两个端口地址,它们分别为:

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

#define DM_DATA_PORT           (*((volatile unsigned short *) 0x20000304))        //数据口

其实这个地址中2与3之间的几个0 可以换成任意的数字(经过了测试)。


   2.  数据线:arm的数据线直接和dm9000的数据线相连,LDATA0~LDATA15。 这样就可以很快的传输数据。 其实也可以认为对于dm9000,它的数据线和地址线是复用的。因为,cmd为0时,数据线上传输的就是要读写的dm9000的寄存器地址;cmd为1时,数据线上传输的就是数据。

如果要写入DM9000中的某个寄存器,则先把该寄存器的地址赋予DM_ADDR_PORT,然后再把要写入的数据赋予DM_DATA_PORT即可。读取DM9000中的某个寄存器也类似。下面的函数的作用分别是DM9000的读、写寄存器操作:

 

//写DM9000寄存器

void __inline dm_reg_write(unsigned char reg, unsigned char data)

{

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;             //将数据从数据端口读出

}

    3. 中断信号。 dm9000的INT引脚与ARM的一个外部中断相连,我们用到的中断主要是发送完毕和接收完毕后产生的中断。


二、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内部有16k大小的SRAM用于接受和发送数据缓存。其中3k用于发包,13k用于收包。此外,用于发包的3k中,可以缓存两个packet。通常我们用的方法是在程序中维护一个包个数变量packet_cnt,当发包时,直接写入sram,然后发送。产生发送完毕后,判断packet_cnt。如果packet_cnt>0,就接着继续发。 

       当需要连续发送或接收数据时,我们需要分别把DM9000寄存器MWCMD或MRCMD赋予数据端口,这样就指定了SRAM中的某个地址,并且在传输完一个数据后,指针会指向SRAM中的下一个地址,从而完成了连续访问数据的目的。但当我们在发送或接受一个数据后,指向SRAM的数据指针不需要变化时,则要把MWCMDX或MRCMDX赋予数据端口。下面的程序为DM9000发送数据的函数,它的两个输入参数分别为要发送数据数组首地址和数据数组长度。在这里我们已经知道数据的宽为16位,它是由DM9000的硬件引脚设置实现的。

1. 发包。下面是赵老师的程序:

 
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, 0x83);          //打开DM9000接收数据中断和发送中断
}
我在试验的过程中, 发现程序会在下面这句话死循环:
	while((dm_reg_read(DM9000_NSR) & 0x0c) == 0)
       ;                           //等待数据发送完成
后来,我改成了中断的方式进行发送:

void dm_tran_packet(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);          //把数据发送到以太网上

         dm_reg_write(DM9000_IMR, 0x83);  
}

void dm_trans_done(void) 
{
	   uart_printf("%s  ", __func__);	
}
void __irq DM9000ISR(void)
{
       int i, packet_len;
	   unsigned char status;
       char buffer[128];
       rSRCPND = rSRCPND | (0x1<<4);
       rINTPND = rINTPND | (0x1<<4);

//	   uart_printf("dm9000isr  ");
	   
       if(rEINTPEND&(1<<7)) {
              rEINTPEND = rEINTPEND | (0x1<<7);

			  status  = dm_reg_read(DM9000_ISR);			 //清中断
			  dm_reg_write(DM9000_ISR, status);
			  
			  if(status & ISR_PRS ){
              		packet_len = dm_rec_packet(buffer);                  //接收网卡数据

				if((buffer[12]==0x08)&&(buffer[13]==0x06)){			//是ARP协议		
					  for( i = 0; i< packet_len; i++) {
						 uart_printf("0x%x ", buffer[i]);
					  }  
				}
			} else if(status & ISR_PTS) {
				  dm_trans_done();
			}
			
       }
}

改成这样之后,依然不能产生发送完毕的中断。(可以成功的发包,我通过发包软件抓到了发送的包,但就是不产生中断)

 

       2. 收包。 接收数据就略显复杂,因为它是有一定格式要求的。在接收到的一包数据中的首字节如果为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;
                            }
                     }
              }
       }
       return rx_length;
}
四、 简单ARP协议的实现

下面就利用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应答。


另外我们还要事先定义一个遵循ARP协议格式的数组:

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, 17, 131,                                 //发送端IP协议地址
       0x00,0x00,0x00,0x00,0x00,0x00,        //接收端以太网硬件地址
       192, 168, 17, 130                                 //接收端IP协议地址
};

 

其中发送端硬件地址,即以太网源地址(00-01-02-03-04-05)是我们初始化DM9000时定义的。而发送端IP协议地址是我们任意定义的。

 

该测试程序的主程序为:

 

void eint7_init()
{
	//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;
}

void netinterface_test(void)
{
       char buff[12];
       int n;
	   Uart_Send("\n\rhellonetinterface", 20);
	      
       dm_init();              //DM9000初始化
       eint7_init();

       while(1) {
       		memset(buff, 0 ,sizeof(buff));
	       	n = Uart_Rcv(buff, 12);// loop here
			if( n <= 0 )
				continue;
			Uart_Send(buff, n);
			if(buff[0] == 's') {
				uart_printf("send ");
				dm_tran_packet(arpsendbuf, 42 );	//发送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系统已经把我们开发板上的网卡信息添加到了它的静态列表中。

      文中有一部分转自赵老师的博客。 

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值