技术中心
 
 

LonWorks现场总线设备驱动设计与实现

   日期:2006-07-06     作者:管理员    

关键词: 嵌入式Linux   LonWorks现场总线   设备驱动

1  引言
    新设备及新的接口规范等的不断出现,使得操作系统的设备驱动部分的开发工作层出不穷。我们在基于嵌入式Linux智能控制器的开发中,首先遇到了LonWorks现场总线设备的驱动程序问题。于是我们对Linux的驱动程序实现机制进行了深入研究,并开发了LonWorks现场总线设备的驱动程序。

2  关于LonWorks现场总线

    现场总线是一种工业数据总线,是连接智能现场设备和自动化系统的高可靠的数字式、双向传输的通信技术,可方便地构成全数字化的分布式现场控制网络。对于防护工程这种工程轴线长、监控测点分散的系统,特别适合采用现场总线技术构成工程的分布式智能化控制系统。

    在各种现场总线中,LonWorks总线技术以其在技术先进性、可靠性、开放性、拓扑结构灵活性等方面独特的优势,为集散式监控系统提供了很强的实现手段。使其特别适合于建筑的楼宇

自动化系统。根据防护工程是一种特殊的地下建筑工程以及其布局特点,特别适合采用LonWorks现场总线技术实现其内部设备的自动化监控。

    网络接口卡是主计算机与LonWorks网络的接口,使计算机能够完成与LonWorks节点之间的数据交换。在我们设计的智能控制器中,采用专门的嵌入式LonWorks现场总线接口卡作为嵌入式CPU与Lon-
Works现场总线网络之间的接口设备。

2.1  LonWorks现场总线网卡的原理

    了解LonWorks现场总线卡的工作原理,对编写驱动程序是必要的,故我们首先给出其工作原理,并对此作一些必要的解释。图1是嵌入式LonWorks网卡的硬件电路方框图。

LonWorks现场总线设备驱动设计与实现如图

 

    过去实现微控制器与ISA总线的接口一般使用8155、8255,电路复杂,调试困难,在本网卡的设计中,使用可编程逻辑阵列(CPLD)来实现与ISA总线的接口逻辑,只用一个芯片就完成了所有功能,大大简化了网卡的电路。

2.2  LonWorks现场总线网卡的工作过程

    计算机与微控制器之间数据交换接口的应用程序框图如图2、3所示。在程序框图中,主要实现了计算机与微控制器之间读写数据、置标志位和清除标志位的功能。CPLD内部实现了存储数据和标志位的寄存器。

LonWorks现场总线设备驱动设计与实现如图

 

LonWorks现场总线设备驱动设计与实现如图

3  LonWorks现场总线网卡设备驱动实现

    我们在Linux平台上开发和设计了LonWorks现场总线网卡的软件。软件部分由应用程序和设备驱动程序两部分构成,这里首先主要讨论的是设备驱动程序部分。在Linux平台上实现对硬件的驱动支持可以有两种方式:一种是直接在用户空间来实现;另一种是使用Linux内核中提供的机制来实现。考虑到用户空间驱动程序的局限性,比如为了访问特权指令(I/O指令)必须做一些影响系统安全的设置等等,我们在开发中采用了第二种方式。

3.1  Linux的可加载模块机制

    Linux内核提供了两种机制来开发设备驱动程序:一种是直接把驱动程序链接到内核中:另一种则是通过称为Linux可加载模块的机制来开发可动态加载和卸载的驱动模块。而第一种方式可以在后一种方式成功后,采用与内核一起提供的配置工具和接口来完成。所以,我们只研究可加载模块机制。

    Linux作为单核结构其效率比较高,但是系统灵活性不足,为了平衡这两者的关系,它提供了可动态加载机制。利用这种机制我们可以开发Linux内核模块,并且可以动态的对它加载和卸载。Linux下的设备驱动程序一般都支持这种方式,且模块被加载到内核后,它就可以任意的利用核心提供的各种资源和服务了。为了让模块利用核心提供的资源,Linux内核维护了一张所有内核资源的符号表(在接下来的部分我们称它为内核资源符号表),用于在模块载入时解决对相应资源的引用问题。并且,Linux允许模块的堆栈操作,由此一个模块可以使用其他模块所提供的资源。也就是说:一个模块对另一个模块的资源的使用与其对内核资源的使用非常相似,不同的只是这些服务的资源从属于另一个模块而已。每当一个模块被加载Linux就会有一个修改内核资源符号表的过程,将该模块所提供的服务和资源加入进去,这样另一个模块载入时,如果需要就可以引用这个模块的资源了。而卸载一个模



