linux驱动原理-LED驱动分析

linux驱动原理-LED驱动分析
linux驱动原理-LED驱动分析

第五章:Linux驱动介绍

5.1 驱动原理:

LINUX提供标准接口函数给底层,底层驱动按照LINUX编程规则进行驱动编写。操作系统是通过各种驱动程序来驾驭硬件设备的,它为用户屏蔽了各种各样的设备,驱动硬件是操作系统最基本的功能,并且提供统一的操作方式。设备驱动程序是内核的一部分,硬件驱动程序是操作系统最基本的组成部分,在Linux内核源程序中也占有60%以上。因此,熟悉驱动的编写是很重要的.

Linux内核中采用可加载的模块化设计(LKMs,Loadable Kernel Modules),一般情况下编译的Linux内核是支持可插入式模块的,也就是将最基本的核心代码编译在内核中,其他的代码可以编译到内核中,或者编译为内核的模块文件(在需要时动态加载)。

5.2 内核模块的主要相关命令

◆lsmod:列出当前系统中加载的模块,其中左边第一列是模块名,第二列是该模块大小,第三列

则是使用该模块的对象数目。

◆rmmod:是用于将当前模块卸载。

◆insmod和modprobe是用于加载当前模块,但insmod不会自动解决依存关系,即如果要加

载的模块引用了当前内核符号表中不存在的符号,则无法加载,也不会去查在其他尚未加载的模块中是否定义了该符号;modprobe可以根据模块间依存关系以及/etc/modules.conf文件中的内容自动加载其他有依赖关系的模块。

5.3 设备分类

Linux系统的设备分为三类:字符设备--(包含一个混杂设备)、块设备和网络设备。

字符设备通常指像普通文件或字节流一样,以字节为单位顺序读写的设备,如并口设备、虚拟控制台等。字符设备可以通过设备文件节点访问,它与普通文件之间的区别在于普通文件可以被随机访问(可以前后移动访问指针),而大多数字符设备只能提供顺序访问,因为对它们的访问不会被系统所缓存。但也有例外,例如帧缓存(framebuffer)是一个可以被随机访问的字符设备。

块设备通常指一些需要以块为单位随机读写的设备,如IDE硬盘、SCSI硬盘、光驱等。块设备也是通过文件节点来访问,它不仅可以提供随机访问,而且可以容纳文件系统(例如硬盘、闪存等)。Linux可以使用户态程序像访问字符设备一样每次进行任意字节的操作,只是在内核态内部中的管理方式和内核提供的驱动接口上不同。

$ ls –l /dev

crw-rw---- 1 root uucp 4, 64 08-30 22:58 ttyS0 /*串口设备, c表示字符设备*/ brw-r----- 1 root disk 8, 0 08-11 23:03 sda /*硬盘设备,b表示块设备*/ 5.4 设备驱动程序工作原理

模块在调用insmod命令时被加载,此时的入口点是init_module()函数,通常在该函数中完成设备的注册。同样,模块在调用rmmod命令时被卸载,此时的入口点是cleanup_module()函数,在该函数中完成设备的卸载。在设备完成注册加载之后,用户的应用程序就可以对该设备进行一定的操作,

如open()、read()、write()等,而驱动程序就是用于实现这些操作,在用户应用程序调用相应入口函数时执行相关的操作。

5.5 应用程序、库、内核、驱动程序的关系:

4层软件关系如下:

1.应用程序通过open函数打开设备文件;

2.库根据open函数执行swi中断,引起异常进入内核;

3.内核根据异常相关参数(应用程序传递的)找到相应驱动程序,并返回一文件句柄给库;

4.库根据文件句柄,触发库提供的write或ioclt函数(函数相关参数由应用程序提供)执行swi触

发异常后进入内核;

5.内核根据传递的相关参数调用驱动程序相关函数进行相关操作,如点亮led等。

5.6 LINUX驱动程序开发步骤:

1.查看原理图、数据手册,了解设备的操作方法;

2.在内核中找到相近的驱动程序,以它为模块进行开发,有时候需要从零开始;

3.实现驱动程序的初始化:比如向内核注册这个驱动程序,这样应用程序传入文件名时,内核才能找

到相应的驱动程序;

4.设计所要实现的操作,比如open、close、read、write等函数;

5.实现中断服务(中断并不是每个设备驱动所必须的)。

6.编译该驱动程序到内核中,或者用insmod命令加载;

7.测试驱动程序。

5.7 驱动程序的加载和卸载:

可以将驱动程序静态编译进内核中,也可以将它作为模块在使用时再加载。在配置内核时,如果某个配置项设为m,就表示它将会被编译成一个模块。在linux2.6内核中,模块的扩展名为.ko,可以使用insmod 命令加载,使用rmmod命令卸载,使用lsmod命令查看内核中已经加载了哪些模块。

当使用insmod加载模块时,模块的初始化函数被调用,它用来向内核注册驱动程序;当使用rmmod卸载模块时,模块的清除函数被调用。在驱动代码中,这两个函数要么取固定的名字:init_module和

5.8关键概念

5.8.1 不可剥夺型(non-preemptive kernel):(分时操作系统内核)

要求每个任务主动放弃CPU的使用权。

优点:响应中断快;几乎无须使用信号量保护共享数据,运行中的任务占有CPU,而不必担心被别的任务抢占。

缺点:响应时间,高优先级的任务已经进入就绪态,但还不能运行,要等,直到当前运行着的任务释放CPU。

5.8.2 可剥夺型内核(preemptive kernel):(实时操作系统内核)

最高优先级的任务一旦就绪,总能得到CPU的使用权。当一个运行着的任务使一个比它优先级高的任务进入就绪态时,当前任务的CPU使用权就被剥夺了,或者说被挂起了,更高优先级的任务立刻得到了CPU的使用权。如果是中断服务子程序使一个高优先级的任务进入就绪态,中断完成时,中断了的任务被挂起,优先级高的任务开始运行。可剥夺型内核使得任务级响应时间得以最优化。

5.8.3 可重入函数:

可以被一个以上的任务调用,而不必担心数据被破坏。可重入函数任何时候都可以被中断,一段时间以后又可以运行,而相应的数据不会丢失。可重入函数或者只使用局部变量,即变量保存在CPU寄存器中或堆栈中;或者使用全局变量,则要对全局变量予以保护。(一个函数被多个任务调用时,每个任务都有自己独立的栈函数存放该函数运行的中间变量)。

5.8.4 资源:

任何被任务所占用的实体都可称为资源。资源可以是输入/输出设备,也可以是一个变量,一个结构或一个数组。

5.8.5 共享资源:

可以被一个以上使用的资源叫做共享资源。为了防止数据被破坏,每个任务在与共享资源打交道时,必须独占该资源。这叫做互斥(mutual exclusion)。

5.8.6 代码的临界段

也称为临界区,指处理时不可分割的代码。一旦这部分代码开始执行,则不允许任何中断打入。为确保临界段代码的执行不被中断,在进入临界段之前必须关中断,而临界段代码执行完后,要立即开中断。

5.8.7 实时系统的特点

如果逻辑和时序出现偏差,将会引起严重后果。

有两种类型的实时系统:

1.软实时系统:系统的宗旨是使各个任务尽快的运行,而不要求限定某一任务在多长时间内完成;如

uc/os操作系统。

2.硬实时系统:各个任务不仅须执行无误,而且要做到准时。像VxWorks通过硬件完成(例如通过

定时器等方式),优先级高任务先执行,且每个任务执行时间可以指定。

大多数实时系统是两者的结合。如uc/os中可以通过中断发生来改变任务运行状态(任务切换)就是一种硬件机制来完成。

5.8.8 死锁:

也称为抱死(deadlock或deadly embrace),指两个任务无限期的相互等待对方控制着的资源。最简单的防止死锁的方法,让每个任务都:

(1)先得到全部需要的资源,再做下一步的工作;

(2)用同样的顺序申请多个资源;

(3)释放资源时,使用相反的顺序(按啥顺序得到,就按啥顺序释放,和中断进栈、出栈类似)。

内核通过定义等待超时来化解死锁-当等待时间超过了某一确定值,而信号量还是无效状态时,就会返回某种形式的出现超时错误的代码。这个出错代码告知该任务,不是得到了资源使用权,而使系统错误。

5.8.9 Linux将进程状态描述为如下五种:

TASK_RUNNING:可运行状态。处于该状态的进程能被调度执行而成为当前进程。

TASK_INTERRUPTIBLE:可中断的睡眠状态。处于该状态的进程在所需资源有效时被唤醒,也能通过信号或定时中断唤醒。

TASK_UNINTERRUPTIBLE:不可中断的睡眠状态。处于该状态的进程仅当所需资源有效时被唤醒。

TASK_ZOMBIE:僵尸状态。表示进程结束且已释放资源,但其task_struct(任务结构体)仍未释放。相当于进程已经结束,但在内核的任务表里面还有相应的结构资源。

TASK_STOPPED:暂停状态。处于该状态的进程通过其他进程的信号才能被唤醒。

如果处于暂停状态的进程被其它进程唤醒,但资源没有到位,那么就进入睡眠状态。

5.8.10 linux内核的三种调度方法:

1.SCHED_OTHER 分时调度策略;(各任务时间片平均分配,linux

2.4以前的内核)

2.SCHED_FIFO实时调度策略,先到先服务;(各任务谁优先级高,先运行谁,linux改进版本,要付费的)

3.SCHED_RR实时调度策略,时间片轮转(同优先级就可以按时间分片,不是同优先级按实时调度)(如linux2.6版本,半实时操作系统)。

实时进程将得到优先调用,实时进程根据实时优先级决定调度权值,分时进程则通过nice(优先级)和counter(个数,时间片)值决定权值,nice越小(优先级越高),counter越大,被调度的概率越大,也就是曾经使用了cpu最少的进程将会得到优先调度。

SHCED_RR和SCHED_FIFO的不同:

当采用SHCED_RR策略的进程的时间片用完,系统将重新分配时间片,并置于就绪队列尾。放在队列尾保证了所有具有相同优先级的RR任务的调度公平。

SCHED_FIFO一旦占用cpu则一直运行。一直运行直到有更高优先级任务到达或自己放弃。

如果有相同优先级的实时进程(根据优先级计算的调度权值是一样的)已经准备好,FIFO时必须等待该进程主动放弃后才可以运行这个优先级相同的任务。而RR可以让每个任务都执行一段时间。

相同点:

1)RR和FIFO都只用于实时任务。

2)创建时优先级大于0(1-99)。

3)按照可抢占优先级调度算法进行。

