Linux驱动-使用软定时器实现PWM输出

Linux驱动-使用软定时器实现PWM输出

​ 在没有pwm外设的情况下,可以使用定时器+GPIO的方法来实现pwm输出,实现pwm频率和占空比可控的功能。本文实现了一个Linux内核驱动,使用两个软定时器来实现pwm输出功能。但是受软定时器时间精度的影响,这种方式实现的pwm输出频率不可能非常高,其占空比可调粒度也较小,但是对于一般的风扇控制是足够了。

1. 设备树节点

/ {
	pwm-soft {
        compatible = "thj,pwm-soft";    /* compatible属性 */
        gpios = <&gpio0 8 0>;           /* pwm输出gpio */
        npwm = <1>;                     /* pwm通道个数 */
    };
};

pwm-soft节点的compatible是驱动匹配属性,gpios是输出pwm使用的gpio,这个可以根据具体的硬件平台自行配置,npwm是pwm的通道个数。

2. 内核驱动实现

​ 该驱动文件名为pwm-soft.c,采用platform平台驱动进行编写,驱动的具体实现采用内核的pwm驱动框架。将产生pwm所需要的资源封装成一个结构进行统一管理:

struct pwm_soft_dev {
	struct pwm_chip chip; 			 /* pwm设备 */
	int gpio;                        /* pwm输出gpio */
	struct device_node *node;        /* pwm设备节点 */
	struct timer_list period_timer;  /* 周期定时器 */
	struct timer_list duty_timer;    /* 占空比定时器 */
	unsigned int period;             /* pwm周期 */
	unsigned int duty;               /* pwm占空比 */
};

其中,chip成员是内核pwm驱动框架的注册对象。gpio是用于输出pwm信号,nodepwm-soft设备树节点;period_timerduty_timer这两个定时器的作用是控制pwm的周期和占空比;periodduty为pwm周期和占空比的时间,单位为毫秒,这是因为Linux内核软定时器精度较低。

驱动的实现采用了platform框架,在加载驱动的时候会根据of_match_table中的compatible属性与设备树中的compatible属性进行匹配,如果匹配成功则probe函数将会执行,platform_driver的注册如下:

static const struct of_device_id pwm_soft_of_match[] = {
	{.compatible = "thj,pwm-soft"},
	{}
};
MODULE_DEVICE_TABLE(of, pwm_soft_of_match);

static struct platform_driver pwm_soft_driver = {
	.driver = {
		.name = "pwm-soft",
		.of_match_table = pwm_soft_of_match
	},
	.probe = pwm_soft_probe,
	.remove = pwm_soft_remove
};
module_platform_driver(pwm_soft_driver);

​ 驱动的匹配表为pwm_soft_of_match,设备树节点pwm-softcompatible和匹配表的compatible匹配成功后,pwm_soft_probe就会主动执行。

​ 在pwm_soft_probe中的主要工作就是,获取设备树节点中指定的pwm输出gpio,初始化周期和占空比定时器,注册pwm设备。

int pwm_soft_probe(struct platform_device *pdev) {

	struct pwm_soft_dev *pwm_soft;
	int ret;

	/* 申请pwm_soft_dev对象 */
	pwm_soft = devm_kzalloc(&pdev->dev, sizeof(struct pwm_soft_dev), GFP_KERNEL);
	if(!pwm_soft) {
		printk(KERN_ERR"pwm_soft:failed malloc pwm_soft_dev\n");
		return -ENOMEM;
	}
		
	/* 获取pwm-soft设备树节点 */
	pwm_soft->node = of_find_node_by_path("/pwm-soft");
	if(pwm_soft->node == NULL) {
		printk(KERN_ERR"pwm_soft:failed to get pwm-soft node\n");
		return -EINVAL;
	}

	/* 获取pwm输出gpio */
	pwm_soft->gpio = of_get_named_gpio(pwm_soft->node, "gpios", 0);
	if(!gpio_is_valid(pwm_soft->gpio)) {
		printk(KERN_ERR"pwm_soft:failed to get pwm gpio\n");
		return -EINVAL;
	}

	/* 申请gpio */
	ret = gpio_request(pwm_soft->gpio, "pwm-gpio");
	if(ret) {
		printk(KERN_ERR"pwm_soft:failed to request pwm gpio\n");
	}

	/* 设置管脚为输出模式 */
	gpio_direction_output(pwm_soft->gpio, 0);
	
	/* 初始化周期控制定时器 */
	init_timer(&pwm_soft->period_timer);
	pwm_soft->period_timer.function = period_timer_function;
	pwm_soft->period_timer.data = (unsigned long)pwm_soft;

	/* 初始化占空比控制定时器 */
	init_timer(&pwm_soft->duty_timer);
	pwm_soft->duty_timer.function = duty_timer_function;
	pwm_soft->duty_timer.data = (unsigned long)pwm_soft;

	/* 注册pwm设备 */
	pwm_soft->chip.dev = &pdev->dev;
	pwm_soft->chip.ops = &pwm_soft_ops;
	pwm_soft->chip.base = 0;
	ret = of_property_read_u32(pwm_soft->node, "npwm", &pwm_soft->chip.npwm);
	if(ret) {
		dev_err(&pdev->dev, "failed to read npwm\n");
		return ret;
	}
	ret = pwmchip_add(&pwm_soft->chip);
	if(ret < 0) {
		dev_err(&pdev->dev, "pwmchip_add failed:%d\n", ret);
		return ret;
	}

	platform_set_drvdata(pdev, pwm_soft);
	
	return ret;
}

