首页 > 系统相关 >Linux驱动之利用STM32、设备树、pwm子系统实现风扇的分级调控

Linux驱动之利用STM32、设备树、pwm子系统实现风扇的分级调控

时间:2024-07-07 19:58:03浏览次数:14  
标签:__ pwm ret STM32 include fan Linux PWM

Linux驱动之利用STM32、设备树、pwm子系统实现风扇的分级调控

系 统:Linux 5.10.61
开发板:STM32mp157a
硬 件:风扇

一、首先我们需要对PWM和定时器(TIM)的联系简单的做一下了解,具体详细的PWM原理可见PWM原理 PWM频率与占空比详解

​ PWM(脉宽调制)和TIM(定时器)在嵌入式系统中有着紧密的联系。以下是它们之间的主要关系和作用:

(一)、PWM(Pulse Width Modulation)脉宽调制

​ PWM 是一种用于控制模拟电路的数字信号技术。通过调节脉冲的宽度(即高电平持续时间),可以控制平均输出电压,从而实现对设备如电机、LED亮度的调节。PWM 信号的频率和占空比是两个关键参数。

(二)、TIM(Timer)定时器

TIM 是微控制器中用于计时和产生精确时间间隔的硬件模块。它们通常用于以下几个方面:

  • 产生定时中断
  • 计数事件(如外部脉冲)
  • 产生PWM信号

(三)、PWM 和 TIM 的联系

  1. PWM的实现依赖于定时器
    • 定时器可以用于产生周期性的中断,进而改变PWM信号的高电平和低电平持续时间。
    • 在STM32等微控制器中,定时器模块通常具有专门的PWM模式,可以直接生成PWM信号。通过设置定时器的相关寄存器(如自动重装载寄存器ARR和比较寄存器CCR),可以调节PWM信号的频率和占空比。
  2. 定时器配置
    • 频率设置:定时器的频率决定了PWM信号的频率。通过设置定时器的预分频器(Prescaler)和自动重装载寄存器(ARR),可以配置定时器的计数频率。
    • 占空比设置:PWM信号的占空比由定时器的比较寄存器(CCR)决定。通过改变CCR的值,可以调节PWM信号的高电平时间。

二、编写设备树

1、查看设备原理图,查看风扇的连接方式

硬件连接:
在这里插入图片描述

拓展版:

在这里插入图片描述

开发板:
在这里插入图片描述

上图我们可以看到,我们的风扇模块连接是这样的:风扇—TIM1—PE9—PWM(AF1)功能 到了PE9这个引脚上

内存地址映射:0x44000000

在这里插入图片描述

引脚复用:
在这里插入图片描述
上图可以看到想要用到pwm(这里是tim1控制)需要将PE9复用为AF1。

下面我们看着原理图以及芯片手册简单的画一下我们的连接图:
在这里插入图片描述

2、查看厂商设备树,分析填写内容

先看厂商提供的帮助文档,这是为了让我们知道,我们想建立一个pwm节点都需要得到哪些信息:

帮助文档路径:xxx@xxx:xxxx/linux-5.10.61/Documentation/devicetree/bindings/pwm
在这里插入图片描述

读帮助文档得知,我们的pwm节点应该像这样:

pwm1: pwm@fe510000 {                                // 定义名为pwm1的PWM控制器节点,地址为0xfe510000
    compatible = "st,pwm";                          // 指定该设备与"st,pwm"驱动程序兼容
    reg = <0xfe510000 0x68>;                        // 定义寄存器基地址0xfe510000和大小0x68
    #pwm-cells = <2>;                               // 指定pwm-cells的数量为2,即每个PWM描述符包含两个参数
    pinctrl-names = "default";                      // 定义引脚控制状态名为"default"
    pinctrl-0 = <&pinctrl_pwm1_chan0_default        // 引脚控制组0,包含四个默认状态的引脚配置
                 &pinctrl_pwm1_chan1_default
                 &pinctrl_pwm1_chan2_default
                 &pinctrl_pwm1_chan3_default>;
    clocks = <&clk_sysin>;                          // 指定该PWM控制器使用的时钟源为clk_sysin
    clock-names = "pwm";                            // 定义时钟的名称为"pwm"
    st,pwm-num-chan = <4>;                          // 定义该PWM控制器拥有4个PWM通道
    st,capture-num-chan = <2>;                      // 定义该PWM控制器拥有2个捕获通道
};

我们去Linux源码目录下的设备树文件中找到我们所需要的信息:
在这里插入图片描述

找与我们板子匹配的设备树,我这里用到的是STM32MP157A的板子,那么我就去找其对应的设备树:

在这里插入图片描述
在这里插入图片描述
由于我们这此设备树中没有找到我们所想要得到的信息,那么我们就去该文件的头文件中去找我们想要的信息:
在这里插入图片描述

上图我们找到了tim1的设备树节点位置,下面我们接着找到PE9复用为PWM的配置信息:
在这里插入图片描述

我们进入到这个xxxxx-pinctrl.dtsi中:
在这里插入图片描述

仿照其原有格式去寻找我们的PE9复用AF1:(‘E’, 9, AF1)我们去ctrl+F去查找我们想要的:
在这里插入图片描述

3、接下来修改我们的设备树,将上面的内容复制到我们的设备树中进行修改:

在根节点下添加:
/{
    ...
        	//描述风扇信息:
	pwm_fan{
		compatible = "wyc , pwm_fan";
		status = "okay";
		//描述引脚
		pwms = <&pwm1 0 100 0>;

	}    
};
在根节点外引用我们的厂商节点进行修改:
&timers1 {
	/delete-property/dmas;
	/delete-property/dma-names;
	
	status = "okay";
	
	pwm1:pwm {
		compatible = "st,stm32-pwm";
		#pwm-cells = <3>;
		status = "okay";
		//添加pwm引脚复用:PE9复用为PWM功能:
		pinctrl-names = "default","sleep";
		pinctrl-0 = <&pwm1_pins_a>;
		pinctrl-1 = <&pwm1_sleep_pins_a>;
	};
};
&pinctrl
{
	pwm1_pins_a: pwm1-0 {
		pins {
			pinmux = <STM32_PINMUX('E', 9, AF1)>; /* TIM1_CH1 */
		};
	};
	pwm1_sleep_pins_a: pwm1-sleep-0 {
		pins {
			pinmux = <STM32_PINMUX('E', 9, ANALOG)>; /* TIM1_CH1 */
		};
	};
}

下面在我们源码目录下执行:make dtbs命令 如下:

在这里插入图片描述

这样我们就得到了添加好设备节树点的设备树

我们将设备树移动到我们的开发板对应目录下

重启开发板

我们的设备树就写好了。

三、写驱动代码

(一)、头文件 :fan_dev.h

#ifndef __FAN_DEV_H__
#define __FAN_DEV_H__

#include <linux/init.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/uaccess.h>
#include <linux/fs.h>
#include <linux/pwm.h>
#include <linux/err.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <asm/io.h>
#include <linux/types.h>

#define NO_ONE _IO('f', 1)    //低速命令
#define NO_TWO _IO('f', 2)    //高速命令
#define NO_THREE _IO('f', 3)  //极速命令

#endif

(二)、驱动函数 : fan_dev.c

#include "../include/fan_dev.h"

#define NAME "pwm_fan_mod" // 定义设备名称常量

// 全局变量定义
struct class *cls = NULL;        // 类指针,用于设备类创建
struct device *dev = NULL;       // 设备指针,用于设备创建
struct device_node *node = NULL; // 设备树节点指针
struct pwm_device *pwm_dev;      // PWM设备指针
int major;                       // 主设备号

// 打开设备的函数定义
int fan_open(struct inode *inode, struct file *file)
{
    printk("%s,%s,%d\n", __FILE__, __func__, __LINE__); // 打印文件名、函数名和代码行号
    return 0;
}

// 关闭设备的函数定义
int fan_close(struct inode *inode, struct file *file)
{
    printk("%s,%s,%d\n", __FILE__, __func__, __LINE__);
    return 0;
}

// 设备IO控制函数
long fan_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
    int ret;
    printk("%s,%s,%d\n", __FILE__, __func__, __LINE__);

    // 根据cmd执行相应的操作
    switch (cmd)
    {
    case NO_ONE: // 低速
        // 设置PWM占空比和周期
        ret = pwm_config(pwm_dev, 350000, 1000000);
        if (ret < 0)
        {
            printk("pwm_config error\n");
            return ret;
        }
        // 使能PWM输出
        ret = pwm_enable(pwm_dev);
        if (ret < 0)
        {
            printk("pwm_enable error\n");
            return ret;
        }
        break;
    case NO_TWO: // 高速
        // 设置PWM占空比和周期
        ret = pwm_config(pwm_dev, 600000, 1000000);
        if (ret < 0)
        {
            printk("pwm_config error\n");
            return ret;
        }
        // 使能PWM输出
        ret = pwm_enable(pwm_dev);
        if (ret < 0)
        {
            printk("pwm_enable error\n");
            return ret;
        }
        break;
    case NO_THREE: // 极速
        // 设置PWM占空比和周期
        ret = pwm_config(pwm_dev, 900000, 1000000);
        if (ret < 0)
        {
            printk("pwm_config error\n");
            return ret;
        }
        // 使能PWM输出
        ret = pwm_enable(pwm_dev);
        if (ret < 0)
        {
            printk("pwm_enable error\n");
            return ret;
        }
        break;
    default:
        break;
    }

    return 0;
}

