LCD(五)Backlight背光子系统

2023-11-15

一、Backlight背光子系统概述

LCD的背光原理主要是由核心板的一根引脚控制背光电源,一根PWM引脚控制背光亮度组成,应用程序可以通过改变PWM的频率达到改变背光亮度的目的。

                                        Backlight背光子系统构建过程结构关系图

            黑色加粗部分为开发人员需要填充的部分,其中pwm_id:第几个定时器来做pwm;max_brightness背光调节范围的最大值;dft_brightness:默认背光的当前值;pwm_period_ns:定时器周期;update_status()更新背光驱动;get_brightness()获得当前背光值。

二、PWM核心驱动

代码/arch/arm/plat-samsung/pwm.c

内核中需要使能“PWM device support”

System Type -->
    [*]PWM device support
它是 pwm核心驱动,该驱动把设备和驱动没有分离开来,都写在了这个 pwm.c中,我们先看看 pwm.c中的驱动部分
static int __init pwm_init(void)
{
	int ret;

	clk_scaler[0] = clk_get(NULL, "pwm-scaler0");//获取0号时钟
	clk_scaler[1] = clk_get(NULL, "pwm-scaler1");//获取1号时钟

	if (IS_ERR(clk_scaler[0]) || IS_ERR(clk_scaler[1])) {
		printk(KERN_ERR "%s: failed to get scaler clocks\n", __func__);
		return -EINVAL;
	}

	ret = platform_driver_register(&s3c_pwm_driver);//注册pwm驱动
	if (ret)
		printk(KERN_ERR "%s: failed to add pwm driver\n", __func__);

	return ret;
}
//s3c_pwm_driver定义
static struct platform_driver s3c_pwm_driver = {
	.driver		= {
		.name	= "s3c24xx-pwm",//驱动名
		.owner	= THIS_MODULE,
	},
	.probe		= s3c_pwm_probe,//探测函数
	.remove		= __devexit_p(s3c_pwm_remove),
	.suspend	= s3c_pwm_suspend,
	.resume		= s3c_pwm_resume,
};

探测函数

static int s3c_pwm_probe(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	struct pwm_device *pwm;
	unsigned long flags;
	unsigned long tcon;
	unsigned int id = pdev->id;
	int ret;

	if (id == 4) {
		dev_err(dev, "TIMER4 is currently not supported\n");
		return -ENXIO;
	}

	pwm = kzalloc(sizeof(struct pwm_device), GFP_KERNEL);
	if (pwm == NULL) {
		dev_err(dev, "failed to allocate pwm_device\n");
		return -ENOMEM;
	}

	pwm->pdev = pdev;
	pwm->pwm_id = id;

	/* calculate base of control bits in TCON */
	pwm->tcon_base = id == 0 ? 0 : (id * 4) + 4;//计算TCON中控制哪个定时器

	pwm->clk = clk_get(dev, "pwm-tin");//获取预分频后的时钟
	if (IS_ERR(pwm->clk)) {
		dev_err(dev, "failed to get pwm tin clk\n");
		ret = PTR_ERR(pwm->clk);
		goto err_alloc;
	}

	pwm->clk_div = clk_get(dev, "pwm-tdiv");
	if (IS_ERR(pwm->clk_div)) {//获取二次分屏后的时钟
		dev_err(dev, "failed to get pwm tdiv clk\n");
		ret = PTR_ERR(pwm->clk_div);
		goto err_clk_tin;
	}

	local_irq_save(flags);

	tcon = __raw_readl(S3C2410_TCON);
	tcon |= pwm_tcon_invert(pwm);//信号反转输出
	__raw_writel(tcon, S3C2410_TCON);

	local_irq_restore(flags);


	ret = pwm_register(pwm);//注册pwm设备
	if (ret) {
		dev_err(dev, "failed to register pwm\n");
		goto err_clk_tdiv;
	}

	pwm_dbg(pwm, "config bits %02x\n",
		(__raw_readl(S3C2410_TCON) >> pwm->tcon_base) & 0x0f);

	dev_info(dev, "tin at %lu, tdiv at %lu, tin=%sclk, base %d\n",
		 clk_get_rate(pwm->clk),
		 clk_get_rate(pwm->clk_div),
		 pwm_is_tdiv(pwm) ? "div" : "ext", pwm->tcon_base);

	platform_set_drvdata(pdev, pwm);
	return 0;

 err_clk_tdiv:
	clk_put(pwm->clk_div);

 err_clk_tin:
	clk_put(pwm->clk);

 err_alloc:
	kfree(pwm);
	return ret;
}

pwm设备函数的注册pwm_register

static LIST_HEAD(pwm_list);
static DEFINE_MUTEX(pwm_lock);
static int pwm_register(struct pwm_device *pwm)
{
	pwm->duty_ns = -1;
	pwm->period_ns = -1;

	mutex_lock(&pwm_lock);
	list_add_tail(&pwm->list, &pwm_list);//把pwm设备挂到pwm_list链表上
	mutex_unlock(&pwm_lock);

	return 0;
}

