linux I2C应用编程
一、前言本人熟悉I2C的时序,可以用单片机写I2C驱动程序,但是在linux上使用i2c接口不用我们去定义时序,我只想知道在linux平台上是如何用函数传输I2C数据的,因此本文只讨论linux下如何将I2C用起来。
二、打开设备
linux下一切皆文件,I2C设备也是一个文件,我使用的2416开发板上有一组I2C,设备路径为/dev/i2c-0,在2440的开发板上则为/dev/i2c/0,根据平台的不同会有所不同。在使用I2C设备之前要先打开这个设备,代码如下:
fd = open("/dev/i2c/0", O_RDWR);三、数据读写
数据写入用的不是write函数,数据读取也不是用read函数,而是统一用ioctl,用法如下:
ioctl的第一个参数传入已经打开的I2C设备的文件描述符,第二个参数传入I2C_RDWR,表示进行数据读写,第三个参数传入一个struct i2c_rdwr_ioctl_data类型的指针,struct i2c_rdwr_ioctl_data类型定义在linux/i2c-dev.h中,其结构定义如下:
struct i2c_rdwr_ioctl_data {
struct i2c_msg __user *msgs; /* pointers to i2c_msgs */
__u32 nmsgs; /* number of i2c_msgs */
}; 一个该结构表示一次传输,一次传输可以包含若干个消息,nmsgs用于指定消息数量。一般来说一次写数据包含一个消息,一次读数据包含2个消息,因此写数据时nmsgs的值为1,msgs指向一个消息,读数据时nmsgs为2,msgs指向一个包含2个消息的数组。
struct i2c_msg结构定义如下:
struct i2c_msg {
__u16 addr; /* slave address */
__u16 flags;
#define I2C_M_TEN 0x0010 /* this is a ten bit chip address */
#define I2C_M_RD 0x0001 /* read data, from slave to master */
#define I2C_M_NOSTART 0x4000 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_REV_DIR_ADDR 0x2000 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_IGNORE_NAK 0x1000 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NO_RD_ACK 0x0800 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_RECV_LEN 0x0400 /* length will be first received byte */
__u16 len; /* msg length */
__u8 *buf; /* pointer to msg data */
}; 根据注释可知addr指的是I2C设备地址,它可以是7位地址或10位地址(说实话我没见过10位地址的设备),根据设备的实际情况而定。我们只讨论一般情况,即7位地址。
这里需要特别注意的是,由于是7位地址,因此不包含读写位,并且是右对齐的,比如一个器件的手册说它的设备读地址为0xAF,写地址为0xAE,它的这种说法是将读写位也代入进来了,因此我们编程时传入的地址为0x57。
如果是写数据,第二个成员flags就传0,如果是读数据就传I2C_M_RD。
第三个成员就是读或者写的数据长度,第四个成员指向读写缓冲区的地址。这里又有需要注意的地方,I2C设备地址不算入数据长度中,但是寄存器地址要放入数据缓冲区中。
写数据的程序如下:
1 /**
2* \brief I2C写数据
3*
4* \param fd:I2C设备文件描述符
5* \param dev_addr:I2C设备地址
6* \param reg_addr:寄存器地址
7* \param data:指向希望写入的数据地址
8* \param len:希望写入的字节个数
9*
10* \retval 成功返回0,失败返回-1
11*
12* \note 该函数适用于8位从机地址,且寄存器地址只有1个字节的情况
13*/
14 int i2c_write(int fd, unsigned short dev_addr, unsigned char reg_addr, unsigned char* data, unsigned int len)
15 {
16 int ret = -1;
17 unsigned char buff = { 0 };
18
19 buff = reg_addr;
20 memcpy(&buff, data, len);
21
22 //写数据是1个msg
23 struct i2c_msg msg = {
24 .addr = dev_addr,
25 .flags = 0,
26 .len = len + 1,
27 .buf = buff,
28 };
29
30 struct i2c_rdwr_ioctl_data rdwr_msg = {
31 .msgs = &msg,
32 .nmsgs = 1,
33 };
34
35 ret = ioctl(fd, I2C_RDWR, &rdwr_msg);
36
37 return ret;
38 } 我们设想一个简单的数据写入过程,向某个8位寄存器写入一个字节时,在I2C总线上发生的事件依次是:起始信号→设备(写)地址→从机应答→寄存器地址→从机应答→要写入的数据→从机应答→停止信号。
假设,设备写地址为0xAE,寄存器地址为0x01,写入的数据为0x0F,那么代码如下:
buf = 0xF0;
i2c_write(fd, MAX30100_DEV_ADDR, 0x01, buf, 1); //向0x01寄存器写入0xF0 用逻辑分析仪抓取到的波形为:
https://img2022.cnblogs.com/blog/1432180/202206/1432180-20220625120137591-522513935.png
读数据的代码如下:
1 /**
2* \brief I2C读数据
3*
4* \param fd:I2C设备文件描述符
5* \param dev_addr:I2C设备地址
6* \param reg_addr:寄存器地址
7* \param data:存放读取到的数据
8* \param len:希望读取的字节个数
9*
10* \retval 成功返回0,失败返回-1
11*
12* \note 该函数适用于8位从机地址,且寄存器地址只有1个字节的情况
13*/
14 int i2c_read(int fd, unsigned short dev_addr,unsigned char reg_addr, unsigned char* data, unsigned int len)
15 {
16 int ret = -1;
17
18 //读数据有2个msg
19 struct i2c_msg msg = {
20 {
21 .addr = dev_addr, //设备地址
22 .flags = 0, //标志,为0表示写数据
23 .len = 1, //要写的数据的长度
24 .buf = ®_addr, //要写的数据的地址
25 },
26 {
27 .addr = dev_addr, //设备地址
28 .flags = I2C_M_RD,//标志,I2C_M_RD表示主机向主机读数据
29 .len = len, //要读取的数据的长度
30 .buf = data, //读取的数据存放的地址
31 },
32 };
33
34 struct i2c_rdwr_ioctl_data rdwr_msg = {
35 .msgs = msg,
36 .nmsgs = 2,
37 };
38
39 ret = ioctl(fd, I2C_RDWR, &rdwr_msg);
40
41 return ret;
42 } 再设想一个简答的数据读出过程,从某个8位寄存器中读出一个字节时,在I2C总线上发生的事件依次是:起始信号→设备(写)地址→从机应答→寄存器地址→从机应答→起始信号→设备(读)地址→从机应答→从机发送寄存器中的数据→主机发送非应答→停止信号。
那么调用如下代码就可从寄存器0x01中读出数据:
i2c_read(fd, MAX30100_DEV_ADDR, 0x01, buf, 1);//从0x01寄存器读取数据 用逻辑分析仪抓取波形为:
https://img2022.cnblogs.com/blog/1432180/202206/1432180-20220625120607833-808824134.png
从写数据和读数据的代码中可以看出,写数据需要一个msg,而读数据需要2个msg,可以这样去理解,一个ioctl会出现一次停止信号,一个msg会出现一次起始信号,在读取数据的过程中需要发送1个停止信号和2个起始信号,因此读数据的代码中有2个msg。
完整代码如下:
i2c.c
1 #include <stdio.h>
2 #include <sys/types.h>
3 #include <sys/stat.h>
4 #include <fcntl.h>
5 #include <unistd.h>
6 #include <string.h>
7 #include <stdlib.h>
8 #include <linux/i2c.h>
9 #include <linux/i2c-dev.h>
10 #include "i2c.h"
11
12
13 /**
14* \brief I2C读数据
15*
16* \param fd:I2C设备文件描述符
17* \param dev_addr:I2C设备地址
18* \param reg_addr:寄存器地址
19* \param data:指向希望写入的数据地址
20* \param len:希望写入的字节个数
21*
22* \retval 成功返回0,失败返回-1
23*/
24 int i2c_init(unsigned char* dev_path)
25 {
26 int fd = 0;
27
28 //打开IIC总线设备节点
29 fd = open(dev_path, O_RDWR);
30
31 return fd;
32 }
33
34 /**
35* \brief I2C读数据
36*
37* \param fd:I2C设备文件描述符
38* \param dev_addr:I2C设备地址
39* \param reg_addr:寄存器地址
40* \param data:指向希望写入的数据地址
41* \param len:希望写入的字节个数
42*
43* \retval 成功返回0,失败返回-1
44*
45* \note 该函数适用于8位从机地址,且寄存器地址只有1个字节的情况
46*/
47 int i2c_write(int fd, unsigned short dev_addr, unsigned char reg_addr, unsigned char* data, unsigned int len)
48 {
49 int ret = -1;
50 unsigned char buff = { 0 };
51
52 buff = reg_addr;
53 memcpy(&buff, data, len);
54
55 //写数据是1个msg
56 struct i2c_msg msg = {
57 .addr = dev_addr,
58 .flags = 0,
59 .len = len + 1,
60 .buf = buff,
61 };
62
63 struct i2c_rdwr_ioctl_data rdwr_msg = {
64 .msgs = &msg,
65 .nmsgs = 1,
66 };
67
68 ret = ioctl(fd, I2C_RDWR, &rdwr_msg);
69
70 return ret;
71 }
72
73 /**
74* \brief I2C读数据
75*
76* \param fd:I2C设备文件描述符
77* \param dev_addr:I2C设备地址
78* \param reg_addr:寄存器地址
79* \param data:存放读取到的数据
80* \param len:希望读取的字节个数
81*
82* \retval 成功返回0,失败返回-1
83*
84* \note 该函数适用于8位从机地址,且寄存器地址只有1个字节的情况
85*/
86 int i2c_read(int fd, unsigned short dev_addr,unsigned char reg_addr, unsigned char* data, unsigned int len)
87 {
88 int ret = -1;
89
90 //读数据有2个msg
91 struct i2c_msg msg = {
92 {
93 .addr = dev_addr, //设备地址
94 .flags = 0, //标志,为0表示写数据
95 .len = 1, //要写的数据的长度
96 .buf = ®_addr, //要写的数据的地址
97 },
98 {
99 .addr = dev_addr, //设备地址
100 .flags = I2C_M_RD,//标志,I2C_M_RD表示主机向主机读数据
101 .len = len, //要读取的数据的长度
102 .buf = data, //读取的数据存放的地址
103 },
104 };
105
106 struct i2c_rdwr_ioctl_data rdwr_msg = {
107 .msgs = msg,
108 .nmsgs = 2,
109 };
110
111 ret = ioctl(fd, I2C_RDWR, &rdwr_msg);
112
113 return ret;
114 }
115
116
117 /**
118* \brief 测试程序
119*/
120 #if 1
121 int main(int argc, const char *argv[])
122 {
123 char buf = { 0 };
124 int fd = 0;
125
126 fd = i2c_init("/dev/i2c/0"); //初始化I2C设备
127 if (fd < 0) {
128 printf("i2c_init failed\n");
129 return 0;
130 }
131
132 //i2c_read(fd, MAX30100_DEV_ADDR, 0x01, buf, 1);
133
134 buf = 0xF0;
135 i2c_write(fd, MAX30100_DEV_ADDR, 0x01, buf, 1); //向0x01寄存器写入0xF0
136
137 i2c_read(fd, MAX30100_DEV_ADDR, 0x01, buf, 1);//从0x01寄存器读取数据
138
139 return 0;
140 }
141 #endifi2c.h
1 #ifndef __I2C_H
2 #define __I2C_H
3
4 #define MAX30100_DEV_ADDR 0x57 //定义MAX30100的设备地址
5
6
7 /**
8* \brief I2C读数据
9*
10* \param fd:I2C设备文件描述符
11* \param dev_addr:I2C设备地址
12* \param reg_addr:寄存器地址
13* \param data:指向希望写入的数据地址
14* \param len:希望写入的字节个数
15*
16* \retval 成功返回0,失败返回-1
17*/
18 extern int i2c_init(unsigned char* dev_path);
19
20 /**
21* \brief I2C读数据
22*
23* \param fd:I2C设备文件描述符
24* \param dev_addr:I2C设备地址
25* \param reg_addr:寄存器地址
26* \param data:指向希望写入的数据地址
27* \param len:希望写入的字节个数
28*
29* \retval 成功返回0,失败返回-1
30*
31* \note 该函数适用于8位从机地址,且寄存器地址只有1个字节的情况
32*/
33 extern int i2c_write(int fd, unsigned short dev_addr, unsigned char reg_addr, unsigned char* data, unsigned int len);
34
35 /**
36* \brief I2C读数据
37*
38* \param fd:I2C设备文件描述符
39* \param dev_addr:I2C设备地址
40* \param reg_addr:寄存器地址
41* \param data:存放读取到的数据
42* \param len:希望读取的字节个数
43*
44* \retval 成功返回0,失败返回-1
45*
46* \note 该函数适用于8位从机地址,且寄存器地址只有1个字节的情况
47*/
48 extern int i2c_read(int fd, unsigned short dev_addr,unsigned char reg_addr, unsigned char* data, unsigned int len);
49
50 #endif
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页:
[1]