单片机驱动DM9000网卡芯片详细调试过程

发布者:画意人生最新更新时间:2016-10-04 来源: eefocus关键字:单片机  驱动DM9000  网卡芯片  调试过程 手机看文章 扫描二维码
随时随地手机看文章
和其它网卡芯片不同,DM9000系列网卡芯片在嵌入式开发板上很常见,尤其是有关ARM-Linux的开发板上的网络连接部分几乎都是采用该芯片完成的。当然,其它网卡芯片,如RTL8019的应用也很常见,在很多开发板上得到应用然而RTL8019的介绍在网上可以找到非常详细的介绍,尤其是用单片机对其做底层驱动的介绍非常丰富。下面的网站就介绍了用AVR驱动RTL8019网卡芯片的非常详细的过程,有兴趣的朋友可以参考一下。

http://members.home.nl/bzijlstra/software/examples/RTL8019as.htm AVR驱动RTL8019网卡芯片的详细介绍。

    言归正传。在网上也能找到许多关于DM9000网卡芯片的介绍,然而这些介绍大多是关于Linux或WinCE下的驱动程序或移植,很少有介绍单片机驱动DM9000的例子。因此我在这里把我调试DM9000E的过程详细说明一下,仅供参考。

    本文主要介绍单片机驱动DM9000E网卡芯片的详细过程。从网卡电路的连接,到网卡初始化相关程序调试,再到ARP协议的实现,一步一步详细介绍调试过程。如果有时间也会把UDP和TCP通讯实验过程写出来。当然,会用单片机编写DM9000的驱动,再想编写ARM下的Linux的驱动就容易的多了。在调试之前,应该先参考两份技术文档,可以从下面网站中下载。

DM9000E.pdf(芯片数据资料)和 DM9000 Application Notes Ver 1_22 061104.pdf(应用手册)

http://www.davicom.com.tw

    一、电路连接

     DM9000E网卡芯片支持8位、16位、32位模式的处理器,通过芯片引脚EEDO(65脚)和WAKEUP(79脚)的复位值设置支持的处理器类型,如16位处理器只需将这两个引脚接低电平即可,其中WAKEUP内部有60K下拉电阻,因此可悬空该引脚,或作为网卡芯片唤醒输出用。其它型号请参考相应的数据手册。

单片机驱动DM9000网卡芯片详细调试过程(转) - 东海 - 东海的博客

图1 DM9000引脚

    如图所示,对处理器驱动网卡芯片来说,我们比较关心的有以下几个引脚:IOR、IOW、AEN、CMD(SA2)、INT、RST,以及数据引脚SD0-SD15-SD31和地址引脚SA4-SA9。其中,地址引脚配合AEN引脚来选通该网卡芯片,对于大多数的应用来说没有意义,因为在我们的应用中一般只用一个网卡芯片,而这些地址引脚主要用于在多网卡芯片环境下选择其中之一。DM9000工作的默认基地址为0x300,这里我们按照默认地址选择,将SA9、SA8接高电平,SA7-DA4接低电平。多网卡环境可以根据TXD0-TXD3配置SA4-SA7来选择不同的网卡,这里不做介绍,有兴趣的朋友请参考应用手册和数据手册。数据引脚SD0-SD31则根据前面所讲的配置处理器模式与处理器的数据总线进行选择连接即可,没用到的引脚悬空。那么,除了地址、数据引脚外,剩下的与处理器有关引脚对我们来说及其重要了,而与处理器无关的引脚,只需按照应用手册连接即可。

    IOR和IOW是DM9000的读写选择引脚,低电平有效,即低电平时进行读(IOR)写(IOW)操作;AEN是芯片选通引脚,低电平有效,该引脚为低时才能进行读写操作;CMD的命令/数据切换引脚,低电平时读写命令操作,高电平时读写数据操作。 单片机驱动DM9000网卡芯片详细调试过程(转) - 东海 - 东海的博客

图2 读时序

单片机驱动DM9000网卡芯片详细调试过程(转) - 东海 - 东海的博客

图3 写时序

    这些引脚接口和其它单片机外围器件的引脚接口基本相同,其使用也一样。对于有总线接口的单片机来说,如51系列,ARM等直接连接即可。对于没有总线接口的来说,如AVR mega32等可以直接用I/O引脚模拟总线时序进行连接。连接时要参考读写时序,如上图所示。具体连接电路,有时间我再画出来,暂时先略了。

    二、编写驱动程序

    在这,我使用C语言编写驱动程序,这需要非常注意一点,即处理器所用的C编译器使用“大端格式”还是“小端格式”,这可以在相应处理器的C编译器说明上找到。一般比较常见的是小端格式。而对于8位处理器来说,在编写驱动程序时,可以不考虑,但是在编写网络协议的时候,一定好考虑,因为网络协议的格式是大端格式,而大部分编译器或者我们习惯的是小端格式,这一点需要注意。

    在DM9000中,只有两个可以直接被处理器访问的寄存器,这里命名为CMD端口和DATA端口。事实上,DM9000中有许多控制和状态寄存器(这些寄存器在上一篇文章中有详细的使用说明),但它们都不能直接被处理器访问,访问这些控制、状态寄存器的方法是:

(1)、将寄存器的地址写到CMD端口;

(2)、从DATA端口读写寄存器中的数据;

    1、读、写寄存器

    其实,INDEX端口和DATA端口的就是由芯片上的CMD引脚来区分的。低电平为INDEX端口,高电平为DATA端口。所以,要想实现读写寄存器,就必须先控制好CMD引脚。

    若使用总线接口连接DM9000的话,假设总线连接后芯片的基地址为0x800300(24根地址总线),只需如下方法:

#define DM_ADD (*((volatile unsigned int *) 0x8000300))

#define DM_CMD (*((volatile unsigned int *) 0x8000304))

//向DM9000寄存器写数据

void dm9000_reg_write(unsigned char reg, unsigned char data)

{

    udelay(20);//之前定义的微妙级延时函数,这里延时20us

    DM_ADD = reg;//将寄存器地址写到INDEX端口

    udelay(20);

    DM_CMD = data;//将数据写到DATA端口,即写进寄存器

}

//从DM9000寄存器读数据

unsigned int dm9000_reg_read(unsigned char reg)

{

    udelay(20);

    DM_ADD = reg;

    udelay(20);

    return DM_CMD;//将数据从寄存器中读出

}

    只得注意的是前面的两个宏定义DM_ADD和DM_CMD,定义的内容表示指向无符号整形变量的指针,在这里0x800300是DM9000命令端口的地址,对它的赋值操作就相当于把数据写到该地址中,即把数据写到DM9000的命令端口中。读的道理也一样。这是一种很常见的宏定义,一般在处理器中定义通用寄存器也是这样定义的。

    若没有总线接口的话,可以使用IO口模拟总线时序的方法实现寄存器的读写。这里只说明实现步骤。首先将处理器的I/O端口与DM9000的IOR等引脚直接相连(电平匹配的情况下),又假设已经有宏定义“IOR”I/O端口控制DM9000的IOR引脚,其它端口控制DM9000引脚的命名相同,“PIO1”(根据处理器情况,可以是8位、16位或32位的I/O端口组成)控制数据端口。这样宏命名更直观些。写寄存器的函数如下:

void dm9000_reg_write(unsigned char reg, unsigned char data)

{

PIO1 = reg;

AEN = 0;

CMD = 0;

IOR = 1;

IOW = 0;

udelay(1);

AEN = 1;

IOW = 1;

udelay(20);

PIO1 = data;

AEN = 0;

CMD = 0;

IOR = 1;

IOW = 0;

udelay(1);

AEN = 1;

IOW = 1;

}

    读寄存器的写法类似,这里就略一下了。这一过程看上去有些复杂,呵呵,其实执行起来也蛮有效率的,执行时间差不多。这种模拟总线时序的方式实际并不复杂,只是把总线方式下自动执行的过程手动的执行了一遍而已。

    在DM9000中,还有一些PHY寄存器,也称之为介质无关接口MII(Media Independent Interface)寄存器。对这些寄存器的操作会影响网卡芯片的初始化和网络连接,这里不对其进行操作,所以对这些寄存器的访问方法这里也略了(在上篇文章中有介绍)。操作不当反而使网卡不能连接到网络。

    至此,我们已经写好了两个最基本的函数:dm9000_reg_write()和dm9000_reg_read(),以及前面的宏定义DM_ADD和DM_CMD。下面将一直用到。

    2、初始化DM9000网卡芯片。

    初始化DM9000网卡芯片的过程,实质上就是填写、设置DM9000的控制寄存器的过程,这里以程序为例进行说明。其中寄存器的名称宏定义在DM9000.H中已定义好。

注:一下函数中unsigned char为一个字节unsigned int为两个字节

//DM9000初始化

void DM9000_init(void)