pwm.c提供的接口函数

struct pwm_device *pwm_request(int pwm_id, const char *label)
int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns)
int pwm_enable(struct pwm_device *pwm)
void pwm_free(struct pwm_device *pwm)
EXPORT_SYMBOL(pwm_request);  //申请PWM设备
EXPORT_SYMBOL(pwm_config);   //配置PWM设备,duty_ns为空占比,period_ns为周期
EXPORT_SYMBOL(pwm_enable);   //启动Timer定时器
EXPORT_SYMBOL(pwm_disable);   //关闭Timer定时器
分析下最难的一个配置PWM函数,这个函数主要是根据周期period_ns,计算TCNT,根据空占比duty_ns,计算TCMP,然后写入相应寄存器。
int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns)
{
    unsigned long tin_rate;
    unsigned long tin_ns;
    unsigned long period;
    unsigned long flags;
    unsigned long tcon;
    unsigned long tcnt;
    long tcmp;

    /* We currently avoid using 64bit arithmetic by using the
     * fact that anything faster than 1Hz is easily representable
     * by 32bits. */

    if (period_ns > NS_IN_HZ || duty_ns > NS_IN_HZ)
        return -ERANGE;

    if (duty_ns > period_ns)
        return -EINVAL;

    if (period_ns == pwm->period_ns &&
        duty_ns == pwm->duty_ns)
        return 0;

    /* The TCMP and TCNT can be read without a lock, they're not
     * shared between the timers. */

    tcmp = __raw_readl(S3C2410_TCMPB(pwm->pwm_id));
    tcnt = __raw_readl(S3C2410_TCNTB(pwm->pwm_id));

    period = NS_IN_HZ / period_ns;//计算周期

    pwm_dbg(pwm, "duty_ns=%d, period_ns=%d (%lu)\n",
        duty_ns, period_ns, period);

    /* Check to see if we are changing the clock rate of the PWM */

    if (pwm->period_ns != period_ns) {
        if (pwm_is_tdiv(pwm)) {
            tin_rate = pwm_calc_tin(pwm, period);
            clk_set_rate(pwm->clk_div, tin_rate);
        } else
            tin_rate = clk_get_rate(pwm->clk);

        pwm->period_ns = period_ns;

        pwm_dbg(pwm, "tin_rate=%lu\n", tin_rate);

        tin_ns = NS_IN_HZ / tin_rate;
        tcnt = period_ns / tin_ns;//根据周期求TCNT,n=To/Ti
    } else
        tin_ns = NS_IN_HZ / clk_get_rate(pwm->clk);

    /* Note, counters count down */

    tcmp = duty_ns / tin_ns;
    tcmp = tcnt - tcmp;//根据占空比求TCMP
    /* the pwm hw only checks the compare register after a decrement,
       so the pin never toggles if tcmp = tcnt */
    if (tcmp == tcnt)
        tcmp--;

    pwm_dbg(pwm, "tin_ns=%lu, tcmp=%ld/%lu\n", tin_ns, tcmp, tcnt);

    if (tcmp < 0)
        tcmp = 0;

    /* Update the PWM register block. */

    local_irq_save(flags);

    __raw_writel(tcmp, S3C2410_TCMPB(pwm->pwm_id));//写入TCMp
    __raw_writel(tcnt, S3C2410_TCNTB(pwm->pwm_id));//写入TCNT

    tcon = __raw_readl(S3C2410_TCON);
    tcon |= pwm_tcon_manulupdate(pwm);
    tcon |= pwm_tcon_autoreload(pwm);//自动加载
    __raw_writel(tcon, S3C2410_TCON);

    tcon &= ~pwm_tcon_manulupdate(pwm);//更新TCNT和TCMP
    __raw_writel(tcon, S3C2410_TCON);

    local_irq_restore(flags);

    return 0;
}
下面说说这个周期是怎么设计的
我们定时器的输出频率fi=PCLK/(prescaler value+1)/(divider value),这个可以获得确定值
我们需要写入一个初值n给TCNT,这样就可以获得一个频率,为什么呢?
根据初值n=fi/fo,那么n=To/Ti
所以当用户给pwm_config函数传递一个周期period_ns,其实就是To=period_ns
这样根据前面公式n=To/Ti= period_ns/fi,然后将这个初值n写入TCNT就可以改变周期了
 
