Linux i2c子系統(一) _動手寫一個i2c設備驅動


i2c總線是一種十分常見的板級總線,本文以linux3.14.0為參考, 討論Linux中的i2c驅動模型並利用這個模型寫一個mpu6050的驅動, 最后在應用層將mpu6050中的原始數據讀取出來

i2c子系統框架

下圖就是我理解的i2c驅動框架示意圖, 類似中斷子系統, i2c子系統中也使用一個對象來描述一個物理實體, 設備對象與驅動分離, 驅動結合設備對象對硬件設備的描述才可以驅動一個具體的物理設備, 體現了分離的設計思想, 實現了代碼的復用, 比如:

  • 一個i2c控制器就對應一個i2c_board_info, 它驅動就是s3c2410_i2c_driver, 他們通過platform_bus_type協調工作。
  • 一個i2c總線上的設備就對應內核中的一個i2c_client類型的對象, 它的驅動就是的i2c_driver, 二者通過i2c_bus_type協調工作。
  • 同樣是抽象的思路, 對於i2c總線本身, 內核也使用i2c_bus_type來描述。

事實上, 對於任何一種總線, 內核都有一個bus_type類型的對象與之對應, 但是platform_bus_type並沒有對應的實際的物理總線, 這也就是platform總線也叫虛擬總線的原因.

除了分離,i2c子系統也體現的軟件分層的設計思想, 整個i2c子系統由3層構成:設備驅動層--i2c核心--控制器驅動

除了經典的分層與分離模型,我們也可以看到一個有意思的現象——Linux 的應用程序不但可以通過設備驅動來訪問i2c從設備,還可以通過一個並沒有直接掛接到i2c_bus_type的i2c_cleint來找到主機控制器進而訪問任意一個i2c設備, 這是怎么回事呢? 我會在下一篇說^-^

核心結構和方法簡述

核心結構

  • i2c_adapter對象實現了一組通過一個i2c控制器發送消息的所有信息, 包括時序, 地址等等, 即封裝了i2c控制器的"控制信息"。它被i2c主機驅動創建, 通過clien域和i2c_client和i2c_driver相連, 這樣設備端驅動就可以通過其中的方法以及i2c物理控制器來和一個i2c總線的物理設備進行交互
  • i2c_algorithm描述一個i2c主機的發送時序的信息,該類的對象algo是i2c_adapter的一個域,其中的master_xfer()注冊的函數最終被設備驅動端的i2c_transfer()回調。
  • i2c_client描述一個掛接在硬件i2c總線上的設備的設備信息,即i2c設備的設備對象,與i2c_driver對象匹配成功后通過detected和i2c_driver以及i2c_adapter相連,在控制器驅動與控制器設備匹配成功后被控制器驅動通過i2c_new_device()創建。
  • i2c_driver描述一個掛接在硬件i2c總線上的設備的驅動方法,即i2c設備的驅動對象,通過i2c_bus_type和設備信息i2c_client匹配,匹配成功后通過clients和i2c_client對象以及i2c_adapter對象相連
  • i2c_msg描述一個在設備端和主機端之間進行流動的數據, 在設備驅動中打包並通過i2c_transfer()發送。相當於skbuf之於網絡設備,urb之於USB設備。

核心方法

  • i2c_transfer()是i2c核心提供給設備驅動的發送方法, 通過它發送的數據需要被打包成i2c_msg, 這個函數最終會回調相應i2c_adapter->i2c_algorithm->master_xfer()接口將i2c_msg對象發送到i2c物理控制器

核心結構與方法詳述

i2c_adapter

我首先說i2c_adapter, 並不是編寫一個i2c設備驅動需要它, 通常我們在配置內核的時候已經將i2c控制器的設備信息和驅動已經編譯進內核了, 就是這個adapter對象已經創建好了, 但是了解其中的成員對於理解i2c驅動框架非常重要, 所有的設備驅動都要經過這個對象的處理才能和物理設備通信

//include/linux/i2c.h
425 struct i2c_adapter {
426         struct module *owner;
427         unsigned int class;               /* classes to allow probing for */
428         const struct i2c_algorithm *algo; /* the algorithm to access the bus */
429         void *algo_data;
430 
431         /* data fields that are valid for all devices   */
432         struct rt_mutex bus_lock;
433 
434         int timeout;                    /* in jiffies */
435         int retries;
436         struct device dev;              /* the adapter device */
437 
438         int nr;
439         char name[48];
440         struct completion dev_released;
441 
442         struct mutex userspace_clients_lock;
443         struct list_head userspace_clients;
444 
445         struct i2c_bus_recovery_info *bus_recovery_info;
446 };

