ZYNQ学习笔记-IIC驱动的开发

linux中有着一个IIC子系统,为IIC设备驱动提供了抽象IIC接口。IIC与硬件相关的底层驱动由芯片厂商进行适配,普通用户只需要关注上层的抽象层即可完成驱动开发。

Linux中IIC设备的重要概念

关于IIC子系统,有几个重要的概念需要掌握。首先看一下IIC子系统的框图:
image-20240416195931949

  • I2C核心:提供抽象层代码的核心。
  • I2C总线驱动:提供I2C通信能力。
  • I2C设备驱动:提供设备的注册和设备驱动的注册。

对于用户来说,需要弄明白以下几个数据结构(均定义在include/linux/i2c.h 中):

  • i2c_adapter结构体:对应I2C外设的抽象层,每个代表一个外设。
  • i2c_algorithm结构体:对应I2C的传输方法,包含在i2c_adapter中,传输具体数据时调用的函数方法。

上面两者的具体实现方法由芯片公司提供的I2C总线驱动实现,普通用户无需知道细节。对普通用户来说,需要重点关注以下几个数据结构;

  • i2c_client结构体:
1
2
3
4
5
6
7
8
9
10
11
12
13
struct i2c_client {
unsigned short flags; // 标志
unsigned short addr; // 芯片地址, 7 位,存在低 7 位

char name[I2C_NAME_SIZE]; // 设备名字
struct i2c_adapter *adapter; // 对应的 I2C 适配器
struct device dev; // 内置 device 结构体
int irq; // 中断
struct list_head detected;
#if IS_ENABLED(CONFIG_I2C_SLAVE)
i2c_slave_cb_t slave_cb; /* callback for slave mode */
#endif
}

一个I2C总线下的从设备对应一个i2c_client结构体变量。当驱动每匹配上一个设备时(通过设备树的compatible与of表匹配或者无设备树的i2c_device_id的name与I2C设备名字是否相同),系统就会分配一个client。

  • i2c_driver结构体:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
struct i2c_driver {
unsigned int class;

/* Notifies the driver that a new bus has appeared. You should avoid
* using this, it will be removed in a near future.
*/
int (*attach_adapter)(struct i2c_adapter *) __deprecated;

/* Standard driver model interfaces */
int (*probe)(struct i2c_client *, const struct i2c_device_id *);
int (*remove)(struct i2c_client *);

/* New driver model interface to aid the seamless removal of the
* current probe()'s, more commonly unused than used second parameter.
*/
int (*probe_new)(struct i2c_client *);

/* driver model interfaces that don't relate to enumeration */
void (*shutdown)(struct i2c_client *);

/* Alert callback, for example for the SMBus alert protocol.
* The format and meaning of the data value depends on the protocol.
* For the SMBus alert protocol, there is a single bit of data passed
* as the alert response's low bit ("event flag").
* For the SMBus Host Notify protocol, the data corresponds to the
* 16-bit payload data reported by the slave device acting as master.
*/
void (*alert)(struct i2c_client *, enum i2c_alert_protocol protocol,
unsigned int data);

/* a ioctl like command that can be used to perform specific functions
* with the device.
*/
int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);

struct device_driver driver;
const struct i2c_device_id *id_table;

/* Device detection callback for automatic device creation */
int (*detect)(struct i2c_client *, struct i2c_board_info *);
const unsigned short *address_list;
struct list_head clients;

bool disable_i2c_core_irq_mapping;
};

这个结构体中,需要注意几个成员:

1
2
3
/* Standard driver model interfaces */
int (*probe)(struct i2c_client *, const struct i2c_device_id *);
int (*remove)(struct i2c_client *);

probe函数在设备匹配成功后会执行,每次匹配得到一个i2c_client。

remove函数在注销设备时使用,要注销的设备通过i2c_client传入。

1
2
struct device_driver driver;
const struct i2c_device_id *id_table;

两个成员设计IIC设备的匹配。传统匹配使用i2c_device_id表进行,使用设备树则用device_driver中的of_match_table 成员变量匹配compatible。