接着我再补充说明下pwm_config函数里代码注释关于自动加载怎么回事?
定时器工作原理其实是TCNT的值在时钟到来时,减一计数,每次减一完后,拿当前TCNT与TCMP比较,如果TCNT=TCMP,那么信号电平反向输出,然后TCNT继续减一计数,直到TCNT减到零后,如果有自动加载功能那么此时将由TCNTB把计数初值再次写给TCNTP,同时TCMPB把比较值给TCMP,这样就完成一次初值重装,然后继续进行计数。我们给这种加载模式起了个名字叫双缓冲机制,其中TCMPB和TCNTB就是Buffer缓存。

前面说pwm.c集驱动和设备于一体,那么下面我们看看设备相关的代码

注:kernel-3.0.8在/arch/plat-samsung/dev-pwm.c文件中
#define TIMER_RESOURCE_SIZE (1)

#define TIMER_RESOURCE(_tmr, _irq)			\
	(struct resource [TIMER_RESOURCE_SIZE]) {	\
		[0] = {					\
			.start	= _irq,			\
			.end	= _irq,			\
			.flags	= IORESOURCE_IRQ	\
		}					\
	}

#define DEFINE_S3C_TIMER(_tmr_no, _irq)			\
	.name		= "s3c24xx-pwm",		\
	.id		= _tmr_no,			\
	.num_resources	= TIMER_RESOURCE_SIZE,		\
	.resource	= TIMER_RESOURCE(_tmr_no, _irq),\

/*
 * since we already have an static mapping for the timer,
 * we do not bother setting any IO resource for the base.
 */

struct platform_device s3c_device_timer[] = {
	[0] = { DEFINE_S3C_TIMER(0, IRQ_TIMER0) },
	[1] = { DEFINE_S3C_TIMER(1, IRQ_TIMER1) },
	[2] = { DEFINE_S3C_TIMER(2, IRQ_TIMER2) },
	[3] = { DEFINE_S3C_TIMER(3, IRQ_TIMER3) },
	[4] = { DEFINE_S3C_TIMER(4, IRQ_TIMER4) },
};
EXPORT_SYMBOL(s3c_device_timer);
        上面的代码就是设备部分代码,其实就是五个定时器的资源,我们把目光放在DEFINE_S3C_TIMER宏上,你会发现其设备名是"s3c24xx-pwm",而我们在pwm.c中定义的驱动名也是"s3c24xx-pwm",这样如果我们把设备注册到内核,那么设备"s3c24xx-pwm"和驱动"s3c24xx-pwm"就会匹配成功。所以如果你用到定时器0,那么你只要在BSP中添加s3c_device_timer[0]就可以了。我们现在做的是Backlight背光驱动,使用的是Timer0定时器,我们就在tq210的BSP文件mach-tq210.c中添加如下代码

static struct platform_device *tq210_devices[] __initdata = {
	...
	
#ifdef CONFIG_BACKLIGHT_PWM
	&s3c_device_timer[0],
#endif

	...
};

三、Backlight核心驱动

代码/driver/video/backlight/backlight.c

内核中需要使能“Lowlevel Backlight controls”

Device Drivers --->
    Graphics support  --->
        [*] Backlight & LCD device support  --->
            <*>   Lowlevel Backlight controls

代码分析

static int __init backlight_class_init(void)
{
	backlight_class = class_create(THIS_MODULE, "backlight");//在/sys/class下注册backlight类
	if (IS_ERR(backlight_class)) {
		printk(KERN_WARNING "Unable to create backlight class; errno = %ld\n",
				PTR_ERR(backlight_class));
		return PTR_ERR(backlight_class);
	}

	backlight_class->dev_attrs = bl_device_attributes;//添加类属性
	backlight_class->suspend = backlight_suspend;
	backlight_class->resume = backlight_resume;
	return 0;
}

backlight背光系统的主要就是靠这个类属性,设置背光值就是向这个类属性中某个成员写入背光值,这个类属性就是给用户的同一接口

#define __ATTR(_name,_mode,_show,_store) { \
	.attr = {.name = __stringify(_name), .mode = _mode },	\
	.show	= _show,					\
	.store	= _store,					\
}
static struct device_attribute bl_device_attributes[] = {
	__ATTR(bl_power, 0644, backlight_show_power, backlight_store_power),
	__ATTR(brightness, 0644, backlight_show_brightness,
		     backlight_store_brightness),
	__ATTR(actual_brightness, 0444, backlight_show_actual_brightness,
		     NULL),
	__ATTR(max_brightness, 0444, backlight_show_max_brightness, NULL),
	__ATTR(type, 0444, backlight_show_type, NULL),
	__ATTR_NULL,
};
很明显,在backlight类中我们创建了bl_power,brightness,actural_brightness,max_brightness四个成员,其中brightness是当前亮度,max_brightness是最大亮度。当用户层通过cat或者echo命令就会触发这些成员。对于这些属性的读写函数,我们先看看读的函数backlight_show_max_brightness吧
static ssize_t backlight_show_max_brightness(struct device *dev,
		struct device_attribute *attr, char *buf)
{
	struct backlight_device *bd = to_backlight_device(dev);

