600字范文,内容丰富有趣,生活中的好帮手!
600字范文 > Linux驱动-使用软定时器实现PWM输出

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

时间:2019-08-19 02:18:29

相关推荐

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/pwmchip0echo 0 > export #导入pwm通道0cd ./pwm0echo 500 > period #设置周期为500msecho 250 > duty_cycle #设置占空比为250msecho 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");

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。