步进电机Linux驱动

news/2024/10/3 4:37:00

本文将介绍步进电机Linux驱动程序,分为以三部分:步进电机介绍,硬件原理图以及程序编写
1 步进电机介绍
步进电机是一种将电脉冲信号转变为角位移或者线位移的开环控制元件,在非超载的状态下,电机的转速、停止位置只取决于脉冲信号的频率和脉冲数,不受负载变化的影响,并且只有周期性误差而没有累积误差,因此在速度、位置等控制领域有广泛的应用。
步进电机主要分为三类:永磁式,反应式,混合式
主要技术指标如下:


下图是一个四相永磁式步进电机,以它为例介绍步进电机是如何工作的。内容参考韦东山imx6ull裸机开发手册。


更具体地内容可以参考韦东山老师地驱动实验班课程视频。

2 硬件原理图
我使用的步进电机与老师课程上所使用的型号不同,但是使用原理上是一致的。我所使用的电机是四线双极性步进电机,使用的驱动芯片是TC1508S,外部电路原理图如下:

分析上述原理图,驱动模块接受4个GPIO输出,得到四个输出。使用开发板上imx6ull的四个引脚:GPIO1_IO01,GPIO1_IO02, JTAG_MOD,GPIO1_IO04,分别接到模块的IA,IB,IC,ID,模块的四个输出OA,OB,OC,OD分别接到电机的A+,A-,B+,B-,模块的输入和输出对应关系如下图:

根据逻辑真值表,可以得到四线双极性步进电机对应imx6ull引脚的输出真值表如下图:

3 程序
3.1 设备树节点

点击查看代码
mymotor {compatible = "motor_driver";pinctrl-names = "default";pinctrl-0 = <&pinctrl_motor>;motor-gpios = <&gpio1 1 GPIO_ACTIVE_HIGH&gpio1 2 GPIO_ACTIVE_HIGH&gpio1 10 GPIO_ACTIVE_HIGH&gpio1 4 GPIO_ACTIVE_HIGH&gpio1 3 GPIO_ACTIVE_LOW	/*LED*/>;status = "okay";};pinctrl_motor: motorGrp {                /*!< Function assigned for the core: Cortex-A7[ca7] */fsl,pins = <MX6UL_PAD_GPIO1_IO01__GPIO1_IO01           0x000010B0 /*A*/MX6UL_PAD_GPIO1_IO02__GPIO1_IO02           0x000010B0 /*B*/MX6UL_PAD_JTAG_MOD__GPIO1_IO10             0x0000B0A0 /*C*/MX6UL_PAD_GPIO1_IO04__GPIO1_IO04           0x000010B0 /*D*/MX6UL_PAD_GPIO1_IO03__GPIO1_IO03		   0x000010B0 /*LED*/>;};

3.2 驱动程序代码
驱动程序提供三种驱动方式,具体使用哪种由应用程序决定,驱动代码如下:

