0%

Linux驱动学习日记(25) Linux 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
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
/*
* @Author: 胡城玮
* @FilePath: mpu6050.c
* @Date: 2026-03-16
* @Description:
* @Version: 0.1
*/

#include "mpu6050.h"
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/i2c.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define MPU6050_CNT 1
#define MPU6050_NAME "mpu6050"

struct mpu6050_dev{
dev_t devid;
struct cdev cdev;
struct class *class;
struct device *device;
struct device_node *nd;
int major;
void *private_data;
s16 acc[3],gyr[3]; // 加速度、角速度
};

static struct mpu6050_dev mpu6050dev;

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

msg[0].addr = client->addr;
msg[0].flags = 0;
msg[0].buf = &reg;
msg[0].len = 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\r\n",ret,reg,len);
return -EREMOTEIO;
}
return ret;
}

static int mpu6050_write_regs(struct mpu6050_dev *dev, u8 reg, u8* buf, int 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);

msg.addr = client->addr;
msg.buf = b;
msg.flags = 0;
msg.len = len+1;

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

static u8 mpu6050_read_reg(struct mpu6050_dev *dev, u8 reg)
{
u8 data = 0;

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

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

static void mpu6050_read_data(struct mpu6050_dev *dev)
{
u8 buf[6];
int ret = 0;

mpu6050_read_regs(dev, MPU6050_GYRO_XOUT_H, buf, 6);
dev->gyr[0] = (buf[0] << 8)|buf[1];
dev->gyr[1] = (buf[2] << 8)|buf[3];
dev->gyr[2] = (buf[4] << 8)|buf[5];

mpu6050_read_regs(dev, MPU6050_ACCEL_XOUT_H, buf, 6);
dev->acc[0] = (buf[0] << 8)|buf[1];
dev->acc[1] = (buf[2] << 8)|buf[3];
dev->acc[2] = (buf[4] << 8)|buf[5];

s16 data[6];
data[0] = dev->acc[0];
data[1] = dev->acc[1];
data[2] = dev->acc[2];
data[3] = dev->gyr[0];
data[4] = dev->gyr[1];
data[5] = dev->gyr[2];

ret = copy_to_user(buf, data, sizeof(data));
}

static int mpu6050_open(struct inode *inode,struct file *filp)
{
filp->private_data = &mpu6050dev;
return 0;
}

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

static ssize_t mpu6050_read (struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{
struct mpu6050_dev *dev = (struct mpu6050_dev *)filp->private_data;

mpu6050_read_data(dev);
return 0;
}

static const struct file_operations mpu6050_fops = {
.owner = THIS_MODULE,
.open = mpu6050_open,
.release = mpu6050_release,
.read = mpu6050_read,
};

static int mpu6050_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
int ret = 0;
u8 buf;
printk("matched!\n");
mpu6050dev.device = device_create(mpu6050dev.class, NULL, mpu6050dev.devid, NULL, MPU6050_NAME);
if(IS_ERR(mpu6050dev.device))
{
return PTR_ERR(mpu6050dev.device);
}

mpu6050dev.private_data = client;

msleep(500);

mpu6050_write_reg(&mpu6050dev, MPU6050_PWR_MGMT_1, 0x00); //解除休眠状态
mpu6050_write_reg(&mpu6050dev, MPU6050_SMPLRT_DIV , 0x07); //陀螺仪采样率
mpu6050_write_reg(&mpu6050dev, MPU6050_CONFIG , 0x06);
mpu6050_write_reg(&mpu6050dev, MPU6050_ACCEL_CONFIG , 0x00); //配置加速度传感器工作在2G模式
mpu6050_write_reg(&mpu6050dev, MPU6050_GYRO_CONFIG, 0x18); //陀螺仪自检及测量范围,典型值:0x18(不自检,2000deg/s)

msleep(200);

buf = mpu6050_read_reg(&mpu6050dev, MPU6050_WHO_AM_I);

if (buf != 0x68) {
ret = -ENODEV;
goto err_dev;
}

printk("MPU6050 Init SUCCESS!\n");
return 0;

err_dev:
printk("MPU6050 Init failed!\n");
device_destroy(mpu6050dev.class, mpu6050dev.devid);
return ret;
}

static void mpu6050_remove(struct i2c_client *client)
{
device_destroy(mpu6050dev.class,mpu6050dev.devid);
}

static const struct of_device_id mpu6050_of_match[] = {
{.compatible = "hcw,mpu6050"},
{}
};

static struct i2c_driver mpu6050_drv = {
.probe = mpu6050_probe,
.remove = mpu6050_remove,
.driver ={
.name = "mpu6050_drv",
.of_match_table = mpu6050_of_match,
.owner = THIS_MODULE,
},
};

static int __init mpu6050_init(void)
{
u8 ret = 0;
if(mpu6050dev.major)
{
mpu6050dev.devid = MKDEV(mpu6050dev.major, 0);
register_chrdev_region(mpu6050dev.devid, MPU6050_CNT, MPU6050_NAME);
}
else
{
alloc_chrdev_region(&mpu6050dev.devid,0, MPU6050_CNT,MPU6050_NAME);
mpu6050dev.major = MAJOR(mpu6050dev.devid);
}

cdev_init(&mpu6050dev.cdev, &mpu6050_fops);
cdev_add(&mpu6050dev.cdev, mpu6050dev.devid, MPU6050_CNT);

mpu6050dev.class = class_create(THIS_MODULE, MPU6050_NAME);
if(IS_ERR(mpu6050dev.class))
{
return PTR_ERR(mpu6050dev.class);
}
ret = i2c_add_driver(&mpu6050_drv);
if (ret)
goto destroy_class;

return 0;

destroy_class:
class_destroy(mpu6050dev.class);
cdev_del(&mpu6050dev.cdev);
unregister_chrdev_region(mpu6050dev.devid, MPU6050_CNT);

return ret;
}

static void __exit mpu6050_exit(void)
{
i2c_del_driver(&mpu6050_drv);
class_destroy(mpu6050dev.class);
cdev_del(&mpu6050dev.cdev);
unregister_chrdev_region(mpu6050dev.devid, MPU6050_CNT);
}

module_init(mpu6050_init);
module_exit(mpu6050_exit);
MODULE_AUTHOR("hcw");
MODULE_LICENSE("GPL");

  我们将代码喂给 chatgpt,让他给我们一些修改建议
  1、read() 根本没有把数据正确返回给用户层
  我原来的 copy_to_user 放在了 read_data 函数里,正确的做法应该放在 fops 中的 read 函数里,如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static ssize_t mpu6050_read (struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{
int ret = 0;
struct mpu6050_dev *dev = (struct mpu6050_dev *)filp->private_data;

mpu6050_read_data(dev);

s16 data[6];
data[0] = dev->acc[0];
data[1] = dev->acc[1];
data[2] = dev->acc[2];
data[3] = dev->gyr[0];
data[4] = dev->gyr[1];
data[5] = dev->gyr[2];

ret = copy_to_user(buf, data, sizeof(data));
return 0;
}

  2、I2C 读写函数要做统一返回值检查
  现在的代码中 mpu6050_read_regs() 对 i2c_transfer() 做了检查,但是 mpu6050_write_regs() 直接把 i2c_transfer() 返回值原样交出去,没有统一转成 0 / 负错误码。mpu6050_read_reg() 和 mpu6050_write_reg() 也基本没处理失败情况。
  我们应该改成这样的风格:
  传输成功返回 0,传输失败返回负错误码,单字节读写函数改成 int mpu6050_read_reg(…, u8 *val),不要直接返回 u8。

  3、优化 probe 初始化流程
  在 probe 初始化中,我们应该先保存 client、做芯片初始化、读 WHO_AM_I 确认正常,最后在 device_create。

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
static int mpu6050_probe(struct i2c_client *client, 
const struct i2c_device_id *id)
{
int ret = 0;
u8 buf;
printk("matched!\n");

mpu6050dev.private_data = client;

int ret;

msleep(500);

ret = mpu6050_write_reg(&mpu6050dev, MPU6050_PWR_MGMT_1, 0x00);
if (ret) {
printk("write MPU6050_PWR_MGMT_1 failed, ret=%d\n", ret);
return ret;
}

ret = mpu6050_write_reg(&mpu6050dev, MPU6050_SMPLRT_DIV, 0x07);
if (ret) {
printk("write MPU6050_SMPLRT_DIV failed, ret=%d\n", ret);
return ret;
}

ret = mpu6050_write_reg(&mpu6050dev, MPU6050_CONFIG, 0x06);
if (ret) {
printk("write MPU6050_CONFIG failed, ret=%d\n", ret);
return ret;
}

ret = mpu6050_write_reg(&mpu6050dev, MPU6050_ACCEL_CONFIG, 0x00);
if (ret) {
printk("write MPU6050_ACCEL_CONFIG failed, ret=%d\n", ret);
return ret;
}

ret = mpu6050_write_reg(&mpu6050dev, MPU6050_GYRO_CONFIG, 0x18);
if (ret) {
printk("write MPU6050_GYRO_CONFIG failed, ret=%d\n", ret);
return ret;
}

msleep(200);

mpu6050_read_reg(&mpu6050dev, MPU6050_WHO_AM_I, &buf);
if (ret)
return ret;

if (buf != 0x68) {
ret = -ENODEV;
goto err_dev;
}

mpu6050dev.device = device_create(mpu6050dev.class, NULL, mpu6050dev.devid, NULL, MPU6050_NAME);
if(IS_ERR(mpu6050dev.device))
{
return PTR_ERR(mpu6050dev.device);
}

printk("MPU6050 Init SUCCESS!\n");
return 0;

err_dev:
printk("MPU6050 Init failed!\n");
return ret;
}

  试着装载新编译好的模块,但是依然出现以下报错

1
2
3
/home/hcw/learn/24_mpu6050 # insmod mpu6050.ko
[ 384.326515] matched!
[ 385.077229] MPU6050 Init failed!

   说明设备树节点已经和 of_match_table 匹配成功。那么错误点大概率在这里,我们试着将读到的 ID 值打印出来看看

1
2
3
4
5
6
7
8
9
10
ret = mpu6050_read_reg(&mpu6050dev, MPU6050_WHO_AM_I, &buf);
if (ret)
return ret;

printk("WHO_AM_I = 0x%02x\n", buf);

if (buf != 0x68) {
ret = -ENODEV;
goto err_dev;
}

  可以看到器件 ID 读取出来以后是 0x70,居然不是 0x68,说明我手上的芯片不是 MPU6050(但它标注的确实是 6050,坑人)。我们修改判断条件,终于初始化成功!

图 1 初始化成功

  运行测试程序,终于读取到了正确的加速度和角速度。

图 2 读取成功

  最终代码如下:

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
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
/*
* @Author: 胡城玮
* @FilePath: mpu6050.c
* @Date: 2026-03-16
* @Description:
* @Version: 0.1
*/

#include "mpu6050.h"
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/i2c.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define MPU6050_CNT 1
#define MPU6050_NAME "mpu6050"

struct mpu6050_dev{
dev_t devid;
struct cdev cdev;
struct class *class;
struct device *device;
struct device_node *nd;
int major;
void *private_data;
s16 acc[3],gyr[3]; // 加速度、角速度
};

static struct mpu6050_dev mpu6050dev;

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

msg[0].addr = client->addr;
msg[0].flags = 0;
msg[0].buf = &reg;
msg[0].len = 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) {
if (ret >= 0)
ret = -EIO;
return ret;
}

return 0;
}

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

b[0] = reg;
memcpy(&b[1], buf,len);

msg.addr = client->addr;
msg.buf = b;
msg.flags = 0;
msg.len = len+1;

ret = i2c_transfer(client->adapter, &msg, 1);
if(ret != 1)
{
if (ret >= 0)
ret = -EIO;
return ret;
}

return 0;
}

static u8 mpu6050_read_reg(struct mpu6050_dev *dev, u8 reg, u8 *val)
{
int ret = 0;

ret = mpu6050_read_regs(dev, reg, val, 1);

if (ret)
return ret;
return ret;
}

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

static void mpu6050_read_data(struct mpu6050_dev *dev)
{
u8 buf[6];
int ret = 0;

mpu6050_read_regs(dev, MPU6050_GYRO_XOUT_H, buf, 6);
dev->gyr[0] = (buf[0] << 8)|buf[1];
dev->gyr[1] = (buf[2] << 8)|buf[3];
dev->gyr[2] = (buf[4] << 8)|buf[5];

mpu6050_read_regs(dev, MPU6050_ACCEL_XOUT_H, buf, 6);
dev->acc[0] = (buf[0] << 8)|buf[1];
dev->acc[1] = (buf[2] << 8)|buf[3];
dev->acc[2] = (buf[4] << 8)|buf[5];


}

