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");

Linux驱动-使用软定时器实现PWM输出相关推荐

  1. NXP JN5169使用定时器进行PWM输出和定时功能

    NXP JN5169使用定时器进行PWM输出和定时功能 一.定时器介绍 1.定时器介绍 2.定时器可操作的模式 3.定时器DIO 4.定时器和PWM模式 5.定时器中断 二.实现代码 1.PWM输出 ...

  2. 【STM32】通用定时器的PWM输出(实例:PWM输出)

    STM32F1xx官方资料: <STM32中文参考手册V10>-第14章  通用定时器 通用定时器PWM概述 STM32定时器输出通道引脚 这里以TIM3为例来讲解.STM32的通用定时器 ...

  3. stm32无源蜂鸣器定时器_stm32定时器实现PWM输出控制无源蜂鸣器(HAL)

    (一)PWM概念和原理 脉冲宽度调制(PWM),是英文"Pulse Width Modulation"的缩写,简称脉宽调制,是利用微处理器的数字输出来对模拟电路进行控制的一种非常有 ...

  4. stm32通用定时器的PWM输出

    配置过程:(以TIM3为例,其CH1-CH4为:PA6.PA7.PB0.PB1) 1)开启TIM3时钟,配置4个IO口为复用推挽输出. 2)设置TIM3的ARR和PSC来控制PWM的周期. 3)设置T ...

  5. linux驱动中使用定时器

    我的内核是2.4.18的.Linux的内核中定义了一个定时器的结构: #include<linux/timer.h> struct timer_list { structlist_head ...

  6. AT32(四):TMR——定时器、PWM输出与捕获

    前言 用AT32F421的基础定时器TMR6配合中断实现定时功能,用通用定时器TMR3实现PMW波的输出与捕获.本文记录了测试AT32F4单片机定时器的过程. 环境 VScode ( EIDE + C ...

  7. 10.利用STM32定时器的PWM输出功能,直接获取PWM波形。

    本实验向大家展示如何输出占空比固定的PWM波形. 1.工程的建立: 2.主函数代码: 3.pwm_output.c代码: 4.output.h代码: 5.结果: 6.结果显示不出来的请看上几节的文章, ...

  8. Linux驱动:基于imx6ull分析PWM驱动及backlight屏幕背光驱动

    文章目录 1.PWM驱动 1.1 驱动分析 1.2 使用方法 2.backlight驱动 2.1 驱动分析 2.2 调试方法 1.PWM驱动 1.1 驱动分析 先看设备树文件imx6ull.dtsi的 ...

  9. 从零开始之驱动发开、linux驱动(三十三、PWM子系统)

    内核中三星默认是没选PWM支持的,我们先配置一下: make menuconfig Device Drivers ---> [*] Pulse-Width Modulation (PWM) Su ...

最新文章

  1. Apache Kafka - Schema Registry
  2. php处理heic格式图片,iPhone 照片为heic格式怎么处理?
  3. windows下 cl: 命令行 error D8021 :无效的数值参数“/Wno-cpp”的解决办法
  4. vba 字典_VBA中字典的基础概念及调用方法
  5. Visitor(访问者)--对象行为型模式
  6. java绘制_Java 绘制简单图形的问题
  7. 全开源智睿企业网站管理系统 v11.1.0源码
  8. [十]JavaIO之FilterInputStream FilterOutputStream
  9. iOS地图 -- 定位初使用
  10. scala教程(一)
  11. 智鼎逻辑推理题及答案_PreTalent职场说|2020常见校招笔试题型解析
  12. matlab怎么排序数组,如何在MATLAB中排序结构数组?
  13. win7设置环境变量未生效
  14. 天津智慧路灯+“新基建”示范区建设启动、沪苏通长江公铁大桥
  15. git cz 代替 git commit,让提交信息更加明确
  16. HTML5自学笔记上
  17. 高德地图怎么画圈_点标记-覆盖物-教程-地图 JS API | 高德地图API
  18. 一键卸载oracle11,Oracle11完全卸载
  19. 修改Discuz! X2标题、底部和Archiver页面的版权信息
  20. multisim中运放在哪找

热门文章

  1. 迁移学习系列--迁移学习理论
  2. 深度解析蚂蚁Ant Design的设计原则
  3. (疯狂的石头)你泡妞还真下血本啊铃声 (疯狂的石头)你泡妞...
  4. IDEA热部署插件JRebel使用
  5. 改了包名字就 找不到或无法加载主类 com.xxx.xxx
  6. linux parted分区教程,分区工具parted的详解及常用分区使用方法
  7. cobol 文件相关语句
  8. win10+opencv3.2+vs2015配置
  9. 教你用Python获取qq好友备注名称
  10. Linux-mmap映射物理内存到用户空间