网络对于嵌入式系统来讲必不可少。但是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 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; //将数据从数据端口读出
- }
完成了对DM9000寄存器的读写函数的编写,下面咱们就能够初始化DM9000,它的过程就是适当配置DM9000寄存器的过程。DM9000的内部寄存器在这里就不作介绍,并且DM9000的应用数据手册也有如何初始化DM9000的步骤,咱们这里只给出具体的程序:spa
- 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通咱们的开发板了。