抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

对于 I2C 主机驱动,一旦编写完成就不需要再做修改,其他的 I2C 设备直接调用主机驱动提供的 API 函数完成读写操作即可。这个正好符合 Linux 的驱动分离与分层的思想,因此 Linux内核也将 I2C 驱动分为两部分:

①、I2C 总线驱动, I2C 总线驱动就是 SOC 的 I2C 控制器驱动,也叫做 I2C 适配器驱动。

②、I2C 设备驱动, I2C 设备驱动就是针对具体的 I2C 设备而编写的驱动。

具体的I2C协议就不具体介绍了。

1.Linux下IIC驱动框架

image-20241214220251751

1.1IIC总线驱动

对于 I2C 而言,不需要虚拟出一条总线,直接使用 I2C总线即可。 I2C 总线驱动重点是 I2C 适配器(也就是 SOC 的 I2C 接口控制器)驱动,这里要用到两个重要的数据结构: i2c_adapteri2c_algorithm, Linux 内核将 SOC 的 I2C 适配器(控制器)抽象成 i2c_adapter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
struct i2c_adapter {
struct module *owner;
unsigned int class; /* classes to allow probing for */
const struct i2c_algorithm *algo; /* the algorithm to access the bus */
void *algo_data;

/* data fields that are valid for all devices */
struct rt_mutex bus_lock;

int timeout; /* in jiffies */
int retries;
struct device dev; /* the adapter device */

int nr;
char name[48];
struct completion dev_released;

struct mutex userspace_clients_lock;
struct list_head userspace_clients;

struct i2c_bus_recovery_info *bus_recovery_info;
const struct i2c_adapter_quirks *quirks;
};

i2c_algorithm 类型的指针变量 algo,对于一个 I2C 适配器,肯定要对外提供读写 API 函数,设备驱动程序可以使用这些 API 函数来完成读写操作。 i2c_algorithm 就是 I2C 适配器与 IIC 设备进行通信的方法。

1
2
3
4
5
6
7
8
9
10
struct i2c_algorithm {
int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
int num);
int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,
unsigned short flags, char read_write,
u8 command, int size, union i2c_smbus_data *data);

/* To determine what the adapter supports */
u32 (*functionality) (struct i2c_adapter *);
};
  • master_xfer 就是 I2C 适配器的传输函数,可以通过此函数来完成与 IIC 设备之间的通信。
  • smbus_xfer 就是 SMBUS 总线的传输函数。

综上所述, I2C 总线驱动,或者说 I2C 适配器驱动的主要工作就是初始化 i2c_adapter 结构体变量,然后设置 i2c_algorithm 中的 master_xfer 函数。 完成以后通过 i2c_add_numbered_adapteri2c_add_adapter 这两个函数向系统注册设置好的 i2c_adapter,这两个函数的原型如下:

1
2
int i2c_add_adapter(struct i2c_adapter *adapter)
int i2c_add_numbered_adapter(struct i2c_adapter *adap)

这两个函数的区别在于 i2c_add_adapter 使用动态的总线号,而 i2c_add_numbered_adapter使用静态总线号。

  • adapter 或 adap:要添加到 Linux 内核中的 i2c_adapter,也就是 I2C 适配器。

  • 返回值: 0,成功;负值,失败。

删除 I2C 适配器的话使用 i2c_del_adapter 函数

1
void i2c_del_adapter(struct i2c_adapter * adap)
  • adap:要删除的 I2C 适配器。

  • 返回值: 无。

1.2 I2C设备驱动

I2C 设备驱动重点关注两个数据结构: i2c_clienti2c_driver

1
2
3
4
5
6
7
8
9
10
11
struct i2c_client {
unsigned short flags; /* div., see below */
unsigned short addr; /* chip address - NOTE: 7bit */
/* addresses are stored in the */
/* _LOWER_ 7 bits */
char name[I2C_NAME_SIZE];
struct i2c_adapter *adapter; /* the adapter we sit on */
struct device dev; /* the device structure */
int irq; /* irq issued by device */
struct list_head detected;
};
  • 一个设备对应一个 i2c_client,每检测到一个 I2C 设备就会给这个 I2C 设备分配一个i2c_client。

一般在设备树里面添加具体的I2C芯片,比如以下,”fsl,fxls8471”,系统在解析设备树的时候就会知道有这个I2C设备,然后会创建对应的i2c_client

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
&i2c1 {
clock-frequency = <100000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c1>;
status = "okay";

mag3110@0e {
compatible = "fsl,mag3110";
reg = <0x0e>;
position = <2>;
};

fxls8471@1e {
compatible = "fsl,fxls8471";
reg = <0x1e>;
position = <0>;
interrupt-parent = <&gpio5>;
interrupts = <0 8>;
};
};

i2c_driver 初始化与注册,需要I2C设备驱动编写人员编写的。IIC驱动程序就是初始化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
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 *);

/* 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").
*/
void (*alert)(struct i2c_client *, 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;
};
  • 当 I2C 设备和驱动匹配成功以后 probe 函数就会执行,和 platform 驱动一样
  • device_driver 驱动结构体,如果使用设备树的话,需要设置 device_driver 的of_match_table 成员变量,也就是驱动的兼容(compatible)属性。
  • id_table 是传统的、未使用设备树的设备匹配 ID 表。

i2c_driver 注册函数为 int i2c_register_driver

1
2
int i2c_register_driver(struct module *owner,
struct i2c_driver *driver)
  • owner: 一般为 THIS_MODULE。

  • driver:要注册的 i2c_driver。

  • 返回值: 0,成功;负值,失败。

注销 I2C 设备驱动的时候需要将前面注册的 i2c_driver 从 Linux 内核中注销掉,需要用到i2c_del_driver 函数,此函数原型如下:

1
void i2c_del_driver(struct i2c_driver *driver)
  • driver:要注销的 i2c_driver。

  • 返回值: 无。

1.3 设备和驱动匹配过程

设备和驱动的匹配过程也是由 I2C 总线完成的, I2C 总线的数据结构为 i2c_bus_type

1
2
3
4
5
6
7
struct bus_type i2c_bus_type = {
.name = "i2c",
.match = i2c_device_match,
.probe = i2c_device_probe,
.remove = i2c_device_remove,
.shutdown = i2c_device_shutdown,
};

.match 就是 I2C 总线的设备和驱动匹配函数,在这里就是 i2c_device_match 这个函数,此函数内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
static int i2c_device_match(struct device *dev, struct device_driver *drv)
{
struct i2c_client *client = i2c_verify_client(dev);
struct i2c_driver *driver;

if (!client)
return 0;

/* Attempt an OF style match */
if (of_driver_match_device(dev, drv))
return 1;

/* Then ACPI style match */
if (acpi_driver_match_device(dev, drv))
return 1;

driver = to_i2c_driver(drv);
/* match on an id table if there is one */
if (driver->id_table)
return i2c_match_id(driver->id_table, client) != NULL;

return 0;
}
  • of_driver_match_device 函数用于完成设备树设备和驱动匹配。比较 I2C 设备节点的 compatible 属性和 of_device_id 中的 compatible 属性是否相等,如果相当的话就表示 I2C设备和驱动匹配。
  • acpi_driver_match_device 函数用于 ACPI 形式的匹配。
  • i2c_match_id 函数用于传统的、无设备树的 I2C 设备和驱动匹配过程。比较 I2C设备名字和 i2c_device_id 的 name 字段是否相等,相等的话就说明 I2C 设备和驱动匹配。

1.4 NXP官方驱动

打开imx6ull.dtsi,搜索i2c,会看到三个i2c控制器,但是他们的compatible属性都是一样的。

image-20241214211456657

搜索compatible属性,

image-20241214211622461

查看此.c文件,会发现这是个标准的 platform 驱动。

重点:NXP自己写的结构体,里面就包含了i2c_adapter结构体。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct imx_i2c_struct {
struct i2c_adapter adapter;
struct clk *clk;
void __iomem *base;
wait_queue_head_t queue;
unsigned long i2csr;
unsigned int disable_delay;
int stopped;
unsigned int ifdr; /* IMX_I2C_IFDR */
unsigned int cur_clk;
unsigned int bitrate;
const struct imx_i2c_hwdata *hwdata;

struct imx_i2c_dma *dma;
};
1
2
3
4
static struct i2c_algorithm i2c_imx_algo = {
.master_xfer = i2c_imx_xfer,
.functionality = i2c_imx_func,
};

