课题水下机器人需要测定水下机器人的位姿,为此应用了加速度计MMA7455,该传感器可以用SPI或I2C读取数字信号到MCU。
驱动MMA7455在atmega128上已经实现,但是由于mega128的速度、资源等瓶颈使得继续开发受到一定限制,故改用arm处理器。
在arm处理器上运行linux操作系统,要完成对MMA7455加速度计的驱动需要了解linux下的设备驱动程序,因此编写了相应的设备驱动程序。
在学习过程中Makefile的编写非常重要,对于我这样的菜鸟来说感觉makefile是设备驱动的一大障碍,试了好多各种各样的makefile,最终发现一个好用的makefile,而且似乎可以通用,于是记录下来了:http://blog.youkuaiyun.com/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多的问题,让我灰心丧气了好久好久,然后一点一点的解决,今天终于读出数来了,心里那个兴奋啊。。。呵呵