分类 汽车 下的文章

做嵌入式项目时,I2C 通信总是不稳定,有时候能通有时候不通。排查了半天,最后发现是开漏输出没加上拉电阻。这个问题很常见,90% 的工程师都忽略了这一点。

问题现象

// 配置 GPIO 为开漏输出
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = GPIO_PIN_6;  // SCL
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;  // 开漏输出
GPIO_InitStruct.Pull = GPIO_NOPULL;  // 没有上下拉
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

I2C 通信时好时坏,有时候能通,有时候直接卡死。用逻辑分析仪看波形,发现 SCL 线有时候拉不上去。

原因分析

说白了,就是开漏输出的特性决定的。

推挽输出(Push-Pull)

  • 可以主动输出高电平和低电平
  • 内部有两个 MOS 管,一个拉高,一个拉低
  • 不需要外部上下拉电阻

开漏输出(Open-Drain)

  • 只能主动输出低电平
  • 高电平是靠外部上拉电阻拉上去的
  • 如果没有上拉电阻,高电平就是浮空状态

坑在于:开漏输出的"高电平"不是 MCU 主动输出的,而是靠外部上拉电阻拉上去的。如果没有上拉,高电平就是浮空,电平不确定。

解决方案

方案一:配置内部上拉

// 配置 GPIO 为开漏输出 + 内部上拉
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = GPIO_PIN_6;  // SCL
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;  // 开漏输出
GPIO_InitStruct.Pull = GPIO_PULLUP;  // 内部上拉
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

坑在于:内部上拉电阻通常在 20kΩ-50kΩ,阻值比较大。对于 I2C 这种需要快速上升沿的场景,内部上拉可能不够。

方案二:外部上拉电阻(推荐)

// 在硬件上加外部上拉电阻
// SCL 线:4.7kΩ 上拉到 VCC
// SDA 线:4.7kΩ 上拉到 VCC

I2C 协议推荐使用 4.7kΩ 上拉电阻。如果总线上设备多或者线长,可以适当减小阻值(比如 2.2kΩ)。

方案三:根据场景选择

// 场景 1:普通 GPIO 控制 LED
// 推挽输出,不需要上下拉
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;

// 场景 2:I2C 通信
// 开漏输出 + 外部上拉
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;
GPIO_InitStruct.Pull = GPIO_NOPULL;  // 用外部上拉

// 场景 3:按键输入
// 输入模式 + 内部上拉
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;

// 场景 4:UART TX
// 推挽输出,空闲状态保持高电平
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;

常见场景配置

I2C 通信

// I2C GPIO 配置
GPIO_InitStruct.Pin = GPIO_PIN_6 | GPIO_PIN_7;  // SCL, SDA
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;  // 开漏输出
GPIO_InitStruct.Pull = GPIO_PULLUP;  // 内部上拉(建议用外部 4.7kΩ)
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

SPI 通信

// SPI GPIO 配置
GPIO_InitStruct.Pin = GPIO_PIN_5 | GPIO_PIN_7;  // SCK, MOSI
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;  // 推挽输出
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

UART 通信

// UART TX 配置
GPIO_InitStruct.Pin = GPIO_PIN_9;  // TX
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;  // 推挽输出
GPIO_InitStruct.Pull = GPIO_PULLUP;  // 空闲状态高电平
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

踩坑总结

  1. 开漏输出只能主动拉低,高电平靠外部上拉
  2. I2C 必须用开漏输出 + 上拉电阻
  3. SPI、UART 用推挽输出,不需要上下拉
  4. 内部上拉电阻阻值大,高速通信建议用外部上拉
  5. 记住口诀:"推挽上下拉是白搭,开漏不拉就抓瞎"

这个问题的坑在于:开漏输出的高电平是靠上拉电阻实现的,没有上拉就是浮空。理解了这个原理,配置就很清晰了。

用Python写了个批量下载脚本,可以实现批量爬取AUTOSAR官网标准文档。没有做多线程,异常处理逻辑也基本没有,仅能在网络良好情况下实现基本功能而已。
默认下载R20-11版本,分类保存到D盘根目录,修改categorycategory_names 理论上可以下载其他版本(AUTOSAR官网不改版的前提下),具体category的值可以打开Chrome/Edge浏览器开发者模式找。

- 阅读剩余部分 -