
大家好,我是良许。
在嵌入式开发中,IIC(I2C)总线可以说是最常用的通信协议之一了。
无论是读取传感器数据、控制EEPROM存储器,还是与各种外设进行通信,IIC总线都扮演着重要角色。
但很多初学者在使用IIC时,往往只关注软件层面的时序和协议,却忽略了硬件层面的关键设计。
今天我就来聊聊IIC总线硬件部分的两个核心要点:开漏输出和上拉电阻。
理解了这两点,你才能真正掌握IIC总线的精髓。
在深入讲解之前,我们先简单回顾一下IIC总线的基本构成。
IIC总线只需要两根信号线就能实现多主机、多从机之间的通信,这两根线分别是:
一条IIC总线上可以挂载多个设备,每个设备都有唯一的地址。
这种简洁的设计让IIC总线在嵌入式系统中广受欢迎,特别是在PCB布线空间有限的场景下。
但问题来了:多个设备共用同一根数据线和时钟线,它们是如何避免冲突的呢?这就要说到IIC总线硬件设计的核心机制了。
开漏输出(Open-Drain)是IIC总线最核心的硬件特性。
要理解开漏输出,我们先来看看常见的GPIO输出模式。
在普通的推挽输出(Push-Pull)模式下,GPIO引脚可以主动输出高电平(通过上管导通)或低电平(通过下管导通)。
这种模式下,引脚能够提供较强的驱动能力,但有个致命问题:如果两个推挽输出的引脚连接在一起,一个输出高电平,另一个输出低电平,就会造成短路,可能烧毁芯片。
而开漏输出则不同,它的内部结构只有一个下拉的NMOS管,没有上拉的PMOS管。这意味着:
这种"只能拉低,不能拉高"的特性,正是开漏输出的精髓所在。
你可能会问:只能拉低不能拉高,这不是很鸡肋吗?恰恰相反,这正是IIC总线能够实现多设备共享总线的关键。
第一个优势:线与逻辑
多个开漏输出连接在同一根线上时,会形成"线与"(Wired-AND)逻辑。
只要有任何一个设备输出低电平,整条总线就是低电平;只有当所有设备都输出高阻态时,总线才能被上拉电阻拉到高电平。
这种特性在IIC总线中至关重要。
比如在多主机系统中,如果两个主机同时发送数据产生冲突,通过检测总线电平,主机可以发现冲突并进行仲裁。
发送"1"的主机如果检测到总线为"0",就知道有其他主机在发送数据,会主动放弃总线控制权。
第二个优势:电平转换
开漏输出配合上拉电阻,可以轻松实现不同电压域之间的电平转换。
比如一个3.3V的MCU和一个5V的传感器通信,只需要将上拉电阻接到5V电源,就能实现电平匹配。
3.3V的MCU输出低电平时可以将总线拉低,输出高阻态时总线被上拉到5V,这个5V电平不会损坏MCU(因为MCU引脚是高阻态,没有电流流入)。
第三个优势:避免总线冲突
在推挽输出模式下,如果两个设备同时驱动总线,一个输出高一个输出低,就会造成短路。
而开漏输出永远不会主动输出高电平,最多只是高阻态,因此不会产生短路风险。
在STM32中配置IIC引脚为开漏输出非常简单。
使用HAL库的话,代码如下:
void MX_I2C1_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* 使能GPIOB时钟 */
__HAL_RCC_GPIOB_CLK_ENABLE();
/* 配置IIC引脚:PB6(SCL), PB7(SDA) */
GPIO_InitStruct.Pin = GPIO_PIN_6 | GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_AF_OD; // 复用开漏输出
GPIO_InitStruct.Pull = GPIO_NOPULL; // 不使用内部上下拉
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF4_I2C1;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
/* 配置IIC外设 */
hi2c1.Instance = I2C1;
hi2c1.Init.ClockSpeed = 100000; // 100kHz标准速率
hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;
hi2c1.Init.OwnAddress1 = 0;
hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
HAL_I2C_Init(&hi2c1);
}注意代码中的 GPIO_MODE_AF_OD,这就是配置为复用功能的开漏输出模式。
同时 GPIO_NOPULL 表示不使用芯片内部的上下拉电阻,因为我们需要外部上拉电阻。
前面提到,开漏输出只能拉低电平,不能主动输出高电平。
那么高电平从哪里来呢?答案就是上拉电阻。
上拉电阻一端连接到电源(通常是VCC),另一端连接到IIC总线。
当所有设备的开漏输出都处于高阻态时,上拉电阻会将总线"拉"到高电平。
当任何一个设备输出低电平时,由于低电平的驱动能力远强于上拉电阻,总线会被拉到低电平。
可以把上拉电阻想象成一根弹簧,总是试图把总线拉到高电平。
而开漏输出就像一只手,需要的时候可以把总线按下去(拉低),松开手(高阻态)时弹簧就会把总线弹回高电平。
上拉电阻的阻值选择是个技术活,选大了选小了都不行。
阻值太小的问题:
如果上拉电阻太小(比如1kΩ),虽然可以提供很强的上拉能力,但会带来两个问题:
阻值太大的问题:
如果上拉电阻太大(比如100kΩ),上拉能力会变弱,带来的问题是:
合适的阻值范围:
一般来说,IIC总线的上拉电阻推荐范围是:
最常用的值是4.7kΩ,这是一个经过实践检验的经验值,在大多数应用场景下都能良好工作。
如果你想精确计算上拉电阻的阻值,可以使用以下公式。首先需要确定总线电容 Cbus,它包括:
假设IIC总线时钟频率为 fSCL,上升时间要求为 tr(标准模式下最大1000ns,快速模式下最大300ns),则上拉电阻的最大值为:

