ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

没有外部上拉,I2C 也能跑?GD32F303软件模拟I2C 实战与避坑

没有外部上拉,I2C 也能跑?GD32F303软件模拟I2C 实战与避坑 “板子已经打样硬件没法改I2C 引脚没有外部上拉GD32 标准库gpio_init()里又没有‘开漏 上拉’这个选项。”这是很多嵌入式项目都会遇到的尴尬局面。MCU 是 GD32F303从机是 SHT4x软件 I2C 使用 PA2/PA3。最终代码实测能正常运行但背后有几个关键原理和坑必须讲清楚。这篇文章从原理到代码一次说透。一、问题现场板子已打样I2C 却没上拉先还原场景MCUGD32F303从机SHT4x 温湿度传感器软件 I2C 引脚PA2 SCLPA3 SDAPCB 已经打样无法增加外部上拉电阻标准库gpio_init()中没有“开漏输出 内部上拉”的组合一开始很自然会想把 GPIO 配成开漏输出再打开内部上拉不就行了但翻完手册和 GPIO 结构图如上图所示会发现输入模式下内部上拉有效输出模式下输出电路没有上拉/下拉电阻开漏输出只能主动拉低不能主动拉高写 1 只是释放总线高电平仍然需要外部上拉。而 I2C 协议偏偏要求低电平主动拉低高电平靠上拉拉高。于是问题变成板子不能改没有外部上拉开漏又无法输出高怎么办二、原理I2C 为什么必须上拉I2C 总线是“线与”结构。总线上所有设备只能主动拉低不能主动拉高高电平由上拉电阻把线拉上去。标准 I2C 的 GPIO 配置是开漏输出外部上拉电阻典型 2.2kΩ ~ 10kΩ写 1释放总线由外部上拉拉高写 0主动拉低。所以没有上拉I2C 的高电平就不确定。很多 I2C 通信失败不是代码问题而是没加上拉上拉太大走线太长总线电容太大上升沿太慢。三、GD32 GPIO 结构输出没上拉输入才有GD32F303 的 GPIO 寄存器大致如下寄存器地址偏移作用GPIO_CTL00x00低 8 位引脚模式GPIO_CTL10x04高 8 位引脚模式GPIO_OMODE0x08输出模式推挽/开漏GPIO_PUD0x0C上拉/下拉GPIO_ISTAT0x10输入状态GPIO_OCTL0x14输出状态GPIO_BOP0x18置位GPIO_BC0x1C清除关键点CTL 决定引脚是输入、输出、复用、模拟OMODE 决定输出是推挽还是开漏PUD 决定上拉、下拉、浮空输出模式下PUD 虽然存在但输出驱动本身不能主动拉高开漏输出写 1只是释放总线不是输出高。所以gpio_init()没有“开漏 上拉”这种组合不是库漏了而是硬件结构决定的。四、救火方案推挽拉低 输入上拉模拟高既然板子不能改就采用纯软件方案。核心思想目标操作输出低推挽输出模式写 0输出高输入模式 内部上拉释放总线读电平输入模式 内部上拉读 ISTAT注意这里输出低用的是推挽输出不是开漏。为什么可以因为代码里永远不写 1。需要低推挽输出 0强下拉需要高切输入输出驱动器关闭内部上拉拉高从机拉低主机处于输入模式不会冲突线与特性成立。Sensirion 官方驱动就是这样做的void sensirion_SDA_out(void) { gpio_init(SDA_PORT, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, SDA_PIN); gpio_bit_write(SDA_PORT, SDA_PIN, RESET); } void sensirion_SDA_in(void) { gpio_init(SDA_PORT, GPIO_MODE_IPU, GPIO_OSPEED_50MHZ, SDA_PIN); }这套逻辑等价于开漏输出输出低强下拉输出高输出驱动器关闭内部上拉拉高从机拉低主机处于输入模式不会冲突线与特性成立。PA2/PA3 是普通 GPIO驱动能力约 25mA速度可到 50MHz硬件底子完全够用。唯一短板是内部上拉约 40kΩ偏大。五、代码教程 先适配 Sensirion 官方驱动如果你只想快速跑通可以先用gpio_init()版本。#include gd32f30x.h #include delay.h #include sensirion_sw_i2c_gpio.h #define SDA_PORT GPIOA #define SDA_PIN GPIO_PIN_3 #define SCL_PORT GPIOA #define SCL_PIN GPIO_PIN_2 void sensirion_init_pins(void) { rcu_periph_clock_enable(RCU_GPIOA); /* 初始释放总线输入上拉 */ gpio_init(SDA_PORT, GPIO_MODE_IPU, GPIO_OSPEED_50MHZ, SDA_PIN); gpio_init(SCL_PORT, GPIO_MODE_IPU, GPIO_OSPEED_50MHZ, SCL_PIN); } void sensirion_SDA_in(void) { gpio_init(SDA_PORT, GPIO_MODE_IPU, GPIO_OSPEED_50MHZ, SDA_PIN); } void sensirion_SDA_out(void) { gpio_init(SDA_PORT, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, SDA_PIN); gpio_bit_write(SDA_PORT, SDA_PIN, RESET); } uint8_t sensirion_SDA_read(void) { /* 防御性确保是输入上拉 */ sensirion_SDA_in(); return gpio_input_bit_get(SDA_PORT, SDA_PIN) RESET ? 0 : 1; } void sensirion_SCL_in(void) { gpio_init(SCL_PORT, GPIO_MODE_IPU, GPIO_OSPEED_50MHZ, SCL_PIN); } void sensirion_SCL_out(void) { gpio_init(SCL_PORT, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, SCL_PIN); gpio_bit_write(SCL_PORT, SCL_PIN, RESET); } uint8_t sensirion_SCL_read(void) { sensirion_SCL_in(); return gpio_input_bit_get(SCL_PORT, SCL_PIN) RESET ? 0 : 1; } void sensirion_sleep_usec(uint32_t useconds) { delay_us(useconds); }这个版本能跑但gpio_init()内部有循环每次切换模式开销较大实际 I2C 速率可能只有 70kHz 左右。对 SHT4x 够用但不够优雅。六、避坑清单1. 内部上拉 40kΩ 是短板I2C 上升沿时间由 RC 决定t_rise ≈ 0.8473 × R × C40kΩ 内部上拉 几十 pF 总线电容上升沿可能在 0.7~1.7µs。100kHz I2C 要求上升沿小于 1000ns已经处于临界状态。建议速率降到 50kHz 以下从机紧贴 MCU走线尽量短有条件飞线 4.7kΩ 外部上拉。2. 模式切换瞬间可能产生毛刺gpio_init()只改模式寄存器不改输出数据寄存器。如果 OCTL 里对应位是 1切到推挽输出的瞬间引脚可能短暂输出高。寄存器版里先GPIO_BC PIN清输出锁存再切输出能避免这个问题。3. 读 SDA 前必须切输入上拉sensirion_SDA_read()里强制调用sda_in_pullup()否则读到的是输出锁存不是总线真实电平。4. 时钟拉伸SHT4x 通常不需要时钟拉伸但通用 I2C 代码要检测 SCL 是否被从机拉低。如果从机支持时钟拉伸主机必须等待 SCL 释放。5. 多从机、长走线每增加一个从机总线电容增加约 10~20pF。多从机时上升沿更慢需要进一步降低速率或者飞线加 2.2kΩ~4.7kΩ 外部上拉。6. 硬件 I2C 有坑GD32F303 的硬件 I2C 与 STM32F103 有类似问题总线死锁、时钟拉伸支持不佳、高速下可能卡死。已经调通软件 I2C 的项目继续软件模拟更可控。七、实测与验证用示波器看什么最可靠的验证方式是看波形SCL、SDA 上升沿是否陡峭高电平是否达到 3.3V低电平是否接近 0V上升沿时间是否小于 1µs100kHz 标准模式有无毛刺、振铃。如果上升沿太慢优先降低 I2C 速率缩短走线飞线加外部上拉。八、总结短期、中期、长期维度评估正确性✅ 完全正确能稳定工作驱动能力✅ 推挽输出低能力强时序质量⚠️ 上升沿慢内部 40kΩ 是短板速度上限⚠️ 约 70kHz受gpio_init开销限制潜在风险⚠️ 模式切换毛刺、读前需手动切模式适合场景✅ SHT4x 这类低速、短距离、单从机传感器不适合场景❌ 高速、长走线、多从机、强干扰、正式高可靠产品一句话建议短期代码能跑继续用没问题中期用寄存器操作替代gpio_init有条件飞线加 4.7kΩ 上拉长期下次改版把 I2C 移到 PB6/PB7 或 PB8/PB9预留外部上拉必要时再用硬件 I2C。当前方案不是标准 I2C但在“板子已定型、无法改硬件”的现实下它是一个非常实用的救火方案。只要控制好速率和总线电容SHT4x 这类传感器完全可以稳定工作。能跑是第一步知道为什么能跑、哪里可能出问题才是工程师的底气。
返回列表