1. 整体方案

本仓库基于 MicroPython for ESP32(ESP32 port)与 ESP-IDF v5.x,错误处理分为两个层级:

  • C 层(编译进固件):位于 firmware/port/builtins/(ESP32 板型)和 esp32s3/firmware/builtins/(ESP32-S3 板型),通过 MicroPython 的 mp_raise_* API 抛出 Python 异常。

  • Python 层(运行时加载):位于 firmware/port/modules/、esp32s3/firmware/boards/*/modules/ 以及 docs/examples/,使用 try/except 捕获 OSError、ValueError 等标准异常,部分模块还自定义了 Exception 子类。

项目没有统一的错误码枚举或中间件式错误处理器;错误以“底层 C 抛异常 → 上层 Python try/except 捕获”的方式自底向上传播。

2. C 层错误模型

2.1 使用的 MicroPython 异常类型

  • mp_raise_OSError(-ret):用于 I/O 类错误,将 ESP-IDF 返回的负 errno 转换为 Python OSError。典型位置:esp32s3/firmware/builtins/machine_i2c_extmod.c 中所有 I2C 读写失败路径(如 machine_i2c_start、machine_i2c_stop、machine_i2c_readinto、machine_i2c_write、machine_i2c_readfrom、machine_i2c_writeto、machine_i2c_writevto 等)。这是 I2C 子系统的统一错误出口。

  • mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("...")):用于语义性错误,如 machine_i2c_init/start/stop/read/write 在对应函数指针为 NULL 时抛出 OSError("I2C operation not supported")。

  • mp_raise_ValueError(...) / mp_raise_msg_varg(&mp_type_ValueError, ...):用于参数校验错误,如 machine_pin.c 中的 invalid pin、pin can only be input、bad wake value、no resources、invalid pin for wake;machine_i2c.c 中的 I2C(%d) doesn't exist;modmachine.c 中 machine_freq 对非允许频率抛出 frequency must be 20MHz, 40MHz, 80Mhz, 160MHz or 240MHz。

  • mp_raise_TypeError / mp_raise_RuntimeError:在 modmachine.c 的 machine_sleep_helper 中,当触摸唤醒配置失败时抛出 RuntimeError("esp_sleep_enable_touchpad_wakeup() failed")。

2.2 错误来源映射

C 文件

主要职责

典型错误

esp32s3/firmware/builtins/machine_i2c_extmod.c

通用 machine.I2C 协议绑定

I2C 操作不支持、硬件返回负 errno

esp32s3/firmware/builtins/machine_i2c.c

硬件 I2C 实例化与参数校验

无效 I2C id

esp32s3/firmware/builtins/machine_pin.c

GPIO Pin 构造与 irq/wake 配置

无效引脚、模式不兼容、唤醒资源冲突

firmware/port/builtins/modmachine.c

系统级功能(freq/sleep/reset/unique_id)

非法频率、睡眠唤醒失败、软复位

2.3 重启与恢复

  • machine_reset():调用 esp_restart() 直接硬重启。

  • machine_soft_reset():设置 pyexec_system_exit = PYEXEC_FORCED_EXIT 并抛出 SystemExit,实现 MicroPython 解释器级软复位。

  • machine_reset_cause():将 ESP-IDF 的 esp_reset_reason() 映射到 MP_PWRON_RESET、MP_WDT_RESET、MP_DEEPSLEEP_RESET、MP_HARD_RESET、MP_SOFT_RESET 等常量,供 Python 层诊断重启原因。

3. Python 层错误处理约定

3.1 启动阶段容错(_boot.py)

esp32s3/firmware/boards/labplus_Ledong_v2/modules/_boot.py 展示了典型的启动容错模式:

  • 挂载文件系统失败时捕获 OSError,回退到 inisetup.setup() 重建 VFS。

  • 获取机器型号、MAC 地址等操作均用 try/except 包裹,失败时打印 Unknown machine / Unknown mac 而非中断启动。

  • 上电后立即关闭 RGB LED 防止随机灯亮,属于“先初始化再尝试业务”的容错策略。

3.2 传感器/I2C 设备封装层的重试 + 明确异常

esp32s3/firmware/boards/labplus_Ledong_v2/modules/mpython.py 中磁力计/加速度计相关代码呈现固定模式:

  • 每个 I2C 读/写序列外层套 retry < 5 的循环。

  • 内部用裸 try: 捕获所有异常(包括 I2C 底层抛出的 OSError),失败则 retry += 1。

  • 重试耗尽后 raise Exception("i2c read/write error!"),抛出一个无具体类型的通用 Exception,而非 OSError 或自定义异常类。该模式出现在 _get_raw、_get_temperature、_get_id、_judge_id 等多个方法中。

3.3 BLE 自定义异常

esp32s3/firmware/boards/labplus_Ledong_v2/modules/educore/mpython_ble/advertising/__init__.py 定义了 class BLEError(Exception):,是仓库中唯一观察到的自定义异常类,用于 BLE 子系统。

3.4 示例代码中的网络错误处理

docs/examples/network/ 下的示例(http_server.py、tcpClient.py、udp_client.py、AnalogClock.py、DigitalClock.py 等)普遍采用 try: ... except OSError: 的模式捕获网络 I/O 错误,体现教程代码对 OSError 作为网络错误的约定。

4. 架构与分层约定

层级

位置

错误方式

说明

硬件驱动层(C)

esp32s3/firmware/builtins/*.c

mp_raise_OSError / mp_raise_ValueError

严格遵循 MicroPython 异常体系,错误信息通过 MP_ERROR_TEXT 提供

系统接口层(C)

firmware/port/builtins/modmachine.c

mp_raise_* + 重启常量

暴露 reset_cause、soft_reset、sleep 等系统能力

板级 Python 模块

esp32s3/firmware/boards/*/modules/mpython.py