IIC驱动的单位是driver,IIC驱动加载时需要调用函数:

1
int i2c_register_driver(struct module *owner,struct i2c_driver *driver)

owner一般为THIS_MODULE

与该函数对应有个宏帮助传递owner参数:

1
2
#define i2c_add_driver(driver) \
i2c_register_driver(THIS_MODULE, driver)

卸载驱动时需要调用注销:

1
void i2c_del_driver(struct i2c_driver *driver)

IIC驱动的单位是driver,这句话理解为,一个driver对应多个client,即一个驱动可以驱动多个同类型设备(基于匹配表)。每匹配到一个client,都需要在probe把字符设备创建过程跑一遍,在remove把注销过程跑一遍

一般的驱动注册结构

对于一般用户,需要注册i2c_driver,一般的注册结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/* i2c 驱动的 probe 函数 */
static int xxx_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
/* 函数具体程序 */
return 0;
}

/* i2c 驱动的 remove 函数 */
static int xxx_remove(struct i2c_client *client)
{
/* 函数具体程序 */
return 0;
}

/* 传统匹配方式 ID 列表 */
static const struct i2c_device_id xxx_id[] = {
{"xxx", 0},
{}
};

/* 设备树匹配列表 */
static const struct of_device_id xxx_of_match[] = {
{ .compatible = "xxx" },
{ /* Sentinel */ }
};

/* i2c 驱动结构体 */
static struct i2c_driver xxx_driver = {
.probe = xxx_probe,
.remove = xxx_remove,
.driver = {
.owner = THIS_MODULE,
.name = "xxx",
.of_match_table = xxx_of_match,
},
.id_table = xxx_id,
};

/* 驱动入口函数 */
static int __init xxx_init(void)
{
int ret = 0;

ret = i2c_add_driver(&xxx_driver);
return ret;
}

/* 驱动出口函数 */
static void __exit xxx_exit(void)
{
i2c_del_driver(&xxx_driver);
}

module_init(xxx_init);
module_exit(xxx_exit);

同时Linux也提供了一个宏来简化驱动注册:

1
module_i2c_driver(xxx_driver);

宏展开后是:

1
2
3
4
5
6
7
8
9
10
static int __init xxx_driver_init(void)
{
return i2c_add_driver(&xxx_driver);
}
module_init(xxx_driver_init);
static void __exit xxx_driver_exit(void)
{
i2c_del_driver(&xxx_driver);
}
module_exit(xxx_driver_exit);

可见方便了使用。

设备匹配方式

  • 无设备树时匹配方式

老式匹配方式,这里不再多说。需要使用一个i2c_board_info 结构体来描述一个设备。

  • 设备树添加方式

示例:

1
2
3
4
5
6
7
8
&i2c0 {
clock-frequency = <100000>;

rtc@51 {
compatible = "nxp,pcf8563";
reg = <0x51>;
};
};

对以上,驱动中的of_match表如下:

1
2
3
4
5
/* 匹配列表 */
static const struct of_device_id pcf8563_of_match[] = {
{ .compatible = "zynq-pcf8563" },
{ /* Sentinel */ }
};

实际数据传输函数

匹配到设备后需要利用IIC传输实际的数据,这里就涉及到一个函数:

1
2
3
int i2c_transfer(struct i2c_adapter *adap,
struct i2c_msg *msgs,
int num)

该函数最终会调用I2C 适配器中 i2c_algorithm 里面的 master_xfer 函数 ,即IIC总线驱动中硬件相关传输函数。

该函数参数如下:

adap: 所使用的 I2C 适配器, i2c_client.i2c_adapter。
msgs: I2C 要发送的一个或多个消息。
num: 消息数量,也就是 msgs 的数量。
返回值: 负值,失败,其他非负值,发送的 msgs 数量。

