【转】可复用的基于ARM的W5100底层驱动设计

Home / 博客 / 【转】可复用的基于ARM的W5100底层驱动设计

摘要: 为了缩短基于ARM的网络化嵌入式应用开发周期、降低开发成本和提高产品质量,提出一种有别于传统利用操作系统开发嵌入式应用的模式。在该模式的框架下,对W5100以太网芯片底层驱动进行编写、封装和测试。测试表明该驱动能完成预期功能。该驱动一旦被设计好,就可以在多个项目中重复使用,缩短网络化嵌入式系统开发周期,降低开发成本和提高产品质量。

关键词: W5100驱动;复用;面向对象;ARM;嵌入式应用;开发模式

 

引言

嵌入式操作系统的引入大大提高了嵌入式系统的功能,方便了嵌入式应用软件的设计,但同时也占用了宝贵的嵌入式资源。

嵌入式操作系统大多采用组件化、模块化的设计思想,以搭积木的方式通过互连构造软件,因而是可配置的。但是由于操作系统的多样性,不同操作系统提供的配置方式迥异且繁简不一。而由于硬件平台的多样性,即使是相同的操作系统,其应用配置也有差别。结果是,应用程序开发者必须熟悉不同的硬件平台和操作系统才能进行有效的应用开发,增加了应用开发的难度。特别是网络化嵌入式应用一般出现在比较大型的项目中,复杂度和难度大大增加。因此,提高网络化嵌入式应用产品质量、缩短开发周期、降低开发成本是开发人员面临的迫切要求。

1 开发模式方案选择

面向对象的方法、设计模式的思想是当前实现软件模块化、提高软件可复用性的最优方法。面向对象编程语言、组件和构架是被广泛认可的、用以降低软件成本并提高软件质量的技术。

面向对象的主要好处在于它强调模块性和可扩展性,将易变的实现细节封装在稳定的接口后面,增强了软件的可复用性。但是,在目前的嵌入式实时系统中采用面向对象的方法进行上层软件的设计还有很多困难。最主要的,就是底层实时操作系统没有提供有力支持,即使上层软件勉强采用了面向对象的方法,代码的模块化、可移植性、可复用性也难有提高。

因此,为了避免采用传统操作系统的开发模式带来复杂问题,本文采用一种自定义的裸机开发模式。该模式避免了不同操作系统平台改变带来复杂的问题。其创建项目过程没有复杂的裁剪,只有根据需要添加相关驱动和编写适当应用层代码。即使是硬件平台的改变,也只是根据硬件配置不同改变其条件编译而已。

2 自定义裸机开发模式

自定义开发模式下的应用软件体系结构如图1所示。该体系结构包含管理层、应用层、控件层、虚设备层和实设备层。其中管理层处于类似于操作系统中“内核”的地位,为其他层的管理者。

ARM-1

图1
自定义开发模式下的应用软件体系结构
跟PC机上的Windows应用软件类似,应用层是由一个或者多个窗口组成的,有可视窗口和不可视窗口。其中各个窗口中又包含一个或者多个控件。控件为窗口提供各种服务,由设备层提供支撑。在控件层和实设备层中间有一个虚设备层。
根据设备功能的复杂程度,虚设备分为简单的虚设备和复合虚设备。实设备分为简单的实设备和复合实设备。其中复合设备是由简单设备组合而成。
从类的关系看,虚设备层就是含有虚函数的基类,该函数一般没有实现,只是声明了接口,实设备层就是从该基类继承下来的,具体实现是由该实设备层来完成。由于接口的稳定性,这就保证了底层硬件改变时,应用层的程序几乎可以没有改变或者改变甚小。
3
自定义开发模式下的W5100驱动编写
3.1
接口电路说明
本驱动设计采用W5100串行SPI接口。SPI接口模式只需要4个引脚进行数据通信,分别为SCLK、/SS(SPI从模式选择输入引脚,低电平有效)、MOSI、MISO。W5100
的SPI_EN 引脚高电平表示SPI
使能,/RESET引脚低电平实现W5100芯片的复位。本项目选择的MCU芯片为LPC2138,其中W5100与LPC2138对应引脚连线如表1所列。
表1  LPC2138与W5100对应引脚连线说明

ARM-2

