应用笔记 4913

采用MAX44009环境光传感器,轻松控制LCD显示屏亮度

By: Ilya Veygman

摘要 : 本应用笔记主要讲述采用MAX44009环境光传感器控制便携式设备(譬如智能手机和平板电脑)背光亮度的应用。针对背光亮度调节,本文介绍了两种不同的控制方案。此外,本文还就如何获得更好的控制效果提供了相关建议,同时也提供了实现本文所述算法的源代码。

引言

环境光传感器(ALS)集成电路正越来越多地用于各种显示器和照明设备,以节省电能,改善用户体验。借助ALS解决方案,系统设计师可根据环境光强度,自动调节显示屏的亮度。因为背光照明的耗电量在系统的总耗电量中占据很大的比例,实行动态的背光亮度控制,可节省大量的电能。此外,它还能够改善用户体验,让显示屏亮度根据环境光条件自行调整到最佳状态。

系统实现需要三大部分:监测环境光强的光传感器、数据处理装置(通常是微控制器)、控制背光输入电流的执行器。

背光控制:环境光传感器

图1是实施背光控制的系统示范框图。在这套组合中,光传感器是关键的组成部分,因为它要向系统的其他模块提供环境光强信息。光传感器必须具备将光信号转换成电信号的信号转换器(譬如光电二极管或CdS光敏电阻)和信号放大和/或调节装置以及模/数转换器(ADC)。

图1. 实施背光控制的系统框图
图1. 实施背光控制的系统框图

图2所示为分立光电二极管电路,从图中可以看出,该电路需要一个或多个运算放大器:一个用于电流到电压的转换,可能还需要一级放大,提供附加增益。它还包括一些分支电路,用于供电,确保高度可靠的信号链。而在空间极其宝贵的应用中,所需元件的数量过多可能导致空间受限问题。

图2. 光电二极管电路分立设计
图2. 光电二极管电路分立设计

这里还存在一个更细微的问题。具体而言,理想情况下,应确保环境光的测量模拟了人眼对光线的响应机制。这通常借助CIE提供的视觉亮度曲线(图3)。然而,光电二极管很少能够完全模拟这种响应机制,因为它们通常具有很高的红外(IR)灵敏度。在IR强度较大的光照条件(譬如白炽灯或日光)下,这种红外灵敏度会造成错误地判断光线强度。

解决上述问题的方法之一是使用两个光电二极管:一个采用对可见光和红外光都很敏感的元件,另一个采用只对红外光敏感的元件。最终用前者的响应值减去后者的响应值,将红外干扰降至最小,获得准确的可见光响应。

这种解决方案虽然有效,却增加了分立电路的占用空间。通常还很难、甚至不可能让两个分立的光电二极管配合得足够紧密,以实现消除红外干扰的目的。如果不配备精密放大器(譬如对数放大器),动态范围可能很小。换句话说,很难利用这种组合获得可重复的结果。

图3. CIE曲线和典型的光电二极管
图3. CIE曲线和典型的光电二极管

高集成度解决方案不仅能够获得比人眼光学系统更真实的光强数据,还能够节省大量空间MAX44009等环境光传感器,可将所有信号调节和模/数转换器集成在一个小封装(2mm x 2mm UTDFN封装)内,从而在空间受限应用中有效节省电路板面积。

图4提供了MAX44009的功能框图,采用I²C通信协议,使其与微控制器的连接方式更简单,数据传输速度更快。除此之外,该解决方案的高集成特性使其能够置于柔性电缆,安装在离主电路板距离合适的位置。

图4. MAX44009功能框图
图4. MAX44009功能框图

背光控制:调节显示屏亮度

该控制方案的第二部分是调节显示屏的背光亮度。这可通过多种方式实现,具体取决于设备中的显示屏模块。有两种最简单的方式,一种是借助脉冲宽度调制(PWM)方案的直接调节方式,另一种是采用显示屏控制器的间接调节方式。

许多显示屏模块如今都配有一个集成控制器,用户可以通过向控制器发送串行命令,直接设置背光亮度。如果显示屏模块未配备集成控制器,还可安装一个简单的背光控制执行器,控制显示屏后面用于背光照明的白光LED灯的输入电流。实现这种控制的一种简单办法是:直接给LED串联一个场效应晶体管(FET),使用PWM信号快速打开、关闭FET (图5)。然而,也可以利用单一芯片(用于LED控制的MAX1698升压转换器)准确、可靠地调节(图6),请参考应用笔记3866“Low-power PWM output controls LED brightness”,获取详细信息。

图5. 简单的PMW控制电路
图5. 简单的PMW控制电路

图6. 基于MAX1698的LED亮度调节器
图6. 基于MAX1698的LED亮度调节器

背光控制:建立连接

最后一步就是在传感器和执行器之间建立连接,通过微控制器实现。有人可能首先要问:“环境光强如何映射到背光亮度?”事实上,有些文献专门介绍了相关方案。其中一种映射方式是,Microsoft®针对运行Windows® 7¹操作系统的计算机提出的。图7所示曲线是由Microsoft提供的,它可以将环境光强度映射到显示屏亮度(以全部亮度的百分比表示)。