块时,就要知道当前模块是否正在被使用。如果没有被使用,在卸载时要能够通知该模块它将被卸载,以便由它自己释放已被它占用的系统资源。然后,Linux还要从内核资源符号表中删除所有该模块提供的资源和服务。

    从上面的原理分析可知,内核模块编写时应该具有两个主要的接口函数:init_module()用于在模块加载时由加载模块的工具调用,以便于注册一些必要的服务和申请一些资源。cleanup_module()用于在模块卸载时由删除模块的工具来调用,清除掉由init_module()所做的工作,从而使内核模块可以安全的卸载。其中对init_module()调用的一种工具是在根用户执行insmod命令来加载模块时执行。而对于cleanup_module()的调用是在根用户使用rmmod命令来卸载模块时执行。

3.2  Linux下设备驱动程序
    系统调用是操作系统内核和应用程序之间的接口,设备驱动程序是操作系统内核和机器硬件之间的接口。设备驱动程序为应用程序屏蔽了硬件的细节,这样在应用程序看来,硬件设备只是一个设备

文件,可以通过相应的系统调用象操作普通文件一样对硬件设备进行操作。

(1) Linux设备分类
    Linux系统的设备分为字符设备(char device),块设备(block device)和网络设备(network device)三种。字符设备是指存取时没有缓存的设备,如系统的串口设备/dev/cua0, /dev/cual。块设备的读写则都有缓存来支持,只能以块为单位进行读写,并且块设备必须能够随机存取(random access),即不管块处于设备的什么地方都可以对它进行读写,字符设备则没有这个要求。块设备主要包括硬盘软盘设备,CD-ROM等。网络设备在Linux里做专门的处理。Linux的网络系统主要是基于BSD
unix的socket机制。

(2) 设备标识方式

    Linux设备由一个主设备号和一个次设备号标识。主设备号唯一标识了设备类型,即设备驱动程序类型,它是块设备表或字符设备表中相应表项的索引。次设备号仅由设备驱动程序解释,一般用于识别在若干可能的硬件设备中,I/O请求所涉及到的那个设备。值得一提的是次设备号还可以被分成几个部分用来区分子设备驱动程序和具体的设备。

(3) Linux设备驱动程序组成部分

Linux设备驱动程序可以分为三个主要组成部分:

●自动配置和初始化子程序。负责检测所要驱动的硬件设备是否存在和是否能正常工作。如果该设备正常,则对这个设备及其相关的、设备驱动程序需要的软硬件进行初始化。

●服务于I/O请求的子程序。它们主要是对file_operations结构的各个入口点的实现。这部分的实现支持了文件系统的调用(如open,close,
read等等)。

●中断服务子程序。在Linux系统中,并不是直接从中断向量表中调用设备驱动程序的中断服务子程序,而是由Linux系统来接收硬件中断,再由系统来调用中断服务子程序。
但是,这三个部分不是必须在每个驱动程序中必须具有的。

3.3  LonWorks现场总线网卡驱动程序

    研究了Linux的设备管理以及设备驱动程序实现方法后,我们来设计LonWorks现场总线设备驱动程序,并对实现中的一些关键问题进行探讨。

(1) LonWorks现场总线网卡驱动程序
    在驱动程序设计和开发中,我们一定要注意的问题是机制(Mechanism)与策略(Policy)的分离。这里所谓的机制是指我们的驱动程序提供的接口应该很忠实地反映设备的原始功能(bare function),而与应用无关。而策略是指一旦这个设备驱动程序为设备机制提供了相应的软件接口,那么应用程序开发人员就能按照特定的方式使用机制接口。可以说,在内核驱动程序开发过程中,所设计的数据结构,以及确定的接口命令都是为以后的应用策略提供的一种机制。而如前所述,这种机制在Unix类系统内部是通过一组固定的入口点来提供的。由于我们要开发的设备驱动程序是一个字符型的设备,所以接下来我们首先分析字符型设备驱动程序中常用的入口点:

