24 Linux PWM 驱动

news2024/11/17 21:28:41

一、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不需要DMA
		pwm1: 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.coloradmin.cn/o/1499286.html

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

相关文章

【易飞】易飞ERP自动审核程序功能

易飞ERP自动审核程序功能 一、 使用场景二、 操作说明三、 安装方式 一、 使用场景 OA系统集成 与第三方OA系统软件集成&#xff0c;在OA软件审核完成后&#xff0c;直接将ERP中的单据审核。MES系统集成 MES系统生成单据写入到易飞ERP中&#xff0c;并需要自动审核单据&#x…

java SSM旅游景点与公交线路查询系统myeclipse开发mysql数据库springMVC模式java编程计算机网页设计

一、源码特点 java SSM旅游景点与公交线路查询系统是一套完善的web设计系统&#xff08;系统采用SSM框架进行设计开发&#xff0c;springspringMVCmybatis&#xff09;&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系…

微服务---Eureka注册中心

目录 一、服务中的提供者与消费者 二、Eureka工作流程 三、搭建Eureka服务 四、服务拉取 五、总结 1.搭建EurekaServer 2.服务注册 3.服务发现 一、服务中的提供者与消费者 服务提供者&#xff1a;一次业务中&#xff0c;被其他微服务调用的服务。即提供接口给其他微服务。…

Leetcode HOT150

55. 跳跃游戏 给你一个非负整数数组 nums &#xff0c;你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。 判断你是否能够到达最后一个下标&#xff0c;如果可以&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 示例 1 …

(十五)【Jmeter】取样器(Sampler)之HTTP请求

简述 操作路径如下: HTTP请求 (HTTP Sampler): 作用:模拟发送HTTP请求并获取响应。配置:设置URL、请求方法、请求参数等参数。使用场景:测试Web应用程序的HTTP接口性能。优点:支持多种HTTP方法和请求参数,适用于大多数Web应用程序测试。缺点:功能较为基础,对于复杂…

STL空间配置器

参考《STL源码剖析-侯捷》一书 (SGI版本STL) 前置 六大组件 空间配置器实现 SGI版本的空间配置器有两个&#xff0c;一个名为allocator&#xff0c;一个名为alloc。前者符合部分标准&#xff0c;但效率不好&#xff0c;只是对operator new和operator delete进行了封装&#…

【Java EE初阶二十九】Linux 系统的学习

当前写的博客系统程序,只是部署在咱们自己的电脑上,其他用户是无法直接访问的.由于 NAT 机制的存在,导致了IP 地址就被分成了 内网 IP 和 外网 IP. 云服务器,包括公司中使用专用服务器,一般都是 Linux 系统&#xff0c;这个系统的使用和 Windows 差异很大.(通过命令行来操作的系…

企业有多套系统需要集成,如何高效率实施项目?

企业有多套系统需要集成&#xff0c;如何解决&#xff1f; 许多企业为了提升业务效率、优化管理流程以及实现数据驱动决策&#xff0c;纷纷引入了多套信息系统。这些系统可能包括ERP&#xff08;企业资源计划&#xff09;、CRM&#xff08;客户关系管理&#xff09;、SCM&#…

时间序列分析技巧(一):根据ACF、PACF进行AR、MA、ARMA模型选择

程序员如何选择职业赛道&#xff1f; &#x1f349;CSDN小墨&晓末:https://blog.csdn.net/jd1813346972 个人介绍: 研一&#xff5c;统计学&#xff5c;干货分享          擅长Python、Matlab、R等主流编程软件          累计十余项国家级比赛奖项&#…

Linux网络内核基础框架

本文了解的具体内容与核心 TCP/IP 协议体系 应用层&#xff1a;为网络用户提供各种服务&#xff0c;例如电子邮件、文件传输等。表示层&#xff1a;为不同主机间的通信提供统一的数据表示形式。会话层&#xff1a;负责信息传输的组织和协调&#xff0c;管理进程会话过程。传输层…

Spark 核心API