i2c_msg定义在include/uapi/linux/i2c.h :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct i2c_msg {
__u16 addr; /* 从机地址 */
__u16 flags; /* 标志 */
#define I2C_M_RD 0x0001 /* read data, from slave to master */
/* I2C_M_RD is guaranteed to be 0x0001! */
#define I2C_M_TEN 0x0010 /* this is a ten bit chip address */
#define I2C_M_RECV_LEN 0x0400 /* length will be first received byte */
#define I2C_M_NO_RD_ACK 0x0800 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_IGNORE_NAK 0x1000 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_REV_DIR_ADDR 0x2000 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NOSTART 0x4000 /* if I2C_FUNC_NOSTART */
#define I2C_M_STOP 0x8000 /* if I2C_FUNC_PROTOCOL_MANGLING */
__u16 len; /* 消息(数据)长度 */
__u8 *buf; /* 消息(数据)地址 */
};

传输前一般都会先构建好msg。

使用该函数读取I2C设备寄存器的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
 /* 设备结构体 */
struct xxx_dev {
......
void *private_data; /* 私有数据,一般会设置为 i2c_client */
};
/**
* @description : 读取 I2C 设备多个寄存器数据
* @param – dev : I2C 设备
* @param – reg : 要读取的寄存器首地址
* @param – val : 读取到的数据
* @param – len : 要读取的数据长度
* @return : 操作结果
*/
static int xxx_read_regs(struct xxx_dev *dev, u8 reg, void *val, int len)
{
int ret;
struct i2c_msg msg[2];
struct i2c_client *client = (struct i2c_client *)dev->private_data;

/* msg[0],第一条写消息,发送要读取的寄存器首地址 */
msg[0].addr = client->addr; /* I2C 器件地址 */
msg[0].flags = 0; /* 标记为发送数据 */
msg[0].buf = &reg; /* 读取的首地址 */
msg[0].len = 1; /* reg 长度 */

/* msg[1],第二条读消息,读取寄存器数据 */
msg[1].addr = client->addr; /* I2C 器件地址 */
msg[1].flags = I2C_M_RD; /* 标记为读取数据 */
msg[1].buf = val; /* 读取数据缓冲区 */
msg[1].len = len; /* 要读取的数据长度 */

ret = i2c_transfer(client->adapter, msg, 2);
if(ret == 2) {
ret = 0;
} else {
ret = -EREMOTEIO;
}
return ret;
}

/*
* @description : 向 I2C 设备多个寄存器写入数据
* @param – dev : 要写入的设备结构体
* @param – reg : 要写入的寄存器首地址
* @param – val : 要写入的数据缓冲区
* @param – len : 要写入的数据长度
* @return : 操作结果
*/
static s32 xxx_write_regs(struct xxx_dev *dev, u8 reg, u8 *buf, u8 len)
{
u8 b[256];
struct i2c_msg msg;
struct i2c_client *client = (struct i2c_client *)dev->private_data;

b[0] = reg; /* 寄存器首地址 */
memcpy(&b[1],buf,len); /* 将要发送的数据拷贝到数组 b 里面 */

msg.addr = client->addr; /* I2C 器件地址 */
msg.flags = 0; /* 标记为写数据 */

msg.buf = b; /* 要发送的数据缓冲区 */
msg.len = len + 1; /* 要发送的数据长度 */

return i2c_transfer(client->adapter, &msg, 1);
}

除此之外还有两个API函数用来收发数据,最后都会调用i2c_transfer。

1
2
3
int i2c_master_send(const struct i2c_client *client,
const char *buf,
int count)
1
2
3
int i2c_master_recv(const struct i2c_client *client,
char *buf,
int count)

count: 要接收的数据字节数,要小于 64KB,因为 i2c_msg 的 len 成员变量是一个 u16(无
符号 16 位)类型的数据。

实战-MPU6050驱动的编写

首先,在设备树中添加MPU6050节点:

1
2
3
4
5
6
7
8
&i2c0{
clock-frequency=<100000>;

mpu6050@68{
compatible = "atk,mpu6050";
reg=<0x68>;
};
};

驱动中编写如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
#include "mpu6050.h"