2. I2C 设备驱动编写流程

2.1未使用设备树的时候

在未使用设备树的时候需要在 BSP 里面使用 i2c_board_info 结构体来描述一个具体的 I2C 设备。

1
2
3
4
5
6
7
8
9
10
struct i2c_board_info {
char type[I2C_NAME_SIZE]; /* I2C 设备名字 */
unsigned short flags; /* 标志 */
unsigned short addr; /* I2C 器件地址 */
void *platform_data;
struct dev_archdata *archdata;
struct device_node *of_node;
struct fwnode_handle *fwnode;
int irq;
};

type 和 addr 这两个成员变量是必须要设置的,一个是 I2C 设备的名字,一个是 I2C 设备的器件地址。

2.2使用设备树的时候

使用设备树的时候 I2C 设备信息通过创建相应的节点就行了。I2C设备挂到哪个I2C控制器下就在哪个控制器下添加对应的节点。

比如 NXP 官方的 EVK 开发板在 I2C1 上接了 mag3110 这个磁力计芯片,因此必须在 i2c1 节点下创建 mag3110 子节点,然后在这个子节点内描述 mag3110 这个芯片的相关信息。

1
2
3
4
5
6
7
8
9
i2c1: i2c@021a0000 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "fsl,imx6ul-i2c", "fsl,imx21-i2c";
reg = <0x021a0000 0x4000>;
interrupts = <GIC_SPI 36 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6UL_CLK_I2C1>;
status = "disabled";
};
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
&i2c1 {
clock-frequency = <100000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c1>; //引脚
status = "okay";

mag3110@0e { //0e 芯片地址
compatible = "fsl,mag3110"; //
reg = <0x0e>; //0e 低7位地址
position = <2>;
};

fxls8471@1e {
compatible = "fsl,fxls8471";
reg = <0x1e>;
position = <0>;
interrupt-parent = <&gpio5>; //中断引脚
interrupts = <0 8>;
};
};


pinctrl_i2c1: i2c1grp {
fsl,pins = <
MX6UL_PAD_UART4_TX_DATA__I2C1_SCL 0x4001b8b0
MX6UL_PAD_UART4_RX_DATA__I2C1_SDA 0x4001b8b0
>;
};
  • 向 i2c1 添加 mag3110 子节点,第 7 行“mag3110@0e”是子节点名字,“@”后面的“0e”就是 mag3110 的 I2C 器件地址。第 8 行设置 compatible 属性值为“fsl,mag3110”。第 9 行的 reg 属性也是设置 mag3110 的器件地址的,因此值为 0x0e。 I2C 设备节点的创建重点是 compatible 属性和 reg 属性的设置,一个用于匹配驱动,一个用于设置器件地址。

3.驱动编写

本实验使用的是mini开发版,不带AP3216C,因此选取MPU6050作为实验。

输出的是模块的原始数据,因此看不到pich,yaw,roll。需要加入dmp。但这是后话了。。

驱动实验采取的是MISC框架。

3.1修改设备树

I2C的引脚,UART4_RXD作为I2C1_SDA,UART4_TXD作为I2C1_SCL。

1
2
3
4
5
6
pinctrl_i2c1: i2c1grp {
fsl,pins = <
MX6UL_PAD_UART4_TX_DATA__I2C1_SCL 0x4001b8b0
MX6UL_PAD_UART4_RX_DATA__I2C1_SDA 0x4001b8b0
>;
};
1
2
3
4
5
6
7
8
9
10
11
12
&i2c1 {
clock-frequency = <100000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c1>;
status = "okay";

//#define MPU6500_ID 0X68 /* MPU6500的器件ID*/
mpu6050@68 {
compatible = "cbus,mpu6050";
reg = <0x68>;
};
};

3.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
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.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.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/atomic.h>
#include <linux/string.h>
#include <linux/interrupt.h>
#include <linux/input.h>
#include "linux/i2c.h"
#include "mpu6050reg.h"
#include "linux/miscdevice.h"

#define MISC6050_NAME "mpu6050"
#define MISC6050_MINOR 255 //255就是自动分配

