
大家好,我是良许。
在嵌入式开发中,I2C(IIC)总线是我们最常用的通信接口之一。
无论是读取传感器数据、控制EEPROM存储器,还是与各种外设通信,I2C总线都扮演着重要角色。
但很多初学者在画原理图或者调试I2C设备时,经常会遇到一个问题:为什么I2C总线的SDA和SCL线上必须要加上拉电阻?不加会怎么样?加多大的合适?
今天这篇文章,我就来详细讲解I2C总线上拉电阻的原理、作用以及选型方法,让大家彻底搞明白这个问题。
在讲上拉电阻之前,我们先要理解I2C总线的工作方式。
I2C是一种半双工、同步串行通信协议,只需要两根线就能实现多主机、多从机通信:
这里有个关键点:I2C总线采用开漏(Open-Drain)或开集(Open-Collector)输出结构。什么意思呢?
在开漏输出模式下,GPIO引脚内部只有一个NMOS管(或NPN三极管)。
这个管子只能做两件事:
注意,开漏输出无法主动输出高电平,它只能输出低电平或者高阻态。
这就是问题的关键所在。
你可能会问,为什么不用推挽输出(Push-Pull)呢?
推挽输出既能输出高电平,又能输出低电平,多方便啊!
原因很简单:I2C总线支持多主机架构。
想象一下,如果有两个主机同时往总线上发数据:
这时候VCC和GND直接短路,会烧毁芯片!这在硬件设计中是绝对不允许的。
而使用开漏输出就不会有这个问题:
这种"线与"逻辑完美解决了总线冲突问题,这也是I2C总线能够实现多主机通信的基础。
现在我们知道了,开漏输出无法主动输出高电平。
那总线怎么变成高电平呢?答案就是:靠上拉电阻。
上拉电阻一端连接VCC,另一端连接I2C总线。
当所有设备的NMOS都关断(高阻态)时,上拉电阻会把总线电平拉到VCC,形成高电平。
简单来说:
这样,I2C总线就能正常表示0和1两种状态了。
上拉电阻还有一个重要作用:改善信号质量。
I2C总线在传输数据时,信号需要在高低电平之间快速切换。
由于总线存在寄生电容(来自PCB走线、芯片引脚等),如果没有上拉电阻,总线电平会"飘"在不确定的状态,导致通信失败。
上拉电阻提供了一个确定的充电路径,让总线能够快速、稳定地回到高电平状态。
前面提到,I2C总线采用"线与"逻辑。具体来说:
这种特性使得I2C能够实现:
理解了上拉电阻的作用,接下来的问题是:应该选多大的电阻值?
这个问题没有固定答案,需要根据实际情况权衡。
阻值太大或太小都会有问题。
如果上拉电阻阻值太大(比如100kΩ),会导致:
充电速度慢:总线电容通过大电阻充电,上升沿变得很缓慢,信号边沿不够陡峭。这会限制I2C的通信速率,甚至导致通信失败。
抗干扰能力差:大电阻提供的驱动能力弱,总线容易受到外部干扰。
如果上拉电阻阻值太小(比如1kΩ),会导致:
功耗增加:设备输出低电平时,会有较大的电流从VCC经过上拉电阻流向GND,增加系统功耗。
以3.3V系统为例,如果上拉电阻是1kΩ,输出低电平时的电流为:

这个电流对于低功耗设备来说是不可接受的。
驱动能力要求高:小电阻意味着设备需要更强的驱动能力来拉低总线,可能超出芯片的灌电流能力。
上拉电阻的选择需要考虑以下因素:
总线电容:包括PCB走线电容、芯片引脚电容等,通常在10pF到400pF之间。
通信速率:I2C有几种速率模式:
上升时间要求:I2C协议规定了信号上升时间的最大值:
根据RC充电公式,上拉电阻的最大值可以这样估算:

其中tr是允许的最大上升时间,Cbus是总线电容。
举个例子,如果总线电容是100pF,通信速率是400kbps(快速模式),上升时间要求不超过300ns:

所以上拉电阻应该选择小于3.5kΩ的值。
根据实际经验,以下是不同应用场景的推荐阻值:
1. 标准模式(100kbps)
2. 快速模式(400kbps)
3. 快速模式+(1Mbps)
在实际项目中,4.7kΩ是最常用的阻值,它在大多数情况下都能工作良好。
如果遇到通信问题,可以根据示波器测量的波形来调整。
下面给一个STM32使用HAL库配置I2C的代码示例,帮助大家理解实际应用:
// I2C初始化代码
I2C_HandleTypeDef hi2c1;
void MX_I2C1_Init(void)
{
hi2c1.Instance = I2C1;
hi2c1.Init.ClockSpeed = 400000; // 400kbps快速模式
hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;
hi2c1.Init.OwnAddress1 = 0;
hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
hi2c1.Init.OwnAddress2 = 0;
hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
if (HAL_I2C_Init(&hi2c1) != HAL_OK)
{
Error_Handler();
}
}
// GPIO配置(在HAL_I2C_MspInit中调用)
void HAL_I2C_MspInit(I2C_HandleTypeDef* i2cHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(i2cHandle->Instance==I2C1)
{
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_I2C1_CLK_ENABLE();
// PB6: I2C1_SCL
// PB7: I2C1_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_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF4_I2C1;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
}
}
// 读取I2C设备数据示例
uint8_t I2C_ReadByte(uint8_t DevAddress, uint8_t RegAddress)
{
uint8_t data;
// 写寄存器地址
HAL_I2C_Master_Transmit(&hi2c1, DevAddress, &RegAddress, 1, 100);
// 读取数据
HAL_I2C_Master_Receive(&hi2c1, DevAddress, &data, 1, 100);
return data;
}注意代码中的关键配置:
GPIO_MODE_AF_OD:配置为开漏输出模式GPIO_NOPULL:不使用内部上拉,因为外部已经有上拉电阻问题1:I2C通信失败,设备无响应
可能原因:
解决方法:用示波器观察SCL和SDA波形,检查上升沿是否足够陡峭。
问题2:通信不稳定,偶尔出错
可能原因:
解决方法:尝试调整上拉电阻阻值,优化PCB布线,在VCC附近加滤波电容。
使用示波器观察I2C波形时,重点关注:
如果上升沿过缓,说明上拉电阻太大或总线电容太大;如果有明显的振铃,可能是阻抗不匹配或干扰问题。
I2C总线必须加上拉电阻,这是由其开漏输出特性决定的。上拉电阻的作用包括:
上拉电阻的阻值选择需要权衡多个因素,包括通信速率、总线电容、功耗要求等。
一般来说,4.7kΩ是最常用的阻值,适用于大多数应用场景。
如果遇到通信问题,可以根据实际波形测量结果进行调整。
在实际项目中,建议在原理图设计阶段就预留好上拉电阻位置,并且选择0402或0603封装的贴片电阻,方便后期调试时更换不同阻值。
同时,注意PCB布线时尽量缩短I2C走线长度,减小寄生电容,这样可以获得更好的信号质量和更高的通信速率。
希望这篇文章能帮助大家彻底理解I2C总线上拉电阻的原理和应用。
如果你在实际项目中遇到I2C相关问题,不妨从检查上拉电阻开始排查!
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。