图7. 将环境光强映射为最佳显示屏亮度的曲线示例
图7. 将环境光强映射为最佳显示屏亮度的曲线示例

这种特殊曲线可以用以下函数表示:
Eq01.
如果设备采用的是已集成亮度控制功能的LCD控制芯片,就可通过向芯片发送指令,轻松设置背光亮度。如果设备采用的是PWM直接控制亮度,则要考虑如何将比例信号映射至显示屏亮度。

在MAX1698示例中,根据其产品说明书的介绍,可以将驱动电流映射为电压。通过这个示例,我们可以假设LED电流强度几乎与其电流呈线性关系。这样,我们就可以在上述等式中乘上一个系数,计算出PWM所映射的有效电压,该电压再被映射至LED电流,最后转化成显示屏亮度。

方案实施

最好不要从一个亮度级直接跳转到另一个亮度级,而是平滑上调和下调背光亮度,确保不同亮度等级之间无缝过渡。为了达到这一目的,最好采用带有固定或不同亮度步长、可逐步调节亮度的定时中断,也可采用带有可控制LED输入电流的PWM值的定时中断,或者是能够发送到显示屏控制器的串行指令的定时中断。图8提供了这种算法的一个示例。

图8. 步进式亮度调节的算法示例
图8. 步进式亮度调节的算法示例

另一个问题是,系统响应环境光强变化的速度。我们应尽量避免过快地改变亮度等级。这是因为光强的瞬间变化(譬如一扇窗户打开或瞬间有一束光扫过)可能导致背光亮度发生不必要的变化,这往往会造成用户感觉不适。并且,较长的响应时间还有助于减少微控制器对光传感器的检测次数,从而可以释放一定的微控制器资源。

最初级的方法就是每隔一两秒钟检查一次光传感器,然后相应地调整背光亮度。更好的方法是,只有光线强度偏离特定范围一定时间后,才对背光亮度进行调节。譬如,如果正常光强是200lux,我们可能只会在光强降到180lux以下或升至220lux以上,而且持续时间超过数秒的情况下才调节亮度。幸运的是,MAX44009集成了中断引脚和阈值寄存器,可轻松实现这个目的。

附录:源代码

#define MAX44009_ADDR	0x96
// begin definition of slave addresses for MAX44009
#define INT_STATUS	0x00
#define INT_ENABLE	0x01
#define CONFIG_REG	0x02
#define HIGH_BYTE	0x03
#define LOW_BYTE	0x04
#define THRESH_HIGH	0x05
#define THRESH_LOW	0x06
#define THRESH_TIMER	0x07
// end definition of slave addresses for MAX44009
 
extern float SCALE_FACTOR;	// captures scaling factors to map from % brightness to PWM
float currentBright_pct;	// the current screen brightness, in % of maximum
float desiredBright_pct;	// the desired screen brightness, in % of maximum
float stepSize;			// the step size to use to go from the current 
				// brightness to the desired brightness
uint8 lightReadingCounter;	
 
/**
 *	Function:	SetPWMDutyCycle
 *
 *	Arguments:	uint16 dc - desired duty cycle
 *
 *	Returns:	none
 *
 *	Description:	Sets the duty cycle of a 16-bit PWM, assuming that in this 
 *			architecture, 0x0000 = 0% duty cycle
 * 			0x7FFF = 50% and 0xFFFF = 100%
**/
extern void SetPWMDutyCycle(uint16 dc);
 
/**
 *	Function:	I2C_WriteByte
 *
 *	Arguments:	uint8 slaveAddr - address of the slave device
 *			uint8 command - destination register in slave device
 *			uint8 data - data to write to the register
 *
 *	Returns:	ACK bit
 *
 *	Description:	Performs necessary functions to send one byte of data to a 
 *			specified register in a specific device on the I2C bus
**/
uint8 2C_WriteByte(uint8 slaveAddr, uint8 command, uint8 data);
 
/**
 *	Function:	I2C_ReadByte
 *
 * 	Arguments:	uint8 slaveAddr - address of the slave device
 *			uint8 command - destination register in slave device
 *			uint8 *data - pointer data to read from the register
 * 
 *	Returns:	ACK bit
 *
 *	Description:	Performs necessary functions to get one byte of data from a 
 *			specified register in a specific device	on the I2C bus
**/
uint8 I2C_ReadByte(uint8 slaveAddr, uint8 command, uint8* data);
 
/**
 *	Function:	getPctBrightFromLuxReading
 *	
 *	Arguments:	float lux - the pre-computed ambient light level
 *
 *	Returns:	The % of maximum brightness to which the backlight should be set 
 *			given the ambient light (0 to 1.0)
 *
 *	Description:	Uses a function to map the ambient light level to a backlight 
 *			brightness by using a predetermined function
**/
float getPctBrightFromLuxReading(float lux);
 