● open入口点
打开设备准备I/O操作。对字符设备文件进行打开操作,都会调用设备的open入口点。open子程序必须对将要进行的I/O操作做好必要的准备工作,如清除缓冲区等。如果设备是独占的,即同一时刻只能有一个程序访问此设备,则open子程序必须设置一些标志以表示设备处于忙状态。

●release入口点
关闭一个设备。当最后一次使用设备终








结后,调用release子程序。独占设备必须改变前由open子程序设置的标志,以便设备可再次被使用。

●read入口点
从设备上读数据。对于有缓冲区的I/O操作,一般是从缓冲区里读数据。对字符设备文件进行读操作将调用read子程序。

●write入口点
往设备上写数据。对于有缓冲区的I/O操作,一般是把数据写入缓冲区里。对字符设备文件进行写操作将调用write子程序。

● ioctl入口点
执行读、写之外的一些硬件控制操作。

●poll入口点
把对许多非阻塞操作的设备描述符集合起来,等待事件的发生,以便于集中检查,看数据是否可从设备读取或设备是否可用于写数据,这样就做到了所谓的多路复用。

    以上入口点构成了设备驱动程序的三大组成部分中I/O请求的部分,在Linux中它们由file_operations结构来封装,并不是所有的字符设备驱动程序都必须提供以上每一个入口点的实现,如果设备驱动程序没有提供上述入口点中的某几个,系统会用缺省的子程序来代替。

    由上面的描述可见,在内核设备驱动程序的设计中,相应的机制的提

供主要是对设备入口点的选择和设计。

    针对LonWorks现场总线网卡的特点,我们选择并实现了五个入口点,即open, release,read,write, ioctl。对于open和release入口点由于设备特点,我们只需要控制设备驱动模块在使用时,不被异常释放即可。接下来,我们将描述以上设计实现中与Linux内核相关的一些调用和问题。

(2) 对file_operations结构的初始化file_operations结构是Linux操作系统中用于实现驱动程序的最重要的数据结构,我们已经在前面提到过,它对Linux提供I/O请求的子程序的一系列入口点进行了封装。该结构贯穿在整个驱动程序中,故我们在文件作用域内定义了它的一个变量,并对本程序中用到的入口点做了初始化,其代码如下:
struct file_operations lmdev_fops= {
NULL,
lmdev_read,
//把实现的lmdev_read函数指针赋给read入口点。
lmdev_write,
//把实现的lmdev_write函数指针赋给write入口点。
NULL,
NULL,
lmdev_ioctl,
//把实现的lmdev_ioctl函数指针赋给ioctl入口点。
NULL,
lmdev_open,
//把实现的lmdev_ open函数指针赋给open入口点。
lmdev_release,
//把实现的lmdev_release函数指针赋给release入口点。
NULL,
NULL,
NULL,
NULL,
};
对于lmdev-*函数的实现方法,我们将在后面做详细的讨论。

(3) 模块初始化与模块卸载
●LonWorks现场总线网卡驱动模块初始化,通过对init_module的实现来完成以下几个任务。以字符设备类型向系统注册LonWorks现场总线设备卡,同时动态获得其设备号。通过调用下面这个函数int
register_ chrdev(unsigned int major, const char*name,struct file_operations
*fops)来实现。这里我们使major参数为0,这样系统就会动态的分配并返回主设备号。name参数是用于标识设备的字符串。file_operatons传入的是如前所述的lmdev_fops。然后,向系统申请LonWorks现场总线网卡的I/O端口地址。我们根据该卡上的跳线得到的I/O地址,调用系统提供的宏:check_region(start,n)//检查端口地址范围start到start+n-1是否可用,是则返回0,否则返回1。request_region(start,n,name)//用于申请通过以上函数检查的地址范围。接下来,做一些必要的系统日志,根据各种条件用printk向系统日志缓冲区写入不同级别的信息。最后,控制对内核资源提供的符号表输出的符号信息(即在可加载模块机制部分提到的模块要注册的服务)。这里使用EX-PORT_NO_SYMBOLS使得该模块不输出任何符号信息。

