24 Linux PWM 驱动

一、PWM 驱动简介

  其实在 stm32 中我们就学过了 PWM,这里就是再复习一下。PWM(Pulse Width Modulation),称为脉宽调制,PWM 信号图如下:

  PWM 最关键的两个参数:频率和占空比。

  频率是指单位时间内脉冲信号的周期数。比如开关灯,开关一次算一次周期,在 1s 进行多少次开关(开关一次为一个周期)。

  占空比是指一个周期内高电平时间和低电平时间的比例。也拿开关当作例子,总共 100s,开了 50s 灯(高电平),关了 50s 灯(低电平),这时候的占空比就为 50%(比例)。

1. 设备树下的 PWM 控制器节点

① 定时器

  PWM 其实就是由定时器来产生,STM32MP157总共有很多定时器。

  TIM1/TIM8:有两个 16 位高级定时器,主要用于电机控制。每个定时器支持 4 通道 PWM 信号。

  TIM2/TIM3/TIM4/TIM5:这四个是通用定时器,TIM3/TIM4 是 16 位定时器,TIM2/TIM5是 32 位定时器。每个定时器支持 4 通道 PWM 信号。

  TIM15/TIM16/TIM17: 这 3 个也都是 16 位的通用定时器, TIM15 支持 2 通道的 PWM 信号, TIM16/TIM17 每个定时器支持 1 通道的 PWM 信号。 

  多通道控制 PWM 好处:

  1、独立控制:多通道 PWM 允许每个通道独立地配置和控制,可以针对不同的需求进行个性化设置。

  2、同步控制:通过使用多通道PWM,确保各个通道的PWM信号在时间上保持一致,避免信号间的干扰或不匹配。

② TIM1 简介

  ① 16 位的向上、向下自动加载计数器。

  ② 16 位可编程的预分频器。  

  ③ 6 个独立的通道,这些通道的功能如下:
  — 输入捕获(只有通道 5 和 6 支持)。
  — 输出比较
  — PWM 波形生成(边缘和中间对齐模式)。
  — 单脉冲模式。

  ④ 带有死区的可编程互补输出。

  ⑤ 以下事件可以生成中断或者 DMA:
  — 更新事件,计数器溢出。
  — 触发事件,计数器开始、停止、初始化等。
  — 输入捕获。
  — 输出比较 

③ TIM1 设备节点

  在 Documentation/devicetree/bindings/mfd/stm32-timers.txt 文件夹下可以看到 TIM 在设备树中需要注意的事情。

  1、必须的参数:

  compatible:必须是 "st,stm32-timers"。

  reg:定时器物理寄存器地址,对于 TIM1,地址为 0x44000000,这个是在 STM32MP157 数据手册上的。(我找了半天没找到,有大佬说说在哪吗?)

  clock-names:时钟源名字,设置为 "int"。

  clocks:时钟源。

  

  2、可选参数:

  resets:复位句柄,用来复位定时器控制器。

  dmas:DMA 通道,最多 7 通道 DMA。

  dma-names:DMA 名称列表,必须和 "dmas" 属性匹配,可选的名字有:“ch1”、“ch2”、“ch3”、“ch4”、“up”、“trig”、“com”。 

  3、可选的子节点:

  定时器有很多功能,不同的功能需要不同的子节点表示,可选三种子节点:

  pwm: 描述定时器的 PWM 功能。

  timer: 描述定时器的定时功能。

  counter: 描述定时器的计数功能。 

  现在来看实际的定时器节点,打开 /home/alientek/linux/atk-mpl/linux/my_linux/linux5.4.31/arch/arm/boot/dts/stm32mp151.dtsi 文件,找到 timers1 设备节点。