{
    unsigned int i;

    IO0DIR |= 1 << 8;

    IO1CLR |= 1 << 8;

    udelay(500000);

    IO2SET |= 1 << 8;

    udelay(500000);

    IO1CLR |= 1 << 8;

    udelay(500000);

/*以上部分是利用一个IO口控制DM9000的RST引脚,使其复位。这一步可以省略,可以用下面的软件复位代替*/

    dm9000_reg_write(GPCR, 0x01);//设置 GPCR(1EH) bit[0]=1,使DM9000的GPIO3为输出。

    dm9000_reg_write(GPR, 0x00);//GPR bit[0]=0 使DM9000的GPIO3输出为低以激活内部PHY。

    udelay(5000);//延时2ms以上等待PHY上电。

    dm9000_reg_write(NCR, 0x03);//软件复位

    udelay(30);//延时20us以上等待软件复位完成

    dm9000_reg_write(NCR, 0x00);//复位完成,设置正常工作模式。

    dm9000_reg_write(NCR, 0x03);//第二次软件复位,为了确保软件复位完全成功。此步骤是必要的。

    udelay(30);

    dm9000_reg_write(NCR, 0x00);

/*以上完成了DM9000的复位操作*/

    dm9000_reg_write(NSR, 0x2c);//清除各种状态标志位

    dm9000_reg_write(ISR, 0x3f);//清除所有中断标志位

/*以上清除标志位*/

    dm9000_reg_write(RCR, 0x39);//接收控制

    dm9000_reg_write(TCR, 0x00);//发送控制

    dm9000_reg_write(BPTR, 0x3f);

    dm9000_reg_write(FCTR, 0x3a);

    dm9000_reg_write(RTFCR, 0xff);

    dm9000_reg_write(SMCR, 0x00);

/*以上是功能控制,具体功能参考上一篇文章中的说明,或参考数据手册的介绍*/

    for(i=0; i<6; i++)

        dm9000_reg_write(PAR + i, mac_addr[i]);//mac_addr[]自己定义一下吧,6个字节的MAC地址

/*以上存储MAC地址(网卡物理地址)到芯片中去,这里没有用EEPROM,所以需要自己写进去*/

/*关于MAC地址的说明,要参考网络相关书籍或资料*/

    dm9000_reg_write(NSR, 0x2c);

    dm9000_reg_write(ISR, 0x3f);

/*为了保险,上面有清除了一次标志位*/

    dm9000_reg_write(IMR, 0x81);

/*中断使能(或者说中断屏蔽),即开启我们想要的中断,关闭不想要的,这里只开启的一个接收中断*/

/*以上所有寄存器的具体含义参考上一篇文章,或参考数据手册*/

}

    这样就对DM9000初始化完成了,怎么样,挺简单的吧。

    3、发送、接收数据包

    同样,以程序为例,通过注释说明。

//发送数据包

//参数:datas为要发送的数据缓冲区(以字节为单位),length为要发送的数据长度(两个字节)。

void sendpacket(unsigned char *datas, unsigned int length)

