在此文档的指导下写出linux步进电机驱动的前提是:
1、懂得linux内核模块编程。
2、明白步进电机的原理及驱动电路。
3、能在51单片机(或其他的CPU)下驱动步进电机。
一:运行环境
硬件平台:tq2440开发板(CPU,s3c2440)
软件平台:linux2.6.30.4内核
编译工具:arm-linux-gcc交叉编译器
二:跟驱动相关的基本内核函数
1、动态分配设备号
Int alloc_chrdev_region(dev_t *dev, unsigned int baseminor, unsigned int count, const char *name)
dev:用于存放分配到的设备号
baseminor:要注册的次设备号的起始值
count:要注册的设备号个数
name:设备名称
返回值:0表示成功,负数为错误码
2、注销设备号
Void unregister_chrdev_region(dev_t from, unsigned int count)
from:要注销的设备号的起始值
count:要注销的设备号个数
3、创建字符设备并初始化
在驱动中,字符设备其实就是个结构体,如下
struct cdev {
const struct file_operations *ops; //指向对该设备的操作结构体
/*其结构体成员有很多,这里只列出其中很重要的一个*/
}
struct file_operations {
struct module *owner; //一般为THIS_MODULE
ssize_t (*read)(struct file *, char __user *, size_t, loff_t *);
ssize_t (*write)(struct file *, char __user *, size_t, loff_t *);
int (*open)(struct inode *, struct file *);
int (*release)(struct inode *, struct file *);
int (*ioctl)( struct inode *, struct file *, unsigned int, long );
/*这里知识列出部分成员*/
}
void cdev_init(struct *cdev, const struct file_operations *fops)
cdev:指向被初始化的字符设备
fops:指向字符设备支持的一系列文件操作
4、注册字符设备
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
p:指向要注册的字符设备
dev:此字符设备要绑定的设备号的起始值
count:此字符设备要绑定的设备号的个数
返回值:0表示注册成功,负数表示错误码
5、注销字符设备
void cdev_del(struct cdev *p)
p:指向被注销的字符设备
6、struct file_operations成员函数与应用层的联系
当应用程序调用open(),close(),read(),write(),ioctl()等时,系统调用会转去调用对应驱动的struct file_operations中的open(),release(),read(),write(),ioctl()等。
三、操作ARM的GPIO的内核函数
在步进电机驱动(驱动电路方案是L297+L298)中,引脚使用情况是GPF3对应着驱动电路的时钟口,GPF4对应着驱动电路的使能口,GPG0对应着驱动电路的正反转口。
s3c2440有9组GPIO引脚,共130个。每组GPIO引脚由3个寄存器控制,分别是GPXCON,GPXUP,GPXDAT。引脚一般可做输入,或输出,或其它功能,由GPXCON控制。GPXUP控制着引脚是否使用内部上拉电阻。GPXDAT为引脚的输入或输出的数据。(更详细的介绍可看s3c2440的英文芯片资料)
内核已经提供了很多直接操纵GPIO引脚的函数,因此我们没必要通过读写GPXCON、GPXUP、GPXDAT来控制GPIO引脚,主要的内核函数如下:
1、设置引脚是否使用内部上拉电阻
void s3c2410_gpio_pullup(unsigned int pin, unsignedint to)
pin:引脚的编号
to:0->使用内部上拉电阻
1->不使用内部上拉电阻
例如:
s3c2410_gpio_pullup(
S3C2410_GPB0, 0);//GPB0引脚使用内部上拉电阻
2、配置引脚的功能
void s3c2410_gpio_cfgpin(unsigned int pin, unsignedint function);
pin:引脚的编号
function:S3C2410_GPXn_INP->输入功能
S3C2410_GPXn_OUTP->输出功能
其它功能各引脚不一样,详见s3c2440芯片资料
例如:
s3c2410_gpio_cfgpin(S3C2410_GPA0,
S3C2410_GPA0_ADDR0);//设置GPA0引脚作为地址总线
s3c2410_gpio_cfgpin(S3C2410_GPB0, S3C2410_GPB0_OUTP);//设置GPB0引脚作为输出
3、设置引脚的输出值
s3c2410_gpio_setpin(unsigned int pin, unsigned intto);
pin:引脚编号
to:0->输出为低电平
1->输出为高电平
例如:
s3c2410_gpio_setpin(S3C2410_GPB0, 1);//令GPB0引脚输出高电平
4、读取引脚的状态
unsigned int s3c2410_gpio_getpin(unsigned int pin);
pin:引脚编号
返回值:0->当前引脚为低电平
非0->当前引脚为高电平
四、所有源代码
1、步进电机驱动
#include
#include
#include
#include
#include
#include
#define CW 0
#define CCW 1
#define POWERUP 2
#define POWERDOWN 3
#define CK0 4
#define CK1 5
#define en S3C2410_GPF4
#define ck S3C2410_GPF3
#define cw S3C2410_GPG0
MODULE_LICENSE("GPL ");
MODULE_AUTHOR("GDUFS");
static void init(void) ////初始化引脚状态
{
s3c2410_gpio_cfgpin(en,S3C2410_GPF4_OUTP);
s3c2410_gpio_cfgpin(ck,S3C2410_GPF3_OUTP);
s3c2410_gpio_cfgpin(cw,S3C2410_GPG0_OUTP);
/*s3c2410_gpio_cfgpin(en,s3c2410_GPF3_Output);
s3c2410_gpio_cfgpin(ck,s3c2410_GPF4_OUTPUT);
s3c2410_gpio_cfgpin(cw,s3c2410_GPG0_OUTPUT);*/
s3c2410_gpio_pullup(en,0);
s3c2410_gpio_pullup(ck,0);
s3c2410_gpio_pullup(cw,0);
s3c2410_gpio_setpin(en,1);
s3c2410_gpio_setpin(ck,1);
s3c2410_gpio_setpin(cw,1);
}
//以下为对应的驱动函数
static int open_motor(struct inode *inode,struct file*file)
{
return 0;
}
static int release_motor(struct inode *inode,structfile *file)
{
return 0;
}
static ssize_t read_motor(struct file *file,char__user *buf,size_t count,loff_t *pos)
{
returncount;
}
static ssize_t write_motor(struct file *file,char__user *buf,size_t count,loff_t *pos)
{
returncount;
}
static ssize_t ioctl_motor(struct inode *inode,structfile *file,unsigned int cmd,long data)
{
switch(cmd)
{
caseCW:
{
s3c2410_gpio_setpin(cw,0); //正转
break;
}
caseCCW:
{
s3c2410_gpio_setpin(cw,1); //反转
break;
}
casePOWERUP:
{
s3c2410_gpio_setpin(en,1); //使能驱动电路
break;
}
casePOWERDOWN:
{
s3c2410_gpio_setpin(en,0); //禁止驱动电路
break;
}
caseCK0:
{
s3c2410_gpio_setpin(ck,0); //设置时钟线为低电平
break;
}
caseCK1:
{
s3c2410_gpio_setpin(ck,1); //设置时钟线为高电平
break;
}
default:
return-EINVAL;
}
return 0;
}
static struct file_operations fopt=
{
.owner=THIS_MODULE,
.open=open_motor,
.release=release_motor,
.read=read_motor,
.write=write_motor,
.ioctl=ioctl_motor,
};
static dev_t devn; ///创建设备号
static struct cdev motor; ///创建字符设备
static int _main(void)
{
init();
if(alloc_chrdev_region(&devn,0,1,"motor")<0) ///申请设备号
printk("<0>allocdevn fail
");
else
{
printk("<0>major=%d
",MAJOR(devn));
printk("<0>minor=%d
",MINOR(devn));
}
cdev_init(&motor,&fopt); ///初始化字符设备
if(cdev_add(&motor,devn,1)<0) ////注册字符设备
printk("<0>addmotor fail
");
else
printk("<0>addmotor success
");
return 0;
}
static __exit void _leave(void)
{
cdev_del(&motor); ////注销字符设备
unregister_chrdev_region(devn,1); ////注销设备号
}
module_init(_main);
module_exit(_leave);
2、编译驱动的Makefile
ifneq ($(KERNELRELEASE),)
obj-m := motor.o
else
KDIR := /home/tool/linux-2.6.30.4/ #开发板所用的内核所在的文件
#KDIR := /lib/modules/2.6.27.5-117.fc10.i686/build
all:
make -C$(KDIR) M=$(PWD) modules
clean:
rm -f *.ko*.o *.mod.o *.mod.c *.symvers
#else
#obj-m := led.o
endif
3、测试程序
#include
#include
#include
#include
#include
#include
//#include
#define CW 0
#define CCW 1
#define POWERUP 2
#define POWERDOWN 3
#define CK0 4
#define CK1 5
int main (int geshu,char **zifu)
{
int fb=58;
unsigned char b=5;
if((fb=open("/dev/motor",O_RDWR))==-1)
{
perror("open");
return -1;
}
printf("fd=%d
",fb);
if(ioctl(fb,POWERUP)<0) ////上电
printf("ioctlpowerup fail
");
if(ioctl(fb,CW)<0) ////正转
printf("ioctlCW fail
");
while(1)
{ ////驱动时钟
if(ioctl(fb,CK0)<0)
printf("ioctlSTEP fail
");
usleep(2000);
if(ioctl(fb,CK1)<0)
printf("ioctlSTEP fail
");
usleep(2000);
}
ioctl(fb,POWERDOWN); ////断电
close(fb);
return 0;
}
五、编译安装过程
1、交叉编译过程
2、安装并测试过程
在NFS下进行程序调试。
六、遇到的问题及解决方法
在写此驱动的过程中主要遇到一下三个问题:
1、在测试程序中把if((fb=open("/dev/motor",O_RDWR))==-1)写成了if(fb=open("/dev/motor",O_RDWR)==-1),从而导致总是得不到想要的结果,后来快绝望的时候(折腾了我两天),在不经意间发现了错误所在。
2、 开发板输出的高电平是3.3V,而步进电机驱动电路需要的高电平是5.0V,刚开始使用74HC32芯片想将3.3V转换为5.0V,但失败了,因为74HC32不兼容TTL电平。后来改用LM339比较器,将3.3V转换为5.0V,成功。
3、 s3c2410_gpio_cfgpin(S3C2410_GPB0, S3C2410_GPB0_OUTP);在这函数中刚开始时,找不到代表输出功能的宏(mach/hardware.h中没有说明,在网上也找不到)。于是尝试了S3C2410_GPB0_OUTPUT S3C2410_GPB0_Output,都失败。后来想到用整数去代替宏,写成了以下形式s3c2410_gpio_cfgpin(S3C2410_GPB0, 1),发现这只对GPG0口有效,可能不同引脚的S3C2410_GPXn_OUTP对应着不同的数吧。最后,也是不经意间在论坛上看到了OUTP。
七、体会
不包括前期知识的准备时间,从开始写此驱动到完成此驱动,总共历时4天。回想起来期间所遇到的问题,突然间觉得本来这过程本可以更顺利的,因为遇到的都是一些小问题(正如上面所说)。但又细细想一下,这些小问题又使我受益匪浅。比如,上面所说的第一个问题,开始时,以为是驱动程序出了问题,于是反复查看、修改所用到的内核函数,或者换另一种方式写。正因为这样,我对内核函数的使用、结构,从刚开始的生疏到了后来的熟练。对于第三个问题,为了找到答案,我基本看遍了mach目录下的所有头文件,又学到了不少知识。所以塞翁失马焉知非福。问题虽小,但寻找答案的过程却是可以学到不少知识。