timers1: timer@44000000 {    // 定义一个timers1的子设备,并且物理地址为44000000#address-cells = <1>;    // 定义该节点子节点地址单元格数量#size-cells = <0>;       // 定义该节点子节点大小单元格数量compatible = "st,stm32-timers";reg = <0x44000000 0x400>;    // 指定寄存器物理地址(物理地址0x44000000,大小0x400)clocks = <&rcc TIM1_K>;      // 指定时钟源clock-names = "int";         // 指定时钟源名称dmas = <&dmamux1 11 0x400 0x80000001>,    // 指定定时器使用的DMA控制器和通道号<&dmamux1 12 0x400 0x80000001>,<&dmamux1 13 0x400 0x80000001>,<&dmamux1 14 0x400 0x80000001>,<&dmamux1 15 0x400 0x80000001>,<&dmamux1 16 0x400 0x80000001>,<&dmamux1 17 0x400 0x80000001>;dma-names = "ch1", "ch2", "ch3", "ch4",    // 指定每个DMA通道名字"up", "trig", "com";status = "disabled";    // 设备未启用pwm {compatible = "st,stm32-pwm";#pwm-cells = <3>;    // 指定PWM单元格数量为3,即占空比、频率和相位角status = "disabled";};timer@0 {compatible = "st,stm32h7-timer-trigger";reg = <0>;status = "disabled";};counter {compatible = "st,stm32-timer-counter";status = "disabled";};};

④ PWM 设备子节点

  打开 Documentation/devicetree/bindings/pwm/pwm-stm32.txt 文件,可以看到 PWM 子节点属性信息:

  compatible:必须是 “st,stm32-pwm”。

  pinctrl-names:设置为 "default",也可以额外添加 "sleep",以在低功率时将引脚设置为睡眠状态。

  pinctrl-n:PWM 引脚 pinctrl 句柄,用来指定 PWM 信号输出引脚。 

  #pwm-cells:设置为 3,即占空比、频率和相位角。

2. PWM 子系统

  Linux 内核提供了 PWM 子系统框架,所以编写 PWM 驱动的时候需要符合这个框架。PWM子系统的核心是 pwm_chip 结构体,定义在文件 include/linux/pwm.h 中:

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;
};

  pwm_ops 结构体就是 PWM 外设的各种操作函数集合,编写 PWM 外设驱动的时候必须要实现。pwm_ops在 pwm.h 头文件中:

struct pwm_ops {int (*request)(struct pwm_chip *chip, struct pwm_device *pwm); /* 请求 PWM */void (*free)(struct pwm_chip *chip, struct pwm_device *pwm); /* 释放 PWM */int (*capture)(struct pwm_chip *chip, struct pwm_device *pwm, struct pwm_capture *result, unsigned long timeout); /* 捕获 PWM 信号 */int (*apply)(struct pwm_chip *chip, struct pwm_device *pwm, const struct pwm_state *state); /* 新的 PWM 配置方法,配置 PWM 周期和占空比 */void (*get_state)(struct pwm_chip *chip, struct pwm_device *pwm, struct pwm_state *state); struct module *owner;/* Only used by legacy drivers */int (*config)(struct pwm_chip *chip, struct pwm_device *pwm, int duty_ns, int period_ns); /* 配置 PWM 周期和占空比 */int (*set_polarity)(struct pwm_chip *chip, struct pwm_device *pwm,enum pwm_polarity polarity);/* 设置 PWM 极性 */int (*enable)(struct pwm_chip *chip, struct pwm_device *pwm);/* 使能 PWM */void (*disable)(struct pwm_chip *chip, struct pwm_device *pwm);/* 关闭 PWM */
};

  pwm_ops 函数不用全部实现,但是配置 PWM 的函数必须全部实现,比如 apply 或 config。apply 函数是新的配置 PWM 方法,config 和 config 之后的函数都是老版本内核所使用的函数。

  PWM 子系统驱动首先得初始化 pwm_chip,之后向内核注册(pwmchip_add)初始化好的 pwm_chip,用完后并且要注销(pwmchip_remove) pwm_chip。

/** @description : 向内核注册 pwm_chip* @param - chip : 要向内核注册的 pwm_chip* @return : 0 成功;负数 失败*/
int pwmchip_add(struct pwm_chip *chip);/*************** 分割线 ***************//** @description : 向内核注销 pwm_chip* @param - chip : 要移除的 pwm_chip* @return : 0 成功;负数 失败*/
int pwmchip_remove(struct pwm_chip *chip);

  PWM 设置就两个方面:频率和占空比。TIM 的 PSC 寄存器是用来设置定时器分频器,当 TIM 时钟源确定以后,设置 PSC 分频值就可以得到 TIM 最终的时钟频率。TIM 的 ARR 寄存器是自动加载寄存器,将 TIM 设置为向下计数器,定时器开启之后每个时钟周期计数器减一,直到计数器减为0。这个时候将 ARR 的值加载到计数器里,计数器会重新倒计时,以此往复。所以 PSC 和 ARR 决定了 PWM 周期值。注意,一个定时器的 PWM 只能设置同一个周期,如果要想多路周期不同的 PWM 信号,那就要使用多个不同的 TIM。

  一个定时器下的 4 路 PWM可以设置不同的占空比,相当于一个定时器下的 4 路 PWM 信号,周期是一样的,但是占空比可以不同。 