{
    unsigned int len, i;
    
    dm9000_reg_write(IMR, 0x80);//先禁止网卡中断,防止在发送数据时被中断干扰
    
    len = length;

    dm9000_reg_write(TXPLH, (len>>8) & 0x0ff);

    dm9000_reg_write(TXPLL, len & 0x0ff);

/*这两句是将要发送数据的长度告诉DM9000的寄存器*/

    DM_ADD = MWCMD;//这里的写法是针对有总线接口的处理器,没有总线接口的处理器要注意加上时序。

    for(i=0; i

    {

        udelay(20);

        DM_CMD = datas[i] | (datas[i+1]<<8);

    }

/*上面是将要发送的数据写到DM9000的内部SRAM中的写FIFO中,注意没有总线接口的处理器要加上适当的时序*/

/*只需要向这个寄存器中写数据即可,MWCMD是DM9000内部SRAM的DMA指针,根据处理器模式,写后自动增加*/

    dm9000_reg_write(TCR, 0x01);//发送数据到以太网上

    while((dm9000_reg_read(NSR) & 0x0c) == 0);//等待数据发送完成

    udelay(20);

    dm9000_reg_write(NSR, 0x2c);//清除状态寄存器,由于发送数据没有设置中断,因此不必处理中断标志位

    dm9000_reg_write(IMR, 0x81);//DM9000网卡的接收中断使能

}

    以上是发送数据包,过程很简单。而接收数据包确需要些说明了。DM9000从网络中接到一个数据包后,会在数据包前面加上4个字节,分别为“01H”、“status”(同RSR寄存器的值)、“LENL”(数据包长度低8位)、“LENH”(数据包长度高8位)。所以首先要读取这4个字节来确定数据包的状态,第一个字节“01H”表示接下来的是有效数据包,若为“00H”则表示没有数据包,若为其它值则表示网卡没有正确初始化,需要从新初始化。

    如果接收到的数据包长度小于60字节,则DM9000会自动为不足的字节补上0,使其达到60字节。同时,在接收到的数据包后DM9000还会自动添加4个CRC校验字节。可以不予处理。于是,接收到的数据包的最小长度也会是64字节。当然,可以根据TCP/IP协议从首部字节中出有效字节数,这部分在后面讲解。下面为接收数据包的函数。

//接收数据包

//参数:datas为接收到是数据存储位置(以字节为单位)

//返回值:接收成功返回数据包类型,不成功返回0

unsigned int receivepacket(unsigned char *datas)

{

    unsigned int i, tem;

    unsigned int status, len;

    unsigned char ready;

    ready = 0;//希望读取到“01H”

    status = 0;//数据包状态

     len = 0; //数据包长度

/*以上为有效数据包前的4个状态字节*/

    if(dm9000_reg_read(ISR) & 0x01)

    {

        dm9000_reg_write(ISR, 0x01);

    }

/*清除接收中断标志位*/

/***********************************************************************************/

/*这个地方遇到了问题,下面的黑色字体语句应该替换成成红色字体,也就是说MRCMDX寄存器如果第一次读不到数据,还要读一次才能确定完全没有数据。

在做 PING 实验时证明:每个数据包都是通过第二次的读取MRCMDX寄存器操作而获知为有效数据包的,对初始化的寄存器做了多次修改依然是此结果,但是用如下方法来实现,绝不会漏掉数据包。*/

    ready = dm9000_reg_read(MRCMDX); // 第一次读取,一般读取到的是 00H

    if((ready & 0x0ff) != 0x01)

    {

        ready = dm9000_reg_read(MRCMDX); // 第二次读取,总能获取到数据

        if((ready & 0x01) != 0x01)

         {

            if((ready & 0x01) != 0x00) //若第二次读取到的不是 01H 或 00H ,则表示没有初始化成功

            {

                 dm9000_reg_write(IMR, 0x80);//屏幕网卡中断

                 DM9000_init();//重新初始化

                 dm9000_reg_write(IMR, 0x81);//打开网卡中断

            }

            retrun 0;

         }

    }

/* ready = dm9000_reg_read(MRCMDX); // read a byte without pointer increment

    if(!(ready & 0x01))

    {

         return 0;

    }*/

/***********************************************************************************/

/*以上表示若接收到的第一个字节不是“01H”,则表示没有数据包,返回0*/

    status = dm9000_reg_read(MRCMD);

    udelay(20);

    len = DM_CMD;

    if(!(status & 0xbf00) && (len < 1522))

    {

        for(i=0; i

        {

            udelay(20);

            tem = DM_CMD;

            datas[i] = tem & 0x0ff;

            datas[i + 1] = (tem >> 8) & 0x0ff;

        }

    }

    else

    {
        return 0;

    }

/*以上接收数据包,注意的地方与发送数据包的地方相同*/

    if(len > 1000) return 0;

    if( (HON( ETHBUF->type ) != ETHTYPE_ARP) &&

        (HON( ETHBUF->type ) != ETHTYPE_IP) )

    {

        return 0;

    }

    packet_len = len;

/*以上对接收到的数据包作一些必要的限制,去除大数据包,去除非ARP或IP的数据包*/
    
      
    return HON( ETHBUF->type ); //返回数据包的类型,这里只选择是ARP或IP两种类型

}

    注意:上面的函数用到了一些宏定义,已经在头文件中定义过,这里说明一下:其中uint16定义为两个字节的变量,根据C编译器进行定义。

unsigned char Buffer[1000];//定义了一个1000字节的接收发送缓冲区

uint16 packet_len;//接收、发送数据包的长度,以字节为单位。

struct eth_hdr //以太网头部结构,为了以后使用方便

{

unsigned char d_mac[6];   //目的地址

unsigned char s_mac[6];   //源地址

uint16 type;     //协议类型

};

struct arp_hdr //以太网头部+ARP首部结构

{

struct eth_hdr ethhdr;    //以太网首部

uint16 hwtype;     //硬件类型(1表示传输的是以太网MAC地址)

uint16 protocol;    //协议类型(0x0800表示传输的是IP地址)

unsigned char hwlen;     //硬件地址长度(6)

unsigned char protolen;    //协议地址长度(4)

uint16 opcode;     //操作(1表示ARP请求,2表示ARP应答)

unsigned char smac[6];    //发送端MAC地址

unsigned char sipaddr[4];    //发送端IP地址

unsigned char dmac[6];    //目的端MAC地址

unsigned char dipaddr[4];    //目的端IP地址

};

struct ip_hdr //以太网头部+IP首部结构

{

struct eth_hdr ethhdr;    //以太网首部

unsigned char vhl,      //4位版本号4位首部长度(0x45)

           tos;     //服务类型(0)

   uint16 len,      //整个IP数据报总字节长度

         ipid,           //IP标识

         ipoffset;     //3位标识13位偏移

unsigned char ttl,             //生存时间(32或64)

          proto;         //协议(1表示ICMP,2表示IGMP,6表示TCP,17表示UDP)

uint16 ipchksum;    //首部校验和

unsigned char srcipaddr[4],    //源IP

             destipaddr[4];   //目的IP

};

    以上定义的三种首部结构,是根据TCP/IP协议的相关规范定义的,后面会对ARP协议进行详细讲解。

【上半部分完】

   4、验证初始化中的各个函数。

    下面我们来看一下,上面所写的初始化函数是否可用。以上我们写好了三个函数,分别为

DM9000_init(),sendpacket()和receivepacket(),保存并命名为dm9000.c。既然我们要进行调试,当

然要有结果输出,根据自己的处理器的情况写一个串口程序,这些函数是学某个单片机的基础,这里不

做详细介绍,用到是时候会在函数里注释一下。

    接下来我们来写个主函数,新建C文件,命名为mian.c,填写如下函数:

void main(void)

{

    unsigned int i;

    unsigned char c;

    uart0_init();//初始化串口,调试时用到

    DM9000_init();//初始化网卡

    print_regs();/*通过串口,将DM9000中的寄存器打印出来,显示在超级终端上。此函数根据自己

的处理器进行修改,功能仅仅是读DM9000寄存器dm9000_reg_read(),再通过串口打印出来而已*/

}

    函数写好,保存文件,连接硬件,连接网线到电脑上或局域网上,运行结果如下图所示: 单片机驱动DM9000网卡芯片详细调试过程(转) - 东海 - 东海的博客

图4 显示寄存器值

    这里首先检查,各个控制寄存器是否是自己写进去的值,在检查状态寄存器是否正确,其中主要要

看NSR寄存器的bit[5]是否为“1”,该位表示是否连接成功。本例中NSR的值为40H,括号里的数为对应

的十进制数。

    下面我们将主函数改进一下,增加个中断接收函数,查看是否能接收到数据。

void main(void)

{

    unsigned int i;

    unsigned char c;

    uart0_init();//初始化串口,调试时用到

    DM9000_init();//初始化网卡

/********************************************************************************/

/*这一部分要根据自己的处理器情况,将DM9000的INT引脚连接到处理器的外部中断上,打开中断*/

/********************************************************************************/

    sendpacket(60);/*我事先已经在Buffer[]中存储了ARP请求数据包,这里就直接发送了,以便接收

ARP应答包。大家可以先参考后面讲的ARP协议,根据自己机器的情况,将数据事先存到Buffer[]中*/

    while(1);//等待中断

}

void int_issue(void) //中断处理函数,需要根据自己的处理器进行设置

{

    unsigned int i;

    i = receivepacket(Buffer);//将数据读取到Buffer中。

int_again :

    if(i == 0)

    {

        return;

    }

     else

     {

        print_buffer();//将接收到的所有数据打印出来

         while(1);//停止在这里等待观察,注意:实际应用中是不允许停止在中断中的。

     }

/************************************************************************************/

/*这里加上这一段,目的是判断中断期间是否接收到其它数据包。有则加以处理。不加也完全可以*/

/* 根据自己的处理器,判断处理器是否还处在中断状态,若是则进行如下操作,不是则跳过该段。*/

    i = receivepacket(Buffer);

    if(i != 0)

    {

        goto int_again;

    }

/************************************************************************************/

}

    编译调试,运行结果如下: 单片机驱动DM9000网卡芯片详细调试过程(转) - 东海 - 东海的博客

图5 接收数据包中的数据

    这是一个ARP应答包,包含了我电脑上的MAC地址和局域网内的IP地址。反正我也不是啥重要人物,

这里就不保密了,呵呵。

    如果一些顺利,到这里对DM9000网卡芯片的初始化工作就完成了。如果出现问题,出现问题首先要

检查寄存器的值是否正确。可以将DM9000中的寄存器打印出来,查看到底是哪里的问题。如果打印出的

值很混乱,在确保串口程序无误的前提下,查看硬件连接,以及寄存器读写时序是否正确,重复调试几

次查找原因。

    三、ARP协议的实现

    1、ARP协议原理简述

    ARP协议(Address Resolution Protocol 地址解析协议),在局域网中,网络中实际传输的是“

帧”,帧里面有目标主机的MAC地址。在以太网中,一个注意要和另一个主机进行直接通信,必须要知

道目标主机的MAC地址。这个MAC地址就是标识我们的网卡芯片唯一性的地址。但这个目标MAC地址是如

何获得的呢?这就用到了我们这里讲到的地址解析协议。所有“地址解析”,就是主机在发送帧前将目

标IP地址转换成MAC地址的过程。ARP协议的基本功能就是通过目标设备的IP地址,查询目标设备的MAC

地址,以保证通信的顺利进行。所以在第一次通信前,我们知道目标机的IP地址,想要获知目标机的

MAC地址,就要发送ARP报文(即ARP数据包)。它的传输过程简单的说就是:我知道目标机的IP地址,

那么我就向网络中所有的机器发送一个ARP请求,请求中有目标机的IP地址,请求的意思是目标机要是

收到了此请求,就把你的MAC地址告诉我。如果目标机不存在,那么此请求自然不会有人回应。若目标

机接收到了此请求,它就会发送一个ARP应答,这个应答是明确发给请求者的,应答中有MAC地址。我接

到了这个应答,我就知道了目标机的MAC地址,就可以进行以后的通信了。因为每次通信都要用到MAC地

址。

    ARP报文被封装在以太网帧头部中传输,如图为ARP请求报文的头部格式。 单片机驱动DM9000网卡芯片详细调试过程(转) - 东海 - 东海的博客

图6 用于以太网的ARP请求或应答分组格式

    注意,以太网的传输存储是“大端格式”,即先发送高字节后发送低字节。例如,两个字节的数据

,先发送高8位后发送低8位。所以接收数据的时候要注意存储顺序。

    整个报文分成两部分,以太网首部和ARP请求/应答。下面挑重点讲述。

“以太网目的地址”字段:若是发送ARP请求,应填写广播类型的MAC地址FF-FF-FF-FF-FF-FF,意思是

让网络上的所有机器接收到;

“帧类型”字段:填写08-06表示次报文是ARP协议;

“硬件类型”字段:填写00-01表示以太网地址,即MAC地址;

“协议类型”字段:填写08-00表示IP,即通过IP地址查询MAC地址;

“硬件地址长度”字段:MAC地址长度为6(以字节为单位);

“协议地址长度”字段:IP地址长度为4(以字节为单位);

“操作类型”字段:ARP数据包类型,0表示ARP请求,1表示ARP应答;

“目的以太网地址”字段:若是发送ARP请求,这里是需要目标机填充的。


    2、ARP的处理程序

    ARP协议原理很简单,下面我们来编写ARP协议的处理函数。新建文件命名为arp.c,填写如下函数

unsigned char mac_addr[6] = {*,*,*,*,*,*};

unsigned char ip_addr[4] = { 192, 168, *, * };

unsigned char host_ip_addr[4] = { 192, 168, *, * };

unsigned char host_mac_addr[6]={ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };

unsigned char Buffer[1000];

uint16 packet_len;

/*这些全局变量,在前面将的文件中有些已经有过定义,这里要注意在前面加上“extern”关键字。“

*”应该根据自己的机器修改*/

#define HON(n) ((((uint16)((n) & 0xff)) << 8) | (((n) & 0xff00) >> 8))

/*此宏定义是将小端格式存储的字(两个字节)转换成大端格式存储*/

void arp_request(void) //发送ARP请求数据包

{

//以太网首部

memcpy(ARPBUF->ethhdr.d_mac, host_mac_addr, 6);

/*字符串拷贝函数,文件要包含头文件。参数依次是,拷贝目标指针,拷贝数据源指针,拷

贝字符数*/

memcpy(ARPBUF->ethhdr.s_mac, mac_addr, 6);

ARPBUF->ethhdr.type = HON( 0x0806 );

/*小端格式的编译器,可以用HON()宏来转换成大端格式,如果你的编译器是大端格式,直接填写

0x0806即可*/

/*就是简单的按照协议格式填充,以下同*/

//ARP首部

ARPBUF->hwtype = HON( 1 );

ARPBUF->protocol = HON( 0x0800 );

ARPBUF->hwlen = 6;

ARPBUF->protolen = 4;

ARPBUF->opcode = HON( 0 );

memcpy(ARPBUF->smac, mac_addr, 6);

memcpy(ARPBUF->sipaddr, ip_addr, 4);

memcpy(ARPBUF->dipaddr, host_ip_addr, 4);

packet_len = 42;//14+28=42

sendpacket( Buffer, packet_len );

}

注释:ARPBUF的宏定义和ARP首部结构,在前面已经讲过。同时注意执行该函数时中断的处理。这里没

作处理。

    看上去很easy吧,下面函数实现接收ARP请求或接收ARP应答的处理。

unsigned char arp_process(void)//ARP接收函数,成功返回1,否则返回0

{

//简单判断ARP数据包有无损坏,有损坏则丢弃,不予处理

if( packet_len < 28 )//ARP数据长度为28字节为无效数据

{

return 0;

}

switch ( HON( ARPBUF->opcode ) )

{

   case 0    : //处理ARP请求

         if( ARPBUF->dipaddr[0] == ip_addr[0] &&

             ARPBUF->dipaddr[1] == ip_addr[1] &&

             ARPBUF->dipaddr[2] == ip_addr[2] &&

             ARPBUF->dipaddr[3] == ip_addr[3] )//判断是否是自己的IP,是否向自己询问MAC地址

         { 
             ARPBUF->opcode = HON( 2 );//设置为ARP应答

             memcpy(ARPBUF->dmac, ARPBUF->smac, 6);

             memcpy(ARPBUF->ethhdr.d_mac, ARPBUF->smac, 6);

             memcpy(ARPBUF->smac, mac_addr, 6);

             memcpy(ARPBUF->ethhdr.s_mac, mac_addr, 6);

             memcpy(ARPBUF->dipaddr, ARPBUF->sipaddr, 4);

             memcpy(ARPBUF->sipaddr, ip_addr, 4);

             ARPBUF->ethhdr.type = HON( 0x0806 );

             packet_len = 42;

             sendpacket( Buffer, packet_len );//发送ARP数据包

             return 1;

         }

         else

         {

             return 0;

         }

         break;

   case 1    : //处理ARP应答

         if( ARPBUF->dipaddr[0] == ip_addr[0] &&

             ARPBUF->dipaddr[1] == ip_addr[1] &&

             ARPBUF->dipaddr[2] == ip_addr[2] &&

             ARPBUF->dipaddr[3] == ip_addr[3] )//再次判断IP,是否是给自己的应答

         {

          memcpy(host_mac_addr, ARPBUF->smac, 6);//保存服务器MAC地址

          return 1;

         }

         else

         {

             return 0;

         }

         break;

default     ://不是ARP协议

         return 0;

}

}

    根据ARP协议格式看这两个函数并不困难。于是我们又得到两个函数:arp_request()和

arp_process()。

    3、ARP程序调试

    下面我们修改主函数和中断处理函数。

    将mian()函数中的“sendpacket(60);”语句换成“arp_request();”语句。

void int_issue(void) //中断处理函数,需要根据自己的处理器进行设置

{

    unsigned int i;

    i = receivepacket(Buffer);//将数据读取到Buffer中。

    if(i == 0)

    {

        return;

    }

     else

     {

         i = arp_process();

         if(i == 1)//判断是否是ARP协议

             print_hostmacaddr();//打印目标机的MAC地址,就是用串口打印host_mac_addr[]中的6

个字节

     }

}

    保存运行调试。 单片机驱动DM9000网卡芯片详细调试过程(转) - 东海 - 东海的博客

图7 主机MAC地址

    至此,关于DM9000的调试过程就完成了。之后我还调试了UDP通讯、TCP通讯等,主要是关于协议的

处理了,这里就不介绍了。有兴趣的朋友可以参看《TCP/IP协议》第一卷,将会有很大帮助。希望这些

调试过程能为读者或多火烧的提供些有用的信息,也欢迎大家和我一起讨论。

关键字:单片机  驱动DM9000  网卡芯片  调试过程 引用地址:单片机驱动DM9000网卡芯片详细调试过程

上一篇:C51、PIC和AVR单片机性能比较
下一篇:一些实用的单片机c程序

推荐阅读最新更新时间:2024-03-16 15:13

STM32的一些基本知识总结
Cortex-M3缩略语 AMBA:先进单片机总线架构 ADK:AMBA设计套件 AHB:先进高性能总线 AHB-AP:AHB访问端口 APB:先进外设总线 ARM ARM:ARM架构参考手册 ASIC:行业领域专用集成电路 ATB :先进跟踪总线 BE8:字节不变式大端模式 CPI:每条指令的周期数 DAP:调试访问端口 DSP:数字信号处理(器) DWT:数据观察点及跟踪 ETM:嵌入式跟踪宏单元 FPB:闪存地址重载及断点 FSR:fault状态寄存器 HTM:Core Sight AHB跟踪宏单元 ICE:在线仿真器 IDE:集成开发环境 IRQ:中断请求(通常是外中断请求) ISA:指令系统架构 ISR:中断服务例程 I
[单片机]
STM32的一些基本知识总结
2011年市场景气低迷依旧,但供求不平衡有望缓解
受访人:潘松,上海海尔集成电路有限公司副总经理   全球经济还远远没有走出金融危机的影响,以美国为代表的世界主要经济体还陷在泥潭中,而以中国为代表的新兴经济体又面临通胀等压力。总体来说,2011年的全球IC和电子元器件市场不容乐观。在市场成长率、出货量方面仍会较为低迷;不过2010年下半年出现的因过度紧缩造成的供求不平衡将不再出现。   绿色环境能源政策引发的产品升级,以及物联网、智能电网、生产设备更新等一系列内需市场的启动,会给中高端产品市场带来较好的机会。但在低端消费品市场方面,随着人民币升值的压力,劳力、资源成本的上升,低端产品将会面临更为残酷的竞争。上述市场以及未来中高端消费品市场如3DTV、4G通讯、电容触控等
[半导体设计/制造]
大联大友尚集团推出基于TI高性能MCU EtherCAT接口参考解决方案
2017年3月9日,致力于亚太地区市场的领先半导体元器件分销商---大联大控股宣布,其旗下友尚推出基于TI C2000 Delfino™TMS320F2837xD的EtherCAT接口参考解决方案。 大联大友尚代理的TI最高性能的C2000 Delfino™TMS320F2837xD是一款功能强大的32位MCU,具有双CPU和双CLA,总系统吞吐量高达800 MIPS。凭借新的VCU和TMU 加速器、PWM增强功能以及16位精度ADC和更多模拟和控制外设,该微控制器可以应对最高级的控制环路挑战,例如工业驱动器和伺服电机控制、太阳能逆变器和转换器、数字电源、电力输送以及电力线通信等等。 其32位C28x架构CPU的内核可提供
[单片机]
大联大友尚集团推出基于TI高性能<font color='red'>MCU</font> EtherCAT接口参考解决方案
51单片机串口通信的注记
最近折腾51单片机,当然学到的都是比较基础的东西,可能我认为比较复杂的概念有 中断 和 串口通信 ,这篇主要是讲串口通信 使用的自然是山大王STC89C52RC的芯片来学习 串口通信主要是C51单片机的RXD 和TXD 与上位机进行通信,由于上位机一般是电脑,所以要TTL转成电脑能读出的RS232的电路来支持 一般使用MAX232 芯片 串口通信电路如下 之后就可以电脑的串口与这里的串口头子接了,注意 串口先要交叉一下 即电脑的TXD 对应下位机的RXD C51单片机串口通信模式有4种,最常用的是第一种 方式0 同步移位寄存器方式 ,一般用来扩展I/O口, 方式1 10位异步收发(8位数据),
[单片机]
51<font color='red'>单片机</font>串口通信的注记
基础概念之单片机的时钟周期和机器周期概念
1.什么是51单片机的时钟周期? 科普中国:时钟周期,又称为震荡周期,是为单片机提供定时信号的震荡源的周期,是单片机最基本的时间单位。谈到时钟周期就必须说晶体振荡器即即晶振,晶振可以说是单片机的心脏,它为单片机提供一个时钟频率 fosc(震荡频率)。 而所谓的震荡频率(时钟频率)自然是晶振一秒钟震荡的次数。时钟频率越高,单片机运行速度越快,就如电脑的CPU什么可以加速到多少GHz一样。自然时钟周期Tosc=1/fosc. 例如 震荡频率为12MHz的单片机即 fosc = 12MHz,那么震荡周期 Tosc = 1/fosc= 1/12 us。 chen:说白了晶振的倒数就是时钟周期。 2.什么是51单片机的机器周期?
[单片机]
在8031单片机上扩展4KB EPROM程序存储器
扩展程序 存储器 常用的芯片是EPROM(Erasable Programmable Read Only Memory)型(紫外线可擦除型), 如2716(2K×8)、2732(4K×8)、2764(8K×8)、27128(16K×8)、27256(32K×8)、27512(64K×8)等。另外,还有+5 V电可擦除EEPROM,如2816(2K×8)、2864(8K×8)等等。 紫外线擦除电可编程只读存储器EPROM是国内用得较多的程序存储器。EPROM芯片上有一个玻璃窗口,在紫外线照射下,存储器中的各位信息均变1,即处于擦除状态。擦除干净的EPROM可以通过编程器将应用程序固化到芯片中。 如果程序总量不超过4
[单片机]
在8031<font color='red'>单片机</font>上扩展4KB EPROM程序存储器
基于飞思卡尔MCU的空调(HVAC)与供暖通风方案
方案描述: 空调 ( HVAC ) 与 供暖通风 系统用于调节车内气流,为驾乘者提供舒适的环境。这些系统一般需要根据不同输入条件 (如温度) 控制多个电机 (如运行风机和风扇),高端系统还含有LCD显示屏,甚至触控执行器。 HVAC 系统必须有效、安静且经济地运行。多个电机需要同时控制,而且系统需要满足汽车系统严格的可靠性要求。新型系统使用更少的传感器检测车内环境,因此对计算能力提出了更高的要求。 飞思卡尔 丰富的微控制器 ( MCU ) 产品组合可在存储器容量、性能和引脚数方面,满足车载 HVAC 系统的不同应用要求。此外,由于具备PWM功能、低静态电流、过流保护、过热保护和负载电流反馈等特点,采用电机驱动器 (如MC33
[汽车电子]
基于飞思卡尔<font color='red'>MCU</font>的空调(HVAC)与供暖通风方案
MSP430单片机I/O端口控制特点
MSP430的I/O端口可以实现双向的输入、输出;完成一些特殊功能如:驱动LCD、A/D转换、捕获比较等;实现I/O各种中断。MSP430采用了传统的8位端口方式保证其兼容性,即每个I/O端口控制8个I/O引脚。为了实现对I/O端口每一个引脚的复杂控制,MSP430中的每个I/O口都对应一组8位的控制寄存器(如图1)。寄存器中的每一位对应一个I/O引脚,实现对该引脚的独立控制。寄存器的功能和数目是由该I/O口所能完成的功能以及类型确定的。 图1为MSP430的一个I/O端口的控制结构示意图。对于最基本的只能完成输入、输出功能的I/O端口其控制寄存器只有3个。其中,输入寄存器保存输入状态;输出寄存器保存输出的状态,方向寄存器控制
[单片机]
小广播
添点儿料...
无论热点新闻、行业分析、技术干货……
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved