ESP-IDF USB PHY 功能测试应用解析:PHY sanity checks 测试套件详解
发布时间:2026/9/13 23:55:17来源:尧图网络
ESP-IDF USB PHY 功能测试应用解析PHY sanity checks 测试套件详解【免费下载链接】esp-idfEspressif IoT Development Framework. Official development framework for Espressif SoCs.项目地址: https://gitcode.com/GitHub_Trending/es/esp-idf导读本文围绕 ESP-IDF 仓库中 components/esp_hw_support/test_apps/usb_phy/ 下的 USB: PHY sanity checks 测试应用展开系统讲解 ESP-IDF 中 USB PHY 驱动的三种 PHY 目标内部 FSLS PHY、内部 UTMI PHY、外部 PHY的初始化/释放验证逻辑、OTG 模式与速率枚举、外部 PHY 与 OTG IO 引脚配置结构以及自动化测试的运行方式。读完本文你将掌握usb_new_phy/usb_del_phy的核心 API 语义、测试用例对不支持目标的降级断言策略以及如何在本仓库中运行该测试套件。测试应用概览与支持目标该测试应用位于 components/esp_hw_support/test_apps/usb_phy/README.md是 ESP-IDF 中针对 USB PHY 驱动的一组健康检查sanity checks测试验证 PHY 在不同目标芯片上的初始化和释放行为是否正确。官方文档明确列出的支持目标如下Supported TargetsESP32-H4ESP32-P4ESP32-S2ESP32-S3ESP32-S31不过测试代码本身并没有针对具体芯片型号做#ifdef硬编码而是通过 SoC 能力宏SOC_*做特性探测。从 test_app_main.c 可以看到三组能力判定#if (SOC_USB_FSLS_PHY_NUM 0) #include hal/usb_wrap_ll.h #define EXT_PHY_SUPPORTED USB_WRAP_LL_EXT_PHY_SUPPORTED #else #define EXT_PHY_SUPPORTED 0 #endif #if SOC_USB_UTMI_PHY_NUM 0 #define UTMI_PHY_SUPPORTED 1 #else #define UTMI_PHY_SUPPORTED 0 #endif #if CONFIG_IDF_TARGET_ESP32S31 #define INT_PHY_ALIASES_UTMI 1 #else #define INT_PHY_ALIASES_UTMI 0 #endif #if (SOC_USB_FSLS_PHY_NUM 0) || INT_PHY_ALIASES_UTMI #define INT_PHY_SUPPORTED 1 #else #define INT_PHY_SUPPORTED 0 #endif内部 FSLS PHYFull/Low Speed PHY由SOC_USB_FSLS_PHY_NUM决定是否存在是否支持外部 PHY 由 HAL 层的USB_WRAP_LL_EXT_PHY_SUPPORTED决定内部 UTMI PHYHigh Speed PHY由SOC_USB_UTMI_PHY_NUM决定ESP32-S31 特例INT_PHY_ALIASES_UTMI宏表明该目标只对外暴露 UTMI PHY请求内部 FSLS PHY 会被别名映射到 UTMI后文结合源码详述。相应地自动化测试 pytest_usb_phy.py 使用soc_filtered_targets(SOC_USB_OTG_SUPPORTED 1)过滤出所有支持 USB OTG 的目标芯片因此凡具备 USB OTG 外设且能力宏满足条件的芯片都可纳入测试范围不局限于 README 表格列出的五款芯片。被测驱动接口usb_phy 公共 API测试应用直接调用的是 components/esp_hw_support/include/esp_private/usb_phy.h 中声明的 ESP-IDF USB PHY 驱动接口。虽然该头文件位于esp_private私有目录但其实现 components/esp_hw_support/usb_phy/usb_phy.c 正是测试所覆盖的核心代码。核心枚举与配置结构PHY 目标usb_phy_target_ttypedef enum { USB_PHY_TARGET_INT, /** USB target is internal FSLS PHY */ USB_PHY_TARGET_UTMI, /** USB target is internal UTMI PHY */ USB_PHY_TARGET_EXT, /** USB target is external PHY */ USB_PHY_TARGET_MAX, } usb_phy_target_t;PHY 控制器usb_phy_controller_tUSB OTGUSB_PHY_CTRL_OTG以及支持 USB Serial JTAG 的目标上的USB_PHY_CTRL_SERIAL_JTAG。OTG 模式usb_otg_mode_t默认模式USB_PHY_MODE_DEFAULT、主机模式USB_OTG_MODE_HOST、设备模式USB_OTG_MODE_DEVICE。USB 速率usb_phy_speed_t枚举值说明USB_PHY_SPEED_UNDEFINED未定义由驱动自行选择USB_PHY_SPEED_LOWUSB Low Speed1.5 Mbit/sUSB_PHY_SPEED_FULLUSB Full Speed12 Mbit/sUSB_PHY_SPEED_HIGHUSB High Speed480 Mbit/s配置结构usb_phy_config_ttypedef struct { usb_phy_controller_t controller; /** USB PHY controller */ usb_phy_target_t target; /** USB PHY target INT/EXT */ usb_otg_mode_t otg_mode; /** USB OTG mode */ usb_phy_speed_t otg_speed; /** USB OTG speed */ const usb_phy_ext_io_conf_t *ext_io_conf; /** USB external PHY IO pins configuration */ const usb_phy_otg_io_conf_t *otg_io_conf; /** USB OTG IO pins configuration */ } usb_phy_config_t;主要函数语义esp_err_t usb_new_phy(const usb_phy_config_t *config, usb_phy_handle_t *handle_ret)初始化并启用一个新的 USB PHY同时会启用 OTG 控制器。返回值包括ESP_OK、ESP_ERR_INVALID_STATEPHY 已被占用、ESP_ERR_NO_MEM、ESP_ERR_NOT_SUPPORTED当前目标不支持所选 PHY、ESP_ERR_INVALID_ARG参数非法。esp_err_t usb_phy_otg_set_mode(usb_phy_handle_t handle, usb_otg_mode_t mode)动态切换 OTG 模式若句柄对应的控制器不是 OTG返回ESP_FAIL。esp_err_t usb_del_phy(usb_phy_handle_t handle)释放 PHY引用计数归零后卸载整个 PHY 控制对象并关闭 USB 外设时钟。esp_err_t usb_phy_get_phy_status(usb_phy_target_t target, usb_phy_status_t *status)查询指定目标的占用状态USB_PHY_STATUS_FREE/USB_PHY_STATUS_IN_USE。void usb_phy_set_otg_suspend_state(bool in_suspend)与void usb_phy_clear_otg_wakeup_status(void)仅作用于 UTMIHSPHY 的挂起与唤醒状态控制。外部 PHY 与 OTG IO 配置外部 PHY 需要提供 8 个引脚的 IO 映射usb_phy_ext_io_conf_t输入侧为vp_io_num、vm_io_num、rcv_io_num分别对应USB_EXTPHY_VP/VM/RCV_IDX输出侧为suspend_n_io_num、oen_io_num、vpo_io_num、vmo_io_num、fs_edge_sel_io_num对应USB_EXTPHY_SUSPND/OEN/VPO/VMO/SPEED_IDX。OTG 的 IO 配置usb_phy_otg_io_conf_t包含 11 个引脚输入侧iddig_io_num、avalid_io_num、vbusvalid_io_num、bvalid_io_num、sessend_io_num输出侧idpullup_io_num、dppulldown_io_num、dmpulldown_io_num、drvvbus_io_num、chrgvbus_io_num、dischrgvbus_io_num。头文件还提供了自供电设备场景的便捷宏USB_PHY_SELF_POWERED_DEVICE(vbus_monitor_io)usb_phy.h它将除bvalid_io_numVBUS 监测脚之外的所有 OTG 引脚置为-1即GPIO_NUM_NC不连接。测试用例逐条解析测试入口 test_app_main.c 调用unity_run_menu()进入 Unity 测试菜单所有用例标记为[phy]分组。setUp/tearDown通过unity_utils_record_free_mem()与unity_utils_evaluate_leaks()进行堆内存泄漏检查且设置了 128 字节的泄漏阈值。用例一Init internal PHY target[phy]该用例验证内部 PHY 在 Host 与 Device 两种模式下的 init deinit 闭环// Host mode usb_phy_handle_t phy_handle NULL; const usb_phy_config_t phy_config { .controller USB_PHY_CTRL_OTG, .target USB_PHY_TARGET_INT, .otg_mode USB_OTG_MODE_HOST, .otg_speed USB_PHY_SPEED_UNDEFINED, .ext_io_conf NULL, .otg_io_conf NULL, }; #if INT_PHY_SUPPORTED TEST_ASSERT_EQUAL(ESP_OK, usb_new_phy(phy_config, phy_handle)); TEST_ASSERT_NOT_NULL(phy_handle); TEST_ASSERT_EQUAL(ESP_OK, usb_del_phy(phy_handle)); #else TEST_ASSERT_NOT_EQUAL(ESP_OK, usb_new_phy(phy_config, phy_handle)); TEST_ASSERT_NULL(phy_handle); #endif值得注意的细节若目标芯片支持内部 FSLS PHYINT_PHY_SUPPORTED则断言 init 成功、句柄非空、释放成功否则断言 init必须失败且句柄保持NULL。这是一种典型的按能力反向断言测试模式保证在不支持的平台上不会误通过。Device 模式部分将otg_speed设为USB_PHY_SPEED_FULLFull Speed12 Mbit/s与内部 FSLS PHY 的 Full/Low Speed 定位一致。从实现看usb_new_phy 在!SOC_USB_FSLS_PHY_NUM时对USB_PHY_TARGET_INT直接返回ESP_ERR_NOT_SUPPORTED而在支持 FSLS 的平台上初始化内部 PHY 时会把 D/D- 引脚的驱动能力设置为GPIO_DRIVE_CAP_340mA见 usb_phy.c这是内部 PHY 与 GPIO 外设共享焊盘时的必要配置。用例二Init external FSLS PHY[phy]该用例验证外部 PHY 的强约束必须提供ext_io_confusb_phy_handle_t phy_handle NULL; usb_phy_config_t phy_config { .controller USB_PHY_CTRL_OTG, .target USB_PHY_TARGET_EXT, .otg_mode USB_OTG_MODE_HOST, .otg_speed USB_PHY_SPEED_UNDEFINED, .ext_io_conf NULL, // 缺省预期失败 .otg_io_conf NULL, }; #if EXT_PHY_SUPPORTED // Init ext PHY without ext_io_conf - FAIL TEST_ASSERT_NOT_EQUAL(ESP_OK, usb_new_phy(phy_config, phy_handle)); TEST_ASSERT_NULL(phy_handle); // Init ext PHY with ext_io_conf - PASS const usb_phy_ext_io_conf_t ext_io_conf { // Some random values .vp_io_num 1, .vm_io_num 1, .rcv_io_num 1, .suspend_n_io_num 1, .oen_io_num 1, .vpo_io_num 1, .vmo_io_num 1, .fs_edge_sel_io_num 1, }; phy_config.ext_io_conf ext_io_conf; TEST_ASSERT_EQUAL(ESP_OK, usb_new_phy(phy_config, phy_handle)); TEST_ASSERT_NOT_NULL(phy_handle); TEST_ASSERT_EQUAL(ESP_OK, usb_del_phy(phy_handle)); #else TEST_ASSERT_NOT_EQUAL(ESP_OK, usb_new_phy(phy_config, phy_handle)); TEST_ASSERT_NULL(phy_handle); #endif测试注释明确指出Some random values测试中传入的引脚号只是占位值。其背后的实现约束在 usb_phy.cESP_RETURN_ON_FALSE(phy_target ! USB_PHY_TARGET_EXT || config-ext_io_conf, ESP_ERR_INVALID_ARG, USBPHY_TAG, ext_io_conf must be provided for ext PHY);即请求外部 PHY 但未提供 IO 配置时返回ESP_ERR_INVALID_ARG。若芯片不支持外部 FSLS PHYEXT_PHY_SUPPORTED 0则 usb_phy.c 会在参数校验后直接返回ESP_ERR_NOT_SUPPORTED。配置外部 PHY 时驱动会为每个引脚通过 GPIO 信号矩阵完成信号路由输入引脚VP/VM/RCV调用phy_configure_pin_input输出引脚SUSPEND_N/OEN/VPO/VMO/FS_EDGE_SEL调用phy_configure_pin_output见 usb_phy.c。引脚号为GPIO_NUM_NC-1的项会被跳过不做任何路由配置。用例三Init internal UTMI PHY[phy]UTMIHigh SpeedPHY 是 P4、H4、S31 等新一代目标上主要的内部 PHY。用例以USB_PHY_TARGET_UTMI为目标、Host 模式执行 init/deinit#if UTMI_PHY_SUPPORTED TEST_ASSERT_EQUAL(ESP_OK, usb_new_phy(phy_config, phy_handle)); TEST_ASSERT_NOT_NULL(phy_handle); TEST_ASSERT_EQUAL(ESP_OK, usb_del_phy(phy_handle)); #else TEST_ASSERT_NOT_EQUAL(ESP_OK, usb_new_phy(phy_config, phy_handle)); TEST_ASSERT_NULL(phy_handle); #endif从实现看UTMI PHY 的初始化路径会调用usb_utmi_hal_init()usb_phy.c。OTG 模式切换时UTMI 目标走独立分支通过usb_utmi_hal_enable_data_pulldowns(mode USB_OTG_MODE_HOST)软件控制 D/D- 上的 15k 下拉电阻——Host 模式下使能下拉Device 模式下断开下拉见 usb_phy.c。这一行为对应了源码注释中某些目标上 D/D- 的 15k 下拉电阻不由 USB-OTG 外设直接控制而必须由软件控制的硬件约束。用例四Init all PHYs in a loop[phy]该用例验证多个 PHY 目标可以同时被分配并在循环中重复 init/deinit 两次检验驱动状态机与引用计数的健壮性#if !INT_PHY_SUPPORTED TEST_IGNORE_MESSAGE(Internal PHY target is not supported on this target); #endif for (int i 0; i 2; i) { // 先分配内部 FSLS PHYUSB_PHY_TARGET_INT, Host 模式 TEST_ASSERT_EQUAL(ESP_OK, usb_new_phy(phy_config, phy_handle)); TEST_ASSERT_NOT_NULL(phy_handle); // UTMI-only 目标会把内部 PHY 目标别名到 UTMI // 此时第二次 UTMI 分配必须失败因为是同一个物理 PHY 实例。 #if UTMI_PHY_SUPPORTED !INT_PHY_ALIASES_UTMI phy_config.target USB_PHY_TARGET_UTMI; TEST_ASSERT_EQUAL(ESP_OK, usb_new_phy(phy_config, phy_handle_2)); TEST_ASSERT_NOT_NULL(phy_handle_2); #elif EXT_PHY_SUPPORTED // 否则尝试分配外部 PHY ... #elif INT_PHY_ALIASES_UTMI phy_config.target USB_PHY_TARGET_UTMI; TEST_ASSERT_NOT_EQUAL(ESP_OK, usb_new_phy(phy_config, phy_handle_2)); TEST_ASSERT_NULL(phy_handle_2); #endif TEST_ASSERT_EQUAL(ESP_OK, usb_del_phy(phy_handle)); if (phy_handle_2) { TEST_ASSERT_EQUAL(ESP_OK, usb_del_phy(phy_handle_2)); } }该用例的三条分支体现了不同目标的 PHY 资源模型同时支持 FSLS 与 UTMI 的目标如 ESP32-P4先分配 FSLS 再分配 UTMI二者都成功支持外部 PHY 的目标第二个分配落到外部 PHY 上同样成功仅暴露 UTMI、且内部目标被别名到 UTMI 的目标如 ESP32-S31第二个 UTMI 分配必须失败因为底层是同一个物理 PHY 实例。底层的资源独占逻辑在 usb_phy.c每个 PHY 目标对应phy_ctrl_obj_t中的一个槽位fsls_phy/utmi_phy/external_phy分配前通过usb_phy_get_phy_status查询状态若为USB_PHY_STATUS_IN_USE则返回ESP_ERR_INVALID_STATEselected PHY is in use。而 ESP32-S31 的别名行为在 usb_phy.c#if CONFIG_IDF_TARGET_ESP32S31 if (config-controller USB_PHY_CTRL_OTG phy_target USB_PHY_TARGET_INT) { ESP_LOGW(USBPHY_TAG, Using UTMI PHY instead of requested internal PHY); phy_target USB_PHY_TARGET_UTMI; } #endif请求内部 PHY 会被打日志警告并透明地映射为 UTMI这正是测试中INT_PHY_ALIASES_UTMI分支断言的依据。用例五ESP32-P4 TinyUSB backward compatibility[phy]仅 P4该用例专门验证 ESP32-P4 的向后兼容逻辑。初始的 P4 设备支持针对 USB-DWC HS 与 UTMI PHY 构建为了在 USB Device 模式下保持向后兼容当otg_speed为USB_PHY_SPEED_UNDEFINED或USB_PHY_SPEED_HIGH时驱动会将请求的目标改写为 UTMI PHYusb_phy.c#if CONFIG_IDF_TARGET_ESP32P4 if (config-otg_mode USB_OTG_MODE_DEVICE (config-otg_speed USB_PHY_SPEED_UNDEFINED || config-otg_speed USB_PHY_SPEED_HIGH)) { if (phy_target ! USB_PHY_TARGET_UTMI) { ESP_LOGW(USBPHY_TAG, Using UTMI PHY instead of requested %s PHY, (phy_target USB_PHY_TARGET_INT) ? internal : external); phy_target USB_PHY_TARGET_UTMI; } } #endif测试覆盖两种真实场景test_app_main.cesp_tinyusb组件使用的配置Device 模式 USB_PHY_SPEED_UNDEFINEDupstream tinyusb 示例使用的配置Device 模式 USB_PHY_SPEED_HIGH。两者请求的都是USB_PHY_TARGET_INT但最终都必须成功分配——因为内部实现已将它们映射到 UTMI PHY。测试基础设施与配置工程 CMake 配置CMakeLists.txt 采用 ESP-IDF 测试应用的典型精简做法cmake_minimum_required(VERSION 3.22) include($ENV{IDF_PATH}/tools/cmake/project.cmake) # Trim the build. Include the minimal set of components, main, and anything it depends on. idf_build_set_property(MINIMAL_BUILD ON) project(test_app_usb_phy)MINIMAL_BUILD ON会裁剪构建依赖只包含测试主体main及其实际依赖的组件从而缩短编译时间这也是 tools/cmake/project.cmake 提供的构建优化特性。sdkconfig 默认配置sdkconfig.defaults 由idf.py save-defconfig生成包含# CONFIG_ESP_TASK_WDT_INIT is not set # 关闭任务看门狗 CONFIG_HEAP_POISONING_COMPREHENSIVEy # 全面堆毒化用于检测越界/释放后使用 # CONFIG_UNITY_ENABLE_FLOAT is not set # CONFIG_UNITY_ENABLE_DOUBLE is not set # 关闭浮点断言以减小体积 CONFIG_UNITY_ENABLE_BACKTRACE_ON_FAILy # 断言失败时打印回溯其中全面堆毒化与 Unity 的setUp/tearDown内存泄漏检测unity_utils_evaluate_leaks配合确保每个 PHY init/deinit 循环都不会泄漏堆内存。自动化测试入口pytest_usb_phy.py 是 pytest-embedded 风格的集成测试pytest.mark.generic idf_parametrize(target, soc_filtered_targets(SOC_USB_OTG_SUPPORTED 1), indirect[target]) def test_usb_phy(dut: Dut) - None: dut.run_all_single_board_cases(groupphy)它会基于SOC_USB_OTG_SUPPORTED 1的能力宏自动筛选可运行的目标芯片然后运行所有标记为groupphy的单板用例。这套测试框架与仓库中其他测试应用一致可通过pytest配合pytest-embedded插件在真实硬件上执行。如何构建与运行该测试应用遵循 ESP-IDF 标准测试应用的构建流程。以 ESP32-S3 为例在已设置IDF_PATH的环境中cd components/esp_hw_support/test_apps/usb_phy idf.py set-target esp32s3 idf.py build idf.py flash monitor烧录后会在串口终端进入 Unity 测试菜单可运行全部[phy]用例或选择单个用例执行。若要跑自动化集成测试则在仓库根目录使用 pytest-embeddedpytest components/esp_hw_support/test_apps/usb_phy --target esp32s3注意由于驱动接口位于esp_private私有头文件中usb_phy.h该测试应用本身并不面向普通用户代码其价值在于作为 ESP-IDF 内部 USB PHY 驱动的回归验证套件——任何对 PHY 分配逻辑、OTG 模式切换或目标芯片能力宏的改动都可以通过这组用例快速验证。小结USB: PHY sanity checks 虽是一个体量精简的测试应用却完整覆盖了 ESP-IDF USB PHY 驱动的核心行为面三类 PHY 目标的分配/释放、Host/Device 模式切换、外部 PHY 的强制 IO 配置约束、UTMI 下拉电阻的软件控制以及 ESP32-P4/ESP32-S31 特有的向后兼容与别名映射逻辑。结合 usb_phy.c 的实现这些用例不仅验证 API 正确性还充当了目标芯片能力差异的行为文档——对于需要移植 USB 外设栈或排查 USB PHY 初始化问题的开发者是极具参考价值的起点。【免费下载链接】esp-idfEspressif IoT Development Framework. Official development framework for Espressif SoCs.项目地址: https://gitcode.com/GitHub_Trending/es/esp-idf创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
网站建设高端定制企业官网