GPIO模拟输出PWM调节屏幕背光亮度

一、概述

        很多时候由于节省硬件资源,降低成本,会把PWM控制芯片去掉或者是改做它用,导致当我们想用PWM方式控制背光时只能使用普通的GPIO口。本篇文档就来讲解下如何使用GPIO模拟PWM功能进行背光的控制。本文以IMX6为例。

二、确认硬件接口

硬件连接如下:

查看IMX6手册,发现引脚为GPIO3_IO18

三、软件配置

1、添加设备树节点

2、驱动编写

#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/err.h>
#include <linux/pwm.h>
#include <linux/slab.h>
#include <linux/clk.h>
 
#include <linux/iio/iio.h>
#include <linux/iio/machine.h>
#include <linux/iio/driver.h>
#include <linux/iio/consumer.h>
#include <linux/delay.h>
#include <linux/time.h>
#include <linux/timer.h>
#include <linux/input.h>
#include <linux/interrupt.h>
#include <linux/of_platform.h>
#include <linux/of_device.h>
#include <linux/types.h>
#include <linux/ioport.h>
#include <asm/irq.h>
#include <linux/of_irq.h>
#include <linux/of_address.h>
#include <linux/reset.h>

struct gpio_blacklight_dev {
	int value;
	unsigned gpio;
	struct gpio_desc * gpiod;
	struct mutex mtx;
	const char* name;
	int light_level;
	struct device *dev;
	int high_time;
	int low_time;
	unsigned long flags;
	unsigned active_low;
	struct timer_list pwm_timer;	
	void __iomem *reg_base;
};
static int gpio_sim = 0;
static int tcount = 0, flag = 0;
static int high_flag;
static int low_flag;
static int frequency = 1000; // 初始频率,单位:Hz
static int high_cnt = 50;//调节该值就能操作占空比
static int period_cnt = 100;//100个定时周期认为是一个pwm周期,所以定时器的时间应该是1s/frequency/period_cnt

static struct hrtimer pwm_timer1;
static ktime_t pwm_period;

// 背光最大值
static int max_high_cnt = 100; // 定义最大值

static ssize_t max_light_level_show(struct device *dev, struct device_attribute *attr, char *buf)
{
    return sprintf(buf, "%d\n", max_high_cnt);
}

static ssize_t max_light_level_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
    long new_max_high_cnt;
    kstrtoul(buf, 10, &new_max_high_cnt);
    if (new_max_high_cnt >= 0) {
        max_high_cnt = new_max_high_cnt;
    }
    return count;
}

static ssize_t light_level_show(struct device *dev, struct device_attribute *attr, char *buf)
{
	struct gpio_blacklight_dev *g_dev = (struct gpio_blacklight_dev *)dev_get_drvdata(dev);
	return sprintf(buf, "%d\n", high_cnt);
}
		
 
static ssize_t light_level_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
	long light_level;	
	struct gpio_blacklight_dev *g_dev = (struct gpio_blacklight_dev *)dev_get_drvdata(dev);	
	kstrtoul(buf, 10, &light_level);
	high_cnt = light_level;
	return count;
}
static DEVICE_ATTR(light_level, S_IRUGO | S_IWUSR, light_level_show, light_level_store);
static DEVICE_ATTR(max_light_level, 0664, max_light_level_show, max_light_level_store);

static struct device_attribute light_level = {
	.attr = {
		.name = "light_level",
		.mode = S_IRUGO | S_IWUGO | S_IXUGO ,
	},
	.show = light_level_show,
	.store = light_level_store,
};
static const struct of_device_id of_gpio_blacklights_match[] = {
	{ .compatible = "gpio-backlight" },
	{},
};
 
MODULE_DEVICE_TABLE(of, of_gpio_blacklights_match);
 
 /*************************************************************************/
static enum hrtimer_restart pwm_timer_callback(struct hrtimer *timer) {
	static int timer_cnt = 0;
	if(++timer_cnt == high_cnt){
		 gpio_set_value(gpio_sim, 0);
	}
	if(timer_cnt == period_cnt){
		 gpio_set_value(gpio_sim, 1);
		 timer_cnt = 0;
	}
    hrtimer_forward(timer, timer->_softexpires, pwm_period);
 
    printk("pwm_timer_callback\n");
    return HRTIMER_RESTART;
}
 