二、PWM 驱动编写

1. 修改设备树

  由于使用自带的 PWM 驱动,所以只需要修改设备树即可。这次使用 PA10 引脚,我们需要在设备树里添加 PA10 引脚信息及 TIM1 通道 3 的 PWM 信息。

  打开 /home/alientek/linux/atk-mpl/linux/my_linux/linux-5.4.31/arch/arm/boot/dts/stm32mp15-pinctrl.dtsi 文件,找到 pwm1_pins_a: pwm1-0:

  修改成:

  由于 stm32mp151.dtsi 文件有 "timers1"节点,但这个节点默认为 disable,不能直接使用,所以需要在 stm32mp157d-atk.dts 向 timers 追加一些内容。

  打开 /home/alientek/linux/atk-mpl/linux/my_linux/linux-5.4.31/arch/arm/boot/dts/stm32mp157d-atk.dts 文件,加入以下内容:

&timers1 {status = "okay";/delete-property/dmas;/delete-property/dma-names;		// 这里是把dma和dma-names属性删除,因为PWM不需要DMApwm1: pwm {pinctrl-0 = <&pwm1_pins_a>;pinctrl-1 = <&pwm1_sleep_pins_a>;pinctrl-names = "default", "sleep";#pwm-cells = <2>;	// 现在只有占空比和频率status = "okay";};
};

  最后需要检查设备树中是否有其他外设用到了 PA10 或者 gpioa 10,如果有那就要屏蔽掉。我觉得直接先拿去编译,然后开启开发板,如果有出错的,那就会出现  gpio-keys gpio-keys: failed to get gpio: -16 类似的情况,就去找。

2. 使能 PWM 驱动

  默认是使能的,我们可以看看在哪使能。先进入 linux/atk-mpl/linux/my_linux/linux-5.4.31,输入命令 make menuconfig 进入图形化配置界面。进入以下路径:

-> Device Drivers-> Pulse-Width Modulation (PWM) Support-> <*> STMicroelectronics STM32 PWM     // 选中

3. PWM 驱动测试

① 确定 TIM 的 pwmchipX 文件

  这里因为要使用示波器,我暂时没有所以效果图就没有,但还是看一下流程。在开启开发板之前需要将新编译的设备树文件放在 tftproot 里面。

  开启开发板,第一件事情就是确定 pwmchip 是否属于 TIM1,进入目录 /sys/class/pwm,可以看到 pwmchip0,进入这个目录。

  进入 pwmchip0 目录后会打印出其路径,我们可以看到寄存器起始地址为 0x44000000,所以 pwmchip0 就是对应的 TIM1。

  为什么需要这样复杂的方式来确定 TIM 对应的 pwmchip 文件?原因就是当多个 TIM 的 PWM 功能开启后,pwmchip 文件会发生相应的改变,所以用这种方式来相互对应。

② 调出 pwmchip0 的 pwm2 子目录 

  pwmchip0 是 TIM1 的总目录,TIM1 有 4 路 PWM,每一路都可以单独打开或者关闭,CH1~CH4 对应的编号为 0~3,所以打开 TIM1 的 CH3 输入命令如下:

echo 2 > /sys/class/pwm/pwmchip0/export
# 如果要打开TIM1_CH4的话,那就是修改 echo 2 为 echo 3

③ 设置 PWM 频率

  这里是周期值,单位 ns,假设 20KHz 频率,周期 = 1 / 频率,所以周期 = 50000ns,输入以下命令:

echo 50000 > /sys/class/pwm/pwmchip0/pwm2/period

④ 设置 PWM 占空比

  设置占空比不是直接设置占空比,而是需要设置一个高电平时间,那么低电平时间自然而然就出来了。比如 20KHz 频率下的 20% 占空比。高电平时间 = 周期 * 占空比,高电平时间 = 10000ns。

  命令如下:

echo 10000 > /sys/class/pwm/pwmchip0/pwm2/duty_cycle

