课题水下机器人需要测定水下机器人的位姿,为此应用了加速度计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多的问题,让我灰心丧气了好久好久,然后一点一点的解决,今天终于读出数来了,心里那个兴奋啊。。。呵呵