第47~50行代码是对pwm_chip进行初始化。在内核pwm驱动框架中,用struct pwm_chip来描述一个pwm设备,该数据结构中包含成员struct pwm_ops,这个结构体中的函数为pwm设备的底层操作方法,在编写pwm驱动的时候需要实现这些操作方法。最后使用pwmchip_addpwm_chip注册到pwm核心层。

pwm设备的操作方法定义如下:

static const struct pwm_ops pwm_soft_ops = {
	.owner = THIS_MODULE,
	.config = pwm_soft_config,
	.enable = pwm_soft_enable,
	.disable = pwm_soft_disable
};

可见驱动中并没有实现pwm_ops中的所有函数,只实现了其中几个。其中config用于配置pwm设备的周期和占空比,enable用于使能pwm输出,disable用于关闭pwm输出。

pwm_soft_config实现如下:

int pwm_soft_config(struct pwm_chip *chip, struct pwm_device *pwm,
		  int duty_ns, int period_ns) {

	struct pwm_soft_dev *pwm_soft = to_pwm_soft_dev(chip);

	/* 占空比和周期都以ms为单位 */
	if(duty_ns >= period_ns) {
		duty_ns = period_ns - 1;
	}

	pwm_soft->period = period_ns;
	pwm_soft->duty = duty_ns;

	return 0;
}

在该函数中,主要是将应用层配置的周期period_us和占空比duty_us赋值给pwm_soft->periodpwm_soft->duty

,需要注意的是,由于使用的是内核软定时器实现pwm输出,其时间精度较低,在此以1ms为单位。另外,duty_ns = period_ns - 1目的是防止满占空比的时候两个定时器同时到到,造成输出电平逻辑错误。pwm_soft_enablepwm_soft_disable实现如下:

int pwm_soft_enable(struct pwm_chip *chip, struct pwm_device *pwm) {

	struct pwm_soft_dev *pwm_soft = to_pwm_soft_dev(chip);

	/* gpio拉高 */
	gpio_set_value(pwm_soft->gpio, 1);

	/* 开启周期定时器和占空比定时器 */
	mod_timer(&pwm_soft->period_timer, jiffies + msecs_to_jiffies(pwm_soft->period));
	mod_timer(&pwm_soft->duty_timer, jiffies + msecs_to_jiffies(pwm_soft->duty));
	
	return 0;
}

void pwm_soft_disable(struct pwm_chip *chip, struct pwm_device *pwm) {

	struct pwm_soft_dev *pwm_soft = to_pwm_soft_dev(chip);

	/* 删除定时器 */
	del_timer_sync(&pwm_soft->duty_timer);
	del_timer_sync(&pwm_soft->period_timer);

	/* gpio拉低*/
	gpio_set_value(pwm_soft->gpio, 0);

}

上面函数都用到了to_pwm_soft_dev,其定义如下:

static inline struct pwm_soft_dev *to_pwm_soft_dev(struct pwm_chip *chip) {
	return container_of(chip, struct pwm_soft_dev, chip);
}

container_of这个宏应该很熟悉了,在此不做介绍。该函数的作用是通过chip地址得到自定义数据结构pwm_soft_dev的地址。pwm_soft_enablepwm_soft_disable的实现很简单,就是设置gpio默认输出电平和对两个定时器进行启动和删除。