struct i2c_adapter
--428-->這個i2c控制器需要的控制算法, 其中最重要的成員是master_xfer()接口, 這個接口是硬件相關的, 里面的操作都是基於具體的SoCi2c寄存器的, 它將完成將數據發送到物理i2c控制器的"最后一公里"
--436-->表示這個一個device, 會掛接到內核中的鏈表中來管理, 其中的
--443-->這個節點將一個i2c_adapter對象和它所屬的i2c_client對象以及相應的i2c_driver對象連接到一起

下面是2個i2c-core.c提供的i2c_adapter直接相關的操作API, 通常也不需要設備驅動開發中使用,

i2c_add_adapter

這個API可以將一個i2c_adapter類型的對象注冊到內核中, 源碼我就不貼了, 下面是他們的調用關系,我們可以從中看到一個adapter對象和系統中的i2c_driver對象以及i2c_client對象的匹配流程。
首先,我們在驅動中構造一個i2c_adapter對象的時候,對其中的相關域進行初始化,這里我們最關心它的父設備

//drivers/i2c/buses/i2c-s3c2410.c
1072 static int s3c24xx_i2c_probe(struct platform_device *pdev) 
1073 {    
1140         i2c->adap.dev.parent = &pdev->dev;  
1210 }

得到了這樣一個i2c_adapter對象,我們就可以調用這個API將它注冊到內核,調用關系如下:

i2c_add_adapter()
1            └──i2c_register_adapter(adapter)
2                      ├──adap->dev.bus = &i2c_bus_type;
3                      ├──adap->dev.type = &i2c_adapter_type;
4                      │            └──i2c_adapter_attr_groups
5                      │                       └── i2c_adapter_attr_group
6                      │                                   └── i2c_adapter_attrs
7                      │                                               └── &dev_attr_new_device.attr
8                      │                                                            └──DEVICE_ATTR(new_device, S_IWUSR, NULL, i2c_sysfs_new_device);
9                      │                                                                        └──i2c_sysfs_new_device()
10                     │                                                                                                 └──list_add_tail(&client->detected, &adap->userspace_clients);
11                     └──device_register(&adap-dev);
12                                  ├──device_initialize(dev);
13                                  │            ├──/* /sys/devices/ */
14                                  │            ├──struct kset *devices_kset;
15                                  │            ├──dev->kobj.kset = devices_kset;
16                                  │            ├──kobject_init(&dev->kobj, &device_ktype);
17                                  │            └──set_dev_node(dev, -1);
18                                  └──device_add(dev);
19                                              ├──parent=get_device(dev->parent);
20                                              ├──kobj = get_device_parent(dev, parent);
21                                              │            └──return &parent->kobj;
22                                              ├──dev->kobj.parent = kobj;
23                                              ├──set_dev_node(dev, dev_to_node(parent));
24                                              ├──kobject_add(&dev->kobj, dev->kobj.parent, NULL);
25                                              │            ├──kobject_add_varg(kobj, parent, fmt, args);
26                                              │            ├──kobj->parent = parent;
27                                              │            ├──kobject_add_internal(kobj);
28                                              │            ├──parent = kobject_get(kobj->parent);
29                                              │            ├──kobj_kset_join(kobj);
30                                              │            │            ├──kset_get(kobj->kset)
31                                              │            │            └──**list_add_tail(&kobj->entry, &kobj->kset->list); **
32                                              │            ├──kobj->parent = parent;
33                                              │            └──create_dir(kobj);
34                                              ├──device_create_file(dev, &dev_attr_uevent);
35                                              ├──device_create_sys_dev_entry(dev);
36                                              ├──devtmpfs_create_node(dev);
37                                              ├──device_add_class_symlinks(dev);
38                                              ├──device_add_attrs(dev);
39                                              ├──bus_add_device(dev);
40                                              ├──bus_probe_device(dev);
41                                              ├──klist_add_tail(&dev->p->knode_parent,&parent->p->klist_children);
42                                              └──klist_add_tail(&dev->knode_class,&dev->class->p->klist_devices);

調用關系就是這樣了,下面我簡單解釋一下這個樹

i2c_add_adapter()
--10-->將i2c_adapter對象中的userspace_clients與匹配到的client對象中detected連接到一起
--15-->將/sys/devices的kset賦值給i2c_adapter->device->kobject->parent,即建立i2c_adapter對象和/sys/devices的父子關系, 參見"Linux設備管理(一)_kobject, kset,ktype分析"
--20-->獲取device->parent的kobject對象
--22-->將device->parent的kobject對象作為device->kobject->parent, 形成device->kobject->parent == device->parent->kobject
--31-->將這個device->kobject掛接到device->kset->list鏈表中, 由此可見, kobject->kset指向的kset對象和kobject->entry掛接到的kset對象可以不是一個, 與"Linux設備管理(一)_kobject, kset,ktype分析"那種情況不同.

