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信号,node
为pwm-soft
设备树节点;period_timer
和duty_timer
这两个定时器的作用是控制pwm的周期和占空比;period
和duty
为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-soft
的compatible
和匹配表的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_add
将pwm_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->period
和pwm_soft->duty
,需要注意的是,由于使用的是内核软定时器实现pwm输出,其时间精度较低,在此以1ms为单位。另外,duty_ns = period_ns - 1
目的是防止满占空比的时候两个定时器同时到到,造成输出电平逻辑错误。pwm_soft_enable
和pwm_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_enable
和pwm_soft_disable
的实现很简单,就是设置gpio默认输出电平和对两个定时器进行启动和删除。
在pwm_soft_probe
函数中,设置的周期和占空比的超时回调函数为:period_timer_function
,duty_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_add
将pwm_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输出相关推荐
- NXP JN5169使用定时器进行PWM输出和定时功能
NXP JN5169使用定时器进行PWM输出和定时功能 一.定时器介绍 1.定时器介绍 2.定时器可操作的模式 3.定时器DIO 4.定时器和PWM模式 5.定时器中断 二.实现代码 1.PWM输出 ...
- 【STM32】通用定时器的PWM输出(实例:PWM输出)
STM32F1xx官方资料: <STM32中文参考手册V10>-第14章 通用定时器 通用定时器PWM概述 STM32定时器输出通道引脚 这里以TIM3为例来讲解.STM32的通用定时器 ...
- stm32无源蜂鸣器定时器_stm32定时器实现PWM输出控制无源蜂鸣器(HAL)
(一)PWM概念和原理 脉冲宽度调制(PWM),是英文"Pulse Width Modulation"的缩写,简称脉宽调制,是利用微处理器的数字输出来对模拟电路进行控制的一种非常有 ...
- stm32通用定时器的PWM输出
配置过程:(以TIM3为例,其CH1-CH4为:PA6.PA7.PB0.PB1) 1)开启TIM3时钟,配置4个IO口为复用推挽输出. 2)设置TIM3的ARR和PSC来控制PWM的周期. 3)设置T ...
- linux驱动中使用定时器
我的内核是2.4.18的.Linux的内核中定义了一个定时器的结构: #include<linux/timer.h> struct timer_list { structlist_head ...
- AT32(四):TMR——定时器、PWM输出与捕获
前言 用AT32F421的基础定时器TMR6配合中断实现定时功能,用通用定时器TMR3实现PMW波的输出与捕获.本文记录了测试AT32F4单片机定时器的过程. 环境 VScode ( EIDE + C ...
- 10.利用STM32定时器的PWM输出功能,直接获取PWM波形。
本实验向大家展示如何输出占空比固定的PWM波形. 1.工程的建立: 2.主函数代码: 3.pwm_output.c代码: 4.output.h代码: 5.结果: 6.结果显示不出来的请看上几节的文章, ...
- Linux驱动:基于imx6ull分析PWM驱动及backlight屏幕背光驱动
文章目录 1.PWM驱动 1.1 驱动分析 1.2 使用方法 2.backlight驱动 2.1 驱动分析 2.2 调试方法 1.PWM驱动 1.1 驱动分析 先看设备树文件imx6ull.dtsi的 ...
- 从零开始之驱动发开、linux驱动(三十三、PWM子系统)
内核中三星默认是没选PWM支持的,我们先配置一下: make menuconfig Device Drivers ---> [*] Pulse-Width Modulation (PWM) Su ...
最新文章
- Apache Kafka - Schema Registry
- php处理heic格式图片,iPhone 照片为heic格式怎么处理?
- windows下 cl: 命令行 error D8021 :无效的数值参数“/Wno-cpp”的解决办法
- vba 字典_VBA中字典的基础概念及调用方法
- Visitor(访问者)--对象行为型模式
- java绘制_Java 绘制简单图形的问题
- 全开源智睿企业网站管理系统 v11.1.0源码
- [十]JavaIO之FilterInputStream FilterOutputStream
- iOS地图 -- 定位初使用
- scala教程(一)
- 智鼎逻辑推理题及答案_PreTalent职场说|2020常见校招笔试题型解析
- matlab怎么排序数组,如何在MATLAB中排序结构数组?
- win7设置环境变量未生效
- 天津智慧路灯+“新基建”示范区建设启动、沪苏通长江公铁大桥
- git cz 代替 git commit,让提交信息更加明确
- HTML5自学笔记上
- 高德地图怎么画圈_点标记-覆盖物-教程-地图 JS API | 高德地图API
- 一键卸载oracle11,Oracle11完全卸载
- 修改Discuz! X2标题、底部和Archiver页面的版权信息
- multisim中运放在哪找