应用QQ2440(s3c2440)ARM开发板驱动MMA7455加速度计的linux设备驱动编写


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

 







 

你可能感兴趣的:(c,linux,qq,struct,测试,makefile)