高通平台GPIO模拟PWM控制背光

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

一、选取GPIO口并进行配置

    1、需要查看寄存器手册,选取对应的具有GP_CLK功能的GPIO口——gpio49。

    2、我们需要先看下,该管脚是否被其他模块应用。查看配置信息,发现该管脚被默认用作了UIM2。我们要先关闭UIM模块对其的操作,如果你仅仅负责BSP领域,那么请找对应modem的同事帮忙把对这个脚的使用关掉。

    3、接下来就是将该管脚配置为clk模式。

        3.1 dtsi中添加节点

        kernel/msm-3.18/arch/arm/boot/dts/qcom/msm8909-pinctrl.dtsi

                gpio_pwm_default: gpio_pwm_default {
			mux {
				pins = "gpio49";
				function = "gcc_gp1_clk_a";
			};

			config {
				pins = "gpio49";
				drive-strength = <16>;
				bias-disable;
			};
		};

        3.2 定义设备节点

        kernel/msm-3.18/arch/arm/boot/dts/qcom/msm8909-mdss.dtsi

		beeper: beeper {
			compatible = "gpio-beeper";
			pinctrl-names = "default";
			pinctrl-0 = <&gpio_pwm_default>;
			clocks = <&clock_gcc clk_gp1_clk_src>;
			clock-names = "gpio-pwm-clk";
		};

二、配置时钟信息

    既然我们作为CLK使用,必然需要配置其对应的频率。

    kernel/msm-3.18/drivers/clk/msm/clock-gcc-8909.c

static struct clk_freq_tbl ftbl_gcc_gp1_3_clk[] = {
	F(   150000,    xo,     1,      1,      128),
	F( 19200000,	xo,	1,	0,	  0),
	F(   9375,	xo,	16,	1,	 128),//添加对应的频率信息
	F_END
};

    分别解析一下对应的这几个值的信息:9375——对应的clk频率,xo——时钟源,16——SRC_DIV,1——M,128——N。

三、驱动适配

    添加设备初始化信息    kernel/msm-3.18/drivers/video/msm/mdss/mdss_dsi.c

#include <linux/clk.h>
#include <soc/qcom/clock-local2.h>
#include <linux/io.h>
... ...
struct clk *pclk;
struct rcg_clk *gp1_rcg_clk;
extern void mdss_set_gpio_pwm(int level);

static int mdss_dsi_bl_probe(struct platform_device *pdev)
{
	int ret;
	int level = 50;
	if (!pdev || !pdev->dev.of_node) {
		pr_err("%s: pdev not found for DSI controller\n", __func__);
		return -ENODEV;
	}

	pclk = devm_clk_get(&pdev->dev, "gpio-pwm-clk");//获取clk信息
	ret = clk_set_rate(pclk, 9375);//设置clk频率
	if (ret){
		printk("clk set rate fail, ret = %d\n", ret);
	}

	ret = clk_prepare_enable(pclk);//使能clk
	if (ret){
		printk("%s: clk_prepare error!!!\n", __func__);
	}else{
		printk("%s: clk_prepare success!\n", __func__);
	}

	gp1_rcg_clk = to_rcg_clk(pclk);

	mdss_set_gpio_pwm(level);//设置背光,个人添加的方法

	return 0;
}

static const struct of_device_id mdss_dsi_bl_dt_match[] = {
	{.compatible = "gpio-beeper"},
	{}
};
MODULE_DEVICE_TABLE(of, mdss_dsi_bl_dt_match);

static struct platform_driver mdss_dsi_bl_driver = {
	.probe = mdss_dsi_bl_probe,
	.shutdown = NULL,
	.driver = {
		.name = "beeper",
		.of_match_table = mdss_dsi_bl_dt_match,
	},
};

static int mdss_dsi_bl_register_driver(void)
{
	return platform_driver_register(&mdss_dsi_bl_driver);
}

static int __init mdss_dsi_bl_driver_init(void)
{
	int ret;

	ret = mdss_dsi_bl_register_driver();
	if (ret) {
		pr_err("mdss_dsi_bl_register_driver() failed!\n");
		return ret;
	}

	return ret;
}
fs_initcall(mdss_dsi_bl_driver_init);

    自定义方法,该方法作为设置背光等级   kernel/msm-3.18/drivers/video/msm/mdss/mdss_dsi_panel.c

#include <linux/io.h>
#include <soc/qcom/clock-local2.h>
#include <linux/clk.h>
... ...
extern struct clk *pclk;
extern struct rcg_clk *gp1_rcg_clk;
#define CBCR_OFFSET 0x0
#define CMD_RCGR_OFFSET 0x4
#define D_OFFSET 0x14
#define GP1_CLK_BASE 0x8000

