查看: 1151|回复: 0
打印 上一主题 下一主题

接收和发送缓冲区分配

[复制链接]
跳转到指定楼层
沙发
发表于 2015-10-29 08:57:56 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
接收和发送缓冲区分配
       以太网数据的接收和发送离不开驱动芯片内部的RAM,也可称之为硬件缓冲区。ENC28J60包括8K 的硬件缓冲区,该硬件缓冲区一部分被接收缓冲区使用,另一部分为发送缓冲区使用。操作ENC28J60的最终目的为操作该硬件缓冲区。执行以太网发送命令时,向发送缓冲区中填充数据,并触发相关寄存器发送以太网数据;执行以太网接收命令时,通过查询相关寄存器或者外部中断的方式获得以太网数据输入事件,接着从接收缓冲区中读取相关数据。
(1)   把缓冲区划分为两个部分。把8K的硬件缓冲区划分为两个部分至少需要四个参数,接收缓冲区需要一个起始地址和一个结束地址加以描述,发送缓冲区也需要一个起始地址和一个结束地址加以描述。最理想的方式,两个缓冲区完全占据了8K的硬件缓冲区,完美地利用这一空间。由于ENC28J60的寄存器长度为8位,而硬件缓冲区的大小为8K,所以前面提到的4个地址需要8个寄存器才可以完全描述,需要把单个地址分为高8位和低8位。在AVRNET项目中,接收缓冲区较大,而发送缓冲区较小。在以太网协议中,最大的报文长度为1518字节,而最小报文长度为60字节。发送缓冲区等于或略大于1518字节,剩余的部分全部分配给接收缓冲区。接收缓冲区较大也是考虑到AVR的处理能力有限,若某个时间点收到多个以太网报文,可以先把报文闲置与硬件缓冲区中,待空闲时再从缓冲区中取出。
/* 接收缓冲区起始地址 */
#define RXSTART_INIT                0x00
/* 接收缓冲区停止地址 */
#define RXSTOP_INIT                 (0x1FFF - 0x0600 - 1)
/* 发送缓冲区起始地址 发送缓冲区大小约1500字节*/
#define TXSTART_INIT                (0x1FFF - 0x0600)
/* 发送缓冲区停止地址 */
#define TXSTOP_INIT                 0x1FFF


图硬件缓冲区结构
(2)   对于发送缓冲区而言,需要指定发送缓冲区写指针,使用写缓冲区命令操作该部分缓冲区,写指针的地址会不断增长,若遇到结束地址会重新返回起始地址。对于接收缓冲区而言就稍微复杂一点,每次读取之前必须明确该次操作时的读指针位置,根据前文的代码,缓冲区读指针的起始地址为0,在第一次读操作发生之后需要立即设置下次读操作的读指针地址。ENC28J60读缓冲区时,读取的数据并不全是以太网的数据,在以太网数据之前还有下一个数据包的地址指针占两个字节,接收状态向量占4个字节,接着才是以太网数据包,该数据包包括目标MAC地址,源MAC地址,数据包类型等等;最后为CRC校验和。在接收状态向量的起始2个字节为该以太网数据包的长度,该参数也是非常有用的参数。


图接收数据包结构
对于发送缓冲区而言,需要指定发送缓冲区写指针,使用写缓冲区命令操作该部分缓冲区,写指针的地址会不断增长,若遇到结束地址会重新返回起始地址。对于接收缓冲区而言就稍微复杂一点,每次读取之前必须明确该次操作时的读指针位置,根据前文的代码,缓冲区读指针的起始地址为0,在第一次读操作发生之后需要立即设置下次读操作的读指针地址。ENC28J60读缓冲区时,读取的数据并不全是以太网的数据,在以太网数据之前还有下一个数据包的地址指针占两个字节,接收状态向量占4个字节,接着才是以太网数据包,该数据包包括目标MAC地址,源MAC地址,数据包类型等等;最后为CRC校验和。在接收状态向量的起始2个字节为该以太网数据包的长度,该参数也是非常有用的参数。
3 寄存器操作实现
      ENC28j60的寄存器操作分为2+2+2部分,分别为写寄存器和读寄存器部分,读缓冲区和写缓冲区部分,写PHY寄存器和读PHY寄存器部分。
3.1 读写寄存器
       读或写寄存器的函数如下