4)就绪态的实时任务立即抢占非实时任务。

(SCHED_FIFO是实时,一个任务在执行,相同优先级其它任务不能执行;SHCED_RR是实时,同优先级协商式,一个任务在执行,相同优先级可以同时以时间片方式执行)。

5.8.11 所有任务都采用linux分时调度策略时:

1.创建任务指定采用分时调度策略,并指定优先级nice值(-20~19)。

2.将根据每个任务的nice值确定在cpu上的执行时间(counter)。

3.如果没有等待资源,则将该任务加入到就绪队列中。

4.调度程序遍历就绪队列中的任务,通过对每个任务动态优先级的计算(counter+20-nice)结果,

选择计算结果最大的一个去运行,当这个时间片用完后(counter减至0)或者主动放弃cpu时,该任务将被放在就绪队列末尾(时间片用完)或等待队列(因等待资源而放弃cpu)中。

5.此时调度程序重复上面计算过程,转到第4步。

6.当调度程序发现所有就绪任务计算所得的权值都为不大于0时,重复第2步。

5.8.12 所有任务都采用FIFO时,

1.创建进程时指定采用FIFO,并设置实时优先级rt_priority(1-99)。

2.如果没有等待资源,则将该任务加入到就绪队列中。

3.调度程序遍历就绪队列,根据实时优先级计算调度权值(1000+rt_priority),选择权值最高的任务

使用cpu,该FIFO任务将一直占有cpu直到有优先级更高的任务就绪(即使优先级相同也不行)或者主动放弃(等待资源)。

4.调度程序发现有优先级更高的任务到达(高优先级任务可能被中断或定时器任务唤醒,再或被当前

运行的任务唤醒,等等),则调度程序立即在当前任务堆栈中保存当前cpu寄存器的所有数据,重新从高优先级任务的堆栈中加载寄存器数据到cpu,此时高优先级的任务开始运行。重复第3步。

5.如果当前任务因等待资源而主动放弃cpu使用权,则该任务将从就绪队列中删除,加入等待队列,

此时重复第3步。

5.8.13 所有任务都采用RR调度策略时

1.创建任务时指定调度参数为RR,并设置任务的实时优先级和nice值(nice值将会转换为该任务的

时间片的长度)。

2.如果没有等待资源,则将该任务加入到就绪队列中。

3.调度程序遍历就绪队列,根据实时优先级计算调度权值(1000+rt_priority),选择权值最高的任务

使用cpu。

4.如果就绪队列中的RR任务时间片为0,则会根据nice值设置该任务的时间片,同时将该任务放

入就绪队列的末尾。重复步骤3。

5.当前任务由于等待资源而主动退出cpu,则其加入等待队列中。重复步骤3。

系统中既有分时调度,又有时间片轮转调度和先进先出调度:

1)RR调度和FIFO调度的进程属于实时进程,以分时调度的进程是非实时进程。

2)当实时进程准备就绪后,如果当前cpu正在运行非实时进程,则实时进程立即抢占非实时进程。

3)RR进程和FIFO进程都采用实时优先级做为调度的权值标准,RR是FIFO的一个延伸。FIFO时,

如果两个进程的优先级一样,则这两个优先级一样的进程具体执行哪一个是由其在队列中的未知决定的,这样导致一些不公正性(优先级是一样的,为什么要让你一直运行?),如果将两个优先级一样的任务的调度策略都设为RR,则保证了这两个任务可以循环执行,保证了公平。

5.8.14 进程调度依据(系统进程调度原理)

Linux只有一个可运行队列,处于TASK_RUNNING状态的实时进程和普通进程都加入到这个可运行队列中。Linux的进程调度采用了动态优先级和权值调控的方法,既可实现上述三种调度策略,又能确保实时进程总是比普通进程优先使用CPU。举例说明:linux是半实时操作系统,进程之间运行占用时间如下分析:

1.范例1说明:

1)假设系统有1000个时钟滴答,也就是有1000个时间片;

时间片不是固定的,它是人为设定的,可以通过修改系统滴答时钟相关驱动参数来修改系统滴答时钟频率(是1秒为单位,1秒滴答时钟1000次,那么时间片就为1000,时钟节拍为1ms)。

2)假设有三个进程:A,B,C 这三个进程优先级:A>B>C;

3)三个进程运行时间片(系统分配的):

A=100 conter ;

B=50 conter;

C=200 conter。

注意:每个进程的conter数(运行分配时间片)是由系统自动分配(只要进程注册到内核就会自动分配)。

进程之前运行如下:

A进程运行一段时间后,处于休眠状态;

然后B进程开始运行一段时间,然后B处于休眠状态;

然后C进程再运行。

高优先级占用的时间比例多一些,低优先级占用的时间比例少一些。相当于A,B,C根据优先级分配一定权数(笔者假定的),然后根据每个进程分配的时间片多少再分配一定权数。及每个进程权数多少由进程优先级和时间片多少确定。

假定A,B,C三个进程因为优先级和时间片综合评估给予相应权数比例为A:B:C=5:3:2,那么

假定1000片时间片分1/10即100时间片,在100时间片中:

A先运行50时间片,然后处于休眠状态;

B再运行30时间片,然后处于休眠状态;

C再运行20时间片;然后继续进行下一个100时间片的运行,运行过程和前100时间片一样。

A先运行50时间片,然后处于休眠状态;

B再运行20时间片,然后处于休眠状态;(B到现在已经运行完)

C再运行30时间片;然后继续进行下一个100时间片的运行,运行过程和前100时间片一样。

A先运行70时间片,然后处于休眠状态;

B再运行0时间片,然后处于休眠状态;(因为B已经运行完)

C再运行30时间片;然后继续进行下一个100时间片的运行,运行过程和前100时间片一样。

A先运行30时间片,然后处于休眠状态;(A现在已经运行完)

B再运行0时间片,然后处于休眠状态;(因为B已经运行完)

C再运行70时间片;然后继续进行下一个100时间片的运行,运行过程和前100时间片一样。

A先运行0时间片,然后处于休眠状态;(因为A已经运行完)

B再运行0时间片,然后处于休眠状态;(因为B已经运行完)

C再运行50时间片;然后继续进行下一个100时间片的运行,运行过程和前100时间片一样。

然后A,B,C进程全部进入休眠状态。(注意只要每个进程执行完,就自动处于休眠状态,如果休眠时间到就进入就绪状态,又重新开始执行)。一般执行完成顺序为A,B,C。

2.范例2说明:

1)假设系统有1000个时钟滴答,也就是有1000个时间片;

2)假设有五个进程:A,B,C,D,E 这三个进程优先级:A>B=C=D>E;

3)五个进程运行时间片(系统分配的):

A=100conter;

B=50conter;

C=100conter;

D=200conter;

E=100conter。

4)假设1000时间片分1/10即100片,然后先运行A一定时间片,然后A处于休眠状态,然后再运行B,C,D(同优先级按时间片比例运行,即B运行1时间片,然后C运行2时间片,C运行4时间片),然后B,C,D休眠,然后运行E。(循环10次)。

5.8.15 描述进程的数据结构task_struct(任务结构体)

task_struct(任务结构体)中用以下几个数据作为调度依据:

struct task_struct

{

……

/*是否需要重新调度. need_resched:任务结构体内部的一个标志,标志成立,

就进入就绪状态.*/

volatile long need_resched;

/*进程当前还拥有的时间片该任务(进程)运行1个时间片就减一。*/

long counter;

long nice; /*普通进程的动态优先级,来自UNIX系统*/

unsigned long policy; /*进程调度策略*/

unsigned long rt_priority; /*实时进程的优先级*/

……

};

counter的值是动态变化的,进程运行时,每一个时钟滴答后,其值减1。当counter值为0时,表示该进程时间片已用完,该进程回到可运行队列中(就绪态),等待再次调度(下一次系统重新分配conter 时再执行)。

为确保实时进程优于普通进程,Linux采取加权处理法。在进程调度过程中,每次选取下一个运行进程时,调度程式首先给可运行队列中的每个进程赋予一个权值weight。普通进程的权值就是其counter和优先级nice的综合,而实时进程的权值是他的rt_priority的值加1000(可以修改,但一般不建议修改,选择默认),确保实时进程的权值总能大于普通进程。调度程式检查可运行队列中所有进程的权值,选取权值最大者作为下一个运行进程,确保了实时进程优先于普通进程获得CPU。

5.8.16 Linux使用内核函数goodness()对进程进行加权处理:

Static inline goodness (struct task_struct * pint this_cpu, struct mm_struct *this_mm)

{

Int weight;

Weight=-1;

/*判断如果任务的调度策略被置为SCHED_YIELD的话,则置权值为-1,返回。

系统调用SCHED_YIELD表示为“礼让”进程,其权值为最低

*/

If (p->policy & SCHED_YIELD)

goto out;

/*先对普通进程进行处理(由于多数是普通进程,这样做有利于提高系统效率),

如是“礼让”进程就退出

*/

If (p->policy==SCHED_OTHER)

{

weight=p->counter; /*返回权值为进程的counter值*/

/*如果当前进程的counter为0,则表示当前进程的时间片已用完,直接返回*/

If (! weight) //如果时间片用完就退出

Goto out;

/*如果CONFIG_SMP条件成立就执行后面语句,如果不成立#endif前不执行。*/

#Ifdef CONFIG_SMP

If (p->processor==this_cpu)

Weight+=PROC_CHANGE_PENALTY;

#Endif

/*

对进程权值进行微调,如果进程的内存空间使用当前正在运行的进程的内存空间,

则权值额外加1

*/

If (p->mm==this_mm||! p->mm)

Weight+=1;

/*将权值加上20和进程优先级nice的差。普通进程的权值主要由counter值和nice值组成*/

Weight+=20-p->nice;

Goto out;//转移到OUT处执行。

}

/*对实时进程进行处理,返回权值为rt_priority+1000,确保优先级高于普通进程*/

Weight=1000+p->rt_priority;

Out:

return weight;

}

从goodness()函数能看出,对于普通进程,其权值主要取决于剩余的时间配额和nice两个因素。nice 的规定取值范围为19~-20,只有特权用户才能把nice值设为负数,而表达式(20-p->nice)掉转方向成为1~40。所以,综合的权值在时间片尚未用完时基本上是两者之和。如果是内核进程,或其用户空间和当前进程相同,则权值将额外加1作为奖励。对于实时进程,其权值为1000+p->rt_priority,当p->counter达到0时该进程将移到队列的尾部,但其优先级仍不少于1000。可见当有实时进程就绪时,普通进程是没机会运行的。