struct misc_mpu6050_dev
{
void *private;
unsigned short ax,ay,az; //陀螺仪测量值 16位
unsigned short gx,gy,gz; //加速度计 16 位
unsigned short temperature; //温度 16 位
};
struct misc_mpu6050_dev misc_mpu6050;

//读寄存器
static int mpu6050_read_regs(struct misc_mpu6050_dev *dev,u8 reg,void *val,int len)
{
//adsp:IIC设备对应的适配器,也就是IIC接口,当IIC设备和驱动匹配以后,probe函数执行,probe函数传递进来的第一个参数就是i2c_client,
//在i2c_client里面保存了I2C设备所在对应的i2c_adapter。
//mags:就是构成的I2C传输数据。这里需要分两步。//msg[1]: 读取的数据,这里要读取多个数据,所以要设置msg[1].len
struct i2c_msg msgs[2];
struct i2c_client *client = (struct i2c_client*)dev->private;

//msg[0]: 发送要读取的寄存器地址
msgs[0].addr = client->addr; //从机地址,也就是MPU6050地址
msgs[0].flags = 0; //表示为要发送的数据
msgs[0].buf = &reg; //要发送的数据 也就是寄存器地址
msgs[0].len = 1; //要发送的寄存器地址长度,这里为1

//msg[1]: 读取的数据
msgs[1].addr = client->addr; //从机地址,也就是MPU6050地址
msgs[1].flags = I2C_M_RD; //表示为读数据
msgs[1].buf = val; //接收到的从机发送的数据
msgs[1].len = len; //要读取的寄存器长度

return i2c_transfer(client->adapter,msgs,2);
}
//写寄存器
static int mpu6050_write_regs(struct misc_mpu6050_dev *dev,u8 reg,u8 *buf,u8 len) //注意设备的寄存器位数,8位就是u8,16位就是u16
{
u8 send_buf[2];
struct i2c_msg msgs;
struct i2c_client *client = (struct i2c_client*)dev->private;

//构建要发送的数据,也就是寄存器首地址+实际数据
send_buf[0] = reg;
memcpy(&send_buf[1],buf,len);

msgs.addr = client->addr; //从机地址,也就是MPU6050地址
msgs.flags = 0; //表示为要发送的数据
msgs.buf = send_buf; //要发送的数据 也就是寄存器首地址+实际数据
msgs.len = len + 1; //要发送的数据长度 + 寄存器地址长度,这里为1

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

//读取单个寄存器
static unsigned char mpu6050_read_reg(struct misc_mpu6050_dev *dev,u8 reg)
{
u8 val;
mpu6050_read_regs(dev,reg,&val,1);
return val;
}

//写入单个寄存器
//reg: 寄存器
//u8 data: 要写入的数据
static void mpu6050_write_reg(struct misc_mpu6050_dev *dev,u8 reg,u8 data)
{
mpu6050_write_regs(dev,reg,&data,1);
}

//获取mpu6050数据
static void mpu6050_get_data(struct misc_mpu6050_dev *dev)
{
unsigned char buf_a[6]; //加速度
unsigned char buf_g[6]; //陀螺仪
unsigned char buf_t[2]; //温度
short temp;
short raw;

mpu6050_read_regs(dev,MPU_ACCEL_XOUTH_REG,buf_a,6);
dev->ax = ((short)buf_a[0] << 8) | buf_a[1];
dev->ay = ((short)buf_a[2] << 8) | buf_a[3];
dev->az = ((short)buf_a[4] << 8) | buf_a[5];


mpu6050_read_regs(dev,MPU_GYRO_XOUTH_REG,buf_g,6);
dev->gx = ((short)buf_g[0] << 8) | buf_g[1];
dev->gy = ((short)buf_g[2] << 8) | buf_g[3];
dev->gz = ((short)buf_g[4] << 8) | buf_g[5];

mpu6050_read_regs(dev,MPU_TEMP_OUTH_REG,buf_t,2);
raw = ((short)buf_t[0]<<8)|buf_t[1];
temp=3600+(raw)*100/340; //未开启浮点运算,所以扩大了100倍,所以还是有误差的。
dev->temperature = temp ;
}

static int misc6050_open(struct inode *inode, struct file *file)
{
unsigned char value;
file->private_data = &misc_mpu6050;

//初始化mpu6050 具体配置过程是参考裸机例程
mpu6050_write_reg(&misc_mpu6050,MPU_PWR_MGMT1_REG,0x80);//复位6050
mdelay(100);
mpu6050_write_reg(&misc_mpu6050,MPU_PWR_MGMT1_REG,0X00);//唤醒6050
mpu6050_write_reg(&misc_mpu6050,MPU_GYRO_CFG_REG,3);//陀螺仪传感器,±2000dps
mpu6050_write_reg(&misc_mpu6050,MPU_ACCEL_CFG_REG,0);//加速度传感器,±2g
mpu6050_write_reg(&misc_mpu6050,MPU_SAMPLE_RATE_REG,(1000/50-1));//设置采样率50Hz
mpu6050_write_reg(&misc_mpu6050,MPU_CFG_REG,4);//设置LPF为采样率的一半
mpu6050_write_reg(&misc_mpu6050,MPU_INT_EN_REG,0X00); //关闭所有中断
mpu6050_write_reg(&misc_mpu6050,MPU_USER_CTRL_REG,0X00);//I2C主模式关闭
mpu6050_write_reg(&misc_mpu6050,MPU_FIFO_EN_REG,0X00); //关闭FIFO
mpu6050_write_reg(&misc_mpu6050,MPU_PWR_MGMT1_REG, 0X01); //设置CLKSEL,PLL X轴为参考
mpu6050_write_reg(&misc_mpu6050,MPU_PWR_MGMT2_REG,0X00); //加速度与陀螺仪都工作
value = mpu6050_read_reg(&misc_mpu6050,MPU_DEVICE_ID_REG); //读取器件ID
printk("mpu6050_read_reg:0x%x\n",value);

return 0;
}

static ssize_t misc6050_read(struct file *file, char __user *buf, size_t cnt, loff_t *offt)
{
short data[7];
struct misc_mpu6050_dev *dev = file->private_data;

//向应用返回mpu6050原始数据 数据保存在结构体中
mpu6050_get_data(dev);
data[0] = dev->ax;
data[1] = dev->ay;
data[2] = dev->az;
data[3] = dev->gx;
data[4] = dev->gy;
data[5] = dev->gz;
data[6] = dev->temperature;
if(copy_to_user(buf,data,sizeof(data))){
printk("copy_to_user failed!\r\n");
return -EFAULT;
}
return 0;
}

static ssize_t misc6050_write(struct file *file, const char __user *buf, size_t cnt, loff_t *offt)
{
//struct misc_mpu6050_dev *dev = file->private_data;
printk("misc6050_write!\r\n");
return 0;
}

static int misc6050_release(struct inode *inode, struct file *file)
{
printk("misc6050_release!\r\n");
return 0;
}

//字符设备操作集
struct file_operations misc6050_fops = {
.owner = THIS_MODULE,
.open = misc6050_open,
.read = misc6050_read,
.write = misc6050_write,
.release = misc6050_release,
};

struct miscdevice mpu6050_miscdev = {
.minor = MISC6050_MINOR,
.name = MISC6050_NAME,
.fops = &misc6050_fops,
};

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

//搭建字符设备驱动框架 使用MISC注册驱动
ret = misc_register(&mpu6050_miscdev);
if(ret < 0){
printk("mpu6050 misc device register failed!\r\n");
ret = -EFAULT;
}
misc_mpu6050.private = client;
printk("mpu6050_i2c_probe\n");
return ret;
}

