linux I2C应用编程

打印 上一主题 下一主题

主题 785|帖子 785|积分 2355

一、前言

  本人熟悉I2C的时序,可以用单片机写I2C驱动程序,但是在linux上使用i2c接口不用我们去定义时序,我只想知道在linux平台上是如何用函数传输I2C数据的,因此本文只讨论linux下如何将I2C用起来。
二、打开设备

  linux下一切皆文件,I2C设备也是一个文件,我使用的2416开发板上有一组I2C,设备路径为/dev/i2c-0,在2440的开发板上则为/dev/i2c/0,根据平台的不同会有所不同。在使用I2C设备之前要先打开这个设备,代码如下:
  1. 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中,其结构定义如下:
  1. struct i2c_rdwr_ioctl_data {
  2.     struct i2c_msg __user *msgs;    /* pointers to i2c_msgs */
  3.     __u32 nmsgs;            /* number of i2c_msgs */
  4. };
复制代码
  一个该结构表示一次传输,一次传输可以包含若干个消息,nmsgs用于指定消息数量。一般来说一次写数据包含一个消息,一次读数据包含2个消息,因此写数据时nmsgs的值为1,msgs指向一个消息,读数据时nmsgs为2,msgs指向一个包含2个消息的数组。
  struct i2c_msg结构定义如下:
  1. struct i2c_msg {
  2.     __u16 addr;    /* slave address            */
  3.     __u16 flags;
  4. #define I2C_M_TEN        0x0010    /* this is a ten bit chip address */
  5. #define I2C_M_RD        0x0001    /* read data, from slave to master */
  6. #define I2C_M_NOSTART        0x4000    /* if I2C_FUNC_PROTOCOL_MANGLING */
  7. #define I2C_M_REV_DIR_ADDR    0x2000    /* if I2C_FUNC_PROTOCOL_MANGLING */
  8. #define I2C_M_IGNORE_NAK    0x1000    /* if I2C_FUNC_PROTOCOL_MANGLING */
  9. #define I2C_M_NO_RD_ACK        0x0800    /* if I2C_FUNC_PROTOCOL_MANGLING */
  10. #define I2C_M_RECV_LEN        0x0400    /* length will be first received byte */
  11.     __u16 len;        /* msg length                */
  12.     __u8 *buf;        /* pointer to msg data            */
  13. };
复制代码
  根据注释可知addr指的是I2C设备地址,它可以是7位地址或10位地址(说实话我没见过10位地址的设备),根据设备的实际情况而定。我们只讨论一般情况,即7位地址。
  这里需要特别注意的是,由于是7位地址,因此不包含读写位,并且是右对齐的,比如一个器件的手册说它的设备读地址为0xAF,写地址为0xAE,它的这种说法是将读写位也代入进来了,因此我们编程时传入的地址为0x57。
  如果是写数据,第二个成员flags就传0,如果是读数据就传I2C_M_RD。
  第三个成员就是读或者写的数据长度,第四个成员指向读写缓冲区的地址。这里又有需要注意的地方,I2C设备地址不算入数据长度中,但是寄存器地址要放入数据缓冲区中。
  写数据的程序如下:
  1. 1 /**
  2. 2  * \brief I2C写数据
  3. 3  *
  4. 4  * \param[in] fd:I2C设备文件描述符
  5. 5  * \param[in] dev_addr:I2C设备地址
  6. 6  * \param[in] reg_addr:寄存器地址
  7. 7  * \param[in] data:指向希望写入的数据地址
  8. 8  * \param[in] len:希望写入的字节个数
  9. 9  *
  10. 10  * \retval 成功返回0,失败返回-1
  11. 11  *
  12. 12  * \note 该函数适用于8位从机地址,且寄存器地址只有1个字节的情况
  13. 13  */
  14. 14 int i2c_write(int fd, unsigned short dev_addr, unsigned char reg_addr, unsigned char* data, unsigned int len)
  15. 15 {
  16. 16     int ret = -1;
  17. 17     unsigned char buff[20] = { 0 };
  18. 18     
  19. 19     buff[0] = reg_addr;
  20. 20     memcpy(&buff[1], data, len);
  21. 21
  22. 22     //写数据是1个msg
  23. 23     struct i2c_msg msg = {
  24. 24         .addr = dev_addr,
  25. 25         .flags = 0,
  26. 26         .len = len + 1,
  27. 27         .buf = buff,
  28. 28     };
  29. 29     
  30. 30     struct i2c_rdwr_ioctl_data rdwr_msg = {
  31. 31         .msgs = &msg,
  32. 32         .nmsgs = 1,
  33. 33     };
  34. 34     
  35. 35     ret = ioctl(fd, I2C_RDWR, &rdwr_msg);
  36. 36     
  37. 37     return ret;
  38. 38 }