⑤ 使能 TIM1 通道3

  注意,一定要先设置了频率和占空比后,才能开启定时器,否则会提示参数出错。命令如下:

echo 1 > /sys/class/pwm/pwmchip0/pwm2/enable

⑥ 极性反转

  我们之前设置的 PWM 占空比为 20%,只需要修改极性就可以把占空比设置为 80%。

  极性反转:

echo "inversed" > /sys/class/pwm/pwmchip0/pwm2/polarity

  恢复极性:

echo "normal" > /sys/class/pwm/pwmchip0/pwm2/polarity

总结

  无论是在学习 STM32 的时候还是现在学习 Linux 驱动的时候,都涉及到了 PWM,它最关键的两个参数就是频率和占空比。计算公式也同样重要。

  这一章学习了设备树中的 TIM 和 PWM 节点设置,并且这一次也是用自带的定时器来驱动 PWM,最后在 PWM 测试的时候需要注意到先设置 频率和占空比 后才能使能。

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

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

相关文章

如何使用c++的PCL库 对Las点云进行重建

在 C 中对点云进行重建通常需要使用一些专门的库和算法。下面是一种常见的方法&#xff0c;使用 PCL&#xff08;点云库&#xff09;进行点云重建&#xff1a; 安装 PCL 库&#xff1a;首先&#xff0c;需要安装 PCL 库。可以在 PCL 的官方网站上找到安装指南和文档。 读取点云…

排序算法:插入排序和希尔排序

一、插入排序 1.基本原理 插入排序&#xff08;英语&#xff1a;Insertion Sort&#xff09;是一种简单直观的排序算法。它的工作原理是通过构建有序序列&#xff0c;对于未排序数据&#xff0c;在已排序序列中从后向前扫描&#xff0c;找到相应位置并插入。插入排序在实现上…

排序算法:冒泡排序和简单选择排序

一、冒泡排序 1.冒泡排序的基本原理 对存放原始数组的数据&#xff0c;按照从前往后的方向进行多次扫描&#xff0c;每次扫描都称为一趟。当发现相邻两个数据的大小次序不符合时&#xff0c;即将这两个数据进行互换&#xff0c;如果从小大小排序&#xff0c;这时较小的数据就…

python将conda环境打入docker环境中

1.假设你本地已经安装好了conda相关的 ubuntu安装python以及conda-CSDN博客 并且已经创建启动过相关的环境&#xff0c;并且install了相关的包。 我本地的conda环境叫做,gptsovits_conda3 2.下载conda打包工具 conda install conda-pack pip install conda-pack 3.打包 con…

蓝桥杯day6队列-3.3

目录 1.约瑟夫环 1.注意&#xff01;q.push(q.front()); 2.机器翻译 3.小桥的神秘礼盒 4.餐厅排队 1.约瑟夫环 今天学习了队列的STL写法&#xff0c;来试试这个题。 #include<bits/stdc.h> using namespace std;int main() {int n,m;cin>>n>>m;queue&l…

基于uniapp cli项目开发的老项目,运行报错path.replace is not a function

项目&#xff1a;基于uniapp cli的微信小程序老项目 问题&#xff1a;git拉取代码&#xff0c;npm安装包时就报错&#xff1b; cnpm能安装成功包&#xff0c;运行报错 三种方法尝试解决&#xff1a; 更改代码&#xff0c;typeof pathstring的话&#xff0c;才走path.replace…

稀疏数组实现

博文主要是自己学习的笔记&#xff0c;供自己以后复习使用&#xff0c; 参考的主要教程是B站的 尚硅谷数据结构和算法 稀疏数组(sparse array) 实际需求&#xff1a;五子棋程序中的存盘退出和续上盘的功能 问题分析&#xff1a; 如果直接用二维数组&#xff0c;很多值是默认…

定时执行专家V7.1 多国语言版本日文版发布 - タスク自動実行ツールV7.1 日本語版リリース

◆ 软件介绍  ソフトの紹介 《定时执行专家》是一款制作精良、功能强大、毫秒精度、专业级的定时任务执行软件。软件具有 25 种【任务类型】、12 种【触发器】触发方式&#xff0c;并且全面支持界面化【Cron表达式】设置。软件采用多线程并发方式检测任务触发和任务执行&…

前端性能优化 | CDN缓存

