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(应用手册)
或者
DM9000 Datasheet VF03:
http://www.davicom.com.tw/userfile/24247/DM9000-DS-F03-041906_1.pdf
DM9000A Datasheet:
http://www.davicom.com.tw/userfile/24247/DM9000A-DS-F01-101906.pdf
DM9000 Application Notes V1.22
http://www.davicom.com.tw/big5/download/Data Sheet/DM9000_Application_Notes_Ver_1_22 061104.pdf
一、电路连接
图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则根据前面所讲的配置处理器模式与处理器的数据总线进行选择连接即可,没用到的引脚悬空。那么,除了地址、数据引脚外,剩下的与处理器有关引脚对我们来说及其重要了,而与处理器无关的引脚,只需按照应用手册连接即可。
图2 读时序
图3 写时序
这些引脚接口和其它单片机外围器件的引脚接口基本相同,其使用也一样。对于有总线接口的单片机来说,如51系列,ARM等直接连接即可。对于没有总线接口的来说,如AVR mega32等可以直接用I/O引脚模拟总线时序进行连接。连接时要参考读写时序,如上图所示。具体连接电路,有时间我再画出来,暂时先略了。
二、编写驱动程序
在这,我使用C语言编写驱动程序,这需要非常注意一点,即处理器所用的C编译器使用“大端格式”还是“小端格式”,这可以在相应处理器的C编译器说明上找到。一般比较常见的是小端格式。而对于8位处理器来说,在编写驱动程序时,可以不考虑,但是在编写网络协议的时候,一定好考虑,因为网络协议的格式是大端格式,而大部分编译器或者我们习惯的是小端格式,这一点需要注意。
在DM9000中,只有两个可以直接被处理器访问的寄存器,这里命名为CMD端口和DATA端口。事实上,DM9000中有许多控制和状态寄存器(这些寄存器在上一篇文章中有详细的使用说明),但它们都不能直接被处理器访问,访问这些控制、状态寄存器的方法是:
(1)、将寄存器的地址写到CMD端口;
(2)、从DATA端口读写寄存器中的数据;
其实,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)
{
}
//从DM9000寄存器读数据
unsigned int dm9000_reg_read(unsigned char reg)
{
}
只得注意的是前面的两个宏定义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。下面将一直用到。
初始化DM9000网卡芯片的过程,实质上就是填写、设置DM9000的控制寄存器的过程,这里以程序为例进行说明。其中寄存器的名称宏定义在DM9000.H中已定义好。
注:一下函数中unsigned char为一个字节unsigned int为两个字节
//DM9000初始化
void DM9000_init(void)
{
}
这样就对DM9000初始化完成了,怎么样,挺简单的吧。
同样,以程序为例,通过注释说明。
//发送数据包
//参数:datas为要发送的数据缓冲区(以字节为单位),length为要发送的数据长度(两个字节)。
void sendpacket(unsigned char *datas, unsigned int length)
{
}
以上是发送数据包,过程很简单。而接收数据包确需要些说明了。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)
{
}
注意:上面的函数用到了一些宏定义,已经在头文件中定义过,这里说明一下:其中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;
uint16 protocol;
unsigned char hwlen;
unsigned char protolen;
uint16 opcode;
unsigned char smac[6];
unsigned char sipaddr[4];
unsigned char dmac[6];
unsigned char dipaddr[4];
};
struct ip_hdr //以太网头部+IP首部结构
{
struct eth_hdr ethhdr;
unsigned char vhl,
unsigned char ttl,
uint16 ipchksum;
unsigned char srcipaddr[4],
};