<font size="3">unsigned char enc28j60Read(unsigned char address)
{
       /* 设定寄存器地址区域 */
       enc28j60SetBank(address);
       /* 读取寄存器值 发送读寄存器命令和地址 */
       return enc28j60ReadOp(ENC28J60_READ_CTRL_REG, address);
}
void enc28j60Write(unsigned char address, unsigned char data)
{
       /* 设定寄存器地址区域 */
       enc28j60SetBank(address);
       /* 写寄存器值 发送写寄存器命令和地址 */
       enc28j60WriteOp(ENC28J60_WRITE_CTRL_REG, address, data);
}</font>
复制代码
       读写寄存器的分为两步,第一步为选定寄存器的BANK编号,第二步为使用写命令或读命令,操作指定地址的寄存器。在ENC28J60中,由ECON1中的某两位保存BANK编号,ECON1是比较特殊的控制寄存器,在4个BANK中具有该寄存器且该寄存器的地址相同。Enc28j60Bank为全局变量,用于保存当前的BANK编号,如果两次操作控制寄存器在同一个BANK时,该变量保持不变,若两次操作的控制寄存器位于不同的BANK,那么BANK的值会变为新的BANK编号。
<font size="3">void enc28j60SetBank(unsigned char address)
{
       /* 计算本次寄存器地址在存取区域的位置 */
       if((address & BANK_MASK) != Enc28j60Bank)
       {
    /* 清除ECON1的BSEL1 BSEL0 详见数据手册15页 */
    enc28j60WriteOp(ENC28J60_BIT_FIELD_CLR, ECON1, (ECON1_BSEL1|ECON1_BSEL0));
    /* 请注意寄存器地址的宏定义,bit6 bit5代码寄存器存储区域位置 */
    enc28j60WriteOp(ENC28J60_BIT_FIELD_SET, ECON1, (address & BANK_MASK)>>5);
    /* 重新确定当前寄存器存储区域 */
    Enc28j60Bank = (address & BANK_MASK);
       }
}</font>
复制代码
<font size="3">unsigned char enc28j60ReadOp(unsigned char op, unsigned char address)
{
       unsigned char dat = 0;

       /* CS拉低 使能ENC28J60 */
       ENC28J60_CSL();
       /* 操作码和地址 */
       dat = op | (address & ADDR_MASK);
  /* 通过SPI写数据*/
       spi_sendbyte(dat);
  /* 通过SPI读出数据 */
       dat = spi_sendbyte(0xFF);

  /* 如果是MAC和MII寄存器,第一个读取的字节无效,该信息包含在地址的最高位 */
       if(address & 0x80)
       {
              /* 再次通过SPI读取数据 */
    dat = spi_sendbyte(0xFF);
       }

  /* CS拉高 禁止ENC28J60 */
       ENC28J60_CSH();

  /* 返回数据 */
       return dat;
}</font>
复制代码
读控制寄存器实际上就是严格遵守数据手册的操作要求,一次编写程序。在这里由于读MAC和MII寄存器时,第一个接收到的字节为无效字节,第二个字节才为有效字节。程序通过寄存器地址的最高位来判断是否为MAC或MII寄存器。写寄存器函数较为简单,第一次字节包括操作码和寄存器地址,第二个字节则为数据。在这两个函数中参数op为ENC28J60的指令,或称之为操作码,该指令占据了SPI第一个字节的前3位,参数address为寄存器地址,参数data为寄存器的具体值。
这两个函数和硬件发生某些关系,ENC28J60_CSL()和ENC28J60_CSH()为操作CS端口的操作宏,而spi_sendbyte()可通过SPI发送一个字节。修改这些函数即可在其他平台上使用ENC28J60。不过请特别注意,在使用其他开发板时由于SPI总线上可能挂载多个设备,单独使用ENC28J60时需要把其他设备的CS端口拉高,或安装一个上拉电阻。
<font size="3">unsigned char enc28j60ReadOp(unsigned char op, unsigned char address)
{
       unsigned char dat = 0;

       /* CS拉低 使能ENC28J60 */
       ENC28J60_CSL();
       /* 操作码和地址 */
       dat = op | (address & ADDR_MASK);
  /* 通过SPI写数据*/
       spi_sendbyte(dat);
  /* 通过SPI读出数据 */
       dat = spi_sendbyte(0xFF);

  /* 如果是MAC和MII寄存器,第一个读取的字节无效,该信息包含在地址的最高位 */
       if(address & 0x80)
       {
              /* 再次通过SPI读取数据 */
    dat = spi_sendbyte(0xFF);
       }

  /* CS拉高 禁止ENC28J60 */
       ENC28J60_CSH();

  /* 返回数据 */
       return dat;
}
void enc28j60WriteOp(unsigned char op, unsigned char address, unsigned char data)
{
       unsigned char dat = 0;
  /* 使能ENC28J60 */                                                   
       ENC28J60_CSL();        
  /* 通过SPI发送 操作码和寄存器地址 */                        
       dat = op | (address & ADDR_MASK);
  /* 通过SPI1发送数据 */
       spi_sendbyte(dat);
  /* 准备寄存器数值 */                             
       dat = data;
  /* 通过SPI发送数据 */
       spi_sendbyte(dat);
  /* 禁止ENC28J60 */                     
       ENC28J60_CSH();   
}</font>
复制代码转载

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 加入因仑

本版积分规则

快速回复 返回顶部 返回列表