pwm_soft_probe函数中,设置的周期和占空比的超时回调函数为:period_timer_functionduty_timer_function

static void period_timer_function(unsigned long arg) {
	struct pwm_soft_dev *pwm_soft = (struct pwm_soft_dev *)arg;

	/*设置 gpio为1 */
	gpio_set_value(pwm_soft->gpio, 1);

	/* 重新开启周期定时器和占空比定时器 */
	mod_timer(&pwm_soft->period_timer, jiffies + msecs_to_jiffies(pwm_soft->period));
	mod_timer(&pwm_soft->duty_timer, jiffies + msecs_to_jiffies(pwm_soft->duty));
}

static void duty_timer_function(unsigned long arg) {
	struct pwm_soft_dev *pwm_soft = (struct pwm_soft_dev *)arg;

	/* 设置gpio为0 */
	gpio_set_value(pwm_soft->gpio, 0);
}

这两个超时回调函数就是输出pwm的逻辑,当pwm_soft_enable执行后,gpio被拉高,两个定时器启动。首先超时的是占空比定时器,在这个超时回调中会将gpio拉低,一直保持到周期定时器超时。在周期定时器超时回调中,会将gpio拉高然后根据周期定时时间和占空比定时器时间重启两个定时器,如此循环就实现了pwm输出。

3. 使用

使用内核自带的pwm驱动框架的好处就是,使用非常简单。使用pwmchip_addpwm_chip注册到pwm核心层和使能pwm内核驱动后,会生成/sys/class/pwm/pwmchip0/目录,直接操作其中的文件就可以控制pwm输出,如下:

cd /sys/class/pwm/pwmchip0
echo 0 > export             #导入pwm通道0
cd ./pwm0
echo 500 > period           #设置周期为500ms
echo 250 > duty_cycle       #设置占空比为250ms
echo 1 > enable             #使能pwm输出
echo 0 > enable             #禁止pwm输出

上面是通过shell命令进行pwm控制的,也可以使用文件操作函数进行编程,实现pwm控制。

4. 整体代码

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/gpio.h>
#include <linux/timer.h>

struct pwm_soft_dev {
	struct pwm_chip chip; 			 /* pwm设备 */
	int gpio;                        /* pwm输出gpio */
	struct device_node *node;        /* pwm设备节点 */
	struct timer_list period_timer;  /* 周期定时器 */
	struct timer_list duty_timer;    /* 占空比定时器 */
	unsigned int period;             /* pwm周期 */
	unsigned int duty;               /* pwm占空比 */
};

static inline struct pwm_soft_dev *to_pwm_soft_dev(struct pwm_chip *chip) {
	return container_of(chip, struct pwm_soft_dev, chip);
}

int pwm_soft_config(struct pwm_chip *chip, struct pwm_device *pwm,
		  int duty_ns, int period_ns) {

	struct pwm_soft_dev *pwm_soft = to_pwm_soft_dev(chip);

	/* 占空比和周期都以ms为单位 */
	if(duty_ns >= period_ns) {
		duty_ns = period_ns - 1;
	}

	pwm_soft->period = period_ns;
	pwm_soft->duty = duty_ns;

	return 0;
}

int pwm_soft_enable(struct pwm_chip *chip, struct pwm_device *pwm) {

	struct pwm_soft_dev *pwm_soft = to_pwm_soft_dev(chip);

	/* gpio拉高 */
	gpio_set_value(pwm_soft->gpio, 1);

	/* 开启周期定时器和占空比定时器 */
	mod_timer(&pwm_soft->period_timer, jiffies + msecs_to_jiffies(pwm_soft->period));
	mod_timer(&pwm_soft->duty_timer, jiffies + msecs_to_jiffies(pwm_soft->duty));
	
	return 0;
}

void pwm_soft_disable(struct pwm_chip *chip, struct pwm_device *pwm) {

	struct pwm_soft_dev *pwm_soft = to_pwm_soft_dev(chip);

	/* 删除定时器 */
	del_timer_sync(&pwm_soft->duty_timer);
	del_timer_sync(&pwm_soft->period_timer);

	/* gpio拉低*/
	gpio_set_value(pwm_soft->gpio, 0);

}

static const struct pwm_ops pwm_soft_ops = {
	.owner = THIS_MODULE,
	.config = pwm_soft_config,
	.enable = pwm_soft_enable,
	.disable = pwm_soft_disable
};

