课题水下机器人需要测定水下机器人的位姿,为此应用了加速度计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 //主设备号 //int whtaever=0; volatile int *spi_gpecon=NULL;//GPG Part define volatile int *spi_gpedat=NULL; volatile int *spi_gpeup=NULL; volatile int *s3c2440_clkcon=NULL; volatile int *spi_spcon0=NULL;//SPI Part define 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; //定义SPI对应的引脚号 static unsigned long spi_pin_tab[]={ S3C2410_GPE11, S3C2410_GPE12, S3C2410_GPE13, S3C2410_GPG2, }; //配置SPI引脚功能 static unsigned long spi_pin_cfg_tab[]={ S3C2410_GPE11_SPIMISO0, S3C2410_GPE12_SPIMOSI0, S3C2410_GPE13_SPICLK0, S3C2410_GPG2_OUTP, }; /*====================================== 驱动程序函数:__open 功能说明 :挂载模块退出程序 =======================================*/ 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) 功能说明 :写一个字节到SPI 入口参数:预写入的值 返回值:无 说明: ==============================================================================*/ 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) 功能说明 :从spi读一个字节 入口参数:无 返回值:读到的值 说明: ==============================================================================*/ 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; } /*====================================== 驱动程序函数:ioctl 功能说明 : =======================================*/ 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://读spi设备的值 //printk("<1>spi read!\n"); ch=readByte(); copy_to_user(spiBuf,&ch,1); //printk("<1>spi read content:%x\n",*spiBuf); return 1; break; case 1://向spi写入数据 //printk("<1>spi write!\n"); copy_from_user(kbuf,spiBuf,1); //printk("<1>copy_from_user OK!\n"); writeByte(*kbuf); //printk("<1>spi write content:%x\n",*spiBuf); return 1; break; case 3://设置SS引脚为低 s3c2410_gpio_setpin(spi_pin_tab[3],0);//nSS0=0 return 0; break; case 4://设置SS引脚为高 s3c2410_gpio_setpin(spi_pin_tab[3],1);//nSS0=1 return 0; break; default: return -EINVAL; } } /*====================================== 驱动程序函数:fops 功能说明 :注册驱动程序的接口功能函数 =======================================*/ static struct file_operations spi_fops = { .open =spi_open, .ioctl = spi_ioctl, }; /*====================================== 驱动程序函数:__init 功能说明 :挂载模块初始化程序 =======================================*/ 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); //GPIO_E寄存器(其中有SPI_0的引脚设置) spi_gpecon = (int *)ioremap (0x56000040,4); //GPeCON ,765位为SPI_0 spi_gpedat = (int *)ioremap (0x56000044,2); //GPeDAT spi_gpeup = (int *)ioremap (0x56000048,2); //GPeUP spi_spcon0 = (int *)ioremap(0x59000000,1); //spcon0:spi控制寄存器0 spi_spsta0 = (int *)ioremap(0x59000004,1); //spsta0:spi状态寄存器0 spi_sppin0 = (int *)ioremap(0x59000008,1); //sppin0:spi引脚控制寄存器0 spi_sppre0= (int *)ioremap(0x5900000c,1); //sppre0:spi波特率预分频寄存器0 spi_sptdat0 = (int *)ioremap(0x59000010,1); //sptdat0:spi发送数据寄存器0 spi_sprdat0 = (int *)ioremap(0x59000014,1); //sprdat0:spi接收数据寄存器0 printk(DEVICE_NAME" initial OK!\n"); } /*====================================== 驱动程序函数:__exit 功能说明 :挂载模块退出程序 =======================================*/ 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); //end
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文件时,要用命令
make v=arm
因为在makefile中设置了条件判断ifeq().
Code3:用于测试生成的驱动程序的测试程序
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/ioctl.h> //定义SPI设备驱动的读写标志符 #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) 功能: SPI主机向MMA7455加速度计写入指令 参数: MMA7455的寄存器和指令 返回值: 无 说明: ==================================*/ 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) 功能: SPI主机读取MMA7455加速度计值 参数: MMA7455的寄存器 返回值: 指定寄存器的值 说明: ==================================*/ 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(void) 功能: SPI初始化 参数: spi设备文件句柄 返回值: 无 说明: ==================================*/ void readAcc(int fd) { spiReadacc(fd,accXreg,&Xdata); spiReadacc(fd,accYreg,&Ydata); spiReadacc(fd,accZreg,&Zdata); } /*================================== 函数: void readAcc(void) 功能: SPI初始化 参数: spi设备文件句柄 返回值: 无 说明: ==================================*/ void showAcc(void) { printf("%x %x %x",Xdata,Ydata,Zdata); } /*================================== 函数: void acc_init(int fd) 功能: SPI初始化 参数: spi设备文件句柄 返回值: 无 说明: ==================================*/ 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) 功能: 测试程序入口 参数: 返回值: 无 说明:spi测试程序 ==================================*/ 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文件,挂载方法为:
insmod xxxx.ko
其中xxxx就是你的设备驱动程序的文件名
挂载上之后不需要在mknod了,因为在我们的设备驱动程序初始化中已经有了MKDEV了:
devfs_mk_cdev(MKDEV(SPI_MAJOR_NUM, 0), S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP, DEVICE_NAME); //设备注册完成后用此句创建设备节点
之后就运行我们的测试程序:
./test
然后呢又出错了:好像是什么Permission denied,好说,修改一下权限:
chmod 777 test
然后再运行
./test
这下OK了!可以运行鸟。。。
其中遇到过N多的问题,让我灰心丧气了好久好久,然后一点一点的解决,今天终于读出数来了,心里那个兴奋啊。。。呵呵