void mdss_set_gpio_pwm(int level)
{
	int ret;

	ret = clk_set_rate(pclk, 9375);

	if (ret){
		printk("clk set rate fail, ret = %d\n", ret);
	}

	ret = clk_prepare_enable(pclk);
	if (ret){
		printk("%s: clk_prepare error!!!\n", __func__);
	}else{
		printk("%s: clk_prepare success!\n", __func__);
	}

	gp1_rcg_clk = to_rcg_clk(pclk);

	if(level == 0){
		mb();
		writel_relaxed(0x0, *gp1_rcg_clk->base + GP1_CLK_BASE + CBCR_OFFSET);//根据寄存器手册,该寄存器代表clk是否使能,写1代表enable,写0表示disable。当level值为0时将clk关闭。
	}else{
		writel_relaxed(((~(128 * level / 50)) & 0x0ff), *gp1_rcg_clk->base + GP1_CLK_BASE + D_OFFSET ); //128 is the value of N, if just output clock, please remove this line.
//此处的level即为对应的等级,这里只能为1-100,所以我们传值时要对0-255进行转换再使用。另外注意前面有取反的符号,这个是高通定死的,我们写的背光等级是对应值取反。
		mb();
		writel_relaxed(0x3, *gp1_rcg_clk->base+ GP1_CLK_BASE + CMD_RCGR_OFFSET); //RCGR
		mb();
		writel_relaxed(0x1, *gp1_rcg_clk->base + GP1_CLK_BASE + CBCR_OFFSET); //CBCR
	}
}

    添加BL_GPIO控制选项   kernel/msm-3.18/drivers/video/msm/mdss/mdss_dsi.h

enum dsi_panel_bl_ctrl {
	BL_PWM,
	BL_WLED,
	BL_DCS_CMD,
	BL_GPIO,    //gpio控制选项
	UNKNOWN_CTRL,
};

    根据qcom,mdss-dsi-bl-pmic-control-type选择对应的控制方式:

int mdss_panel_parse_bl_settings(struct device_node *np,
			struct mdss_dsi_ctrl_pdata *ctrl_pdata)
{
    ... ...
    }else if (!strcmp(data, "bl_ctrl_gpio")) {
	ctrl_pdata->bklt_ctrl = BL_GPIO;
    } else if (!strcmp(data, "bl_ctrl_pwm")) {
	ctrl_pdata->bklt_ctrl = BL_PWM;
	ctrl_pdata->pwm_pmi = of_property_read_bool(np,
				"qcom,mdss-dsi-bl-pwm-pmi");
    ... ...
}

    在背光控制方法中根据控制方式添加对应的背光控制函数:

static void mdss_dsi_panel_bl_ctrl(struct mdss_panel_data *pdata,
							u32 bl_level)
{
    ... ...
    case BL_PWM:
	mdss_dsi_panel_bklt_pwm(ctrl_pdata, bl_level);
	break;
    case BL_GPIO:
	bl_level = 100*bl_level/255;//将0-255等级划分为0-100
	if(bl_level>99){
		bl_level = 99;
	}
	mdss_set_gpio_pwm(bl_level);
	break;
    ... ...
}

此时我们已经设配完成,已经可以进行正常的背光亮度调节,以及量灭屏操作。

四、修改占空比

    首先看下占空比公式:D/N

    假设我们设置的F(100000, xo, 3, 1, 64),那么M = 1 N = 64 D = 0.5。当N为128时,D最小也可为0.5。此时我们可以看到占空比可以为1-128个等级。

    M = 1 M = 0x1 

    N = 64 NOT_N_M = 0xFFC0 

    D = 0.5 NOT_2D = 0xFFFE

 

Resulting Clock = 0.100 MHz
Error = 0.000 MHz
Duty Cycle = 0.78%
Resulting Jitter = 0.000 ns
Period = 10000.000 ns
Pulse High = 78.125 ns
Pulse Low = 9921.875 ns

 

 

 

  • 4
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
GPIO模拟PWM(脉冲宽度调制)是一种通过在GPIO引脚上快速切换电平来模拟PWM信号的方法。在某些单片机或嵌入式系统中,可能没有硬件支持的PWM功能,但可以使用GPIO来实现类似的效果。 要在GPIO上模拟PWM,你可以按照以下步骤进行操作: 1. 选择一个合适的GPIO引脚作为输出引脚。 2. 使用编程语言(如Python或C)来控制GPIO引脚的电平。 3. 通过控制GPIO引脚的电平持续时间来模拟PWM信号的占空比。 4. 使用一个循环或定时器来控制PWM信号的频率。 下面是一个使用Python进行GPIO模拟PWM的示例代码: ```python import time import RPi.GPIO as GPIO GPIO.setmode(GPIO.BCM) GPIO.setup(18, GPIO.OUT) pwm = GPIO.PWM(18, 100) # 设置频率为100Hz pwm.start(50) # 设置初始占空比为50% try: while True: # 改变占空比 for duty_cycle in range(0, 101, 5): pwm.ChangeDutyCycle(duty_cycle) time.sleep(0.1) for duty_cycle in range(100, -1, -5): pwm.ChangeDutyCycle(duty_cycle) time.sleep(0.1) except KeyboardInterrupt: pass pwm.stop() GPIO.cleanup() ``` 以上代码使用RPi.GPIO库来控制树莓派的GPIO引脚。通过改变`ChangeDutyCycle()`函数的参数,可以改变PWM信号的占空比。循环部分可以让PWM信号在0%到100%之间循环变化。 请注意,GPIO模拟PWM的精度可能不如硬件PWM,并且频率也会受到系统性能的限制。因此,对于需要高精度和稳定性的应用,建议使用支持硬件PWM的设备或模块。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值