核心 API spark core API 指的是 spark 预定义好的算子。无论是 spark streaming 或者 Spark SQL 都是基于这些最基础的 API 构建起来的。理解这些核心 API 也是写出高效 Spark 代码的基础。 Transformation 转化类的算子是最多的&#xff0c;学会使用这些算子就应付多数的数…

惊艳!AI助力,图像放大不再模糊!

文章目录 &#x1f4d6; 介绍 &#x1f4d6;&#x1f3e1; 演示环境 &#x1f3e1;&#x1f4d2; 使用方法/运行效果 &#x1f4d2;&#x1f48b; 效果演示 &#x1f388; 获取方式 &#x1f388;⚓️ 相关链接 ⚓️ &#x1f4d6; 介绍 &#x1f4d6; 在数字时代&#xff0c;图…

【Godot 4.2】Tree控件与TreeItem完全解析

概述 本篇是控件完全解析系列之一&#xff0c;主要总结一下Tree控件与TreeItem的使用。 Tree控件是一个非常强大的控件&#xff0c;尤其是在编写一些相关的程序或编辑器插件时&#xff0c;非常适合展示树形组织的节点型数据。 本篇将从简单的添加根节点&#xff0c;根节点子…

低通滤波器(SMART PLC双线性变换法)

低通滤波器的详细介绍可以参考信号处理专栏,常用链接如下: 1、双线性变换+欧拉后向差分 https://rxxw-control.blog.csdn.net/article/details/128586285https://rxxw-control.blog.csdn.net/article/details/1285862852、博途PLC一阶低通滤波器 https://rxxw-control.blo…

设计模式学习笔记(二):工厂方法模式

一、定义 工厂方法模式&#xff08;Factory Method Pattern&#xff09;是一种创建型设计模式&#xff0c;它提供了一种在不指定具体类的情况下创建对象的方法。工厂方法模式定义了一个用于创建对象的接口&#xff0c;让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟…

大语言模型系列-GPT-3.5(ChatGPT)

文章目录 前言一、GPT-3.5的创新点二、GPT-3.5的训练流程SFT数据集RM数据集PPO数据集 三、ChatGPT的诞生总结 前言 《Training language models to follow instructions with human feedback&#xff0c;2022》 前文提到了GPT-3的缺点&#xff0c;其中最大的问题是&#xff1…

C++ 11 新特性 override和final

一.override和final介绍 在C11中&#xff0c;override和final是两个用于支持继承和多态的重要关键字。它们的具体作用如下&#xff1a; override&#xff1a;这个关键字用于派生类中&#xff0c;以确保虚函数的正确重写。当一个派生类的函数被声明为override时&#xff0c;编译…

【Ubuntu 20.04 / 22.04 LTS】最新 esp-matter SDK 软件编译环境搭建步骤

仓库链接&#xff1a;esp-matter SDK官方软件说明&#xff1a;ESP Matter Programming Guide官方参考文档&#xff1a;使用 Matter-SDK 快速搭建 Matter 环境 (Linux) 环境要求 Ubuntu 20.04 或 Ubuntu22.04网络环境支持访问 Gihub 在安装 esp-matter SDK 软件编译环境之前&a…

基于C++中netCDF库读取.nc数据时的一些坑

本文介绍基于C 语言的netCDF库读取.nc格式的栅格文件时&#xff0c;出现数据无法读取、数据读取错误、无法依据维度提取变量等情况的原因与解决方法。 最近&#xff0c;由于需要读取ERA5气象数据&#xff0c;因此使用C语言中的netCDF库读取.nc格式文件&#xff1b;这其中也是踩…

Docker基础教程 - 7 容器数据卷

更好的阅读体验&#xff1a;点这里 &#xff08; www.doubibiji.com &#xff09; 7 容器数据卷 什么是容器卷&#xff0c;为什么需要容器卷&#xff1f; 我们在运行容器的时候&#xff0c;产生的数据都是保存在容器内部的。如果使用Docker来运行mysql容器&#xff0c;数据…