ICode9

精准搜索请尝试: 精确搜索
首页 > 系统相关> 文章详细

linux I2C应用编程

2022-06-25 12:36:05  阅读:176  来源: 互联网

标签:I2C addr 编程 param 地址 linux msg 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[in] fd:I2C设备文件描述符
 5  * \param[in] dev_addr:I2C设备地址
 6  * \param[in] reg_addr:寄存器地址
 7  * \param[in] data:指向希望写入的数据地址
 8  * \param[in] 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[20] = { 0 };
18     
19     buff[0] = reg_addr;
20     memcpy(&buff[1], 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[0] = 0xF0;
    i2c_write(fd, MAX30100_DEV_ADDR, 0x01, buf, 1); //向0x01寄存器写入0xF0

  用逻辑分析仪抓取到的波形为:

  

 

   读数据的代码如下:

 1 /**
 2  * \brief I2C读数据
 3  * 
 4  * \param[in] fd:I2C设备文件描述符
 5  * \param[in] dev_addr:I2C设备地址
 6  * \param[in] reg_addr:寄存器地址
 7  * \param[out] data:存放读取到的数据
 8  * \param[in] 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[2] = {
20         {
21             .addr = dev_addr,   //设备地址
22             .flags = 0,         //标志,为0表示写数据
23             .len = 1,           //要写的数据的长度
24             .buf = &reg_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寄存器读取数据

  用逻辑分析仪抓取波形为:

 

   从写数据和读数据的代码中可以看出,写数据需要一个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[in] fd:I2C设备文件描述符
 17  * \param[in] dev_addr:I2C设备地址
 18  * \param[in] reg_addr:寄存器地址
 19  * \param[in] data:指向希望写入的数据地址
 20  * \param[in] 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[in] fd:I2C设备文件描述符
 38  * \param[in] dev_addr:I2C设备地址
 39  * \param[in] reg_addr:寄存器地址
 40  * \param[in] data:指向希望写入的数据地址
 41  * \param[in] 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[20] = { 0 };
 51     
 52     buff[0] = reg_addr;
 53     memcpy(&buff[1], 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[in] fd:I2C设备文件描述符
 77  * \param[in] dev_addr:I2C设备地址
 78  * \param[in] reg_addr:寄存器地址
 79  * \param[out] data:存放读取到的数据
 80  * \param[in] 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[2] = {
 92         {
 93             .addr = dev_addr,   //设备地址
 94             .flags = 0,         //标志,为0表示写数据
 95             .len = 1,           //要写的数据的长度
 96             .buf = &reg_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[16] = { 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[0] = 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 #endif

i2c.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[in] fd:I2C设备文件描述符
11  * \param[in] dev_addr:I2C设备地址
12  * \param[in] reg_addr:寄存器地址
13  * \param[in] data:指向希望写入的数据地址
14  * \param[in] len:希望写入的字节个数
15  *
16  * \retval 成功返回0,失败返回-1
17  */
18 extern int i2c_init(unsigned char* dev_path);
19 
20 /**
21  * \brief I2C读数据
22  * 
23  * \param[in] fd:I2C设备文件描述符
24  * \param[in] dev_addr:I2C设备地址
25  * \param[in] reg_addr:寄存器地址
26  * \param[in] data:指向希望写入的数据地址
27  * \param[in] 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[in] fd:I2C设备文件描述符
39  * \param[in] dev_addr:I2C设备地址
40  * \param[in] reg_addr:寄存器地址
41  * \param[out] data:存放读取到的数据
42  * \param[in] 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

 

标签:I2C,addr,编程,param,地址,linux,msg,i2c
来源: https://www.cnblogs.com/Suzkfly/p/16404986.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有