	return sprintf(buf, "%d\n", bd->props.max_brightness);//输出最大亮度
}

这个函数很简单,但是重点是引入了几个backlight背光子系统的几个重要的数据结构,我们好好学习下。

首先是backlight背光子系统的设备结构体backlight_device

struct backlight_device {
	/* Backlight properties */
	struct backlight_properties props;//背光属性

	/* Serialise access to update_status method */
	struct mutex update_lock;

	/* This protects the 'ops' field. If 'ops' is NULL, the driver that
	   registered this device has been unloaded, and if class_get_devdata()
	   points to something in the body of that driver, it is also invalid. */
	struct mutex ops_lock;
	const struct backlight_ops *ops;//背光操作函数,类似于file_operation

	/* The framebuffer notifier block */
	struct notifier_block fb_notif;

	struct device dev;//内嵌设备
};
下面先看看背光属性结构体backlight_properties
struct backlight_properties {
	/* Current User requested brightness (0 - max_brightness) */
	int brightness;//当前背光值
	/* Maximal value for brightness (read-only) */
	int max_brightness;//最大背光值
	/* Current FB Power mode (0: full on, 1..3: power saving
	   modes; 4: full off), see FB_BLANK_XXX */
	int power;
	/* FB Blanking active? (values as for power) */
	/* Due to be removed, please use (state & BL_CORE_FBBLANK) */
	int fb_blank;
	/* Backlight type */
	enum backlight_type type;
	/* Flags used to signal drivers of state changes */
	/* Upper 4 bits are reserved for driver internal use */
	unsigned int state;

#define BL_CORE_SUSPENDED	(1 << 0)	/* backlight is suspended */
#define BL_CORE_FBBLANK		(1 << 1)	/* backlight is under an fb blank event */
#define BL_CORE_DRIVER4		(1 << 28)	/* reserved for driver specific use */
#define BL_CORE_DRIVER3		(1 << 29)	/* reserved for driver specific use */
#define BL_CORE_DRIVER2		(1 << 30)	/* reserved for driver specific use */
#define BL_CORE_DRIVER1		(1 << 31)	/* reserved for driver specific use */

};

在看看背光操作函数

struct backlight_ops {
	unsigned int options;

#define BL_CORE_SUSPENDRESUME	(1 << 0)

	/* Notify the backlight driver some property has changed */
	int (*update_status)(struct backlight_device *);//更新背光状态
	/* Return the current backlight brightness (accounting for power,
	   fb_blank etc.) */
	int (*get_brightness)(struct backlight_device *);//获取背光值
	/* Check if given framebuffer device is the one bound to this backlight;
	   return 0 if not, !=0 if it is. If NULL, backlight always matches the fb. */
	int (*check_fb)(struct backlight_device *, struct fb_info *);
};
当前背光值函数 backlight_store_brightness
static ssize_t backlight_store_brightness(struct device *dev,
		struct device_attribute *attr, const char *buf, size_t count)
{
	int rc;
	struct backlight_device *bd = to_backlight_device(dev);
	unsigned long brightness;

	rc = strict_strtoul(buf, 0, &brightness);
	if (rc)
		return rc;

	rc = -ENXIO;

	mutex_lock(&bd->ops_lock);
	if (bd->ops) {
		if (brightness > bd->props.max_brightness)
			rc = -EINVAL;
		else {
			pr_debug("backlight: set brightness to %lu\n",
				 brightness);
			bd->props.brightness = brightness;//传入背光值
			backlight_update_status(bd);//更新背光状态
			rc = count;
		}
	}
	mutex_unlock(&bd->ops_lock);

	backlight_generate_event(bd, BACKLIGHT_UPDATE_SYSFS);

	return rc;
}
static inline void backlight_update_status(struct backlight_device *bd)
{
	mutex_lock(&bd->update_lock);
	if (bd->ops && bd->ops->update_status)
		bd->ops->update_status(bd);
	mutex_unlock(&bd->update_lock);
}
对于这个backlight背光核心层驱动backlight.c,剩下的就是这个pwm.c给我们提供了哪些接口函数了
struct backlight_device *backlight_device_register(const char *name,
              struct device *parent, void *devdata, struct backlight_ops *ops)

void backlight_device_unregister(struct backlight_device *bd)

EXPORT_SYMBOL(backlight_device_register);  //注册背光设备
EXPORT_SYMBOL(backlight_device_unregister); //注销背光设备

四、基于PWM&Backlight的背光驱动

        结合上面的 PWM核心层和 Backlight背光子系统核心层,根据基于 pwm的背光驱动 /driver/video/backlight/pwm_bl.c来修改成基于 tq210的蜂鸣器驱动

内核中需要使能“Generic PWM based Backlight Driver”

Device Drivers --->
    Graphics support  --->
        [*] Backlight & LCD device support  --->
            <*>  Generic PWM based Backlight Driver

