打印

单片机驱动DM9000

[复制链接]
3916|9
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
river_huang|  楼主 | 2011-4-25 12:45 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
    在DM9000中,只有两个可以直接被处理器访问的寄存器,这里命名为CMD端口和DATA端口。事实上,DM9000中有许多控制和状态寄存器(这些寄存器在上一篇**中有详细的使用说明),但它们都不能直接被处理器访问,访问这些控制、状态寄存器的方法是:
(1)、将寄存器的地址写到CMD端口;
(2)、从DATA端口读写寄存器中的数据;
    1、读、写寄存器
    其实,INDEX端口和DATA端口的就是由芯片上的CMD引脚来区分的。低电平为INDEX端口,高电平为DATA端口。所以,要想实现读写寄存器,就必须先控制好CMD引脚。
    若使用总线接口连接DM9000的话,假设总线连接后芯片的基地址为0x800300(24根地址总线),只需如下方法:
#define DM_ADD (*[1]
#define DM_CMD (*[2]
//向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);//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<len; i+=2)//16 bit mode
    {
        udelay(20);
        DM_CMD = datas | (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<len; i+=2)// 16 bit mode
        {
            udelay(20);
            tem = DM_CMD;
            datas = 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两种类型

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

相关帖子

沙发
river_huang|  楼主 | 2011-5-10 15:40 | 只看该作者
路过··顶下···

使用特权

评论回复
板凳
jiangjie21IC| | 2011-5-11 22:22 | 只看该作者
路过一下......

使用特权

评论回复
地板
gstlzs| | 2011-5-12 11:41 | 只看该作者
路过一下......

使用特权

评论回复
5
river_huang|  楼主 | 2011-5-17 10:23 | 只看该作者
路过···

使用特权

评论回复
6
kj852284877| | 2011-5-17 10:24 | 只看该作者
顶起

使用特权

评论回复
7
river_huang|  楼主 | 2011-8-22 10:04 | 只看该作者
:)

使用特权

评论回复
8
chendefy| | 2011-8-22 22:46 | 只看该作者
好长的代码,标记一下,以后看用不用得上

使用特权

评论回复
9
axw_bab| | 2011-12-7 14:03 | 只看该作者
路过

使用特权

评论回复
10
river_huang|  楼主 | 2012-4-20 12:01 | 只看该作者
自己过来看看··

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

4

主题

133

帖子

1

粉丝