IIC协议

协议:
协议简单来说就是主机与从机双方约定一组动作,只要一方做了特定的动作,另一方就可以知道你要干什么,然后就可以给出特定的回复动作,多次重复就可以实现通信。

以下是我总结的IIC读写的步骤,仅供参考:
IIC 读数据:
发送起始信号
发送从机地址 - 发送(写入)
发送寄存器地址
起始信号
发送从机地址 - 接收(读取)
接收数据
发送停止信号

IIC 写数据:
发送起始信号
发送从机地址 - 发送(写入)
发送寄存器地址
发送数据
发送停止信号

软件模拟IIC实现与硬件IIC

由于在IIC总线的通信过程中,时钟信号由主设备产生,从设备根据时钟信号进行数据传输(即同步通信)。故可通过代码方便地模拟该协议,也即所谓的模拟IIC协议。
不过,对于异步通信来说,软件就没办法进行模拟了,比如常见的串口通信。

在软件模拟 IIC 时 GPIO 一般为开漏模式,支持线与功能;不过由于开漏模式无法输出真正的高电平,所以需要外部上拉(IIC的电平只是通信使用,所以负载强度不大;一般总线上认为,低于0.3Vdd为低电平,高于0.7Vdd为高电平)
推挽输出不能实现线与功能,因为如果两个输出引脚,一个输出高电平P-MOS管导通,一个输出低电平N-MOS管导通,则P-MOS管上方的高电平会经过P-MOS -> N-MOS -> GND,整个通路上没有外接电阻,因此电阻很小相当于高电平直接接到低电平造成了短路。

模拟 I2C 是用两条 GPIO 管脚的软件模拟的,将一个 GPIO 设置为数据线 SDA,另外一个设置为时钟线 SCL。
硬件 I2C 则是通过一个 I2C 控制器实现的,该控制器被建立在微控制器芯片或单独的 I2C 芯片中,通过集成的硬件内部逻辑和电路来控制时序和数据格式,实现 I2C 总线通信。
软件与硬件的IIC各有优缺点,可根据实际情况选用。

注:在使用软件模拟IIC时,其实IIC的IO口既可以配置为推挽输出也可以配置为开漏输出,不同之处在于当IO口配置为推挽输出时,发送和接收数据时需要切换IO口的输入输出模式,发送数据时需要将IO口切换为输出模式,接收数据时需要将IO口切换为输入模式。如果配置开漏输出则不需要切换IO口的输入输出模式。我们知道推挽输出不具有线与的功能,但是由于我们使用软件IIC时通常不会有多个设备连接到一个总线上的情况,所以只有一个从设备的话,也就不会有线与的情况发生了,可以使用推挽输出。但是我们需要根据发送数据和接收数据来切换IO口的工作模式。

模拟IIC代码实现

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
34
35
36
37
38
39
uint8_t I2C_Read_Data(uint8_t Address, uint8_t *Buffer, uint16_t Length)
{
My_I2C_Start();
My_I2C_WriteByte(Address); // 器件地址
if (My_I2C_GetACK())
{
My_I2C_Stop();
return 1;
}

My_I2C_WriteByte(0x00); // 寄存器地址
if (My_I2C_GetACK())
{
My_I2C_Stop();
return 1;
}

My_I2C_Start();
My_I2C_WriteByte(Address + 1);
if (My_I2C_GetACK())
{
My_I2C_Stop();
return 1;
}

while (1)
{
*Buffer++ = My_I2C_ReadByte();
if (--Length == 0)
{
My_I2C_PutACK(1);
break;
}
My_I2C_PutACK(0);
}

My_I2C_Stop();
return 0;
}

硬件IIC通信示例

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
void My_IIC_Init(void)
{
rcu_periph_clock_enable(RCU_I2C1);
rcu_periph_clock_enable(RCU_GPIOB);

gpio_af_set(GPIOB, GPIO_AF_1, GPIO_PIN_10);
gpio_af_set(GPIOB, GPIO_AF_1, GPIO_PIN_11);

gpio_mode_set(GPIOB, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_10); // SCL
gpio_output_options_set(GPIOB, GPIO_OTYPE_OD, GPIO_OSPEED_50MHZ, GPIO_PIN_10); // 复用开漏
gpio_mode_set(GPIOB, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_11); // SDA
gpio_output_options_set(GPIOB, GPIO_OTYPE_OD, GPIO_OSPEED_50MHZ, GPIO_PIN_11);

/* configure I2C clock */
i2c_clock_config(I2C1, 100000, I2C_DTCY_2);
/* configure I2C address */
i2c_mode_addr_config(I2C1, I2C_I2CMODE_ENABLE, I2C_ADDFORMAT_7BITS, OWN_ADDRESS7); // 主机地址

/* enable I2C1 */
i2c_enable(I2C1);
/* enable acknowledge */
i2c_ack_config(I2C1, I2C_ACK_ENABLE);
}

