Linux驱动之利用STM32、设备树、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资源的生命周期,减少了因为手动管理资源而导致的错误。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值