在咱們實際開發中,I2C 總線驅動通常芯片原廠會提供,咱們開發通常是設計設備驅動。html
本文結合以前對Linux內核的IIC子系統的分析 ,以及對字符設備的cdev接口的分析,本文將編寫基於IIC總線與cdev接口的MPU6050設備的實例代碼並對其進行分析。node
IIC子系統分析:詳見Linux IIC總線驅動框架。linux
字符設備的cdev接口分析:詳見Linux字符設備驅動(一):cdev接口。緩存
硬件接口:數據結構
CPU:s5pv210;框架
掛載IIC總線編號:0。ide
IIC從設備驅動掛載在IIC總線下,IIC總線管理着IIC從設備的設備信息(i2c_client)與設備驅動(i2c_driver)。所以,IIC從設備驅動的編寫分爲兩個部分:註冊IIC從設備信息、編寫IIC從設備驅動程序。spa
在設備驅動程序中使用IIC總線,需確保Linux內核支持IIC。在menuconfig下作以下設置接口。設計
Device Drivers --->code
<*> I2C support --->
在/kernel/arch/arm/mach-s5pv210/mach-x210.c文件中添加以下信息,向Linux內核註冊IIC從設備信息。
static struct i2c_board_info mpu6050_i2c_devs0[] __initdata = { { I2C_BOARD_INFO("mpu6050", 0x2b),//構建i2c_board_info結構體,0x2b爲從設備的地址 }, }; static void __init smdkc110_machine_init(void) { ... ... i2c_register_board_info(0, mpu6050_i2c_devs0, ARRAY_SIZE(mpu6050_i2c_devs0));//向內核註冊IIC從設備信息,0表示該從設備掛載在IIC總線適配器0 ... ... }
(1)mpu6050_common.h
將mpu6050_common.h文件添加至/kernel/drivers/i2c/busses目錄下。
#ifndef _MPU6050_COMMON_H_ #define _MPU6050_COMMON_H_ #define MPU6050_MAGIC 'K' //mpu6050數據結構 union mpu6050_data { struct { short x; short y; short z; }accel; struct { short x; short y; short z; }gyro; unsigned short temp; }; //mpu6050的ioctl的命令定義 #define GET_ACCEL _IOR(MPU6050_MAGIC, 0, union mpu6050_data)//讀取加速度計的數據 #define GET_GYRO _IOR(MPU6050_MAGIC, 1, union mpu6050_data)//讀取陀螺儀的數據 #define GET_TEMP _IOR(MPU6050_MAGIC, 2, union mpu6050_data)//讀取溫度的數據 #endif
(2)mpu6050_dev.h
mpu6050_dev.h文件是mpu6050的寄存器地址的定義文件,將其添加至添加至/kernel/drivers/i2c/busses目錄下。
#ifndef _MPU6050_DEV_H_ #define _MPU6050_DEV_H_ #define SMPLRT_DIV 0x19 //陀螺儀採樣率,典型值:0x07(125Hz) #define CONFIG 0x1A //低通濾波頻率,典型值:0x06(5Hz) #define GYRO_CONFIG 0x1B //陀螺儀自檢及測量範圍,典型值:0x18(不自檢,2000deg/s) #define ACCEL_CONFIG 0x1C //加速計自檢、測量範圍及高通濾波,典型值:0x18(不自檢,2G,5Hz) #define ACCEL_XOUT_H 0x3B #define ACCEL_XOUT_L 0x3C #define ACCEL_YOUT_H 0x3D #define ACCEL_YOUT_L 0x3E #define ACCEL_ZOUT_H 0x3F #define ACCEL_ZOUT_L 0x40 #define TEMP_OUT_H 0x41 #define TEMP_OUT_L 0x42 #define GYRO_XOUT_H 0x43 #define GYRO_XOUT_L 0x44 #define GYRO_YOUT_H 0x45 #define GYRO_YOUT_L 0x46 #define GYRO_ZOUT_H 0x47 //陀螺儀z軸角速度數據寄存器(高位) #define GYRO_ZOUT_L 0x48 //陀螺儀z軸角速度數據寄存器(低位) #define PWR_MGMT_1 0x6B //電源管理,典型值:0x00(正常啓用) #define WHO_AM_I 0x75 //IIC地址寄存器(默認數值0x68,只讀) #define SlaveAddress 0x68 //MPU6050-I2C地址寄存器 #endif
(3)mpu6050.c
#include <linux/module.h> #include <linux/init.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/device.h> #include <linux/i2c.h> #include <linux/uaccess.h> #include <linux/usb.h> #include <linux/cdev.h> #include "mpu6050_dev.h" #include "mpu6050_common.h" #define DEV_MINOR 100 //IIC從設備的起始次設備號 #define DEV_CNT 1 //IIC從設備的個數 #define DEV_NAME "mpu6050" //IIC從設備名稱 static struct i2c_client *mpu6050_client; static struct cdev mpu6050_dev; static dev_t mpu6050_devnum; //設備號 static struct class *mpu6050_class;//設備類 /* * 功能:向mpu6050從設備寫入數據 * * 參數:struct i2c_client *client:指向mpu6050從設備 * const unsigned char reg:需寫入的mpu6050的寄存器 * const unsigned char val:寫入的數值 */ static void mpu6050_write_byte(struct i2c_client *client, const unsigned char reg, const unsigned char val) { char txbuf[2] = {reg, val};//數據緩存buffer //封裝msg struct i2c_msg msg[2] = { [0] = { .addr = client->addr, .flags= 0, .len = sizeof(txbuf), .buf = txbuf, }, }; i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg));//與從設備進行數據通訊 } /* * 功能:向mpu6050從設備讀取數據 * * 參數:struct i2c_client *client:指向mpu6050從設備 * const unsigned char reg:需讀取的mpu6050的寄存器 * * 返回值:char:讀取的數據 */ static char mpu6050_read_byte(struct i2c_client *client,const unsigned char reg) { char txbuf[1] = {reg};//數據緩衝buffer char rxbuf[1] = {0}; //封裝msg struct i2c_msg msg[2] = { [0] = { .addr = client->addr, .flags = 0, .len = sizeof(txbuf), .buf = txbuf, }, [1] = { .addr = client->addr, .flags = I2C_M_RD, .len = sizeof(rxbuf), .buf = rxbuf, }, }; i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)); //與從設備進行數據通訊 return rxbuf[0]; } //mpu6050硬件初始化 static void mpu6050_init(struct i2c_client *client) { mpu6050_write_byte(client, PWR_MGMT_1, 0x00); mpu6050_write_byte(client, SMPLRT_DIV, 0x07); mpu6050_write_byte(client, CONFIG, 0x06); mpu6050_write_byte(client, GYRO_CONFIG, 0x18); mpu6050_write_byte(client, ACCEL_CONFIG, 0x0); } static int mpu6050_open(struct inode *ip, struct file *fp) { return 0; } static int mpu6050_release(struct inode *ip, struct file *fp) { return 0; } static long mpu6050_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) { int res = 0; union mpu6050_data data = {{0}}; switch(cmd) { //讀取加速度計的數據 case GET_ACCEL: data.accel.x = mpu6050_read_byte(mpu6050_client,ACCEL_XOUT_L); data.accel.x|= mpu6050_read_byte(mpu6050_client,ACCEL_XOUT_H)<<8; data.accel.y = mpu6050_read_byte(mpu6050_client,ACCEL_YOUT_L); data.accel.y|= mpu6050_read_byte(mpu6050_client,ACCEL_YOUT_H)<<8; data.accel.z = mpu6050_read_byte(mpu6050_client,ACCEL_ZOUT_L); data.accel.z|= mpu6050_read_byte(mpu6050_client,ACCEL_ZOUT_H)<<8; break; //讀取陀螺儀的數據 case GET_GYRO: data.gyro.x = mpu6050_read_byte(mpu6050_client,GYRO_XOUT_L); data.gyro.x|= mpu6050_read_byte(mpu6050_client,GYRO_XOUT_H)<<8; data.gyro.y = mpu6050_read_byte(mpu6050_client,GYRO_YOUT_L); data.gyro.y|= mpu6050_read_byte(mpu6050_client,GYRO_YOUT_H)<<8; data.gyro.z = mpu6050_read_byte(mpu6050_client,GYRO_ZOUT_L); data.gyro.z|= mpu6050_read_byte(mpu6050_client,GYRO_ZOUT_H)<<8; printk("gyro:x %d, y:%d, z:%d\n",data.gyro.x,data.gyro.y,data.gyro.z); break; //讀取溫度的數據 case GET_TEMP: data.temp = mpu6050_read_byte(mpu6050_client,TEMP_OUT_L); data.temp|= mpu6050_read_byte(mpu6050_client,TEMP_OUT_H)<<8; printk("temp: %d\n",data.temp); break; default: printk(KERN_INFO "invalid cmd"); break; } printk("acc:x %d, y:%d, z:%d\n",data.accel.x,data.accel.y,data.accel.z); res = copy_to_user((void *)arg,&data,sizeof(data)); return sizeof(data); } //mpu6050操做集 static const struct file_operations mpu6050_fops = { .owner = THIS_MODULE, .open = mpu6050_open, .release = mpu6050_release, .unlocked_ioctl = mpu6050_ioctl, }; static int mpu6050_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct device *dev; mpu6050_client = client; /*****************************初始化硬件設備******************************/ //初始化mpu6050 mpu6050_init(client); dbg("probe:name = %s,flag =%d,addr = %d,adapter = %d,driver = %s\n", client->name, client->flags,client->addr,client->adapter->nr,client->driver->driver.name ); /*********************************建立接口********************************/ cdev_init(&mpu6050_dev, &mpu6050_fops); //關聯dev與fops alloc_chrdev_region(&mpu6050_devnum, DEV_MINOR, DEV_CNT, DEV_NAME);//自動分配設備號 cdev_add(&mpu6050_dev, mpu6050_devnum, DEV_CNT); //添加設備至設備鏈表 mpu6050_class = class_create(THIS_MODULE,DEV_NAME); //建立設備類 dev = device_create(mpu6050_class, NULL , mpu6050_devnum, "%s%d", DEV_NAME);//建立mpu6050設備 if (IS_ERR(dev)) { dbg("device create error\n"); goto out; } return 0; out: return -1; } static int mpu6050_remove(struct i2c_client *client) { dbg("remove\n"); device_destroy(mpu6050_class, mpu6050_devnum); class_destroy(mpu6050_class); unregister_chrdev_region(mpu6050_devnum,DEV_CNT); return 0; } //與mpu6050的設備信息匹配 static struct i2c_device_id mpu6050_ids[] = { {"mpu6050",0x2b}, {} }; //聲明mpu6050_ids是i2c類型的一個設備表 MODULE_DEVICE_TABLE(i2c,mpu6050_ids); //定義並初始化從設備驅動信息 static struct i2c_driver mpu6050_driver = { .probe = mpu6050_probe, .remove = mpu6050_remove, .id_table = mpu6050_ids, .driver = { .name = "mpu6050", .owner = THIS_MODULE, }, }; static int __init mpu6050_i2c_init(void) { return i2c_add_driver(&mpu6050_driver);//註冊設備驅動 } static void __exit mpu6050_i2c_exit(void) { i2c_del_driver(&mpu6050_driver); //註銷設備驅動 } MODULE_AUTHOR("Lin"); MODULE_DESCRIPTION("mpu6050 driver"); MODULE_LICENSE("GPL"); module_init(mpu6050_i2c_init); module_exit(mpu6050_i2c_exit);