i2c_del_adapter()

從內核中刪除一個adapter

i2c_client

在i2c設備端,驅動開發的主要工作和平台總線一樣:構建設備對象和驅動對象,我用的開發板上的i2c總線上掛接的設備是mpu6050,接下來我就以我的板子為例,討論如何編寫i2c設備端的驅動。
同樣這里的設備對象也可以使用三種方式構建:平台文件,模塊和設備樹。
本文采用設備樹的方式構建設備對象,我們可以參考內核文檔"Documentations/devicetree/bindings/i2c/i2c-s3c2410.txt"以及設備樹中的樣板來編寫我們的設備樹節點,** 我們在設備樹中可不會寫mpu6050內部寄存器的地址,因為這些寄存器地址SoC看不到**。

/{
109           i2c@138B0000 {
110                      #address-cells = <1>;
111                      #size-cells = <0>;
112                      samsung,i2c-sda-delay = <100>;
113                      samsung,i2c-max-bus-freq = <20000>;
114                      pinctrl-0 =<&i2c5_bus>;
115                      pinctrl-names="default";
116                      status="okay";
117                      mpu6050@68{
118                                 compatible="invensense,mpu6050";
119                                 reg=<0x68>;
120                      };
121           };

/
--109-->即我們SoC上的i2c控制器的地址
--116-->這個一定要okay,其實是對"./arch/arm/boot/dts/exynos4.dtsi +387"處的status = "disabled"的重寫,相同的節點的不同屬性信息都會被合並,相同節點的相同的屬性會被重寫
--117-->設備子節點,/表示板子,它的子節點node1表示SoC上的某個控制器,控制器中的子節點node2表示掛接在這個控制器上的設備(們)。68即是設備地址。
--118-->這個屬性就是我們和驅動匹配的鑰匙,一個字符都不能錯
--119-->這個屬性是從設備的地址,我們可以通過查閱手冊"MPU-6050_DataSheet_V3_4"得到

寫了這個設備節點,內核就會為我們在內核中構造一個i2c_client對象並掛接到i2c總線對象的設備鏈表中以待匹配,這個設備類如下

//include/linux/i2c.h
217 struct i2c_client {                           
218         unsigned short flags;           /* div., see below              */
219         unsigned short addr;            /* chip address - NOTE: 7bit    */
220                                         /* addresses are stored in the  */
221                                         /* _LOWER_ 7 bits               */
222         char name[I2C_NAME_SIZE];
223         struct i2c_adapter *adapter;    /* the adapter we sit on        */
224         struct device dev;              /* the device structure         */
225         int irq;                        /* irq issued by device         */
226         struct list_head detected;
227 };

--219-->設備地址
--223-->表示這個client從屬的i2c主機對應的adapter對象,驅動方法中使用這個指針發送數據
--224-->表示這是一個device
--225-->中斷使用的中斷號
--226-->將所有i2c_client連在一起的節點

i2c_driver

和平台總線類似,i2c驅動對象使用i2c_driver結構來描述,所以,編寫一個i2c驅動的本質工作就是構造一個i2c_driver對象並將其注冊到內核。我們先來認識一下這個對象

//include/linux/i2c.h
161 struct i2c_driver {
162         unsigned int class;
167         int (*attach_adapter)(struct i2c_adapter *) __deprecated;
170         int (*probe)(struct i2c_client *, const struct i2c_device_id *);
171         int (*remove)(struct i2c_client *);
174         void (*shutdown)(struct i2c_client *);
175         int (*suspend)(struct i2c_client *, pm_message_t mesg);
176         int (*resume)(struct i2c_client *);
183         void (*alert)(struct i2c_client *, unsigned int data);
188         int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);
190         struct device_driver driver;
191         const struct i2c_device_id *id_table;
194         int (*detect)(struct i2c_client *, struct i2c_board_info *);
195         const unsigned short *address_list;
196         struct list_head clients;                             
197 };

struct i2c_driver
--170-->探測函數,匹配成功之后執行,會將匹配到的i2c_client對象傳入,完成申請資源,初始化,提供接口等工作。
--171-->移除函數,設備消失時會調用,驅動模塊被rmmod時也會先被調用,完成和probe相反的操作。
--174-->這三個方法都是電源管理相關的接口
--190-->表明這是一個設備的驅動類,和platform一樣,用於匹配設備樹的of_match_table域在這里
--191-->用於使用平台文件或模塊編寫設備信息時進行匹配使用,相當於platform_driver中的id_table。
--197-->用於將所有i2c_driver聯系到一起的節點

