I2C 被应用在构造简单且可以牺牲传输速度来降低制造成本的外设上。

本篇幅在此不介绍 IElec 的原理,如若想要了解原理请看目录中其他知识板块 - IElec集成电路总线。

本篇幅暂时采用软件 IElec 的模式来进行代码编写,HAL库以及硬件 IElec 将陆续更新。

IElec 的特性

IElec 是同步传输方式,具有强制性的起始信号、停止信号、ACK响应信号、7位的从机地址。但是时钟拉伸(每一个时钟脉冲的时间)、软件复位等可以用户自定义,还可以选择10位从机地址的设备,这解决了IElec只能接128个设备的限制问题。并且在挂载多个从机的情况下,可以设置起始字节(start byte)。

IElec 数据有效性

由以下时序图可知,如果要数据能够有效传输,要先使数据线稳定,换个说法就是要先发数据,再给时钟脉冲(上升沿),当SCL为低电平时传输的数据允许变更。
并且SDA线上传输的每个字节必须是8位长。每次传输可以传输的字节数不受限制。每个字节后面必须有一个确认位。

IElec 完整的数据传输时序图

如图是 IElec 的一个完整的数据传输的时序图,在这种传输中,就可以实现读/写格式的各种组合。

IElec 信号类型

红色方框处分别为起始信号、停止信号。

  1. 起始信号的产生过程: 保持 SCL 为高电平, SDA 产生一个下降沿,然后 SCL 拉低,一个 START 信号产生:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* @brief IElec Start
*
*/
void IElec_Start(void)
{
SDA_OUT();
HAL_GPIO_WritePin(GPIOB,SDA_Pin,GPIO_PIN_SET); //数据线拉高
HAL_GPIO_WritePin(GPIOB,SCL_Pin,GPIO_PIN_SET); //时钟线拉高
delay_us(4);
HAL_GPIO_WritePin(GPIOB,SDA_Pin,GPIO_PIN_RESET);//延时4us左右后拉低数据线
delay_us(4);
HAL_GPIO_WritePin(GPIOB,SCL_Pin,GPIO_PIN_RESET);//再延时4us左右后拉低时钟线
}
  1. 停止信号的产生过程:保持 SCL 为高电平, SDA 产生一个上升沿,一个 STOP 信号产生。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* @brief IElec Stop
*
*/
void IElec_Stop(void)
{
SDA_IN();
HAL_GPIO_WritePin(GPIOB,SCL_Pin,GPIO_PIN_RESET); //时钟线拉低
HAL_GPIO_WritePin(GPIOB,SDA_Pin,GPIO_PIN_RESET); //数据线拉低
delay_us(4);
HAL_GPIO_WritePin(GPIOB,SCL_Pin,GPIO_PIN_SET); //时钟线拉高,保持4us左右
delay_us(4);
HAL_GPIO_WritePin(GPIOB,SDA_Pin,GPIO_PIN_SET); //数据线拉高,保持4us左右
delay_us(4);
}
  1. 应答信号的产生过程: SCL 在高电平期间 SDA 始终处于低电平 ( SCL 保持时间 <= SDA 保持时间),同时此信号需要在传输完毕一个字节后发送
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* @brief Master send ACK signal
*
*/
void IElec_ACK(void)
{
SDA_OUT();
HAL_GPIO_WritePin(GPIOB,SCL_Pin,GPIO_PIN_RESET); //时钟线拉低
delay_us(2);
HAL_GPIO_WritePin(GPIOB,SDA_Pin,GPIO_PIN_RESET); //数据线拉低
delay_us(2);
HAL_GPIO_WritePin(GPIOB,SCL_Pin,GPIO_PIN_SET); //时钟线拉高,保持4us左右
delay_us(5);
HAL_GPIO_WritePin(GPIOB,SCL_Pin,GPIO_PIN_RESET); //时钟线拉低
delay_us(1);
}
  1. 非应答信号的产生过程: SCL 在高电平期间 SDA 始终处于高电平 (SCL 保持时间 <= SDA 保持时间),需要在传输完毕一个字节后发送
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* @brief Master do not send ACK signal
*
*/
void IElec_NACK(void)
{
SDA_OUT();
HAL_GPIO_WritePin(GPIOB,SCL_Pin,GPIO_PIN_RESET); //时钟线拉低
delay_us(2);
HAL_GPIO_WritePin(GPIOB,SDA_Pin,GPIO_PIN_SET); //数据线拉高
delay_us(2);
HAL_GPIO_WritePin(GPIOB,SCL_Pin,GPIO_PIN_SET); //时钟线拉高,保持4us左右
delay_us(5);
HAL_GPIO_WritePin(GPIOB,SCL_Pin,GPIO_PIN_RESET); //时钟线拉低
delay_us(1);
}