void I2C_Read_Data(void)
{
uint16_t timeout = 500;
/* 等IIC总线空闲 */
while (i2c_flag_get(I2C1, I2C_FLAG_I2CBSY));
/* 发送起始信号 */
i2c_start_on_bus(I2C1);
/* 等到起始信号发送完毕 */
while (!i2c_flag_get(I2C1, I2C_FLAG_SBSEND));

/* 发送从机地址 - 发送(写入) */
i2c_master_addressing(I2C1, SLA_ADDRESS7, I2C_TRANSMITTER); // 从机地址
/* 发送从机地址 - 若IIC从机在这儿会卡死,需添加返回操作 */
while (!i2c_flag_get(I2C1, I2C_FLAG_ADDSEND))
{
timeout--;
if (timeout == 0)
{
/* 发送结束信号 */
i2c_stop_on_bus(I2C1);
printf("No I2C Device\r\n");
return;
}
}
/* 清除 I2C_FLAG_ADDSEND 标志位 */
i2c_flag_clear(I2C1, I2C_FLAG_ADDSEND);

/* 发送寄存器地址 */
i2c_data_transmit(I2C1, REG_ADDRESS7); // 寄存器地址
while (!i2c_flag_get(I2C1, I2C_FLAG_BTC));

/* 发送起始信号 */
i2c_start_on_bus(I2C1);
/* 等到起始信号发送完毕 */
while (!i2c_flag_get(I2C1, I2C_FLAG_SBSEND));

/* 发送从机地址 - 接收(读取) */
i2c_master_addressing(I2C1, SLA_ADDRESS7, I2C_RECEIVER);
/* 匹配从机地址 */
while (!i2c_flag_get(I2C1, I2C_FLAG_ADDSEND));
/* 清除 ADDSEND 标志位 */
i2c_flag_clear(I2C1, I2C_FLAG_ADDSEND);

/* 读取数据 - 9个字节 */
for (int index = 0; index < 9; index++)
{
if (index == 8)
{
/* wait until the last data byte is received into the shift register */
while (!i2c_flag_get(I2C1, I2C_FLAG_BTC));
/* 失能应答 */
i2c_ack_config(I2C1, I2C_ACK_DISABLE);
}

/* wait until the RBNE bit is set */
while (!i2c_flag_get(I2C1, I2C_FLAG_RBNE));
/* read a data from I2C_DATA */
i2c_recv_buf[index] = i2c_data_receive(I2C1);
}

/* 发送结束信号 */
i2c_stop_on_bus(I2C1);
/* 等到结束信号发送完毕 */
while (I2C_CTL0(I2C1) & I2C_CTL0_STOP);

/* 使能应答 */
i2c_ack_config(I2C1, I2C_ACK_ENABLE);
}

补充

推挽输出

推挽(Push-Pull)通常用于需要强驱动能力的场景。
推挽输出既可以向负载灌电流,也可以从负载抽取电流。推拉式输出既提高电路的负载能力,又提高开关速度。
下面是一个典型的推挽输出电路:上面的三极管是NPN型三极管,下面的三极管是PNP型三极管,分别有以下两种情况,请留意控制端、输入端和输出端。

输出低电平:从负载拉电流
推挽输出_低
当Vin电压为V-时,下面的三极管有电流流出,Q4导通,Q3截止,于是电流从上往下流过。经过下面的P型三极管提供电流给负载(Rload),这就叫「挽」。

输出高电平:向负载灌电流
推挽输出_高
当Vin电压为V+时,上面的N型三极管控制端有电流输入,Q3导通,Q4截止,于是电流从上往下通过,提供电流给负载。经过上面的N型三极管提供电流给负载(Rload),这就叫「推」。

开漏输出(开集输出)

开漏输出指的是场效应管(可以类比晶体管来理解,对于晶体管来说,也就变成了开集电极输出)的漏极开路输出,只能输出低电平和高阻态(只有接上拉电阻才能输出高电平)。

