以太网通讯是一种被广泛使用的数据通讯方式。在嵌入式应用中也经常使用,但协议栈的实现并不是一件容易的事。不过有些以太网控制器就带有协议栈,如w5500。在本篇中我们将讨论如何设计并实现w5500以太网控制器的驱动。
1、功能概述w5500是wiznet开发的单芯片全硬件tcp/ip协议栈,能够方便的实现网络连接应用。
1.1、硬件描述w5500作为一款全硬件tcp/ip嵌入式以太网控制器,为嵌入式系统提供了更加简易的互联网连接方案。w5500 集成了 tcp/ip 协议栈,10/100m 以太网数据链路层(mac)及物理层(phy),使得用户使用单芯片就能够在他们的应用中拓展网络连接。 其引脚排布及分装如下:
w5500全硬件 tcp/ip 协议栈支持 tcp,udp,ipv4,icmp,arp,igmp 以及 pppoe 协议。w5500 内嵌 32k 字节片上缓存以供以太网包处理。使用w5500,只需要一些简单的socket 编程就能实现以太网应用。用户可以同时使用8个硬件socket 独立通讯。
w5500提供了spi(外设串行接口)从而能够更加容易与外设mcu整合。而且,w5500的使用了新的高效spi协议支持80mhz速率,从而能够更好的实现高速网络通讯。为了减少系统能耗,w5500提供了网络唤醒模式(wol)及掉电模式供客户选择使用。
1.2、通讯接口w5500提供了spi(串行外部接口)作为外设主机接口,有scsn,sclk,mosi, miso共4路信号,且作为spi从机工作。w5500与mcu的连接方式如下图所示。根据scsn是否受主机控制,将其工作模式分为可变数据长度模式和固定数据长度模式。在可变数据长度模式中,w5500可以与其他spi设备共用spi接口。在固定数据长度模式,spi将指定给w5500,不能与其他spi设备共享。
spi协议定义了四种工作模式(模式 0,1,2,3)。每种模式的区别是根据sclk的极性及相位不同定义的。spi 的模式 0 和模式 3 唯一不同的就是在非活动状态下,sclk 信号的极性。spi的模式0和3,数据都是在sclk的上升沿锁存,在下降沿输出。w5500支持spi模式0及模式3。mosi和miso信号无论是接收或发送,均遵从从最高标志位(msb)到最低标志位(lsb)的传输序列。
1.3、内部寄存器w5500的spi数据帧包括了16位地址段的偏移地址,8位控制段和n字节数据段。如图下图所示:
地址段为w5500的寄存器或tx/rx缓存区指定了16位的偏移地址。 这16 位偏移地址的值来自从最高标志位到最低标志位的顺序传输。
控制段指定了地址段设定的偏移区域归属,读/写访问模式及spi工作模式。8位控制段可以通过修改区域选择位(bsb[4:0]),读/写访问模式位(rwb)以及spi工作模式位(om[1:0])来重新定义。区域选择位选择了归属于偏移地址的区域。
spi数据帧的数据段通过偏移地址自增(每传输1字节偏移地址加1),支持连续数据读/写。
w5500有1个通用寄存器,8个socket寄存器区,以及对应每个socket的收发缓存区。每个区域均通过spi数据帧的区域选择位(bsb[4:0])来选取。每一个socket的发送缓存区都在一个16kb的物理发送内存中,初始化分配为2kb。每一个socket的接收缓存区都在一个16kb 的物理接收内存中,初始化分配为 2kb。无论给每个socket 分配多大的收/发缓存,都必须在 16 位的偏移地址范围内(从 0x0000 到 0xffff)。
通用寄存器区配置了w5500的ip地址、mac地址等基本信息。该区域可以通过spi数据帧的区域选择位(bsb[4:0])选定。
w5500支持8个socket作为通讯信道。每一个socket通过socket n寄存器区控制(0≤n≤7)。socket n寄存器可以通过spi数据帧中的区域选择寄存器(bsb[4:0])来选定对应的寄存器n。
2、驱动设计与实现我们已经对w5500以太网控制器的引脚封装、接口方式、协议栈的操作流程以及基本操作库有了比较详细的了解。接下来我们将设计并实现w5500以太网控制器的驱动程序。
2.1、对象定义在使用一个对象之前我们需要获得一个对象。同样的我们想要w5500以太网控制器就需要先定义w5500以太网控制器的对象。
2.1.1、对象的抽象我们要得到w5500以太网控制器对象,需要先分析其基本特性。一般来说,一个对象至少包含两方面的特性:属性与操作。接下来我们就来从这两个方面思考一下w5500以太网控制器的对象。
先来考虑属性,作为属性肯定是用于标识或记录对象特征的东西。我们来考虑w5500以太网控制器对象属性。作为以太网控制器,w5500对象显然需要有网络配置参数作为它的属性,包括ip地址和mac地址等。所以我们将网络参数定义为对象的属性。在这里我们以结构体的方式来定义网络参数。
接着我们还需要考虑w5500以太网控制器对象的操作问题。其实我们对w5500的操作就是对spi接口的操作,这里我们因为使用了厂家的基础库,所以以函数注册回调函数的方式传递了操作函数。我们不需要再将对spi端口作为对象的操作,而是将他们以函数指针的方式在初始化函数中传入。那么我们对对象的操作就是读取和写入信息的操作,而具体的数据处理总是依赖于具体应用,所以我们将其作为对象的操作。
根据上述我们对w5500以太网控制器的分析,我们可以定义w5500以太网控制器的对象类型如下:
1 /* 定义w5500对象类型 */2 typedef struct w5500object {3 wiz_netinfo gwiznetinfo;4 uint16_t (*dataparsing)(uint8_t *rxbuffer,uint16_t rxsize,uint8_t *txbuffer);//接收消息解析及返回消息生成,返回值为返回消息的字节长度5 uint16_t (*requestdata)(uint8_t *rqbuffer); //得到请求命令,一般用于客户端发起访问6 }w5500objecttype;2.1.2、对象初始化我们知道,一个对象仅作声明是不能使用的,我们需要先对其进行初始化,所以这里我们来考虑w5500以太网控制器对象的初始化函数。一般来说,初始化函数需要处理几个方面的问题。一是检查输入参数是否合理;二是为对象的属性赋初值;三是对对象作必要的初始化配置。据此我们设计w5500以太网控制器对象的初始化函数如下:
1 /*w5500对象初始化*/ 2 void w5500initialization(w5500objecttype *w5500, 3 uint8_t mac[6], //本地mac地址 4 uint8_t ip[4], //本地ip地址 5 uint8_t sn[4], //子网掩码 6 uint8_t gw[4], //网关地址 7 uint8_t dns[4], //dns服务器地址 8 dhcp_mode dhcp, //dhcp类型 9 w5500cscristype cris_en,10 w5500cscristype cris_ex,11 w5500cscristype cs_sel,12 w5500cscristype cs_desel,13 w5500spireadbytetype spi_rb,14 w5500spiwritebytetype spi_wb,15 w5500dataparsingtype dataparse,16 w5500requestdatatype requst17 )18 {19 if((w5500==null)||(cris_en==null)||(cris_ex==null)||(cs_sel==null)||(cs_desel==null)||(spi_rb==null)||(spi_wb==null))20 {21 return;22 }23 24 for(int i=0;igwiznetinfo.mac[i]=mac[i];27 }28 29 for(int i=0;igwiznetinfo.ip[i]=ip[i];32 w5500->gwiznetinfo.sn[i]=sn[i];33 w5500->gwiznetinfo.gw[i]=gw[i];34 w5500->gwiznetinfo.dns[i]=dns[i];35 }36 37 w5500->gwiznetinfo.dhcp=dhcp;38 39 /*注册tcp通讯相关的回调函数*/40 registerfunction(cris_en,cris_ex,cs_sel,cs_desel,spi_rb,spi_wb);41 42 /*初始化芯片参数*/43 chipparametersconfiguration();44 45 /*初始化网络通讯参数*/46 networkparameterconfiguration(w5500->gwiznetinfo);47 48 if(dataparse!=null)49 {50 w5500->dataparsing=dataparse;51 }52 else53 {54 w5500->dataparsing=loopbackdatahandle;55 }56 57 if(requst!=null)58 {59 w5500->requestdata=requst;60 }61 else62 {63 w5500->requestdata=defaultrequest;64 }65 }2.2、对象操作我们已经完成了w5500以太网控制器对象类型的定义和对象初始化函数的设计。但我们的主要目标是获取对象的信息,接下来我们还要实现面向w5500以太网控制器的各类操作。
w5500以太网控制器有哪些操作呢?作为通讯接口,最主要的就是数据的发送于接收。这些函数我们当然可以实现它,不过在厂商提供的基础库中已经提供了这些函数,我们直接实用就好了,这里就不再列出了。
3、驱动的使用我们已经设计了w5500以太网控制器的驱动,接下来我们设计一个简单的应用验证这一驱动。
3.1、声明并初始化对象使用基于对象的操作我们需要先得到这个对象,所以我们先要使用前面定义的w5500以太网控制器对象类型声明一个w5500以太网控制器对象变量,具体操作格式如下:
w5500objecttype w5500;
声明了这个对象变量并不能立即使用,我们还需要使用驱动中定义的初始化函数对这个变量进行初始化。这个初始化函数所需要的输入参数如下:
w5500objecttype *w5500,
uint8_t mac[6], //本地mac地址
uint8_t ip[4], //本地ip地址
uint8_t sn[4], //子网掩码
uint8_t gw[4], //网关地址
uint8_t dns[4], //dns服务器地址
dhcp_mode dhcp, //dhcp类型
w5500cscristype cris_en,
w5500cscristype cris_ex,
w5500cscristype cs_sel,
w5500cscristype cs_desel,
w5500spireadbytetype spi_rb,
w5500spiwritebytetype spi_wb,
w5500dataparsingtype dataparse,
w5500requestdatatype requst
对于这些参数,对象变量我们已经定义了。而ip地址这些参数我们只需要睡着时输入就可以了。主要的是我们需要定义几个函数,并将函数指针作为参数。这几个函数的类型如下:
1 /*解析接收到的数据*/ 2 typedef uint16_t (*w5500dataparsingtype)(uint8_t *rxbuffer,uint16_t rxsize,uint8_t *txbuffer); 3 4 /*得到请求命令,一般用于客户端发起访问*/ 5 typedef uint16_t (*w5500requestdatatype)(uint8_t *rqbuffer); 6 7 /*定义片选及临界区操作函数类型*/ 8 typedef void (*w5500cscristype)(void); 9 10 /*定义spi读一个字节函数类型*/11 typedef uint8_t (*w5500spireadbytetype)(void);12 13 /*定义spi写一个字节函数类型*/14 typedef void (*w5500spiwritebytetype)(uint8_t wb);对于这几个函数我们根据样式定义就可以了,具体的操作可能与使用的硬件平台有关系。片选操作函数用于多设备需要软件操作时,如采用硬件片选可以传入null即可。具体函数定义如下:
1 /*写1字节数据到spi总线*/ 2 static void spi_writebyte(uint8_t txdata) 3 { 4 hal_spi_transmit(&w5500hspi,&txdata,1,1000); 5 } 6 7 /*从spi总线读取1字节数据*/ 8 static uint8_t spi_readbyte(void) 9 {10 uint8_t rxdata;11 hal_spi_receive(&w5500hspi,&rxdata,1,1000);12 return rxdata;//返回接收的数据13 }14 15 /*进入临界区*/16 static void spi_crisenter(void)17 {18 __set_primask(1);19 }20 21 /*退出临界区*/22 static void spi_crisexit(void)23 {24 __set_primask(0);25 }26 27 /*片选信号输出低电平*/28 static void spi_cs_select(void)29 {30 hal_gpio_writepin(gpiob, gpio_pin_12, gpio_pin_reset);31 }32 33 /*片选信号输出高电平*/34 static void spi_cs_deselect(void)35 {36 hal_gpio_writepin(gpiob, gpio_pin_12, gpio_pin_set);37 }38 39 /*数据回环处理*/40 static uint16_t loopbackdatahandle(uint8_t *rxbuffer,uint16_t rxsize,uint8_t *txbuffer)41 {42 uint16_t txsize = 0;43 44 txsize=(uint16_t)rxsize;45 46 for(int i=0;i
data_buffer_size) { size = data_buffer_size; } uint8_t rxbuffer[data_buffer_size]; ret = recv(sn,rxbuffer,size); if(ret dataparsing(rxbuffer,ret,txbuffer); uint16_t sentsize=0; while(length != sentsize) { ret = send(sn,txbuffer+sentsize,length-sentsize); if(ret < 0) { close(sn); return ret; } sentsize += ret; // 不用管sockerr_busy, 因为它是零. } } break; } case sock_close_wait: { if((ret=disconnect(sn)) != sock_ok) { return ret; } break; } case sock_init: { if( (ret = listen(sn)) != sock_ok) { return ret; } break; } case sock_closed: { if((ret=socket(sn,sn_mr_tcp,lport,0x00))!= sn) { return ret; } break; } default: { break; } } return 1;}4、应用总结这一篇中我们设计并实现了w5500以太网控制器的驱动程序,而且也设计了一个简单的应用来验证它。我们也在多个实际项目中使用w5500及驱动程序,并在此基础上实现过如modbus tcp等数据传输协议,在实际使用中效果良好。
需要说明的是我们并没有从最底层开始实现驱动程序。当然,我们完全可以同过操作寄存器实现最基础的驱动开发,但在本篇中没有这么做是因为已有的驱动底层已经很完备了,不需要重复劳动。另一方面,我们希望再次基础上做更高层次的封装,以便与使用驱动的人能够专注于具体的应用逻辑,所以我们封装了如tcp服务器及tcp客户端等,使用者则可以专注于应用协议本身。
本篇中只是验证了tcp服务器,但在使用驱动时,如果向实现如http服务器只需要修改对象的dataparsing操作就可以了。
源码下载:https://github.com/foxclever/experiphdriver
Enea最新智能手机应用程序
DIY制作感应加热器电路图设计
普渡科技推出高端送餐机器人“贝拉”
4通道、12位模数转换器SC1634、AD9633适用在红外热成像模组中
除甲醛净化器的推荐,如何正确选购甲醛空气净化器
W5500以太网控制器的驱动设计与实现
江苏省车联网行业发展现状分析,政策环境带动行业快速发展
分析clock tree的小工具——CCOPT Clock Tree Debugger(二)
在世界舞台MBBF一骑绝尘:永远更快一步的北京5G是怎样炼成的?
ic设计商三季度出货量成长有望创新高度
智能家居的产品和技术方案应用落地还需“总线+无线”相辅相成
欧盟投资4.4亿美元研究杀人机器人 不顾民众反对
如何找到合适的适配器?
农药残留检测仪的工作原理及应用
人工智能或让人们有更多的时间陪家人
小米在俄罗斯手机市场销量超越三星,位居第二
无磁隙输出变压器的单端放大器,Special class A amplifier
现代电机的关键技术
久量股份发布2020年度业绩快报
TCL在QD-Mini LED技术领域的布局