#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/i2c.h>
#include <linux/uaccess.h>
#include <linux/delay.h>
#include <linux/fs.h>

#define MPU6050_CNT 1
#define MPU6050_NAME "mpu6050"

struct mpu6050_dev {
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */

int major; /* 主设备号 */
void *private_data; /* 私有数据用于存储client*/
signed int gyro_x_adc; /* 陀螺仪X轴原始值 */
signed int gyro_y_adc; /* 陀螺仪Y轴原始值 */
signed int gyro_z_adc; /* 陀螺仪Z轴原始值 */
signed int accel_x_adc; /* 加速度计X轴原始值 */
signed int accel_y_adc; /* 加速度计Y轴原始值 */
signed int accel_z_adc; /* 加速度计Z轴原始值 */
signed int temp_adc; /* 温度原始值 */
};

static struct mpu6050_dev mpu6050dev;

static int mpu6050_release(struct inode *inode, struct file *filp)
{
return 0;
}

static int mpu6050_open(struct inode *inode, struct file *filp)
{
filp->private_data = &mpu6050dev; /* 设置私有数据 */
return 0;
}

static s32 mpu6050_write_regs(struct mpu6050_dev *dev, u8 reg, u8 *buf, u8 len)
{
u8 b[256];
struct i2c_msg msg;
struct i2c_client *client = (struct i2c_client *)dev->private_data;

b[0] = reg; /* 寄存器首地址 */
memcpy(&b[1],buf,len); /* 将要写入的数据拷贝到数组b里面 */

msg.addr = client->addr; /* mpu6050地址 */
msg.flags = 0; /* 标记为写数据 */

msg.buf = b; /* 要写入的数据缓冲区 */
msg.len = len + 1; /* 要写入的数据长度 */

return i2c_transfer(client->adapter, &msg, 1);
}

static void mpu6050_write_onereg(struct mpu6050_dev *dev, u8 reg, u8 data)
{
u8 buf = 0;
buf = data;
mpu6050_write_regs(dev, reg, &buf, 1);
}

static int mpu6050_read_regs(struct mpu6050_dev *dev, u8 reg, void *val, int len)
{
int ret;
struct i2c_msg msg[2];
struct i2c_client *client = (struct i2c_client *)dev->private_data;

/* msg[0]为发送要读取的首地址 */
msg[0].addr = client->addr; /* */
msg[0].flags = 0; /* 标记为发送数据 */
msg[0].buf = &reg; /* 读取的首地址 */
msg[0].len = 1; /* reg长度*/

/* msg[1]读取数据 */
msg[1].addr = client->addr; /* */
msg[1].flags = I2C_M_RD; /* 标记为读取数据*/
msg[1].buf = val; /* 读取数据缓冲区 */
msg[1].len = len; /* 要读取的数据长度*/

ret = i2c_transfer(client->adapter, msg, 2);
if(ret == 2) {
ret = 0;
} else {
printk("i2c rd failed=%d reg=%06x len=%d\n",ret, reg, len);
ret = -EREMOTEIO;
}
return ret;
}

static unsigned char mpu6050_read_onereg(struct mpu6050_dev *dev, u8 reg)
{
u8 data = 0;

mpu6050_read_regs(dev, reg, &data, 1);
return data;

}

void mpu6050_readdata(struct mpu6050_dev *dev)
{
unsigned char data[14];
mpu6050_read_regs(dev, MPU6050_RA_ACCEL_XOUT_H, data, 14);

dev->accel_x_adc = (signed short)((data[0] << 8) | data[1]);
dev->accel_y_adc = (signed short)((data[2] << 8) | data[3]);
dev->accel_z_adc = (signed short)((data[4] << 8) | data[5]);
dev->temp_adc = (signed short)((data[6] << 8) | data[7]);
dev->gyro_x_adc = (signed short)((data[8] << 8) | data[9]);
dev->gyro_y_adc = (signed short)((data[10] << 8) | data[11]);
dev->gyro_z_adc = (signed short)((data[12] << 8) | data[13]);
}