static int mpu6050_open(struct inode *inode,struct file *filp)
{
filp->private_data = &mpu6050dev;
return 0;
}

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

static ssize_t mpu6050_read (struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{
int ret = 0;
struct mpu6050_dev *dev = (struct mpu6050_dev *)filp->private_data;

mpu6050_read_data(dev);

s16 data[6];
data[0] = dev->gyr[0];
data[1] = dev->gyr[1];
data[2] = dev->gyr[2];
data[3] = dev->acc[0];
data[4] = dev->acc[1];
data[5] = dev->acc[2];

ret = copy_to_user(buf, data, sizeof(data));
return 0;
}

static const struct file_operations mpu6050_fops = {
.owner = THIS_MODULE,
.open = mpu6050_open,
.release = mpu6050_release,
.read = mpu6050_read,
};

static int mpu6050_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
u8 buf;
printk("matched!\n");

mpu6050dev.private_data = client;

int ret = 0;

msleep(500);

ret = mpu6050_write_reg(&mpu6050dev, MPU6050_PWR_MGMT_1, 0x00);
if (ret) {
printk("write MPU6050_PWR_MGMT_1 failed, ret=%d\n", ret);
return ret;
}

ret = mpu6050_write_reg(&mpu6050dev, MPU6050_SMPLRT_DIV, 0x07);
if (ret) {
printk("write MPU6050_SMPLRT_DIV failed, ret=%d\n", ret);
return ret;
}

ret = mpu6050_write_reg(&mpu6050dev, MPU6050_CONFIG, 0x06);
if (ret) {
printk("write MPU6050_CONFIG failed, ret=%d\n", ret);
return ret;
}

ret = mpu6050_write_reg(&mpu6050dev, MPU6050_ACCEL_CONFIG, 0x00);
if (ret) {
printk("write MPU6050_ACCEL_CONFIG failed, ret=%d\n", ret);
return ret;
}

ret = mpu6050_write_reg(&mpu6050dev, MPU6050_GYRO_CONFIG, 0x18);
if (ret) {
printk("write MPU6050_GYRO_CONFIG failed, ret=%d\n", ret);
return ret;
}

msleep(200);

ret = mpu6050_read_reg(&mpu6050dev, MPU6050_WHO_AM_I, &buf);
if (ret)
return ret;

printk("WHO_AM_I = 0x%02x\n", buf);

if (buf != 0x70) {
ret = -ENODEV;
goto err_dev;
}

mpu6050dev.device = device_create(mpu6050dev.class, NULL, mpu6050dev.devid, NULL, MPU6050_NAME);
if(IS_ERR(mpu6050dev.device))
{
return PTR_ERR(mpu6050dev.device);
}

printk("MPU6050 Init SUCCESS!\n");
return 0;

err_dev:
printk("MPU6050 Init failed!\n");
return ret;
}

