课题水下机器人需要测定水下机器人的位姿,为此应用了加速度计MMA7455,该传感器可以用SPI或I2C读取数字信号到MCU。
驱动MMA7455在atmega128上已经实现,但是由于mega128的速度、资源等瓶颈使得继续开发受到一定限制,故改用arm处理器。
在arm处理器上运行linux操作系统,要完成对MMA7455加速度计的驱动需要了解linux下的设备驱动程序,因此编写了相应的设备驱动程序。
在学习过程中Makefile的编写非常重要,对于我这样的菜鸟来说感觉makefile是设备驱动的一大障碍,试了好多各种各样的makefile,最终发现一个好用的makefile,而且似乎可以通用,于是记录下来了:http://blog.csdn.net/joygo007/article/details/6639368。
我用的是SPI来驱动MMA7455,因为相对I2C来说,我觉得SPI还是稍微简单些。
先将代码附上,再在后面慢慢分享从中遇到的各种困难以及解决的过程。
Code1:linux 下针对MMA7455的SPI设备驱动程序
- #include <linux/config.h>
- #include <linux/kernel.h>
- #include <linux/fs.h>//fops
- #include <linux/module.h>
- #include <linux/init.h>
- #include <linux/devfs_fs_kernel.h>
- #include <linux/miscdevice.h>
- #include <asm/arch/regs-gpio.h>//gpio寄存器地址头文件
- #include <asm/hardware.h>//s3c2410_gpio_setpin等函数
- #include <linux/delay.h>
- #include <asm/irq.h>
- #include <asm/io.h>
- #include <asm/uaccess.h>
-
- #define DEVICE_NAME "spi_gh" //设备名称
- #define SPI_MAJOR_NUM 236 //主设备号
-
-
- volatile int *spi_gpecon=NULL;
- volatile int *spi_gpedat=NULL;
- volatile int *spi_gpeup=NULL;
- volatile int *s3c2440_clkcon=NULL;
- volatile int *spi_spcon0=NULL;
- volatile int *spi_spsta0=NULL;
- volatile int *spi_sppin0=NULL;
- volatile int *spi_sppre0=NULL;
- volatile int *spi_sptdat0=NULL;
- volatile int *spi_sprdat0=NULL;
-
- #define SPI_TXRX_READY (((*spi_spsta0)&0x1) == 0x1)
- int loopChar=0x88;
-
-
- static unsigned long spi_pin_tab[]={
- S3C2410_GPE11,
- S3C2410_GPE12,
- S3C2410_GPE13,
- S3C2410_GPG2,
-
- };
-
-
- static unsigned long spi_pin_cfg_tab[]={
- S3C2410_GPE11_SPIMISO0,
- S3C2410_GPE12_SPIMOSI0,
- S3C2410_GPE13_SPICLK0,
- S3C2410_GPG2_OUTP,
- };
-
-
-
-
- static int spi_open(struct inode *inode,struct file *filp)
- {
- *s3c2440_clkcon |=0x40000;
- printk("s3c2440_clkcon=%08X\n",*s3c2440_clkcon);
-
- *spi_sppre0=0x18;
- printk("spi_sppre0=%02X\n",*spi_sppre0);
- *spi_spcon0=(0<<6)|(0<<5)|(1<<4)|(1<<3)|(0<<2)|(0<<1)|(0<<0);
- printk("spi_spcon0=%02X\n",*spi_spcon0);
- *spi_sppin0=(0<<2)|(0<<0);
- printk("spi_sppin0=%02X\n",*spi_sppin0);
- printk("KKK:OPEN OK\n");
- return 0;
-
- }
-
-
-
-
-
-
-
-
- static void writeByte(const char c)
- {
- int j = 0;
- *spi_sptdat0 = c;
- for(j=0;j<0xFF;j++);
- while(!SPI_TXRX_READY)
- for(j=0;j<0xFF;j++);
- }
-
-
-
-
-
-
-
-
- static char readByte(void)
- {
- int j = 0;
- char ch = 0;
- *spi_sptdat0 = (char)loopChar;
- for(j=0;j<0xFF;j++);
- while(!SPI_TXRX_READY)
- for(j=0;j<0xFF;j++);
- ch=*spi_sprdat0;
- return ch;
- }
-
-
-
-
- static int spi_ioctl( struct inode *inode, struct file *file,
- unsigned int RdorWr, char * spiBuf )
- {
- char whtevr;
- char * ch;
- char * kbuf=&whtevr;
- switch(RdorWr)
- {
- case 0:
-
- ch=readByte();
- copy_to_user(spiBuf,&ch,1);
-
- return 1;
- break;
- case 1:
-
- copy_from_user(kbuf,spiBuf,1);
-
- writeByte(*kbuf);
-
- return 1;
- break;
- case 3:
- s3c2410_gpio_setpin(spi_pin_tab[3],0);
- return 0;
- break;
- case 4:
- s3c2410_gpio_setpin(spi_pin_tab[3],1);
- return 0;
- break;
- default:
- return -EINVAL;
- }
- }
-
-
-
-
-
- static struct file_operations spi_fops = {
- .open =spi_open,
- .ioctl = spi_ioctl,
- };
-
-
-
-
-
- static int __init spi_init(void)
- {
- int ret,i;
- ret=register_chrdev(SPI_MAJOR_NUM,DEVICE_NAME,&spi_fops);
- if(ret<0)
- {
- printk(DEVICE_NAME" can't register major number\n");
- return ret;
- }
- devfs_mk_cdev(MKDEV(SPI_MAJOR_NUM, 0), S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP, DEVICE_NAME);
-
-
- for(i=0;i<4;i++)
- {
- s3c2410_gpio_cfgpin(spi_pin_tab[i],spi_pin_cfg_tab[i]);
- }
-
- s3c2440_clkcon = (int *)ioremap(0x4C00000c,3);
-
- spi_gpecon = (int *)ioremap (0x56000040,4);
- spi_gpedat = (int *)ioremap (0x56000044,2);
- spi_gpeup = (int *)ioremap (0x56000048,2);
-
- spi_spcon0 = (int *)ioremap(0x59000000,1);
- spi_spsta0 = (int *)ioremap(0x59000004,1);
- spi_sppin0 = (int *)ioremap(0x59000008,1);
- spi_sppre0= (int *)ioremap(0x5900000c,1);
- spi_sptdat0 = (int *)ioremap(0x59000010,1);
- spi_sprdat0 = (int *)ioremap(0x59000014,1);
-
- printk(DEVICE_NAME" initial OK!\n");
-
- }
-
-
-
-
-
- static void __exit spi_exit(void)
- {
- devfs_remove(DEVICE_NAME);
- unregister_chrdev(SPI_MAJOR_NUM,DEVICE_NAME);
- }
-
-
-
-
-
- module_init(spi_init);
- module_exit(spi_exit);
-
-
Code2:针对以上SPI设备驱动代码的makefile文件:
- obj-m := spi_MMA7455.o #spi_MMA7455为上面代码文件的名字
- ifeq ($(v),arm)
- KERNELDIR :=/opt/friendlyARMQQ2440/ghCodes/kernel-2.6.13 #这里是我的ARM开发板的内核路径
- else
- KERNELDIR := /lib/modules/$(shell uname -r)/build #这里是PC机端的内核路径,在ARM开发时用不着,这个是用来在PC机上做其他测试时用的
- endif
- default:
- make -C $(KERNELDIR) M=$(shell pwd) modules
- install:
- insmod spi_MMA7455.ko
- uninstall:
- rmmod spi_MMA7455.ko
- clean:
- make -C $(KERNELDIR) M=$(shell pwd) clean
在运行该makefile文件时,要用命令
因为在makefile中设置了条件判断ifeq().
Code3:用于测试生成的驱动程序的测试程序
- #include <stdio.h>
- #include <stdlib.h>
- #include <unistd.h>
- #include <sys/ioctl.h>
-
-
- #define SPI_READFLAG 0//读SPI设备标识
- #define SPI_WRITEFLAG 1//写SPI设备标识
-
- #define SPI_SS_L 3//置SS位为低
- #define SPI_SS_H 4//置SS位为高
-
-
- #define accXreg 0x06
- #define accYreg 0x07
- #define accZreg 0x08
-
-
- #define ACC_2G_RANGE 0x05
- #define ACC_4G_RANGE 0x09
- #define ACC_8G_RANGE 0x01
-
-
- char Xdata=0,Ydata=0,Zdata=0;
-
-
-
-
-
-
-
- void spiWrite2acc(int fd,unsigned char reg,char data)
- {
- char wter=0;
- char *writebuf=&wter;
- *writebuf=((reg&0x3F)<<1)|0x80;
- ioctl(fd, SPI_SS_L);
- ioctl(fd, SPI_WRITEFLAG, writebuf);
- ioctl(fd, SPI_WRITEFLAG, &data);
- ioctl(fd, SPI_SS_H);
- }
-
-
-
-
-
-
-
-
- void spiReadacc(int fd,unsigned char reg,char *rdbuf)
- {
- char wter=0;
- char *writebuf=&wter;
- ioctl(fd, SPI_SS_L);
- *writebuf=(reg &0x3F)<<1;
- ioctl(fd, SPI_WRITEFLAG, writebuf);
- ioctl(fd, SPI_READFLAG, rdbuf);
- ioctl(fd, SPI_SS_H);
- }
-
-
-
-
-
-
-
-
- void readAcc(int fd)
- {
- spiReadacc(fd,accXreg,&Xdata);
- spiReadacc(fd,accYreg,&Ydata);
- spiReadacc(fd,accZreg,&Zdata);
- }
-
-
-
-
-
-
-
-
- void showAcc(void)
- {
- printf("%x %x %x",Xdata,Ydata,Zdata);
- }
-
-
-
-
-
-
-
-
- void acc_init(int fd)
- {
- char ret;
- ioctl(fd, SPI_SS_H);
- spiWrite2acc(fd,0x16,ACC_2G_RANGE);
- spiReadacc(fd,0x16,&ret);
- if(ACC_2G_RANGE!=ret)
- printf("No acceleration!\n");
-
- readAcc(fd);
- }
-
- void delay_gh(int time)
- {
- int i,j;
- for(i=0;i<0xffff;i++)
- for(j=time;j>0;j--);
- }
-
-
-
-
-
-
-
-
- int main(int argc, char **argv)
- {
-
- int fd;
- int on=1;
- int led_no;
- char wtevr=0;
-
- char whtevr1,whtevr2=0x56;
- char *redbuf=&whtevr1;
- char *writebuf=&whtevr2;
-
- fd = open("/dev/spi_gh", 0);
- if (fd < 0) {
- perror("open device spi_gh");
- exit(1);
- }
- printf("OPEN OK!\n");
-
- acc_init(fd);
- while(1)
- {
- readAcc(fd);
- printf("%x %x %x\n",Xdata,Ydata,Zdata);
- delay_gh(0x10);
- }
-
- close(fd);
- return 0;
- }
这个程序能够完成基本的测试功能,读出MMA7455加速度计的值,要用交叉编译哦:
- arm-linux-gcc -o test test.c
完成以上程序的编译后下载到ARM开发板,记住不要用NFS哦,会出错的,错误好像是说版本问题:
insmod: kernel-module version mismatch
chdev_test.ko was compiled for kernel version
chdev_test.ko was compiled for kernel version
出错了,就要解决错误,方法是:把下载程序的方法改为用ftp下载或U盘下载等多种方法,看看使用手册吧
之后要挂载我们的.ko文件,挂载方法为:
其中xxxx就是你的设备驱动程序的文件名
挂载上之后不需要在mknod了,因为在我们的设备驱动程序初始化中已经有了MKDEV了:
- devfs_mk_cdev(MKDEV(SPI_MAJOR_NUM, 0), S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP, DEVICE_NAME);
之后就运行我们的测试程序:
然后呢又出错了:好像是什么Permission denied,好说,修改一下权限:
然后再运行
这下OK了!可以运行鸟。。。
其中遇到过N多的问题,让我灰心丧气了好久好久,然后一点一点的解决,今天终于读出数来了,心里那个兴奋啊。。。呵呵