树莓派4B——PWM

树莓派4B——PWM

设备树

bcm2711.dtsi

定义部分:

                pwm1: pwm@7e20c800 {
                        compatible = "brcm,bcm2835-pwm";
                        reg = <0x7e20c800 0x28>;
                        clocks = <&clocks BCM2835_CLOCK_PWM>;
                        assigned-clocks = <&clocks BCM2835_CLOCK_PWM>;
                        assigned-clock-rates = <10000000>;
                        #pwm-cells = <2>;
                        status = "disabled";
                };

compatible 字段表示匹配驱动的名字。
reg表示地址空间,阅读手册的话会发现其实0x7e20c800是PWM1的基地址,0x28表示地址长度,正是下面寄存器的地址范围大小。
在这里插入图片描述
下面还有一些和pwm有关的描述,不清楚具体的含义(应该是pinctrl子系统),但是和这个图有关
在这里插入图片描述

  pwm0_0_gpio12: pwm0_0_gpio12 {
                pin-pwm {
                        pins = "gpio12";
                        function = "alt0";	/* 功能 */
                        bias-disable;		/* 配置状态 */
                };
        };
        pwm0_0_gpio18: pwm0_0_gpio18 {
                pin-pwm {
                        pins = "gpio18";
                        function = "alt5";
                        bias-disable;
                };
        };
        pwm1_0_gpio40: pwm1_0_gpio40 {
                pin-pwm {
                        pins = "gpio40";
                        function = "alt0";
                        bias-disable;
                };
        };
        pwm0_1_gpio13: pwm0_1_gpio13 {
                pin-pwm {
                ...

bcm2711-rpi-4-b.dts

&pwm1 {
        pinctrl-names = "default";	/* 设备的状态,这里只有一种 */
        pinctrl-0 = <&pwm1_0_gpio40 &pwm1_1_gpio41>;	/* 该状态的引脚定义 */
        status = "okay";
};

&pwm1 {
        status = "disabled";
};

这里也不清楚,status决定是否启用,后面的应该会覆盖前面的,将其改为okay。

编译

重新编译设备树:

sudo make dtbs
sudo cp arch/arm64/boot/dts/broadcom/bcm2711-rpi-4-b.dtb /boot
sudo reboot

重启后会在/sys/class/pwm下生成pwmchip0。
在这里插入图片描述

# 引出
echo 1 > /sys/class/pwm/pwmchip0/export
# 设置周期
echo 1000000 > /sys/class/pwm/pwmchip0/pwm1/period
# 设置占空比
echo 500000 > /sys/class/pwm/pwmchip0/pwm1/duty_cycle
# 使能pwm
echo 1 > /sys/class/pwm/pwmchip0/pwm1/enable
# 关闭pwm
echo 0 > /sys/class/pwm/pwmchip0/pwm1/enable 


# pwm0
echo 0 > /sys/class/pwm/pwmchip0/export
echo 1000000 > /sys/class/pwm/pwmchip0/pwm0/period
echo 500000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycle
echo 1 > /sys/class/pwm/pwmchip0/pwm0/enable

找了半天,默认的管脚似乎没有引出来,改为gpio12试下。
在这里插入图片描述
设备树节点改了之后,测试的pwm还是没有效果,不知道啥原因(树莓派的pwm和音频audio似乎是共用的硬件,不知道是不是系统起来后干了啥事情)。
在这里插入图片描述
最后采用的方式是修改/boot/config.txt,在末尾添加:

dtoverlay=pwm-2chan,pin=12,func=4,pin2=13,func2=2

这个方式可以正常的操控pwm管脚。

驱动编写(基于PWM子系统)

在设备树根节点下添加测试节点

/* pwm test */
        my_pwm_led{
                compatible = "pgg,my_pwm_led";

                back{
                        pwms = <&pwm 0 5000000>;
                        pwm-names = "my_pwm_led";
                };
        };

内核驱动:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
//#include <linux/ide.h>
#include <linux/errno.h>
#include <linux/gpio.h>
// #include <asm/mach/map.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/io.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>

#define LED_PWM_CMD_SET_DUTY         0x01
#define LED_PWM_CMD_SET_PERIOD       0x02
#define LED_PWM_CMD_SET_BOTH         0x03
#define LED_PWM_CMD_ENABLE           0x04
#define LED_PWM_CMD_DISABLE          0x05

struct my_pwm_param_struct 
{
    int duty_ns;        /* 占空比 */
    int period_ns;      /* 周期 */
};

struct my_pwm_led_dev_struct 
{
    dev_t dev_no;                    
    struct cdev chrdev;             /* 字符设备 */    
    struct class *led_class;
    struct device_node *dev_node;
    struct pwm_device *led_pwm;     /* pwm设备结构体 */
};

static struct my_pwm_param_struct 	 my_pwm_para;
static struct my_pwm_led_dev_struct   my_pwm_led_dev;

static int red_led_drv_open (struct inode *node, struct file *file)
{
    int ret = 0;
    
	//pwm_set_polarity(my_pwm_led_dev.led_pwm, PWM_POLARITY_INVERSED);
	pwm_enable(my_pwm_led_dev.led_pwm);     /* 使能pwm设备 */

    printk("led_pwm open\r\n");
    return ret;
}

static ssize_t red_led_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
    int err;

    if (size != sizeof(my_pwm_para)) return -EINVAL;

    /* 接收用户空间拷贝过来的数据,也就是write函数的数据 */
	err = copy_from_user(&my_pwm_para, buf, size);
    if (err > 0) return -EFAULT;

	pwm_config(my_pwm_led_dev.led_pwm, my_pwm_para.duty_ns, my_pwm_para.period_ns);     /* 配置pwm */

	return 1;
}

static long _drv_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    int ret = 0;
    void __user *my_user_space = (void __user *)arg;
    
    switch (cmd)
    {
        case LED_PWM_CMD_SET_DUTY:
            ret = copy_from_user(&my_pwm_para.duty_ns, my_user_space, sizeof(my_pwm_para.duty_ns));
            if (ret > 0) 
				return -EFAULT;
            pwm_config(my_pwm_led_dev.led_pwm, my_pwm_para.duty_ns, my_pwm_para.period_ns);
            break;
        case LED_PWM_CMD_SET_PERIOD:
            ret = copy_from_user(&my_pwm_para.period_ns, my_user_space, sizeof(my_pwm_para.period_ns));
            if (ret > 0) 
				return -EFAULT;
            pwm_config(my_pwm_led_dev.led_pwm, my_pwm_para.duty_ns, my_pwm_para.period_ns);
            break;
        case LED_PWM_CMD_SET_BOTH: 
            ret = copy_from_user(&my_pwm_para, my_user_space, sizeof(my_pwm_para));
            if (ret > 0) 
				return -EFAULT;
            pwm_config(my_pwm_led_dev.led_pwm, my_pwm_para.duty_ns, my_pwm_para.period_ns);
            break;
        case LED_PWM_CMD_ENABLE:
            pwm_enable(my_pwm_led_dev.led_pwm);
            break;
        case LED_PWM_CMD_DISABLE:
            pwm_disable(my_pwm_led_dev.led_pwm);
            break;
    }
	return 0;
}