try/except + 重试 + raise Exception(...)

对底层 I2C 做重试包装,但顶层仍抛通用 Exception

启动脚本

*_boot.py

try/except OSError

保证启动流程健壮,失败时降级

用户示例

docs/examples/*

try/except OSError

教学示例统一捕获 OSError

5. 约束与规范(从代码行为归纳)

  1. C 层禁止返回错误码给 Python:所有底层 I2C/PIN 操作失败都通过 mp_raise_* 立即转为 Python 异常,调用方无需检查返回值。

  2. I2C 错误一律映射为 OSError:machine_i2c_extmod.c 中所有负返回值统一经 mp_raise_OSError(-ret) 转换,使 Python 层可用 except OSError 统一处理。

  3. 参数校验错误使用 ValueError:无效引脚、非法频率、非法唤醒值等均抛 ValueError,便于区分“参数错误”和“运行时 I/O 错误”。

  4. 启动阶段必须容忍异常:_boot.py 对所有可能失败的初始化步骤加 try/except,确保即使某项服务不可用也能继续启动。

  5. 传感器封装层采用“重试 + 通用 Exception”模式:mpython.py 中 I2C 访问失败最多重试 5 次,之后抛出无类型信息的 Exception("i2c read/write error!")——这是一种简化设计,未定义专用异常类型。

  6. 重启原因通过常量暴露:modmachine.c 将 ESP-IDF 重启原因映射为 MP_PWRON_RESET、MP_WDT_RESET、MP_DEEPSLEEP_RESET、MP_HARD_RESET、MP_SOFT_RESET 五个整数常量,供 Python 层 machine.reset_cause() 查询。

  7. BLE 子系统有独立异常命名空间:advertising/__init__.py 中 BLEError 是唯一自定义异常类,表明 BLE 模块有意隔离其错误域。

6. 关键文件

  • esp32s3/firmware/builtins/machine_i2c_extmod.c — I2C 协议层错误统一出口

  • esp32s3/firmware/builtins/machine_i2c.c — I2C 实例化参数校验

  • esp32s3/firmware/builtins/machine_pin.c — GPIO 引脚与唤醒配置错误

  • firmware/port/builtins/modmachine.c — 系统重启、睡眠、频率等错误与重启原因

  • esp32s3/firmware/boards/labplus_Ledong_v2/modules/_boot.py — 启动容错模板

  • esp32s3/firmware/boards/labplus_Ledong_v2/modules/mpython.py — 传感器 I2C 重试与通用 Exception 模式

  • esp32s3/firmware/boards/labplus_Ledong_v2/modules/educore/mpython_ble/advertising/__init__.py — BLE 自定义异常 BLEError

  • docs/examples/network/http_server.py、tcpClient.py、udp_client.py、AnalogClock.py、DigitalClock.py — 示例层 try/except OSError 惯例