static int mpu6050_i2c_remove(struct i2c_client *client)
{
misc_deregister(&mpu6050_miscdev); //misc驱动卸载
printk("mpu6050_i2c_remove\n");
return 0;
}
//传统的匹配表
static const struct i2c_device_id mpu6050_id[] = {
{ "cbus,mpu6050", 0 },
{ }
};
//设备树匹配表
static const struct of_device_id mpu6050_of_match[] = {
{ .compatible = "cbus,mpu6050", },
{ }
};
static struct i2c_driver mpu6050_driver = {
.probe = mpu6050_i2c_probe,
.remove = mpu6050_i2c_remove,
.driver = {
.name = "mpu6050",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(mpu6050_of_match),
},
.id_table = mpu6050_id, //未使用设备树
};

static int __init mpu6050_init(void)
{
int ret = 0;
ret = i2c_add_driver (&mpu6050_driver);
printk("mpu6050_init\n");
return ret;
}

static void __exit mpu6050_exit(void)
{
i2c_del_driver(&mpu6050_driver);
}

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

3.3应用程序

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
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include "linux/ioctl.h"
#include <linux/input.h>



int main(int argc, char *argv[]){
int fd,ret;
char *filename;
unsigned short data[7];

if (argc != 2){
printf("Error Usage!!!\r\n");
return -1;
}

filename = argv[1];

fd = open(filename,O_RDWR);
if (fd < 0){
printf("file %s open failed!!!\r\n",filename);
return -1;
}

while (1)
{
ret = read(fd,&data,sizeof(data));
if (ret == 0)
{
printf("加速度的值为:ax = %d, ay = %d, az = %d.\r\n",data[0],data[1],data[2]);
printf("陀螺仪的值为:gx = %d, gy = %d, gz = %d.\r\n",data[3],data[4],data[5]);
printf("温度值为:temp = %d.%d\r\n",data[6]/100,data[6]%100);
}
else
{
printf("read data failed!!!\r\n");
return -1;
}
usleep(200000); //200ms
}
close(fd);
return 0;
}

