一周搞定MPU6050Linux驱动(1)

第一日

准备工作:

1、硬件平台

firefly-rk3288开发板,MPU6500六轴陀螺仪模块

2、参考

http://blog.csdn.net/airk000/article/details/22655171

http://developer.t-firefly.com/thread-10874-1-1.html

 

开干:

1、搭建开发环境

首先,搭建firefly-rk3288开发板的内核编译平台、源码树等。意思就是需要在ubuntu系统中下载firefly-rk3288的源码,按照http://developer.t-firefly.com/thread-10874-1-1.html论坛中提供的编译方法,对源码进行编译,然后利用论坛中提到的烧写工具烧写进开发板,使之能够正常运行。整个编译烧写过程,论坛中讲解的非常清楚。需要注意的是,在这一步中

./mkbootimg --kernel arch/arm/boot/zImage --ramdisk ../initrd.img --second resource.img  -o boot.img

论坛上提供的initrd.img可能不太好使,可以下载官方的镜像,解包得到ramdisk并使用它替代initrd.img。

所有的步骤在官网上都有教程,很详细,可以参考官网。其他的开发板,大同小异,不同的芯片有不同的方法烧写源码。

 

2、驱动测试Helloword

hello.c:

#include

#include

#include

static int __init hello_init(void)

{

printk(KERN_ALERT "hello driver init!\n");

return 0;

}

static void __exit hello_exit(void)

{

printk(KERN_ALERT "hello driver exit\n");

}

Makefile:

module_init(hello_init);

module_exit(hello_exit);

 

PWD=$(shell pwd)

KDIR:=/home/liugang/rk3288-ubuntu/firefly-rk3288-kernel

 

obj-m := hello.o

 

all:

make ARCH=arm CROSS_COMPILE=/home/liugang/rk3288-ubuntu/arm-eabi-4.6/bin/arm-eabi- -C $(KDIR) M=$(PWD) modules

 

clean:

@rm -rf *.ko

@rm -rf *.o

@rm -rf *.mod.c

@rm -rf *.symvers

@rm -rf *.order

@rm -rf .*.cmd

@rm -rf .tmp_versions/

 

直接make,得到hello.ko,将hello.ko使用fileZilla拷贝到开发板上

insmod hello.ko

rmmod hello.ko

可以得到:

说明内核源码树建立成功。

 

3、学习6050驱动

首先对Linux i2c设备驱动进行学习。参考http://blog.csdn.net/airk000/article/details/22655171

这个人的博客,对i2c总线驱动和挂载在总线上的设备驱动都进行了研究,并写出了很好的总结。另外,他对MPU6050 6轴陀螺仪的驱动也进行了实现。在他的github上有源码,可以直接下载学习。感谢分享。

挂载在i2c总线上的设备驱动,需要进行一下三个方面的代码编写:

a、i2_adapter 适配器,是i2c master的总线类别

b、i2c_client 挂载adapter上的从设备。

c 、i2_driver i2设备的驱动程序,通过name匹配到设备

就是对从设备结构体进行实现。将对应的参数、函数填写到结构体即可。client一般是在板级初始化得到的,driver和device以及client对应的name一样,这样内核将它们匹配起来。在初始化阶段,相应名字的client被得到。

下面我们将airk000同学的博客提到的mpu6050代码拿出来进行学习。这里我只对源码进行详细的注释,以达到学习的目的。

 

mpu_client.c

 

#include

#include

#include

#include

#include

#include "mpu6050_reg.h"

#include "log.h" //kernel调试需要的一些函数和define

 

static struct i2c_board_info mpu6xxx_id =

{

I2C_BOARD_INFO(SENSOR_NAME, 0x68),

//SENSOR_NAME在mpu6050_reg.h中定义,为"mpu6050-i2c"。

};

/*struct i2c_board_info {

    char type[I2C_NAME_SIZE];  //芯片类型,用于初始化i2c_client.name

    unsigned short flags;  //用于初始化i2c_client.flags

    unsigned short addr;  //存储于i2c_client.addr

    void *platform_data;  //存储于i2c_client.dev.platform_data

    struct dev_archdata *archdata;  //拷贝至i2c_client.dev.archdata

    int irq;  //存储于i2c_client.irq

};

i2c_board_info 用于构建信息表来列出存在的I2C设备。这一信息用于增长新型I2C驱动的驱动模型树。对于主板,它使用i2c_register_board_info()来静态创建。对于子板,利用已知的适配器使用i2c_new_device()动态创建。*/

 

static int __init mpu6xxx_init(void)

