首先要将启动代码start.S中存储控制器中的值改下,主要是BANK4,设置成16位总线模式,还是间要设下:
如下:
/*******************************************************************
*存储控制器13个寄存器的设置值
********************************************************************/
.align 4
mem_cfg_val:
.long 0x220D1110
.long 0x00000700
.long 0x00000700
.long 0x00000700
.long 0x00000700
.long 0x00001f7c
.long 0x00000700
.long 0x00018005
.long 0x00018005
.long 0x008C07A3
.long 0x000000B2
.long 0x00000030
.long 0x00000030
TCP/IP协议栈分为4层:网际接口层,网际层,传输层,应用层。为了在mini2440上建立一个简易的TCP/IP协议栈,我们需要实现以下几个部分,MAC层协议的实现(对应网际接口层),IP/ARP层协议的实现(对应网际层),UDP层协议实现(对应传输层),tftp协议层程序实现(对应应用层);这里为什么在传输层选择实现UDP协议,而不使用TCP协议,一,tftp协议是基于UDP协议的,so。。。,二,pc与开发板所处的环境一般很可靠,UDP足以。那么,今天首先来实现MAC层协议。
MAC层协议处于最底层,直接和网卡进行打交道,简单的说,MAC层要实现的功能就是将网卡(如DM9000)收到的数据加上MAC头然后传给IP层,或者将MAC层送过来的数据去掉MAC头,然后通过实际的网卡(如DM9000)发送出去。
所以要实现MAC层协议,我们要做两部分工作:1,实际网卡的接收与发送函数;2,对实际网卡要接收或发送的数据进行MAC层处理,如加上MAC层头发送出去或者去掉MAC头进行接收。细分如下:
1:DM9000的初始化;
2:DM9000的发送函数;
3:DM9000的接收函数;
4:MAC层头部的处理函数。
谈到了DM9000,首先讲下DM9000的相关知识:
第一, mini2440上DM9000的地址问题:
在DM9000的datasheet第31页写道:
要想操作DM9000,只有两个接口,一个是INDEX接口,一个是DATA接口,当CMD=0时,操作的是INDEX接口,当CMD=1时,操作的是DATA接口。当要操作DM9000中的寄存器时,首先要将DM9000的寄存器保存在INDEX接口中,然后再通过写数据到DATA接口实现DM9000的写寄存器操作,或者通过读DATA接口实现DM9000寄存器的读操作。
那么CMD在mini2440上接的是什么呢?
可以看出接的是CMD接的是ADDR2,同时DM9000是接的nGCS4,第4个bank,所以DM9000的基地址为0x2000,0000;因此可以算出INDEX端口的地址为:0x2000,0000;而DATA端口的地址为0x2000,0004(CMD=1即ADDR2=1),而为什么网上有很多人将DM9000的地址写成0x2000,0300也可以的呢?应该是这个人的开发板上的DM9000的CMD引脚接到了ADDR8上,很有意思的一点是,DM9000的地址只受CMD这一条地址线的影响,因此0x2000,0000和0x2000,0300对它来说是没有区别的。
根据以上所述,要想往DM9000的寄存器写值,或读取DM9000寄存器的值,用如下程序实现:
#define DM9000_ADDR (*((volatile unsigned short *)0x20000000))
#define DM9000_DATA (*((volatile unsigned short *)0x20000004))
void dm9000_reg_write(unsigned char reg, unsigned char data) //write the register
{
msdelay(1);
DM9000_ADDR = reg;
msdelay(1);
DM9000_DATA = data;
}
unsigned char dm9000_reg_read(unsigned char reg) //read the register,8 bits mode
{
msdelay(1);
DM9000_ADDR = reg;
msdelay(1);
return DM9000_DATA;
}
unsigned short dm9000_reg_read16(unsigned char reg) //read the register,16 bits mode
{
msdelay(1);
DM9000_ADDR = reg;
msdelay(1);
return DM9000_DATA;
}
这里为什么实现了一个读8位的和一个读16位的寄存器,因为mini2440上DM9000是连接成16位总线数据的,所以当我们在读寄存器的时候用读8位的实现,而在读DM9000数据的时候用读16位数据的来读,在DM9000中有一个寄存器叫做MRCMD:
我们在初始化的时候会将DM9000初始化成16位模式,所以这个RX SRAM的指针会自动加16位,这也就是出现上面两种读寄存器函数的原因。
说了这么多,进入第一话题:DM9000初始化:
首先来看一下DM9000的初始化流程:
1:开启DM9000芯片:
dm9000_reg_write(DM9000_GPCR, 0x01); //start dm9000
dm9000_reg_write(DM9000_GPR, 0x00);
2:进行两次软启动:
dm9000_reg_write(DM9000_NCR, 0x03); //make two soft reset,this is the first
msdelay(50);
dm9000_reg_write(DM9000_NCR, 0x00);
dm9000_reg_write(DM9000_NCR, 0x03); //this is the second soft reset
msdelay(50);
dm9000_reg_write(DM9000_NCR, 0x00);
3:清除状态寄存器:
dm9000_reg_write(DM9000_NSR, 0x2c); //clear the status
dm9000_reg_write(DM9000_ISR, 0x3f); //16 bit bus mode
4:接收发送设置和一些参数设置:
dm9000_reg_write(DM9000_RCR, 0x39); //receive control
dm9000_reg_write(DM9000_TCR, 0x00); //send control
dm9000_reg_write(DM9000_BPTR, 0x3f);
dm9000_reg_write(DM9000_FCTR, 0x3a);
dm9000_reg_write(DM9000_RTFCR, 0xff);
dm9000_reg_write(DM9000_SMCR, 0x00);
5:写入自己定义的MAC地址:
for (i = 0; i < 6; i++) //mac address
dm9000_reg_write(DM9000_PAR + i, mac_addr[i]);
6:再次清除状态寄存器:
dm9000_reg_write(DM9000_NSR, 0x2c); //clear the status
dm9000_reg_write(DM9000_ISR, 0x3f); //16 bit bus mode
7:关闭接收发送中断,因为本例接收发送全部试用查询方式:
dm9000_reg_write(DM9000_IMR,0x80); //disable the interrupt
为什么要这么做,这就是芯片的要求,以后如果你再遇到DM9000,就按这个顺序进行初始化。当中的寄存器的作用参考datasheet。
初始化完成之后,如果初始化成功,RJ-45接口的绿灯会亮,同时去读NSR寄存器,bit6应为1,代表连接成功。
见下图:
这里说下一个小插曲,当我将程序下到板子中去运行时,发现RJ-45的绿灯亮了,应该是连接成功了,但是我去读NSR寄存器时,却发现读出来的总是0,真是急煞我也。后来才发现,DM9000初始化之后到DM9000正常工作之前会有一段时间,可以看ubutun上面的网络标志,在大约初始化3秒之后,pc机上的网络连接才显示网络以连接,所以延时5秒之后再去读NSR寄存器的值时,可以发现读出来的就是1了。符合理论要求。
好了,DM9000的初始化讲完了,下面开始讲DM9000的发送函数:
写程序一定要养成一个良好的习惯,那就是先要画出流程图,然后在根据流程图写程序,这样思路才会很清晰,如果你直接写,只会是一头雾水,调程序也不好调。
那么看下DM9000发送数据的流程:
1:检测工作模式,总线是8位,16位,or 32位,通过读ISR寄存器获得
io_mode = dm9000_reg_read(DM9000_ISR)>>6;
注,我这里就不检测了,直接按照16位模式来^_^
2:将要发送的数据长度写入相应寄存器:
dm9000_reg_write(DM9000_TXPLH,(len >> 8)&0x0ff);//write the length of your packet which //will be send
dm9000_reg_write(DM9000_TXPLL,len&0x0ff);
3:将要发送的数据写入相应的寄存器:
DM9000_ADDR = DM9000_MWCMD;
for (i = 0; i < len; i += 2) //mini2440 has connect dm9000 in 16 bits mode
{
msdelay(1);
DM9000_DATA = datas[i] | (datas[i+1] << 8);
}
注:这里并没欧调用dm9000_reg_write函数,因为我们每次要写入16位,同时内部的TX SRAM的指针也会自动增加16位,而dm9000_reg_write函数是8位数据写函数,这里同样按照先将要写的寄存器赋给INDEX端口,然后再将要写的数据赋给DATA端口的思想。因为是一直操作MWCMD寄存器,所以我们只向INDEX端口赋一次值就可以了,不用每次都赋值。
4:设置传送标志位,启动数据发送:
dm9000_reg_write(DM9000_TCR, 0x01);
5:检测发送是否完成:
while(1) //when send completed, TCR bits0 will set to 0
{
if(((dm9000_reg_read(DM9000_TCR)) & 0x01) == 0x00)
break;
}
while((dm9000_reg_read(DM9000_NSR)&0x0c) == 0) //check again
msdelay(1);
继续,DM9000接收函数的实现:
DM9000在数据包接收的时候有些特殊,DM9000的接收到的数据放在内部的RX FIFO中,在每一个接收到的数据包的前面都有一个4字节的头,可以用MRCMDX和MRCMD两个寄存器来读取接收到的数据包的信息。这4个字节头的内容分别为:
第一个:是接收数据标志,若为0x01,则表示有数据被接收并保存在RX SRAM中,这时候可以读出并进行处理,如果为0x00则表示没有数据到达。如果既不是0x01也不是0x00,则认为有异常发生,应该将DM9000重启以使芯片恢复到正常状态。
第二个:是status,是接收数据包的状态字,起其内容和寄存器0x06H的内容相同,可以用来判断接收的数据是否正常。或发生了何种异常。
第三,第四个字节,分别为接收到数据时数据包的长度,分别为低字节和高字节,在读取数据包的时候可以用这个长度来进行控制。这4个字节是DM9000在接收数据的时候添加的信息,不属于数据包的内容,从第5个字节开始的数据才是真正的数据包的内容,长度存在第3、4字节当中。
按照惯例,要想着整个接收数据的流程是怎么样的:
1:判断有没有数据包到达:
ready = dm9000_reg_read(DM9000_MRCMDX); //this is MRCMDX,after we first read,the point will not increase
if((ready & 0x01) != 0x01) //first we will read 0x00H
{
ready = dm9000_reg_read(DM9000_MRCMDX); //second we will read 0x01H
if((ready & 0x01) != 0x01)
{
if((ready & 0x01) != 0x00) //after we read twice,if not 0x01or0x00,there is a error init it
{
dm9000_init();
}
return 0;
}
}
注意:上面读的是MRCMDX,内部SRAM指针不会自动增加
2:若有,则接收数据呗
status = dm9000_reg_read16(DM9000_MRCMD);//notice:this is MRCMD,as we will read the data,so we use MRCMD
这里用的是MRCMD,同时用的是dm9000_reg_read16,这里的status并不全是status还包括0x01。
len = DM9000_DATA; //read the length,16 bits mode,the reg has set by the prior step
前面已经将MRCMD的值赋给INDEX端口了,所以可以接着直接读数据,又因为每次是读16位,所以得到的直接是数据的长度。
下面就是真正的数据了,接收到我们定义的缓存里。
datas = (unsigned char *)&buffer[0]; //now we begin to receive the real data
for(i = 0; i < len; i += 2) //16 bits mode
{
msdelay(1); //the reg MRCMD has set by the prior prior step
tem = DM9000_DATA;
datas[i] = tem & 0x0ff;
datas[i+1] = (tem>>8)&0x0ff;
}
3:将接收到的数据传给MAC层进行处理
net_receive(&buffer[0],packet_len);
这一句就是将接收到的数据传送给MAC层处理。
好了,来看MAC层的实现:无非就是发送时加MAC头部,接收时去掉头部
void mac_send( unsigned char *buf, unsigned int len, unsigned short proto)
{
buf -= sizeof(struct eth_hdr); //增加MAC头部的空间,注意buf不是从0开始的
len += sizeof(struct eth_hdr);//再加上MAC头部的长度
struct eth_hdr *p = (struct eth_hdr *)(buf);
memcpy (p->s_mac, mac_addr, 6); //初始化MAC头
memcpy (p->d_mac, host_mac_addr, 6);
p->type = htons(proto);
dm9000_send_packet( buf, len ); //送给DM9000发送
}
这个函数主要是将上层入IP或ARP层传过来的数据包再加上MAC头发出去。
那么接收时去掉MAC头的函数呢,还记得在dm9000_receive_packet( )函数的最后将DM9000的数据通过net_receive(&buffer[0],packet_len);传到上层去了,那么就来看看这个函数表:
void net_receive( unsigned char *buf, unsigned int len )
{
struct eth_hdr *p = (struct eth_hdr *)(buf);
switch ( htons(p->type) )
{
case PROTO_ARP:
buf += sizeof(struct eth_hdr);
len -= sizeof(struct eth_hdr);
arp_process( buf, len );
break;
case PROTO_IP:
buf += sizeof(struct eth_hdr);
len -= sizeof(struct eth_hdr);
//ip_process( buf, len );
break;
default:
printf("\r\tFrame type error (=0x%04X)!\n", p->type);
break;
}
}
首先判断收到的数据包是IP包,还是ARP包,因为在MAC层上面,IP层和ARP层是处于同一层的,判断之后就是去掉MAC头啦,还是那样的小技巧,然后跳转到上一层的相应函数接着去掉各自的头部继续进行处理。
这里提到了ARP,ARP就是地址解析协议,简单地说,主机之间同过IP地址进行标识,但是各个主机之间实际是通过各自网卡的MAC地址进行标识从而通信,那么ARP的作用就是将IP地址转换为MAC地址。
这里为了测试我们的网卡的发送接收函数是否正常,写了一个arp请求函数,来看是否能够收到主机的ARP应答,从而得到主机的MAC地址。
程序如下:
void arp_request(void) //发送ARP请求数据包
{
unsigned char * arp_buf = &buffer[256];
arpbuf = (struct arp_hdr *)arp_buf;
arpbuf->hwtype = htons( 1 ); //the type of send hardware address,if 1 means yi tai wang di zhi
arpbuf->protocol = htons( 0x0800 ); //means ip------>arp
arpbuf->hwlen = 6;
arpbuf->protolen = 4;
arpbuf->opcode = htons( 1 ); //arp request
memcpy(arpbuf->smac, mac_addr, 6);
memcpy(arpbuf->sipaddr, ip_addr, 4);
memcpy(arpbuf->dipaddr, host_ip_addr, 4);
packet_len = 28;
mac_send(arp_buf, packet_len ,PROTO_ARP );
}
这事ARP请求函数,这里讲一个小插曲,刚开始总是收不到任何数据,后来才发现
mac_send(arp_buf, packet_len ,PROTO_ARP );写成
mac_send(arp_buf, packet_len ,PROTO_IP );了。
这里还是要仔细查阅下ARP报文的格式,在ARP报文前面的MAC头里面的帧格式应该天ARP:0x0806而不是IP:0x0800
当然了,我们是想获得主机的MAC地址,这里你要清楚的一点是主机(PC)上面TCP/IP已经很完善了,所以你发一个ARP请求报文过去,主机肯定会发一个ARP应答报文回来,所以你要做的工作就是接收这个ARP应答报文,保存这个报文里面所包含的主机的MAC地址,在这里,我们不仅仅实现了这个功能,同时也实现了如果主机发过来,ARP请求,我们处理这个请求的功能,实际上就是将自己的MAC地址发给主机。
unsigned char arp_process(unsigned char *buf, unsigned int len)
{
struct arp_hdr *pRx, *pTx;
pRx = (struct arp_hdr *)(buf);
pTx = (struct arp_hdr *)&buffer[512];
if(len<28)
{
printf("\narp packet length is not right!\n\r");
return 0;
}
switch (htons(pRx->opcode))
{
case 1: //处理ARP请求
if((pRx->dipaddr[0] == ip_addr[0]) && \
(pRx->dipaddr[1] == ip_addr[1]) && \
(pRx->dipaddr[2] == ip_addr[2]) && \
(pRx->dipaddr[3] == ip_addr[3]) )
{
pRx->hwtype = htons(1);
pRx->protocol = htons(0x0800); //代表IP协议
pRx->hwlen = 6;
pRx->protolen = 4;
pRx->opcode = htons(2); //ARP应答报文
memcpy(pTx->sipaddr,ip_addr,4); //将我们的IP,MAC地址发给请求方
memcpy(pTx->smac,mac_addr,6);
memcpy(pTx->dipaddr,pRx->sipaddr,4); //将收到的源方的IP地址,MAC地址变为目标地址
memcpy(pTx->dmac, pRx->smac,6);
mac_send( (unsigned char*)pTx, sizeof(struct arp_hdr), PROTO_ARP);
}
break;
case 2: //处理arp应答
if((pRx->dipaddr[0] == ip_addr[0]) && \
(pRx->dipaddr[1] == ip_addr[1]) && \
(pRx->dipaddr[2] == ip_addr[2]) && \
(pRx->dipaddr[3] == ip_addr[3]) )
memcpy(host_mac_addr,pRx->smac,6);
printf("\nwe receive host mac address\n\r");
printf("host_mac_addr 0 :%0x\n\r",host_mac_addr[0]);
printf("host_mac_addr 1 :%0x\n\r",host_mac_addr[1]);
printf("host_mac_addr 2 :%0x\n\r",host_mac_addr[2]);
printf("host_mac_addr 3 :%0x\n\r",host_mac_addr[3]);
printf("host_mac_addr 4 :%0x\n\r",host_mac_addr[4]);
printf("host_mac_addr 5 :%0x\n\r",host_mac_addr[5]);
break;
default:
printf("\n\r arp_opcode Not Support.\n");
break;
}
}
关于ARP的相关知识,自己查阅资料,也可以参考我转载的两篇文章:单片机驱动DM9000进行学习,看完之后自然会明白这两段程序。
这里再讲一个小插曲:
刚开始的时候:收数据错,是因为我dm9000收数据程序
unsigned short *datas;
datas = (unsigned short *)&buffer[0]; //now we begin to receive the real data
for(i = 0; i < len; i += 2) //16 bits mode
{
msdelay(1); //the reg MRCMD has set by the prior prior step
tem = DM9000_DATA;
datas[i] = tem & 0x0ff;
printf(“data: %0x”, datas[i]);
datas[i+1] = (tem>>8)&0x0ff;
printf(“data: %0x”, datas[i+1]);
}
加了这两句之后,发现打印出的数据都会在后面带个00,这个00肯定是多于的,于是定位到了这2句:
unsigned short *datas;
datas = (unsigned short *)&buffer[0];
看见了吧,两个字节,data[i]收一个数据之后,data[i+1]收下一个数据,那就是直接加了2个字节啊,所以会出现在每个数据之后都会跟一个00的原因。
所以改成如下:
unsigned char *datas;
datas = (unsigned char *)&buffer[0]; //now we begin to receive the real data
for(i = 0; i < len; i += 2) //16 bits mode
{
msdelay(1);
tem = DM9000_DATA; //the reg MRCMD has set by the prior prior step
datas[i] = tem & 0x0ff;
datas[i+1] = (tem>>8)&0x0ff;
}
就对了,
这时,收到数据了,结果并没有得到主机的MAC地址,通过打印主机的MAC地址发现还是全是FF,因为如果收到ARP应答包,程序会将ARP应答包中主机MAC地址替换掉原有的广播MAC地址(全是FF),后来将收到的数据全部打印出来,结果发现收到的不是ARP包,而是IP包,而且IP包中有主机的MAC地址,真是神了啊。
我发的ARP请求包,却收到了IP包。。。。。。。
于是我通过wireshark抓包,进行分析,发现主机确实收到了我的ARP请求包,这说明DM9000的发送函数是没有问题的,但是我收到的IP包是一个组播数据包,也就是不是针对我发的,只要在这个组内,都会收到数据包并不是ARP类型的包,说明我的dm9000的接收函数存在问题,但是看看也没什么问题。于是我烧写了linux内核,然后用vivi启动内核,然后ping了下,再抓包,发现正常的流程是发一个arp请求包,紧接着就会再发一个ARP应答包的,就在这时,我看到了一个致命的问题,主机的IP地址在我的程序中写错了一个输,本应是192.168.0.231,却被我写成了192.168.1.231,悲剧啊,怪不得总是收不到ARP应答包啊,请求的主机都不存在,怎么收到回应呢。。。。。。。。。
最后来看下效果图吧:
主机的MAC地址:
主机收到的ARP请求包:
主机发送的ARP应答包:
程序保存通过ARP请求获得的主机的MAC地址:
这里将源码上传:
m-boot_dm900发送接收,ARP协议正常.rar m-boot_dm9000初始化正常.rar
基于VxWorks的S3C2440开发板上DM9000网络芯片驱动开发.rar
注意m-boot_dm9000初始化正常中dm9000接收函数是错的,可以与对的进行对比
m-boot_dm900发送接收,ARP协议正常就是全对了
参考下面的博客了解ARP,DM9000
http://hi.baidu.com/firstm25/blog/item/989c2afcbddee387b801a0ee.html
http://hi.baidu.com/firstm25/blog/item/4f4157fb950301274f4aeab2.html