八,实现tftp下载之DM9000驱动

首先要将启动代码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头进行接收。细分如下:

1DM9000的初始化;

2DM9000的发送函数;

3DM9000的接收函数;

4MAC层头部的处理函数。

 

谈到了DM9000,首先讲下DM9000的相关知识:

第一,   mini2440DM9000的地址问题:

DM9000datasheet31页写道:

要想操作DM9000,只有两个接口,一个是INDEX接口,一个是DATA接口,当CMD0时,操作的是INDEX接口,当CMD1时,操作的是DATA接口。当要操作DM9000中的寄存器时,首先要将DM9000的寄存器保存在INDEX接口中,然后再通过写数据到DATA接口实现DM9000的写寄存器操作,或者通过读DATA接口实现DM9000寄存器的读操作。

那么CMDmini2440上接的是什么呢?

 

可以看出接的是CMD接的是ADDR2,同时DM9000是接的nGCS4,第4bank,所以DM9000的基地址为0x20000000;因此可以算出INDEX端口的地址为:0x2000,0000;DATA端口的地址为0x20000004CMD=1ADDR2=1),而为什么网上有很多人将DM9000的地址写成0x20000300也可以的呢?应该是这个人的开发板上的DM9000CMD引脚接到了ADDR8上,很有意思的一点是,DM9000的地址只受CMD这一条地址线的影响,因此0x200000000x20000300对它来说是没有区别的。

根据以上所述,要想往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位的寄存器,因为mini2440DM9000是连接成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字节的头,可以用MRCMDXMRCMD两个寄存器来读取接收到的数据包的信息。这4个字节头的内容分别为:

第一个:是接收数据标志,若为0x01,则表示有数据被接收并保存在RX SRAM中,这时候可以读出并进行处理,如果为0x00则表示没有数据到达。如果既不是0x01也不是0x00,则认为有异常发生,应该将DM9000重启以使芯片恢复到正常状态。

第二个:是status,是接收数据包的状态字,起其内容和寄存器0x06H的内容相同,可以用来判断接收的数据是否正常。或发生了何种异常。

第三,第四个字节,分别为接收到数据时数据包的长度,分别为低字节和高字节,在读取数据包的时候可以用这个长度来进行控制。这4个字节是DM9000在接收数据的时候添加的信息,不属于数据包的内容,从第5个字节开始的数据才是真正的数据包的内容,长度存在第34字节当中。

 

按照惯例,要想着整个接收数据的流程是怎么样的:

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发送

}

 

这个函数主要是将上层入IPARP层传过来的数据包再加上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头啦,还是那样的小技巧,然后跳转到上一层的相应函数接着去掉各自的头部继续进行处理。

 

这里提到了ARPARP就是地址解析协议,简单地说,主机之间同过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头里面的帧格式应该天ARP0x0806而不是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);                      //将我们的IPMAC地址发给请求方

                            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





你可能感兴趣的:(TFTP,DM9000驱动)