3.4mpu6050寄存器

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
#ifndef MPU6050REG_H
#define MPU6050REG_H

#define MPU9250_ADDR 0X68 /* MPU6500的器件IIC地址 */
#define MPU6500_ID 0X68 /* MPU6500的器件ID */

/*MPU9250内部封装了一个AK8963磁力计,地址和ID如下: */
#define AK8963_ADDR 0X0C /* AK8963的I2C地址 */
#define AK8963_ID 0X48 /* AK8963的器件ID */

/* MPU6500的内部寄存器 */
#define MPU_SELF_TESTX_REG 0X0D //自检寄存器X
#define MPU_SELF_TESTY_REG 0X0E //自检寄存器Y
#define MPU_SELF_TESTZ_REG 0X0F //自检寄存器Z
#define MPU_SELF_TESTA_REG 0X10 //自检寄存器A
#define MPU_SAMPLE_RATE_REG 0X19 //采样频率分频器
#define MPU_CFG_REG 0X1A //配置寄存器
#define MPU_GYRO_CFG_REG 0X1B //陀螺仪配置寄存器
#define MPU_ACCEL_CFG_REG 0X1C //加速度计配置寄存器
#define MPU_MOTION_DET_REG 0X1F //运动检测阀值设置寄存器
#define MPU_FIFO_EN_REG 0X23 //FIFO使能寄存器
#define MPU_I2CMST_CTRL_REG 0X24 //IIC主机控制寄存器
#define MPU_I2CSLV0_ADDR_REG 0X25 //IIC从机0器件地址寄存器
#define MPU_I2CSLV0_REG 0X26 //IIC从机0数据地址寄存器
#define MPU_I2CSLV0_CTRL_REG 0X27 //IIC从机0控制寄存器
#define MPU_I2CSLV1_ADDR_REG 0X28 //IIC从机1器件地址寄存器
#define MPU_I2CSLV1_REG 0X29 //IIC从机1数据地址寄存器
#define MPU_I2CSLV1_CTRL_REG 0X2A //IIC从机1控制寄存器
#define MPU_I2CSLV2_ADDR_REG 0X2B //IIC从机2器件地址寄存器
#define MPU_I2CSLV2_REG 0X2C //IIC从机2数据地址寄存器
#define MPU_I2CSLV2_CTRL_REG 0X2D //IIC从机2控制寄存器
#define MPU_I2CSLV3_ADDR_REG 0X2E //IIC从机3器件地址寄存器
#define MPU_I2CSLV3_REG 0X2F //IIC从机3数据地址寄存器
#define MPU_I2CSLV3_CTRL_REG 0X30 //IIC从机3控制寄存器
#define MPU_I2CSLV4_ADDR_REG 0X31 //IIC从机4器件地址寄存器
#define MPU_I2CSLV4_REG 0X32 //IIC从机4数据地址寄存器
#define MPU_I2CSLV4_DO_REG 0X33 //IIC从机4写数据寄存器
#define MPU_I2CSLV4_CTRL_REG 0X34 //IIC从机4控制寄存器
#define MPU_I2CSLV4_DI_REG 0X35 //IIC从机4读数据寄存器

