I2C协议介绍
什么是I2C协议
IIC(Inter-Integrated Circuit)协议也称为I2C总线,是一种串行通信协议,通常用于连接低速外设。它由Philips(现在的NXP Semiconductors)公司于1980年代初开发,现在已经成为一个标准。IIC总线只需要两条数据线,分别是串行数据线(SDA)和串行时钟线(SCL),这使得它成为一种非常简单的接口。它适用基于芯片的通信,例如连接传感器、存储器或数字信号处理器等。
在I2C协议中,总线上有一个主设备和多个从设备。主设备掌控着总线上的通信过程,负责发起、控制、停止通信。而从设备则需要等待主设备的请求,接收或发送数据。主设备和从设备之间的数据交换采用帧格式,每个帧通常包含地址、数据和控制信息。主设备根据从设备的地址来选中要通信的设备,从设备则根据控制信息进行相应的操作。IIC协议可以支持多个从设备连接到同一个主设备,为系统设计提供了更大的灵活性。
I2C的硬件实现
I2C总线通常使用两种电压电平,即高电平(VH)和低电平(VL)。高电平为2.5V至5.5V,低电平为0V至0.3V;这些电压电平范围是根据I2C规范确定的。I2C总线有不同的传输速率可选,包括标准模式(100 kbps)、快速模式(400 kbps)以及高速模式。传输速率的选择取决于应用的需求和设备的支持能力。为避免信号冲突,微处理器(MCU)必须只能驱动SDA和 SCL在低电平,即开漏输出。设置为开漏模式主要是为了保护器件和防止干扰。
- 防止干扰:多个器件共享同一条数据线(SDA)和同一条时钟线(SCL),如果采用推挽输出模式,多个器件的输出将会叠加在数据线上,造成信号干扰,严重时会损坏器件或导致通信错误。而采用开漏输出模式,则各个器件的输出只有拉低数据线的部分,不会干扰彼此,从而提高了总线的可靠性和抗干扰能力。
- 防止短路:在开漏输出模式下,由于器件的输出只有拉低数据线的部分,如果两个或多个器件同时输出,也不会造成短路。而如果采用推挽输出模式,两个或多个器件同时输出时,可能会形成短路。比如主设备输出高电平,从设备输出低电平。
- 因设置为开漏模式,需要连接一个外部的上拉电阻(例如:10k)将信号提拉至高电平。故I2C总线中的SDA(数据线)和SCL(时钟线)通常都连接了上拉电阻,以确保逻辑高电平的稳定性。上拉电阻的阻值通常在2.2kΩ至10kΩ之间,具体取决于总线的电容负载和通信距离。
I2C总线的最大线缆长度和传输容量受到一定限制。在标准模式下,最大线缆长度大约在1米左右,而在快速模式下,最大线缆长度约为0.3米。此外,线缆上的总线容量也会对传输速率产生影响。
I2C数据传输
I2C只有两根通信线,因此它数据传输是基于时钟信号的。各个设备在时钟信号的控制下进行数据的收发操作。下面是I2C总线的几个重要的时序:
起始信号:SCL在高电平的状态下,SDA的电平由高转低,表示开始一次通信。
停止信号:SCL在高电平的状态下,SDA的电平由低转高,表示结束这次通信。主设备在发送停止信号后不能再向从设备发送任何数据,除非再次发送起始信号。
数据传输:主设备和从设备进行数据的传输,可以是一个或多个字节的数据,发送和接收都是基于地址选择的。
I2C还提供了一种称为“ACK/NACK”(应答/非应答)的确认机制。如果一个设备接收到数据,它将通过在SDA线上拉低电平来发送一个应答信号以通知发送方数据已被接收。相反,如果数据被损坏或未接收,接收设备将发送非应答信号。(在SDA上保持高电平)。
在IIC总线中,时钟线由主设备控制,每个数据位在时钟边沿更新,传输的最高速率取决于总线上最慢的设备。一般来讲,IIC总线的通信速率比较慢,通常在几百kbps的范围内。如果需要更高的传输速率,可以采用其他通信协议,如SPI协议、CAN协议等。
I2C的通信流程
I2C通信流程按照以下步骤进行:
- 主控向总线发送开始信号。
- 主控将要通信的设备地址和读写位(R/W)发送到总线上。
- 设备接收到地址后发送应答信号,主控接收到应答信号后发送数据或继续发送地址。
- 设备接收到数据后发送应答信号,主控接收到应答信号后可以继续发送数据或者停止通信。
- 主控向总线发送停止信号。
I2C 基本参数
速率: 地奇星的I2C总线支持有标准模式(100 kbps)、快速模式(400 kbps)、快速模式 Plus (1Mbps)和高速模式(3.4Mbps)四种传输模式。
器件地址: 每个设备都有唯一的7位或10位地址,可以通过地址选择来确定与谁进行通信。
总线状态: I2C总线有五种状态,分别是空闲状态、起始信号、结束信号、响应信号、数据传输。
数据格式: I2C总线有两种数据格式,标准格式和快速格式。标准格式是8位数据字节加上1位ack/nack(应答/非应答)位,快速格式允许两个字节同时传输。
由于SCL和SDA线是双向的,它们也可能会由于外部原因(比如线路中的电容等)出现电平误差,而从而导致通信出错。因此,在IIC总线中,通常使用上拉电阻来保证信号线在空闲状态下的电平为高电平。
地奇星硬件I2C图
BH1750 光照实验
注:工程是 uart 的工程基础上进行开发的。
首先我们这里需要打开 I3C的时钟,如下图所示:
接下来配置引脚 在 FSP Configuration -> Pins -> Peripherals -> Connectivity:I3C/IIC -> I3C/IIC 接下来我们需要配置PIN Group Select 为模式 B only,Operation Mode 选择 Custom,如下图所示:
接下来,添加I2C的模块, New Stack -> Connectivity -> I2C Master(r_iic_b_master) ,如下图所示:
接下来我们选择 g_i2c_master0 模块,然后点击属性,然后放大,如下图所示:
需要修改的内容如下图所示:
设置完成后,按下 Ctrl + S 进行保存,然后点击 Generate Project Content 进行工程生成。
程序编写
然后再 src 下创建一个 i2c 的文件夹,在里面创建 bsp_bh1750.c 与 bsp_bh1750.h 两个文件,在创建一个 Applay 的文件夹,在里面创建 app.c 与 app.h 两个文件如下图所示:
#include "Apply\app.h"
#include "i2c\bsp_bh1750.h"
#include "uart/bsp_uart.h"
void Run(void)
{
UART0_Init();
BH1750_Init();
printf("欢迎使用立创·地奇星RA6E2开发板\r\n");
printf("接下来开始读取光照实验:\r\n");
while(1)
{
uint32_t value = BH1750_read();
printf ("光照强度=%ld\r\n",value);
R_BSP_SoftwareDelay (1, BSP_DELAY_UNITS_SECONDS);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#ifndef __APP_H
#define __APP_H
#include "hal_data.h"
#include <stdio.h>
void Run(void);
#endif
2
3
4
5
6
7
8
9
#include "bsp_bh1750.h"
void BH1750_callback(i2c_master_callback_args_t *p_args)
{
//防止编译器报错
(void)p_args;
}
void BH1750_Init(void)
{
uint8_t cmd = 0x10; // BH1750的初始化命令(连续高分辨率模式测量)
fsp_err_t err = R_IIC_B_MASTER_Open(&I2C_BH1750_ctrl, &I2C_BH1750_cfg);
if (err != FSP_SUCCESS) {
printf("I2C初始化失败: %d\n", err);
return;
}
// 获得I2C地址
printf("配置的I2C地址: 0x%02lX\n", I2C_BH1750_cfg.slave);
// 发送BH1750的模式设置命令
err = R_IIC_B_MASTER_Write(&I2C_BH1750_ctrl, &cmd, 1, false);
if (err != FSP_SUCCESS) {
printf("BH1750模式设置失败: %d\n", err);
return;
}
R_BSP_SoftwareDelay(180, BSP_DELAY_UNITS_MILLISECONDS); // 等待传感器初始化
}
uint32_t BH1750_read(void)
{
uint8_t data[2] = {0};
uint16_t raw_lux = 0;
uint32_t lux = 0;
fsp_err_t err;
// 读取BH1750的2字节数据(最后一个参数应为false:读完后发送停止位)
err = R_IIC_B_MASTER_Read(&I2C_BH1750_ctrl, data, 2, false);
if (err != FSP_SUCCESS) {
printf("BH1750读取失败,错误码: %d\n", err);
return 0;
}
R_BSP_SoftwareDelay(180, BSP_DELAY_UNITS_MILLISECONDS); // 等待
raw_lux = (data[0] << 8) | data[1];
lux = (uint32_t)(raw_lux / 1.2f); // 转换为光照值
return lux;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#ifndef __BSP_BH1750_H
#define __BSP_BH1750_H
#include "hal_data.h"
#include "uart/bsp_uart.h"
#include <stdio.h>
void BH1750_Init(void);
uint32_t BH1750_read(void);
#endif
2
3
4
5
6
7
8
9
10
11
#include "hal_data.h"
#include "Apply\app.h"
FSP_CPP_HEADER
void R_BSP_WarmStart(bsp_warm_start_event_t event);
FSP_CPP_FOOTER
void hal_entry(void)
{
/* TODO: add your own code here */
Run();
#if BSP_TZ_SECURE_BUILD
/* Enter non-secure code */
R_BSP_NonSecureEnter();
#endif
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
实验现象
实物连接
注:串口模块需要自行连接。
实验结果