【IMX6ULL驱动开发学习】21.Linux驱动之PWM子系统(以SG90舵机为例)

1.设备树部分

首先在 imx6ull.dtsi 文件中已经帮我们定义好了一些pwm的设备树节点,这里以pwm2为例

c 复制代码
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>;
};

我们要在设备树(.dts)文件中引用和使能该节点,同时指定好pwm映射到的GPIO引脚(即pinctrl子系统,我这里映射到了GPIO1_9上)

c 复制代码
&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";
};

使用pwm 只需要在设备树节点中添加两条属性信息,如下所示

c 复制代码
pwms = <"&PWMn id period_ns>;
pwm-names = "name";
  • pwms :属性是必须的,它共有三个属性值

  • &PWMn 指定使用哪个pwm,在imx6ull.dtsi文件中定义,总共有8个可选;

  • id :pwm的id通常设置为0。

  • period_ns :用于设置周期。单位是ns。

  • pwm-names :定义pwm设备名字。(可以不设置)

最后在根节点下添加自己定义的节点

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

2.驱动代码部分

老一套的字符设备驱动框架:

  • 驱动入口出口
  • 驱动入口定义注册字符设备、创建字符设备节点、注册platform设备;
  • 驱动出口反注册platfrom设备、删除字符设备节点、反注册字符设备
  • 构建file_operations结构体
  • 构建platform_device结构体,编写probe函数

如下代码所示:

c 复制代码
#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");
  • 首先 struct device_node *node = pdev->dev.of_node; 获取子节点,在设备树插件中,我们把PWM相关信息保存在 hc_sg90 的子节点中, 所以这里首先获取子节点。

  • 在子节点获取成功后我们使用 devm_of_pwm_get 函数获取pwm, 由于节点内只有一个PWM 这里将最后一个参数直接设置为NULL,这样它将获取第一个PWM。

  • 依次调用 pwm_config、pwm_set_polarity、pwm_enable 函数配置**PWM、设置输出极性、 使能PWM输出,**需要注意的是这里设置的极性为正常极性, 这样pwm_config函数第二个参数设置的就是pwm波的一个周期内的高电平事件。

其中write函数中关于SG90的占空比计算就不多说了,根据如下图来计算吧

不难得出高电平时间每多出1ms(1000000ns) 对应角度多出9度的结论

则旋转到角度 1 度时,对应的高电平时间为 (500000 + 1000000)/9 ns (因为0度对应的高电平时间为0.5ms = 500000ns)

则旋转到角度 n 度时,高电平时间为 (500000 + n * 1000000)/9 ns


3.应用程序部分

运行示例: ./sg90_test 90 , 即转到90度的位置

c 复制代码
#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;
}
相关推荐
南宫生1 分钟前
力扣-图论-17【算法学习day.67】
java·学习·算法·leetcode·图论
sanguine__17 分钟前
Web APIs学习 (操作DOM BOM)
学习
数据的世界012 小时前
.NET开发人员学习书籍推荐
学习·.net
cominglately2 小时前
centos单机部署seata
linux·运维·centos
魏 无羡2 小时前
linux CentOS系统上卸载docker
linux·kubernetes·centos
CircleMouse2 小时前
Centos7, 使用yum工具,出现 Could not resolve host: mirrorlist.centos.org
linux·运维·服务器·centos
四口鲸鱼爱吃盐3 小时前
CVPR2024 | 通过集成渐近正态分布学习实现强可迁移对抗攻击
学习
木子Linux3 小时前
【Linux打怪升级记 | 问题01】安装Linux系统忘记设置时区怎么办?3个方法教你回到东八区
linux·运维·服务器·centos·云计算
mit6.8243 小时前
Ubuntu 系统下性能剖析工具: perf
linux·运维·ubuntu
鹏大师运维3 小时前
聊聊开源的虚拟化平台--PVE
linux·开源·虚拟化·虚拟机·pve·存储·nfs