那么接下來就是填充對象了,我們這里使用的是設備樹匹配,所以of_match_table被填充如下。

struct of_device_id mpu6050_dt_match[] = {
	{.compatible = "invensense,mpu6050"},
	{},
};
struct i2c_device_id mpu6050_dev_match[] = {};

然后將這兩個成員填充到i2c_driver對象如下,這個階段我們可以在mpu6050_probe中只填寫prink來測試我們的驅動方法對象是否有問題。

struct i2c_driver mpu6050_driver = {
	.probe = mpu6050_probe,
	.remove = mpu6050_remove,
	.driver = {
		.owner = THIS_MODULE,
		.name = "mpu6050drv",
		.of_match_table = of_match_ptr(mpu6050_dt_match), 
	},
	.id_table = mpu6050_dev_match,
};

使用下述API注冊/注銷驅動對象,這個宏和module_platform_driver一樣是內核提供給我們一個用於快速實現注冊注銷接口的快捷方式,寫了這句以及模塊授權,我們就可以靜待各種信息被打印了

module_i2c_driver(mpu6050_driver);

i2c_msg

如果測試通過,我們就要研究如何找到adapter以及如何通過找到的adapter將數據發送出去。沒錯,我說的i2c_msg

 68 struct i2c_msg {                                                                                                   
 69         __u16 addr;     /* slave address                        */
 70         __u16 flags;
 71 #define I2C_M_TEN               0x0010  /* this is a ten bit chip address */
 72 #define I2C_M_RD                0x0001  /* read data, from slave to master */
 73 #define I2C_M_STOP              0x8000  /* if I2C_FUNC_PROTOCOL_MANGLING */
 74 #define I2C_M_NOSTART           0x4000  /* if I2C_FUNC_NOSTART */
 75 #define I2C_M_REV_DIR_ADDR      0x2000  /* if I2C_FUNC_PROTOCOL_MANGLING */
 76 #define I2C_M_IGNORE_NAK        0x1000  /* if I2C_FUNC_PROTOCOL_MANGLING */
 77 #define I2C_M_NO_RD_ACK         0x0800  /* if I2C_FUNC_PROTOCOL_MANGLING */
 78 #define I2C_M_RECV_LEN          0x0400  /* length will be first received byte */
 79         __u16 len;              /* msg length                           */
 80         __u8 *buf;              /* pointer to msg data                  */
 81 };  

struct i2c_msg
--69-->從機地址
--70-->操作標志,I2C_M_RD為讀(0),寫為1
--79-->有效數據長度
--80-->裝載有效數據的頭指針

我們知道,i2c總線上傳入數據是以字節為單位的,而我們的通信類別分為兩種:讀and寫,對於寫,通常按照下面的時序

Mater S I2CAddr+WriteBit InternalRegisterAddr DATA DATA P
Slave ACK ACK ACK ACK

對於讀,通常是按照下面的時序

Mater S I2CAddr+WriteBit InternalRegisterAddr S I2CAddr+ReadBit ACK NACK P
Slave ACK ACK ACK DATA DATA

i2c子系統為了實現這種通信方法,為我們封裝了i2c_msg結構,對於每一個START信號,都對應一個i2c_msg對象實際操作中我們會將所有的請求封裝成一個struct i2c_msg[],一次性將所有的請求通過i2c_transfer()發送給匹配到的client的從屬的adapter,由adapter根據相應的algo域以及master_xfer域通過主機驅動來將這些請求發送給硬件上的設備

實例

這是一個通過i2c總線來訪問mpu6050的驅動

//mpu6050_common.h
#define MPU6050_MAGIC 'K'

union mpu6050_data
{
	struct {
		short x;
		short y;
		short z;
	}accel;
	struct {
		short x;
		short y;
		short z;
	}gyro;
	unsigned short temp;
};