●LonWorks现场总线网卡模块卸载需要完成以下几个任务:
调用release_region(start,n)宏释放模块初始化时申请的I/O端口资源。
调用int unregister_chrdev(unsigned int major, const char*name);
向系统注销该字符设备,本程序中major参数即前面注册时动态获得的主设备号,name与注册时提供的name字符串相同。调用printk函数,做一些必要的系统日






























志。

(4) 对file operations结构中入口点的实现

●open和release入口点。

       这两个入口点在本模块中被赋予的就是前面在介绍file_operations结构时给出的lmdev_open和lmdev_close函数指针,它们主要通过调用MOD_INC_USE_COUNT及MOD_DEC_USE_COUNT来进行模块计数。用计数来对LonWorks现场总线设备驱动模块是否正在被使用进行控制,防止模块正在使用时被意外卸载而导致核心对设备操作出现异常。

●对read/write入口点的实现
    这个入口点在本模块中被赋予的就是前面在介绍file_operations结构时给出的lmdev_read函数指针,它是对设备操作的核心部分,根据前面描述的算法,它实现了如下几个功能:

    用inb_p宏,访问硬件的状态和数据端口,以读取相应的状态和数据信息。
调用long_sleep_on_timeout(wait_queue_he

ad_t *q, long timeout)函数把当前进程加入时钟等待队列q中,使它等待timeout时间。根据LonWorks现场总线卡的工作方式来看,这样做可以减少轮询时间,大大的提高了效率。

    Linux分为核心空间和用户空间,用户空间的代码不能直接访问核心空间,故需调用Linux核心提供的copy_to_user(to,from,n)宏,把数据从内核空间地址from拷贝到用户空间地址to中。这样,系统调用返回后,用户空间的代码就可以通过to指针来访问相应的数据并进行处理了。这样核心驱动模块部分的程序就完成了。

(5) 编译内核模块
    在程序完成后,用gcc编译成目标文件(不链接,生成*.o文件),要做到这一点只需在gcc命令行里加上-c参数。另外,还要加上-D_KERNEL_ -DMODULE参数。上述程序可以这么编译。
root# gcc -c -D-KERNEL_-DMODULE -Wall -02
lmdev.c。其中参数-Wall的功能是打印附加的警告信息。由于头文件中的函数都是声明为inline的,还必须给编译器指定-O选项。gcc只有打开优化选项后才能扩展内嵌函数,不过它能同时接受-g和-O选项,这样就可以调试那些内嵌函数的代码了。优化参数-O有三个级别:Ol,02, 03,它们的优化程度不同,优化效果03大于02大于Ol。编译好模块后的如何加载模块,在前面已经有所描述,这里就不再叙述了。

3.4  应用程序开发
    在对以上模块编译并加载后,Linux根据用户可用mknod命令,利用动态分配的主设备号(该设备号在用户空间可以从/proc/devices文件中用设备名获得)建立相应的设备文件,并对它设置恰当读写权限后,就可以在我们的应用程序中,使用Linux的文件系统调用通过这个设备文件来操作LonWorks现场总线卡了。这样做不仅使得应用程序编程风格更加统一,代码更具鲁棒性,应用系统更加安全更易于维护。而且可在核心级来保证关键部分的实时响应,从而降低了用户程序开发的难度。
下面是我们为这个驱动开发的测试程序的一些实验结果,测试项目为:
通过智能控制器检测LonWorks现场总线网络节点分布状况。
测试结果如图4所示:

LonWorks现场总线设备驱动设计与实现如图

      结果分析:智能控制器分别检测出1#LonWorks现场总线通道6号节点有一个开关量前端(LM1202),2#LonWorks现场总线通道11号节点有一模拟量前端(LM1101)。智能控制器检测结果与实际LonWorks现场总线网络节点分布一致,这说明LonWorks现场总线设备驱动程序正确运行。到此,LonWorks现场总线设备驱动开发完成。

4  结束语
    本文在研究了基于嵌入式Linux平台的驱动程序开发机制后,研究实现了嵌入式Linux环境下的LonWorks现场总线网卡的设备驱动。应用实现结果表明,该设备驱动运转正常、性能可靠。这一研究课题的成功的解决对于拓宽LonWorks现场总线应用领域具有借鉴和推广意义。











 
  
  
  
  
 
更多>同类技术
 
全年征稿 / 资讯合作
 
推荐图文
推荐技术
可能喜欢