以下图为例,当内部输出为1时,经过非门变为0,也就是场效应管的栅极电压为0,此时场效应管截止(相当于开路),那么单片机IO的输出是什么呢?是没办法确定的,注意并没有上拉电路。如果有上拉电阻呢,当然就是VCC,也就是逻辑1。当内部输出0时,栅极电压为1,此时场效应管导通,单片机IO与地连接,输出为0。
开漏输出

开漏结构(OD)对比推挽结构:开漏结构只有一个三极管或者MOS管,推挽结构则有两个。
之所以叫开漏,是因为MOS管分为三极:源极、栅极、漏极。漏极开路输出,所以叫开漏输出;
如果是三极管:基极、集电极、发射极,集电极开路输出,就叫开集输出(OC)。

浮空输入

顾名思义,浮空就是浮在空中,既不上拉也不下拉;
通俗讲就是让管脚什么都不接,浮空着,呈高阻态。
浮空最大的特点就是电压的不确定性,它可能是0V,也可能是VCC,还可能是介于两者之间的某个值(最有可能)完全由外部输入决定,引脚悬空的情况下,该端口的电平是不确定的。

用途:

  • 浮空可用来做ADC输入,这样可以减少上下拉电阻对结果的影响。
  • 用于外部按键输入。

高阻态

高阻状态是三态门电路的一种状态,三态为高电平、低电平、高阻态。
当处于高阻态时,无论该门的输入如何变化,都不会对其输出有影响。
高阻态近似为开路状态,控制信号无法控制引脚的电平,引脚测量电压可能为任意的电压值。

上拉 & 下拉

上拉就是将引脚通过一个电阻连接到VCC上;
上拉电阻的功能主要是为集电极开路输出型电路和开漏输出提供输出电流通道,将不确定的信号通过一个电阻钳位在高电平(可以结合上面的开漏输出来理解),电阻同时起限流作用。
所谓的强上拉、弱上拉,只是上拉电阻的阻值不同。

下拉就是将引脚通过一个电阻连接到GND上,将不确定的信号通过一个电阻钳位在低电平。

三极管 & 场效应管

三个极:
三极管是半导体基本元器件之一,具有电流放大作用;
共有三个极:发射极(Emitter)、基极(Base)和集电极(Collector);中间部分是基区,两侧部分是发射区和集电区,排列方式有PNP和NPN两种。
场效应管有三个极:源极(S),栅极(G),漏极(D),对应于晶体管的发射极(E),基极(B),以及集电极(C),排列方式也有P沟道和N沟道两种。

N-MOS的特性,Vgs 大于一定的值就会导通,适合用于源极接地时的情况(低端驱动)。
P-MOS的特性,Vgs 小于一定的值就会导通,适合用于源极接 VCC 时的情况(高端驱动)。

三极管与场效应管的基本工作原理:

  • 三极管‌:三极管是一种电流控制元件,其工作原理是通过控制输入电流来改变输出电流。三极管有NPN型和PNP型两种类型,电流方向不同‌。
  • 场效应管‌:场效应管是一种电压控制元件,通过控制输入电压来改变输出电流。场效应管分为结型场效应管和金属氧化物场效应管(MOS管),其中MOS管是最常见的类型‌。

三极管与场效应管的主要区别:

  1. 控制方式‌:三极管是电流控制元件,需要输入电流才能产生输出电流;而场效应管是电压控制元件,输入端电流极小,适用于低功耗应用‌。
  2. 输入阻抗‌:三极管的输入阻抗较小,而场效应管的输入阻抗较大,适用于高输入电阻的场合‌。
  3. 噪声性能‌:场效应管的噪声系数较低,适用于低噪声放大器的前置级‌。
  4. 温度特性‌:三极管是负温度系数器件,而场效应管是正温度系数器件,场效应管在高温下功耗增加,可能导致更高的温度‌。
  5. 开关频率‌:场效应管的开关频率高于三极管,适用于高速开关应用‌。

应用场景:

  • 三极管‌:适用于低频放大、开关电路和功率放大等应用。
  • 场效应管‌:适用于高频放大、低噪声放大、开关电路和低功耗电路等‌。

线与

线与:所有GPIO输出高就是高,只要有一个输出低,整条线上面的都是低,这就是“与”的意思。

参考资料

I2C推挽结构和开漏结构 - 电子发烧友

从IIC看推挽输出和开漏输出 - 知乎

推挽输出和开漏输出 - 博客园

推挽、开漏等概念总结 - STM32社区

三极管 - CSDN

认识场效应管MOSFET - 知乎