嵌入式Linux驱动程序和QT.ppt_第1页
嵌入式Linux驱动程序和QT.ppt_第2页
嵌入式Linux驱动程序和QT.ppt_第3页
嵌入式Linux驱动程序和QT.ppt_第4页
嵌入式Linux驱动程序和QT.ppt_第5页
已阅读5页,还剩42页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

嵌入式Linux驱动程序开发,辽宁工程技术大学电信学院,1 嵌入式Linux的设备管理,Linux将设备分成两大类:一类是块设备,类似磁盘以记录块或扇区为单位,成块进行输入/输出的设备;另一类是字符设备,类似键盘以字符为单位,逐个进行输入/输出的设备。网路设备是介于块设备和字符设备之间的一种特殊设备。 块设备接口仅支持面向块的I/O操作,所有I/O操作都通过在内核地址空间中的I/O缓冲区进行,它可以支持随机存取的功能。文件系统通常都建立在块设备上。 字符设备接口支持面向字符的I/O操作,由于它们不经过系统的快速缓存,所以它们负责管理自己的缓冲区结构。字符设备接口只支持顺序存取的功能,一般不能进行任意长度的I/O请求,而是限制I/O请求的长度必须是设备要求的基本块长的倍数。 处理器与设备间数据交换方式 处理器与外设之间传输数据的控制方式通常有3种:查询方式、中断方式和直接内存存取(DMA)方式。 1查询方式 设备驱动程序通过设备的I/O端口空间,以及存储器空间完成数据的交换。例如,网卡一般将自己的内部寄存器映射为设备的I/O端口,而显示卡则利用大量的存储器空间作为视频信息的存储空间。利用这些地址空间,驱动程序可以向外设发送指定的操作指令。通常来讲,由于外设的操作耗时较长,因此,当处理器实际执行了操作指令之后,驱动程序可采用查询方式等待外设完成操作。 驱动程序在提交命令之后,开始查询设备的状态寄存器,当状态寄存器表明操作完成时,驱动程序可继续后续处理。查询方式的优点是硬件开销小,使用起来比较简单。但在此方式下,CPU要不断地查询外设的状态,当外设未准备好时,就只能循环等待,不能执行其他程序,这样就浪费了CPU的大量时间,降低了处理器的利用率。,2中断方式 查询方式白白浪费了大量的处理器时间,而中断方式才是多任务操作系统中最有效利用处理器的方式。当CPU进行主程序操作时,外设的数据已存入端口的数据输入寄存器,或端口的数据输出寄存器已空,此时由外设通过接口电路向CPU发出中断请求信号。CPU在满足一定条件下,暂停执行当前正在执行的主程序,转入执行相应能够进行输入/输出操作的子程序,待输入/输出操作执行完毕之后,CPU再返回并继续执行原来被中断的主程序。这样,CPU就避免了把大量时间耗费在等待、查询外设状态的操作上,使其工作效率得以大大提高。中断方式的原理示意图如下图所示。 能够向CPU发出中断请求的设备或事件称为中断源。中断源向CPU发出中断请求,若优先级别最高,则CPU在满足一定的条件时,可中断当前程序的运行,保护好被中断的主程序的断点及现场信息,然后根据中断源提供的信息,找到中断服务子程序的入口地址,转去执行新的程序段,这就是中断响应。CPU响应中断是有条件的,如内部允许中断、中断未被屏蔽、当前指令执行完等。CPU响应中断以后,就会中止当前的程序,转去执行一个中断服务子程序,以完成为相应设备的服务。 系统引入中断机制后,CPU与外设处于“并行”工作状态,便于实现信息的实时处理和系统的故障处理。,3直接访问内存(DMA)方式 利用中断,系统和设备之间可以通过设备驱动程序传送数据,但是,当传送的数据量很大时,因为中断处理上的延迟,利用中断方式的效率会大大降低。而直接内存访问(DMA)可以解决这一问题。DMA可允许设备和系统内存间在没有处理器参与的情况下传输大量数据。设备驱动程序在利用DMA之前,需要选择DMA通道并定义相关寄存器,以及数据的传输方向,即读取或写入,然后将设备设定为利用该DMA通道传输数据。设备完成设置之后,可以立即利用该DMA通道在设备和系统的内存之间传输数据,传输完毕后产生中断以便通知驱动程序进行后续处理。在利用DMA进行数据传输的同时,处理器仍然可以继续执行指令。,设备驱动程序的概念,设备驱动程序实际是处理和操作硬件控制器的软件,从本质上讲,是内核中具有最高特权级的、驻留内存的、可共享的底层硬件处理例程。驱动程序是内核的一部分,是操作系统内核与硬件设备的直接接口,驱动程序屏蔽了硬件的细节,完成以下功能: 对设备初始化和释放; 对设备进行管理,包括实时参数设置,以及提供对设备的操作接口; 读取应用程序传送给设备文件的数据或者回送应用程序请求的数据; 检测和处理设备出现的错误。 Linux操作系统将所有的设备全部看成文件,并通过文件的操作界面进行操作。对用户程序而言,设备驱动程序隐藏了设备的具体细节,对各种不同设备提供了一致的接口,一般来说,是把设备映射为一个特殊的设备文件,用户程序可以像对其他文件一样对此设备文件进行操作。这意味着: 由于每一个设备至少由文件系统的一个文件代表,因而都有一个“文件名”。 应用程序通常可以通过系统调用open()打开设备文件,建立起与目标设备的连接。 打开了代表着目标设备的文件,即建立起与设备的连接后,可以通过read()、write()、ioctl()等常规的文件操作对目标设备进行操作。 设备文件的属性由三部分信息组成:第一部分是文件的类型,第二部分是一个主设备号,第三部分是一个次设备号。其中类型和主设备号结合在一起惟一地确定了设备文件驱动程序及其界面,而次设备号则说明目标设备是同类设备中的第几个。 由于Linux 中将设备当做文件处理,所以对设备进行操作的调用格式与对文件的操作类似,主要包括open()、read()、write()、ioctl()、close()等。应用程序发出系统调用命令后,会从用户态转到核心态,通过内核将open()这样的系统调用转换成对物理设备的操作。,驱动程序结构,1自动配置和初始化子程序,用来检测所需驱动的硬件设备是否工作正常、对正常工作的设备及其相关驱动程序所需要的软件状态进行初始化。 2服务于I/O请求的子程序,该子程序称为驱动程序的上半部。这部分程序在执行时,系统仍认为与进行调用的进程属于同一个进程,只是由用户态变成了核心态,可以在其中调用sleep()等与进程运行环境有关的函数。 3中断服务程序,又称为驱动程序的下半部,由Linux系统来接收硬件中断,再由系统调用中断服务子程序。 在系统内部,I/O设备的存取通过一组固定的入口点来进行,入口点也可以理解为设备的句柄,就是对设备进行操作的基本函数。字符型设备驱动程序提供如下几个入口点: open入口点。打开设备准备I/O操作。对字符设备文件进行打开操作,都会调用设备的open入口点。open子程序必须对将要进行的I/O操作做好必要的准备工作,如清除缓冲区等。如果设备是独占的,即同一时刻只能有一个程序访问此设备,则open子程序必须设置一些标志以表示设备处于忙状态。 close入口点。关闭一个设备。当最后一次使用设备完成后,调用close子程序。独占设备必须标记设备方可再次使用。 read入口点。从设备上读数据。对于有缓冲区的I/O操作,一般是从缓冲区里读数据。对字符设备文件进行读操作将调用read子程序。 write入口点。往设备上写数据。对于有缓冲区的I/O操作,一般是把数据写入缓冲区里。对字符设备文件进行写操作将调用write子程序。 ioctl入口点。执行读、写之外的操作。 select入口点。检查设备,看数据是否可读或设备是否可用于写数据。select系统调用在检查与设备文件相关的文件描述符时使用select入口点。,struct file_operations struct module *owner; loff_t (*llseek) (struct file *, loff_t, int); ssize_t (*read) (struct file *, char *, size_t, loff_t *); ssize_t (*write) (struct file *, const char *, size_t, loff_t *); int (*readdir) (struct file *, void *, filldir_t); unsigned int (*poll) (struct file *, struct poll_table_struct *); int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); int (*mmap) (struct file *, struct vm_area_struct *); int (*open) (struct inode *, struct file *); int (*flush) (struct file *); int (*release) (struct inode *, struct file *); int (*fsync) (struct file *, struct dentry *, int datasync); int (*fasync) (int, struct file *, int); int (*lock) (struct file *, int, struct file_lock *); ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *); ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *); ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int); unsigned long (*get_unmapped_area)( struct file *, unsigned long, unsigned long, unsigned long, unsigned long ); ; lseek,移动文件指针的位置,只能用于可以随机存取的设备。 read,进行读操作,buf为存放读取结果的缓冲区,count为所要读取的数据长度。 write,进行写操作,与read类似。 select,进行选择操作。 ioctl,进行读、写以外的其他操作。 mmap,用于把设备的内容映射到地址空间,一般只有块设备驱动程序使用。 open,打开设备进行I/O操作。返回0表示成功,返回负数表示失败。 release,即close 操作。,struct file主要用于与文件系统相关的设备驱动程序,可提供关于被打开的文件的信息,定义如下: struct file struct list_head f_list; struct dentry *f_dentry; struct vfsmount *f_vfsmnt; struct file_operations *f_op; atomic_t f_count; unsigned int f_flags; mode_t f_mode; loff_t f_pos; unsigned long f_reada, f_ramax, f_raend, f_ralen, f_rawin; struct fown_struct f_owner; unsigned int f_uid, f_gid; int f_error; unsigned long f_version; /* needed for tty driver, and maybe others */ void *private_data; /* preallocated helper kiobuf to speedup O_DIRECT */ struct kiobuf *f_iobuf; long f_iobuf_lock; ; 在用户自己的驱动程序中,首先要根据驱动程序的功能,完成file_operations结构中函数的实现。不需要的函数接口可以直接在file_operations结构中初始化为NULL。file_operations中的变量会在驱动程序初始化时,注册到系统内部。每个进程对设备的操作,都会根据主次设备号,转换成对file_operations结构的访问。,2 设备驱动程序的开发过程,由于嵌入式设备由于硬件种类非常丰富,在默认的内核发布版中不一定包括所有驱动程序。所以进行嵌入式Linux系统的开发,很大的工作量是为各种设备编写驱动程序。除非系统不使用操作系统,程序直接操纵硬件。嵌入式Linux系统驱动程序开发与普通Linux开发没有区别。可以在硬件生产厂家或者Internet上寻找驱动程序,也可以根据相近的硬件驱动程序来改写,这样可以加快开发速度。实现一个嵌入式Linux设备驱动的大致流程如下。 (1)查看原理图,理解设备的工作原理。一般嵌入式处理器的生产商提供参考电路,也可以根据需要自行设计。 (2)定义设备号。设备由一个主设备号和一个次设备号来标识。主设备号惟一标识了设备类型,即设备驱动程序类型,它是块设备表或字符设备表中设备表项的索引。次设备号仅由设备驱动程序解释,区分被一个设备驱动控制下的某个独立的设备。 (3)实现初始化函数。在驱动程序中实现驱动的注册和卸载。 (4)设计所要实现的文件操作,定义file_operations结构。 (5)实现所需的文件操作调用,如read、write等。 (6)实现中断服务,并用request_irq向内核注册,中断并不是每个设备驱动所必需的。 (7)编译该驱动程序到内核中,或者用insmod命令加载模块。 (8)测试该设备,编写应用程序,对驱动程序进行测试。,模块化驱动程序设计,在探讨模块之前,有必要先看一看内核模块与应用程序之间的区别。一个应用从头到尾完成一个任务,而模块则是为以后处理某些请求而注册自己,完成这个任务后,它的“主”函数就立即中止了。 然而,内核源码仅能连接编译到内核模块中,不像应用那样有众多的支持库,内核能调用的仅是由内核开放出来的那些函数。由于没有库连接到模块中,所以源码文件不应该模块化任何常规头文件。与内核有关的所有内容都定义在目录/usr/include/linux和/usr/include/asm下的头文件中。 1内核空间和用户空间 当谈到软件时,我们通常称执行态为“内核空间”和“用户空间”,在Linux系统中,内核在最高级执行,也称为“管理员态”,在这一级任何操作都可以执行。而应用程序则执行在最低级,所谓的“用户态”,在这一级处理器禁止对硬件的直接访问和对内存的未授权访问。模块是在所谓的“内核空间”中运行的,而应用程序则是在“用户空间”中运行的。它们分别引用不同的内存映射,也就是程序代码使用不同的“地址空间”。 Linux通过系统调用和硬件中断完成从用户空间到内核空间的控制转移。执行系统调用的内核代码在进程的上下文中执行,它执行调用进程的操作而且可以访问进程地址空间的数据。但处理中断与此不同,处理中断的代码相对进程而言是异步的,而且与任何一个进程都无关。模块的作用就是扩展内核的功能,是运行在内核空间的模块化的代码。模块的某些函数作为系统调用执行,而某些函数则负责处理中断。,各个模块被分别编译并链接成一组目标文件,这些文件能被载入正在运行的内核,或从正在运行的内核中卸载。必要时内核能请求内核守护进程Kerneld对模块进行加载或卸载。根据需要动态载入模块可以保证内核达到最小,并且具有很大的灵活性。内核模块一部分保存在Kernel中,另一部分在Modules包中。在项目一开始,很多地方对设备安装、使用和改动都是通过编译进内核来实现的,对驱动程序稍微做点改动,就要重新烧写一遍内核,而且烧写内核经常容易出错,还占用资源。模块采用的则是另一种途径,内核提供一个插槽,它就像一个插件,在需要时,插入内核中使用,不需要时从内核中拔出。这一切都由一个称为Kerneld的守护进程自动处理。 2模块化的优缺点 内核模块的动态加载具有以下优点:将内核映像的尺寸保持在最小,并具有最大的灵活性。这便于检验新的内核代码,而不需要重新编译内核并重新引导。 但是,内核模块的引入也对系统性能和内存的利用有负面影响。装入的内核模块与其他内核部分一样,具有相同的访问权限,由此可见,差的内核模块会导致系统崩溃。为了使内核模块能访问所有内核资源,内核必须维护符号表,并在加载和卸载模块时修改这些符号表。由于有些模块要求利用其他模块的功能,故内核要维护模块之间的依赖性。内核必须能够在卸载模块时通知模块,并且要释放分配给模块的内存和中断等资源。内核版本和模块版本的不兼容也可能导致系统崩溃,因此,严格的版本检查是必需的。尽管内核模块的引入同时带来不少问题,但是模块机制确实是扩充内核功能的一种行之有效的方法,也是在内核级进行编程的有效途径。,设备注册和初始化,设备的驱动程序在加载的时候首先需要调用入口函数init_module(),该函数最重要的一个工作就是向内核注册该设备,对于字符设备调用register_chrdev()完成注册。register_chrdev 的定义为:int register_chrdev(unsigned int major, const char *name, struct file_ operations *fops); 其中,major是为设备驱动程序向系统申请的主设备号,如果为0,则系统为此驱动程序动态分配一个主设备号。name是设备名,fops是对各个调用的入口点说明。此函数返回0时表示成功;返回-EINVAL,表示申请的主设备号非法,主要原因是主设备号大于系统所允许的最大设备号;返回-EBUSY,表示所申请的主设备号正在被其他设备程序使用。如果动态分配主设备号成功,此函数将返回所分配的主设备号。如果register_chrdev()操作成功,设备名就会出现在/proc/dvices文件中。 Linux在/dev目录中为每个设备建立一个文件,用ls l命令列出函数返回值,若小于0,则表示注册失败;返回0或者大于0的值表示注册成功。注册以后,Linux将设备名与主、次设备号联系起来。当有对此设备名的访问时,Linux通过请求访问的设备名得到主、次设备号,然后把此访问分发到对应的设备驱动,设备驱动再根据次设备号调用不同的函数。 当设备驱动模块从Linux内核中卸载,对应的主设备号必须被释放。字符设备在cleanup_ module()函数中调用unregister_chrdev()来完成设备的注销。unregister_chrdev()的定义为:int unregister_chrdev(unsigned int major, const char *name); 此函数的参数为主设备号major和设备名name。Linux内核把name和major在内核注册的名称对比,如果不相等,卸载失败,并返回EINVAL;如果major大于最大的设备号,也返回EINVAL。,包括设备注册在内,设备驱动的初始化函数主要完成的功能是有以下5项。 (1)对驱动程序管理的硬件进行必要的初始化。 对硬件寄存器进行设置。比如,设置中断掩码,设置串口的工作方式、并口的数据方向等。 (2)初始化设备驱动相关的参数。 一般说来,每个设备都要定义一个设备变量,用以保存设备相关的参数。在这一步骤里对设备变量中的项进行初始化。 (3)在内核注册设备。 调用register_chrdev()函数来注册设备。 (4)注册中断。 如果设备需要IRQ支持,则要使用request_irq()函数注册中断。 (5)其他初始化工作。 初始化部分一般还负责给设备驱动程序申请包括内存、时钟、I/O端口等在内的系统资源,这些资源也可以在open子程序或者其他地方申请。这些资源不用时,应该释放,以利于资源的共享。 若驱动程序是内核的一部分,初始化函数则要按如下方式声明: int _init chr_driver_init(void); 其中_init是必不可少的,在系统启动时会由内核调用chr_driver_init,完成驱动程序的初始化。 当驱动程序是以模块的形式编写时,则要按照如下方式声明: int init_module(void) 当运行后面介绍的insmod命令插入模块时,会调用init_module函数完成初始化工作。,中断管理,设备驱动程序通过调用request_irq函数来申请中断,通过free_irq来释放中断。它们在linux/sched.h中的定义如下: int request_irq( unsigned int irq, void (*handler)(int irq,void dev_id,struct pt_regs *regs), unsigned long flags, const char *device, void *dev_id ); void free_irq(unsigned int irq, void *dev_id); 通常从request_irq函数返回的值为0时,表示申请成功;负值表示出现错误。 irq表示所要申请的硬件中断号。 handler为向系统登记的中断处理子程序,中断产生时由系统来调用,调用时所带参数irq为中断号,dev_id为申请时告诉系统的设备标识,regs为中断发生时寄存器内容。 device为设备名,将会出现在/proc/interrupts文件里。 flag是申请时的选项,它决定中断处理程序的一些特性,其中最重要的是决定中断处理程序是快速处理程序(flag里设置了SA_INTERRUPT)还是慢速处理程序(不设置SA_INTERRUPT)。 下面的代码将在SBC-2410X的Linux中注册外部中断2。 eint_irq = IRQ_EINT2; set_external_irq (eint_irq, EXT_FALLING_EDGE,GPIO_PULLUP_DIS); ret_val = request_irq(eint_irq,eint2_handler, “S3C2410X eint2”,0); if(ret_val 0) return ret_val; 用来打开和关闭中断的函数如下: #define cli() _asm_ _volatile_ (“cli“:) #define sli() _asm_ _volatile_ (“sli“:),加载和卸载驱动程序,1入口函数 在编写模块程序时,必须提供两个函数,一个是int init_module(),在加载此模块的时候自动调用,负责进行设备驱动程序的初始化工作。init_module()返回0,表示初始化成功,返回负数表示失败,它在内核中注册一定的功能函数。在注册之后,如果有程序访问内核模块的某个功能,内核将查表获得该功能的位置,然后调用功能函数。init_module()的任务就是为以后调用模块的函数做准备。 另一个函数是void cleanup_module(),该函数在模块被卸载时调用,负责进行设备驱动程序的清除工作。这个函数的功能是取消init_module()所做的事情,把init_module()函数在内核中注册的功能函数完全卸载,如果没有完全卸载,在此模块下次调用时,将会因为有重名的函数而导致调入失败。 在2.3版本以上的Linux内核中,提供了一种新的方法来命名这两个函数。例如,可以定义init_my()代替init_module()函数,定义exit_my()代替cleanup_module()函数,然后在源代码文件末尾使用下面的语句: module_init(init_my); module_exit(exit_my); 这样做的好处是,每个模块都可以有自己的初始化和卸载函数的函数名,多个模块在调试时不会有重名的问题。,2模块加载与卸载 虽然模块作为内核的一部分,但并未被编译到内核中,它们被分别编译和链接成目标文件。Linux中模块可以用C语言编写,用gcc命令编译成模块*.o,在命令行里加上-c的参数和“-D_KERNEL_-DMODULE”参数。然后用depmod -a 使此模块成为可加载模块。模块用insmod命令加载,用rmmod命令来卸载,这两个命令分别调用init_module()和cleanup_ module()函数,还可以用lsmod命令来查看所有已加载的模块的状态。 insmod命令可将编译好的模块调入内存。内核模块与系统中其他程序一样是已链接的目标文件,但不同的是它们被链接成可重定位映像。insmod将执行一个特权级系统调用get_kernel_sysms()函数以找到内核的输出内容,insmod修改模块对内核符号的引用后,将再次使用特权级系统调用create_module()函数来申请足够的物理内存空间,以保存新的模块。内核将为其分配一个新的module结构,以及足够的内核内存,并将新模块添加在内核模块链表的尾部,然后将新模块标记为uninitialized。 利用rmmod命令可以卸载模块。如果内核中还在使用此模块,这个模块就不能被卸载。原因是如果设备文件正被一个进程打开就卸载还在使用的内核模块,并导致对内核模块的读/写函数所在内存区域的调用。如果幸运,没有其他代码被加载到那个内存区域,将得到一个错误提示;否则,另一个内核模块被加载到同一区域,这就意味着程序跳到内核中另一个函数的中间,结果是不可预见的。,3 LED驱动程序设计,LED接口设计,S3C2440提供了多达117个可编程的通用I/O端口,可以方便地输入输出各种信号。下表为LED对应的I/O口。LED控制采用低电平有效方式,当端口电平为低时点亮LED指示灯,输出高电平时LED熄灭。与LED相连的通用I/O端口由表所示的控制寄存器配置。,. #define GPIO_CTL_BASE 0x56000000 /*IO口控制寄存器及地址*/ #define bGPIO(p) _REG(GPIO_CTL_BASE + (p) /*寄存器地址0X50000000p*/ #define GPBCON bGPIO(0x10) /*寄存器地址0X56000010*/ #define GPBDAT bGPIO(0x14) /*寄存器地址0X56000014*/ #define GPBUP bGPIO(0x18) /*寄存器地址0X56000018*/ . #define MAKE_GPIO_NUM(p, o) (p GPIO_MODE_SHIFT) #define GRAB_PULLUP(x) (x) & GPIO_PULLUP_MASK) GPIO_PULLUP_SHIFT) #define GRAB_PORT(x) (x) & GPIO_PORT_MASK) GPIO_PORT_SHIFTT) #define GRAB_OFS(x) (x) & GPIO_OFS_MASK) GPIO_OFS_SHIFT) .,/*端口参数设置宏*/ #define set_gpio_ctrl(x) ( GPCON(GRAB_PORT(x) ) #define write_gpio_reg(x, v) (GPDAT(GRAB_PORT(x) = (v),LED驱动程序代码分析,1系统资源和宏定义 #define DEVICE_NAME “leds“ /*定义led 设备的名字*/ #define LED_MAJOR 231 /*定义led 设备的主设备号*/ static unsigned long led_table = /*I/O 方式led 设备对应的硬件资源*/ GPIO_B7, GPIO_B8, GPIO_B9, GPIO_B10,; 2入口函数 模块的入口函数leds_init(),所做的工作是点亮I/O端口对应的LED,这些二极管可以作为状态指示之用。register_chrdev()完成字符设备在系统中的注册,并建立与文件系统的并联。 static int _init leds_init (void) int ret; int i; /*在内核中注册设备*/ ret = register_chrdev(LED_MAJOR, DEVICE_NAME, ,3字符设备定义 static devfs_handle_t devfs_handle; 4ioctl ioctl入口,执行读、写之外的操作,通过参数cmd设定命令参数。 static int leds_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned longarg) switch(cmd) case 0: case 1: if (arg 4) return -EINVAL; write_gpio_bit(led_tablearg, !cmd); default: return -EINVAL; 5文件系统接口定义 static struct file_operations leds_fops = owner: THIS_MODULE, ioctl: leds_ioctl, ; 6模块化 module_init(leds_init); module_exit(leds_exit); 用insmod命令加载模块时,调用module_init();用rmmod命令来卸载模块时,调用module_exit()函数。,加载运行LED驱动程序,1应用程序设计 #include #include #include #include int main(int argc, char *argv) int on; int led_no; int fd; if (argc != 3 | sscanf(argv1, “%d“, 该程序首先读取命令行的参数输入,其中参数argv1赋值给led_no,表示发光二极管的序号;argv2赋值给on。led_no的取值范围是13,on取值为0或1,0表示熄灭LED,1表示点亮LED。 参数输入后通过fd = open(“/dev/leds“, 0)打开设备文件,在保证参数输入正确和设备文件正确打开后,通过语句ioctl(fd, on, led_no)实现系统调用ioctl,并通过输入的参数控制LED。在程序的最后关闭设备句柄。,嵌入式图形用户界面开发 Qt/Embedded,GUI概述,目前的桌面机操作系统大多有着美观、操作方便、功能齐全的GUI(图形用户界面),例如KDE或者GNOME。GUI(图形用户界面)是指计算机与其使用者之间的对话接口,可以说,GUI是当今计算机技术的重大成就。它的存在为使用者提供了友好便利的界面,并大大地方便了非专业用户的使用,使得人们从繁琐的命令中解脱出来,可以通过窗口、菜单方便地进行操作。 UNIX环境下的图形视窗标准为X Window System,Linux是类UNIX系统,所以顶层运行的GUI系统是兼容X标准的XFree86系统。X标准大致可以划分X Server、Graphic Library(底层绘图函数库)、Toolkits、Window Manager等几大部分。其好处是具有可扩展性、可移植性等优点,但对于嵌入式系统而言无疑太过庞大、累赘、低效。目前流行的嵌入式GUI与X思路不同,这些GUI一般不局限于X标准,更强调系统的空间和效率。,嵌入式GUI的特点,在嵌入式系统中,GUI的地位也越来越重要,但是不同于桌面机系统,嵌入式GUI要求简单、直观、可靠、占用资源小且反应快速,以适应系统硬件资源有限的条件。另外,由于嵌入式系统硬件本身的特殊性,嵌入式GUI应具备高度可移植性与可裁减性,以适应不同的硬件条件和使用需求。 体积小; 运行时耗用系统资源小; 上层接口与硬件无关,高度可移植; 高可靠性; 在某些应用场合应具备实时性。,Qt/Embedded的特点,MiniGUI(1),提起国内的开源软件,就肯定会提到MiniGUI,它由魏永明先生和众多志愿者开发,是一个基于Linux的实时嵌入式系统的轻量级图形用户界面支持系统。 MiniGUI分为最底层的GAL层和IAL层,向上为基于标准POSIX接口中pthread库的Mini-thread架构和基于Server/Client的Mini-Lite架构。其中前者受限于thread模式对于整个系统的可靠性进程中某个thread的意外错误可能导致整个进程的崩溃,该架构应用于系统功能较为单一的场合。Mini-Lite应用于多进程的应用场合,采用多进程运行方式设计的Server/Client架构能够较好地解决各个进程之间的窗口管理、Z序剪切等问题。MiniGUI还有一种从Mini-Lite衍生出的standalone运行模式。与Lite架构不同的是,standalone模式一次只能以窗口最大化的方式显示一个窗口。这在显示屏尺寸较小的应用场合具有一定的应用意义。,MiniGUI(2),MiniGUI的IAL层技术SVGA lib、LibGGI、基于framebuffer的native图形引擎以及哑图形引擎等,对于Trolltech公司的QVFB在X Window下也有较好的支持。IAL层则支持Linux标准控制台下的GPM鼠标服务、触摸屏、标准键盘等。 MiniGUI下丰富的控件资源也是MiniGUI的特点之一。MiniGUI免费版本是1.3.3。在该版本的控件中已经添加了窗口皮肤、工具条等桌面GUI中的高级控件支持。对比其他系统,“Mini”是MiniGUI的特色,轻量、高性能和高效率的MiniGUI已经应用在电视机顶盒、实时控制系统、掌上电脑等诸多场合。,Microwindows,Microwindows Open Source Project成立的宗旨在于针对体积小的装置,建立一套先进的视窗环境,在Linux桌面上通过交叉编译可以很容易地制作出Microwindows的程序。Microwindows能够在没有任何操作系统或其他图形系统的支持下运行,它能对裸显示设备进行直接操作。这样,Microwindows就显得十分小巧,便于移植到各种硬件和软件系统上。 然而Microwindows的免费版本进展一直很慢,几乎处于停顿状态,而且至今为止,国内没有任何一家对Microwindows提供全面技术支持、服务和担保的专业公司。,常用GUI比较,Qt/Embedded架构,Qt/Embedded以原始Qt为基础,并做了许多出色的调整以适用于嵌入式环境。Qt/Embedded通过Qt API与Linux I/O设施直接交互,成为嵌入式Linux端口。 同Qt/X11相比,Qt/Embedded很省内存,因为它不需要一个X服务器或是Xlib库,它在底层抛弃了X lib,采用framebuffer作为底层图形接口。同时,将外部输入设备抽象为keyboard和mouse输入事件。,Qt/Embedded窗口系统,一个Qt/Embedded窗口系统包含了一个或多个进程,其中的一个进程可作为服务器。该服务进程会分配客户显示区域,以及产生鼠标和键盘事件。该服务进程还能够提供输入方法和一个用户接口给运行起来的客户应用程序。该服务进程其实就是一个有某些额外权限的客户进程。 客户与服务器之间的通信使用共享内存的方法实现,通信量应该保持最小。这就是Qt/Embedded库内部层次分明的处理过程。 QProcess类提供了另外一种异步的进程间通信机制。它用于启动一个外部的程序并且通过写一个标准的输入和读取外部程序的标准输出和错误码来和它们通信。,Qt/Embedded字体 (1),Qt/Embedded支持四种不同的字体格式:True Type字体(TTF),Postscript Type1字体,位图发布字体(BDF)和Qt的预呈现(Pre-rendered)字体(QPF)。Qt还可以通过增加QFontFactory的子类来支持其他字体,也可以支持以插件方式出现的反别名字体。 每个TTF或者TYPE1类型的字体首次在图形或者文本方式的环境下被使用时,这些字体的字形都会以指定的大小被预先呈现出来,呈现的结果会被缓冲。根据给定的字体尺寸(例如10或12点阵)预先呈现TTF或者TYPE1类型的字体文件并把结果以QPF的格式保存起来,这样可以节省内存和CPU的处理时间。,Qt/Embedded字体 (2),QPF文件包含了一些必要的字体,这些字体可以通过makeqpf工具取得,或者通过运行程序时加上“-savefonts”选项获取。如果应用程序中使用到的字体都是QPF格式,那么Qt/Embedded将被重新配置,并排除对TTF和TYPE1类型的字体的编译,这样就可以减少Qt/Embedded的库的大小和存储字体的空间。例如一个10点阵大小的包含所有ASCII字符的QPF字体文件的大小为1300Byte,这个文件可以直接从物理存储格式映射成为内存存储格式。 Qt/Embedded的字体通常包括Unicode字体的一部分子集,ASCII和Latin-1。,Qt/Embedded输入设备及输入法,Qt/Embedded 3.0支持几种鼠标协议:BusMouse、IntelliMouse,Microsoft和MouseMan. Qt/Embedded还支持NECVr41XX和iPAQ的触摸屏。通过从QWSMouseHandler或者Qcalibra- tedMouseHandler派生子类,开发人员可以让Qt/Embedded支持更多的客户指示设备。 对于非拉丁语系字符(例如阿拉伯,中文,希伯来和日语)的输入法,需要把它写成过滤器的方式,并改变键盘的输入。输入法的作者应该对全部的Qt API的使用有完整的认识。在一个无键盘的设备上,输入法成了惟一的输入字符的手段。Qtopia提供了4种输入方法:笔迹识别器、图形化的标准键盘、Unicode键盘和基于字典方式提取的键盘。,Qt/Embedded屏幕加速,通过子类化QScreen和QgfxRaster可以实现硬件加速,从而为屏幕操作带来好处。Troll- tech提供了Mach64和Voodoo3视频卡的硬件加速的驱动例子,同时可以按照协议编写其他的驱动程序。,Qt的支撑工具,Qt包含了许多支持嵌入式系统开发的工具,有两个最实用的工具是qmake和Qt designer(图形设计器)。 qmake是一个为编译Qt/Embedded库和应用而提供的Makefile生成器。它能够根据一个工程文件(.pro)产生不同平台下的Makefile文件。qmake支持跨平台开发和影子生成(影子生成是指当工程的源代码共享给网络上的多台机器时,每台机器编译链接这个工程的代码将在不同的子路径下完成,这样就不会覆盖别人的编译链接生成的文件。qmake还易于在不同的配置之间切换。) Qt图形设计器可以使开发者可视化地设计对话框而不需编写代码。使用Qt图形设计器的布局管理可以生成能平滑改变尺寸的对话框。 qmake和Qt图形设计器是完全集成在一起的。,Qt/Embedded信号和插槽机制 (1),信号和插槽机制是Qt的核心机制,要精通Qt编程就必须对信号和插槽有所了解。信号和插槽是一种高级接口,应用于对象之间的通信,它是Qt的核心特性,也是Qt区别于其他工具包的重要地方。信号和插槽是Qt自行定义的一种通信机制,它独立于标准的C/C+语言,必须借助一个称为moc(Meta Object Compiler)的Qt工具,该工具是一个C+预处理程序,它为高层次的事件处理自动生成所需要的附加代码。 信号与插槽机制是一种强有力的对象间通信机制,完全可以取代原始的回调和消息映射机制。在Qt中信号和插槽取代了上述这些

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论