tq210蜂鸣器使用GPD0_1口,该端口工作在TOU0模式下,就可以通过设备定时器的TCNT和TCMP来控制定时器的波形。

首先,在tq210的BSP文件mach-tq210.c,如下添加

static struct platform_device tq210_backlight_device = {
	.name		= "pwm-backlight",//设备名
	.dev		= {
		.parent		= &s3c_device_timer[0].dev,//该设备基于pwm中的0号定时器
		.platform_data	= &tq_backlight_data,
	},
};

添加平台数据

static struct platform_pwm_backlight_data tq210_backlight_data = {
	.pwm_id		= 0,//对应的就是Timer0
	.max_brightness	= 255,//最大亮度
	.dft_brightness	= 100,//255,//当前亮度
        .lth_brightness = 50,//咱不知道干啥用的 
        .pwm_period_ns	= 20000,//78770,//T0,即输出时钟周期
	.init		= tq210_backlight_init,//端口初始化
	.exit		= tq210_backlight_exit,
};
static int tq210_backlight_init(struct device *dev)
{
	int ret;

	ret = gpio_request(S5PV210_GPD0(0), "Backlight");
	if (ret) {
		printk(KERN_ERR "failed to request GPD for PWM-OUT 0\n");
		return ret;
	}

	/* Configure GPIO pin with S5PV210_GPD_0_0_TOUT_0 */
	s3c_gpio_cfgpin(S5PV210_GPD0(0), S3C_GPIO_SFN(2));

	return 0;
}

static void tq210_backlight_exit(struct device *dev)
{
	s3c_gpio_cfgpin(S5PV210_GPD0(0), S3C_GPIO_OUTPUT);
	gpio_free(S5PV210_GPD0(0));
}

然后把tq210_backlight_device添加到tq210_devices数组

static struct platform_device *tq210_devices[] __initdata = {
	...
	
#ifdef CONFIG_BACKLIGHT_PWM
	&s3c_device_timer[0],
	&s3c_device_timer[1],
	&s3c_device_timer[2],
	&s3c_device_timer[3],
	&tq210_backlight_device,//同时添加对应的s3c_device_timer[0]
#endif

	...
};

最后添加头文件

#include <linux/pwm_backlight.h>
分析pwm_bl.c文件
static struct platform_driver pwm_backlight_driver = {
	.driver		= {
		.name	= "pwm-backlight", //驱动名需要与设备名保持一致
		.owner	= THIS_MODULE,
	},
	.probe		= pwm_backlight_probe,
	.remove		= pwm_backlight_remove,
	.suspend	= pwm_backlight_suspend,
	.resume		= pwm_backlight_resume,
};
static int __init pwm_backlight_init(void)
{
	return platform_driver_register(&pwm_backlight_driver);
}

探测函数

static int pwm_backlight_probe(struct platform_device *pdev)
{
	struct backlight_properties props;
	struct platform_pwm_backlight_data *data = pdev->dev.platform_data;
	struct backlight_device *bl;
	struct pwm_bl_data *pb; //本驱动的私有结构体
	int ret;

	if (!data) {
		dev_err(&pdev->dev, "failed to find platform data\n");
		return -EINVAL;
	}

	if (data->init) {//初始化端口,在BSP文件中定义
		ret = data->init(&pdev->dev);
		if (ret < 0)
			return ret;
	}

	pb = kzalloc(sizeof(*pb), GFP_KERNEL);
	if (!pb) {
		dev_err(&pdev->dev, "no memory for state\n");
		ret = -ENOMEM;
		goto err_alloc;
	}

	pb->period = data->pwm_period_ns;//获取周期
	pb->notify = data->notify;
	pb->check_fb = data->check_fb;
	pb->lth_brightness = data->lth_brightness *
		(data->pwm_period_ns / data->max_brightness);
	pb->dev = &pdev->dev;

	pb->pwm = pwm_request(data->pwm_id, "backlight");//注册pwm设备
	if (IS_ERR(pb->pwm)) {
		dev_err(&pdev->dev, "unable to request PWM for backlight\n");
		ret = PTR_ERR(pb->pwm);
		goto err_pwm;
	} else
		dev_dbg(&pdev->dev, "got pwm for backlight\n");

	memset(&props, 0, sizeof(struct backlight_properties));
	props.type = BACKLIGHT_RAW;
	props.max_brightness = data->max_brightness;
	bl = backlight_device_register(dev_name(&pdev->dev), &pdev->dev, pb,
				       &pwm_backlight_ops, &props);//注册backlight设备,注意pwm_backlight_ops函数
	if (IS_ERR(bl)) {
		dev_err(&pdev->dev, "failed to register backlight\n");
		ret = PTR_ERR(bl);
		goto err_bl;
	}

	bl->props.brightness = data->dft_brightness;
	backlight_update_status(bl);//先点亮光

	platform_set_drvdata(pdev, bl);//设置bl私有数据
	return 0;

err_bl:
	pwm_free(pb->pwm);
err_pwm:
	kfree(pb);
err_alloc:
	if (data->exit)
		data->exit(&pdev->dev);
	return ret;
}
对于这个驱动,重点关注的是注册backlight设备时传入的参数pwm_backlight_ops,因为我们之前分析backlight背光子系统时说过,背光设备结构体中有个操作背光的函数集合,在我们的pwm_bl.c中,就需要定义这个操作背光的函数集合,也就是pwm_backlight_ops函数
static const struct backlight_ops pwm_backlight_ops = {
	.update_status	= pwm_backlight_update_status,//更新背光亮度
	.get_brightness	= pwm_backlight_get_brightness,//获取背光亮度
	.check_fb	= pwm_backlight_check_fb,
};
static int pwm_backlight_update_status(struct backlight_device *bl)
{
	struct pwm_bl_data *pb = dev_get_drvdata(&bl->dev);
	int brightness = bl->props.brightness;
	int max = bl->props.max_brightness;

	if (bl->props.power != FB_BLANK_UNBLANK)
		brightness = 0;

	if (bl->props.fb_blank != FB_BLANK_UNBLANK)
		brightness = 0;

	if (pb->notify)
		brightness = pb->notify(pb->dev, brightness);

	if (brightness == 0) {//背光值为0,关闭被背光
		pwm_config(pb->pwm, 0, pb->period);
		pwm_disable(pb->pwm);
	} else {//调用pwm中的API设置背光
		brightness = pb->lth_brightness +
			(brightness * (pb->period - pb->lth_brightness) / max);
		pwm_config(pb->pwm, brightness, pb->period);
		pwm_enable(pb->pwm);
	}
	return 0;
}

