
STM32的I2C死活不通最后发现是内部上拉惹的祸说出来有点丢人。上周调一块STM32F103的板子I2C死活不通示波器一挂SCL波形跟心电图似的SDA干脆一条直线。我当时第一反应——一定是初始化代码写错了。改了一遍又一遍。HAL库的I2C初始化翻来覆去对了三四遍时序参数从标准模式切到快速模式再切回来GPIO配置也检查了Open Drain没错AFIO也开了。没用。又怀疑是从机地址不对。拿逻辑分析仪抓发现主机根本没发start信号。不对发了但从机没应答。再一看SDA线上拉不到3.3V卡在1.8V晃悠。这就很诡异了——板子上根本没焊外部上拉电阻纯靠STM32内部的上拉撑着。// 我当时GPIO初始化大概是这样的 GPIO_InitTypeDef GPIO_InitStruct {0}; __HAL_RCC_GPIOB_CLK_ENABLE(); GPIO_InitStruct.Pin GPIO_PIN_6 | GPIO_PIN_7; // PB6SCL, PB7SDA GPIO_InitStruct.Mode GPIO_MODE_AF_OD; // 开漏输出 GPIO_InitStruct.Speed GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOB, GPIO_InitStruct);看着挺标准的对吧问题就出在这儿——内部上拉没开。等等你可能要说开漏输出配内部上拉没搞错我之前也是这么想的。开漏嘛外部拉电阻的活儿跟内部上拉有啥关系。但ST的参考手册里写得很清楚当GPIO配置为开漏输出时内部上拉电阻仍然可以使能而且某些引脚在开漏模式下内部上拉弱到什么程度呢弱到你挂一个从机就拉不动了。F103的内部上拉电阻参考手册上写的是典型值30kΩ到50kΩ之间注意这只是典型值——有些引脚更离谱都到70kΩ了。I2C标准模式要求上拉电阻一般在4.7kΩ左右快速模式甚至要求更低。你算算差了快一个数量级。那为什么平时STM32的内部上拉用着好像也没出问题因为大多数时候你操作的GPIO是推挽输出推挽模式下内部上拉那点电流根本无所谓。但I2C是开漏靠上拉电阻把线拉高。内部那三五十kΩ的电阻总线电容稍微大一点RC充电时间常数就上去了上升沿变缓从机就判断不了高电平。解决办法最稳妥的法子就是焊外部上拉电阻两颗4.7kΩ上去世界清净0805封装就行。要是板子已经做好了不方便改就把GPIO配置改成这样GPIO_InitStruct.Pin GPIO_PIN_6 | GPIO_PIN_7; GPIO_InitStruct.Mode GPIO_MODE_AF_OD; GPIO_InitStruct.Pull GPIO_PULLUP; // 开漏模式下也要开上拉 GPIO_InitStruct.Speed GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOB, GPIO_InitStruct);加了这行GPIO_PULLUP波形直接恢复正常。但说实话这招有前提条件——总线电容要小、从机最多一个、速率别超100kHz。你要是挂了两个以上的从机或者想跑400kHz快速模式还是乖乖焊外部电阻吧。内部上拉当个救急手段可以长期用不靠谱。说到这我又想起来之前用STM32G0的时候踩过另一个坑。当时从F4平台迁移一个项目到G0I2C部分直接把CubeMX生成的初始化代码拷过去。编译过了I2C也能通信但跑着跑着就丢数据。不是每次都能复现有时候跑一两个小时才出现一次。查了两天。G0和F4的I2C外设在架构上有区别——F4用的是传统I2C模块Timing靠I2C_CCR和I2C_TRISE两个寄存器配G0用的是新的I2C模块靠一个I2C_TIMINGR寄存器搞定。CubeMX生成的代码里那个值在不同系列之间完全不通用。/* I2C_TIMINGR 寄存器配置 (STM32G0, 100kHz) */ I2cHandle.Init.Timing 0x00201D2B;这串数字看着就像随便写的但它其实是根据系统时钟、I2C速率和rise time算出来的。直接把F4的配置搬上来表面能跑实际时序已经偏了。最坑的是HAL的HAL_I2C_Init函数不校验timing值的合理性你传个驴唇不对马嘴的值它也说ok然后通信就在临界状态飘着。还有个细节STM32的I2C从机地址。我之前一直用7位地址但HAL库的HAL_I2C_Master_Transmit和HAL_I2C_Slave_Receive这些函数地址参数到底传7位还是8位不同版本的HAL库处理方式不一样。早期的HAL库要求左移一位后的地址就是7位地址左移1位拼上R/W位后来有些版本又改成了直接传原始7位地址。要是库版本升级了这块也得跟着改。回头看我那个板子后来测了一下PCB布线SDA和SCL走了差不多15cm还拐了两个直角、过了一个过孔。15cm在100kHz下倒不至于出问题但加上弱上拉、加上过孔的寄生电容拉不起来就完全不冤了。现在画板子凡是遇到I2C我的原则就是线能短则短上拉电阻别省从机超过一个直接上PCA9548做I2C MUX。踩过的坑多了自然就老实了。