{

struct i2c_adapter *adap;

struct i2c_client *client;

 

adap = i2c_get_adapter(1); //实例i2c_adapter,使用的是i2c-1

if (!adap)

goto erradap;

 

mpu6xxx_id.irq = gpio_to_irq(48);

//获取中断号,gpio_to_irq函数的参数48具体怎么来的?这里不是很清楚,需要详细查看一下GPIO的驱动。

client = i2c_new_device(adap, &mpu6xxx_id);

//将设备挂载到adpter,获取client,如果在dts中或者以前的板级定义中定义了,在初始化阶段,即可完成挂载阶段,直接获取client。

if (!client)

goto errclient;

D("Client OK\n");

return 0;

 

errclient:

i2c_unregister_device(client);

 

erradap:

i2c_put_adapter(adap);

return -ENODEV;

}

static void __exit mpu6xxx_exit(void)

{

}

module_init(mpu6xxx_init);

module_exit(mpu6xxx_exit);

MODULE_AUTHOR("Kevin Liu");

MODULE_LICENSE("GPL");

 

/*mpu_client.c的目的很简单,就是将name为mpu6050-i2c的设备挂载到i2c-1总线上得到相应的clent。*/

 

mpu_driver.c

 

#include

#include

#include

#include

#include

#include

#include

#include

 

#include "log.h"

#include "mpu6050_reg.h"

 

MODULE_LICENSE("GPL");

MODULE_AUTHOR("Kevin Liu");

 

struct axis_data {

s16 value;

int standby;

};

 

struct sub_sensor {

int st; // self-test

int reset; // reset

int sel; // full scale range

struct axis_data x;

struct axis_data y;

struct axis_data z;

};

 

struct temp_sensor {

int enable;

int reset;

s16 value;

};

 

struct pwr_mgmt {

int reset;

int sleep;

int cycle;

int cycle_HZ;

int clksel;

int all_standby;

};

 

struct mpu6050_data {

struct mutex lock;

struct i2c_client *client;

struct delayed_work work;

struct workqueue_struct *wq;

int delay_ms;

 

struct sub_sensor gyro;

struct sub_sensor accel;

struct temp_sensor temp_s;

struct pwr_mgmt power;

int dlph;

int dhph;

};

 

enum {

RANGE,

LSB

};

static float accel_sel[][2] = {

{2, 16384},

{4, 8192},

{8, 4096},

{16, 2048}

};

 

static float gyro_sel[][2] = {

{250, 131},

{500, 65.5},

{1000, 32.8},

{2000, 16.4}

};

 

static void mpu6050_enable(struct mpu6050_data *mpu6050)

{

struct i2c_client *client = mpu6050->client;

i2c_smbus_write_byte_data(client, MPU6050_REG_PWR_MGMT_1, 0);

}

 

static void mpu6050_disable(struct mpu6050_data *mpu6050)

{

struct i2c_client *client = mpu6050->client;

i2c_smbus_write_byte_data(client, MPU6050_REG_PWR_MGMT_1,

1 << PWR_1_SLEEP_OFFSET);

}

 

static void mpu6050_reset(struct mpu6050_data *mpu6050)

{

struct i2c_client *client = mpu6050->client;

i2c_smbus_write_byte_data(client, MPU6050_REG_PWR_MGMT_1,

1 << PWR_1_DEVICE_RESET_OFFSET);

}

 

/*

* Get gyro/accel/temprature data

* @type : 0 - gyro

* 1 - accel

* 2 - temprature

*/

static int mpu6050_read_data(struct mpu6050_data *mpu6050, int type)

{

s16 values[3];

int i, addr, ret;

struct i2c_client *client = mpu6050->client;

 

switch(type) {

case 0:

addr = MPU6050_REG_GYRO_XOUT_H;

break;

case 1:

addr = MPU6050_REG_ACCEL_XOUT_H;

break;

case 2:

addr = MPU6050_REG_TEMP_OUT_H;

break;

default:

addr = MPU6050_REG_GYRO_XOUT_H;

break;

}

 

if (type == 0 || type == 1) {

ret = i2c_smbus_read_i2c_block_data(client, addr,

6, (u8 *)values);

if (ret < 0) {

E("error read gyro\n");

return ret;

}

 

for (i = 0; i < 3; i++) {

values[i] = be16_to_cpu(values[i]);

}

} else if (type == 2) {

ret = i2c_smbus_read_i2c_block_data(client, addr,

2, (u8 *)values);

if (ret < 0) {

E("error read gyro\n");

return ret;

}

 

for (i = 0; i < 1; i++) {

values[i] = be16_to_cpu(values[i]);

}

}

 

switch(type) {

case 0:

mpu6050->gyro.x.value = values[0];

mpu6050->gyro.y.value = values[1];

mpu6050->gyro.z.value = values[2];

break;

case 1:

mpu6050->accel.x.value = values[0];

mpu6050->accel.y.value = values[1];

mpu6050->accel.z.value = values[2];

break;

case 2:

mpu6050->temp_s.value = values[0];

break;

default:

break;

}

 

return 0;

}

 