五、测试








本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

LCD(五)Backlight背光子系统 的相关文章

  • 1.14.ARM裸机第十四部分-LCD显示器

    第一部分 章节目录 1 14 1 LCD简介 1 14 2 LCD的接口技术 1 14 3 LCD如何显示图像1 1 14 4 LCD如何显示图像2 1 14 5 LCD的六个主要时序参数1 1 14 6 LCD的六个主要时序参数2 1 1
  • 设备树学习之(十二)LCD驱动

    开发板 xff1a tiny4412SDK 43 S702 43 4GB Flash 要移植的内核版本 xff1a Linux 4 4 0 xff08 支持device tree xff09 u boot版本 xff1a 友善之臂自带的 U
  • micropython 8266 驱动 12864G 液晶LCD屏幕

    1 xff0c 接线顺序 引脚定义 cs 61 Pin 4 片选 reset 61 Pin 5 复位 rs 61 Pin 16 数据 指令 1数据 0 指令 DC sda 61 Pin 13 数据信号 sck 61 Pin 14 时钟信号
  • ILI9341的使用之【一】TFT-LCD原理(转载)

    近期开始研究手上的LCD屏的使用 该LCD屏使用了ILI9341的IC做为驱动 因此边研究边留下记录 与上一个时钟系列类似 xff0c 想必这又是一个大工程 xff0c 因为ILI9341的数据手册就有200多页 xff0c 从硬件原理 x
  • LCD 12864B V2.0的使用

    内置ST7920控制器和中文字库的LCD12864的使用 前言 大家好 xff0c 我是小灬贱 今天我给大家带来LCD12864的使用方法以及我的一些经验 文章里面如有不妥之处或者表达不清晰的地方还请各位多多指教 可以在文下评论或者私信我
  • LCD液晶屏没有点亮,为什么会自动出现笔段、走线、乱码等现象?

    一般LCD段码液晶屏在不触碰的情况下 xff0c 是不会产生这种自动出现笔段 走线 乱码等现象 只有在物体或手碰到液晶屏的时候才会或多或少的出现这种问题 xff0c 而造成这种现象的是 静电 静电是一种处于静止状态的电荷 在干燥和多风的秋天
  • 基于STM32F411使用SPI+DMA驱动LCD

    先看效果 F411CE 硬件SPI xff0b DMA驱动LCD 基于HAL库 其实HAL库我用不太习惯 xff0c 一直也是用的标准库 但HAL库确实是好上手一些 xff0c 就迅速创建一个新的template 这次就当尝试一下吧 xff
  • Linux-LCD驱动实现

    一 帧缓冲设备驱动在Linux子系统中的结构 xff1a 二 帧缓冲相关的重要数据结构 从帧缓冲设备驱动程序结构 看 xff0c 该驱动主要跟fb info结构体有关 xff0c 该结构体记录了帧缓冲设备的全部信息 xff0c 包括设备的设
  • LCD与LED液晶显示屏的区别

    什么是LCD LCD是液晶显示屏Liquid Crystal Display的全称 xff0c 主要有TFT UFB TFD STN等几种类型的液晶显示屏无法定位程序输入点于动态链接库上 笔记本液晶屏常用的是TFT TFT xff08 Th
  • 使用FSMC驱动LCD以及数据线偏移的问题

    FSMC的理解 使用FSMC功能将8080接口的LCD当外部RAM来使用 xff08 数据传给LCD时没经过内部SRAM xff0c 所以一帧图片很大也可以直接传 xff09 xff0c 根据STM的地址分配图可以看出外部RAM的地址由0x
  • Android中LCD背光驱动

    分类 Linux驱动 Android系统 2013 06 17 16 28 287人阅读 评论 0 收藏 举报 目录 Android的Setting Android的背光JNI层 Android的背光HAL层 Linux的背光内核层 Lin
  • MSP430 LCD控制器解释

    CC430F613x的LCD控制器最多能控制160段 The LCD B controller features are Display memory Automatic signal generation Configurable fra
  • Linux下lt8911exb调试总结

    调试lt8911exb花了两天时间 总结下 ic功能介绍 mipi转eDP http cn lontiumsemi com UploadFiles pdf LT8911EXB Product Brief pdf github下有驱动源码 可
  • LCD 亮度相关(背光) 正负压相关

    LCD 亮度相关 背光 kernel msm 3 18 drivers video msm mdss mdss fb c 调用led classdev register 注册lcd backlight sys class leds lcd
  • LCD和LED屏幕的工作原理总结

    1 点阵取模原理之横向取模与纵向取模 1 1 针式打印机 针式打印机16针是纵向排列 每次打印垂直的16bit 然后右移一bit 继续下列打印 字节的MSB表示最上面的点 字节LSB表示最下面的点 由于汉字字模的点阵是横向排列的 而提供给打
  • 我所理解的DRM显示框架

    什么是DRM DRM全称是DirectRenderingManager 是linux主流的一种显示框架 支持多图层合成 为用户图层提供统一的API libdrm 来访问GPU 实现统一管理 它是为了解决多个程序对video card访问协同
  • LCD DRM驱动框架分析一

    本文是基于rk3566 rk3568平台 从概念和框架上对LCD DRM驱动框架进行分析 一 DRM Direct Rendering Manager 简介 DRM 是 Linux 目前主流的图形显示框架 相比 FB 架构 DRM 更能适应
  • HT1621B液晶驱动(附电路图)

    最近做了一个项目用到了HT1621 电路图 datasheet code HT1621B datasheet include ht1621 h void ht1621 send high order data UCHAR data UCHA
  • 00.mipi协议

    mipi差分信号原理 理解mipi协议 MIPI DSI LP mode命令及格式详解 MIPI信号的分析 结合示波器实际测试波形 MIPI 移动行业处理器接口 是Mobile Industry Processor Interface的缩写
  • 如何获得 (lcd) 显示屏对角线的实际尺寸,即它是 17 英寸还是 19 英寸或其他?

    这对我很有用 因为我必须在屏幕上以正确的尺寸映射对象 如果我使用分辨率为 1280x1024 的 19 lcd 和正常的 96dpi 设置 那么为了映射正确的 1 英寸正方形 我必须编写这样的 xaml