由此能看出,通过goodness()函数,Linux从优先考虑实时进程出发,实现了多种调度策略的统一处理,其设计思想可谓非常巧妙。

5.9中断与异常

5.9.1 LINUX异常处理体系结构概述

内核的中断处理结构有很好的扩充性,并适当屏蔽了一些实现细节。但开发人员应该深入“黑匣子”了解其中的实现原理。

1.异常作用

异常,就是可以打断CPU正常运行流程的一些事情,比如外部中断、未定义的指令、试图修改只读的数据、执行SWI指令(软件中断指令)等。当这些事情发生时,CPU暂停当前的程序,先处理异常事件,然后再继续执行被中断的程序。操作系统中经常通过异常来完成一些特定的功能,这些异常如下所示(但不限于这些例子,如“中断”也是一种异常):

当CPU执行未定义的机器指令时将触发“未定义指令异常”,操作系统可以利用这个特点使用一些自定义的机器指令,它们在异常处理函数中实现。

可以将一块数据设为只读的,然后提供给多个进程共用,这样可以节省内存。当某个进程试图修改其中的数据时,将触发“数据访问中止异常”,在异常处理函数中将这块数据复制出一份可写的副本,提供给这个进程使用。

当用户程序试图读写的数据或执行的指令不在内存中时,也会触发一个“数据访问中止异常”或“指令预取中止异常”,在异常处理函数中将这些数据或指令读入内存(内存不足时还可以将不使用的数据、指令唤出内存),然后重新执行被中断的程序。这样可以节省内存、还使得操作系统可以运行这类程序:它们使用的内存远大于实际的物理内存。

当程序使用不对齐的地址访问内存时,也会触发“数据访问中止异常”,在异常程序中先使用多个对齐的地址读出数据;对于读操作,从中选取数据组合好后返回被中断的程序;对于写操作,修改其中的部分数据后再写入内存。这使得程序(特别是应用程序)不用考虑地址对齐的问题。

用户程序可以通过“swi”指令触发“swi异常”,操作系统在swi异常处理函数中实现各种系统调用。

2. linux内核对异常的设置

表5.1列出了常见的异常.

内核在start_kernel函数(源码在init/main.c中)中调用trap_init、init_IRQ两个函数来设置异常的处理函数。Init_IRQ函数分析:

中断也是一种异常,之所以把它单独提出来,是因为中断的处理与具体开发板密切相关,除一些必须、共用的中断(比如系统时钟中断、片内外设UART中断)外,必须由驱动开发者提供处理函数。内核提炼出中断处理的共性,搭建了一个非常容易扩充的中断处理体系。

Init_IRQ忽视(代码在arch/arm/kernel/irq.c中)被用来初始化中断的处理框架,设置各种中断的默认处理函数。当发生中断时,中断总入口函数arm_do_IRQ就可以调用这些函数作进一步处理。

3. LINUX中断处理体系结构

LINUX内核将所有的中断统一编号,使用一个irq_desc结构数组来描述这些中断:每个数组项对应一个中断(也有可能是一组中断,它们共用相同的中断号),里面记录了中断的名称、中断状态、中断标记(比如中断类型、是否共享中断等),并提供了中断的低层硬件访问函数(清除、屏蔽、使能中断),提供了这个中断的处理函数入口,通过它可以调用用户注册的中断处理函数。

中断的处理流程如下(前四步为中断初始化,第5步为中断注册):

1)发生中断时,CPU执行异常向量vector_irq的代码(也就是说程序自动跳入矢量中断入口);

2)在vector_irq(矢量中断入口)里面,最终会调用中断处理的总入口函数asm_do_IRQ(该函数用

来查找本次中断所在中断向量表的位置。注意:快中断不进入总入口函数,直接进入自己独立的中断入口)。

3)asm_do_IRQ根据中断号调用irq_desc(中断向量表)数组项中的handle_irq(具体中断处理函数

指针);

4)Handle_irq会使用chip(根据原芯片厂固定的中断编号)成员中的函数来设置硬件,比如清除中断、

禁止中断、重新使能中断等;

5)Handle_irq逐个调用用户在action链表中注册的处理函数即将中断处理函数指针写入action链表相

应位置中。

可见,中断体系结构的初始化:就是构造这些数据结构,比如irq_desc数组项中的handle_irq、chip 等成员;用户注册中断:就是构造action链表;用户卸载中断时就是从action链表中去除不需要的项。

小知识点:7种中断源入口地址:

1)复位中断入口地址:Reset(复位时进入)

2)未定义指令中止异常中断入口:

3)软件中断入口:swi

4) 指令预取终止异常中断入口:

5)数据访问终止异常中断入口:

6)保留(未用)入口:

7)矢量中断入口(保护串口、定时器、AD、IIC等):

8)快中断入口。

用户注册中断处理函数的过程:

用户(即驱动程序)通过request_irq(中断注册函数)函数向内核注册中断处理函数,request_irq 函数根据中断号找到irq_desc(中断编号)数组项,然后在它的action链表中添加一个表项。

卸载中断处理函数:

中断是一种很稀缺的资源,当不再使用一个设备时,应该释放它占据的中断。这通过free_irq函数来实现,它与request_irq一样,也是在kernel/irq/manage.c中定义。它的函数原型如下:void free_irq(unsigned int irq,void *dev_id)

它需要用到两个参数:irq和dev_id,它们与通过reqeust_irq注册中断函数时使用的参数一样。使用中断号irq定位action链表,再使用dev_id在action链表中找到要卸载的表项。所以,同一个中断的不同中断处理函数必须使用不同的dev_id(每个中断自己的结构体)来区分,这就要求在注册共享中断时参数dev_id必须惟一。Free_irq函数的处理过程与reqeust_irq函数相反。

根据中断号irq、dev_id从action链表中找到表项,将它移除;如果它是惟一的表项,还要调用IRQ_DESC[IRQ].CHIP->SHUTDOWN或IRQ_DESC[IRQ]:CHIP->DISABLE来关闭中断。

图5.1中断优先级仲裁器逻辑图

5.9.2 中断注册方法

在linux内核中用于申请中断的函数是request_irq(),函数原型在Kernel/irq/manage.c中定义:int request_irq(unsigned int irq,

irq_handler_t handler,

unsigned long irqflags,

const char* devname,

void * dev_id)

irq是要申请的硬件中断号;

handler是向系统注册的中断处理函数,是一个回调函数,中断发生时,系统调用这个函数,dev_id 参数将被传递给它;

irqflags是中断处理的属性,若设置了IRQF_DISABLED (老版本中的SA_INTERRUPT,本版zhon

已经不支持了),则表示中断处理程序是快速处理程序,快速处理程序被调用时屏蔽所有中断,慢速处理程序不屏蔽;若设置了IRQF_SHARED (老版本中的SA_SHIRQ),则表示多个设备共享中断,若设置了IRQF_SAMPLE_RANDOM(老版本中的SA_SAMPLE_RANDOM),表示对系统熵有贡献,对系统获取随机数有好处。(这几个flag是可以通过或的方式同时使用的);

Devname是中断名字字符串,没有特别注意的地方;

dev_id在中断共享时会用到,一般设置为这个设备的设备结构体或者NULL。devname设置中断名称,在cat /proc/interrupts中可以看到此名称。request_irq()返回0表示成功,返回-INVAL表示中断号无效或处理函数指针为NULL,返回-EBUSY表示中断已经被占用且不能共享。关于中断注册的例子,大家可在内核中搜索下request_irq。

在编写驱动的过程中,比较容易产生疑惑的地方是:

1、中断向量表在什么位置?是如何建立的?

2、从中断开始,系统是怎样执行到我自己注册的函数的?

3、中断号是如何确定的?对于硬件上有子中断的中断号如何确定?

4、中断共享是怎么回事,dev_id的作用是?

本文以2.6.26内核和S3C2440处理器为例,为大家讲解这几个问题。

5.9.3 异常向量表的建立

异常向量表不需要用户建立,Linux移植时已经建立好,只需要用户注册。

在ARM V4及V4T以后的大部分处理器中,中断向量表的位置可以有两个位置:一个是0,另一个是0xffff0000。可以通过CP15协处理器c1寄存器中V位(bit[13])控制。V和中断向量表的对应关系如下:V=0 ~0x00000000~0x0000001C

V=1 ~0xffff0000~0xffff001C

arch/arm/mm/proc-arm920.S中

.section ".text.init", #alloc, #execinstr

__arm920_setup:

...... orr r0, r0, #0x2100 @ ..1. ...1 ..11 (1)

@bit13=1 中断向量表基址为0xFFFF0000。R0的值将被付给CP15的C1.

在linux中,向量表建立的函数为:

init/main.c->start_kernel()->trap_init()

void __init trap_init(void)

{

unsigned long vectors = CONFIG_VECTORS_BASE;

……

memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);

memcpy((void *)vectors + 0x200,

__stubs_start,

__stubs_end - __stubs_start);

....

}

在2.6.26内核中CONFIG_VECTORS_BASE最初是在各个平台的配置文件中设定的,如:

arch/arm/configs/s3c2410_defconfig中。

CONFIG_VECTORS_BASE=0xffff0000

__vectors_end 至__vectors_start之间为异常向量表。位于arch/arm/kernel/entry-armv.S

.globl __vectors_start

__vectors_start:

swi SYS_ERROR0:

b vector_und + stubs_offset //复位异常:

ldr pc, .LCvswi + stubs_offset //未定义指令异常:

b vector_pabt + stubs_offset //软件中断异常:

b vector_dabt + stubs_offset //数据异常:(一般分开)

b vector_addrexcptn + stubs_offset //保留:

b vector_irq + stubs_offset //普通中断异常:

b vector_fiq + stubs_offset //快速中断异常:

.globl __vectors_end:

__vectors_end:

_stubs_end 至__stubs_start之间是异常处理的位置。也位于文件arch/arm/kernel/entry-armv.S中。vector_und、vector_pabt、vector_irq、vector_fiq都在它们中间。

stubs_offset值如下:

.equ stubs_offset, __vectors_start + 0x200 - __stubs_start

stubs_offset是如何确定的呢?(引用网络上的一段比较详细的解释)

