一週搞定MPU6050Linux驅動(1)
阿新 • • 發佈:2019-02-01
第一日
準備工作:
1、硬體平臺
firefly-rk3288開發板,MPU6500六軸陀螺儀模組
2、參考
開幹:
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驅動
這個人的部落格,對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,得到:
可以看到驅動確實在工作,在不斷獲取陀螺儀資料,這裡並沒有將資料進行轉化。
有什麼疑問或者交流的加我QQ475292178,請註明“交流探討”