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,一经查实,立即删除!

相关文章

【数据结构】一、数据结构的基本概念

基本概念 数据是信息的载体&#xff0c;是描述客观事物属性的数、字符及所有能输入到计算机中并被计算机程序识别和处理的符号的集合。 数据是计算机程序加工的原料。 数据元素是数据的基本单位。通常作为一个整体进行考虑和处理&#xff0c;用一个数据元素描述一个个体。一…

idea拉取代码报错

idea拉取代码报错下面错误 Update failed WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY! Someone could be eavesdropping on you right now (man-in-the-middle attack)! It is also possible that a host k…

华清远见作业第四十三天——FreeRTOS(第一天)

1.总结keil5下载代码和编译代码需要注意的事项 点击魔法棒&#xff0c;在Debug项中&#xff0c;点击setting&#xff0c;检查有无设备连接&#xff0c;把Flash项中的Reset and Run勾选。 2.总结STM32Cubemx的使用方法和需要注意的事项 1、创建新项目 2、配置外设 3、配置时钟…

如何使用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;这时较小的数据就…

第十八天-Scrapy爬虫框架实战(瓜子二手车)

1.创建scrapy项目 首先创建python项目&#xff0c;在项目命令行中执行 #安装依赖 pip3 install scrapy #创建scrapy项目 scrapy startproject scrapy_guazi_demo cd scrapy_guazi_demo scrapy genspider guazi guazi.com 2.item.py 声明字段 class ScrapyGuaziDemoItem(scra…

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…

解读电影级视频生成模型 MovieFactory

Diffusion Models视频生成-博客汇总 前言:MovieFactory是第一个全自动电影生成模型,可以根据用户输入的文本信息自动扩写剧本,并生成电影级视频。其中针对预训练的图像生成模型与视频模型之间的gap提出了微调方法非常值得借鉴。这篇博客详细解读一下这篇论文《MovieFactory:…

基于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的作…

解决Git报错:fatal: detected dubious ownership in repository at

在通过 Git Bash 提交项目代码时输入 git add . 命令后&#xff0c;报错&#xff1a;fatal: detected dubious ownership in repository at 这是因为该项目的所有者与现在的用户不一致 比如说&#xff1a; 该项目的所有者是 Administrator&#xff0c;而当前用户是 YuYang, 那…

java 初始化流程?

Java类的初始化流程是指在类被加载到内存并且链接完成后&#xff0c;执行类的初始化操作的过程。在Java中&#xff0c;类的初始化是在以下情况下触发的&#xff1a; 创建类的实例&#xff1a;当使用new关键字创建类的实例时&#xff0c;会触发类的初始化。 访问类的静态成员&a…

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

出口认证是一种贸易信任背书&#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等其他语言。 …

2575. 找出字符串的可整除数组

2575. 找出字符串的可整除数组 题目链接&#xff1a;2575. 找出字符串的可整除数组 代码如下&#xff1a; class Solution { public:vector<int> divisibilityArray(string word, int m) {vector<int> res;long long num0;for(int i0;i<word.size();i){num(nu…