当汇编器看到B指令后会把要跳转的标签转化为相对于当前PC的偏移量(±32M)写入指令码。从上面的代码可以看到中断向量表和stubs都发生了代码搬移,所以如果中断向量表中仍然写成 b vector_irq,那么实际执行的时候就无法跳转到搬移后的vector_irq处,因为指令码里写的是原来的偏移量,所以需要把指令码中的偏移量写成搬移后的。我们把搬移前的中断向量表中的irq入口地址记irq_PC,它在中断向量表的偏移量就是irq_PC-vectors_start, vector_irq在stubs中的偏移量是vector_irq-stubs_start,这两个偏移量在搬移前后是不变的。搬移后vectors_start在0xffff0000处,而stubs_start在0xffff0200处,所以搬移后的vector_irq相对于中断向量中的中断入口地址的偏移量就是,200+vector_irq在stubs中的偏移量再减去中断入口在向量表中的偏移量,即200+ vector_irq-stubs_start-irq_PC+vectors_start = (vector_irq-irq_PC) + vectors_start+200-stubs_start,对于括号内的值实际上就是中断向量表中写的vector_irq,减去irq_PC是由汇编器完成的,而后面的vectors_start+200-stubs_start就应该是stubs_offset,实际上在entry-armv.S中也是这样定义的。

5.9.4 中断处理过程

这一节将以S3C2410为例,描述linux-2.6.26内核中,从中断开始,中断是如何一步一步执行到我们注册函数的。

1. 首先中断来了跳到中断向量表(固定的,1级结构)arch\arm\kernel\entry-armv.S

__vectors_start:

swi SYS_ERROR0

b vector_und + stubs_offset

ldr pc, .LCvswi + stubs_offset

b vector_pabt + stubs_offset

b vector_dabt + stubs_offset

b vector_addrexcptn + stubs_offset

b vector_irq + stubs_offset

b vector_fiq + stubs_offset

.globl __vectors_end

__vectors_end:

中断发生后,跳转到 b vector_irq + stubs_offset的位置执行。注意现在的向量表的初始位置是0xffff0000。

2. 中断跳转的(中断向量表)入口位置(自己建立的,2级结构)arch\arm\kernel\entry-armv.S

.globl __stubs_start

__stubs_start:

/* Interrupt dispatcher*/

vector_stub irq, IRQ_MODE, 4 @IRQ_MODE在include\asm\ptrace.h中定义:0x12 .long __irq_usr @ 0 (USR_26 / USR_32)

.long __irq_invalid @ 1 (FIQ_26 / FIQ_32)

.long __irq_invalid @ 2 (IRQ_26 / IRQ_32)

.long __irq_svc @ 3 (SVC_26 / SVC_32)

.long __irq_invalid @ 4

.long __irq_invalid @ 5

.long __irq_invalid @ 6

.long __irq_invalid @ 7

.long __irq_invalid @ 8

.long __irq_invalid @ 9

.long __irq_invalid @ a

.long __irq_invalid @ b

.long __irq_invalid @ c

.long __irq_invalid @ d

.long __irq_invalid @ e

.long __irq_invalid @ f

上面代码中vector_stub宏的定义为:

.macro vector_stub, name, mode, correction=0

.align 5

vector_\name:

.if \correction

sub lr, lr, #\correction

.endif

@

@ Save r0, lr_ (parent PC) and spsr_

@ (parent CPSR)

@

stmia sp, {r0, lr} @ save r0, lr

mrs lr, spsr