static void period_timer_function(unsigned long arg) {
	struct pwm_soft_dev *pwm_soft = (struct pwm_soft_dev *)arg;

	/*设置 gpio为1 */
	gpio_set_value(pwm_soft->gpio, 1);

	/* 重新开启周期定时器和占空比定时器 */
	mod_timer(&pwm_soft->period_timer, jiffies + msecs_to_jiffies(pwm_soft->period));
	mod_timer(&pwm_soft->duty_timer, jiffies + msecs_to_jiffies(pwm_soft->duty));
}

static void duty_timer_function(unsigned long arg) {
	struct pwm_soft_dev *pwm_soft = (struct pwm_soft_dev *)arg;

	/* 设置gpio为0 */
	gpio_set_value(pwm_soft->gpio, 0);
}

int pwm_soft_probe(struct platform_device *pdev) {

	struct pwm_soft_dev *pwm_soft;
	int ret;

	/* 申请pwm_soft_dev对象 */
	pwm_soft = devm_kzalloc(&pdev->dev, sizeof(struct pwm_soft_dev), GFP_KERNEL);
	if(!pwm_soft) {
		printk(KERN_ERR"pwm_soft:failed malloc pwm_soft_dev\n");
		return -ENOMEM;
	}
		
	/* 获取pwm-soft设备树节点 */
	pwm_soft->node = of_find_node_by_path("/pwm-soft");
	if(pwm_soft->node == NULL) {
		printk(KERN_ERR"pwm_soft:failed to get pwm-soft node\n");
		return -EINVAL;
	}

	/* 获取pwm输出gpio */
	pwm_soft->gpio = of_get_named_gpio(pwm_soft->node, "gpios", 0);
	if(!gpio_is_valid(pwm_soft->gpio)) {
		printk(KERN_ERR"pwm_soft:failed to get pwm gpio\n");
		return -EINVAL;
	}

	/* 申请gpio */
	ret = gpio_request(pwm_soft->gpio, "pwm-gpio");
	if(ret) {
		printk(KERN_ERR"pwm_soft:failed to request pwm gpio\n");
	}

	/* 设置管脚为输出模式 */
	gpio_direction_output(pwm_soft->gpio, 0);
	
	/* 初始化周期控制定时器 */
	init_timer(&pwm_soft->period_timer);
	pwm_soft->period_timer.function = period_timer_function;
	pwm_soft->period_timer.data = (unsigned long)pwm_soft;

	/* 初始化占空比控制定时器 */
	init_timer(&pwm_soft->duty_timer);
	pwm_soft->duty_timer.function = duty_timer_function;
	pwm_soft->duty_timer.data = (unsigned long)pwm_soft;

	/* 注册pwm设备 */
	pwm_soft->chip.dev = &pdev->dev;
	pwm_soft->chip.ops = &pwm_soft_ops;
	pwm_soft->chip.base = 0;
	ret = of_property_read_u32(pwm_soft->node, "npwm", &pwm_soft->chip.npwm);
	if(ret) {
		dev_err(&pdev->dev, "failed to read npwm\n");
		return ret;
	}
	ret = pwmchip_add(&pwm_soft->chip);
	if(ret < 0) {
		dev_err(&pdev->dev, "pwmchip_add failed:%d\n", ret);
		return ret;
	}

	platform_set_drvdata(pdev, pwm_soft);
	
	return ret;
}

int pwm_soft_remove(struct platform_device *pdev) {

	struct pwm_soft_dev *pwm_soft = platform_get_drvdata(pdev);

	/* 删除定时器 */
	del_timer_sync(&pwm_soft->duty_timer);
	del_timer_sync(&pwm_soft->period_timer);

	/* 释放gpio */
	gpio_set_value(pwm_soft->gpio, 0);
	gpio_free(pwm_soft->gpio);
	
	/* 卸载pwm设备 */
	return pwmchip_remove(&pwm_soft->chip);
}

static const struct of_device_id pwm_soft_of_match[] = {
	{.compatible = "thj,pwm-soft"},
	{}
};
MODULE_DEVICE_TABLE(of, pwm_soft_of_match);


static struct platform_driver pwm_soft_driver = {
	.driver = {
		.name = "pwm-soft",
		.of_match_table = pwm_soft_of_match
	},
	.probe = pwm_soft_probe,
	.remove = pwm_soft_remove
};
module_platform_driver(pwm_soft_driver);

MODULE_AUTHOR("thj");
MODULE_DESCRIPTION("use timer create pwm output");
MODULE_LICENSE("GPL");

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值