随机推荐

  • Nginx超时配置、限流

    目录 一 说明 二 超时配置 三 限流 限制访问频率 限制并发连接数 四 问题记录 五 参考文章 Author Jinwei EditTimes 2020年11月25日17 31 06 一 说明 Nginx 处理的每个请求均有相应的超时设置
  • spring注解及扩展

    1 spring配置注解 spring建议通过注解配置 替代原xml配置方式 使用配置类替代xml配置的优势大体 1 xml配置维护容易出错而且不易检查 java配置类基于java语法检查 对于java程序员更友好 易于维护 2 注解配置
  • NISEDIT如何发布,Qt如何发布文章?难道还有人不会(超详细教学,跟着走,不会你怪我)

    一 自动发布 直接运行即可 不过多阐述 二 手动发布 文件清单 ExamSys exe account txt exam txt Qt5Core dll Qt5Gui dll Qt5Widgets dll libstdc 6 dll lib
  • LeetCode中函数题中“多出来的参数“---returnsize

    转载 关于returnSize 第一次在leetcode上瞎逛就遇到了就遇到了它 int twoSum int nums int numsSize int target int returnSize 1 这个代码的实现并不是什么难解的方法
  • JVM 由哪些部分组成?

    JVM 由哪些部分组成 解析 这是对 JVM 体系结构的考察 答 JVM 的结构基本上由 4 部分组成 类加载器 在 JVM 启动时或者类运行时将需要的 class 加载到 JVM 中 执行引擎 执行引擎的任务是负责执行 class 文件中
  • Zynq7000硬件开发之芯片供电电源功耗(电流)评估

    案头语 单板硬件的主控芯片集成度越来越高 多核处理器越来越多 一块单板可能只需要1块芯片就能满足整体需求 一方面减少设计复杂度 另一面节省PCB面积成本 能同时掌握硬件原理设计以及PCB Layout设计逐渐成为主流 本系列文章同时包含有两
  • ES6详解 快速上手!

    一 Es6 1 1 ES6的概述 ECMAScript的快速发展 编程语言JavaScript是ECMAScript的实现和扩展 ECMAScript是由ECMA 一个类似W3C的标准组织 参与进行标准化的语法规范 ECMAScript定义
  • 【python量化】用python搭建一个股票舆情分析系统

    写在前面 下面的这篇文章将手把手教大家搭建一个简单的股票舆情分析系统 其中将先通过金融界网站爬取指定股票在一段时间的新闻 然后通过百度情感分析接口 用于评估指定股票的正面和反面新闻的占比 以此确定该股票是处于利好还是利空的状态 1 环境准备
  • C++(Liunx) 使用cut截 取出Ubuntu用户的家目录,要求:不能使用“:“作为分割.

    使用cut截 取出Ubuntu用户的家目录 要求 不能使用 作为分割
  • 43.MQ—RabbitMQ

    目录 一 MQ RabbitMQ 1 同步调用与异步调用 1 1 同步调用 1 2 异步调用 2 MQ之间的区别 3 RabbitMQ学习 3 1 docker下载rabbitmq容器 并启动 3 2 RabbitMQ中的几个概念 3 3
  • Python遥感开发之分段读取和保存遥感数据

    Python遥感开发之分段读取和保存遥感数据 1 分段读取数据 2 实现分批读取数据以及进行计算 3 实现分批保存成TIF文件 所有完整代码 4 分段TIF整合到一个TIF 5 生成一个空白TIF 每个像元值为0的TIF 前言 当遇到批量读
  • Supervisord进程管理工具的安装使用

    先来介绍 supervisord Supervisor 是一个进程监控程序 满足的需求是 我现在有一个进程需要每时每刻不断的跑 但是这个进程又有可能由于各种原因有可能中断 当进程中断的时候我希望能自动重新启动它 此时 我就需要使用到了 Su
  • dataloader的使用

    dataloader 构建可迭代的数据装载器 我们在训练的时候 每一个for循环 每一次iteration 就是从DataLoader中获取一个batch size大小的数据的 dataloader官网 torch utils data P
  • 【MySQL基础】常用函数

    文章目录 单行函数 字符函数 LENGTH CONCAT UPPER LOWER SUBSTR SUBSTRING INSTR TRIM LPAD RPAD REPLACE 数学函数 ABS ROUND CEIL FLOOR TRUNCAT
  • [每日两题系列]刷算法题咯~~

    今日题目 卡片 直线 本系列所选题目均来自力扣或者牛客网站 所选题目主要是以其中的简单题为主 中等题为辅 包含少数困难题 原因是 本人目前能力还不够 开展这个系列的目的是督促自己 在暑假的时间里也要保持有一定的刷题量 拒绝摆烂 话不多说 直
  • python矩阵教程_numpy教程:矩阵matrix及其运算

    numpy矩阵简介 NumPy函数库中存在两种不同的数据类型 矩阵matrix和数组array 都可以用于处理行列表示的数字元素 虽然它们看起来很相似 但是在这两个数据类型上执行相同的数学运算可能得到不同的结果 其中NumPy函数库中的ma
  • 插入MySQL数据库前去除重复数据的几种方法

    在数据存储过程中 可能会遇到数据主键重复的情况 我们可以通过下面几个方法进行处理 1 若数据不存在插入 存在更新 2 使用duplicate key关键字 如插入数据时发生主键冲突就更新数据 3 使用Ingore关键字 4 使用replac
  • BoxFit(缩放模式、自适应模式)

    类似于Android原生的ImageView ScaleType 以下是Flutter提供的Box缩放类型 fill Box被完全填充 相当于ScaleType的FIT XY contain 保持Box的纵横比至至少有一边填充满父控件 相当
  • 单例模式 -- 懒汉模式&饿汉模式

    目录 一 单例模式是什么 二 饿汉模式 三 懒汉模式 一 单例模式是什么 单例模式是一种设计模式 用于将类的实例化限制为一个对象 它确保一个类只有一个实例 并提供了该实例的全局访问点 这种模式被广泛用于创建对象的唯一实例 例如数据库连接和日
  • LCD(五)Backlight背光子系统

    一 Backlight背光子系统概述 LCD的背光原理主要是由核心板的一根引脚控制背光电源 一根PWM引脚控制背光亮度组成 应用程序可以通过改变PWM的频率达到改变背光亮度的目的 Backlight背光子系统构建过程结构关系图 黑色加粗部分