I2C通讯协议简介
I2C 通讯协议(Inter-Integrated Circuit)是由 Phiilps 公司开发的,由于它引脚少,硬件实现简单,可扩展性强,不需要 USART、CAN 等通讯协议的外部收发设备,现在被广泛地使用在系统内多个集成电路(IC)间的通讯。下面我们分别对 I2C 协议的物理层及协议层进行讲解。
1.物理层
2C 通讯设备之间的常用连接方式通讯结构如下
它的物理层有以下几个主要特点:
1.支持多设备的总线。“总线”指多个设备共用的信号线。在一个 I2C 通讯总线中,可连接多个 I2C 通讯设备,支持多个通讯主机及多个通讯从机。
2.支持多设备的总线。“总线”指多个设备共用的信号线。在一个 I2C 通讯总线中,可连接多个 I2C 通讯设备,支持多个通讯主机及多个通讯从机。
3.每个连接到总线的设备都有一个唯一的地址,主机利用这个地址在不同设备之间的访问。
4.总线通过上拉电阻接到电源。当 I2C 设备空闲时,会输出高阻态,而当所有设备都空闲,都输出高阻态时,由上拉电阻把总线拉成高电平。多个主机同时使用总线时,为了防止数据冲突,会利用仲裁方式决定由哪个设备占用总线。
5.常用的速率:标准模式传输速率为 100kbit/s ,快速模式为 400kbit/s。
6.最近 I3C 出来了,性能提升大大的
2.协议层
I2C的协议定义了通讯的起始和停止信号、数据有效性、响应、仲裁、时钟同步和地址广播等环节。I2C通讯过程的基本结构如下:
1.I2C写格式
2.I2C读格式
3.I2C读写格式
I2C 的几个细节如下:
1.I2C 的起始和停止信号
前文中提到的起始(S)和停止(P)信号是两种特殊的状态,见图 23-5。当 SCL 线是高电平时 SDA 线从高电平向低电平切换,这个情况表示通讯的起始。当 SCL 是高电平时 SDA 线由低电平向高电平切换,表示通讯的停止。起始和停止信号一般由主机产生。
2.数据有效性
I2C 使用 SDA 信号线来传输数据,使用 SCL 信号线进行数据同步。SDA 数据线在 SCL的每个时钟周期传输一位数据。传输时,SCL 为高电平的时候 SDA 表示的数据有效,即此时的 SDA 为高电平时表示数据“1”,为低电平时表示数据“0”。当 SCL 为低电平时,SDA 的数据无效,一般在这个时候 SDA 进行电平切换,为下一次表示数据做好准备。
每次数据传输都以字节为单位,每次传输的字节数不受限制。
3.地址及数据方向
I2C 总线上的每个设备都有自己的独立地址,主机发起通讯时,通过 SDA 信号线发送设备地(SLAVE_ADDRESS)来查找从机。I2C 协议规定设备地址可以是 7 位或 10 位,实际中 7位的地址应用比较广泛。紧跟设备地址的一个数据位用来表示数据传输方向,它是数据方向位(R/W),第 8 位或第 11 位。数据方向位为“1”时表示主机由从机读数据,该位为“0”时表示主机向从机写数据
读数据方向时,主机会释放对 SDA 信号线的控制,由从机控制 SDA 信号线,主机接收信号,写数据方向时,SDA 由主机控制,从机接收信号。
4.响应
I2C 的数据和地址传输都带响应。响应包括“应答(ACK)”和“非应答(NACK)”两种信号。作为数据接收端时,当设备(无论主从机)接收到 I2C 传输的一个字节数据或地址后,若希望对方继续发送数据,则需要向对方发送“应答(ACK)”信号,发送方会继续发送下一个数据;若接收端希望结束数据传输,则向对方发送“非应答(NACK)”信号,发送方接收到该信号后会产生一个停止信号,结束信号传输。
传输时主机产生时钟,在第 9 个时钟时,数据发送端会释放 SDA 的控制权,由数据接收端控制 SDA,若 SDA 为高电平,表示非应答信号(NACK),低电平表示应答信号(ACK)。
抄了这么多理论其实没什么用,因为 ESP32 带硬件 I2C,只要调用相关 API 即可,用起来非常简单。
SHT40温湿度传感器参数介绍
SHT40温湿度测试范围
参数 | 精度 |
---|---|
相对湿度精度 | ±1.5%RH |
温度精度 | 高达±0.1°C |
电源电压 | 1.08 V…3.6 V |
平均电流 | 0.4µA(平均速率1Hz) |
工作范围 | 0…100 %RH, -40…125 °C |
SHT40读写时序
硬件设计及原理
本实验板使用了 ESP32 的 I2C_0,下表是我们的程序 IO 的映射。
名称 | 功能 | 映射引脚 |
---|---|---|
SCL | 时钟 | IO22 |
SDA | 数据 | IO21 |
3V3 | 电源正极 | 3V3 |
GND | 电源负极 | GND |
软件设计
代码逻辑
代码编写
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_err.h"
#include "esp_log.h"
#include "driver/i2c.h"
#define I2C_MASTER_SCL 22 // I2C专用时钟线GPIO
#define I2C_MASTER_SDA 21 // I2C专用时钟线GPIO
#define I2C_MASTER_NUM 0 // I2C总线号
#define I2C_MASTER_FREQ_HZ 400*1000 // I2C总线频率
uint8_t c=0xFD;//传感器操作指令
void I2C_Intal()//I2C初始化
{
i2c_config_t conf =//i2c结构配置
{
.mode = I2C_MODE_MASTER, //I2C运行模式
.sda_io_num = I2C_MASTER_SDA, //SDA串口
.sda_pullup_en =GPIO_PULLUP_ENABLE, //SDA使能
.scl_io_num = I2C_MASTER_SCL, //SCL串口
.scl_pullup_en = GPIO_PULLUP_ENABLE, //SCL使能
.master.clk_speed = I2C_MASTER_FREQ_HZ //I2C总线频率
};
i2c_param_config(I2C_MASTER_NUM,&conf);//配置I2C绑定
i2c_driver_install(I2C_MASTER_NUM, I2C_MODE_MASTER, 0, 0, 0);//I2C安装
printf("i2c_driver_install is ok\n");
}
int I2C_ad()//寻找I2C子设备地址
{
int address;
for (address = 0x03; address < 0x77; address++)
{
i2c_cmd_handle_t cmd =i2c_cmd_link_create();//创建I2C句柄
i2c_master_start(cmd);//写启动信号到缓冲函数
i2c_master_write_byte(cmd,(address<<1) | I2C_MASTER_WRITE,I2C_MASTER_NACK);//写一个字节的命令道缓存函数
i2c_master_stop(cmd);//写停止信号到缓冲函数
esp_err_t ert = i2c_master_cmd_begin(I2C_MASTER_NUM,cmd,100);//I2C发送函数
if (ert == ESP_OK)
{
printf("Device found at address 0x%02X\n", address);//将寻找到的设备地址打印输出
}
i2c_cmd_link_delete(cmd);//删除I2C连接函数
vTaskDelay(10);
}
return address;
}
void I2C_read()//接收传感器数据
{
while (1)
{
//I2C地址的写入操作
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd,(0x44 << 1) | I2C_MASTER_WRITE ,I2C_MASTER_NACK);
i2c_master_write(cmd,&c,1,I2C_MASTER_NACK);
i2c_master_stop(cmd);
esp_err_t ert = i2c_master_cmd_begin(I2C_MASTER_NUM,cmd, 50 /portTICK_PERIOD_MS);
i2c_cmd_link_delete(cmd);
if (ert != ESP_OK)
{
ESP_LOGE("Tag:","%dI2C Write failed.....\n",ert);
}
vTaskDelay(50 /portTICK_PERIOD_MS);
//进行I2C的读取操作
uint8_t I2C_data[6]={0,0,0,0,0,0};//用于存储数据
cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd,(0x44 <<1) | I2C_MASTER_READ ,I2C_MASTER_NACK);
for (int i = 0; i < 6; i++)
{
i2c_master_read_byte(cmd,&I2C_data[i],I2C_MASTER_ACK);//将所读数据存储在数组
}
i2c_master_stop(cmd);
ert = i2c_master_cmd_begin(I2C_MASTER_NUM,cmd,50 / portTICK_PERIOD_MS);
i2c_cmd_link_delete(cmd);
if (ert == ESP_OK)
{
for (int i = 0; i < 6; i++)
{
printf(" %d ",I2C_data[i]);
}
printf("\n");
//温度、湿度的数值计算
float t_ticks = (uint16_t)I2C_data[0]*256+(uint16_t)I2C_data[1];
float rh_ticks = (uint16_t)I2C_data[3]*256+(uint16_t)I2C_data[4];
float temperature =-45+175.0*t_ticks / 65535.0;
float humidity =-6+125.0*rh_ticks / 65535.0;
ESP_LOGI("温度","%.2lf",temperature);
ESP_LOGI("湿度","%.2lf",humidity);
}
vTaskDelay(400);
}
i2c_driver_delete(I2C_MASTER_NUM);//删除I2C功能
}
void app_main()
{
I2C_Intal();//I2C初始化
I2C_ad();//I2C寻址
I2C_read();//接收数据消息
}
评论