点击查看代码
#include "asm-generic/current.h"
#include "asm-generic/errno-base.h"
#include "asm-generic/poll.h"
#include "asm-generic/siginfo.h"
#include "asm/signal.h"
#include "asm/uaccess.h"
#include "linux/err.h"
#include "linux/export.h"
#include "linux/gpio/driver.h"
#include "linux/irqreturn.h"
#include "linux/kdev_t.h"
#include "linux/nfs_fs.h"
#include "linux/of.h"
#include "linux/wait.h"
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/gpio/consumer.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/ktime.h>
#include <linux/delay.h>
#include <linux/fcntl.h>
#include <linux/timer.h>
#include <linux/workqueue.h>
#include <asm/current.h>#define clock   1
#define unclock 0static int major;   //主设备号
static struct class *motor_class;
static int count;   //引脚数量,驱动步进电机,应该是4个引脚struct gpio_struct{int gpio_num;   //GPIO引脚编号,老版本struct gpio_desc *desc; //gpio引进描述结构体int irq;    //中断号int flag;
};struct motor_struct {int angle_per_beat; //每一节拍转动的角度int beats_per_round;  //一轮节拍数量char *name; //励磁方式名称int *beats;  //节拍数组
};static int beats_1phase[4] = {0xD, 0x7, 0xE, 0xB}; //一相励磁方式,每一次旋转1.8度.实测每一步是18度
static int beats_2phase[4] = {0x5, 0x6, 0xA, 0x9}; //二相励磁方式,每一次旋转18度
static int beats_12pahse[8] = {0xD, 0x5, 0x7, 0x6, 0xE, 0xA, 0xB, 0x9}; //一二相励磁方式,每次旋转9度static struct motor_struct motor_1phase = {.angle_per_beat = 18,.beats_per_round = 4,.name = "1phase",.beats = beats_1phase,
};static struct motor_struct motor_2phase = {.angle_per_beat = 18,.beats_per_round = 4,.name = "2phase",.beats = beats_2phase,
};static struct motor_struct motor_12phase = {.angle_per_beat = 9,.beats_per_round = 8,.name = "12phase",.beats = beats_12pahse,
};struct gpio_struct *motor_gpios;    //步进电机驱动芯片的四个输入GPIOstatic void delay_us(unsigned int us)
{/*延时us = 1时延时1us函数,因为直接用内核函数好像不太准,所以使用ktime_get_ns函数*/u64 time = ktime_get_ns();while (ktime_get_ns() - time < us*1000);
}static void delay_ms(unsigned int ms)
{/*延时ms = 1时延时1ms函数*/while(ms--){delay_us(1000); //延时1ms}
}int motor_open(struct inode *node, struct file *file)
{int i;for(i = 0; i < count; i++){gpiod_direction_output(motor_gpios[i].desc, 0);}return 0;
}void take_a_step(struct motor_struct *motor, int cur_step)
{/*设置一个节拍分别设置电机四个引脚的电平状态*/int i;for(i = 0; i < 4; i++){gpiod_set_value(motor_gpios[i].desc, (motor->beats[cur_step] >> i) & 1);}// printk("cur_steps: %d, beat: %0x\n", cur_step, motor->beats[cur_step]);
}void take_steps(struct motor_struct *motor, int *run_param)
{/*驱动电机运行(1)motor是使用的电机驱动方式(2)run_param是控制电机运行速度和转动的角度*/int i;int steps;int speed;int led_state = 0;int direction = motor->beats_per_round - 1;  //用于调整正转/反转/*转/s,那么每秒转动360 * run_param[0] 度1s需要360 * run_param[0] / motor->angle_per_beat这么多拍那么每拍间隔 1000 / (360 * run_param[0] / motor->angle_per_beat) ms*/speed = 1000 / (360 * run_param[0] / motor->angle_per_beat);   //运行速度其实也就是延时的时间,单位ms,上限每秒25转,也即每拍间隔至少1msprintk("speed: %d, angle: %d, choice: %d\n", run_param[0], run_param[1], run_param[2]);steps = run_param[1] / motor->angle_per_beat;  //需要的步数 // printk("steps: %d\n", steps);for(i = 0; i < steps; i++){if(run_param[3]){/*正转*/take_a_step(motor, i % motor->beats_per_round); //走一步}else {/*反转*/take_a_step(motor, direction - i % motor->beats_per_round); //走一步}led_state ^= 1;gpiod_set_value(motor_gpios[count-1].desc, led_state);delay_ms(speed);}
}ssize_t motor_write(struct file *file, const char __user *user_buf, size_t size, loff_t *offset)
{/*使用最简单的四节拍方式kernel_buf[0] 速度,转/s,需要转换为延时时间,上限25转/s,也即每一拍至少间隔1mskernel_buf[1] 角度kernel_buf[2] 驱动方式kernel_buf[3] 方向*/int kernel_buf[4];int err;int choice; //选择驱动方式,1,表示1相,2表示2相,3表示12相err = copy_from_user(kernel_buf, user_buf, size);if(kernel_buf[0] >= 25 || kernel_buf[0] <= 0){/*限位保护*/printk("speed is limited between 0 and 25.\n");return -1;}// printk("delay time: %d\n", kernel_buf[0]);choice = kernel_buf[2];// take_steps(&motor_1phase, kernel_buf);switch (choice) {case 1 : take_steps(&motor_1phase, kernel_buf); break;case 2 : take_steps(&motor_2phase, kernel_buf); break;case 3 : take_steps(&motor_12phase, kernel_buf); break;default: printk("motor choice input err!, please input 1, 2, or 3\n"); return -1;}return size;
}static struct file_operations motor_opr = {.owner = THIS_MODULE,.write = motor_write,.open = motor_open
};int motor_probe(struct platform_device *pdev)
{/*驱动和设备节点匹配时执行在这里会做一些初始化操作:获取设备树信息,注册驱动file_operation结构体*/struct device_node *node = pdev->dev.of_node;   //获取设备树中引脚对应的device_node结构体int i;enum of_gpio_flags flag;printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);count = of_gpio_named_count(node, "motor-gpios"); //获取设备树节点中定义了多少个GPIOprintk("counts: %d\n", count);if(count <= 0){printk("%s %s line %d, No available gpio, count = %d\n", __FILE__, __FUNCTION__, __LINE__, count);return -1;}motor_gpios = kzalloc(sizeof(struct gpio_struct) * count, GFP_KERNEL);  //为自定义的引脚结构体分配内存// //获取设备树中定义的节点信息for(i = 0; i < count; i++){//     //获取引脚编号motor_gpios[i].gpio_num = of_get_named_gpio_flags(node, "motor-gpios", i, &flag);printk("gpio_num: %d, value: %d\n", motor_gpios[i].gpio_num, gpio_get_value(motor_gpios[i].gpio_num));if(motor_gpios[i].gpio_num < 0){printk("%s %s line %d, of_get_gpio_flags failed\n", __FILE__, __FUNCTION__, __LINE__);return -1;}//获取引脚描述结构体motor_gpios[i].desc = gpiod_get_index(&pdev->dev, "motor", i);   //这里使用get,那么在remove函数里就需要使用put释放引脚motor_gpios[i].flag = flag & OF_GPIO_ACTIVE_LOW;   motor_gpios[i].irq = gpiod_to_irq(motor_gpios[i].desc);   //获取对应的中断号}/*注册file_operaton结构体,创建类和设备*/major = register_chrdev(0, "motor_driver", &motor_opr);motor_class = class_create(THIS_MODULE, "motor_class");if(IS_ERR(motor_class)){printk("%s %s line %d, class create failed\n", __FILE__, __FUNCTION__, __LINE__);unregister_chrdev(major, "motor_driver");return PTR_ERR(motor_class);}//创建设备节点,不需要为每个引脚都创建设备节点,所有引脚(按键)共用一个设备节点device_create(motor_class, NULL, MKDEV(major, 0), NULL, "motor");return 0;
}int motor_remove(struct platform_device *pdev)
{int i;/*卸载驱动时执行,在这里要撤销中断,注销驱动、类和设备节点,释放动态分配的内存*///删除设备节点device_destroy(motor_class, MKDEV(major, 0));//删除类class_destroy(motor_class);//撤销驱动unregister_chrdev(major, "motor_driver");//撤销中断,释放GPIOfor(i = 0; i < count; i++){gpiod_put(motor_gpios[i].desc);}kfree(motor_gpios);   //释放内存return 0;
}static const struct of_device_id motors_match_table[] = {{.compatible = "motor_driver"}
};struct platform_driver motor_driver = {.probe = motor_probe,.remove = motor_remove,.driver = {.name = "mymotor_driver",.of_match_table = motors_match_table,},
};static int __init motor_drv_init(void)
{int err;printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);err = platform_driver_register(&motor_driver);return err;
}static void __exit motor_drv_exit(void)
{printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);platform_driver_unregister(&motor_driver);
}module_init(motor_drv_init);
module_exit(motor_drv_exit);
MODULE_LICENSE("GPL");
3.3 应用程序代码 应用程序代码如下:
点击查看代码
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <poll.h>
#include <signal.h>int main(int argc, char *argv[])
{int fd;int buf[4]; //buf[0]表示速度,单位转/s,上限25,buf[1]表示转动的角度, buf[3]表示使用的驱动方式,1表示1相,2表示2相,3表示12相if(argc != 6){printf("Usage: %s <key_dev> <speed> <angle> <choice> <dirction 1 or 0>\n", argv[0]);return -1;}buf[0] = atoi(argv[2]);buf[1] = atoi(argv[3]);buf[2] = atoi(argv[4]);if(strcmp(argv[5], "clock") == 0){/*顺时针*/buf[3] = 1;}else{buf[3] = 0;}fd = open(argv[1], O_RDWR);write(fd, buf, 16);return 0;
}