static ssize_t mpu6050_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{
signed int data[7];
long err = 0;
struct mpu6050_dev *dev = (struct mpu6050_dev *)filp->private_data;

mpu6050_readdata(dev);
data[0] = dev->gyro_x_adc;
data[1] = dev->gyro_y_adc;
data[2] = dev->gyro_z_adc;
data[3] = dev->accel_x_adc;
data[4] = dev->accel_y_adc;
data[5] = dev->accel_z_adc;
data[6] = dev->temp_adc;

//printk("mpu6050 Read Data:x_adc:%d\r\n",data[0]);
err = copy_to_user(buf, data, sizeof(data));
return 0;
}

/* mpu6050操作函数 */
static const struct file_operations mpu6050_ops = {
.owner = THIS_MODULE,
.open = mpu6050_open,
.read = mpu6050_read,
.release = mpu6050_release,
};

/**
* mpu6050初始化函数
*/
void mpu6050_reginit(void)
{
u8 value = 0;

mpu6050_write_onereg(&mpu6050dev, MPU6050_RA_PWR_MGMT_1, 0x80);//复位设备
mdelay(50);
mpu6050_write_onereg(&mpu6050dev, MPU6050_RA_PWR_MGMT_1, 0x01);//退出休眠模式,选择X轴PLL作为时钟
mdelay(50);

value = mpu6050_read_onereg(&mpu6050dev, MPU6050_RA_WHO_AM_I);
printk("mpu6050 ID = %#X\r\n", value);

mpu6050_write_onereg(&mpu6050dev, MPU6050_RA_SMPLRT_DIV, 0x00); /* 输出速率是内部采样率 */
//设置量程为最小,提高精度
mpu6050_write_onereg(&mpu6050dev, MPU6050_RA_GYRO_CONFIG, 0x00); /* 陀螺仪±250dps量程 */
mpu6050_write_onereg(&mpu6050dev, MPU6050_RA_ACCEL_CONFIG, 0x00); /* 加速度计±2G量程 */
// mpu6050_write_onereg(&mpu6050dev, MPU6050_RA_GYRO_CONFIG, 0x18); /* 陀螺仪±2000dps量程 */
// mpu6050_write_onereg(&mpu6050dev, MPU6050_RA_ACCEL_CONFIG, 0x18); /* 加速度计±16G量程 */
mpu6050_write_onereg(&mpu6050dev, MPU6050_RA_CONFIG, 0x04); /* 陀螺仪低通滤波BW=20Hz */
mpu6050_write_onereg(&mpu6050dev, MPU6050_RA_PWR_MGMT_2, 0x00); /* 打开加速度计和陀螺仪所有轴 */
mpu6050_write_onereg(&mpu6050dev, MPU6050_RA_FIFO_EN, 0x00); /* 关闭FIFO */
}

static int mpu6050_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
int ret = 0;

/* 1、构建设备号 */
if (mpu6050dev.major) {
mpu6050dev.devid = MKDEV(mpu6050dev.major, 0);
ret=register_chrdev_region(mpu6050dev.devid, MPU6050_CNT, MPU6050_NAME);
} else {
ret=alloc_chrdev_region(&mpu6050dev.devid, 0, MPU6050_CNT, MPU6050_NAME);
mpu6050dev.major = MAJOR(mpu6050dev.devid);
}
if(ret) return ret;

/* 2、注册cdev设备 */
cdev_init(&mpu6050dev.cdev, &mpu6050_ops);
ret=cdev_add(&mpu6050dev.cdev, mpu6050dev.devid, MPU6050_CNT);
if(ret) goto out1;

/* 3、创建类 */
mpu6050dev.class = class_create(THIS_MODULE, MPU6050_NAME);
if (IS_ERR(mpu6050dev.class)) {
ret=PTR_ERR(mpu6050dev.class);
goto out2;
}

/* 4、创建设备 */
mpu6050dev.device = device_create(mpu6050dev.class, &client->dev, mpu6050dev.devid, NULL, MPU6050_NAME);
if (IS_ERR(mpu6050dev.device)) {
ret = PTR_ERR(mpu6050dev.device);
goto out3;
}