static int io_pwm_init(void) {
    // 计算 PWM 周期为pwm_period*period_cnt=pwm_period*100=1*10^(-6)
    pwm_period = ktime_set(0, 1000000000 / frequency/ period_cnt); 
 
    // 初始化定时器
    hrtimer_init(&pwm_timer1, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
    pwm_timer1.function = pwm_timer_callback;//回调函数操作GPIO
 
    // 启动高精度定时器
    hrtimer_start(&pwm_timer1, pwm_period, HRTIMER_MODE_REL);
 
    printk(KERN_INFO "PWM Timer Module Loaded\n");
    printk("io_pwm_init\n");
 
    return 0;
}
/*************************************************************************/

static int gpio_blacklight_probe(struct platform_device *pdev)
{
	
	int count = 0, ret = 0;
	struct gpio_blacklight_platform_data *pdata = dev_get_platdata(&pdev->dev);
	
	struct device *dev = &pdev->dev;
	struct gpio_blacklight_dev *gblacklight_dev;		
	
	printk("enter gpio_blacklight_probe init\n");
	gblacklight_dev = devm_kzalloc(dev, sizeof(struct gpio_blacklight_dev), GFP_KERNEL);
	memset(gblacklight_dev, 0, sizeof(*gblacklight_dev));
	if (!gblacklight_dev)
	{
		printk("devm_kzalloc gblacklight_dev fail \n");
		return -1;
	}
 
	gpio_sim = of_get_named_gpio_flags(pdev->dev.of_node, "gpio-sim", 0, NULL);
	if (gpio_sim < 0)
	{
		printk("%s() Can not read property gpio_sim\n", __FUNCTION__);
		gpio_sim = 0;
	}
	ret = gpio_request(gpio_sim, "gpio-sim");
	if (ret < 0)
	{
		printk("gpio request gpio_sim error!\n");
	}
	else
	{
		printk("==============================gpio-sim\n");
		gpio_direction_output(gpio_sim, 1);
	}
    //放在这里初始化
	io_pwm_init();

	ret = device_create_file(&pdev->dev, &dev_attr_light_level);
	    if (ret) {
       		 dev_err(&pdev->dev, "Failed to create sysfs entry\n");
     	         return ret;
    }
	device_create_file(&pdev->dev, &dev_attr_max_light_level);
	printk("gpio_pwm probe ok\n");
 
err:
	
	return ERR_PTR(ret);
}
 
static int gpio_blacklight_remove(struct platform_device *pdev)
{
	struct gpio_blacklight_dev *gblacklight_dev = (struct gpio_blacklight_dev *)dev_get_drvdata(&pdev->dev);
	del_timer(&gblacklight_dev->pwm_timer);
	device_remove_file(gblacklight_dev->dev, &light_level);	
	dev_set_drvdata(gblacklight_dev->dev, NULL);
	hrtimer_cancel(&pwm_timer1);
	return 0;
}
 
 static struct platform_driver gpio_blacklight_driver = {
	.probe		= gpio_blacklight_probe,
	.remove		= gpio_blacklight_remove,
	.driver		= {
		.name	= "blacklight-gpio",
		.of_match_table = of_match_ptr(of_gpio_blacklights_match),
		.owner = THIS_MODULE,
	},
};
 
module_platform_driver(gpio_blacklight_driver);
MODULE_LICENSE("GPL");		
MODULE_AUTHOR("xth");	

四、测试

1、sys节点

路径:/sys/devices/soc0/blacklight/light_level

问题1:背光无变化

之前调试AIM915X/916X时,将背光脚设置为了916端本地输出,修改为GPIO透传模式,如果使用的是直连的话是没有问题的

PWM GPIO2

915端为输入 i2cset -f -y 2 0x0c 0x0b 0x30

916端远程输出 i2cset -f -y 2 0x2c 0x1d 0x50

修改aim915916驱动如下

再次测试

查看寄存器是否成功写入

调节背光有明显变化,成功:

echo 10 > /sys/devices/soc0/blacklight/light_level

echo 80 > /sys/devices/soc0/blacklight/light_level

问题2:背光上电过快导致屏幕显示出撕扯的画面,或者上电一瞬间闪屏的状态

如何解决开发板背光上电过快导致的闪屏和画面撕裂?

发送每个命令之间添加一个延迟,达到延迟启动背光的效果。

addr  = 0x0B;
value = 0x30;
if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
    printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
    return ret;
}

msleep(100);  // 100毫秒延迟

addr  = 0x1D;
value = 0x50;
if((ret = i2c_smbus_write_byte_data(aim956_client, addr, value)) < 0){
    printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
    return ret;
}

测试无效果

查看日志log,发现PWM驱动是在屏之前加载的

PWM驱动在屏驱动加载之后?

将PWM驱动编译为模块在aim915915加载后再进行加载

make modules -j44

在启动脚本中加载PWM模块

闪屏没了,但会存在一个问题,上电到点亮背光要很长的时间

尝试修改内核中驱动的加载顺序

修改如下:

GPIOPWM驱动是在AIM915916后加载,但是还在存在闪屏

重点看下GPIOPWM驱动的问题,尝试修改占空比以及频率

修改了占空比之后问题解决,驱动在内核中进行加载,优化了系统启动时间

调试到此完成,后续会结合光感模块,实现背光自动调节功能,如感兴趣,可持续关注后续文章内容。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

春风从不入睡、

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值