********************************LoongEmbedded********************************
作者:LoongEmbedded(kandi)
********************************LoongEmbedded********************************
1. 硬件电路设计
基于PWM来调整背光亮度的硬件设计电路如下图所示:
图1
2. 基于PWM的控制原理
我们使用S3C6410的定时器1来输出PWM信号来调整背光亮度,见PWM定时器部分的描述
图2
3. 软件实现
3.1 定时器1的时钟值的确定
1) PCLK
本设计中采用ARM主频为533MHZ,HCLK=133MHZ,PCLK=66MHZ,至于这个值的确定见时钟控制器部分。
2) PCLK时钟的第一级分频值
见图2的描述,也就是8位Prescaler 0的值的确定,见相关寄存器TCFG0的描述
图3
代码中的实现如下:
图4
图4中,我们选择定时器1的Prescaler 0的值为0x3,根据给出的公式:
图5
这样算出PCLK始终经过第一级分频之后的时钟频率为66MHZ/(3+1)=16.5MHZ。
3) PCLK时钟的第二级分频值
见图2的描述,可知每个定制器都有自己的时钟分割器,分频值为1/1、1/2、1/4、1/8、1/16或者是TCLK0作为定时器1的时钟源,图4中,我们选择的是1/8的分频系数,见相关寄存器TCFG1的描述
图6
结合图5,这样我们就可以算出定时器的时钟频率为16.5 MHZ/8=2.0625MHZ,同时可以算出定时器时钟周期为=0.4848us。
3.2 定时器控制寄存器TCON的自动重新装载位和手动更新位
下图是TCON寄存器中的相关描述
图7
在代码中的内容见图4,下面大概描述这两位的作用:
1) 自动重新装载位
当定时器1的下降寄存器的值下降到0的时候,只有使能了自动重新装载位,也即置1,TCNTB1寄存器的值才能自动重新装载到下降寄存器中,从而开始下个周期,才能输出周期新的PWM信号。
2) 手动更新位
只有手动更新位置1的时候,TCNTB1和TCMPB1的值才会装载到TCNT1和TCMP1,也即下降寄存器和比较寄存器中。但是在我们开启定时器的时候,需要对手动更新位清零,否则不会有PWM周期信号输出。
3.3 定时器1计数寄存器TCNTB1、比较寄存器TCMPB1的值和定时器控制寄存器的输出反转位
代码实现如下:
图8
我们知道
1) TCNTB1寄存器
我们知道下降计数器最初被加载的值来之于TCNTB1,而下降寄存器的值递减到0,这里是从5000递减到0的时候(每递减一次,就对应一个定时器1的一个时钟周期,也即0.4848us),会产生INTF_PWM通知CPU定时器操作完成,这时TCNTB1的值就会自动被重新加载到下降定时器中开始输出下个PWM周期时钟。所以我们可以算出此时定时器1输出的一个时钟周期时间为5000*0.4848us=2.4242ms,我们用示波器测出的PWM波形如下:
图9
从图9可知定时器1输出的PWM时钟的周期时间为2.4ms这和2.4242ms很接近,从而验证了理论值是正确的。我们同时也知道是有TCNTB1的值和定时器的时钟一起决定了输出的一个PWM时钟的周期时间的长短。
2) TCMPB1寄存器
脉冲宽度调制功能(PWM)使用TCMPB1寄存器的值,当下降计数器的值等于比较寄存器的值的时候,定时器控制逻辑会改变输出电平,所以可知比较寄存器决定了PWM输出的turn on或turn off时间,结合图8,我们通过背光调节的应用程序来调节背光,比如背光的第5格对应传递进来的dwValue=50,可以算出TCMPB1=1000+35*50=2750,这个值具体的实际意义是什么呢?在定时器1的Start/Stop位置位,并且对手动更新位清零后,就开始输出PWM时钟信后,此时输出低电平(或者高电平,由反转位来决定),从这时开始下降计数器的值就从5000开始递减,当递减到和比较寄存器的值2750相等的时候,定时器控制逻辑就会输出高电平(或是低电平),那么可以算出PWM一个周期时间内输出高电平的时间为(5000-2750)*0.4848us=1.0908ms,我们用示波器测出的波形如下图:
图10
从图10可知定时器1输出的PWM时钟的一个周期时间内输出低电平时间1.080ms,这和1.0908ms,从而验证了理论值是正确的。从而也知道了此时一个PWM周期时间2.4ms内输出低电平,也即点亮背光LED灯的时间为1.0908ms。
3) 定时器控制寄存器的输出反转位
CPU默认的情况下没有使用输出反转位,但推荐使用该位,该位的描述见图7的第10位
那么使用或者不使用反转位时,输出的PWM信号有什么差别呢?先看CPU中相关图:
图11
那么对于定时器1,如果使用反转位,那么在“2) TCMPB1寄存器”中描述的情况下,结合图11,输出的PWM波形就和图11是相反的,见下图:
图12
从图11和图12可知,从一个周期时钟来看,图11低电平的时间和图12高电平的时间都为1.080ms,相信大家可以意会了。
3.4 背光驱动等待的触发事件
背光驱动是基于事件触发的方式来出来,也就是说背光驱动的IST根据等待的事件被触发来做相应的处理,要等待的事件有下面几种
1) 注册表值被改变的事件
这里指注册表键HKEY_CURRENT_USER\ControlPanel\Backlight下面键值的改变,内容如下:
图13
2) 系统电源状态改变
指从电池供电的状态切换到用AC供电的状态,或者从AC供电切换到电池供电的状态。
3) 显示设备的通知
背光驱动通过调用RequestDeviceNotifications函数来告诉设备管理器:如果显示驱动加载或者卸载的时候,设备管理要发消息告诉我(也就是背光驱动。)
4) 退出背光驱动
在背光驱动的初始化数BKL_Init被调用的过程中,如果在分配虚拟内存,或者是创建线程,又或者是其他动作失败的时候,会释放分配的资源,这时候就会触发退出背光驱动的事件;在卸载驱动的时候,不仅要会释放分配的资源,也要触发退出背光驱动的事件
3.5 具体的软件实现
3.5.1 初始化函数BKL_Init()
下面分几部分学习这个函数体
第一部分:
图14
第二部分:
图15
3.5.2 背光驱动的线程fnBackLightThread
第一部分:
图16
接着学习这部分用到的结构体和系统的API函数
1) 消息队列结构体
typedef struct MSGQUEUEOPTIONS_OS {
DWORD dwSize; // size of the structure
DWORD dwFlags; // behavior of message queue
DWORD dwMaxMessages; // max # of msgs in queue
DWORD cbMaxMessage; // max size of msg
BOOL bReadAccess; // read access requested
} MSGQUEUEOPTIONS, FAR *LPMSGQUEUEOPTIONS, *PMSGQUEUEOPTIONS;
DwSize:此结构体所有的成员所占用的字节数。
dwFlags:描述定义的消息队列的行为,设置为MSGQUEUE_NOPRECOMMIT,表示允许根据需要(也即动态)分配队列缓冲区并且在读取消息后允许释放分配的队列缓冲区;设置为MSGQUEUE_ALLOW_BROKEN,表示允许直接读或者写操作而不管之前是否有过度或者写操作。
dwMaxMessages:在任何时候队列允许的最大消息数,如果没有指定在任何时候队列允许的最大消息数,则对此成员赋值为0.
cbMaxMessage:每个消息最大的字节数。
bReadAccess:指明队列的属性,如果为TRUE,表示可以从队列中读消息;如果为FALSE,表示可以往队列中写消息。
2) 创建一个消息队列的函数
CreateMsgQueue(LPCWSTR lpName, LPMSGQUEUEOPTIONS lpOptions);创建一个消息队列
lpName:此函数的第一个参数表示消息队列的名字.
lpOptions:指向MSGQUEUEOPTIONS结构体,主要是设置消息队列的属性。
返回值:返回指定队列的只读或只写句柄。
3) 请求电源管理器通知电源状态改变的函数
HANDLE RequestPowerNotifications(HANDLE hMsgQ,DWORD Flags);
此函数允许应用程序和驱动向电源管理器注册电源通知的事件。
hMsgQ:这是个指向消息队列的句柄,由CreateMsgQueue函数返回。
Flags:此参数的描述见下图:
图17
4) 请求设备管理器通知指定设备驱动加载或者卸载的时候的通知的函数
HANDLE RequestDeviceNotifications (const GUID *devclass, HANDLE hMsgQ, BOOL fAll);
Devclass:指向一个设备接口GUID,设置为NULL表示请求所有的设备接口的通知,但不推荐设置为NULL,因为这样会引起设备管理器额外的工作,并且在大多数情况下是没必要的,代表性的情况是一个驱动只需要预先知道它需要知道的接口就可以了。
hMsgQ:这是个指向消息队列的句柄,由CreateMsgQueue函数返回。
fAll:设置为TRUE,表示设备管理器会将本驱动关注的所有当前存在的设备的通知发送给本驱动;设置为FALSE,表示设备管理器将随后的本驱动关注的设备的通知发送给本驱动。
返回值:返回一个通知句柄。
5) GUID的结构体
typedef struct {
unsigned long Data1;
unsigned short Data2;
unsigned short Data3;
byte Data4[ 8 ];
} GUID;
ConvertStringToGuid函数根据{%08lX-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}格式把PMCLASS_DISPLAY的值TEXT("{EB91C7C9-8BF6-4a2d-9AB8-69724EED97D1}")分离出来保存在GUID结构体类型的变量中,以便告诉设备管理器背光驱动需要在什么驱动加载或卸载的时候通知背光驱动。
第二部分:
图18
在此学习CeFindFirstRegChange函数:
HANDLE CeFindFirstRegChange (
HKEY hKey,
BOOL bWatchSubtree,
DWORD dwNotifyFilter
)
此函数用于创建一个改变通知的句柄并且设置初始化的改变通知的过滤条件(由第二个参数和第三个参数决定),当指定注册表键或子键的改变吻合过滤条件时,等待此改变通知的句柄的线程会得到执行,参数详述见下图:
图19
第三部分:
图20
BOOL ReadMsgQueue(
HANDLE hMsgQ,
LPVOID lpBuffer,
DWORD cbBufferSize,
LPDWORD lpNumberOfBytesRead,
DWORD dwTimeout,
DWORD* pdwFlags
);
hMsgQ:一个打开的消息队列的句柄。
lpBuffer:指向保存所读消息的buffer,此值不能为NULL。
cbBufferSize:保存读取的消息的buffer的大小,以字节为单位,此参数不能为0。
cbBufferSize:实际保存在lpBuffer中的字节数,才参数不能为NULL。
dwTimeout:在读取操作之前的超时时间,如果设置为0,表示如果没有数据读取,读操作不会阻塞;如果设置为INFINITE,读操作会阻塞,直到数据有效或者队列状态的改变。
pdwFlags:指向DWORD变量,此变量指示消息属性的,如果值为MSGQUEUE_MSGALERT指定这是个警告的消息。
返回值:TRUE,表示读取成功,FALSE表示失败。
第四部分:
图21
背光驱动还有一个较为重要的函数BKL_IOControl来控制背光亮度,应该可以看懂,在此就不介绍了,另外就是BKL_PowerUp和BKL_PowerDown在睡眠和唤醒的时候会用到,如果系统支持睡眠和唤醒功能,那么就应该需要在这两个函数中来关闭和打开背光的显示。