实物图如下:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.ryyt.cn/news/44678.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

VSCode使用svn代码管理工具,初次检出失败

打开vscode,若要使用SVN需要下载相应的插件。 2.安装之后,需要对SVN插件进行配置,配置本地SVN的命令行执行文件地址。点击左下角齿轮,选择“设置Settings”。"svn.path": "C:/Program Files/TortoiseSVN/bin/svn.exe"3.如图所示。设置完毕后重启VS COD…

团队作业5月20日

1.整个项目预期的任务量:340 目前已经花的时间:20 剩余的时间:10 2.任务看板照片:4.产品状态: 最新做好的功能: 正在完成中 6.燃尽图:

团队作业5月25日

1.整个项目预期的任务量:340 目前已经花的时间:25 剩余的时间:5 2.任务看板照片:4.产品状态: 最新做好的功能: 正在完成中 6.燃尽图:

【Mysql】Windows下安装和配置Mysql

一、下载 官网下载Mysql:https://dev.mysql.com/downloads/mysql/ 百度网盘链接mysql-8.0.31:https://pan.baidu.com/s/1CiW7oL8fR05NPZT55_9DUQ?pwd=0724 提取码:0724 二、解压 下载完成后我们得到的是一个压缩包,将其解压,我们就可以得到MySQL 8.0.31 的软件本体了(就是…

团队会议4月28日

站立会议: 1.团队照片:2.整个项目预期的任务量:340 目前已经花的时间:9 剩余的时间:19 3.任务看板照片:4.产品状态: 最新做好的功能: 正在完成中 6.燃尽图:

团队会议4月29日

站立会议: 1.团队照片:2.整个项目预期的任务量:340 目前已经花的时间:15 剩余的时间:15 3.任务看板照片:4.产品状态: 最新做好的功能: 正在完成中 6.燃尽图:

OAuth2.0 实现单点登录(四种授权方式)

一、四种授权模式1、客户端模式(Client Credentials)指客户端以自己的名义,而不是以用户的名,向“服务提供商”进行认证。严格的说,客户端模式并不属于OAuth框架所要解决的问题。在这种模式中,用户直接向客户端注册,客户端以自己的名义要求“服务提供商”提供服务,其实…

[转]32th@C++ 20新特性之线程与jthread@20240617

C++ 20新特性之线程与jthread为什么要引入jthread 在C++ 11中,已经引入了std::thread。std::thread为C++标准库带来了一流的线程支持,极大地促进了多线程开发的便利性。但std::thread也存在一些明显的不足和短板,主要有以下几点。 1、生命周期管理的复杂性。std::thread对象…