// 文件操作函数集合
struct file_operations fops =
    {
        .open = fan_open,
        .release = fan_close,
        .unlocked_ioctl = fan_ioctl,
};

// 驱动模块初始化函数
int __init fan_pwm_init(void)
{
    int ret = 0;
    // 从设备树中获取节点
    node = of_find_node_by_name(NULL, "pwm_fan_mod");
    if (NULL == node)
    {
        printk("of_find_node_by_name error\n");
        ret = -ENODATA;
        goto err;
    }
    // 注册字符设备驱动
    major = register_chrdev(0, NAME, &fops);
    if (major < 0)
    {
        printk("register_chrdev error\n");
        ret = major;
        goto err;
    }

    // 创建设备类
    cls = class_create(THIS_MODULE, NAME);
    if (IS_ERR(cls))
    {
        printk("class_create error\n");
        ret = PTR_ERR(cls);
        goto err_class;
    }

    // 创建设备
    dev = device_create(cls, NULL, MKDEV(major, 0), NULL, "pwm_fan");
    if (IS_ERR(dev))
    {
        printk("device_create error\n");
        ret = PTR_ERR(dev);
        goto err_device;
    }

    // 获取PWM设备资源
    pwm_dev = devm_of_pwm_get(dev, node, NULL);
    if (IS_ERR(pwm_dev))
    {
        printk("pwm_get error\n");
        ret = PTR_ERR(pwm_dev);
        goto err_pwm;
    }

    printk("Fan PWM driver ok\n");
    return 0;

err_pwm:
    device_destroy(cls, MKDEV(major, 0));
err_device:
    class_destroy(cls);
err_class:
    unregister_chrdev(major, NAME);
err:
    return ret;
}

// 驱动模块卸载函数
void __exit fan_pwm_exit(void)
{
    pwm_disable(pwm_dev);                 // 禁用PWM
    device_destroy(cls, MKDEV(major, 0)); // 销毁设备
    class_destroy(cls);                   // 销毁设备类
    unregister_chrdev(major, NAME);       // 注销字符设备
    printk("Fan PWM driver exited\n");
}

module_init(fan_pwm_init);
module_exit(fan_pwm_exit);

MODULE_LICENSE("GPL"); // 指明驱动许可证类型

(三)、应用程序 :fan_main.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdbool.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>

#define NO_ONE _IO('f', 1)    //低速命令
#define NO_TWO _IO('f', 2)    //高速命令
#define NO_THREE _IO('f', 3)  //极速命令

int main(int argc, char const *argv[])
{
    int fd = open("/dev/pwm_fan", O_WRONLY);
    int nbytes = 0;
    int flag = 0;

    while (true)
    {
        printf("请选择档位1.低速,2.高速,3.极速:\n");
        scanf("%d", &flag);
        switch (flag)
        {
        case 1:
            printf("1\n");
            nbytes = ioctl(fd, NO_ONE);
            if (nbytes == -1)
            {
                perror("write err");
                return -1;
            }

            break;
        case 2:
            nbytes = ioctl(fd, NO_TWO);
            if (nbytes == -1)
            {
                perror("write err");
                return -1;
            }

            break;
        case 3:
            nbytes = ioctl(fd, NO_THREE);
            if (nbytes == -1)
            {
                perror("write err");
                return -1;
            }

            break;
        default:
            break;
        }
    }
    close(fd);
    return 0;
}

四、可能出现的错误:

1、在加载驱动时可能出现错误,驱动加载不上
     原因:
	这大概率是编译系统源码时没有选配板子的PWM功能
   解决办法:
    	进入我们开发板的源码目录下,执行命令:make menuconfig
    	然后参考自己板子的手册,开启PWM,TIM对应的功能
2、在PWM子系统在注册资源时使用pwm_get可能会在加载驱动时出现pwm_get错误
    原因:
    这是因为pwm_get是手动注册管理资源,可能需要更精确的错误处理和资源清理策略,如果调用pwm_get后没有正确配置或启用PWM设备(或者没有正确处理错误),可能导致后续资源申请失败。
    解决办法:
    在PWM子系统中,注册资源函数有devm_of_pwm_get和pwm_get,我们选用devm_of_pwm_get来注册资源,devm_of_pwm_get通过设备管理器自动处理PWM资源的生命周期,减少了因为手动管理资源而导致的错误。

