目录:

1:概述

2:enc28j60寄存器描述和SPI指令

3:读写寄存器函数

4:缓冲器描述和读写缓冲器函数

5:发送和接受数据包函数


 1、概述:

1.1:以太网是实现LAN的一种技术,它允许嵌入式系统连接到一个LAN中,并可通过使用网关经Internet连接到外部世界,以太网的主要目的是向LAN发送和从LAN接收以太网帧,在TCP/IP协议族中,不包括以太网协议,可以理解它为TCP/IP中的网络接口层(物理层和数据链路层),为TCP/IP中的上层协议(比如:IP协议和ARP协议)提供服务;

1.2:ENC28J60是带SPI接口的以太网控制器,支持IEEE802.3协议,当然,也兼容Ethernet第二版的协议,此协议帧结构简单,被大量使用;

1.3:以太网帧结构:目的mac地址+源mac地址+协议类型+数据;

1.4:本篇编写ENC28J60驱动,实现以太网控制,将我们的嵌入式设备连接到LAN中,并使用ping命令进行测试;

1.5:enc28j60驱动实现需完成三步:(1)读写缓冲区函数实现;(2)发送和接受缓冲器实现;(3)enc28j60初始化,本篇实现先两步;

1.6:参考项目:AVRNET项目

1.7:开发板:STM32F103ZERT 奋斗开发板V5 软件环境:KEIL MDK5


2、Enc28j60寄存器描述和SPI指令

2.1:寄存器描述:enc28j60寄存器主要分2类,控制寄存器和PHY寄存器;

控制寄存器:存储空间分为四个存储区(bank),可使用ECON1中的BSEL1:BSEL0进行选择,可以使用2个bit进行指定。每个bank都是32字节长,可以使用5个bit进行寻址。控制寄存器常被分为三类,ETH、MAC、MII三组寄存器,MAC和MII寄存器的读指令协议和ETH寄存器的有区别,可以用1个bit来标识,所以可以用一个字节来描述寄存器的地址、分组、类别,定义是:bit0:bit4表示寄存器的地址,bit5:bit6表示寄存器分组,bit7最高位表示寄存器类别,所有寄存器在enc28j60.h中定义,如下:

// Bank 2 registers
#define MACON1           (0x00|0x40|0x80)
#define MACON2           (0x01|0x40|0x80)
#define MACON3           (0x02|0x40|0x80)
#define MACON4           (0x03|0x40|0x80)
#define MABBIPG          (0x04|0x40|0x80)
#define MAIPGL           (0x06|0x40|0x80)
#define MAIPGH           (0x07|0x40|0x80)
#define MACLCON1         (0x08|0x40|0x80)
#define MACLCON2         (0x09|0x40|0x80)
#define MAMXFLL          (0x0A|0x40|0x80)
#define MAMXFLH          (0x0B|0x40|0x80)
#define MAPHSUP          (0x0D|0x40|0x80)
#define MICON            (0x11|0x40|0x80)
#define MICMD            (0x12|0x40|0x80)
#define MIREGADR         (0x14|0x40|0x80)
#define MIWRL            (0x16|0x40|0x80)
#define MIWRH            (0x17|0x40|0x80)
#define MIRDL            (0x18|0x40|0x80)
#define MIRDH            (0x19|0x40|0x80)

PHY寄存器:PHY寄存器不能通过SPI指令直接去读写,需通过控制MII寄存器去实现读写;

2.2:SPI指令

enc28j60是SPI接口的以太网控制器,所有的配置和读写都需要通过SPI指令去执行,指令在enc28j60.h中定义,如下:

/*SPI指令集*/
typedef enum
{ENC28J60_READ_CTRL_REG  = 0X00,       //读控制寄存器操作码ENC28J60_WRITE_CTRL_REG = 0x40,       //写控制寄存器操作码 ENC28J60_BIT_FIELD_SET  = 0x80,       //寄存器位域置1操作码,只用于ETH控制寄存器ENC28J60_BIT_FIELD_CLR  = 0xA0,       //寄存器位域清0操作码,只用于ETH控制寄存器ENC28J60_READ_BUF_MEM   = 0x3A,       //读缓冲区操作码ENC28J60_WRITE_BUF_MEM  = 0x7A,       //写缓冲区操作码ENC28J60_SOFT_RESET     = 0xFF,       //软件复位操作码
}OPERATION_CODE;