IElec 应答检测

关于 IElec 的应答检测有如下机制:

  • SCL为高电平的时候可以读取SDA的状态,因此可以将SDA模式切换为输入模式,读取SDA引脚状态,0位应答,1位非应答
  • SCL为低电平允许数据发生变化
  • SDA 为高电平时,可以占用总线,此时将 SDA 拉低,开始通信;为低电平时, SDA 已经被占用。
  • SCL 为高电平时,要求数据稳定(数据的有效时间足够长);为低电平时,允许数据改变。
1
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
/**
* @brief Master wait for ACK from the low Elec
*
* @return uint8_t
*/
uint8_t IElec_WaitACK(void)
{
uint8_t t = 200;
SDA_OUT();
HAL_GPIO_WritePin(GPIOB,SDA_Pin,GPIO_PIN_SET);
delay_us(1);
HAL_GPIO_WritePin(GPIOB,SCL_Pin,GPIO_PIN_RESET);
delay_us(1);
SDA_IN();
delay_us(1);
while (HAL_GPIO_ReadPin(GPIOB,SDA_Pin))
{
/* code */
t --;
delay_us(1);
if (t==0)
{
HAL_GPIO_WritePin(GPIOB,SCL_Pin,GPIO_PIN_RESET);
return 1;
}
delay_us(1);
}
delay_us(1);
HAL_GPIO_WritePin(GPIOB,SCL_Pin,GPIO_PIN_SET);
delay_us(1);
HAL_GPIO_WritePin(GPIOB,SCL_Pin,GPIO_PIN_RESET);
return 0;
}

发送/接收数据

发送数据
发送数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* @brief Send a byte
*
* @param byte
*/
void IElec_SendByte(uint8_t byte)
{
uint8_t bitcnt;
SDA_OUT();
HAL_GPIO_WritePin(GPIOB,SCL_Pin,GPIO_PIN_RESET);
for (bitcnt=0;bitcnt<8;bitcnt++)
{
if (byte&0x80) {HAL_GPIO_WritePin(GPIOB,SDA_Pin,GPIO_PIN_SET);}
else {HAL_GPIO_WritePin(GPIOB,SDA_Pin,GPIO_PIN_RESET);}
byte = byte << 1;
delay_us(2);
HAL_GPIO_WritePin(GPIOB,SCL_Pin,GPIO_PIN_SET);
delay_us(2);
HAL_GPIO_WritePin(GPIOB,SCL_Pin,GPIO_PIN_RESET);
delay_us(2);
}
}

将 SDA 切换为输入模式。拉高 SCL 电平,即可读取数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* @brief Receive a byte
*
* @return uint8_t
*/
uint8_t IElec_ReceiveData(void)
{
uint8_t rece;
uint8_t bitcnt;
rece = 0;
SDA_IN();
delay_us(1);
for (bitcnt = 0; bitcnt < 8; bitcnt++)
{
/* code */
HAL_GPIO_WritePin(GPIOB,SCL_Pin,GPIO_PIN_RESET);
delay_us(2);
HAL_GPIO_WritePin(GPIOB,SCL_Pin,GPIO_PIN_SET);
rece = rece << 1;
if (HAL_GPIO_ReadPin(GPIOB,SDA_Pin)) {rece = rece | 1;}
delay_us(1);
}
HAL_GPIO_WritePin(GPIOB,SCL_Pin,GPIO_PIN_RESET);
return rece;
}

进阶内容

IElec 发展背景

随着大规模集成电路技术的发展,把 CPU 和一个单独工作系统所必需的 ROM、RAM、I/O 端口、A/D、D/A 等外围电路集成在一个单片内而制成的单片机或微控制器愈来愈方便。目前,世界上许多公司生产单片机,品种很多。其中包括各种字长的 CPU,各种容量的 ROM、RAM 以及功能各异的 I/O 接口电路等等,但是,单片机的品种规格仍然有限,所以只能选用某种单片机来进行扩展。扩展的方法有两种:一种是并行总线,另一种是串行总线。由于串行总线的连线少,结构简单,往往不用专门的母板和插座而直接用导线连接各个设备。因此,采用串行线可大大简化系统的硬件设计。PHILIPS公司早在十几年前就推出了I2C串行总线,利用该总线可实现多主机系统所需的裁决和高低速设备同步等功能。因此,这是一种高性能的串行总线。

IElec 简介

IElec(Inter-Integrated Circuit)总线是一种由NXP(原PHILIPS)公司开发的两线式串行总线,用于连接微控制器及其外围设备。多用于主控制器和从器件间的主从通信,在小数据量场合使用,传输距离短,任意时刻只能有一个主机等特性。在 CPU 与被控 Elec 之间、Elec 与 Elec 之间进行双向传送,高速 IElec 总线一般可达 400kbps 以上。