/**
 *	Function:	mapPctBrighttoPWM
 *
 *	Arguments:	float pct
 *
 *	Returns:	PWM counts needed to achieve the specified % brightness (as 
 *			determined by some scaling factors)
**/	
uint16 mapPctBrighttoPWM(float pct);
 
/**
 *	Function:	getLightLevel
 *
 *	Arguments:	n/a
 *
 *	Returns:	the ambient light level, in lux
 *
 *	Description:	Reads both the light registers on the device and returns the 
 *			computed light level
**/
float getLightLevel(void);
 
/**
 *	Function:	stepBrightness
 *
 *	Arguments:	n/a
 *
 *	Returns:	n/a
 *
 *	Description:	This function would be called by an interrupt. It looks at the 
 *			current brightness setting, then the desired brightness setting. 
 *			If there is a difference between the two, the current brightness 
 *			setting is stepped closer to its goal.
**/
void stepBrightness(void);
 
/**
 *	Function:	timerISR
 *
 *	Arguments:	n/a
 *
 *	Returns:	n/a
 *
 *	Description:	An interrupt service routine which fires every 100ms or so. This 
 *			handles all the ambient light sensor and backlight
 *			control code.
**/
void timerISR(void);
 
void main() {
	
	SetupMicro();				// some subroutine which initializes this CPU
	
	I2C_WriteByte(MAX44009_ADDR, CONFIG_REG, 0x80);	// set to run continuously
	
	lightReadingCounter = 0;
	stepSize = .01;
	currentBright_pct = 0.5;
	desiredBright_pct = 0.5;
	SetPWMDutyCycle(mapPctBrighttoPWM(currentBright_pct));
	InitializeTimerInterrupt();		// set this to fire every 100ms
	
	while(1) {
		// do whatever else you need here, the LCD control is done in interrupts
		Idle();
	}
} // main routine
 
// the point at which the function clips to 100%
#define MAXIMUM_LUX_BREAKPOINT	1254.0
float getPctBrightFromLuxReading(float lux) {
	if (lux > MAXIMUM_LUX_BREAKPOINT)
		return 1.0;
	else
		return (9.9323*log(x) + 27.059)/100.0;
} // getPctBrightFromLuxReading
 
uint16 mapPctBrighttoPWM(float pct) {
	return (uint16)(0xFFFF * pct * SCALE_FACTOR);
} // mapPctBrighttoPWM
 
float getLightLevel(void) {
	uint8* lowByte;
	uint8* highByte;
	uint8 exponent;
	uint8 mantissa;
	float result;
	
	I2C_ReadByte(MAX44009_ADDR, HIGH_BYTE, highByte);
	I2C_ReadByte(MAX44009_ADDR, LOW_BYTE, lowByte);
	
	exponent = (highByte & 0xF0) >> 4;// upper four bits of high byte register
	mantissa = (highByte & 0x0F) << 4;// lower four bits of high byte register = 
 					  // upper four bits of mantissa
	mantissa += lowByte & 0x0F; 	  // lower four bits of low byte register = 
					  // lower four bits of mantissa
	
	result = mantissa * (1 << exponent) * 0.045;
	
	return result;
} //getLightLevel
 
void stepBrightness(void) { 
	// if current is at desired, don't do anything
	if (currentBright_pct == desiredBright_pct)
		return;
	// is the current brightness above the desired brightness?
	else if (currentBright_pct > desiredBright_pct) {
		// is the difference between the two less than one step?
		if ( (currentBright_pct-stepSize) < desiredBright_pct)
			currentBright_pct = desiredBright_pct;
		else	
			currentBright_pct -= stepSize;
	} // else if
	else if (currentBright_pct < desiredBright_pct) {
		// is the difference between the two less than one step?
		if ( (currentBright_pct+stepSize) > desiredBright_pct)
			currentBright_pct = desiredBright_pct;
		else	
			currentBright_pct += stepSize;
	} // else if
	
	SetPWMDutyCycle(mapPctBrighttoPWM(currentBright_pct));
	return;
} // stepBrightness
 
void timerISR(void) {
	float lux;
	float pctDiff;
	
	stepBrightness();
	if (lightReadingCounter)
		lightReadingCounter--;
	else {
		lightReadingCounter = 20;	// 2 second delay
		lux = getLightLevel();
		desiredBright_pct = getPctBrightFromLuxReading(lux);
		pctDiff = abs(desiredBright_pct - currentBright_pct);
		stepSize = (pctDiff <= 0.01) ? 0.01:pctDiff/10;
	} // else
	
	ClearInterruptFlag();
} // timerISR


¹
下一步
EE-Mail 订阅EE-Mail,接收关于您感兴趣的新文档的自动通知。
© , Maxim Integrated Products, Inc.
The content on this webpage is protected by copyright laws of the United States and of foreign countries. For requests to copy this content, contact us.
APP 4913:
应用笔记 4913,AN4913, AN 4913, APP4913, Appnote4913, Appnote 4913