同时,为了保证足够的驱动能力,上拉电阻的最小值需要满足:

其中 VOL(max) 是输出低电平的最大值(通常0.4V),IOL是开漏输出的最大吸收电流(查阅芯片手册)。
举个实际例子,假设:
则:

因此上拉电阻应该选择在1kΩ到11.8kΩ之间,选择4.7kΩ是非常合适的。
在实际应用中,有时候会遇到多个模块都带有上拉电阻的情况。
比如你的主板上有上拉电阻,外接的传感器模块上也有上拉电阻。
这时候多个电阻会并联,等效电阻会变小。
两个电阻并联的等效电阻计算公式为:

比如两个4.7kΩ的电阻并联,等效电阻为:

这个值仍然在合理范围内,但如果并联的电阻太多,等效电阻可能会过小,导致功耗增加。
因此在设计时,建议只在主板上放置上拉电阻,外接模块上不要再加上拉电阻。
如果模块已经有上拉电阻,可以考虑用0欧电阻或跳线帽来选择性地启用。
上拉电阻应该尽量靠近主控芯片放置,而不是分散在各个从设备附近。
这样可以减少总线的寄生电容,提高信号质量。
在多层PCB中,建议将IIC走线放在内层,并在下方铺设完整的地平面,以减少干扰。
IIC总线本来是为板级通信设计的,传输距离通常在几厘米到几十厘米之间。
如果需要长距离传输(超过1米),需要特别注意:
在调试IIC通信问题时,可以用示波器观察SCL和SDA信号。正常情况下应该看到:
如果上升沿太慢,说明上拉电阻太大或总线电容太大;如果有振铃,可能需要增加串联电阻或并联电容进行阻尼。
有时候我们需要用GPIO模拟IIC(比如硬件IIC引脚被占用了),这时候也要配置为开漏输出。
示例代码如下:
/* 初始化模拟IIC的GPIO */
void Soft_I2C_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOB_CLK_ENABLE();
/* 配置SCL和SDA为开漏输出 */
GPIO_InitStruct.Pin = I2C_SCL_PIN | I2C_SDA_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD; // 开漏输出
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(I2C_GPIO_PORT, &GPIO_InitStruct);
/* 初始状态设为高电平(实际是高阻态) */
HAL_GPIO_WritePin(I2C_GPIO_PORT, I2C_SCL_PIN, GPIO_PIN_SET);
HAL_GPIO_WritePin(I2C_GPIO_PORT, I2C_SDA_PIN, GPIO_PIN_SET);
}
/* 读取SDA电平 */
uint8_t I2C_SDA_Read(void)
{
return HAL_GPIO_ReadPin(I2C_GPIO_PORT, I2C_SDA_PIN);
}
/* 设置SDA为低电平 */
void I2C_SDA_Low(void)
{
HAL_GPIO_WritePin(I2C_GPIO_PORT, I2C_SDA_PIN, GPIO_PIN_RESET);
}
/* 设置SDA为高电平(高阻态) */
void I2C_SDA_High(void)
{
HAL_GPIO_WritePin(I2C_GPIO_PORT, I2C_SDA_PIN, GPIO_PIN_SET);
}注意在读取SDA电平时,要先将SDA设为高阻态(输出高电平),然后再读取引脚状态。
这样才能正确读取从设备发送的应答信号。
IIC总线的硬件设计看似简单,实则蕴含着精妙的设计思想。
开漏输出和上拉电阻这两个关键点,共同构成了IIC总线多设备共享、双向通信的基础。
开漏输出提供了"线与"逻辑,使得多个设备可以安全地共享同一根总线,避免了总线冲突的风险。
而上拉电阻则为开漏输出提供了高电平,同时还能实现电平转换、限制电流等功能。
两者配合,才能让IIC总线稳定可靠地工作。
在实际应用中,正确选择上拉电阻的阻值、合理布局PCB、注意信号完整性,都是保证IIC通信质量的关键。
希望通过今天的讲解,能让大家对IIC总线有更深入的理解,在以后的项目中少走弯路。
如果你在使用IIC总线时遇到通信不稳定、速率上不去等问题,不妨从硬件层面入手,检查一下是不是开漏输出配置不对,或者上拉电阻选择不合适。
很多时候,硬件问题比软件问题更隐蔽,但一旦找到根源,解决起来反而更简单。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。