注意: 这里要注意IElec是为了与低速设备通信而发明的,所以IElec的传输速率比不上SPI。

IElec 总线拓扑图
IElec 总线拓扑图

IElec 物理层

IElec 一共只有两根总线: 一条是双向的串行数据线 SDA ,另一条是串行时钟线 SCL

  • SDA(Serial Data)是数据线,用于双向传输高低电平信号(即逻辑“0”,逻辑“1”)。
  • SCL(Serial Clock Line)是时钟线,用于发送基准时钟信号,控制数据发送的时序。

所有接到 I2C 总线设备上的串行数据 SDA 都连接到总线的 SDA 上,各设备的时钟线 SCL 接到总线的 SCL 上。 I2C 总线上的每个设备都有自己唯一的一个地址,来确保不同设备之间访问的准确性。

特点

通常我们为了方便把 IElec 设备分为主设备和从设备,基本上谁控制时钟线(即控制 SCL 的电平高低变换)谁就是主设备。

  • IElec 主设备功能: 主要产生时钟,产生起始信号和停止信号
  • IElec 从设备功能: 可编程的 IElec 地址检测,停止位检测
  • IElec 的一个优点是它支持多主控,其中任何一个能够进发送和接收的设备都可以成为主设备。一个主控能够控制信号的传输和时钟频率。当然,在任何时间电上只能有一个主控。
  • 支持不同速率的通讯速度,标准速度(最高速度 100KHz),快速(最高 400KHz)
  • SCL 和 SDA 都需要接上拉电阻(大小由速度和容性负载决定,一般在3.3K ~ 10K之间)保证数据的稳定性,减少干扰。
  • IElec 是半双工,而不是全双工,同一时间只可以单向通信
  • 为了避免总线信号的混乱,要求各设备连接到总线的输出端时必须是漏极开路(OD)输出或集电极开路(OC)输出。

高阻态

漏极开路(Open Drain)即高阻状态,适用于输入/输出,其可独立输入/输出低电平和高阻状态,若需要产生高电平,则需使用外部上拉电阻
高阻状态:高阻状态是三态门电路的一种状态。逻辑门的输出除有高、低电平两种状态外,还有第三种状态——高阻状态的门电路。电路分析时高阻态可做开路理解。
为了避免总线信号的混乱,IElec的空闲状态只能有外部上拉, 而此时空闲设备被拉到了高阻态,也就是相当于断路, 整个IElec总线只有开启了的设备才会正常进行通信,而不会干扰到其他设备。

器件地址

每一个 IElec 器件都有一个器件地址,有的器件地址在出厂时地址就设定好了,用户不可以更改,比如 OV7670 的地址为 0x42。有的器件例如 EEPROM,前四个地址已经确定为 1010,后三个地址是由硬件链接确定的,所以一IElec 总线最多能连 8 个 EEPROM 芯片。

总结

IElec 总线在物理连接上非常简单,分别由 SDA (串行数据线)和 SCL (串行时钟线)及上拉电阻组成。通信原理是通过对 SCL 和 SDA 线高低电平时序的控制,来产生 I2C 总线协议所需要的信号进行数据的传递。在总线空闲状态时, SCL 和 SDA 被上拉电阻 Rp 拉高,使 SDA 和 SCL 线都保持高电平。

  • 通信方式: I2C 通信方式为半双工,只有一根 SDA 线,同一时间只可以单向通信, 485 也为半双工, SPI 和 UART 通信为全双工。
  • 主从机: 主机就是负责整个系统的任务协调与分配,从机一般是通过接收主机的指令从而完成某些特定的任务,主机和从机之间通过总线连接,进行数据通讯。

IElec 协议层

I2C 总线在传送数据过程中共有三种类型信号, 它们分别是:开始信号结束信号应答信号

  • 开始信号: SCL 为高电平时, SDA 由高电平向低电平跳变,开始传送数据。
  • 结束信号: SCL 为高电平时, SDA 由低电平向高电平跳变,结束传送数据。
  • 应答信号: 接收数据的 Elec 在接收到 8Bit 数据后,向发送数据的 Elec 发出特定的低电平脉冲,表示已收到数据。 CPU 向受控单元发出一个信号后,等待受控单元发出一个应答信号, CPU 接收到应答信号后根据实际情况做出是否继续传递信号的按段。若未收到应答信号,由判断为受控单元出现故障。

其中,起始信号是必需的,结束信号和应答信号都可不要。

IElec 时序图
IElec 时序图

协议流程

初始状态

因为 IElec 的 SCL 和SDA 都需要接上拉电阻,保证空闲状态的稳定性。所以IElec总线在空闲状态下 SCL 和 SDA 都保持高电平

开始信号

SCL 保持高电平, SDA 由高电平变为低电平后,延时(>4.7us), SCL 变为低电平。

