首页 > 系统相关 >步进电机Linux驱动

步进电机Linux驱动

时间:2024-06-17 12:31:56浏览次数:26  
标签:__ 步进 电机 int motor Linux include buf struct

本文将介绍步进电机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 0

static 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;    //步进电机驱动芯片的四个输入GPIO

static 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转,也即每拍间隔至少1ms
    printk("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,也即每一拍至少间隔1ms
    kernel_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"); //获取设备树节点中定义了多少个GPIO
    printk("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");
    //撤销中断,释放GPIO
    for(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;
}

实物图如下:

标签:__,步进,电机,int,motor,Linux,include,buf,struct
From: https://www.cnblogs.com/starstxg/p/18249793

相关文章

  • arm linux 启动
    bootloader是系统启动或复位以后执行的第一段代码,它主要用来初始化处理器及外设,然后调用Linux内核//Bootloader在运行过程中虽然具有初始化系统和执行用户输入的命令等作用,但它最根本的功能就是为了启动Linux内核Linux内核在完成系统的初始化之后根据启动参数中指定的roo......
  • linux或者CentOS环境下安装.NET Core环境
    一、下载注册Microsoft密钥:在安装之前,需要:1、注册Microsoft密钥2、注册产品资料库3、安装所需的依赖项打开终端并输入命令:sudorpm-Uvhhttps://packages.microsoft.com/config/centos/8/packages-microsoft-prod.rpm安装效果如下图所示:安装期间会提示用户验证,输......
  • 9、ansible-Ad-Hoc-点对点模式--m shell-shell模块(执行的就是linux命令)
    作用:可以执行任何有效的shell命令,包括管道、重定向和其他shell特性。该模块将在远程主机上启动一个shell,然后在该shell中执行命令。执行结果可以作为任务的输出返回,也可以将其保存到文件或变量中供后续任务使用。请注意,与其他模块相比,shell模块的执行效率较低,因为它需要在......
  • 在Linux中,什么是运维?什么是游戏运维?
    在Linux中,运维和游戏运维是两种不同的运维角色,但它们都涉及到对系统、网络和应用程序的管理、维护和优化。以下是对它们的详细解释:一、运维运维(OperationandMaintenance),通常指互联网运维,是技术部门中的一个重要组成部分,与研发、测试、系统管理共同构成互联网产品技术支撑的四......
  • Linux-grep
    Linux-grepgrep功能:擅长过滤2.2参数:-n ------显示行号-v ------取反,排除 -i ------不区分大小写-w ------根据单词进行过滤(两边有空格特殊符号)grep==egrep或grep-E;是否支持扩展正则| -----或者-o -----显示执行过程,显示出grep匹配的内容 -E -----可以使用扩展正......
  • 在Linux中,有哪些常用的网络管理工具?
    在Linux中,网络管理工具非常多样,它们可以帮助系统管理员监控、配置和故障排除网络。以下是一些常用的网络管理工具:ifconfig(或ip):ifconfig(在较新的系统中被ip命令取代)用于显示和配置网络接口。例如,ipaddrshow可以显示所有网络接口的状态。ip:ip命令是一个多功能的......
  • 在Linux中,如何查看某进程所打开的所有文件?
    在Linux中,查看某个进程所打开的所有文件主要可以使用lsof命令。下面是详细的步骤:确定进程ID(PID):首先,你需要知道你想查询的进程的进程ID。这可以通过多种方式完成,例如使用ps命令配合grep来查找进程名称并提取PID。例如,如果你想查找名为myprocess的进程打开的文件,可以这样做:......
  • 在Linux中,如何显示/test目录下的所有目录?
    在Linux中,若要详细显示/test目录下的所有目录,可以使用几种不同的命令方法,其中最常用的是ls命令结合相应的选项。以下是几种展示方法:1.使用ls命令的基本形式ls/test这个命令会列出/test目录下的所有条目(包括文件和目录),但不会显示详细信息。2.使用ls命令的长格式ls-l/tes......
  • 在Linux中,如何删除/a/b下的所有文件及目录?
    在Linux中,删除一个目录及其所有子文件和子目录是一个需要小心操作的过程,因为一旦执行,你将无法恢复被删除的数据。以下是几种删除/a/b下所有文件和目录的方法:使用rm命令:删除/a/b目录及其所有内容的最简单方法是使用rm命令的-r(递归)选项:rm-rf/a/b/*这里-rf选项组合表示:-r......
  • 【并发程序设计】总篇集 Linux下 C语言 实现并发程序
    11_Concurrent_Programing文章目录11_Concurrent_Programing1.进程概念进程内容进程类型进程状态2.进程常用命令进程信息命令top命令进程信息表进程优先级命令nice命令renice命令后台进程命令3.子进程创建子进程fork函数结束进程exit函数_exit函数回收子进......