复制代码
  我们设想一个简单的数据写入过程,向某个8位寄存器写入一个字节时,在I2C总线上发生的事件依次是:起始信号→设备(写)地址→从机应答→寄存器地址→从机应答→要写入的数据→从机应答→停止信号。
  假设,设备写地址为0xAE,寄存器地址为0x01,写入的数据为0x0F,那么代码如下:
  1.     buf[0] = 0xF0;
  2.     i2c_write(fd, MAX30100_DEV_ADDR, 0x01, buf, 1); //向0x01寄存器写入0xF0
复制代码
  用逻辑分析仪抓取到的波形为:
  
 
   读数据的代码如下:
  1. 1 /**
  2. 2  * \brief I2C读数据
  3. 3  *
  4. 4  * \param[in] fd:I2C设备文件描述符
  5. 5  * \param[in] dev_addr:I2C设备地址
  6. 6  * \param[in] reg_addr:寄存器地址
  7. 7  * \param[out] data:存放读取到的数据
  8. 8  * \param[in] len:希望读取的字节个数
  9. 9  *
  10. 10  * \retval 成功返回0,失败返回-1
  11. 11  *
  12. 12  * \note 该函数适用于8位从机地址,且寄存器地址只有1个字节的情况
  13. 13  */
  14. 14 int i2c_read(int fd, unsigned short dev_addr,  unsigned char reg_addr, unsigned char* data, unsigned int len)
  15. 15 {
  16. 16     int ret = -1;
  17. 17
  18. 18     //读数据有2个msg
  19. 19     struct i2c_msg msg[2] = {
  20. 20         {
  21. 21             .addr = dev_addr,   //设备地址
  22. 22             .flags = 0,         //标志,为0表示写数据
  23. 23             .len = 1,           //要写的数据的长度
  24. 24             .buf = &reg_addr,   //要写的数据的地址
  25. 25         },
  26. 26         {
  27. 27             .addr = dev_addr,   //设备地址
  28. 28             .flags = I2C_M_RD,  //标志,I2C_M_RD表示主机向主机读数据
  29. 29             .len = len,         //要读取的数据的长度
  30. 30             .buf = data,        //读取的数据存放的地址
  31. 31         },
  32. 32     };
  33. 33
  34. 34     struct i2c_rdwr_ioctl_data rdwr_msg = {
  35. 35         .msgs = msg,
  36. 36         .nmsgs = 2,
  37. 37     };
  38. 38
  39. 39     ret = ioctl(fd, I2C_RDWR, &rdwr_msg);
  40. 40
  41. 41     return ret;
  42. 42 }
复制代码
  再设想一个简答的数据读出过程,从某个8位寄存器中读出一个字节时,在I2C总线上发生的事件依次是:起始信号→设备(写)地址→从机应答→寄存器地址→从机应答→起始信号→设备(读)地址→从机应答→从机发送寄存器中的数据→主机发送非应答→停止信号。
  那么调用如下代码就可从寄存器0x01中读出数据:
  1. i2c_read(fd, MAX30100_DEV_ADDR, 0x01, buf, 1);  //从0x01寄存器读取数据
复制代码
  用逻辑分析仪抓取波形为:

 
   从写数据和读数据的代码中可以看出,写数据需要一个msg,而读数据需要2个msg,可以这样去理解,一个ioctl会出现一次停止信号,一个msg会出现一次起始信号,在读取数据的过程中需要发送1个停止信号和2个起始信号,因此读数据的代码中有2个msg。