3、读写寄存器函数

3.1:控制寄存器读写函数

可以调用 enc28j60Write_ctl_register和enc28j60Read_ctrl_register对控制寄存器进行读写,同时,ETH类的寄存器支持位操作,大大方便了寄存器的操作,可以使用enc28j60Write_instruction和enc28j60Read_instruction完成ETH寄存器的位操作;

/**************************
*func:SPI写指令函数
*parameter:instruction:spi操作码;address:寄存器地址;data:需写入的数据
*return:void
***************************/
void enc28j60Write_instruction(OPERATION_CODE instruction, unsigned char address, char data)
{unsigned char dat = 0;//使能片选信号CS_LOW();      //得到包括操作码和寄存器地址的一个字节dat = instruction | (address & ADDR_MASK);//发送操作码和寄存器地址SPI_Send_Byte(dat);//发送数据SPI_Send_Byte(data);//禁止片选信号CS_HIGH();
}/*************************
*func:SPI读指令函数
*parameter:instruction:spi操作码;address:寄存器地址;
*return:读取到的寄存器值
**************************/
unsigned char enc28j60Read_instruction(OPERATION_CODE instruction, unsigned char address)
{unsigned char dat = 0;//使能片选信号CS_LOW();//得到包括操作码和寄存器地址的一个字节dat = instruction | (address & ADDR_MASK);//发送操作码和寄存器地址SPI_Send_Byte(dat);//读取寄存器的值dat = SPI_Receive_Byte();//如果寄存器是MAC/MII寄存器,第一个读取的字节无效,再次读取if(address & 0x80){dat = SPI_Receive_Byte();}//禁止片选信号CS_HIGH();        /*返回寄存器的值*/return dat;
}/*************************
*func:选择bank编号
*parameter:address:寄存器地址
*return:void
*************************/
void enc28j60SetBank(unsigned char address)
{// 判断本次操作的寄存器地址所在的bank和当前Enc28j60Bank是否一致,如果一致,不用进行本次操作if((address & BANK_MASK) != Enc28j60Bank){//使用位域置1操作码,设置ECON1寄存器的BSEL0和BSEL1进行存储区的选择enc28j60Write_instruction(ENC28J60_BIT_FIELD_CLR, ECON1, (ECON1_BSEL1|ECON1_BSEL0));enc28j60Write_instruction(ENC28J60_BIT_FIELD_SET, ECON1, (address & BANK_MASK)>>5);Enc28j60Bank = (address & BANK_MASK);}
}/*写控制寄存器函数*/
void enc28j60Write_ctl_register(unsigned char address, char data)
{//依据address确定bank分区enc28j60SetBank(address);//写寄存器enc28j60Write_instruction(ENC28J60_WRITE_CTRL_REG, address, data);
}/*读控制寄存器函数*/
unsigned char enc28j60Read_ctrl_register(unsigned char address)
{//依据address确定bank分区enc28j60SetBank(address);//读寄存器return enc28j60Read_instruction(ENC28J60_READ_CTRL_REG, address);
}

3.2:PHY寄存器读写函数:设置PHY寄存器是通过设置MII寄存器来实现;

/**********************
*func:写PHY寄存器
*parameter: address:PHY寄存器地址 data:需要写入的数据
*return:void
***********************/
void enc28j60Write_phy_register(unsigned char address, unsigned int data)
{/*将要写入PHY寄存器的地址写入MIREGADR寄存器*/enc28j60Write_ctl_register(MIREGADR, address);  /*将数据的第八位写入MIWRL寄存器*/ enc28j60Write_ctl_register(MIWRL, data);  /*将数据的高八位写入MIWRH寄存器*/ enc28j60Write_ctl_register(MIWRH, data>>8); /*等待MISTAT.BUDY位置1*/while(enc28j60Read_ctrl_register(MISTAT) & MISTAT_BUSY);
}

4、缓冲器描述和读写缓冲器函数

 3.1:缓冲器描述

