【IMX6ULL项目复现】sg90电机-pwm
1. 设备树的设置
我们使用pwm3
进行实验,输出是GPIO1_IO04
引脚,首先设置pinctrl
子系统
//确定使用哪个物理引脚
pinctrl_pwm3: pwm3grp {fsl,pins = <MX6UL_PAD_GPIO1_IO04__PWM3_OUT 0x110B0>;
};
在 pwm3
控制器节点中引用引脚配置
&pwm3 {pinctrl-names = "default";pinctrl-0 = <&pinctrl_pwm3>;clocks = <&clks IMX6UL_CLK_PWM3>,<&clks IMX6UL_CLK_PWM3>;status = "okay";
};
某个设备向PWM
控制器申请服务
hc_sg90 {compatible = "hc-sg90";pwms = <&pwm3 0 20000000>;status = "okay";};
其中,pwms = <&pwm3 0 20000000>;
是指向pwm3
控制器申请服务,0
是逻辑通道的索引,对于 pwm3 来说固定为0,20000000
表示频率20MHz
2. 驱动文件的编写
在基础的字符设备驱动结构上,增加以下内容
- 构建数据结构,存放驱动运行所需的所有数据
struct sg90dev {····struct pwm_device *sg90_PWM; /* PWM设备 */····};
- 在
probe
函数中获取pwm_device
结构体变量(devm_pwm_get
函数解析设备树节点的pwm
属性,获得pwm_device
结构体变量)
并处初始化sg90dev->sg90_PWM = devm_pwm_get(&pdev->dev, "pwm");
pwm
参数pwm_config(sg90dev->sg90_PWM, 500000, 20000000); // 设置PWM参数,0度时,高电平0.5ms,周期:20000000ns = 20ms pwm_set_polarity(sg90dev->sg90_PWM, PWM_POLARITY_NORMAL); /* 设置输出极性:占空比为高电平 */ pwm_enable(sg90dev->sg90_PWM); // 使能PWM
- 在
write
函数中设置占空比res = copy_from_user(&data, buf, size);// 从用户空间拷贝数据到内核空间 pwm_config(sg90dev->sg90_PWM, 500000 + data * 100000 / 9, 20000000); // 设置PWM参数,占空比:0.5ms~2.5ms,对应0~180度,周期:20000000ns = 20ms
write
函数设置占空比:sg90舵机从0度到180度,对应高电平从0.5ms到2.5ms,对应每一度高电平时间增加100000/9 ns - 在
remove
函数中关闭PWMpwm_config(sg90dev->sg90_PWM, 500000, 20000000); // 将舵机设置为0度状态 pwm_disable(sg90dev->sg90_PWM); // 关闭PWM
3. 代码
#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>
#include <linux/cdev.h>#define DEVICE_CNT 1
#define DEVICE_NAME "sg90"struct sg90_dev {struct cdev cdev; /* cdev */struct device *device; /* 设备 */char name[32]; // 从设备树读取的设备名,用于创建设备节点void *private_data; /* 私有数据 */int minor; /* 次设备号 */struct pwm_device *sg90_PWM; // PWM设备结构体
};
static struct sg90_dev *sg90dev;static int major; // 该驱动的主设备号
static dev_t my_device_num; // 存设备号
static struct class *my_dev_class; // 设备类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;if(size != 1) return -EINVAL;res = copy_from_user(&data, buf, size);// 从用户空间拷贝数据到内核空间pwm_config(sg90dev->sg90_PWM, 500000 + data * 100000 / 9, 20000000); // 设置PWM参数,占空比:0.5ms~2.5ms,对应0~180度,周期:20000000ns = 20msreturn size;
}static int sg90_release(struct inode *inode, struct file *filp)
{return 0;
}static struct file_operations my_dev_ops = {.owner = THIS_MODULE,.open = sg90_open,.write = sg90_write,.release = sg90_release,
};static const struct of_device_id sg90_dt_match[] = {{ .compatible = "hc-sg90" },{ },
};static int sg90_platform_probe(struct platform_device *pdev)
{const char *device_name; // 保存设备名称int ret; // 函数调用返回值/* 步骤 1: 为设备私有数据结构分配内存 */sg90dev = kzalloc(sizeof(struct sg90_dev), GFP_KERNEL); // 为每一个物理设备分配私有数据结构if (!sg90dev){printk("kzalloc for device failed!\r\n");return -ENOMEM;}printk("sg90 driver and device was matched!\r\n");/* 步骤 2: 从设备树节点中解析硬件信息 */ret = of_property_read_string(pdev->dev.of_node, "my_name", &device_name); //将np节点中的my_name属性值读出,存入device_nameif (ret < 0) {printk("Property 'my_name' not found in device tree\n");goto err_free_mem; // 跳转到错误处理标签}strncpy(sg90dev->name, device_name, sizeof(sg90dev->name) - 1);//将设备树中读取到的设备名称拷贝到私有数据结构name,sizeof(sg90dev->name) - 1是防止溢出/* 步骤 3:在全局数组中分配次设备号,并完成字符设备注册 */cdev_init(&sg90dev->cdev, &my_dev_ops); // 初始化字符设备sg90dev->cdev.owner = THIS_MODULE; // 标识该字符设备属于哪个内核模块,不是必须的sg90dev->minor = 0; //分配次设备号my_device_num = MKDEV(major, sg90dev->minor); // 重新使用my_device_num,合成完整的设备号ret = cdev_add(&sg90dev->cdev, my_device_num, 1); // 注册字符设备if (ret < 0) {printk("Failed to add cdev for %s\n", sg90dev->name);goto err_free_mem;}/* 步骤 4: 创建设备节点 */sg90dev->device = device_create(my_dev_class, NULL, my_device_num, NULL, sg90dev->name); // 创建设备节点,设备名从设备树读取if (IS_ERR(sg90dev->device)){ret = PTR_ERR(sg90dev->device);printk("device_create for %s failed!\r\n", sg90dev->name);cdev_del(&sg90dev->cdev); // 注销字符设备goto err_free_mem;}/* 设置PWM属性 */sg90dev->sg90_PWM = devm_pwm_get(&pdev->dev, "pwm");if (IS_ERR(sg90dev->sg90_PWM)) {ret = PTR_ERR(sg90dev->sg90_PWM);printk("pwm_get for %s failed!\r\n", sg90dev->name);device_destroy(my_dev_class, MKDEV(major, sg90dev->minor));cdev_del(&sg90dev->cdev); // 注销字符设备goto err_free_mem;}pwm_config(sg90dev->sg90_PWM, 500000, 20000000); // 设置PWM参数,0度时,高电平0.5ms,周期:20000000ns = 20mspwm_set_polarity(sg90dev->sg90_PWM, PWM_POLARITY_NORMAL); /* 设置输出极性:占空比为高电平 */pwm_enable(sg90dev->sg90_PWM); // 使能PWMreturn 0;
err_free_mem:kfree(sg90dev); // 释放内存return ret;
}static int sg90_platform_remove(struct platform_device *pdev)
{// 销毁设备节点,从内核中删除字符设备实例device_destroy(my_dev_class, MKDEV(major, sg90dev->minor));cdev_del(&sg90dev->cdev);pwm_config(sg90dev->sg90_PWM, 500000, 20000000); // 设置PWM参数,0度时,高电平0.5ms,周期:20000000ns = 20mspwm_disable(sg90dev->sg90_PWM); // 关闭PWMkfree(sg90dev); // 释放内存return 0;
}static struct platform_driver sg90_platform_driver = {.driver = {.owner = THIS_MODULE,.name = "sg90",.of_match_table = sg90_dt_match,},.probe = sg90_platform_probe,.remove = sg90_platform_remove,
};static int __init sg90_init(void)
{int ret;/* 申请主设备号 */ret = alloc_chrdev_region(&my_device_num, 0, DEVICE_CNT, DEVICE_NAME);if (ret < 0) {printk("Failed to allocate char device region\n");return ret;}major = MAJOR(my_device_num); // 获取主设备号/* 创建设备类 */my_dev_class = class_create(THIS_MODULE, DEVICE_NAME);if (IS_ERR(my_dev_class)){printk("class_create failed\n");unregister_chrdev_region(my_device_num, 1); // 补充释放return PTR_ERR(my_dev_class); // 返回标准错误码}/* 注册platform驱动 */return platform_driver_register(&sg90_platform_driver);
}static void __exit sg90_exit(void)
{// 注销设备号unregister_chrdev_region(my_device_num, 1);// 删除设备类class_destroy(my_dev_class);/* 注销platform驱动 */platform_driver_unregister(&sg90_platform_driver);}module_init(sg90_init);
module_exit(sg90_exit);
MODULE_LICENSE("GPL");