I2C(Inter-Integrated Circuit,内置集成电路)是一种由飞利浦(Philips)公司于 1982 年开发的短距离、低速、串行通信总线,主要用于连接集成电路(IC)之间的通信,尤其适用于板级设备间的低速数据传输。它凭借简单的硬件结构、较少的引脚需求和灵活的通信方式,在嵌入式系统、消费电子、传感器网络等领域被广泛应用。
一、I2C 的核心特点
- 双线通信:仅需两根信号线即可实现数据传输,分别是:SDA(Serial Data Line):串行数据线,用于双向传输数据;SCL(Serial Clock Line):串行时钟线,由主设备提供时钟信号,控制数据传输速率。
- 主从架构:通信由主设备(如微控制器 MCU)发起和控制,从设备(如传感器、存储器、显示器等)被动响应。总线上可以有多个主设备和多个从设备,但同一时刻只能有一个主设备主导通信。
- 地址机制:每个从设备都有唯一的7 位或 10 位地址(7 位地址最常用),主设备通过地址识别并选择通信的从设备,无需片选信号,简化了硬件设计。
- 同步通信:数据传输由 SCL 线的时钟信号同步,主设备控制时钟频率(通常为 100kbps、400kbps,高速模式可达 3.4Mbps),从设备按时钟节拍收发数据。
- 线与逻辑:SDA 和 SCL 线通过上拉电阻接电源(通常外部连接,部分芯片内置),采用 “线与” 逻辑 —— 当所有设备都不驱动总线时,总线默认处于高电平;任何设备输出低电平都会将总线拉低。这一特性支持多设备共享总线。
二、I2C 的通信流程
I2C 的通信遵循严格的时序规范,核心步骤包括:
- 起始信号(Start Condition):主设备将 SCL 保持高电平时,SDA 从高电平拉低到低电平,标志着一次通信的开始。
- 发送从设备地址和读写位:主设备通过 SDA 线发送 7 位从设备地址,随后发送 1 位 “读写位”(0 表示写操作,1 表示读操作)。从设备接收到地址后,若与自身地址匹配,会在第 9 个时钟周期通过 SDA 线返回应答信号(ACK)(拉低 SDA 表示确认)。
- 数据传输:若为写操作:主设备连续发送 8 位数据,每发送完 1 字节,从设备返回 1 个 ACK;若为读操作:从设备连续发送 8 位数据,每发送完 1 字节,主设备返回 1 个 ACK。
- 停止信号(Stop Condition):主设备将 SCL 保持高电平时,SDA 从低电平拉高到高电平,标志着本次通信结束,总线释放。
三、I2C 的优势与局限性
优势:
- 硬件简单:仅需两根线,节省芯片引脚和 PCB 空间;
- 多设备支持:通过地址区分从设备,适合多传感器、多外设的场景(如一个 MCU 连接温湿度传感器、加速度计、EEPROM 等);
- 灵活性高:支持主从切换、多主设备(需仲裁机制避免冲突);
- 低成本:无需复杂的硬件电路,上拉电阻即可实现总线逻辑。
局限性:
- 传输速率较低:相比 SPI(串行外设接口)等总线,I2C 速率较慢,不适合高速数据传输(如视频、大量实时数据);
- 传输距离短:受总线电容限制,通常仅用于板内或短距离(数米内)通信;
- 抗干扰较弱:双线通信对噪声较敏感,在强电磁环境下需额外防护。
四、使用飞腾派读取I2C总线上的SHT20温湿度传感器的测量值
使用I2C只需要运用这几条指令:
i2c_fd = open(I2C_DEVICE, O_RDWR)
其中I2C_DEVICE是定义的I2C设备的节点。
写指令:write(i2c_fd, buffer, 1)
读指令:read(i2c_fd, buffer, 字节数)
其中i2c_fd是设备,buffer是要写入或者用来存储读出数据的数组
I2C总线的通讯时序大致如下:
写:发送起始信号→发送从机地址→发送寄存器地址→发送数据→(发送CRC,可选)→发送终止信号
读:发送起始信号→发送从机地址→寄存器地址→发送重复起始信号和读信号→读取数据发送终止信号
飞腾派上设备树已经有I2C的库,只需要包含<linux/i2c-dev.h>
库即可直接调用I2C总线并使用。我们在程序头部先把程序有关的头文件和宏定义先写好:
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/i2c-dev.h>
#include <stdint.h>
#include <errno.h>
#include <time.h>
//SHT20对应的I2C控制器的设备节点i2c-2
#define I2C_DEVICE "/dev/i2c-2"
//SHT20对应的I2C设备地址
#define SHT20_ADDRESS 0x40
// SHT20命令定义,使用非主机模式
#define TRIGGER_TEMP_MEASURE_NOHOLD 0xF3
//温度
#define TRIGGER_HUMI_MEASURE_NOHOLD 0xF5
//湿度
#define SOFT_RESET 0xFE
//软重启
/*
SHT20的I2C数据通常可以在数据帧末尾加上CRC校验值用于校验数据是否发生改变,关于CRC的介绍网络上有很多,可以直接搜索学习。
CRC多项式的确定根据SHT20的手册,可知第8,5,4,0位为1,所以CRC多项式应该为0x131
*/
// CRC8多项式
#define POLYNOMIAL 0x131
// 计算CRC8校验
uint8_t crc8(uint8_t *data, int len) {
uint8_t crc = 0xFF;
for (int i = 0; i < len; i++) {
crc ^= data[i];
for (int bit = 8; bit > 0; bit--) {
if (crc & 0x80) {
crc = (crc << 1) ^ POLYNOMIAL;
} else {
crc <<= 1;
}
}
}
return crc;
}
int main() {
int i2c_fd;
uint8_t buffer[3];
printf("SHT20温湿度传感器测试程序\n");
// 打开I2C设备
if ((i2c_fd = open(I2C_DEVICE, O_RDWR)) < 0) {
perror("打开I2C设备失败");
return EXIT_FAILURE;
}
// 设置I2C从机地址
if (ioctl(i2c_fd, I2C_SLAVE, SHT20_ADDRESS) < 0) {
perror("设置I2C地址失败");
close(i2c_fd);
return EXIT_FAILURE;
}
// 发送软复位命令
buffer[0] = SOFT_RESET;
if (write(i2c_fd, buffer, 1) != 1) {
perror("复位命令发送失败");
close(i2c_fd);
return EXIT_FAILURE;
}
usleep(20000); // 等待20ms复位完成
while (1) {
// 读取温度
buffer[0] = TRIGGER_TEMP_MEASURE_NOHOLD;
if (write(i2c_fd, buffer, 1) != 1) {
perror("温度测量命令发送失败");
continue;
}
// 等待测量完成(最大85ms)
usleep(85000);
// 读取3字节数据(MSB, LSB, CRC)
if (read(i2c_fd, buffer, 3) != 3) {
perror("温度数据读取失败");
continue;
}
// 检查CRC
// if (crc8(buffer, 2) != buffer[2]) {
// fprintf(stderr, "温度数据CRC校验失败\n");
// continue;
// }
// 计算温度值
uint16_t raw_temp = (buffer[0] << 8) | buffer[1];
raw_temp &= 0xFFFC; // 清除状态位
float temperature = -46.85 + 175.72 * (raw_temp / 65536.0);
// 读取湿度
buffer[0] = TRIGGER_HUMI_MEASURE_NOHOLD;
if (write(i2c_fd, buffer, 1) != 1) {
perror("湿度测量命令发送失败");
continue;
}
// 等待测量完成(最大29ms)
usleep(29000);
// 读取湿度数据
if (read(i2c_fd, buffer, 3) != 3) {
perror("湿度数据读取失败");
continue;
}
// 检查CRC
// if (crc8(buffer, 2) != buffer[2]) {
// fprintf(stderr, "湿度数据CRC校验失败\n");
// continue;
// }
// 计算湿度值
uint16_t raw_humidity = (buffer[0] << 8) | buffer[1];
raw_humidity &= 0xFFFC; // 清除状态位
float humidity = -6.0 + 125.0 * (raw_humidity / 65536.0);
// 打印结果
time_t now = time(NULL);
struct tm *t = localtime(&now);
printf("[%02d:%02d:%02d] 温度: %.2f°C, 湿度: %.2f%%\n",
t->tm_hour, t->tm_min, t->tm_sec,
temperature, humidity);
sleep(2); // 每2秒读取一次
}
close(i2c_fd);
return EXIT_SUCCESS;
}
五、程序编译
保存上述代码文件为sht20.c,用cd指令选择到保存的文件夹下,使用终端输入
sudo gcc sht20.c -o sht20
就编译完成了。
接下来按照引脚图接线:VCC接3.3V,GND接GND,SDA接第三脚,SCL接第5脚
终端输入指令:
sudo ./sht20
可以看到有输出如下:

