本文将介绍步进电机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");
点击查看代码
#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