Enc28j60缓冲器大小为8K,分成单独的发送和接受空间。接受缓冲器:硬件管理的循环FIFO缓冲器,寄存器对ERXST和ERXND作为指针,定义缓冲器的容量和其在存储器中的位置;ERXWRPT寄存器定义硬件向FIFO中的哪个位置写入其接收到的字节,硬件会自动自动跟新这个地址;ERXRDPT寄存器定义了禁止接受数据的位置,即如果接受的数据没有被主控制器接受的话,如果在有新的数据到达ERXRDPT寄存器定义的地址的时候,新来的数据会被丢弃,所以,每次读数据包后,需要给ERXRDPT赋初值,以释放接受缓冲空间,主要起到保护数据的作用,同时ERXRDPT和ERXWRPT两个寄存器可以得到缓冲区的剩余空间;发送缓冲区:8KB内没有被设定为接受FIFO缓冲区的空间均可作为发送缓冲区,ETXST和ETXND指定缓冲区的大小和位置。ERDPT和EWRPT是独立的读写指针,发送和接受数据包的时候会设置;

缓冲区大小的配置方案:总共8192个字节大小的缓冲区,用1500字节的空间用来作为发送缓冲区,剩下的大部分的空间作为接受缓冲区,局限于嵌入式设备的处理速度,这样的配置可以提高数据的接受能力;

3.2:读写缓冲器函数

/*******************************2:以太网缓冲区操作函数****************************/
/**************************
*func:写缓冲区
*parameter: cnts:写入缓冲区的字节数 buff:存放需要写入的缓冲区的数据
*return:void
***************************/
void enc28j60WriteBuff(unsigned char cnts,unsigned char *buff)
{/*使能片选信号*/CS_LOW();/*发送写缓冲区指令0x7A*/SPI_Send_Byte(ENC28J60_WRITE_BUF_MEM);/*循环发送数据*/for(;cnts>0;cnts--){SPI_Send_Byte(*buff++);}/*禁止片选信号*/CS_HIGH();
}/****************************
*func:读缓冲区
*parameter: cnts:读取缓冲区的字节数 buff:存放从缓冲区读出来的数据
*return:void
*****************************/
void enc28j60ReadBuff(unsigned char cnts,unsigned char *buff)
{/*使能片选信号*/CS_LOW();/*发送读缓冲区指令0x3A*/SPI_Send_Byte(ENC28J60_READ_BUF_MEM);for(;cnts>0;cnts--){*buff++ = SPI_Receive_Byte();}/*禁止片选信号*/CS_HIGH();
}

4、发送和接受数据包函数

4.1:发送数据包
1> 每次发送数据包,都是从发送缓冲区的起始地址开始发送,所以要设置EWRPT写指针的值为发送缓冲区的起始地址;
2> 根据所需发送数据包的长度,设置ETXND发送缓冲器结束地址,数据包长度设置不能超过你的发送缓冲区的大小;
3> 发送一个包控制字节,然后发送数据包,最后使能发送,开始发送数据包给其他设备的网络接口;

/**************************************4:数据包接受和发送函数*******************************/
/***************************
*func:发送一个数据包至网络
*parameter: buffer:需要发送的数据包指针 length:数据包长度
*return:void
***************************/
void enc28j60_packet_send ( unsigned char *buffer, unsigned int length )
{/*设置发送缓冲区起始地址*/enc28j60Write_ctl_register(EWRPTL, (unsigned char)TXSTART_INIT);enc28j60Write_ctl_register(EWRPTH, (unsigned char)TXSTART_INIT>>8 );/*根据发送包的长度,设置发送缓冲区的结束地址*/enc28j60Write_ctl_register(ETXNDL, (unsigned char)(TXSTART_INIT+length));enc28j60Write_ctl_register(ETXNDH, (unsigned char)((TXSTART_INIT+length)>>8));/*发送一个包控制字节*/enc28j60Write_instruction(ENC28J60_WRITE_BUF_MEM, 0, 0x00);/*发送数据包*/enc28j60WriteBuff(length,buffer);/*发送逻辑正在尝试发送数据包*/enc28j60Write_instruction(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_TXRTS);/*如果发送错误,再次启动发送逻辑*/if((enc28j60Read_ctrl_register(EIR) & EIR_TXERIF) ){enc28j60Write_instruction(ENC28J60_BIT_FIELD_CLR, ECON1, ECON1_TXRTS);}
}

4.2:接受数据包

首先,接收到的数据包的格式,如下图:


1> 读取寄存器获得接收缓冲区中共有多少个数据包,有数据包,继续执行;

2> 设置从哪个地址去读取这个数据包,代码中定义了一个变量NextPacketPtr,16位的整形变量,存取的是将要读取数据包的地址,这个变量在初始化函数中被赋值为接受缓冲区的起始地址,在每次接受数据包之前,先将数据包中的下一帧数据包的地址信息取出来给它赋值,让它重新指向下一帧数据包,读写格式为小端模式;

3> 从数据包中的状态向量获得数据包长度(目的地址+源地址+协议类型+数据+填充+CRC),读写格式为下端模式;

4> 读接收缓冲区数据,读之前会做一些数据包长度和接受是否正确的判断;

5> 释放接受缓冲空间,ERXRDPT寄存器赋初值,并且将NextPacketPtr赋给它,让它也指向下一帧数据;

6> 最后我们读走了一包数据,数据包个数减1;

/*************************
*func:从网络接收一个数据包
*parameter: buffer:接收数据包缓冲指针 max_length:最大字节数
*return:返回读取的字节数
**************************/
char num = 0;
unsigned int enc28j60PacketReceive ( unsigned char *buffer, unsigned int max_length )
{unsigned int rxstat = 0;unsigned int length = 0;/*是否收到以太网数据包*/num = enc28j60Read_ctrl_register(EPKTCNT);if( enc28j60Read_ctrl_register(EPKTCNT) == 0 ){return 0;}/*设置读缓冲指针地址*/enc28j60Write_ctl_register(ERDPTL, (NextPacketPtr));  enc28j60Write_ctl_register(ERDPTH, (NextPacketPtr)>>8);/*读下一个包的起始地址,从缓冲区中读取16位的数据*/NextPacketPtr  = enc28j60Read_register_instruction(ENC28J60_READ_BUF_MEM, 0);  NextPacketPtr |= enc28j60Read_register_instruction(ENC28J60_READ_BUF_MEM, 0)<<8; /*从接收状态向量得到包的长,从缓冲区中读取16位的数据*/length  = enc28j60Read_register_instruction(ENC28J60_READ_BUF_MEM, 0);  length |= enc28j60Read_register_instruction(ENC28J60_READ_BUF_MEM, 0)<<8;  /*去除CRC的校验字节数*/  length -= 4;       /*读取接收状态,从缓冲区中读取16位的数据*/rxstat  = enc28j60Read_register_instruction(ENC28J60_READ_BUF_MEM, 0);  rxstat |= enc28j60Read_register_instruction(ENC28J60_READ_BUF_MEM, 0) << 8;  /*读取字节数限制*/if (length > max_length-1)  {  length = max_length-1;  }  /*检查接受是否成功*/if ((rxstat & 0x80)==0)  {  length = 0;  }  else  {  /*接收数据包*/enc28j60ReadBuff(length, buffer);  }  /*释放接受缓冲空间*/enc28j60Write_ctl_register(ERXRDPTL, (NextPacketPtr));  enc28j60Write_ctl_register(ERXRDPTH, (NextPacketPtr)>>8);  /*数据包递减位置1,数据包个数减1*/enc28j60Write_instruction(ENC28J60_BIT_FIELD_SET, ECON2, ECON2_PKTDEC);  /*返回读取的字节数*/return(length);
}