mpu6050dev.private_data = client; /* 设置私有数据 */

mpu6050_reginit();
return 0;
//分级进行反注册设备
out3:
class_destroy(mpu6050dev.class);
out2:
cdev_del(&mpu6050dev.cdev);
out1:
unregister_chrdev_region(mpu6050dev.devid,MPU6050_CNT);

return ret;
}

static int mpu6050_remove(struct i2c_client *client)
{
/* 删除设备 */
cdev_del(&mpu6050dev.cdev);
unregister_chrdev_region(mpu6050dev.devid, MPU6050_CNT);

/* 注销掉类和设备 */
device_destroy(mpu6050dev.class, mpu6050dev.devid);
class_destroy(mpu6050dev.class);
return 0;
}

/* 传统匹配方式ID列表 */
static const struct i2c_device_id mpu6050_id[] = {
{"atk,mpu6050", 0},
{}
};

/* 设备树匹配列表 */
static const struct of_device_id mpu6050_of_match[] = {
{ .compatible = "atk,mpu6050" },
{ /* Sentinel */ }
};

static struct i2c_driver mpu6050_driver = {
.probe = mpu6050_probe,//匹配到设备时调用
.remove = mpu6050_remove,
.driver = {
.owner = THIS_MODULE,
.name = "mpu6050",
.of_match_table = mpu6050_of_match,//设备匹配表
},
.id_table = mpu6050_id,
};


module_i2c_driver(mpu6050_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("LO");

编写测试APP:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#include "stdio.h"
#include "unistd.h"
#include "fcntl.h"


int main(int argc, char *argv[])
{
int i=10;
int fd;
signed int buffer[7];
signed int gyro_x_adc, gyro_y_adc, gyro_z_adc;
signed int accel_x_adc, accel_y_adc, accel_z_adc;
signed int temp_adc;

float gyro_x_act, gyro_y_act, gyro_z_act;
float accel_x_act, accel_y_act, accel_z_act;
float temp_act;

int ret = 0;
char* filename = "/dev/mpu6050";
fd = open(filename, O_RDWR);
if(fd < 0) {
printf("can't open file %s\r\n", filename);
return -1;
}
printf("Start Read.\r\n");
while (i--) {
ret = read(fd, buffer, sizeof(buffer));
if(ret == 0) {
gyro_x_adc = buffer[0];
gyro_y_adc = buffer[1];
gyro_z_adc = buffer[2];
accel_x_adc = buffer[3];
accel_y_adc = buffer[4];
accel_z_adc = buffer[5];
temp_adc = buffer[6];

gyro_x_act = (float)(gyro_x_adc) / 16.4;
gyro_y_act = (float)(gyro_y_adc) / 16.4;
gyro_z_act = (float)(gyro_z_adc) / 16.4;
accel_x_act = (float)(accel_x_adc) / 2048;
accel_y_act = (float)(accel_y_adc) / 2048;
accel_z_act = (float)(accel_z_adc) / 2048;
temp_act = ((float)(temp_adc)/ 340 ) + 36.53;

printf("\r\nADC value:\r\n");
printf("gx = %d, gy = %d, gz = %d\r\n", gyro_x_adc, gyro_y_adc, gyro_z_adc);
printf("ax = %d, ay = %d, az = %d\r\n", accel_x_adc, accel_y_adc, accel_z_adc);
printf("temp = %d\r\n", temp_adc);
printf("TRUE value:");
printf("act gx = %.2f°/S, act gy = %.2f°/S, act gz = %.2f°/S\r\n", gyro_x_act, gyro_y_act, gyro_z_act);
printf("act ax = %.2fg, act ay = %.2fg, act az = %.2fg\r\n", accel_x_act, accel_y_act, accel_z_act);
printf("act temp = %.2f°C\r\n", temp_act);
}
usleep(1000000);
}
close(fd);
return 0;
}