# C语言嵌入式中的I2C

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系统中的具体实现和使用方法。这些代码库涵盖了多种编程语言和硬件平台,为你提供了丰富的参考资源。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值