#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)
//mpu6050_drv.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地址寄存器
#define W_FLG 			0
#define R_FLG 			1
//mpu6050.c
struct mpu6050_pri {
	struct cdev dev;
	struct i2c_client *client;
};
struct mpu6050_pri dev;
static void mpu6050_write_byte(struct i2c_client *client,const unsigned char reg,const unsigned char val)
{ 
	char txbuf[2] = {reg,val};
	struct i2c_msg msg[2] = {
		[0] = {
			.addr = client->addr,
			.flags= W_FLG,
			.len = sizeof(txbuf),
			.buf = txbuf,
		},
	};
	i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg));
}
static char mpu6050_read_byte(struct i2c_client *client,const unsigned char reg)
{
	char txbuf[1] = {reg};
	char rxbuf[1] = {0};
	struct i2c_msg msg[2] = {
		[0] = {
			.addr = client->addr,
			.flags = W_FLG,
			.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];
}
static int dev_open(struct inode *ip, struct file *fp)
{
	return 0;
}
static int dev_release(struct inode *ip, struct file *fp)
{
	return 0;
}
static long dev_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(dev.client,ACCEL_XOUT_L);
		data.accel.x|= mpu6050_read_byte(dev.client,ACCEL_XOUT_H)<<8;
		data.accel.y = mpu6050_read_byte(dev.client,ACCEL_YOUT_L);
		data.accel.y|= mpu6050_read_byte(dev.client,ACCEL_YOUT_H)<<8;
		data.accel.z = mpu6050_read_byte(dev.client,ACCEL_ZOUT_L);
		data.accel.z|= mpu6050_read_byte(dev.client,ACCEL_ZOUT_H)<<8;
		break;
	case GET_GYRO:
		data.gyro.x = mpu6050_read_byte(dev.client,GYRO_XOUT_L);
		data.gyro.x|= mpu6050_read_byte(dev.client,GYRO_XOUT_H)<<8;
		data.gyro.y = mpu6050_read_byte(dev.client,GYRO_YOUT_L);
		data.gyro.y|= mpu6050_read_byte(dev.client,GYRO_YOUT_H)<<8;
		data.gyro.z = mpu6050_read_byte(dev.client,GYRO_ZOUT_L);
		data.gyro.z|= mpu6050_read_byte(dev.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(dev.client,TEMP_OUT_L);
		data.temp|= mpu6050_read_byte(dev.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);
}

struct file_operations fops = {
	.open = dev_open,
	.release = dev_release,
	.unlocked_ioctl = dev_ioctl, 
};

#define DEV_CNT 1
#define DEV_MI 0
#define DEV_MAME "mpu6050"

struct class *cls;
dev_t dev_no ;

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_probe(struct i2c_client * client, const struct i2c_device_id * id)
{
	dev.client = client;
	printk(KERN_INFO "xj_match ok\n");
	cdev_init(&dev.dev,&fops);
	
	alloc_chrdev_region(&dev_no,DEV_MI,DEV_CNT,DEV_MAME);
	
	cdev_add(&dev.dev,dev_no,DEV_CNT);
	
	mpu6050_init(client);

	/*自動創建設備文件*/
	cls = class_create(THIS_MODULE,DEV_MAME);
	device_create(cls,NULL,dev_no,NULL,"%s%d",DEV_MAME,DEV_MI);
	
	printk(KERN_INFO "probe\n");
	
	return 0;
}

static int mpu6050_remove(struct i2c_client * client)
{
	device_destroy(cls,dev_no);
	class_destroy(cls);
	unregister_chrdev_region(dev_no,DEV_CNT);
	return 0;
}

struct of_device_id mpu6050_dt_match[] = {
	{.compatible = "invensense,mpu6050"},
	{},
};

struct i2c_device_id mpu6050_dev_match[] = {};
struct i2c_driver mpu6050_driver = {
	.probe = mpu6050_probe,
	.remove = mpu6050_remove,
	.driver = {
		.owner = THIS_MODULE,
		.name = "mpu6050drv",
		.of_match_table = of_match_ptr(mpu6050_dt_match), 
	},
	.id_table = mpu6050_dev_match,
};
module_i2c_driver(mpu6050_driver);
MODULE_LICENSE("GPL");

通過上面的驅動, 我們可以在應用層操作設備文件從mpu6050寄存器中讀取原始數據, 應用層如下

int main(int argc, char * const argv[])
{
	int fd = open(argv[1],O_RDWR);
	if(-1== fd){
		perror("open");
		return -1;
	}
	union mpu6050_data data = {{0}};
	while(1){
		ioctl(fd,GET_ACCEL,&data);
		printf("acc:x %d, y:%d, z:%d\n",data.accel.x,data.accel.y,data.accel.z);
		ioctl(fd,GET_GYRO,&data);
		printf("gyro:x %d, y:%d, z:%d\n",data.gyro.x,data.gyro.y,data.gyro.z);
		ioctl(fd,GET_TEMP,&data);
		printf("temp: %d\n",data.temp);
		sleep(1);
	}
	return 0;
}

最終可以獲取傳感器的原始數據如下


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM