[IMX6ULL driver development and learning] 21. PWM subsystem driven by Linux (take SG90 steering gear as an example)

1. Device tree section

First of all, some pwm device tree nodes have been defined for us in the imx6ull.dtsi file, here we take pwm2 as an example

pwm2: pwm@02084000 {
    
    
	compatible = "fsl,imx6ul-pwm", "fsl,imx27-pwm";
	reg = <0x02084000 0x4000>;
	interrupts = <GIC_SPI 84 IRQ_TYPE_LEVEL_HIGH>;
	clocks = <&clks IMX6UL_CLK_DUMMY>,
		 <&clks IMX6UL_CLK_DUMMY>;
	clock-names = "ipg", "per";
	#pwm-cells = <2>;
};

We need to reference and enable this node in the device tree (.dts) file, and at the same time specify the GPIO pin to which the pwm is mapped (that is, the pinctrl subsystem, which I mapped to GPIO1_9 here)

&iomuxc {
    
    
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_hog_1>;
	imx6ul-evk {
    
    
		......
		......

		/* SG90 PWM2 GPIO1_IO09 */
		pinctrl_pwm2: pwm2grp {
    
    
			fsl,pins = <
				MX6UL_PAD_GPIO1_IO09__PWM2_OUT   0x110b0
			>;
		};
		......
		......
}

......
......

&pwm2 {
    
    
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_pwm2>;
	clocks = <&clks IMX6UL_CLK_PWM2>,
			 <&clks IMX6UL_CLK_PWM2>;
	status = "okay";
};

To use pwm, you only need to add two pieces of attribute information in the device tree node, as shown below

pwms = <&PWMn id period_ns>;
pwm-names = "name";
  • pwms: attribute is required, it has three attribute values

  • &PWMn specifies which pwm to use, defined in the imx6ull.dtsi file, there are 8 options in total;

  • id: The id of pwm is usually set to 0.

  • period_ns : Used to set the period. The unit is ns.

  • pwm-names: Define the name of the pwm device. (can not be set)

Finally, add your own defined node under the root node

hc_sg90 {
    
    
	compatible    =  "hc-sg90";
	pwms = <&pwm2 0 20000000>;    /* 使用pwm1  id为0   周期为20000000ns = 20ms */
	status 		  =  "okay";
};

2. Driver code part

The old character device driver framework:

  • drive entry exit
  • The driver entry defines registering character devices, creating character device nodes, and registering platform devices;
  • Driver export de-register platfrom device, delete character device node, de-register character device
  • Build the file_operations structure
  • Build the platform_device structure and write the probe function

As shown in the following code:

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/gpio.h>
#include <linux/uaccess.h>
#include <linux/string.h>
#include <linux/interrupt.h>
#include <linux/irqreturn.h>
#include <linux/of_gpio.h>
#include <linux/slab.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/timer.h>
#include <linux/gpio/consumer.h>
#include <linux/delay.h>
#include <linux/timekeeping.h>
#include <linux/wait.h>
#include <linux/irqflags.h>
#include <linux/pwm.h>

static int major;
static struct class *class;

static struct pwm_device *pwm_test;

static int sg90_probe(struct platform_device *pdev)
{
    
    
    struct device_node *node = pdev->dev.of_node;

    printk("sg90 match success \n");
    if (node){
    
    
        /* 从子节点中获取PWM设备 */
        pwm_test = devm_of_pwm_get(&pdev->dev, node, NULL);  
        if (IS_ERR(pwm_test)){
    
    
            printk(KERN_ERR" pwm_test,get pwm  error!!\n");
            return -1;
        }
    }
    else{
    
    
        printk(KERN_ERR" pwm_test of_get_next_child  error!!\n");
        return -1;
    }

    pwm_config(pwm_test, 1500000, 20000000);   /* 配置PWM:1.5ms,90度,周期:20000000ns = 20ms */
    pwm_set_polarity(pwm_test, PWM_POLARITY_NORMAL); /* 设置输出极性:占空比为高电平 */
    pwm_enable(pwm_test);    /* 使能PWM输出 */

    return 0;
}

static int sg90_remove(struct platform_device *dev)
{
    
    
	pwm_config(pwm_test, 500000, 20000000);  /* 配置PWM:0.5ms,0度 */
	pwm_free(pwm_test);

	return 0;
}

static const struct of_device_id sg90_of_match[] = {
    
    
	{
    
     .compatible = "hc-sg90" },
	{
    
     }
};

static struct platform_driver sg90_platform_driver = {
    
    
	.driver = {
    
    
		.name		= "my_sg90",
		.of_match_table	= sg90_of_match,
	},
	.probe			= sg90_probe,
	.remove			= sg90_remove,
};


static int sg90_open (struct inode *node, struct file *filp)
{
    
    
	return 0;
}

static ssize_t sg90_write (struct file *filp, const char __user *buf, size_t size, loff_t *offset)
{
    
    
	int res;
	unsigned char data[1];
	if(size != 1)
		return 1;

	res = copy_from_user(data, buf, size);
	/* 配置PWM:旋转任意角度(单位1度) */
	pwm_config(pwm_test, 500000 + data[0] * 100000 / 9, 20000000);   
	return 1;
}

static int sg90_release (struct inode *node, struct file *filp)
{
    
    
	return 0;
}


static struct file_operations sg90_ops = {
    
    
	.owner		=	THIS_MODULE,
	.open 		= 	sg90_open,
	.write 		= 	sg90_write,
	.release 	=	sg90_release,
};

static int sg90_init(void)
{
    
    
	major = register_chrdev(0 , "sg90", &sg90_ops);
	class = class_create(THIS_MODULE, "sg90_class");
	device_create(class, NULL, MKDEV(major, 0), NULL, "sg90");

	platform_driver_register(&sg90_platform_driver);
	
	return 0;
}

static void sg90_exit(void)
{
    
    
	platform_driver_unregister(&sg90_platform_driver);
	
	device_destroy(class, MKDEV(major, 0));
	class_destroy(class);
	unregister_chrdev(major, "sg90");
}

module_init(sg90_init);
module_exit(sg90_exit);
MODULE_LICENSE("GPL");
  • First, struct device_node *node = pdev->dev.of_node; to get the child nodes. In the device tree plug-in, we save the PWM related information in the child nodes of hc_sg90 , so here we get the child nodes first.

  • After the child node is successfully obtained, we use the devm_of_pwm_get function to obtain pwm. Since there is only one PWM in the node, the last parameter is directly set to NULL, so that it will obtain the first PWM.

  • Call the pwm_config, pwm_set_polarity, and pwm_enable functions in turn to configure **PWM, set output polarity, and enable PWM output. **It should be noted that the polarity set here is normal polarity, so the second parameter of the pwm_config function is set to pwm A high event within one cycle of the wave.

Among them, the calculation of the duty cycle of SG90 in the write function is not much to say, it is calculated according to the following figure

insert image description here
It is not difficult to draw the conclusion that every 1 ms (1000000 ns) of the high-level time corresponds to an angle of 9 degrees more
. When the angle is rotated to 1 degree, the corresponding high-level time is (500000 + 1000000)/9 ns (because 0 degrees corresponds to The high level time is 0.5ms = 500000ns)
, then when it rotates to an angle of n degrees, the high level time is (500000 + n * 1000000)/9 ns


3. Application section

Running example: ./sg90_test 90 , that is, turn to the position of 90 degrees

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
    
    
	int fd;
	int res;
	unsigned char buf[1];

	fd = open("/dev/sg90", O_WRONLY);

	if(fd < 0)
	{
    
    
		printf("sg90 open failed\n");
		return 0;
	}

	buf[0] = atoi(argv[1]);
	write(fd, buf, 1);

	close(fd);
	return 0;
}

Guess you like

Origin blog.csdn.net/HuangChen666/article/details/132041449