Hacktoberfest 2026:维护者为十月标记出来的 issue,仍然开放、适合新手。 浏览 Hacktoberfest issue

ESP32-S3: digitalio open-drain output doesn't work on GPIO45, so bitbangio.I2C fails there

未关闭
#11,669 0 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

维护者通常 1 天内回复

还没有人认领这个 Issue。

评估

难度
4/5
预计耗时
3-5 天
新手友好度
48/100
Issue 类型
缺陷
描述清晰度
基本清楚
活跃度
活跃
技术栈
c, python
领域
embedded-iot

调研方向

Start with the GPIO handling in ports/espressif/common-hal/digitalio/DigitalInOut.c and the open-drain release and read sequence in shared-module/bitbangio/I2C.c. Reproduce the reported difference on GPIO45 with the supplied digitalio script, then trace the lower-level ESP32-S3 GPIO behavior. Done means open-drain transactions on GPIO45 work as they do on GPIO0, without breaking the reported working cases.

由索引模型根据 Issue 内容生成。

描述

CircuitPython 10.3.1 on an ESP32-S3 (M5Stack AtomS3R: espressif_esp32s3_devkitc_1_n8r8, and a local m5stack_atoms3r build). The board's internal I2C bus is SCL GPIO0 / SDA GPIO45 with external pull-ups, a BMI270 at 0x68 and an LP5562 at 0x30.

Symptom: bitbangio.I2C(GPIO0, GPIO45) finds both devices with scan() (at 100 kHz and 10 kHz), but writeto and writeto_then_readfrom raise [Errno 5], and readfrom_into returns 0xFF. busio.I2C on the same pins works (with CIRCUITPY_I2C_ALLOW_STRAPPING_PINS), so the wiring and pull-ups are fine.

Narrowed: the same transaction bit-banged with digitalio, changing only how a line is released. "-od" = the way shared-module/bitbangio/I2C.c does it: an open-drain output set to 1, switched to input and back to read (sda_read, and scl_release's clock-stretch check).

SCL (GPIO0) released by SDA (GPIO45) released by Result
switching to input switching to input works: chip ID 0x24
open-drain output (with the input round trip) switching to input works
switching to input open-drain output (with the input round trip) address ACKed, register byte NACKed, read 0xFF
open-drain output open-drain output the same failure (bitbangio's way)

The identical open-drain sequence works on GPIO0 and fails on GPIO45 (a strapping pin), on both builds, repeatably.

Two more observations:

  • DigitalInOut.drive_mode reports DriveMode.OPEN_DRAIN on GPIO45 after switch_to_output(drive_mode=OPEN_DRAIN), before and after the input round trip (same as GPIO0, 5 and 6), so the requested mode does read back.
  • The address byte is ACKed in every variant; the failure begins after the first input → open-drain round trip on SDA (reading the ACK, then going back to an open-drain output set to 1).

ports/espressif/common-hal/digitalio/DigitalInOut.c is pin-agnostic (gpio_set_direction(GPIO_MODE_OUTPUT | GPIO_MODE_OUTPUT_OD)), so the cause looks to be below it; I haven't found it. Seen on one board (one AtomS3R), so I can't say whether every ESP32-S3's GPIO45 does this.

Repro (save as code.py; output above):

# Repro: digitalio open-drain output fails on ESP32-S3 GPIO45 (so bitbangio.I2C fails there). Save as code.py on an
# M5Stack AtomS3R (internal I2C: SCL GPIO0, SDA GPIO45, external pull-ups; BMI270 at 0x68, chip ID register 0x00 = 0x24)
# and read the serial console. Each run bit-bangs "read the BMI270's chip ID" with digitalio; a line in an "-od" mode is
# released the way shared-module/bitbangio/I2C.c does it (open-drain output set to 1, switched to input and back to
# read). Seen on CircuitPython 10.3.1 (espressif_esp32s3_devkitc_1_n8r8, and an m5stack_atoms3r build):
#   input / scl-od-roundtrip: acks (True, True, True), chip id 24
#   sda-od-roundtrip / both-od-roundtrip: acks (True, False, True), chip id ff
import digitalio, microcontroller as mc
OD = digitalio.DriveMode.OPEN_DRAIN
def run(mode):
    scl = digitalio.DigitalInOut(mc.pin.GPIO0); sda = digitalio.DigitalInOut(mc.pin.GPIO45)
    od = {"scl-od-roundtrip": (scl,), "sda-od-roundtrip": (sda,), "both-od-roundtrip": (scl, sda), "input": ()}[mode]
    for p in od:
        p.switch_to_output(value=True, drive_mode=OD)
    def rel(p):
        if p in od:
            p.value = True
            p.switch_to_input(); p.value; p.switch_to_output(value=True, drive_mode=OD)  # bitbangio's round trip
        else:
            p.switch_to_input()
    def low(p):
        if p in od: p.value = False
        else: p.switch_to_output(value=False)
    def rd(p):
        p.switch_to_input(); v = p.value
        if p in od: p.switch_to_output(value=True, drive_mode=OD)
        return v
    def wbyte(v):
        for i in range(8):
            (rel if v & (0x80 >> i) else low)(sda); rel(scl); low(scl)
        rel(sda); rel(scl); ack = not rd(sda); low(scl); return ack
    def rbyte():
        rel(sda); v = 0
        for _ in range(8):
            rel(scl); v = (v << 1) | rd(sda); low(scl)
        rel(sda); rel(scl); low(scl); return v
    rel(scl); rel(sda); low(sda); low(scl)
    acks = (wbyte(0x68 << 1), wbyte(0x00))
    rel(sda); rel(scl); low(sda); low(scl)
    acks += (wbyte((0x68 << 1) | 1),)
    v = rbyte()
    low(sda); rel(scl); rel(sda)
    scl.deinit(); sda.deinit()
    print("%-18s acks %s chip id %02x" % (mode, acks, v))
for m in ("input", "scl-od-roundtrip", "sda-od-roundtrip", "both-od-roundtrip", "input"):
    run(m)
主要语言
C
星标
4.6k
派生
1.4k
平均合并
1 天 8 小时
30 天内合并 PR
158

环境准备

这个项目没有提供开发容器、Dockerfile 或贡献指南,环境需要你自己搭建:先看它的 README,通用步骤见我们的新手贡献指南。

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

adafruit/circuitpython 的其他 Issue

查看 adafruit/circuitpython 的全部 Issue

相似的 Issue

更多 C Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。