#define MPU_I2CMST_STA_REG 0X36 //IIC主机状态寄存器
#define MPU_INTBP_CFG_REG 0X37 //中断/旁路设置寄存器
#define MPU_INT_EN_REG 0X38 //中断使能寄存器
#define MPU_INT_STA_REG 0X3A //中断状态寄存器

#define MPU_ACCEL_XOUTH_REG 0X3B //加速度值,X轴高8位寄存器
#define MPU_ACCEL_XOUTL_REG 0X3C //加速度值,X轴低8位寄存器
#define MPU_ACCEL_YOUTH_REG 0X3D //加速度值,Y轴高8位寄存器
#define MPU_ACCEL_YOUTL_REG 0X3E //加速度值,Y轴低8位寄存器
#define MPU_ACCEL_ZOUTH_REG 0X3F //加速度值,Z轴高8位寄存器
#define MPU_ACCEL_ZOUTL_REG 0X40 //加速度值,Z轴低8位寄存器

#define MPU_TEMP_OUTH_REG 0X41 //温度值高八位寄存器
#define MPU_TEMP_OUTL_REG 0X42 //温度值低8位寄存器

#define MPU_GYRO_XOUTH_REG 0X43 //陀螺仪值,X轴高8位寄存器
#define MPU_GYRO_XOUTL_REG 0X44 //陀螺仪值,X轴低8位寄存器
#define MPU_GYRO_YOUTH_REG 0X45 //陀螺仪值,Y轴高8位寄存器
#define MPU_GYRO_YOUTL_REG 0X46 //陀螺仪值,Y轴低8位寄存器
#define MPU_GYRO_ZOUTH_REG 0X47 //陀螺仪值,Z轴高8位寄存器
#define MPU_GYRO_ZOUTL_REG 0X48 //陀螺仪值,Z轴低8位寄存器

#define MPU_I2CSLV0_DO_REG 0X63 //IIC从机0数据寄存器
#define MPU_I2CSLV1_DO_REG 0X64 //IIC从机1数据寄存器
#define MPU_I2CSLV2_DO_REG 0X65 //IIC从机2数据寄存器
#define MPU_I2CSLV3_DO_REG 0X66 //IIC从机3数据寄存器

#define MPU_I2CMST_DELAY_REG 0X67 //IIC主机延时管理寄存器
#define MPU_SIGPATH_RST_REG 0X68 //信号通道复位寄存器
#define MPU_MDETECT_CTRL_REG 0X69 //运动检测控制寄存器
#define MPU_USER_CTRL_REG 0X6A //用户控制寄存器
#define MPU_PWR_MGMT1_REG 0X6B //电源管理寄存器1
#define MPU_PWR_MGMT2_REG 0X6C //电源管理寄存器2
#define MPU_FIFO_CNTH_REG 0X72 //FIFO计数寄存器高八位
#define MPU_FIFO_CNTL_REG 0X73 //FIFO计数寄存器低八位
#define MPU_FIFO_RW_REG 0X74 //FIFO读写寄存器
#define MPU_DEVICE_ID_REG 0X75 //器件ID寄存器

/*AK8963的内部寄存器 */
#define MAG_WIA 0x00 //AK8963的器件ID寄存器地址
#define MAG_CNTL1 0X0A
#define MAG_CNTL2 0X0B

#define MAG_XOUT_L 0X03
#define MAG_XOUT_H 0X04
#define MAG_YOUT_L 0X05
#define MAG_YOUT_H 0X06
#define MAG_ZOUT_L 0X07
#define MAG_ZOUT_H 0X08

#endif

4.实验

输入以下命令:

1
2
modprob mpu6050.ko
./mpu6050App /dev/mpu6050

image-20241216215025330

5.注意!

真坑啊,这个模块一会好一会不好,以为代码是哪里错了。找了半天,给它吹会空调就好了。。。

1.要注意IIC器件的一位是几个字节,不过常见的都是8位的,如果要是16位的,就需要在发送和接收数据的时候 ,把数据类型改为u16类型的。

2.有的IIC地址可能是低七位,但是需要在低位加上一位的读和写,此时目标地址就会变化。

评论