开始信号
开始信号
停止信号

SCL 保持高电平(>4us), SDA 由低电平变为高电平(>4.7us)。
.

停止信号
停止信号

在起始条件产生后,总线处于忙状态,由本次数据传输的主从设备独占,其他I2C器件无法访问总线;而在停止条件产生后,本次数据传输的主从设备将释放总线,总线再次处于空闲状态。

数据有效性

IElec 信号在数据传输过程中,当 SCL=1 高电平时,数据线 SDA 必须保持稳定状态,不允许有电平跳变,只有在时钟线上的信号为低电平期间,数据线上的高电平或低电平状态才允许变化。
SCL=1 时 数据线 SDA 的任何电平变换会看做是总线的起始信号或者停止信号。
也就是在 IElec 传输数据的过程中, SCL 时钟线会频繁的转换电平,以保证数据的传输。

应答信号

每当主机向从机发送完一个字节的数据,主机总是需要等待从机给出一个应答信号,以确认从机是否成功接收到了数据,主机SCL拉高,读取从机SDA的电平,为低电平表示产生应答。

  • 应答信号为低电平时,规定为有效应答位(ACK,简称应答位),表示接收器已经成功地接收了该字节;
  • 应答信号为高电平时,规定为非应答位(NACK),一般表示接收器接收该字节没有成功。

**每发送一个字节(8个bit)**在一个字节传输的8个时钟后的第九个时钟期间,接收器接收数据后必须回一个ACK应答信号给发送器,这样才能进行数据传输。应答出现在每一次主机完成8个数据位传输后紧跟着的时钟周期,低电平0表示应答,1表示非应答。

数据传送

SDA 线上的数据在 SCL 时钟“高”期间必须是稳定的,只有当 SCL 线上的时钟信号为低时,数据线上的“高”或“低”状态才可以改变。输出到 SDA 线上的每个字节必须是 8 位,数据传送时,先传送最高位(MSB),每一个被传送的字节后面都必须跟随一位应答位(即一帧共有9位)。

当一个字节按数据位从高位到低位的顺序传输完后,紧接着从设备将拉低SDA线,回传给主设备一个应答位ACK, 此时才认为一个字节真正的被传输完成 ,如果一段时间内没有收到从机的应答信号,则自动认为从机已正确接收到数据。

IElec 写数据

写数据
写数据

多数从设备的地址为7位或者10位,一般都用七位。

八位设备地址=7位从机地址+读/写地址 - 再给地址添加一个方向位位用来表示接下来数据传输的方向:

  • 0 表示主设备向从设备(write)写数据
  • 1 表示主设备向从设备(read)读数据

IElec 的每一帧数据由 9bit 组成,如果是发送数据,则包含 8bit 数据 + 1bit ACK ,如果是设备地址数据,则 8bit 包含 7bit 设备地址 1bit 方向。

在起始信号后必须传送一个从机的地址(7位) 1~7 位为 7 位接收器件地址,第 8 位为读写位,用“0”表示主机发送数据(W),“1”表示主机接收数据(R),第 9 位为 ACK 应答位,紧接着的为第一个数据字节,然后是一位应答位,后面继续第 2 个数据字节。

IElec 读数据

  • 主机首先产生START信号
  • 然后紧跟着发送一个从机地址,注意此时该地址的第8位为0,表明是向从机写命令,
  • 这时候主机等待从机的应答信号(ACK)
  • 当主机收到应答信号时,发送要访问的地址,继续等待从机的应答信号
  • 当主机收到应答信号后,主机要改变通信模式(主机将由发送变为接收,从机将由接收变为发送)所以主机重新发送一个开始start信号,然后紧跟着发送一个从机地址,注意此时该地址的第8位为1,表明将主机设置成接收模式开始读取数据
  • 这时候主机等待从机的应答信号,当主机收到应答信号时,就可以接收1个字节的数据,当接收完成后,主机发送非应答信号,表示不在接收数据
  • 主机进而产生停止信号,结束传送过程

软件IElec和硬件IElec

IElec 的运作方式分为软件 IElec 和硬件 IElec
软件 IElec:软件 IElec 通信指的是用单片机的两个 I/O 端口模拟出来的 IElec ,用软件控制管脚状态以模拟 I2C 通信波形,软件模拟寄存器的工作方式。
硬件 IElec:一块硬件电路,硬件 I2C 对应芯片上的 I2C 外设,有相应 I2C 驱动电路,其所使用的 I2C 管脚也是专用的,硬件(固件)I2C 是直接调用内部寄存器进行配置。

硬件 I2C 的效率要远高于软件的,而软件 2C 由于不受管脚限制,接口比较灵活。

此间车厢已使用  次 |   人乘坐过此趟开往世界尽头的列车