完整代码如下:
  i2c.c
  1.   1 #include <stdio.h>
  2.   2 #include <sys/types.h>
  3.   3 #include <sys/stat.h>
  4.   4 #include <fcntl.h>
  5.   5 #include <unistd.h>
  6.   6 #include <string.h>
  7.   7 #include <stdlib.h>
  8.   8 #include <linux/i2c.h>
  9.   9 #include <linux/i2c-dev.h>
  10. 10 #include "i2c.h"
  11. 11
  12. 12
  13. 13 /**
  14. 14  * \brief I2C读数据
  15. 15  *
  16. 16  * \param[in] fd:I2C设备文件描述符
  17. 17  * \param[in] dev_addr:I2C设备地址
  18. 18  * \param[in] reg_addr:寄存器地址
  19. 19  * \param[in] data:指向希望写入的数据地址
  20. 20  * \param[in] len:希望写入的字节个数
  21. 21  *
  22. 22  * \retval 成功返回0,失败返回-1
  23. 23  */
  24. 24 int i2c_init(unsigned char* dev_path)
  25. 25 {
  26. 26     int fd = 0;
  27. 27     
  28. 28     //打开IIC总线设备节点
  29. 29     fd = open(dev_path, O_RDWR);
  30. 30
  31. 31     return fd;
  32. 32 }
  33. 33
  34. 34 /**
  35. 35  * \brief I2C读数据
  36. 36  *
  37. 37  * \param[in] fd:I2C设备文件描述符
  38. 38  * \param[in] dev_addr:I2C设备地址
  39. 39  * \param[in] reg_addr:寄存器地址
  40. 40  * \param[in] data:指向希望写入的数据地址
  41. 41  * \param[in] len:希望写入的字节个数
  42. 42  *
  43. 43  * \retval 成功返回0,失败返回-1
  44. 44  *
  45. 45  * \note 该函数适用于8位从机地址,且寄存器地址只有1个字节的情况
  46. 46  */
  47. 47 int i2c_write(int fd, unsigned short dev_addr, unsigned char reg_addr, unsigned char* data, unsigned int len)
  48. 48 {
  49. 49     int ret = -1;
  50. 50     unsigned char buff[20] = { 0 };
  51. 51     
  52. 52     buff[0] = reg_addr;
  53. 53     memcpy(&buff[1], data, len);
  54. 54
  55. 55     //写数据是1个msg
  56. 56     struct i2c_msg msg = {
  57. 57         .addr = dev_addr,
  58. 58         .flags = 0,
  59. 59         .len = len + 1,
  60. 60         .buf = buff,
  61. 61     };
  62. 62     
  63. 63     struct i2c_rdwr_ioctl_data rdwr_msg = {
  64. 64         .msgs = &msg,
  65. 65         .nmsgs = 1,
  66. 66     };
  67. 67     
  68. 68     ret = ioctl(fd, I2C_RDWR, &rdwr_msg);
  69. 69     
  70. 70     return ret;
  71. 71 }
  72. 72
  73. 73 /**
  74. 74  * \brief I2C读数据
  75. 75  *
  76. 76  * \param[in] fd:I2C设备文件描述符
  77. 77  * \param[in] dev_addr:I2C设备地址
  78. 78  * \param[in] reg_addr:寄存器地址
  79. 79  * \param[out] data:存放读取到的数据
  80. 80  * \param[in] len:希望读取的字节个数
  81. 81  *
  82. 82  * \retval 成功返回0,失败返回-1
  83. 83  *
  84. 84  * \note 该函数适用于8位从机地址,且寄存器地址只有1个字节的情况
  85. 85  */
  86. 86 int i2c_read(int fd, unsigned short dev_addr,  unsigned char reg_addr, unsigned char* data, unsigned int len)
  87. 87 {
  88. 88     int ret = -1;
  89. 89
  90. 90     //读数据有2个msg
  91. 91     struct i2c_msg msg[2] = {
  92. 92         {
  93. 93             .addr = dev_addr,   //设备地址
  94. 94             .flags = 0,         //标志,为0表示写数据
  95. 95             .len = 1,           //要写的数据的长度
  96. 96             .buf = &reg_addr,   //要写的数据的地址
  97. 97         },
  98. 98         {
  99. 99             .addr = dev_addr,   //设备地址
  100. 100             .flags = I2C_M_RD,  //标志,I2C_M_RD表示主机向主机读数据
  101. 101             .len = len,         //要读取的数据的长度
  102. 102             .buf = data,        //读取的数据存放的地址
  103. 103         },
  104. 104     };
  105. 105
  106. 106     struct i2c_rdwr_ioctl_data rdwr_msg = {
  107. 107         .msgs = msg,
  108. 108         .nmsgs = 2,
  109. 109     };
  110. 110
  111. 111     ret = ioctl(fd, I2C_RDWR, &rdwr_msg);
  112. 112
  113. 113     return ret;
  114. 114 }
  115. 115
  116. 116
  117. 117 /**
  118. 118  * \brief 测试程序
  119. 119  */
  120. 120 #if 1
  121. 121 int main(int argc, const char *argv[])
  122. 122 {
  123. 123     char buf[16] = { 0 };
  124. 124     int fd = 0;
  125. 125     
  126. 126     fd = i2c_init("/dev/i2c/0");    //初始化I2C设备
  127. 127     if (fd < 0) {
  128. 128         printf("i2c_init failed\n");
  129. 129         return 0;
  130. 130     }
  131. 131     
  132. 132     //i2c_read(fd, MAX30100_DEV_ADDR, 0x01, buf, 1);
  133. 133     
  134. 134     buf[0] = 0xF0;
  135. 135     i2c_write(fd, MAX30100_DEV_ADDR, 0x01, buf, 1); //向0x01寄存器写入0xF0
  136. 136     
  137. 137     i2c_read(fd, MAX30100_DEV_ADDR, 0x01, buf, 1);  //从0x01寄存器读取数据
  138. 138     
  139. 139     return 0;
  140. 140 }
  141. 141 #endif