标签:__,pwm,ret,STM32,include,fan,Linux,PWM
From: https://blog.csdn.net/weixin_70520957/article/details/140250941

相关文章

  • Linux系统之 — 线程
    Linux系统之—线程线程介绍线程使用死锁(Deadlock)竞态条件(RaceCondition)线程使用示例服务器端代码示例服务器端示例拆解1.引入头文件和宏定义2.定义全局变量3.定义线程函数4.主函数5.错误处理和资源释放客户端代码示例客户端示例拆解1.引入必要的头文件2.定......
  • RedHat7.4—配置远程管理Linux服务器
    公司的Linux服务器需24小时不间断工作,维护工程师希望通过设置能远程管理和维护服务器。要求通过SSH及VNC远程管理,实现如下配置需求:可以通过SSH服务访问远程主机,可以使用证书登录远程主机,不需要输入远程主机的用户名和密码可以使用VNC服务访问远程主机,使用图形界面访问,桌面端口......
  • Linux 提权-SUID/SGID_2
    本文通过Google翻译SUID|SGIDPart-2–LinuxPrivilegeEscalation这篇文章所产生,本人仅是对机器翻译中部分表达别扭的字词进行了校正及个别注释补充。导航0前言1上文回顾2枚举自定义SUID二进制文件3利用SUID二进制文件–共享对象注入3.1使用Strin......
  • 如何在 Linux 中使用 ACL、chmod 和 chown 进行文件权限控制
    在Linux系统中,ACL(访问控制列表)、chown和chmod是管理文件和目录权限的主要工具。本文将详细介绍如何在CentOS中使用这些工具进行权限控制,并对它们的区别和具体用法进行说明。1.基本概念ACL(AccessControlList)ACL(AccessControlList)是一种用于控制文件和目录访问......
  • Linux进程(1)(结构-操作系统-进程)
    目录1.体系结构 2.操作系统(OperatorSystem)1)概念:2)结构示意图(不完整)3)尝试理解操作系统4)系统调用和库函数概念3.认识进程1.启动2.进程创建的代码方式---重操作,轻原理1)创进程2)我们为什么要创建子进程呢? 1.体系结构输入设备:键盘、鼠标、摄像头、话筒、磁盘、......
  • 在Linux中,当你需要给命令绑定⼀个宏或者按键的时候,应该怎么做呢?
    在Linux中,给命令绑定一个宏或者按键通常涉及到使用shell的内置命令或者编辑器(如vim)的宏定义功能。以下是详细的步骤和方法:1.使用bash的bind命令bashshell的bind命令允许你更改bash对键盘上按下的键和键组合的响应方式,从而实现宏或按键的绑定。基本步骤查找按键的字符序列:......
  • 在Linux中,内核调优配置文件名字有哪些?举例几个内核需要优化的参数配置?
    在Linux中,内核调优涉及到对系统内核的各种参数进行优化,以适应不同的工作负载和场景。这些参数主要存储在两个地方:一个是运行时动态可调的/proc/sys目录下的文件,另一个是持久化的配置文件/etc/sysctl.conf。1.内核调优配置文件/etc/sysctl.conf:这是最常用的内核参数配置文件,用......
  • 在Linux中,bash shell 中的 hash 命令有什么作用?
    在bashshell中,hash命令与命令查找和缓存机制紧密相关。当你输入一个命令时,bash需要找到该命令的可执行文件的位置(即其路径)以便执行它。bash有几种方式来完成这个任务,其中之一就是使用哈希表(hashtable)来缓存之前查找过的命令的路径。这就是hash命令发挥作用的地方。1.hash命令......
  • 在Linux中,系统目前有许多正在运行的任务,在不重启机器的条件下,有什么方法可以把所有正
    在Linux中,可以使用kill命令来终止正在运行的进程。要移除所有正在运行的进程,可以结合使用ps命令和xargs命令来实现。首先,我们可以使用ps命令列出所有正在运行的进程,然后通过管道(|)将结果传递给xargs命令,最后使用kill命令终止这些进程。具体操作如下:打开终端。输入以下命令:ps......
  • 在Linux中,哪⼀个bash内置命令能够进行数学运算?
    在Linux中,bashshell提供了多种方式进行数学运算,但严格来说,bash本身并没有一个专门的内置命令专门用于数学运算,而是通过一些特殊的语法和命令组合来实现。以下是一些常见的bash中进行数学运算的方法:1.使用$((expression))进行算术扩展这是bash中推荐的标准处理方法,用于执行基本......