嵌入式Linux初级实验s3c2410 中.docx
- 文档编号:18548850
- 上传时间:2023-08-19
- 格式:DOCX
- 页数:158
- 大小:360.54KB
嵌入式Linux初级实验s3c2410 中.docx
《嵌入式Linux初级实验s3c2410 中.docx》由会员分享,可在线阅读,更多相关《嵌入式Linux初级实验s3c2410 中.docx(158页珍藏版)》请在冰点文库上搜索。
嵌入式Linux初级实验s3c2410中
第三篇基础实验篇
本篇内容
Linux设备驱动概述★
LED实例★
按键中断实例★
数码管实例★
4*4键盘实例★
LCD实例★
触摸屏实例★
本篇目标
了解Linux设备驱动的相关概念及开发基础★
掌握简单字符设备驱动的程序结构及设计流程★
学习嵌入式Linux中断机制及其驱动程序结构★
学习数码管的显示原理及其驱动程序的设计方法★
熟悉键盘驱动原理,学会为自己的系统添加键盘设备驱动程序★
了解移植LCD显示设备驱动及触摸屏输入设备驱动的过程★
本篇实例
实例一:
LED驱动及测试实例★
实例二:
按键中断驱动及测试实例★
实例三:
数码管实例★
实例四:
4*4键盘实例★
实例五:
LCD驱动移植实例★
实例六:
触摸屏驱动移植实例★
第8章Linux设备驱动概述
在前一篇中,我们介绍了开发嵌入式Linux的基本过程,本章开篇在前一篇的基础上进行设备驱动程序的开发,使得目标板上的硬件资源为板上系统所用,这也是所有设备驱动的巨大贡献。
本章将带领你走进Linux设备驱动开发的世界。
本章首先介绍的是设备驱动的作用及其分类,不同驱动程序的特点等,然后介绍驱动模块的加载和卸载方式等。
8.1设备驱动的角色
任何计算机系统的运行都是系统中软硬件相辅相成的结果,没有硬件的软件是空中楼阁,而没有软件的硬件则只是一堆的电子元器件而已。
硬件是底层基础,是所有软件得以运行的平台,程序最终会实现为硬件上的逻辑电路;软件则是具体应用的实现,根据不同的业务需求而设计。
硬件一般是固定的,软件则很灵活,可以适应各种复杂多变的应用。
从某种程度上来看,计算机系统的软硬件相互成就了对方。
但是,软硬件之间同样存在着悖论,那就是软件和硬件不应该互相渗透入对方的领地。
为尽可能快速地完成设计,应用软件工程师不想也不必关心硬件,而硬件工程师也难有足够的闲暇和能力来顾及软件。
譬如,应用软件工程师在调用套接字发送和接收数据包的时候,他不必关心网卡上的中断、寄存器、存储空间、I/O端口、片选以及其他任何硬件词汇;在使用scanf()函数获取输入的时候,他不用知道底层究竟是怎样把终端设备的操作转化成程序输入的。
也就是说,应用软件工程师需要看到一个没有硬件的纯粹的软件世界,硬件必须被透明地呈现给它。
谁来实现硬件对应用软件工程师的透明化?
这个光荣而艰巨的任务就由设备驱动来完成。
设备驱动,英文名为“DeviceDriver”,全称为“设备驱动程序”,是一种在应用程序和硬件设备之间通信的特殊程序,相当于硬件的接口,应用程序通过它识别硬件,通过向该接口发送、传达命令,对硬件进行具体的操作。
通俗的讲,设备驱动就是“驱使硬件设备行动”。
每一种硬件都有其自身独特的语言,应用程序本身并不能识别,这就需要一个双方都能理解的“桥梁”,而这个“桥梁”,就是驱动程序。
驱动与底层硬件直接打交道,按照硬件设备的具体工作方式操作,如读写设备寄存器,完成设备轮询、中断处理、DMA通信、进行物理内存向虚拟内存的映射等等,从而使得通信设备能收发数据,显示设备能显示文字和画面,存储设备能记录文件和数据。
硬件如果缺少了驱动程序的“驱动”,那么它就无法理解应用层软件传达的命令而不能正常工作,本来性能非常强大的硬件设备就是空有一身本领都无从发挥、毫无用武之地。
因此,设备驱动享有“硬件的灵魂”、“硬件的主宰”、和“硬件与应用软件之间的桥梁”等美誉。
由此可见,设备驱动对应用程序而言,透明化了硬件设备,存在于应用程序和实际设备间的软件层,是硬件设备和应用软件之间的沟通纽带。
应用软件只需要调用系统软件的应用编程接口,而不用去详细的了解硬件设备的性能、参数等就可以让硬件去完成要求的工作。
总之,驱动程序沟通着硬件和应用软件,使得整个计算机系统得以正常运行。
驱动的这种桥梁的角色允许驱动工程师严密地选择设备应该如何表现:
不同的驱动可以提供不同的能力,甚至是同一个设备也可以提供不同的能力。
实际的设备驱动设计应当是在许多不同考虑中的平衡。
一个主要的考虑是:
在给用户尽可能多的选项和编写驱动的时间之间做出平衡,还需要保持事情简单以避免错误潜入。
例如,一个设备可能由不同的程序并发使用,驱动工程师有完全的自由来决定如何处理并发性。
你能在设备上实现内存映射而不依赖它的硬件能力,或者你能提供一个用户库来帮助应用工程师在可用的原语之上实现新策略,等等。
相信只要在PC上安装过操作系统的人都知道,安装完操作系统后,还需要安装驱动程序,这样,系统才能正常运行。
这种驱动是在有操作系统的情况下编写和使用的。
但是,在嵌入式领域中,整个系统有时候并不需要强大的操作系统,而且硬件资源也不具备操作系统的生存空间,存储容量、各种设备等都有严格的限制。
因此,我们的驱动程序将面临两种不同的开发环境。
在下一节中,我们将介绍在无操作系统和有操作系统两种不同的环境下,其设备驱动的特点和开发。
8.2设备驱动和操作系统
上一节笔者介绍了设备驱动在整个计算机系统中的重要地位和作用,以及设备驱动的相关概念。
本小节将根据有无操作系统的情况,介绍无操作系统的设备驱动和有操作系统的设备驱动。
总的来说,在嵌入式设备中没有操作系统,这也是非常普遍的现象,设备驱动可以根据硬件设备的特点自行定义接口,如对串口定义SerialSend()、SerialRecv(),对LED定义LightOn()、LightOff(),对Flash定义FlashWrite()、FlashRead()等。
如果系统中有操作系统,驱动程序的开发则还需要了解相应的操作系统定义的驱动架构,驱动工程师必须按照相应的架构设计驱动,这样,驱动才能良好地整合入操作系统的内核。
在这种情况下,业内人士总结了一个经典公式:
设备驱动开发=硬件控制+内核API+内核驱动框架。
对于这个公式的解释,在下面有较详细的介绍。
8.2.1无操作系统时的设备驱动
并非任何一个计算机都需要运行操作系统,有许多情况下,操作系统都不必存在。
譬如ASIC内部、公交车的刷卡机、电冰箱、微波炉、简单的手机和小灵通等这些简单功能的计算机系统,用单任务架构完全可以很好的支持工作,并不需要多任务调度、文件系统、内存管理等复杂功能。
一个无限循环中夹杂对设备中断的检测或者对设备的轮询是这种系统中软件的典型架构:
代码清单8-2-1单任务软件典型架构
intmain(intargc,char*argv[])
{
while
(1)
{
if(SerialInt==1)/*有串口中断*/
{
ProcessSerialInt(); /*处理串口中断*/
SerialInt=0; /*中断标志清0*/
}
//…
status=Checkxxx();
switch(status)
{
//…
}
//…
}
}
在这样的系统中,虽然不存在操作系统,但是设备驱动则无论如何都必须存在。
一般情况下,对每一种设备驱动都会定义为一个软件模块,包含.h文件和.c文件,前者定义该设备驱动的数据结构并声明外部函数,后者进行驱动的具体实现。
设备驱动工程师需要牢固的硬件基础,驱动直接与硬件打交道,在编写某类硬件设备的驱动时,我们必须掌握该驱动涉及到的硬件的接口和工作原理,因为许多时候,我们需要直接操作寄存器、控制中断和DMA等。
下面我们将定义一个串口的驱动。
代码清单8-2-2无操作系统时串口驱动
/**********serial.h******************/
externvoidSerialInit(void);
externvoidSerialSend(constcharbuf*,intcount);
externvoidSerialRecv(charbuf*,intcount);
/**********serial.c*****************/
/*初始化串口*/
voidSerialInit(void)
{
//...
}
/*串口发送*/
voidSerialSend(constcharbuf*,intcount)
{
//...
}
/*串口接收*/
voidSerialRecv(charbuf*,intcount)
{
//...
}
/*串口中断处理函数*/
voidSerialIsr(void)
{
//...
serialInt=1;
}
代码清单8-2-2定义了无操作系统时的串口驱动,其他模块想要使用串口设备的时候,只需要包含串口设备驱动的头文件即可。
也就是在使用串口的模块中添加串口驱动的头文件serial.h,然后调用其中的外部接口函数,这样我们就能使用我们的串口进行工作了。
如我们要从串口上发送“HelloWorld!
”字符串,使用语句SerialSend(“HelloWorld!
”,12)即可。
由此可见,在没有操作系统的情况下,设备驱动的接口直接提交给应用软件工程师,应用软件没有跨越任何层次就可以直接访问设备驱动的接口。
驱动包含的接口函数也与硬件的功能直接吻合,没有任何附加功能。
一般地说在无操作系统时,整个计算机系统中的硬件资源、设备驱动和应用软件的关系如图8-2-1所示。
图8-2-1无操作系统时硬件、驱动和应用软件的关系
8.2.2有操作系统时的设备驱动
上一节中的设备驱动直接被应用程序调用,不与任何操作系统关联。
当系统中包含操作系统后,设备驱动会变得怎样?
本小节将对这个问题进行讨论。
图8-2-2硬件、驱动、操作系统和应用程序的关系
首先,无操作系统时设备驱动的硬件操作仍然是必不可少的,没有这一部分,设备驱动不可能与硬件打交道,也就是说在无操作系统时驱动所做的工作,在有操作系统时也是要做的。
其次,我们还需要将设备驱动融入操作系统内核。
应用程序是通过调用操作系统的API来实现对硬件的操作的,所以设备驱动需要融入到内核中。
为了实现这种融合,必须在所有的设备驱动中设计面向操作系统内核的接口,这样的接口由操作系统规定,对一类设备而言结构一致,独立于具体的设备。
不同的操作系统中定义的设备驱动架构是不一样的,要将设备驱动融入系统内核中,就需要按照操作系统给出的独立于设备的接口架构设计,如此这般,应用程序就可以使用统一的系统调用接口来访问各种设备。
在前面我们曾提到过设备驱动开发的公式:
设备驱动开发=硬件控制+内核API+内核驱动框架。
其中内核的API包括并发/同步控制、阻塞/唤醒、中断底半部调度、内存和I/O访问等。
由此可见,当系统中存在操作系统时,设备驱动变成了链接硬件和内核的桥梁,操作系统的存在使得单一的“驱动硬件设备工作”变为操作系统与硬件交互的模块,它对外呈现为操作系统API,不再给应用软件工程师直接提供接口。
因此,驱动工程师不仅需要牢固的硬件基础,如硬件的工作原理、寄存器设置等,还需要对驱动中所涉及的内核知识有良好的掌握,包括内核支持的API、内核驱动架构等,才能设计开发出好的设备驱动程序。
也就是说设备驱动从无操作系统时的应用程序和硬件设备之间的桥梁转变成操作系统和硬件设备之间的沟通纽带,其结构关系如图8-2-2所示。
在本书中,笔者后面将主要介绍有操作系统时设备驱动的设计和开发,这里只是希望读者对设备驱动在整个计算机系统的地位和设计开发需要的知识有个了解,具体的开发和设计过程将在后续的内容中详细介绍。
8.3Linux设备驱动
近年来,随着嵌入式系统应用的持续升温,Linux广泛应用于嵌入式领域,逐步成为通信、工业控制、消费电子等领域的主流操作系统,Linux在嵌入式系统中的占有率与日俱增。
这些采用Linux作为操作系统的设备中,无一例外都包含着多个Linux设备驱动。
这些驱动程序在Linux内核里犹如一系列的"黑盒子",使硬件响应定义好的内部编程接口,从而完全隐藏了设备工作的细节。
Linux系统中的设备驱动设计是嵌入式Linux开发中十分重要的部分,前面提到过,它要求开发者不仅要熟悉Linux的内核机制、驱动程序与用户级应用程序的接口关系、考虑系统中对设备的并发操作等等,而且还要非常熟悉所开发硬件的工作原理。
本小节中,笔者首先对Linux系统中的设备分类和特点进行介绍,然后介绍Linux内核中设备驱动的加载和卸载方法。
8.3.1Linux设备的分类及特点
计算机系统的硬件主要由CPU、存储器和外设组成。
随着IC制造工艺的发展,目前,芯片的集成度越来越高,往往在CPU内部就集成了存储器和外设适配器。
ARM、PowerPC、MIPS等处理器都集成了UART、I2C控制器、USB控制器、SDRAM控制器等,有的处理器还集成了片内RAM和Flash。
驱动针对的对象是存储器和外设(包括CPU内部集成的存储器和外设),而不是针对CPU核。
Linux系统中将存储器和外设分为3个基础大类:
字符设备、块设备和网络设备。
(1)字符设备
概括的讲,字符设备指那些必须以串行顺序依次进行访问的设备,如触摸屏、磁带驱动器、鼠标等。
字符设备是一种可以当作一个字节流来存取的设备,字符驱动就负责实现这种行为。
这样的驱动常常至少实现open,close,read,和write系统调用。
字符驱动很好地展现了流的抽象,它通过文件系统结点来存取,也就是说,字符设备被当作普通文件来访问。
字符设备和普通文件之间唯一的不同就是:
你可以在普通文件中移来移去,但是大部分字符设备仅仅是数据通道,你只能顺序存取。
然而,也存在看起来象数据区的字符设备,你可以在里面移来移去的访问数据。
例如,framegrabber经常这样,应用程序可以使用mmap或者lseek存取整个要求的图像。
(2)块设备
块设备是可以用任意顺序访问,以块为单位进行操作,如硬盘、软驱等。
一般来说,块设备和字符设备并没有明显的界限。
如同字符设备,块设备也是通过文件系统结点进行存取。
一个块设备是可以驻有一个文件系统的。
Linux系统中允许应用程序读写一个块设备象一个字符设备一样,它允许一次传送任意数目的字节,当然也包括一个字节。
块和字符设备的区别仅仅在内核在内部管理数据的方式上,如字符设备不经过系统的快速缓冲,而块设备经过系统的快速缓冲,并且在内核/驱动的软件接口上不同。
虽然它们之间的区别对用户是透明的,它们都使用文件系统的操作接口open()、close()、read()、write()等函数进行访问,但是它们的驱动设计存在很大的差异。
(3)网络设备
网络设备是面向数据包的接收和发送而设计的,它与字符设备、块设备不同,并不对应于文件系统中的节点。
内核与网络设备的通信和内核与字符设备、块设备的通信方式可以说是完全不同的。
任何网络事务都通过一个接口来进行,就是说,一个能够与其他主机交换数据的设备。
通常,一个接口是一个硬件设备,但是它也可能是一个纯粹的软件设备,比如环回接口,因此网络设备也可以称为网络接口。
在内核网络子系统的驱动下,网络设备负责发送和接收数据报文。
网络驱动对单个连接一无所知,它只处理报文。
既然网络设备不是一个面向流的设备,一个网络接口就不象字符设备、块设备那么容易映射到文件系统的一个结点上。
Linux提供的对网络设备的存取方式仍然是通过给它们分配一个名字,但是这个名字在文件系统中没有对应的入口,其并不用read和write等函数,而是内核调用和报文传递相关的函数来实现。
另外,TTY、I2C、USB、PCI、LCD等设备驱动本身大体可归纳入这3个基础大类,但是对于这些复杂的设备,Linux系统还定义了独特的驱动体系结构。
这里不做详细介绍,有兴趣去了解的读者朋友可以查阅相关的资料。
近年来,某些设备驱动类别也已经添加到Linux内核中,如FireWire驱动。
与内核处理USB和SCSI驱动相同的方式,内核开发者集合了类别范围内的特性,并把它们输出给驱动实现者,以避免重复工作,因此简化和加强了编写类似驱动的过程。
除了上面对设备的分类的方式之外,还有其他的划分方式,与上面的设备类型是正交的。
通常,某些类型的驱动与给定类型设备其他层的内核支持函数一起工作。
例如,你可以说USB模块,串口模块,SCSI模块等等。
每个USB设备由一个USB模块驱动,与USB子系统一起工作,但是设备自身在系统中表现为一个字符设备(比如一个USB串口),一个块设备(一个USB内存读卡器),或者一个网络设备(一个USB以太网接口)。
8.3.2不同设备的驱动设计概述
在上一小节中,我们介绍了Linux系统中的设备分类及其它们的特点,因为在本书后续的章节中主要介绍字符设备的驱动设计。
在此有必要将此三类驱动的基本设计模式简单的介绍一下,使得读者对整个设备驱动都有所了解。
上述的三类设备,除了网络设备外,字符设备与块设备都被映射到Linux文件系统的文件和目录,通过文件系统的系统调用接口open()、write()、read()、close()等函数访问。
块设备比字符设备复杂,在它上面会有一个磁盘/Flash文件系统,该文件系统对存储介质上的文件和目录进行规范化的组织。
(1)字符设备驱动
Linux字符设备驱动的核心是file_operations结构体,驱动的主体是实现其中的read()、write()、ioctl()、open()、release()等方法,这些方法将完成系统需要对设备进行的操作功能。
其结构形式如代码清单8-3-1所示:
代码清单8-3-1file_operations结构体
structfile_operationsxxx_fops=
{
.owner=THIS_MODULE,
.read=xxx_read,
.write=xxx_write,
.ioctl=xxx_ioctl,
...
};
open()方法:
该方法提供给驱动程序初始化设备的能力,从而为以后的设备操作做好准备,主要完成如下工作:
检查设备特定的错误(例如设备没准备好,或者类似的硬件错误);如果它第一次打开,初始化设备;如果需要,更新f_op指针;分配并填充要放进filp->private_data的任何数据结构等。
此外open操作一般还会递增使用计数,用以防止文件关闭前模块被卸载出内核。
release()方法:
与open方法相反,它主要是释放由open分配的filp->private_data中的所有内容;在最后一次关闭操作时关闭设备;使用计数减1等操作。
read()和write()方法:
read方法完成将数据从内核拷贝到应用程序空间,write方法相反,将数据从应用程序空间拷贝到内核。
ioctl()方法:
ioctl方法主要用于对设备进行读写之外的其他控制,比如配置设备、进入或退出某种操作模式,这些操作一般都无法通过read/write文件操作来完成。
下一章中我们将更加深入地探讨字符设备驱动程序的结构。
(2)块设备驱动
Linux块设备驱动并不直接实现file_operations成员函数,其主体变成处理实现block_device_operations成员函数以及处理上层下达的I/O请求。
block_device_operations结构体中包含了ioctl()、open()、release()方法,因为字符设备和块设备的存取方法不同,其I/O处理请求可以看作是块设备中的read()和write()方法。
块设备调用函数block_read()和block_write()来进行数据读写,这两个函数将向设备请求表中增加读写请求,以便Linux内核可以对请求顺序进行优化。
由于是对内存缓冲区而不是直接对设备进行操作的,因此很大程度上加快了读写速度。
如果内存缓冲区中没有所要读入的数据,或者需要执行写操作将数据写入设备,那么就要执行真正的数据传输。
这里介绍下处理I/O请求的典型流程,如下所示:
代码清单8-3-2块设备处理I/O请求的典型流程
staticvoidxxx_request(request_queue_t*q)
{
structrequest*req;
while((req=elv_next_request(q))!
=NULL)
{
structxxx_dev*dev=req->rq_disk->private_data;
if(!
blk_fs_request(req))//不是文件系统请求
{
printk(KERN_NOTICE"Skipnon-fsrequest\n");
end_request(req,0);//通知请求处理失败
continue;
}
xxx_transfer(dev,req->sector,req->current_nr_sectors,req->buffer,
rq_data_dir(req));//处理这个请求
end_request(req,1);//通知成功完成这个请求
}
(3)网络设备驱动
网络设备和字符设备、块设备不同,Linux系统对其有专门的处理函数和机制。
所有的Linux网络驱动程序都遵循通用的接口,设计时采用的是面向对象的方法。
把所有网络设备都抽象为一个接口对象。
由数据结构structdevice来表示网络设备在内核中的运行情况,即网络设备接口,该结构提供了对所有网络设备的操作集合。
它由以dev_base为头指针的设备链表来集中管理所有网络设备。
该设备链表中的每个元素代表一个网络设备接口。
数据结构device中有很多提供给系统访问和协议层调用的设备方法,包括提供给设备初始化和向系统注册用的init函数、打开和关闭网络设备的open和stop函数、处理数据包发送的函数hard_start_xmit,以及中断处理函数等。
一般来讲,一个网络设备最基本的方法有初始化(initialize)、发送和接收。
初始化,当把驱动程序载入系统的时候会调用此程序,主要完成检测设备、配置和初始化硬件、初始化device结构中的变量等。
设备驱动各函数是网络设备接口层net_device数据结构的具体成员,它通过hard_start_xmit()函数启动发送操作,并通过网络设备上的中断触发接收操作。
Linux下编写网络设备驱动的主体工作是完成net_device结构体的填充以及成员函数的实现,底层最核心的工作是:
发送数据包和接收数据包,接收数据包是由中断触发的。
发送数据包函数的典型结构如下:
代码清单8-3-3网络设备驱动发送数据包的典型结构
intxxx_tx(structsk_buff*skb,structnet_device*dev)
{
intlen;
char*data,shortpkt[ETH_ZLEN];
/*获得有效数据指针和长度*/
data=skb->data;
len=skb->len;
if(len { /*如果帧长小于以太帧最小长度,补0*/ memset(shortpkt,0,ETH_ZLEN); memcpy(shortpkt,skb->data,skb->len); len
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 嵌入式Linux初级实验s3c2410 嵌入式 Linux 初级 实验 s3c2410