1. 程式人生 > >一週搞定MPU6050Linux驅動(1)

一週搞定MPU6050Linux驅動(1)

第一日 準備工作: 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,請註明“交流探討”