I2C(Inter-Integrated Circuit)是一种串行通信协议,用于低速短距离的内部通信。在嵌入式系统中,I2C常用于连接微控制器和外围设备,如传感器、存储器、显示器等。
定义
- 主设备和从设备:I2C通信中有一个主设备和一个或多个从设备。
- 双线通信:I2C使用两条线进行通信,一条是数据线SDA,另一条是时钟线SCL。
- 地址:每个从设备都有一个唯一的地址,主设备通过地址与从设备通信。
功能
I²C(Inter-Integrated Circuit)协议是一种串行通信协议,用于连接低速设备如微控制器、EEPROM、AD/DA转换器等。以下是其主要功能:
- 主从架构:I²C协议采用主从架构,其中一个主设备控制总线,与一个或多个从设备进行通信。
- 双向通信:I²C允许双向通信,即主设备可以向从设备发送数据,从设备也可以向主设备发送数据。
- 多主设备****支持:I²C支持多个主设备,这些主设备可以共享同一总线。
- 同步****通信:I²C使用时钟信号进行同步,确保数据在设备之间准确传输。
- 7位和10位地址模式:I²C支持7位和10位的地址模式,允许连接多达128个或1024个从设备。
- 数据传输****速率:I²C支持不同的数据传输速率,包括标准模式(100 kbps)、快速模式(400 kbps)、快速模式加(1 Mbps)和高速模式(3.4 Mbps)。
- 总线仲裁:当多个主设备试图同时控制总线时,I²C协议提供了一种仲裁机制来决定哪个主设备可以控制总线。
- 时钟延展:从设备可以通过时钟延展来控制数据传输的速率。
- 电源管理:I²C协议支持电源管理功能,允许设备在低功耗模式下工作。
- 简单的硬件接口:I²C协议只需要两条线(数据线和时钟线)进行通信,大大简化了硬件设计。
- 错误检测:I²C协议通过ACK/NACK机制进行错误检测和确认。
I²C协议的这些功能使其成为许多嵌入式系统和电子产品中的理想选择,特别是在需要连接多个低速设备的情况下。
具体使用
1. 用户空间访问
通常,I2C设备由内核驱动程序控制,但也可以通过/dev
接口从用户空间访问所有适配器上的设备。需要加载i2c-dev
模块。
2. 适配器编号
每个注册的i2c适配器都有一个编号,从0开始计数。你可以检查/sys/class/i2c-dev/
来查看哪个编号对应哪个适配器。或者运行i2cdetect -l
来获取所有i2c适配器的格式化列表。
3. C语言示例
以下是一个C语言程序访问i2c适配器的示例:
- 首先,你需要包括两个头文件:
<linux/i2c-dev.h>
和<i2c/smbus.h>
。 - 然后,你必须决定要访问哪个适配器。
- 接下来,打开设备文件,例如:
int file;
int adapter_nr = 2;
char filename[20];
snprintf(filename, 19, "/dev/i2c-%d", adapter_nr);
file = open(filename, O_RDWR);
- 你必须指定要与之通信的设备地址。
- 现在,你可以使用SMBus命令或纯I2C与设备通信。
4. I2C和SMBus协议
注意,只有I2C和SMBus协议的一个子集可以通过read()和write()调用实现。特别是,所谓的组合事务(在同一事务中混合读取和写入消息)不受支持。
5. 编译注意事项
重要的是,由于使用了内联函数,你必须在编译程序时使用-O
或某些变体。
6. 完整接口描述
还有一些IOCTL定义,用于更复杂的操作,如改变从地址、选择十位地址模式、选择SMBus PEC生成和验证等。
7. SMBus级别事务
你可以通过以下函数进行SMBus级别事务:
__s32 i2c_smbus_write_quick(int file, __u8 value);
__s32 i2c_smbus_read_byte(int file);
__s32 i2c_smbus_write_byte(int file, __u8 value);
__s32 i2c_smbus_read_byte_data(int file, __u8 command);
__s32 i2c_smbus_write_byte_data(int file, __u8 command, __u8 value);
__s32 i2c_smbus_read_word_data(int file, __u8 command);
__s32 i2c_smbus_write_word_data(int file, __u8 command, __u16 value);
__s32 i2c_smbus_process_call(int file, __u8 command, __u16 value);
__s32 i2c_smbus_read_block_data(int file, __u8 command, __u8 *values);
__s32 i2c_smbus_write_block_data(int file, __u8 command, __u8 length, __u8 *values);
8. 实现细节
对于有兴趣的人,这里还介绍了内核中使用/dev
接口到I2C时的代码流程。
这些信息为在Linux系统中使用I2C提供了全面的指导,包括如何从用户空间访问I2C设备,如何使用C语言进行编程,以及如何使用SMBus级别的事务。
在Linux系统中使用I2C
在Linux系统中,I2C通常通过内核驱动程序进行操作。以下是一个使用I2C的示例:
#include <linux/i2c.h> // 包括Linux I2C库
struct i2c_adapter *adapter; // 定义一个指向I2C适配器的指针
struct i2c_client *client; // 定义一个指向I2C客户端的指针
adapter = i2c_get_adapter(bus_num); // 获取指定编号的I2C适配器
client = i2c_new_device(adapter, &info); // 使用适配器和信息结构创建新的I2C客户端
i2c_get_adapter(bus_num)
: 此函数用于获取指定编号的I2C适配器。i2c_new_device(adapter, &info)
: 此函数用于使用给定的适配器和信息结构创建新的I2C客户端。
在SSD中的具体实现和使用
在SSD(Solid State Drive)中,I2C可以用于连接控制器和闪存芯片等。以下是一个示例:
#include "i2c.h" // 包括I2C库
I2C_Init(); // 初始化I2C
I2C_Start(); // 开始I2C通信
I2C_SendAddress(SSD_ADDRESS, WRITE); // 发送SSD地址和写入命令
I2C_SendData(data); // 发送数据
I2C_Stop(); // 停止I2C通信
I2C_Init()
: 初始化I2C通信,配置相关硬件。I2C_Start()
: 开始I2C通信,通常涉及生成起始条件。I2C_SendAddress(SSD_ADDRESS, WRITE)
: 发送SSD的地址和写入命令。这通常涉及将设备地址和读/写位放入I2C总线上。I2C_SendData(data)
: 发送数据到I2C总线上。这可以是一个字节或一系列字节。I2C_Stop()
: 停止I2C通信,通常涉及生成停止条件。
这些代码段分别展示了在Linux系统中如何使用I2C通信以及在SSD中如何实现和使用I2C。在Linux中,代码涉及使用内核提供的I2C接口。在SSD中,代码可能涉及更底层的硬件访问和控制。
经典用例解析
Linux实例
这个示例展示了如何通过I2C接口从温度传感器读取温度数据。我们将使用Linux系统,并通过C语言进行编程。
#include <stdio.h>
#include <fcntl.h>
#include <linux/i2c-dev.h>
#include <unistd.h>
#define I2C_ADAPTER_NR 2 // I2C适配器编号
#define SENSOR_ADDR 0x48 // 温度传感器的I2C地址
int main() {
int file;
char filename[20];
snprintf(filename, 19, "/dev/i2c-%d", I2C_ADAPTER_NR); // 构造设备文件名
file = open(filename, O_RDWR); // 打开I2C设备文件
if (file < 0) {
perror("Failed to open the i2c bus");
return 1;
}
if (ioctl(file, I2C_SLAVE, SENSOR_ADDR) < 0) { // 设置I2C从设备地址
perror("Failed to acquire bus access or talk to slave");
return 1;
}
// 读取温度传感器的数据
char buffer[2] = {0};
if (read(file, buffer, 2) != 2) {
perror("Failed to read from the i2c bus");
return 1;
}
// 转换数据到温度值
int temp = (buffer[0] << 8 | buffer[1]) >> 4; // 12位数据
float temperature = temp * 0.0625; // 转换为摄氏度
printf("Temperature: %.2f°C\n", temperature); // 打印温度
close(file); // 关闭I2C设备文件
return 0;
}
代码注释解析
- 适配器和地址设置:我们首先定义了I2C适配器的编号和温度传感器的I2C地址。
- 打开I2C设备文件:通过适配器编号构造设备文件名,并打开该文件。
- 设置I2C从设备地址:使用
ioctl
系统调用设置I2C从设备地址。 - 读取温度数据:从I2C总线读取2个字节的数据。
- 转换数据:将读取的数据转换为温度值。在这个示例中,我们假设传感器提供了12位的温度数据,并且每位代表0.0625摄氏度。
- 打印温度:将转换后的温度值打印到控制台。
- 关闭I2C设备文件:最后,我们关闭I2C设备文件。
这个示例展示了如何通过I2C接口与温度传感器通信,并读取温度数据。它涵盖了I2C通信的基本步骤,包括打开设备文件、设置从设备地址、读取数据、转换数据和关闭设备文件。
SSD实例
当然,下面是一个在SSD(Solid-State Drive)系统中使用I2C接口的经典用例。这个示例展示了如何通过I2C接口与一个EEPROM(电可擦写只读存储器)通信,进行数据的读取和写入。
#include <stdio.h>
#include <fcntl.h>
#include <linux/i2c-dev.h>
#include <unistd.h>
#define I2C_ADAPTER_NR 1 // I2C适配器编号
#define EEPROM_ADDR 0x50 // EEPROM的I2C地址
int main() {
int file;
char filename[20];
snprintf(filename, 19, "/dev/i2c-%d", I2C_ADAPTER_NR); // 构造设备文件名
file = open(filename, O_RDWR); // 打开I2C设备文件
if (file < 0) {
perror("Failed to open the i2c bus");
return 1;
}
if (ioctl(file, I2C_SLAVE, EEPROM_ADDR) < 0) { // 设置I2C从设备地址
perror("Failed to acquire bus access or talk to slave");
return 1;
}
// 写入数据到EEPROM
char writeBuffer[3] = {0x00, 0x00, 'A'}; // 地址0x0000,数据'A'
if (write(file, writeBuffer, 3) != 3) {
perror("Failed to write to the i2c bus");
return 1;
}
sleep(1); // 等待写入完成
// 读取数据从EEPROM
char readBuffer[2] = {0x00, 0x00}; // 地址0x0000
if (write(file, readBuffer, 2) != 2) {
perror("Failed to write to the i2c bus");
return 1;
}
char data;
if (read(file, &data, 1) != 1) {
perror("Failed to read from the i2c bus");
return 1;
}
printf("Data read: %c\n", data); // 打印读取的数据
close(file); // 关闭I2C设备文件
return 0;
}
代码注释解析
- 适配器和地址设置:定义了I2C适配器的编号和EEPROM的I2C地址。
- 打开I2C设备文件:通过适配器编号构造设备文件名,并打开该文件。
- 设置I2C从设备地址:使用
ioctl
系统调用设置I2C从设备地址。 - 写入数据到EEPROM:将数据’A’写入到EEPROM的地址0x0000。写入的数据包括2个字节的地址和1个字节的数据。
- 等待写入完成:EEPROM写入可能需要一些时间,所以我们等待1秒确保写入完成。
- 读取数据从EEPROM:从EEPROM的地址0x0000读取数据。首先写入2个字节的地址,然后读取1个字节的数据。
- 打印读取的数据:将读取的数据打印到控制台。
- 关闭I2C设备文件:最后,关闭I2C设备文件。
这个示例展示了如何通过I2C接口与EEPROM通信,在SSD系统中进行数据的读取和写入。它涵盖了I2C通信的基本步骤,包括打开设备文件、设置从设备地址、写入数据、读取数据和关闭设备文件。这是一个在SSD系统中常见的用例,用于存储和检索小量的非易失性数据。
注意事项
当然,I2C(Inter-Integrated Circuit)协议是一种广泛使用的串行通信协议,用于连接微控制器和其它集成电路。在使用I2C时,有一些关键的注意事项需要考虑,以确保通信的可靠性和效率。
1. 电气连接
- 上拉电阻: I2C使用开漏(或开源)输出,因此需要上拉电阻来确保线路在非活动状态下被拉高。
- 电压匹配: 连接到同一I2C总线的所有设备都应具有相同的逻辑电压等级。
2. 总线速度
- 标准模式、快速模式和高速模式: I2C支持多种速度模式,选择合适的模式可以平衡速度和可靠性。
3. 地址冲突
- 唯一地址: 每个I2C设备都必须具有唯一的地址。地址冲突会导致通信失败。
4. 多主机冲突
- 仲裁: 如果有多个主机尝试同时控制总线,必须有仲裁机制来解决冲突。
5. 数据完整性
- 校验和错误检测: 可以使用校验和或其他错误检测机制来确保数据的完整性。
6. 时序和同步
- 时钟拉伸: 一些I2C设备可能会拉伸时钟以适应其处理能力,主机必须能够适应这种行为。
- 启动和停止条件: 正确的启动和停止条件是I2C通信的关键。
7. 电源管理
- 低功耗模式: 考虑如何在低功耗模式下管理I2C通信,特别是在电池供电的系统中。
8. 兼容性和互操作性
- 设备兼容性: 不同厂商的I2C设备可能存在细微差异,确保兼容性是关键。
9. 线路长度和电容负载
- 线路长度限制: I2C总线的长度和电容负载可能会影响信号质量和通信速度。
10. 安全性
- 防护措施: 在安全关键的应用中,可能需要额外的防护措施来防止未经授权的访问。
总结
I2C是一种强大而灵活的通信协议,但也有许多细节需要注意。从电气连接到数据完整性,从时序同步到安全性,每个方面都需要仔细考虑和计划。理解这些注意事项并将其纳入设计中,可以确保I2C通信的可靠性和效率。
附录
以下是一些在GitHub上找到的与Linux系统中I2C有关的项目和代码库。这些项目可以为你提供关于I2C在Linux环境中的具体使用和实现的详细信息。
1. Rust I2C Library for Linux
这个库提供了在Linux下使用Rust语言与I2C设备进行交互的接口。
2. Linux I2C Library (C/C++/Python)
这个库支持C/C++/Python,可以在Linux系统下与I2C设备进行通信。
3. C Library for Peripheral I/O in Linux
这个C库提供了Linux下的外设I/O(包括GPIO、LED、PWM、SPI、I2C、MMIO、串行)的接口。
4. Linux Kernel Driver for CH341 Emulating the I2C Bus
这个Linux内核驱动程序用于模拟I2C总线的CH341。
5. Simple Linux I2C Example Code
这个项目提供了一个简单的Linux I2C示例代码。
6. SwiftyGPIO: A Swift Library for Linux/ARM Boards
这个Swift库用于Linux/ARM板上的硬件项目,支持GPIOs/SPI/I2C/PWM/UART/1Wire等。
7. Linux I2C Legacy Library
这个库是Linux I2C的遗留库。
通过这些项目,你可以深入了解I2C在Linux系统中的具体实现和使用方法。这些代码库涵盖了多种编程语言和硬件平台,为你提供了丰富的参考资源。