第一日
准备工作:
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 <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
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 <linux/kernel.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/gpio.h>
#include <linux/init.h>
#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 <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/mutex.h>
#include <linux/workqueue.h>
#include <linux/i2c.h>
#include <linux/types.h>
#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,得到:
可以看到驱动确实在工作,在不断获取陀螺仪数据,这里并没有将数据进行转化。