3.2 W5100驱动分析
本驱动开发环境为:CodeWarrior for ARM Developer Suite
V1.2。
在自定义开发模式中,前期编写好的驱动类有引脚类、SPI类、外部中断类,就是所谓简单设备。这些类及其头文件的具体介绍略——编者注。
在使用W5100前需要操作其/SS引脚,选中W5100芯片SPI从模式。初次配置或者重新配置W5100相关参数前,需要操作其/RESET引脚,让所有原来配置复位。配置W5100相关参数是通过SPI读写操作来完成的。
W5100从网络上接收了一个数据包后,会让其/INT引脚从高电平变为低电平。在本项目中,把该引脚跟LPC2138的外部中断1引脚相连,如果开启了外部中断1,那么就触发一个外部中断。该外部中断服务里面应当有实现LPC2138访问W5100,并读取W5100里面接收到的数据包的功能。LPC2138获取W5100里面的数据包,必须通过SPI读写操作的配合才能实现。
综合上面的分析,该W5100驱动应当是由引脚类、外部中断类、SPI操作类互相配合完成,因此W5100驱动是一个复合设备。
3.3
W5100实设备驱动编写
本驱动针对W5100采用UDP协议进行网络通信功能来编写。

首先所有实设备都必须从一个虚设备下继承下来。创建一个名为Ip_NetWork_Virtual_Device网络虚设备,其部分头文件略——编者注。
由于网络通信应用的芯片有多种,但是无论是哪种芯片,实现的功能都离不开网络包的读和写。因此在该虚类里的声明都是共用的功能。基类里面含有虚函数,就是声明了接口,但是没有具体的实现,具体的实现由其具体的实设备来完成。在继承中,如果基类和派生类中定义了同名的成员函数,当用基类指针指向公有派生类的对象后,可以使用虚函数来实现通过基类指针找到相应的派生类成员函数[11]。
W5100的实设备NetWork_W5100类的部分头文件略——编者注。
3.4
W5100实设备驱动说明
3.4.1 虚设备类指针
在W5100实设备头文件中有:
Spi_Virtual_Device*
SpiPort;
OutEint_Virtual_Device* IntDevice;
PinDevice_Virtual_Device *
W5100_Cs;
PinDevice_Virtual_Device *
W5100_RESET;
由于W5100实设备需要几个简单设备配合来完成其功能,因此设计W5100实设备驱动拥有这些简单设备的指针,可以看出这些指针是指向简单虚设备对象的指针。
声明为指向基类对象的指针,当它指向公有派生类对象时,可以利用它来直接访问派生类中从基类继承下来的成员,不能直接访问公有派生类中特定的成员。
采用面向对象中模式编程法则中的依赖反转法则:依赖抽象而不依赖具体[12]。
在main.cpp首先声明如下的实设备,如下:

OutInt_2138 NetOutInt; //外部中断类实设备对象
NetWork_W5100
Net5100;//W5100实设备对象
Spi0_Driver_Lpc Spi0;//SPI实设备对象
Pin_LPC2138 PIN023;
//引脚实设备对象
Pin_LPC2138 PIN031;
//引脚实设备对象
接着在main.cpp采用如下代码完成Net5100和简单设备NetOutInt、Spi0、PIN023、PIN031等的关联:
NetOutInt.WorkModel=Fall_Eage;//表示下降沿触发
NetOutInt.SubDeviceName=Eint1;//表示使用外部中断1
NetOutInt.Ini();
NetOutInt.Father=&Net5100;
Net5100.SpiPort=&Spi0;//设置Spi0和SpiPort指针关联
Net5100.W5100_Cs=&
PIN023; //设置片选引脚关联
Net5100.W5100_RESET=& PIN031;
//设置复位引脚关联
在“Net5100.SpiPort=&Spi0”中SpiPort是指向某基类对象的指针,Spi0是该基类的派生类对象,该语句实现把该指针指向其派生类对象。因此就可以利用该指针直接访问该公有派生类从基类继承来的成员。同样,可以利用W5100_Cs和W5100_RESET等基类对象指针直接访问该基类的派生类——Pin_LPC2138类从基类继承下来的成员,即引脚的操作函数等。
在NetWork_W5100中有W5100_Send_Receive_Data函数就是利用这个技术,该函数如下:
char
NetWork_W5100::W5100_Send_Receive_Data(char dat){
char
i;
W5100_Cs->Clear();
i=SpiPort->SPI_Send_Receive_Data(dat);
W5100_Cs->Set();
return
i;
}
NetWork_W5100类对象能实现SPI读写操作,是因为其拥有一个SPI虚设备的指针。同理,能实现对引脚操作是因为其拥有一个引脚虚设备的指针。
3.4.2
外部中断实设备和W5100实设备关联
NetOutInt是一个外部中断类对象,使用前首先对该对象进行初始化,其中代码“NetOutInt.SubDeviceName=Eint1”表示该类对象和外部中断1产生了绑定。
在本项目测试中,W5100从网络接收到一个数据包后触发了一个外部中断1中断。该W5100实设备类对象Net5100感知该事件,从而对该事件进行处理,接着把该消息发布给其所支撑的控件。
main.cpp中有“NetOutInt.Father=&Net5100;”,其中Father是一个指针,该指针来源如下:
class
Object{
public:
……
Object
*Father;
……
};
由于所有设备类都是从该类间接继承下来,所以都拥有这个Father指针。
“NetOutInt.Father=&Net5100;”的目的是把Net5100对象地址赋给该指针,因此该指针就指向Net5100,说明NetOutInt拥有一个指向Net5100的指针。main.cpp中,外部中断1的服务程序代码如下:
void
__irq
IRQ_Eint1(){
NetOutInt.HardInt(Null);
VICVectAddr=0x00;
NetOutInt.ClearInt();
}
“NetOutInt.HardInt(Null);”其本质就是调用到HardInt函数,如下:
void
OutInt_2138::HardInt(Device*
IntDevice){
……
this->Msg.MsgID=Sys_Msg_OutInt;
this->Msg.Parm1=this->SubDeviceName;
this->Father->Message(Msg);
……
}
“this->Father->Message(Msg);”即中断服务最后把该工作交给Father指针指向的Net5100,接着该对象调用了其Message函数。NetWork_W5100类的Message函数伪代码如下:
void
NetWork_W5100::Message(MessageBody SystemMsg){
if
Socket3
SelectSocket(3);
if Socket2
SelectSocket(2);
if
Socket1
SelectSocket(1);
else
SelectSocket(0);
};
其中NetWork_W5100类的SelectSocket函数如下:
void
NetWork_W5100::SelectSocket(char socket){
uint16
address,inttype;
address=COMMON_BASE+0x100*socket+0x0402;
inttype=NetWork_Read(address);

if((inttype&0x04)==0x04){
//接收数据引起中断
S_UDP_RX_Process(socket,&ReceiveBuffer[0],&ReceiveBuffer[8]);
//从对应的Socket接收数据
Msg.MsgID=Sys_Msg_UdpGetData;
Msg.Msg=&ReceiveBuffer[0];
VclPointer[socket]->Message(Msg);
//向支撑控件发送消息
}
NetWork_Write(address,0xFF);//清除所有的中断
}
可见,W5100驱动最后把网络接收到数据包作为一个消息发给其所支撑的上层控件。
4
W5100驱动测试
4.1 测试方案
在PC机上,利用网络测试工具TCP/UDP
Socke调试工具V2.2,通过网络向W5100的终端发送一个数据包。当该终端接收到该数据包后,把该包往PC机终端发送。如果发送和接收的数据包一致,说明通信测试成功。
4.2
测试过程
PC机端的IP地址为192.168.1.103,某端口号为9000。W5100本身地址设置为192.168.1.101,某端口号为9000。PC机往W5100终端发送数据包,在如图2所示操作界面的数据发送窗口输入“Hello,
This is a happy word!”字符串后,点击“发送数据”,在操作界面的数据接收窗口接收到“Hello, This is a happy
word!”,并且在操作界面上方显示“对方IP:192.168.1.101,对方端口:9000”,这跟W5100终端设置是一致的,说明双方的通信成功。

结语
W5100驱动的创建过程非常复杂,但是对于编写好的驱动,应用者只需要了解该接口使用的说明,而不用关心其复杂的内部实现细节。如果其他项目需要用到W5100,只需要把该驱动添加到该项目中即可,从而实现驱动的复用,避免重复的工作,缩短项目开发周期。如果下次要使用W5100驱动的其他功能,如TCP协议通信,只需要在原来驱动上添加相应的函数即可,因此维护起来更加方便。

编者注:本文为期刊缩略版,全文见本刊网站www.mesnet.com.cn

转自:  《单片机与嵌入式系统应用》杂志

 

与我们更多联系:

WIZnet邮箱:[email protected]

WIZnet中文主页:http://www.iwiznet.cn

WIZnet企业博客:http://e.weibo.com/wiznet2012