static int mpu6050_read_gyro(struct mpu6050_data *mpu6050)

{

return mpu6050_read_data(mpu6050, 0);

}

 

static int mpu6050_read_accel(struct mpu6050_data *mpu6050)

{

return mpu6050_read_data(mpu6050, 1);

}

 

static int mpu6050_read_temprature(struct mpu6050_data *mpu6050)

{

return mpu6050_read_data(mpu6050, 2);

}

 

static void mpu6050_dump_all(struct mpu6050_data *mpu6050)

{

D("Gyro(X:%d Y:%d Z:%d)\tAccel(X:%d Y:%d Z:%d)\tTemp:%d\n",

mpu6050->gyro.x.value, mpu6050->gyro.y.value,

mpu6050->gyro.z.value, mpu6050->accel.x.value, mpu6050->accel.y.value,

mpu6050->accel.z.value, mpu6050->temp_s.value);

}

 

static void mpu6050_work(struct work_struct *work)

{

int ret;

struct mpu6050_data *mpu6050 = container_of(

(struct delayed_work *)work, struct mpu6050_data, work);

 

mpu6050_read_gyro(mpu6050);

mpu6050_read_accel(mpu6050);

mpu6050_read_temprature(mpu6050);

 

mpu6050_dump_all(mpu6050);

 

schedule_delayed_work(&mpu6050->work,

msecs_to_jiffies(mpu6050->delay_ms));

}

 

static int mpu6050_probe(struct i2c_client *client,

const struct i2c_device_id *id)

{

struct mpu6050_data *mpu6050;

u16 version;

 

D("Probe match happend, ID %s\n", id->name);

 

if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {

E("I2C check error\n");

return -EINVAL;

}

mpu6050 = kzalloc(sizeof(*mpu6050), GFP_KERNEL); //申请内存

if (!mpu6050) {

E("Mem error\n");

return -ENOMEM;

} else

D("Alloc OK\n");

 

mpu6050->client = client;

i2c_set_clientdata(client, mpu6050); //mmpu6050在clent中注册

mutex_init(&mpu6050->lock);

mpu6050->delay_ms = 1000;

D("Set OK\n");

 

INIT_DELAYED_WORK(&mpu6050->work, mpu6050_work);

D("Work queue OK\n");

//INIT_DELAYED_WORK 初始化带延时的工作队列work,将mpu6050_work这个函数放到工作队列中,然后等到调用schedule_delayed_work时执行。

 

version = i2c_smbus_read_byte_data(client, MPU6050_REG_WHO_AM_I);

if (version != 0x68) {

E("Version check error 0x%X, skip\n", version);

goto free_all;

} else

D("Version Check OK\n");

// 读ID

mpu6050_reset(mpu6050);

mpu6050_enable(mpu6050);

schedule_delayed_work(&mpu6050->work,

msecs_to_jiffies(mpu6050->delay_ms));

//这里调用异步执行mpu6050_work这个函数。

return 0;

free_all:

kfree(mpu6050);

E("A oh!!!ooops...\n");

return -EINVAL;

}

 

static int mpu6050_remove(struct i2c_client *client)

{

struct mpu6050_data *mpu6050 = i2c_get_clientdata(client);

 

mpu6050_disable(mpu6050);

cancel_delayed_work(&mpu6050->work);

kfree(mpu6050);

 

return 0;

}

 

static struct i2c_device_id mpu6050_ids[] = {

{SENSOR_NAME, 0},

{ },

};

 

static struct i2c_driver mpu6050_driver = {

.driver = {

.name = SENSOR_NAME,

.owner = THIS_MODULE,

},

.class = I2C_CLASS_HWMON,

.id_table = mpu6050_ids,

.probe = mpu6050_probe,

.remove = mpu6050_remove,

};

/*上面定义i2c_driver结构体,整个文件的目的就是实现i2c_driver结构体,并通过module_i2c_driver

注册i2c驱动,当i2c_driver和i2c_client的name一样,系统就对其进行probe,也就是运行mpu6050_probe函数。*/

module_i2c_driver(mpu6050_driver);

 

 

查看整个驱动,实现了i2c_driver,挂载了i2c设备获得了i2c_cilent,使用了工作队列,实现数据的延时连续读取。这里分别对mpu_client.c 和mpu_driver.c进行编译得到mpu_client.ko,mpu_driver.ko。先加载mpu_client,再加载mpu_driver,得到:

可以看到驱动确实在工作,在不断获取陀螺仪数据,这里并没有将数据进行转化。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(Linux编程,硬件)