上图中虚线框住的部分为Remote DMA,也就是单片机对网卡ram进行读写的总线,对8019来说就是ISA总线.没有框住的部分(左边的部分),就是Local DMA,网卡控制器对网卡ram进行读写的总线.
其中的地址总线没有画出来,只画了数据总线.实际在ram的内部还有一些总线仲裁的逻辑,这里也没有画出来.所谓总线仲裁的逻辑就是为了实现两套总线都能进行对ram的读写,而不互相冲突.
网卡控制器读写网卡ram(Local DMA)的优先级比单片机读写网卡ram的优先级要高.优先级要高的意思是:
1.当两者都要请求控制总线时.Local DMA优先获得控制权.
2.高优先级的Local DMA可以中断Remote DMA,而Remote DMA不能中断Local DMA
3.在Remote DMA,也就是单片机对网卡ram读写的过程进行中可以被Local DMA中断,Local DMA中断Remote DMA,然后进行Local DMA的数据传输,Local DMA传输完毕之后继续刚才被中断的Remote DMA,以完成Remote DMA的传输.
上图中的Remote就是Remote DMA的传输,Local burst就是Local DMA的传输.图的左边是一个Remote DMA被Local DMA中断的示意图.Remote DMA是等到Local Burst完成之后才结束该次的传输.被打断多久的时间取决于FT1,FT0(是DCR配置寄存器的位)
单片机的总线要比网卡的DMA总线慢很多.网卡的DMA总线大概在10Mhz,而单片机的总线大概1Mhz.所以在Remote DMA的过程中不需要特别的等待时序.
但是如果使用较快的CPU,比如DSP,ARM等,可能要考虑时序问题.也就是说IOCHRDY(ISA总线的一个信号,RTL8019AS),或者TRDY(PCI总线的信号 RTL8029AS) ,是需要考虑连到CPU上,或者做一定的处理.
那么对于不快也不慢的AVR单片机来说,要不要接IOCHRDY?估计是要的.因为我不提供avr的上网方案,所以也没有做太多的研究.对于77E58来说可以不接IOCHRDY,因为77E58可以内部设置外部ram的存取的速度.
DMA有8位和16位两种.网卡支持这两种DMA,一般我们使用8位的DMA,8位的DMA的接线比较少,同时适合单片机处理.电脑里一般使用16位DMA.
有人问到在电脑里如何使用8位的DMA的问题.有些卡自动检测总线上的IOCS16B来选择总线,比如RTL8019as,我试过RTL8019as使用8位DMA在电脑里是失败的.如果真的要在电脑里使用8位的DMA,要把该引脚IOCS16B断开(可以割断),而不连到ISA总线上,这样这些网卡会自动的进行8位的操作(地址译码为10位).
对于使用DM9008芯片的网卡,16位DMA传输是由SLOT引脚决定的。我试过把DM9008的IOCS16B
引脚与ISA槽断开(通过贴“透明胶”的方法),配套的设置程序检查时死机。如果想DM9008
使用8位DMA操作,应该把SLOT引脚割断,而不是IOCS16B。
在DSP里可以使用16位的DMA.
因为不同的单片机(CPU),代码可能不同,我在下面将用几种表示法来论述:
假设用到的I/O地址为0xC000,读出到temp变量或写temp到寄存器,temp为8位变量:
通用的 | RTL8019 C程序(mcs51) | RTL8019 汇编的程序(mcs51) |
temp=read_register(address) 读寄存器函数 | temp=reg00; | MOV DPTR,#address MOVX A,@DPTR MOV temp,A |
write_register(address,temp) 写寄存器函数 | reg00=temp; | MOV temp,A MOV DPTR,#address MOVX @DPTR,A |
注: #define reg00 XBYTE[0xc000] | 注:address equ 0C000H |
通用的表达式:
void write_register(unsigned char address,unsigned char value)或
void write_register(unsigned int address,unsigned char value)
unsigned char read_register(unsigned char address)或
unsigned char read_register(unsigned int address)
上面的表达式中,根据你的地址或寻址方法而选择unsigned int address或unsigned char address.
上表给出了用c语言或汇编语言或其他语言的表达的等价的程序.
下面给出用51单片机的c语言程序:
#define reg00 XBYTE[0xc000] //reg00- 10为isa网卡接口的寄存器地址240-250;
#define reg01 XBYTE[0xc100]
#define reg02 XBYTE[0xc200]
#define reg03 XBYTE[0xc300]
#define reg04 XBYTE[0xc400]
#define reg05 XBYTE[0xc500]
#define reg06 XBYTE[0xc600]
#define reg07 XBYTE[0xc700]
#define reg08 XBYTE[0xc800]
#define reg09 XBYTE[0xc900]
#define reg0a XBYTE[0xca00]
#define reg0b XBYTE[0xcb00]
#define reg0c XBYTE[0xcc00]
#define reg0d XBYTE[0xcd00]
#define reg0e XBYTE[0xce00]
#define reg0f XBYTE[0xcf00]
#define reg10 XBYTE[0xd000]
xdata unsigned char buffer[1536];//缓冲区,放在外部ram.
unsigned int count;//需要读或写的字节数
unsigned int i;
//DCR=0xc8;要配置DCR为8位的dma
void write_dma(unsigned int address,unsigned int count)//写网卡的ram
{//address为要写入到网卡里的ram的起始地址,count为要连续写入的字节数
page(0);
reg09=address>>8;//address high
reg08=address&0xff;//address low
reg0b=count>>8; //write count high
reg0a=count&0xff;//write count low
reg00=0x12 ;//dma write
for(i=0;i<count;i++)
{
reg10=buffer[i];
}
reg0b=0; // count high 中止DMA操作
reg0a=0;// count low
reg00=0x22;//abort/complete dma page 0
}
执行的结果是将buffer数组的内容被写入到网卡的起始ram地址address的一段ram里.
程序当中的最后3句:
reg0b=0; // count high
reg0a=0;// count low
reg00=0x22;//abort/complete dma page 0
可以不要,但推荐使用,以便发生错误的时候能够正确的退出DMA传输,3句是中止DMA的代码.在电脑里死机是很"正常"的,而在单片机里"死机"是大事,所以要考虑更多,一旦时序配合有问题,DMA就可能发生错误,提供发生错误时的恢复是有必要的.
void read_dma(unsigned int address,unsigned int count)//读网卡ram
{//address为网卡里的ram的起始地址,count为要连续读取的字节数
page(0);
reg09=address>>8;//address high
reg08=address&0xff;//address low
reg0b=count>>8; //write count high
reg0a=count&0xff;//write count low
reg00=0x0a ;//dma read
for(i=0;i<count;i++)
{
buffer[i]=reg10;
}
reg0b=0; // count high 中止DMA操作
reg0a=0;// count low
reg00=0x22;//abort/complete dma page 0
}
执行的结果是将网卡里的起始地址为address的共count个字节读入到buffer[i]里.
如果是使用模拟i/o,程序如下:
unsigned char read_register(unsigned char address)
{//读寄存器
unsigned char temp;
ea=0;//关闭中断是推荐的
p2=address;
p0=0xff; //这句不能省略
read=0;
temp=p0;
read=1;//read为单片机的读引脚
ea=1;
return(temp);
}
void write_register(unsigned char address,unsigned char value)
{
//写寄存器
ea=0;
p2=address;
p0=value;
write=0;//write为单片机的写引脚
write=1;
p0=0xff; //这句也是需要的.
ea=1;
}
那么我给出的read_dma,write_dma里的函数的i/o替换成模拟i/o的函数就可以了,举例如下:
将
#define reg10 0xd0
reg10=buffer[i] ;替换为write_register(reg10,buffer[i]);
buffer[i]=reg10 ;替换为buffer[i]=read_register(reg10);
其他reg0a,reg0b等也是做类似的替换.
建议不要使用模拟i/o,因为速度慢,同时也不可靠.模拟i/o中一般要关闭中断. 因为如果不关闭中断,会引起被中断,使读或写寄存器的时间变得很长.尽管DMA有优先级,但是如果read或write线一直处于低电平的时候,能否被Local DMA中断,我是持有疑问的. 网卡有8字节的FIFO,假设在单片机读写网卡ram的同时,网卡收到一个数据包,那么FIFO最多可以存储64个bit就必须启动Local DMA写网卡ram,网卡的速率为10,000,000bit/秒,64个bit的时间为6.4微秒. 单片机读写一个字节的网卡ram的时间最好不要超过6.4微秒.所以关闭中断以尽快执行i/o操作.虽然资料上没有说是否可以超过6.4微秒.有兴趣的可以做实验看是否一个很慢的read,write会不会影响网卡收数据包.
有时候我们不要相信资料.比如有些单片机书对MOVX指令的时序图就有误.RTL8019AS关于溢出时网卡不会覆盖以前接收的数据包的论述也是有误的. "尽信书不如无书"."实践是检验真理的唯一标准".就像资料说DM9008说支持8位DMA一样,我以前在电脑里试过,怎么也不行,用单片机的时候就可以.网卡并不是为单片机设计的,有些在电脑里不会出现的问题在单片机里可能碰到.
如果采用16位的dma(有些客户用dsp,或者在电脑里驱动,16位的dma要求有16条数据线的CPU,不能用在51单片机),那么也给出大家参考:
//DCR=0xC9;//配置成16位dma.
unsigned int buffer[768];//注意用int的数组
void write_dma(unsigned int address,unsigned int count)//写网卡的ram
{//address为要写入到网卡里的ram的起始地址,count为要连续写入的字节数
page(0);
reg09=address>>8;//address high
reg08=address&0xff;//address low
reg0b=count>>8; //write count high
reg0a=count&0xff;//write count low
reg00=0x12 ;//dma write
count=(count+1) /2;
for(i=0;i<count;i++)
{
reg10=buffer[i];
}
reg0b=0; // count high 中止DMA操作
reg0a=0;// count low
reg00=0x22;//abort/complete dma page 0
}
void read_dma(unsigned int address,unsigned int count)//读网卡ram
{//address为网卡里的ram的起始地址,count为要连续读取的字节数
page(0);
reg09=address>>8;//address high
reg08=address&0xff;//address low
reg0b=count>>8; //write count high
reg0a=count&0xff;//write count low
reg00=0x0a ;//dma read
count=(count+1) /2;
for(i=0;i<count;i++)
{
buffer[i]=reg10;
}
reg0b=0; // count high 中止DMA操作
reg0a=0;// count low
reg00=0x22;//abort/complete dma page 0
}
上面的count=(ount+1) /2,为什么要加一,是因为读取字节数为单数的时候就要.