static int red_led_drv_release(struct inode *node, struct file *filp)
{
    int ret = 0;

    pwm_config(my_pwm_led_dev.led_pwm, 0, 5000);
    printk("led pwm dev close\r\n");
//    pwm_disable(my_pwm_led_dev.led_pwm);
    return ret;
}

static struct file_operations red_led_drv = {
	.owner	 = THIS_MODULE,
	.open    = red_led_drv_open,
	.write   = red_led_drv_write,
    .unlocked_ioctl = _drv_ioctl,
    .release  = red_led_drv_release,
};



static int led_red_driver_probe(struct platform_device *pdev)
{
    int err;
    int ret;
    struct device *tdev;
    struct device_node *child;

	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	
    tdev = &pdev->dev;
    child = of_get_next_child(tdev->of_node, NULL);      /* 获取设备树子节点 */
	if (!child) 
	{
        return -EINVAL;
    }

    my_pwm_led_dev.led_pwm = devm_of_pwm_get(tdev, child, NULL);     /* 从子节点中获取PWM设备 */
    if (IS_ERR(my_pwm_led_dev.led_pwm)) 
	{
        printk(KERN_ERR"can't get led_pwm!!\n");
        return -EFAULT;
    }

    ret = alloc_chrdev_region(&my_pwm_led_dev.dev_no, 0, 1, "led_pwm");
	if (ret < 0) 
	{
		pr_err("Error: failed to register mbochs_dev, err: %d\n", ret);
		return ret;
	}

	cdev_init(&my_pwm_led_dev.chrdev, &red_led_drv);
	cdev_add(&my_pwm_led_dev.chrdev, my_pwm_led_dev.dev_no, 1);

    my_pwm_led_dev.led_class = class_create(THIS_MODULE, "led_pwm");
	
	err = PTR_ERR(my_pwm_led_dev.led_class);
	if (IS_ERR(my_pwm_led_dev.led_class)) 
	{
        goto failed1;
	}

    tdev = device_create(my_pwm_led_dev.led_class , NULL, my_pwm_led_dev.dev_no, NULL, "my_pwm_led"); 
    if (IS_ERR(tdev))
	{
        ret = -EINVAL;
		goto failed2;
	}

   	printk(KERN_INFO"%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
    
    return 0;
failed2:
    device_destroy(my_pwm_led_dev.led_class, my_pwm_led_dev.dev_no);
    class_destroy(my_pwm_led_dev.led_class);
failed1:
    cdev_del(&my_pwm_led_dev.chrdev);
	unregister_chrdev_region(my_pwm_led_dev.dev_no, 1);
    return ret;
}

int led_red_driver_remove(struct platform_device *dev)
{
    // pwm_disable(my_pwm_led_dev.led_pwm);
    // pwm_free(my_pwm_led_dev.led_pwm);
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	
    device_destroy(my_pwm_led_dev.led_class, my_pwm_led_dev.dev_no);
	class_destroy(my_pwm_led_dev.led_class);
	unregister_chrdev_region(my_pwm_led_dev.dev_no, 1);
    cdev_del(&my_pwm_led_dev.chrdev);
     
    return 0;
}

static struct of_device_id dts_match_table[] = {
    {.compatible = "pgg,my_pwm_led", },  
    {},                  
};

static struct platform_driver my_pwm_led_platform_driver = 
{
      .probe = led_red_driver_probe,
      .remove = led_red_driver_remove,
      .driver = 
      {
        .name = "pgg,my_pwm_led",
        .owner = THIS_MODULE,
        .of_match_table = dts_match_table,//通过设备树匹配
      },
};

module_platform_driver(my_pwm_led_platform_driver);

MODULE_AUTHOR("PGG");
MODULE_LICENSE("GPL");

应用程序:

#include "stdio.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
#include <poll.h>
#include <stdint.h>

#define DEV_NAME   "/dev/my_pwm_led"

#define LED_PWM_CMD_SET_DUTY         0x01
#define LED_PWM_CMD_SET_PERIOD       0x02
#define LED_PWM_CMD_SET_BOTH         0x03
#define LED_PWM_CMD_ENABLE           0x04
#define LED_PWM_CMD_DISABLE          0x05

struct led_pwm_param {
    int duty_ns;
    int period_ns;
};

void sleep_ms(unsigned int ms)
{
    struct timeval delay;
	delay.tv_sec = 0;
	delay.tv_usec = ms * 1000; 
	select(0, NULL, NULL, NULL, &delay);
}

int main(int argc, char **argv)
{
    int fd;
    int ret;
  
	/* 2. 打开文件 */
	fd = open(DEV_NAME, O_RDWR | O_NONBLOCK);   // | O_NONBLOCK

	if (fd < 0)
	{
		printf("can not open file %s, %d\n", DEV_NAME, fd);
		perror("open");
		return -1;
	}
     
    int buf = 3;
	struct led_pwm_param led_pwm;
	
	led_pwm.duty_ns = 500;
	led_pwm.period_ns = 5000;
    write(fd, &led_pwm, sizeof(led_pwm));
    sleep_ms(3000);

	for (int i = 0; i < 5; i++)
	{
		led_pwm.duty_ns += 300;
		if (led_pwm.duty_ns < led_pwm.period_ns)
			ioctl(fd, LED_PWM_CMD_SET_DUTY, &led_pwm.duty_ns);
		sleep_ms(1000);
	}

	// led_pwm.duty_ns = 0;
	// ioctl(fd, LED_PWM_CMD_SET_DUTY, &led_pwm.duty_ns);

	sleep_ms(3000);

	while(1)
	{
		if(led_pwm.duty_ns<=500)
		{
			while(led_pwm.duty_ns<led_pwm.period_ns)
			{
				ioctl(fd, LED_PWM_CMD_SET_DUTY, &led_pwm.duty_ns);
				sleep_ms(50);
				led_pwm.duty_ns += 300;
			}
		}
		else
		{
			while(led_pwm.duty_ns > 500)
			{
				ioctl(fd, LED_PWM_CMD_SET_DUTY, &led_pwm.duty_ns);
				sleep_ms(50);
				led_pwm.duty_ns -= 300;
			}
		}
		
	}

	close(fd);
    return 0;
}

总结

pwm子系统基于pinctrl子系统,需要了解pinctrl子系统及其设备树的写法。
内核态基于pwm子系统的驱动编写流程比较简单,获取设备后进行配置即可,要熟悉devm_of_pwm_get等API。

遗留问题

为什么只修改设备树的话,使用PWM无反应,而是要修改config.txt(后者应该也是动态修改了设备树) ?
看了一遍又发现设备树里面是只有PWM1的设备描述,还有一个问题是内核驱动的设备树,并没有找到pwm节点。

参考文章

  1. 树莓派4B开启两个硬件PWM的方法
  2. Linux驱动开发(十七)—树莓派PWM驱动
  3. Linux下pwm用法总结
  4. Linux内核4.14版本——PWM子系统(1)_框架分析
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值