嵌入式以太网第一部分——ENC28J60网卡驱动
目录:
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网卡驱动相关推荐
- 嵌入式以太网第二部分——ENC28J60网卡驱动
目录 1:概述 2:enc28j60初始化 1.概述 1.1:以太网是实现LAN的一种技术,它允许嵌入式系统连接到一个LAN中,并可通过使用网关经Internet连接到外部世界,以太网的主要目的是向L ...
- enc28j60 linux 驱动_linux enc28j60网卡驱动移植(硬件spi和模拟spi)
本来想移植DM9000网卡的驱动,无奈硬件出了点问题,通过杜邦线链接开发板和DM9000网卡模块,系统上电,还没加载网卡驱动就直接崩溃了,找不到原因...刚好手上有一个enc28j60的网卡模块,于是 ...
- Linux下编写ENC28J60网卡驱动,完善网络设备框架
一.框架模型 linux下设备驱动都有一套标准的结构,字符设备,块设备,网络设备都是自己的一套框架.编写驱动只需要把内核的框架搞清楚,然后照着结构填入参数,注册进内核,在应用层就可以按照标准的形式调用 ...
- GD32F4xx 以太网芯片(enc28j60)驱动移植
1.enc28j60 简介 ENC28J60 是带有行业标准串行外设接口(SPI)的独立以太网控制器. 主要特性: (1)SPI最高通信速率:10Mb/s.只支持SPI的模式0,0,且SPI端口要求S ...
- linux没有网卡驱动能pxe吗,PXE所需要的网卡驱动制作
对PXE 来说: vmlinuz:就是引导内核文件 initr.img:就是驱动文件 如果遇到机器网卡不被PXE支持怎么办? 解决思路 如果熟悉 Linux 的启动过程和驱动程序,那么要解决本文的问题 ...
- 基于嵌入式Linux的千兆以太网卡驱动程序设计及测试
基于嵌入式Linux的千兆以太网卡驱动程序设计及测试 一. 引言 千兆以太网是一种具有高带宽和高响应的新网络技术,相关协议遵循IEEE 802.3规范标准.采用和10M以太网相似的帧格式.网络协议和布 ...
- linux kernel有线网卡驱动enc28j60分析 一
1.为了更好低学习linux的网络驱动架构,本文选择分析linux kernel下的有线网卡驱动enc28j60来学习网络驱动架构. enc28j60是一个10/100Mb的有线网卡,适用于嵌入式设备 ...
- 嵌入式Linux——网卡驱动(1):网卡驱动框架介绍
声明:文本是看完韦东山老师的视频和看了一些文章后,所写的总结.我会尽力将自己所了解的知识写出来,但由于自己感觉并没有学的很好,所以文中可能有错的地方敬请指出,谢谢. 在介绍本文之前,我想先对前面的知识 ...
- enc28j60 linux 驱动_enc28j60网卡驱动模块添加进Linux内核,Kconfig,Makefile配置过程...
这里是要把 http://www.linuxidc.com/Linux/2017-02/140819.htm 中的enc28j60网卡驱动模块,添加到2.6.22.6内核中,这个模块代码不需要任何修改 ...
最新文章
- 如何重装计算机操作系统,自己如何重装笔记本电脑操作系统呢?
- 三星 arm9 linux,基于arm9内核三星s3c2410平台下linux四键按键驱动程序
- 为 Jupyter Notebook指定虚拟环境的 Python 解释器
- java.lang.RuntimeException: Canvas: trying to draw too large(203212800bytes) bitmap.
- error LNK2001:错误解决过程
- 为什么OpenCV3在Python中导入名称是cv2
- 网站使用CloudFlare
- CMOS图像传感器——闪烁(flicker)现象
- spring 4.0 JUnit简单的Dao,Service测试
- IT人的素质 设计杂谈
- VS2008中C++打开Excel(MFC)
- InstallShield中通过修改注册表关闭Vista/Windows 7的UAC
- 微星主板不用DrMOS了?
- 使用vba宏/python代码更新word目录——只更新页码
- 使用this.$refs.XXX修改某个元素样式并添加点击事件
- RTX 2013安装破解文档
- 美团点评 2019校园招聘 后台开发方向
- php是什么化学物,dmap是什么化学物质以及它的性质用途
- 阿里大牛分享程序员5年的职业生涯指南
- php中files和FILRS,PHP_php利用header函数实现文件下载时直接提示保存,复制代码 代码如下: ?php $fil - phpStudy...
热门文章
- Android 应用商店的思考
- Flume监控软件——Ganglia安装与部署
- MeeGo的中国救亡之路:Jolla与迪信通牵手合作
- 2016百度之星 - 测试赛(热身,陈题)1001,1002,1003,1004
- 百度之星(2015)
- Python抓取360手机市场APP信息并做简单分析
- Atlassian In Action-Jira之二次开发(五)
- 各位大佬,canoe9.0安装报错显示无法正确检索许可证信息,怎么解决!在线等挺急的!
- svn: 'path' has no ancestry information
- 2020.1.14课程摘要(逻辑教育-王劲胜)