[Linux驱动]GPIO模拟PWM

一、前言

在开发中,有时候已经定版了,但是想做多一路pwm来调节某种功能,这个时候需要用到GPIO模拟pwm。

二、dts编写

blacklight {
		compatible = "gpio-backlight";
		pinctrl-names = "default";
		label = "BLACKLIGHT1";
		gpio-sim = <&pio PB 4 GPIO_ACTIVE_HIGH>;
		status = "Okay";
	};

三、驱动编写

#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/adc.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/io.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/pm_wakeirq.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 int timer_cnt = 0;
static struct hrtimer pwm_timer1;
static ktime_t pwm_period;

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 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;
	}
	gblacklight_dev->reg_base = of_iomap(pdev->dev.of_node, 0);
	if (gblacklight_dev->reg_base == 0)
	{
		printk("%s:Failed to ioremap() io memory region.\n", __func__);
		ret = -EBUSY;
	}
	else
		printk("xxkey base: %p !\n", gblacklight_dev->reg_base);

	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();
	if(device_create_file(&pdev->dev, &light_level))
		printk("zwhtest test device create light_level file fail because of erorr\n");
	platform_set_drvdata(pdev, gblacklight_dev);
 
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("zwh");	

这个代码直接拷下来就能用,修改frequency这个值就能调节频率。 用GPIO模拟的PWM频率做不了太高,high_cnt调节该值可以进行GPIO模拟,主要就是占空比的模拟。这两个值可以抽到dts进行解析,这样每次修改就只需要改dts。

四、总结

这篇笔记有GPIO、PWM、定时器的知识,代码逻辑比较简单,需要好好巩固。

  • 7
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
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的设备或模块。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值