基于TQ2440的嵌入式linux步进电机驱动

2019-07-12 18:08发布

在此文档的指导下写出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目录下的所有头文件,又学到了不少知识。所以塞翁失马焉知非福。问题虽小,但寻找答案的过程却是可以学到不少知识。