前言 CDN&#xff08;Content Delivery Network&#xff09;是一种分布式的网络架构&#xff0c;通过在全球各地部署节点服务器来快速传输和分发网络内容。CDN的主要目标是提供快速、可靠的内容传输&#xff0c;以提升用户体验。 本文主要从以下方面讲解CDN 什么是CDNCDN的作…

外贸常用的出口认证 | 全球外贸数据服务平台 | 箱讯科技

出口认证是一种贸易信任背书&#xff0c;对许多外贸从业者而言,产品的出口认证和当前的国际贸易环境一样复杂多变&#xff0c;不同的目标市场、不同的产品类别,所需要的认证及标准也不同。 国际认证 01 IECEE-CB IECEE-CB体系的中文含义是“关于电工产品测试证书的相互认可体…

数据库-第六/七章 关系数据理论和数据库设计【期末复习|考研复习】

前言 总结整理不易&#xff0c;希望大家点赞收藏。 给大家整理了一下数据库系统概论中的重点概念&#xff0c;以供大家期末复习和考研复习的时候使用。 参考资料是王珊老师和萨师煊老师的数据库系统概论(第五版)。 文章目录 前言第六章 关系数据理论6.1 规范化6.2 范式6.3 规范…

C#,入门教程(26)——数据的基本概念与使用方法

上一篇&#xff1a; C#&#xff0c;入门教程(25)——注释&#xff08;Comments&#xff09;你会吗&#xff1f;看多图演示&#xff0c;学真正注释。https://blog.csdn.net/beijinghorn/article/details/124681888 本文所述的知识基本上适用于C/C&#xff0c;java等其他语言。 …

LVS集群 ----------------(直接路由 )DR模式部署

一、LVS集群的三种工作模式 lvs-nat&#xff1a;修改请求报文的目标IP,多目标IP的DNAT lvs-dr&#xff1a;操纵封装新的MAC地址&#xff08;直接路由&#xff09; lvs-tun&#xff1a;隧道模式 lvs-dr 是 LVS集群的 默认工作模式 NAT通过网络地址转换实现的虚拟服务器&…

在分布式环境中使用状态机支持数据的一致性

简介 在本文中&#xff0c;我们将介绍如何在分布式系统中使用transaction以及分布式系统中transaction的局限性。然后我们通过一个具体的例子&#xff0c;介绍了一种通过设计状态机来避免使用transaction的方法。 什么是数据库transaction Transaction是关系型数据普遍支持的…

java SSM流浪宠物救助与领养myeclipse开发mysql数据库springMVC模式java编程计算机网页设计

一、源码特点 java SSM流浪宠物救助与领养管理系统是一套完善的web设计系统&#xff08;系统采用SSM框架进行设计开发&#xff0c;springspringMVCmybatis&#xff09;&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系…

Fragment

1.网格视图(随便插进来一条) 2.Fragment

谷歌开源的LLM大模型 Gemma 简介

相关链接&#xff1a; Hugging face模型下载地址&#xff1a;https://huggingface.co/google/gemma-7bGithub地址&#xff1a;https://github.com/google/gemma_pytorch论文地址&#xff1a;https://storage.googleapis.com/deepmind-media/gemma/gemma-report.pdf官方博客&…

Linux——文件标识符

目录 一、文件基础 二、常见的C语言文件接口 三、系统文件接口 四、理解语言与系统文件操作的关系 五、如何理解一切皆文件 六、文件标识符再理解 一、文件基础 一个空文件&#xff0c;也会占用磁盘空间&#xff0c;这是因为文件不仅仅有存放在里面的内容&#xff0c;还…

Chapter20-Ideal gases-CIE课本要点摘录、总结(编辑中)

20.1 Particles of a gas Brownian motion Fast modules 速率的数值大概了解下&#xff1a; average speed of the molecules:400m/s speed of sound:approximately 330m/s at STP&#xff08;standard temperature and pressure&#xff09; Standard Temperature and Pres…

ROS 2基础概念#5:执行器(Executor)| ROS 2学习笔记

在ROS 2中&#xff0c;Executor是一个核心概念&#xff0c;负责管理节点&#xff08;Node&#xff09;中的回调函数&#xff0c;如订阅消息的回调、服务请求的回调、定时器回调等。Executor决定了何时以及如何执行这些回调&#xff0c;从而在ROS 2系统中实现异步编程。 ROS 2 …