static void mpu6050_remove(struct i2c_client *client)
{
device_destroy(mpu6050dev.class,mpu6050dev.devid);
}

static const struct of_device_id mpu6050_of_match[] = {
{.compatible = "hcw,mpu6050"},
{}
};

static struct i2c_driver mpu6050_drv = {
.probe = mpu6050_probe,
.remove = mpu6050_remove,
.driver ={
.name = "mpu6050_drv",
.of_match_table = mpu6050_of_match,
.owner = THIS_MODULE,
},
};

static int __init mpu6050_init(void)
{
u8 ret = 0;
if(mpu6050dev.major)
{
mpu6050dev.devid = MKDEV(mpu6050dev.major, 0);
register_chrdev_region(mpu6050dev.devid, MPU6050_CNT, MPU6050_NAME);
}
else
{
alloc_chrdev_region(&mpu6050dev.devid,0, MPU6050_CNT,MPU6050_NAME);
mpu6050dev.major = MAJOR(mpu6050dev.devid);
}

cdev_init(&mpu6050dev.cdev, &mpu6050_fops);
cdev_add(&mpu6050dev.cdev, mpu6050dev.devid, MPU6050_CNT);

mpu6050dev.class = class_create(THIS_MODULE, MPU6050_NAME);
if(IS_ERR(mpu6050dev.class))
{
return PTR_ERR(mpu6050dev.class);
}
ret = i2c_add_driver(&mpu6050_drv);
if (ret)
goto destroy_class;

return 0;

destroy_class:
class_destroy(mpu6050dev.class);
cdev_del(&mpu6050dev.cdev);
unregister_chrdev_region(mpu6050dev.devid, MPU6050_CNT);

return ret;
}

static void __exit mpu6050_exit(void)
{
i2c_del_driver(&mpu6050_drv);
class_destroy(mpu6050dev.class);
cdev_del(&mpu6050dev.cdev);
unregister_chrdev_region(mpu6050dev.devid, MPU6050_CNT);
}

module_init(mpu6050_init);
module_exit(mpu6050_exit);
MODULE_AUTHOR("hcw");
MODULE_LICENSE("GPL");