str lr, [sp, #8] @ save spsr

@ Prepare for SVC32 mode. IRQs remain disabled.

mrs r0, cpsr

eor r0, r0, #(\mode ^ SVC_MODE)

msr spsr_cxsf, r0 @为后面进入svc模式做准备

@ the branch table must immediately follow this code

and lr, lr, #0x0f @进入中断前的mode的后4位

@#define USR_MODE 0x00000010

@#define FIQ_MODE 0x00000011

@#define IRQ_MODE 0x00000012

@#define SVC_MODE 0x00000013

@#define ABT_MODE 0x00000017

@#define UND_MODE 0x0000001b

@#define SYSTEM_MODE 0x0000001f

mov r0, sp

ldr lr, [pc, lr, lsl #2] @如果进入中断前是usr,则取出PC+4*0的内容,即__irq_usr

@如果进入中断前是svc,则取出PC+4*3的内容,即__irq_svc

movs pc, lr @ 当指令的目标寄存器是PC,且指令以S结束,则它会把@ spsr的值恢

复给cpsr branch to handler in SVC mode

.endm

.globl __stubs_start

__stubs_start:

/ * Interrupt dispatcher*/

vector_stub irq, IRQ_MODE, 4

.long __irq_usr @ 0 (USR_26 / USR_32)

.long __irq_invalid @ 1 (FIQ_26 / FIQ_32)

.long __irq_invalid @ 2 (IRQ_26 / IRQ_32)

.long __irq_svc @ 3 (SVC_26 / SVC_32)

用“irq, IRQ_MODE, 4”代替宏vector_stub中的“name, mode, correction”,找到了我们中断处理的入口位置为vector_irq(宏里面的vector_\name)。

从上面代码中的注释可以看出,根据进入中断前的工作模式不同,程序下一步将跳转到_irq_usr 、或__irq_svc等位置。我们先选择__irq_usr作为下一步跟踪的目标。

3.通过一级中断向量表找到二级向量表入口: __irq_usr的实现arch\arm\kernel\entry-armv.S

__irq_usr:

usr_entry @后面有解释

kuser_cmpxchg_check

#ifdef CONFIG_TRACE_IRQFLAGS

bl trace_hardirqs_off

#endif

get_thread_info tsk @获取当前进程的进程描述符中的成员变量thread_info

@的地址,并将该地址保存到寄存器tsk等于r9

@(在entry-header.S中定义)

#ifdef CONFIG_PREEMPT @如果定义了抢占,增加抢占数值

ldr r8, [tsk, #TI_PREEMPT] @ get preempt count

add r7, r8, #1 @ increment it

str r7, [tsk, #TI_PREEMPT]

#endif

irq_handler @中断处理,我们最关心的地方,

#ifdef CONFIG_PREEMPT

ldr r0, [tsk, #TI_PREEMPT]

str r8, [tsk, #TI_PREEMPT]

teq r0, r7

strne r0, [r0, -r0]

#endif

#ifdef CONFIG_TRACE_IRQFLAGS

bl trace_hardirqs_on

#endif

mov why, #0

b ret_to_user @中断处理完成,返回中断产生的位置,

上面代码中的usr_entry是一个宏,主要实现了将usr模式下的寄存器、中断返回地址保存到堆栈中。.macro usr_entry

sub sp, sp, #S_FRAME_SIZE @ S_FRAME_SIZE的值在arch\arm\kernel\asm-offsets.c @ 中定义DEFINE(S_FRAME_SIZE, sizeof(struct pt_regs));实际上等于72 stmib sp, {r1 - r12}

ldmia r0, {r1 - r3}

add r0, sp, #S_PC @ here for interlock avoidance

mov r4, #-1 @ "" "" "" ""

str r1, [sp]@ save the "real" r0 copied

@ from the exception stack

@ We are now ready to fill in the remaining blanks on the stack:

@ r2 - lr_, already fixed up for correct return/restart

@ r3 - spsr_

@ r4 - orig_r0 (see pt_regs definition in ptrace.h)

@ Also, separately save sp_usr and lr_usr

stmia r0, {r2 - r4}

stmdb r0, {sp, lr}^

@ Enable the alignment trap while in kernel mode

alignment_trap r0

@ Clear FP to mark the first stack frame

zero_fp

.endm

上面的这段代码主要在填充结构体pt_regs ,这里提到的struct pt_regs,在include/asm/ptrace.h 中定义。此时sp指向struct pt_regs。

struct pt_regs

{

long uregs[18];

};

#define ARM_cpsr uregs[16]

#define ARM_pc uregs[15]

#define ARM_lr uregs[14]

#define ARM_sp uregs[13]

#define ARM_ip uregs[12]

#define ARM_fp uregs[11]

#define ARM_r10 uregs[10]

#define ARM_r9 uregs[9]

#define ARM_r8 uregs[8]

#define ARM_r7 uregs[7]

#define ARM_r6 uregs[6]

#define ARM_r5 uregs[5]

#define ARM_r4 uregs[4]

#define ARM_r3 uregs[3]

#define ARM_r2 uregs[2]

#define ARM_r1 uregs[1]

#define ARM_r0 uregs[0]

#define ARM_ORIG_r0 uregs[17]

4. 查找中断源:irq_handler的实现过程,arch\arm\kernel\entry-armv.S

.macro irq_handler

get_irqnr_preamble r5, lr @在include/asm/arch-s3c2410/entry-macro.s

@中定义了宏get_irqnr_preamble为空操作,什么都不做1: get_irqnr_and_base r0, r6, r5, lr @判断中断号,通过R0返回,3.5节有实现过程

movne r1, sp @ routine called with r0 = irq number, r1 = struct pt_regs

adrne lr, 1b

bne asm_do_IRQ @进入中断处理。

……

.endm

5. 通过中断号判断中断与向量表对应关系(读取中断服务函数指针):

get_irqnr_and_base中断号判断过程,include/asm/arch-s3c2410/entry-macro.s

.macro get_irqnr_and_base, irqnr, irqstat, base, tmp

mov \base, #S3C24XX_VA_IRQ

@@ try the interrupt offset register, since it is there

ldr \irqstat, [ \base, #INTPND ]

teq \irqstat, #0

beq 1002f

ldr \irqnr, [ \base, #INTOFFSET ] @通过判断INTOFFSET寄存器得到中断位置

mov \tmp, #1

tst \irqstat, \tmp, lsl \irqnr

bne 1001f

@@ the number specified is not a valid irq, so try

@@ and work it out for ourselves

mov \irqnr, #0 @@ start here

@@ work out which irq (if any) we got

movs \tmp, \irqstat, lsl#16

addeq \irqnr, \irqnr, #16

moveq \irqstat, \irqstat, lsr#16

tst \irqstat, #0xff

addeq \irqnr, \irqnr, #8

moveq \irqstat, \irqstat, lsr#8

tst \irqstat, #0xf

addeq \irqnr, \irqnr, #4

moveq \irqstat, \irqstat, lsr#4

tst \irqstat, #0x3

addeq \irqnr, \irqnr, #2

moveq \irqstat, \irqstat, lsr#2

tst \irqstat, #0x1

addeq \irqnr, \irqnr, #1

@@ we have the value

1001:

adds \irqnr, \irqnr, #IRQ_EINT0 @加上中断号的基准数值,得到最终的中断号,注意:此时没有考虑子中断的具体情况,(子中断的问题后面会有讲解)。IRQ_EINT0在include/asm/arch- s3c2410/irqs.h中定义.从这里可以看出,中断号的具体值是有平台相关的代码决定的,和硬件中断挂起寄存器中的中断号是不等的。

1002:

@@ exit here, Z flag unset if IRQ

.endm

6. 中断实现:asm_do_IRQ实现过程,arch/arm/kernel/irq.c

asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)

{

struct pt_regs *old_regs = set_irq_regs(regs);

struct irq_desc *desc = irq_desc + irq;//根据中断号找到对应的irq_desc

/* Some hardware gives randomly wrong interrupts. Rather

than crashing, do something sensible.*/

if (irq >= NR_IRQS)

desc = &bad_irq_desc;

irq_enter(); //没做什么特别的工作,可以跳过不看

desc_handle_irq(irq, desc); // 根据中断号和desc进入中断处理

/* AT91 specific workaround */

irq_finish(irq);

irq_exit();

set_irq_regs(old_regs);

}

static inline void desc_handle_irq(unsigned int irq, struct irq_desc *desc)

{

desc->handle_irq(irq, desc); //中断处理

}

上述asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)使用了asmlinkage标识。那么这个标识的含义如何理解呢?

该符号定义在kernel/include/linux/linkage.h中,如下所示:

#include //各个具体处理器在此文件中定义asmlinkage

#ifdef __cplusplus

#define CPP_ASMLINKAGE extern "C"

#else

#define CPP_ASMLINKAGE

#endif

#ifndef asmlinkage //如果以前没有定义asmlinkage

#define asmlinkage CPP_ASMLINKAGE

#endif

对于ARM处理器的,没有定义asmlinkage,所以没有意义(不要以为参数是从堆栈传递的,对于ARM平台来说还是符合ATPCS过程调用标准,通过寄存器传递的)。

但对于X86处理器的中是这样定义的:

#define asmlinkage CPP_ASMLINKAGE __attribute__((regparm(0)))

表示函数的参数传递是通过堆栈完成的。

7. 中断返回:/arch/arm/kernel/entry-common.S

ENTRY(ret_to_user)

ret_slow_syscall:

disable_irq @ disable interrupts

ldr r1, [tsk, #TI_FLAGS]

tst r1, #_TIF_WORK_MASK

bne work_pending

no_work_pending:

/* perform architecture specific actions before user return */

arch_ret_to_user r1, lr

@ slow_restore_user_regs

ldr r1, [sp, #S_PSR] @ get calling cpsr

ldr lr, [sp, #S_PC]! @ get pc

msr spsr_cxsf, r1 @ save in spsr_svc

ldmdb sp, {r0 - lr}^ @ get calling r0 - lr

mov r0, r0

add sp, sp, #S_FRAME_SIZE - S_PC

movs pc, lr @ return & move spsr_svc into cpsr

以上主要跟踪了从中断发生到调用到对应中断号的desc->handle_irq(irq, desc)中断函数的过程。后面的章节还会继续讲解后面的内容。

5.9.5 中断处理模型

要想弄清楚desc->handle_irq(irq, desc)和我们注册的中断有什么关联,就要了解中断处理模型了。

1. 中断处理模型结构

中断处理模型如下所示:

其中NR_IRQS表示最大的中断号,在include/asm/arch/irq.h中定义。

irq_desc[]是一个指向irq_desc_t结构的数组,irq_desc_t结构是各个设备中断服务例程的描述符。Irq_desc_t结构体中的成员action指向该中断号对应的irqaction结构体链表。Irqaction结构体定义在include/linux/interrupt.h中,如下:

truct irqaction

{

irq_handler_t handler; //中断处理函数,注册时提供

unsigned long flags; //中断标志,注册时提供

cpumask_t mask; //中断掩码

const char *name; //中断名称

void *dev_id; //设备id,本文后面部分介绍中断共享时会详细说明这个参数的作用

struct irqaction *next; //如果有中断共享,则继续执行,

int irq; //中断号,注册时提供

struct proc_dir_entry *dir; //指向IRQn相关的/proc/irq/n目录的描述符

};

在注册中断号为irq的中断服务程序时,系统会根据注册参数封装相应的irqaction结构体。并把中断号为irq的irqaction结构体写入irq_desc [irq]->action。这样就把设备的中断请求号与该设备的中断服务例程irqaction联系在一起了。样当CPU接收到中断请求后,就可以根据中断号通过irq_desc []找到该设备的中断服务程序。

2.中断共享的处理模型

液晶屏驱动板原理维修代换方法

液晶屏驱动板的原理与维修代换方法 1、液晶屏驱动板的原理介绍 液晶屏驱动板常被称为A/D<模拟/数字)板,这从某种意义上反应出驱动板实现的主要功能所在。液晶屏要显示图像需要数字化过的视频信号,液晶屏驱动板正是完成从模拟信号到数字信号<或者从一种数字信号到另外一种数字信号)转换的功能模块,并同时在图像控制单元的控制下去驱动液晶屏显示图像。液晶显示器的驱动板如图1、图2所示。 图1 品牌液晶显示器采用的驱动板 图2部分液晶显示器采用的是通用驱动板 如图3所示,液晶屏驱动板上通常包含主控芯片、MCU微控制器、ROM存储器、电源模块、电源接口、VGA视频信号输入接口、OSD按键板接口、高压板接口、LVDS/TTL驱屏信号接口等部分。 液晶屏驱动板的原理框图如图4所示,从计算机主机显示卡送来的视频信

号,通过驱动板上的VGA视频信号输入接口送入驱动板的主控芯片,主控芯片根据MCU微控制器中有关液晶屏的资料控制液晶屏呈现图像。同时,MCU微控制器实现对整机的电源控制、功能操作等。因此,液晶屏驱动板又被称为液晶显示器的主板。 图3 驱动板上的芯片和接口 液晶屏驱动板损坏,可能造成无法开机、开机黑屏、白屏、花屏、纹波干扰、按键失效等故障现象,在液晶显示器故障中占有较大的比例。 液晶屏驱动板广泛采用了大规模的集成电路和贴片器件,电路元器件布局

紧凑,给查找具体元器件或跑线都造成了很大的困难。在非工厂条件下,它的可修性较小,若驱动板因为供电部分、VGA视频输入接口电路部分损坏等造成的故障,只要有电路知识我们可以轻松解决,对于那些因为MCU微控制器内部的数据损坏造成无法正常工作的驱动板,在拥有数据文件<驱动程序)的前提下,我们可以用液晶显示器编程器对MCU微控制器进行数据烧写,以修复固件损坏引起的故障。早期的驱动板,需要把MCU微控制器拆卸下来进行操作,有一定的难度。目前的驱动板已经普遍开始采用支持ISP<在线编程)的MCU微控制器,这样我们就可以通过ISP工具在线对MCU微控制器内部的数据进行烧写。比如我们使用的EP1112最新液晶显示器编程器就可以完成这样的工作。 图4 驱动板原理框图 在液晶显示器的维修工作中,当驱动板出现故障时,若液晶显示器原本就使用的是通用驱动板,就可以直接找到相应主板代换处理,当然,仍需要在其MCU中写入与液晶屏对应的驱动程序;若驱动板是品牌机主板,我们一般采用市场上常见的“通用驱动板”进行代换方法进行维修; “通用驱动板”也称“万能驱动板”。目前,市场上常见的“通用驱动板”有乐华、鼎科、凯旋、悦康等品牌,如图5所示,尽管这种“通用驱动板”所用元器件与“原装驱动板”不一致,但只要用液晶显示器编程器向“通用驱动板”写入液晶屏对应的驱动程序<购买编程器时会随机送液晶屏驱动程序光盘),再通过简单地改接线路,即可驱动不同的液晶屏,通用性很强,而且维修成本也不高,用户容易接受。

linux驱动原理-LED驱动分析

第五章:Linux驱动介绍 5.1 驱动原理: LINUX提供标准接口函数给底层,底层驱动按照LINUX编程规则进行驱动编写。操作系统是通过各种驱动程序来驾驭硬件设备的,它为用户屏蔽了各种各样的设备,驱动硬件是操作系统最基本的功能,并且提供统一的操作方式。设备驱动程序是内核的一部分,硬件驱动程序是操作系统最基本的组成部分,在Linux内核源程序中也占有60%以上。因此,熟悉驱动的编写是很重要的. Linux内核中采用可加载的模块化设计(LKMs,Loadable Kernel Modules),一般情况下编译的Linux内核是支持可插入式模块的,也就是将最基本的核心代码编译在内核中,其他的代码可以编译到内核中,或者编译为内核的模块文件(在需要时动态加载)。 5.2 内核模块的主要相关命令 ◆lsmod:列出当前系统中加载的模块,其中左边第一列是模块名,第二列是该模块大小,第三列 则是使用该模块的对象数目。 ◆rmmod:是用于将当前模块卸载。 ◆insmod和modprobe是用于加载当前模块,但insmod不会自动解决依存关系,即如果要加 载的模块引用了当前内核符号表中不存在的符号,则无法加载,也不会去查在其他尚未加载的模块中是否定义了该符号;modprobe可以根据模块间依存关系以及/etc/modules.conf文件中的内容自动加载其他有依赖关系的模块。 5.3 设备分类 Linux系统的设备分为三类:字符设备--(包含一个混杂设备)、块设备和网络设备。 字符设备通常指像普通文件或字节流一样,以字节为单位顺序读写的设备,如并口设备、虚拟控制台等。字符设备可以通过设备文件节点访问,它与普通文件之间的区别在于普通文件可以被随机访问(可以前后移动访问指针),而大多数字符设备只能提供顺序访问,因为对它们的访问不会被系统所缓存。但也有例外,例如帧缓存(framebuffer)是一个可以被随机访问的字符设备。 块设备通常指一些需要以块为单位随机读写的设备,如IDE硬盘、SCSI硬盘、光驱等。块设备也是通过文件节点来访问,它不仅可以提供随机访问,而且可以容纳文件系统(例如硬盘、闪存等)。Linux可以使用户态程序像访问字符设备一样每次进行任意字节的操作,只是在内核态内部中的管理方式和内核提供的驱动接口上不同。 $ ls –l /dev crw-rw---- 1 root uucp 4, 64 08-30 22:58 ttyS0 /*串口设备, c表示字符设备*/ brw-r----- 1 root disk 8, 0 08-11 23:03 sda /*硬盘设备,b表示块设备*/ 5.4 设备驱动程序工作原理 模块在调用insmod命令时被加载,此时的入口点是init_module()函数,通常在该函数中完成设备的注册。同样,模块在调用rmmod命令时被卸载,此时的入口点是cleanup_module()函数,在该函数中完成设备的卸载。在设备完成注册加载之后,用户的应用程序就可以对该设备进行一定的操作,

3.5寸液晶屏驱动板说明书

3.5央寸显示屏驱动板技术说明 .系统规格: 输入电源:USB接口DC5V,内置电池供电 驱动显示屏: 3.5英寸TFT显示屏320*240像素(具体型号由乙方来推荐,甲方来确认的。)USB 接口:MINI USB 接口1.1 信号输入输出接口:AV输入(指定摄像头信号)/ AV输出与摄像头同制式 充电接口:锂聚合物充电电池(3.7V ),支持给电池充电。 储存媒介:SD卡(最大容量4G ) 压缩格式:MPEG4 图像存储格式:JPEJ(640*480) 视频录制格式:ASF(320*240) 语言:英语+(任意一种语言) 工作温度:-10-70 度。 充电环境温度:0-40 度 .驱动板结构: 尺寸:105*75MM 接口:(以下接口由甲方提供结构尺寸或者模具,参考板。)

1 : SD存储卡接口; 2 :充电接口,给3.7V锂电池充电。(外接口,和手机充电接口一样) 3 :电源开关(用逻辑电平控制),电源开关与手机模式一样(常按键5秒开机),电源 开关要切断总电源,或者打开总电源。(6*6的按纽开关键,) 4 :供电接口,3.7V锂电池供电接口。(这个接口是电源座,把 3.7V的锂电池接到驱动板上,电源座子是3针,1.25,锂电池连同摄像头一起给你)。 5 : USB接口。与电脑连接,可以直接读取SD卡信息,也可给锂电池充电。 6 : AV输出口,由我CMOS摄像头输入的AV信号,可以直接连接其它显示器上的。例 如电视。(样板上已经有了) 7 : AV输入口视频/电源接口。(2.54间距,5针插头。) 由我CMOS模组提供的AV(模拟信号)。电源接口是提供我CMOS驱动板的3.3V电 源。(总电流连同LED灯80-100mA ) 8 :按键接口,数字按钮,低电平触发。(按钮我CMOS驱动板已经做好了,不需要确 定,只需要接口就可以,后一个没有器件的样板上有接口,接口按键是0电平有触发,) 线路板背面需要一个系统复位按钮,具体位置与样板相同。 长按电源按钮3-5秒开机,操作完毕后,长按3-5秒,关机。 开机显示公司商标信息,图片,开机后处于预览模式中。(商标信息随后给你) 9 : 3.5寸屏接口。(请注意液晶屏摆放位置,方向) 10 : SD卡接口,USB接口,AV输出接口,充电接口的位置以及线路板大小,厚度, 定位螺丝孔位置均参照甲方所提供的样品。

linux驱动学习笔记LED

LED驱动学习: 是一个char字符类型的驱动 //配置模式为输出端口 static unsigned int led_cfg_table [] = { S3C2410_GPB5_OUTP, S3C2410_GPB6_OUTP, S3C2410_GPB7_OUTP, S3C2410_GPB8_OUTP, }; s3c2410_gpio_cfgpin(S3C2410_GPB5, S3C2410_GPB5_OUTP); s3c2410_gpio_cfgpin(37, 0x01 << 10); 这个在\arch\arm\mach-s3c2410\include\mach\regs-gpio.h中定义 #define S3C2410_GPB5 S3C2410_GPIONO(S3C2410_GPIO_BANKB, 5) #define S3C2410_GPB5_INP (0x00 << 10) #define S3C2410_GPB5_OUTP (0x01 << 10) #define S3C2410_GPB5_nXBACK (0x02 << 10) S3C2410_GPIONO(S3C2410_GPIO_BANKB, 5) #define S3C2410_GPIONO(bank,offset) ((bank) + (offset)) #define S3C2410_GPIO_BANKA (32*0) #define S3C2410_GPIO_BANKB(32*1) static int __init dev_init(void) { int ret; int i; for (i = 0; i < 4; i++) { s3c2410_gpio_cfgpin(led_table[i], led_cfg_table[i]); s3c2410_gpio_setpin(led_table[i], 0); } 在驱动的初始化函数中经常看到,__init 前缀,这个在下面文件中定义 file:/include/linux/init.h ? /* These macros are used to mark some functions or ?* initialized data (doesn't apply to uninitialized data) ?* as `initialization' functions. The kernel can take this ?* as hint that the function is used only during the initialization ?* phase and free up used memory resources after ?* ?* Usage: ?* For functions: ?* ?* You should add __init immediately before the function name, like: ?*

液晶显示器常用通用驱动板

液晶显示器常用通用驱动板 2009-12-31 18:22 1.常用“通用驱动板”介绍 目前,市场上常见的驱动板主要有乐华、鼎科、凯旋、华升等品牌。驱动板配上不同的程序,就驱动不同的液晶面板,维修代换十分方便。常见的驱动板主要有以下几种类型: (1) 2023 B-L驱动板 2023B-L驱动板的主控芯片为RTD2023B,主要针对LVDS接口设计,实物如图1所示。 图1 2023B-L驱动板实物 该驱动板的主要特点是:支持LVDS接口液晶面板,体积较小,价格便宜。主要参数如下: 输入接口类型:VGA模拟RGB输入; 输出接口类型:LVDS; 显示模式:640×350/70Hz~1600×1200/75Hz; 即插即用:符合VESA DDC1/2B规范; 工作电压:DC 12V±1.0V,2~3A; 适用范围:适用于维修代换19in以下液晶显示器驱动板。 2023B-L驱动板上的VGA输入接口各引脚功能见表2,TXD、RXD脚一般不用。

表2 VGA插座引脚功能 2023B-L驱动板上的按键接口可以接五个按键、两个LED指示灯,各引脚功能见表3。 表3 2023B-L驱动板上的按键接口引脚功能 2023B-L驱动板上的LVDS输出接口(30脚)引脚功能见表4。 表4 2023B-L驱动板LVDS输出接口各引脚功能 2023B-L驱动板上的高压板接口引脚功能见表5。

表5 2023B-L驱动板上的高压板接口引脚功能 (2)203B-L驱动板 2023B-L主要针对TTL接口设计,其上的LVDS接口为插孔,需要重新接上插针后才能插LVDS插头。2023B-T驱动板实物如图6所示。 图6 2023B-T驱动板实物图 2023B-T驱动板体积比2023B-L稍大,价格也相对高一些,其主要参数如下: 输入接口类型:VGA模拟RGB输入; 输出接口类型:TTL; 显示模式:640×350/70Hz~1280×1024/75 Hz: 即插即用:符合VESA DDC1/2B规范; 工作电压:DC 12V±1.0V,2~3A; 适用范围:适用于维修代换20in以下液晶显示器的驱动板。 2023B-T驱动板的VCA输入接口、按键接口、LVDS输出接口、高压板接口引脚功能与前面介绍的2023B-L驱动板基本一致。

TFT LCD液晶显示器的驱动原理(一)

TFT LCD液晶显示器的驱动原理(一) 前两次跟大家介绍有关液晶显示器操作的基本原理,那是针对液晶本身的特性,与TFT LCD本身结构上的操作原理来做介绍。这次我们针对TFT LCD的整体系统面来做介绍,也就是对其驱动原理来做介绍,而其驱动原理仍然因为一些架构上差异的关系,而有所不同。首先我们来介绍由于 Cs(storage capacitor)储存电容架构不同,所形成不同驱动系统架构的原理。 Cs(storage capacitor)储存电容的架构 一般最常见的储存电容架构有两种,分别是Cs on gate与Cs on common这两种。这两种顾名思义就可以知道,它的主要差别就在于储存电容是利用gate走线或是common走线来完成的。在上一篇文章中提到,储存电容主要是为了让充好电的电压,能保持到下一次更新画面的时候之用。所以我们就必须像在CMOS的制程之中,利用不同层的走线,来形成平行板电容。而在TFT LCD的制程之中,则是利用显示电极与gate走线或是common走线,所形成的平行板电容,来制作出储存电容Cs。

图1就是这两种储存电容架构,从图中我们可以很明显的知道,Cs on gate由于不必像Cs on co mmon一样,需要增加一条额外的common走线,所以它的开口率(Aperture ratio)会比较大。而开口率的大小,是影响面板的亮度与设计的重要因素。所以现今面板的设计大多使用Cs on gate的方式。但是由于Cs on gate的方式,它的储存电容是由下一条的gate走线与显示电极之间形成的。(请见图2的Cs on gate与Cs on common的等效电路) 而gate走线,顾名思义就是接到每一个TFT的gate 端的走线,主要就是作为gate driver送出信号,来打开TFT,好让TFT对显示电极作充放电的动作。所以当下一条gate走线,送出电压要打开下一个TFT时,便会影响到储存电容上储存电压的大小。不过由于下一条gate走线打开到关闭的时间很短,(以1024×768分辨率,60Hz更新频率的面板来说.

基于linux的led驱动程序实现

基于linux的led驱动程序实现 一. 博创开发平台硬件LED的实现 博创开发平台设置了3个GPIO控制的LED和一个可直接产生外部硬件中断的按键,LED分别使用了S3C2410的GPC5,GPC6,GPC7三个GPIO,按键接到INT5中断。下面对S3C2410 GPIO的各个寄存器作出说明,用GPIO控制的LED就是通过操作GPIO的各个寄存器进行配置和操作的。S3C2410包含GPA 、GPB 、……、GPH 八个I/O端口。它们的寄存器是相似的:GPxCON 用于设置端口功能(00 表示输入、01表示输出、10 表示特殊功能、11 保留不用),GPxDAT 用于读/写数据,GPxUP 用于决定是否使用内部上拉电阻(某位为0 时,相应引脚无内部上拉;为1时,相应引脚使用内部上拉)。这里要稍微注意一点的地方是PORTA和其他几组端口的使用不太一样,这里不讨论A口,B到H组口的使用完全相同。以下是S3C2410手册上的数据[13]: 图1.1 S3C2410端口 GPC口有16个IO口,查datasheet《S3C2410》所用的地址为: 图1.2 C组GPIO的地址 即GPCCON 地址为0x56000020,GPCDAT地址为0x56000024,各位的设置具体见下图,则对应的GPCCON寄存器的位为:

图1.3 GPCCON寄存器相应的位 这里用到了5,6,7三个口,CON寄存器要完成对对应口的设置工作,将相应的口设置为输出状态,其他的口不用考虑,设置为输出的话就是0x15<<10,这样3个IO口就设置为了输出。下面就可以通过向DATA口写入低电平来点亮LED,GPCDAT的各位分布如下,每一个bit对应一个口。 图1.4 GPCDAT的位分布 GPCDAT有16位,我们这里要用到的就是5,6,7三位即将这3位设置为低电平点亮LED。具体使用情况见驱动的实现。 这三个LED的硬件原理图如下: 图1.5 GPIO控制的LED硬件原理图 二.通过GPIO控制的LED驱动程序 本驱动中没有用到内核提供的write_gpio宏,对硬件地址的操作完全自己实现,可分为以下几部分: ①模块的初始化和退出: int led_init(void)

Android驱动开发实例(控制LED灯)

Android驱动例子(LED灯控制) 本例子,讲述在Android2.1上完全自已开发一个驱动去控制硬件口并写应用测试该驱动,通过这样一个例子,解析android下的驱动开发流程的应用调用流程,可以说是很好的入门引导 要达到的效果:通过Android的应用,调用驱动程序,在开发板上控制4个LED的亮灭。 一、硬件原理 如上图,通过4个IO口控制这LED,低电平LED亮, 这4个IO口分别是GPM1, GPM2, GPM3, GPM4, 二、驱动程序 1、在kernel文件夹下的driver目录,新键驱动文件夹 # cd kernel_Android_2.6.28.6/drivers 进到开发板的kernel目录,建驱动文件夹 #mkdir ledtest 2、在/driver/ledtest目录下,新建leddriver.c ,leddriver.h , Kconfig, Makefile 等4个文件leddriver.c leddriver.c

1.#include 2.#include 3.#include 4.#include /* For __init/__exit/... */ 5.#include 6.#include 7.#include 8.#include 9.#include 10.#include 11.#include 12.#include 13.#include 14.#include 15.#include 16.#include 17.#include//for register_chrdev() 18.#include 19.#include 20.#include"leddriver.h" 21.#include /* For MODULE_ALIAS_MISCDEV 22.(WATCHDOG_MINOR) */ 23.#include /* For the watchdog specific items */ 24.#include /* For file operations */ 25.#define Viberator_MAJOR 97 //?÷éè±?o? 26.#define SCULL_NR_DEVS 4 27.#define SCULL_QUANTUM 4000 28.#define SCULL_QSET 1000 29.//---do as the GIO driver 30.#define DEVCOUNT 4 31.#define GIO_MINOR 2 /* GIO minor no. */ 32.static dev_t dev; //éê ?? μ?μ??÷ éè±? o? 33.static struct cdev *cdev_p; 34.static int openCnt; 35.//--è???±?á?------------ 36.int VIB_major = 97;//we asigment it for test 37.int VIB_minor = 0; 38.int VIB_nr_devs = SCULL_NR_DEVS; 39.int VIB_quantum = SCULL_QUANTUM; 40.int VIB_qset = SCULL_QSET; 41. 42.static struct class *vib_dev_class; 43.#define GPNCON S3C64XX_GPNCON 44.#define GPNDAT S3C64XX_GPNDAT

linux LED 驱动

以下是扬创开发板给的led例程,将对应用程序和驱动程序进行详细注释和分析,并验证! /* * LED interface driver for utu2440 * This file is subject to the terms and conditions of the GNU General Public * License. See the file "COPYING" in the main directory of this archive * for more details. * bit.lili@https://www.360docs.net/doc/0e14260980.html, 2007-6 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define LED_DRIVER "utu2440 LED Driver v1.00" static unsigned long led_table [] = { S3C2410_GPF4, S3C2410_GPF5, S3C2410_GPF6, S3C2410_GPF7, }; static int led_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg); /*ioctl(fd, on, led_number); inode 和filp 指针是对应应用程序传递的文件描述符fd 的值, 和传递给open 方法的相同参数*/

液晶驱动板规格书产品名称PCB-800099驱动板

液晶驱动板规格书 产品名称 PCB-800099驱动板文件编号 TYT20120909 供应商:深圳市天宇朗通科技有限公司 地址:深圳福田区振华路高科德电子市场42073 联系人 张先生 客户名称: 客户地址: 联系人 文件级别 公共文件 发布日期2012-09-09 1.产品说明: 本驱动板可以 1,1路VGA信号输入 2,2路A V信号输入

3,1路HDMI信号输入,且本IC支持的是HDMI1.1 4,1路倒车信号输入 5,支持宽电压输入,并可以在,5V-24V之间正常工作, 6,标准背光6PIN,接口,可外接高压板 7,驱动板集成液晶屏LED背光驱动板路, 8,标准LVDS信号输出,可支持单6,单8,双6,双8等标准的LVDS信号的液晶屏,但只支持屏供电为3.3V的液晶屏 9,标准按键板接口,并支持双色LED指示灯显示 10,支持TTL信号输出, 可支持AT070TN92, AT065TN14 AT080TN52 AT090TN12 AT090TN10 AT070TN90 AT070TN93 AT070TN94等,通用50PIN接口的TTL液晶屏 11,配合本公司编号为PCB800100的液晶屏转接板,可支持如下液晶屏 EJ070NA01-1024X600分辨率 EJ080NA04B-1024X768分辨率 ZJ070NA01,型号的通用40PIN高分液晶屏 12,配合PCB800100,还可以支持4。3,5,6,7寸,40PIN通用的屏,定义参见AT0543TN24V,1 13,本驱动板最大输出显示分辨率为,1920X1080超过,1440X900显示分辨率时,需要视IC的工作情况,增加散热片,以降低IC的工作温度) 14本IC,VGA部分可以直接输入YPBPR信号,通过程序实现 15,本驱动板可增加遥控功能(需要通过软件实现) 16,本驱动板可以自动检测,并显示相关的输入电压信息-----注,此功能为定制功能,需要联系我公司技术部 17,本驱动板可以支持自动检测信号开关机功能,--此功能为定制功能 18,本驱动板可以加BNC接口---需要定制 19,支持倒车控制,并显示A V2上,倒车电压支持50V以内的电压输入 20,本驱动板定位孔为四个, 21,如果特殊要求,我公司可以提供其它的定制服务 22,客户需要改程序,需要连接我公司,购买相关的程序下载板, 23,利用本公司的USB接口程序下载板,可以自行在BIN代码上添加LOGO

linux简单的gpio驱动实例

今天完成了嵌入式linux的第一个驱动的编写和测试,虽然是个简单的程序,但是麻雀虽小,五脏俱全,希望可以给刚开始接触驱动编写的人一些提示,共同进步。 源代码: 分析如下: 下面是我的驱动程序: #include //配置头文件 #include /*内核头文件,作为系统核心的一部分,设备驱动程序在申请和释放内存时,不是调用malloc和free,而是调用kmalloc和 kfree*/ #include //调度,进程睡眠,唤醒,中断申请,中断释放 #include //时钟头文件 #include //用户定义模块初始函数名需引用的头文件 #include //模块加载的头文件 #include #include //这个是2440的寄存器头文件,asm/srch只是个链接 //实际根据自己的情况查找,一般 是../../linux2.*.*/include/asm/arch-s3c2440里编译器 //自己会查询链接,以前不知道,找了半天 // GPIO_LED DEVICE MAJOR #define GPIO_LED_MAJOR 97 //定义主设备号 //define LED STATUS 我的板子 LED在GPB0 与GPB1 处大家根据自己情况改 #define LED_ON 0 //定义LED灯的状态开 #define LED_OFF 1 // // ------------------- READ ------------------------ 这个前面要加static 否则警告 static ssize_t GPIO_LED_read (struct file * file ,char * buf, size_t count, loff_t * f_ops) {

Linux学习之LED驱动程序简介

这里我们当然也要根据实际来思考我们的LED驱动程序。在STM32点灯的时候,一般输出低电平点灯,输出高电平灭灯。在嵌入Linux操作系统的情况下,我们自然也要想到有个写1/0的思想。类比我们上一篇的hello程序: 我们的LED程序自然要写入的数据为0/1来点亮、熄灭LED。这里我们做的实验室与硬件无关的LED实验:我们的驱动程序在收到应用程序发送过来的0时打印led on、收到1时打印led off。模仿上一篇的hello程序,我们修改得到的与硬件无关的LED程序(核心部分)如下: LED应用程序: LED驱动程序:

加载led驱动模块及运行应用程序: 与硬件有关的LED驱动 上面那一节分享的是与硬件无关的LED驱动实验,主要是为了理清LED驱动的大体思路。这里我们再加入与硬件有关的相关操作以构造与硬件有关的LED驱

动程序。我们在进行STM32的裸机编程的时候,对一些外设进行配置其实就是操作一些地址的过程,这些外设地址在芯片手册中可以看到: 这是地址映射图,这里图中只是列出的外设的边界地址,每个外设又有很多寄存器,这些寄存器的地址都是对外设基地址进行偏移得到的。同样的,对于NXP 的IMX6ULL芯片来说,也是有类似这样的地址的:

此时我们要编写Linux系统下的led驱动,涉及到硬件操作的地方操作的并不是这些地址(物理地址),而是操作系统给我们提供的地址(虚拟地址)。 操作系统根据物理地址来给我们生成一个虚拟地址,我们的led驱动操控这个地址就是间接的操控物理地址。 至于这两个地址是怎么联系起来的,里面个原理我们暂且不展开。我们从函数层面来看,内核给我们提供了ioremap 函数,这个函数可以把物理地址映射为虚拟地址。 这个函数在内核文件arch/arm/include/asm/io.h 中:void __iomem *ioremap(resource_size_t res_cookie, size_t size); ?res_cookie:要映射给的物理起始地址。 ?size:要映射的内存空间大小。 ?返回值:指向映射后的虚拟空间首地址。 与ioremap函数相对应的函数为: void iounmap (volatile void __iomem *addr) ?addr:要取消映射的虚拟地址空间首地址。 地址映射完成之后,我们可以直接通过指针来访问虚拟地址,如:

TFT_LCD液晶显示器的驱动原理详解

TFT LCD液晶显示器的驱动原理 TFT LCD液晶显示器的驱动原理(一) 我们针对TFT LCD的整体系统面来做介绍, 也就是对其驱动原理来做介绍, 而其驱动原理仍然因为一些架构上差异的关系, 而有所不同. 首先我们来介绍由于Cs(storage capacitor)储存电容架构不同, 所形成不同驱动系统架构的原理. Cs(storage capacitor)储存电容的架构 一般最常见的储存电容架构有两种, 分别是Cs on gate与Cs on common这两种. 这两种顾名思义就可以知道, 它的主要差别就在于储存电容是利用gate走线或是common走线来完成的. 在上一篇文章中, 我曾提到, 储存电容主要是为了让充好电的电压,能保持到下一次更新画面的时候之用. 所以我们就必须像在CMOS的制程之中, 利用不同层的走线, 来形成平行板电容. 而在TFT LCD的制程之中, 则是利用显示电极与gate走线或是common走线,所形成的平行板电容,来制作出储存电容Cs.

图1就是这两种储存电容架构, 从图中我们可以很明显的知道, Cs on gate由于不必像Cs on common一样, 需要增加一条额外的common走线, 所以它的开口率(Aperture ratio)会比较大. 而开口率的大小, 是影响面板的亮度与设计的重要因素. 所以现今面板的设计大多使用Cs on gate的方式. 但是由于Cs on gate的方式, 它的储存电容是由下一条的gate走线与显示电极之间形成的.(请见图2的Cs on gate与Cs on common的等效电路) 而gate走线, 顾名思义就是接到每一个TFT的gate端的走线, 主要就是作为gate driver送出信号, 来打开TFT, 好让TFT对显示电极作充放电的动作. 所以当下一条gate走线, 送出电压要打开下一个TFT时 ,便会影响到储存电容上储存电压的大小. 不过由于下一条gate走线打开到关闭的时间很短,(以1024*768分辨率, 60Hz更新频率的面板来说. 一条gate走线打开的时间约为20us, 而显示画面更新的时间约为16ms, 所以相对而言, 影响有限.) 所以当下一条gate走线关闭, 回复到原先的电压, 则Cs储存电容的电压, 也会随之恢复到正常. 这也是为什么, 大多数的储存电容设计都是采用Cs on gate 的方式的原因. 至于common走线, 我们在这边也需要顺便介绍一下. 从图2中我们可以发现, 不管您采用怎样的储存电容架构, Clc的两端都是分别接到显示电极与common. 既然液晶是充满在上下两片玻璃之间, 而显示电极与TFT都是位在同一片玻璃上, 则common电极很明显

非操作系统下lED灯控制

《非操作系统下lED灯控制》 实验报告 学生姓名: 学号: 专业班级: 指导教师: 完成时间: 实验1 非操作系统下LED灯控制实验 一.实验目的 熟悉裸板开发环境构建,掌握利用ADS开发工具或arm-linux-gcc开发工具编写裸 板系统下程序的基本步骤和方法,掌握裸板程序的基本架构,熟悉汇编设计的基本指 令和伪指令的使用方法,掌握S3C6410接口开发基本方法和步骤,并编程设计LED流 水灯程序设计。深刻体会软件控制硬件工作的基本思路和方法。 二.实验内容 实验1.1 熟悉基于嵌入式实验箱TINY6410,再了解LED灯电路; 实验1.2 在非操作系统下,编写驱动leddrv.c和应用程序main.c,完成点亮LED 灯5秒,熄灭5秒,依次重复; 实验1.3 修改上述代码,要求逐个点亮6个LED灯,然后逐个熄灭,要求每个时 延为1秒左右。

三.预备知识 C 语言、微机接口等 四.实验设备及工具(包括软件调试工具) 硬件:ARM 嵌入式开发平台、PC 机Pentium100 以上、串口线。 软件:WinXP或UBUNTU开发环境。 五.实验步骤 5.1 LED流水灯设计子实验1 2. 代码设计 (1)看懂相关硬件电路图,以LED报警灯为例进行设计 打开PDF硬件电路图,明确LED灯用到的多个GPIO及其控制器 本实验电路LED1-------GPM0 LED2-------GPM1 LED3-------GPM2 LED4-------GPM3 LED5-------GPM4 LED6-------GPM5 LED7-------GPQ0 LED8-------GPQ1 得出结论:8个LED灯使用到的硬件控制器分别为__GPM__和___GPQ__两个硬

液晶显示器高压板电路基本工作原理

液晶显示器高压板电路基本工作原理2010-06-11 10:21

高压板电路是一种DC/AC(直流/交流)变换器,它的工作过程就是开关电源工作的逆变过程。开关电源是将市电电网的交流电压转变为稳定的12V直流电压,而高压板电路正好相反,将开关电源输出的12V直流电压转变为高频(40~80kHz)的高压(600~800V)交流电。 电路主要由驱动电路(振荡电路、调制电路)、直流变换电路、Royer结构的驱动电路、保护检测电路、谐振电容、输出电流取样、CCFL等组成。在实际的高压板中,常将振荡器、调制器、保护电路集成在一起,组成一块小型集成电路,一般称为PWM控制IC。 驱动电路采用Royer结构形式。Royer结构的驱动电路也称为自激式推挽多谐振荡器,主要由功率输出管及升压变压器等组成, 、 组成一个具有亮度调整和保护功能的高压板电路。 图中的ON/OFF为振荡器启动/停止控制信号输入端,该控制信号来自驱动板(主板)微控制器(MCU)。当液晶显示器由待机状态转为正常工作状态后,MCU向振荡器送出启动工作信号(高/低电平变化信号),振荡器接收到信号后开始工作,产生频率40~80kHz的振荡信号送入调制器,在调制器内部与PWM激励脉冲信号,送往直流变换电路,使直流变 Royer L1(相当于电感)组成自激振荡电路,产生的振荡信号经功率放大和升压变压器升压耦合,输出高频交流高压,点亮背光灯管。 为了保护灯管,需要设置过电流和过电压保护电路。过电流保护检测信号从串联在背光灯管上的取样电阻R上取得,输送到驱动控制IC IC。当输出电压及背光灯管工作电流出现异常时,驱动控制IC控制调制器停止输出,从而起到保护的作用。 调节亮度时,亮度控制信号加到驱动控制IC,通过改变驱动控制IC输出的PWM脉冲的占空比,进而改变直流变换器输出的直流电压大小,也就改变了加在驱动输出管上的电压大小,即改变了自激振荡的振荡幅度,从而使升压变压器输出的信号幅度、CCFL两端的电压幅度发生变化,达到调节亮度的目的。 该电路只能驱动一只背光灯管。由于背光灯管不能并联或串联应用,所以,若需要驱动多只背光灯管,必须由相应的多个升压变压器输出电路及相适配的激励电路来驱动。

UT-S3C6410 RAM11 Linux下LED灯驱动

UT-S3C6410 ARM11 Linux 下的LED驱动 在李人东老师的要求下,让我把基于Linux下ARM的初级驱动开发流程在这里演示一遍,为了不枉费李人东老师的一片心血,和对ARM还没有入门苦苦探索的亲们,给你们开启一扇窗户,少走一些弯路,废话少说,现在开始: 一、实验环境 操作系统:ubuntu 9.0 或以上 交叉编译环境:arm-Linux-gcc 4.2.2或以上,安装在/usr/local/arm/4.2.2/ 6410板子内核源码路径在:/s3c6410/linux-2.6.28-v1.0/ 硬件平台:UT-S3C6410开发板(其他类型的开发板也可以注意配置GPIO) 注:交叉编译环境一定要装好,一般的开发板给的配套资料中都会有,安装过程也都有详细的过程,如果没有,亲,你只有自己解决了。也可以联系我(476695721@https://www.360docs.net/doc/0e14260980.html,),泪奔支持你们。 二、实验原理 控制LED是最简单的一件事情,就像学C语言时候写的“hello world”程序一样,是一个入门的程序。 首先来了解一下相关的硬件知识: UT-S3C6410LED原理图 UT-S3C6410LED外部引脚图

从上面的原理图可以得知,LED与CPU引脚的连接方法如下,高电平点亮。 LED1 -GPM0 LED2 -GPM1 LED3 -GPM2 LED4 -GPM3 从数据手册可以找到相应的控制方法。这里我们以LED1为例,介绍一下LED1的操作方法,其他的类似,请大家自行分析。

通过上面可以得知,需要先将GPM0设置为输出方式。将寄存器GPMCON低四位配置成0001。 然后将GPMDAT寄存器的第0位置1灯亮,置LED0灯亮,开发板上有四个LED所以要对GPMDAT的低四位进行操作,就可以实现对灯的亮灭操作了。 三、实验步骤 1、编写驱动程序 driver_led.c #include #include #include #include /* copy_to_user,copy_from_user */ #include #include #include #include #include #include

3.5寸液晶屏驱动板说明书

3.5 英寸显示屏驱动板技术说明 一.系统规格: 输入电源:USB接口DC5V,内置电池供电 驱动显示屏:3.5英寸TFT显示屏320*240像素(具体型号由乙方来推荐,甲方来确认的。) USB 接口:MINI USB接口1.1 信号输入输出接口:AV 输入(指定摄像头信号)/ AV 输出与摄像头同制式 充电接口:锂聚合物充电电池(3.7V),支持给电池充电。 储存媒介:SD卡(最大容量4G) 压缩格式:MPEG4 图像存储格式:JPEJ(640*480) 视频录制格式:ASF(320*240) 语言:英语+(任意一种语言) 工作温度:-10-70度。 充电环境温度:0-40度 二.驱动板结构: 尺寸:105*75MM 接口:(以下接口由甲方提供结构尺寸或者模具,参考板。) 1:SD存储卡接口;

2:充电接口,给3.7V锂电池充电。(外接口,和手机充电接口一样) 3:电源开关(用逻辑电平控制),电源开关与手机模式一样(常按键5秒开机),电源开关要切断总电源,或者打开总电源。(6*6的按纽开关键,) 4:供电接口,3.7V锂电池供电接口。(这个接口是电源座,把3.7V的锂电池接到驱动板上,电源座子是3针,1.25,锂电池连同摄像头一起给你)。 5:USB接口。与电脑连接,可以直接读取SD卡信息,也可给锂电池充电。 6:AV输出口,由我CMOS摄像头输入的AV信号,可以直接连接其它显示器上的。例如电视。(样板上已经有了) 7:AV输入口视频/电源接口。(2.54间距,5针插头。) 由我CMOS模组提供的AV(模拟信号)。电源接口是提供我CMOS驱动板的3.3V电源。(总电流连同LED灯80-100mA) 8:按键接口,数字按钮,低电平触发。(按钮我CMOS驱动板已经做好了,不需要确定,只需要接口就可以,后一个没有器件的样板上有接口,接口按键是0电平有触发,) 线路板背面需要一个系统复位按钮,具体位置与样板相同。 长按电源按钮3-5秒开机,操作完毕后,长按3-5秒,关机。 开机显示公司商标信息,图片,开机后处于预览模式中。(商标信息随后给你) 9:3.5寸屏接口。(请注意液晶屏摆放位置,方向) 10:SD卡接口,USB接口,AV输出接口,充电接口的位置以及线路板大小,厚度,定位螺丝孔位置均参照甲方所提供的样品。 三.菜单说明(菜单可以稍作修改)

相关文档
最新文档