linux I2C应用编程
2022/6/26 5:21:43
本文主要是介绍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[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 = ®_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 = ®_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
这篇关于linux I2C应用编程的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-12如何创建可引导的 ESXi USB 安装介质 (macOS, Linux, Windows)
- 2024-11-08linux的 vi编辑器中搜索关键字有哪些常用的命令和技巧?-icode9专业技术文章分享
- 2024-11-08在 Linux 的 vi 或 vim 编辑器中什么命令可以直接跳到文件的结尾?-icode9专业技术文章分享
- 2024-10-22原生鸿蒙操作系统HarmonyOS NEXT(HarmonyOS 5)正式发布
- 2024-10-18操作系统入门教程:新手必看的基本操作指南
- 2024-10-18初学者必看:操作系统入门全攻略
- 2024-10-17操作系统入门教程:轻松掌握操作系统基础知识
- 2024-09-11Linux部署Scrapy学习:入门级指南
- 2024-09-11Linux部署Scrapy:入门级指南
- 2024-08-21【Linux】分区向左扩容的方法