嵌入式以太网第一部分——ENC28J60网卡驱动相关推荐

  1. 嵌入式以太网第二部分——ENC28J60网卡驱动

    目录 1:概述 2:enc28j60初始化 1.概述 1.1:以太网是实现LAN的一种技术,它允许嵌入式系统连接到一个LAN中,并可通过使用网关经Internet连接到外部世界,以太网的主要目的是向L ...

  2. enc28j60 linux 驱动_linux enc28j60网卡驱动移植(硬件spi和模拟spi)

    本来想移植DM9000网卡的驱动,无奈硬件出了点问题,通过杜邦线链接开发板和DM9000网卡模块,系统上电,还没加载网卡驱动就直接崩溃了,找不到原因...刚好手上有一个enc28j60的网卡模块,于是 ...

  3. Linux下编写ENC28J60网卡驱动,完善网络设备框架

    一.框架模型 linux下设备驱动都有一套标准的结构,字符设备,块设备,网络设备都是自己的一套框架.编写驱动只需要把内核的框架搞清楚,然后照着结构填入参数,注册进内核,在应用层就可以按照标准的形式调用 ...

  4. GD32F4xx 以太网芯片(enc28j60)驱动移植

    1.enc28j60 简介 ENC28J60 是带有行业标准串行外设接口(SPI)的独立以太网控制器. 主要特性: (1)SPI最高通信速率:10Mb/s.只支持SPI的模式0,0,且SPI端口要求S ...

  5. linux没有网卡驱动能pxe吗,PXE所需要的网卡驱动制作

    对PXE 来说: vmlinuz:就是引导内核文件 initr.img:就是驱动文件 如果遇到机器网卡不被PXE支持怎么办? 解决思路 如果熟悉 Linux 的启动过程和驱动程序,那么要解决本文的问题 ...

  6. 基于嵌入式Linux的千兆以太网卡驱动程序设计及测试

    基于嵌入式Linux的千兆以太网卡驱动程序设计及测试 一. 引言 千兆以太网是一种具有高带宽和高响应的新网络技术,相关协议遵循IEEE 802.3规范标准.采用和10M以太网相似的帧格式.网络协议和布 ...

  7. linux kernel有线网卡驱动enc28j60分析 一

    1.为了更好低学习linux的网络驱动架构,本文选择分析linux kernel下的有线网卡驱动enc28j60来学习网络驱动架构. enc28j60是一个10/100Mb的有线网卡,适用于嵌入式设备 ...

  8. 嵌入式Linux——网卡驱动(1):网卡驱动框架介绍

    声明:文本是看完韦东山老师的视频和看了一些文章后,所写的总结.我会尽力将自己所了解的知识写出来,但由于自己感觉并没有学的很好,所以文中可能有错的地方敬请指出,谢谢. 在介绍本文之前,我想先对前面的知识 ...

  9. enc28j60 linux 驱动_enc28j60网卡驱动模块添加进Linux内核,Kconfig,Makefile配置过程...

    这里是要把 http://www.linuxidc.com/Linux/2017-02/140819.htm 中的enc28j60网卡驱动模块,添加到2.6.22.6内核中,这个模块代码不需要任何修改 ...

最新文章

  1. 如何重装计算机操作系统,自己如何重装笔记本电脑操作系统呢?
  2. 三星 arm9 linux,基于arm9内核三星s3c2410平台下linux四键按键驱动程序
  3. 为 Jupyter Notebook指定虚拟环境的 Python 解释器
  4. java.lang.RuntimeException: Canvas: trying to draw too large(203212800bytes) bitmap.
  5. error LNK2001:错误解决过程
  6. 为什么OpenCV3在Python中导入名称是cv2
  7. 网站使用CloudFlare
  8. CMOS图像传感器——闪烁(flicker)现象
  9. spring 4.0 JUnit简单的Dao,Service测试
  10. IT人的素质 设计杂谈
  11. VS2008中C++打开Excel(MFC)
  12. InstallShield中通过修改注册表关闭Vista/Windows 7的UAC
  13. 微星主板不用DrMOS了?
  14. 使用vba宏/python代码更新word目录——只更新页码
  15. 使用this.$refs.XXX修改某个元素样式并添加点击事件
  16. RTX 2013安装破解文档
  17. 美团点评 2019校园招聘 后台开发方向
  18. php是什么化学物,dmap是什么化学物质以及它的性质用途
  19. 阿里大牛分享程序员5年的职业生涯指南
  20. php中files和FILRS,PHP_php利用header函数实现文件下载时直接提示保存,复制代码 代码如下: ?php $fil - phpStudy...

热门文章

  1. Android 应用商店的思考
  2. Flume监控软件——Ganglia安装与部署
  3. MeeGo的中国救亡之路:Jolla与迪信通牵手合作
  4. 2016百度之星 - 测试赛(热身,陈题)1001,1002,1003,1004
  5. 百度之星(2015)
  6. Python抓取360手机市场APP信息并做简单分析
  7. Atlassian In Action-Jira之二次开发(五)
  8. 各位大佬,canoe9.0安装报错显示无法正确检索许可证信息,怎么解决!在线等挺急的!
  9. svn: 'path' has no ancestry information
  10. 2020.1.14课程摘要(逻辑教育-王劲胜)