复制代码
i2c.h
  1. 1 #ifndef __I2C_H
  2. 2 #define __I2C_H
  3. 3
  4. 4 #define MAX30100_DEV_ADDR   0x57    //定义MAX30100的设备地址
  5. 5
  6. 6
  7. 7 /**
  8. 8  * \brief I2C读数据
  9. 9  *
  10. 10  * \param[in] fd:I2C设备文件描述符
  11. 11  * \param[in] dev_addr:I2C设备地址
  12. 12  * \param[in] reg_addr:寄存器地址
  13. 13  * \param[in] data:指向希望写入的数据地址
  14. 14  * \param[in] len:希望写入的字节个数
  15. 15  *
  16. 16  * \retval 成功返回0,失败返回-1
  17. 17  */
  18. 18 extern int i2c_init(unsigned char* dev_path);
  19. 19
  20. 20 /**
  21. 21  * \brief I2C读数据
  22. 22  *
  23. 23  * \param[in] fd:I2C设备文件描述符
  24. 24  * \param[in] dev_addr:I2C设备地址
  25. 25  * \param[in] reg_addr:寄存器地址
  26. 26  * \param[in] data:指向希望写入的数据地址
  27. 27  * \param[in] len:希望写入的字节个数
  28. 28  *
  29. 29  * \retval 成功返回0,失败返回-1
  30. 30  *
  31. 31  * \note 该函数适用于8位从机地址,且寄存器地址只有1个字节的情况
  32. 32  */
  33. 33 extern int i2c_write(int fd, unsigned short dev_addr, unsigned char reg_addr, unsigned char* data, unsigned int len);
  34. 34
  35. 35 /**
  36. 36  * \brief I2C读数据
  37. 37  *
  38. 38  * \param[in] fd:I2C设备文件描述符
  39. 39  * \param[in] dev_addr:I2C设备地址
  40. 40  * \param[in] reg_addr:寄存器地址
  41. 41  * \param[out] data:存放读取到的数据
  42. 42  * \param[in] len:希望读取的字节个数
  43. 43  *
  44. 44  * \retval 成功返回0,失败返回-1
  45. 45  *
  46. 46  * \note 该函数适用于8位从机地址,且寄存器地址只有1个字节的情况
  47. 47  */
  48. 48 extern int i2c_read(int fd, unsigned short dev_addr,  unsigned char reg_addr, unsigned char* data, unsigned int len);
  49. 49
  50. 50 #endif
复制代码
 

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

八卦阵

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表