尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

ESP-IDF GPSPI SPI 外设 HAL 层剖析:esp_hal_gpspi 组件的架构、API 与典型使用流程

ESP-IDF GPSPI SPI 外设 HAL 层剖析:esp_hal_gpspi 组件的架构、API 与典型使用流程 ESP-IDF GPSPI SPI 外设 HAL 层剖析esp_hal_gpspi 组件的架构、API 与典型使用流程【免费下载链接】esp-idfEspressif IoT Development Framework. Official development framework for Espressif SoCs.项目地址: https://gitcode.com/GitHub_Trending/es/esp-idfESP-IDF 中的esp_hal_gpspi组件为各系列 ESP 芯片的 GPSPIGeneral Purpose SPI通用 SPI外设提供统一的硬件抽象层HAL是esp_driver_spi等上层 SPI 驱动的底层基座。本篇基于该组件的 README、公共头文件与实现源码完整梳理其HAL 层 低层LL层两层架构、主模式/从模式/从半双工Slave HD三种工作模式的 API 与调用流程、时钟与时序计算机制、SCT 分段传输支持以及构建集成和依赖关系帮助你在理解 ESP-IDF SPI 驱动全栈时看清驱动代码之下寄存器配置究竟是如何被组织起来的。组件定位驱动之下的硬件抽象Beta 状态需留意README 开篇即以醒目提示说明该组件目前处于 beta 状态API、行为与兼容性可能随时变更且不作通知不保证向后兼容。这一声明在所有公共头文件中同样得到强化——spi_hal.h、spi_slave_hal.h 与 spi_slave_hd_hal.h 文件头部的 NOTICE 均注明The hal is not public api, dont use in application code.也就是说本组件的定位是ESP-IDF 内部接口esp_driver_spi等官方 SPI 驱动直接构建在其之上而普通应用开发应使用esp_driver_spi暴露的公共 API。只有需要实现定制 SPI 方案的进阶开发者才会直接调用 HAL 函数README 也明确提醒这些接口internal to ESP-IDF and are subject to change。README 列出的核心能力包括覆盖所有 ESP 芯片家族的统一 SPI 接口多操作模式支持主模式Master全双工与半双工通信从模式Slave标准从操作从半双工模式Slave HD带分段segment事务的半双工从模式在支持的芯片上灵活的 SPI 线制配置1/2/4 线模式可配置的时钟源与频率多种事务格式command、address、dummy、data 各阶段。两层架构HAL 层与低层LL层的分工README 将架构描述为两层源码目录结构与之严格对应HAL 层上层定义与 SPI 外设交互所需的操作序列和数据结构涵盖初始化/反初始化、时钟配置与时序计算、设备与事务设置、主/从/从半双工模式操作。对应组件根目录下的通用实现文件 spi_hal.c、spi_hal_iram.c、spi_slave_hal.c、spi_slave_hd_hal.c 及公共头文件include/hal/下的 spi_hal.h、spi_types.h、spi_slave_hal.h、spi_slave_hd_hal.h。低层LL层底层作为 HAL 与soc组件寄存器定义之间的翻译层负责寄存器访问抽象、芯片特定的寄存器配置与硬件特性兼容。对应每颗芯片独立子目录esp32/、esp32c2/、esp32c3/、esp32c5/、esp32c6/、esp32c61/、esp32h2/、esp32h21/、esp32h4/、esp32p4/、esp32s2/、esp32s3/、esp32s31/等中的spi_periph.c与include/hal/spi_ll.h例如 esp32c3 的 LL 头文件。这种公共 HAL 代码 每芯片一个 LL 实现的布局正是 README 所述consolidate chip-specific differences收敛芯片差异的实现方式HAL 层代码只调用spi_ll_*系列低层函数不直接触碰寄存器寄存器细节含各芯片不同的 FIFO 深度、时钟分频能力、采样点支持等全部隔离在芯片目录内。芯片目录下的include/soc/spi_pins.h则提供该芯片 SPI 引脚映射定义。外设硬件入口通过 spi_periph.h 中声明的信号连接表暴露extern const spi_signal_conn_t spi_periph_signal[SOC_SPI_PERIPH_NUM];spi_hal_init正是通过该表按 host_id 取到外设寄存器基地址见 spi_hal.cvoid spi_hal_init(spi_hal_context_t *hal, uint32_t host_id) { memset(hal, 0, sizeof(spi_hal_context_t)); spi_dev_t *hw spi_periph_signal[host_id].hw; hal-hw hw; spi_ll_master_init(hw); ... }spi_types.h 中定义了外设控制器枚举注意 SPI3 仅在外设数量大于 2 的芯片上编译typedef enum { SPI1_HOST 0, /// SPI1 SPI2_HOST 1, /// SPI2 #if SOC_SPI_PERIPH_NUM 2 SPI3_HOST 2, /// SPI3 #endif SPI_HOST_MAX, /// invalid host value } spi_host_device_t;主模式Master8 步典型流程与核心数据结构README 给出的主模式无 DMA典型使用流程为初始化总线 → 配置时钟 →setup_device更新设备参数 →setup_trans更新事务参数 → 准备发送数据入硬件寄存器 → 触发事务 → 等待完成 → 取回接收数据。spi_hal.h 文件头部注释与之一字不差地重复了该流程并特别强调第 2 步配置时钟速度因为耗时较长建议在初始化阶段完成。关键上下文结构主模式由spi_hal_context_t贯穿始终驱动和 HAL 共同维护typedef struct { spi_dev_t *hw; /// 外设寄存器起始地址 bool dma_enabled; /// 是否启用 DMA初始化后不要更改 spi_hal_trans_config_t trans_config; /// 事务配置 } spi_hal_context_t;时钟与时序是主模式 HAL 最核心的计算逻辑涉及两个结构spi_hal_timing_param_t计算输入时钟源频率clk_src_hz、是否半双工half_duplex、是否免补偿no_compensate、期望频率expected_freq、占空比duty_cycle、数据有效前 SPI 时钟的最大延迟input_delay_ns未知时置 0、是否走 GPIO 矩阵use_gpiospi_hal_timing_conf_t计算输出LL 层寄存器值clock_reg、时钟源clock_source、预分频source_pre_div、进入外设前的实际源频source_real_freq、期望/实际输出频率、用于补偿时序的额外 dummy 位数timing_dummy、MISO 额外延迟timing_miso_delay以及采样点rx_sample_point。计算入口为spi_hal_cal_clock_conf(const spi_hal_timing_param_t *timing_param, spi_hal_timing_conf_t *timing_conf)文档建议highly suggested to do this at initialization, since it takes long time其结果填入设备配置的timing_conf成员在spi_hal_setup_device时生效。另有三个辅助工具函数函数作用spi_hal_master_cal_clock(fapb, hz, duty_cycle)计算 APB 分频下实际可用的时钟频率spi_hal_cal_timing(...)按源频、实际时钟、是否 GPIO 矩阵等计算 dummy 补偿与 MISO 延迟spi_hal_get_freq_limit(gpio_is_used, input_delay_ns)在不使用补偿时获取允许读取的最高频率spi_hal_get_freq_limit直接对应组件 hints.yml 中为上层驱动错误信息提供的排障提示全双工高频下若外设读不到正确数据可尝试使用 IOMUX 引脚提高频率上限或改用半双工模式SPI 主时钟只能取 80 MHz 的整数分频驱动总是选择最接近配置值的可用频率可用SPI_DEVICE_NO_DUMMY绕过该检查但可能读取不可靠。设备与事务两级配置由spi_hal_dev_config_t与spi_hal_trans_config_t承载体现了 README 所说参数按设备/事务粒度更新的设计spi_hal_dev_config_tSPI 模式mode、CS 建立/保持时间cs_setup/cs_hold、片选引脚cs_pin_id、预计算的timing_conf以及一组位域布尔开关SIO 模式、半双工、TX/RX 的 LSB first、免补偿、正片选positive_cs以及部分芯片支持的as_cs——时钟翻转时同时翻转 CSspi_hal_trans_config_t每笔事务都会刷入外设的参数——命令cmd/cmd_bits、地址addr/addr_bits、dummy 位数dummy_bits开启补偿时可能追加额外 dummy、收发位长tx_bitlen/rx_bitlen、发送/接收缓冲send_buffer/rcv_buffer、线制line_mode与片选保持cs_keep_active。其中spi_line_mode_t分别描述 CMD/ADDR/DATA 三个阶段的线宽实现 README 提到的 1/2/4 线灵活组合typedef struct { uint8_t cmd_lines; /// 命令阶段线宽 uint8_t addr_lines; /// 地址阶段线宽 uint8_t data_lines; /// 数据阶段线宽 } spi_line_mode_t;主模式 API 序列对应 8 步流程的公共 API 均在 spi_hal.h 中声明spi_hal_init(hal, host_id)host_id 0 为 SPI1、1 为 SPI2、2 为 SPI3取决于SOC_SPI_PERIPH_NUM时钟计算spi_hal_cal_clock_conf()结果存于设备配置的timing_confspi_hal_setup_device(hal, hal_dev)按设备配置刷写外设spi_hal_setup_trans(hal, hal_dev, hal_trans)按事务配置刷写外设spi_hal_push_tx_buffer(hal, hal_trans)把 TX 数据写入硬件寄存器spi_hal_user_start(hal)触发事务spi_hal_usr_is_done(hal)轮询 trans_donespi_hal_fetch_result(hal)从缓冲取回接收数据。辅助 API 还包括spi_hal_set_data_pin_idle_level()配置总线空闲时数据 IO 默认电平、spi_hal_enable_data_line()使能/关闭 MOSI/MISO 信号线、spi_hal_hw_prepare_rx()/hw_prepare_tx()为新的 DMA 事务准备硬件、spi_hal_get_intr_mask()/spi_hal_clear_intr_mask()按掩码检查/清中断位。SCT 分段配置传输模式spi_hal.h 末尾专设 SCTSegmented-Configure-Transfer一节SCT 模式下每个 segment 用spi_hal_seg_config_t描述——CONF 状态的seg_end/seg_gap_lenCS 非激活的 SPI 时钟长度、PREP 的cs_setup、CMD 的cmd/cmd_bits、ADDR 的addr/addr_bits、DUMMY 的dummy_bits、DOUT 的tx_bitlen、DIN 的rx_bitlen、DONE 的cs_hold。配套 API 有spi_hal_sct_init()、spi_hal_sct_init_conf_buffer()、spi_hal_sct_format_conf_buffer()、spi_hal_sct_set_conf_bits_len()、spi_hal_sct_deinit()。从 spi_hal.c 源码可见SCT 仅在定义了SPI_LL_PERIPH_HAS_SCT的芯片上编译spi_hal_sct_init使能 CONF 状态机、写入 magic number并把中断从 trans_done 切换为SPI_LL_INTR_SEG_DONE段完成中断deinit 时再恢复——从源码结构看这是为需要在同一事务中连续下发配置段数据段的场景典型如部分 NOR flash 的多命令时序服务的芯片特定能力。从模式Slave被动响应事务的 8 步流程README 给出的从模式无 DMA流程为spi_slave_hal_init初始化 → 在 HAL 上下文中配置设备参数mode、位序等→spi_slave_hal_setup_device刷写外设 → 准备发送数据与接收缓冲 →spi_slave_hal_set_trans_bitlen设置事务位长 →spi_slave_hal_user_start触发 →spi_slave_hal_usr_is_done等待完成 →spi_slave_hal_store_result存回接收数据 →spi_slave_hal_get_rcv_bitlen获取接收长度。spi_slave_hal.h 头部注释还额外点出 DMA 场景需在第 2 步初始化 DMA 描述符并在下次事务前检查/复位 DMA。从模式上下文spi_slave_hal_context_t与主模式有本质差异主模式主动发起、驱动决定长度从模式被动响应、事务长度由主端决定。因此其上下文包含预期最大长度与实际接收结果两组字段typedef struct { spi_dev_t *hw; spi_dma_desc_t *dmadesc_rx; // RX DMA 描述符数组 spi_dma_desc_t *dmadesc_tx; // TX DMA 描述符数组 int dmadesc_n; // HAL 可用的描述符数量 struct { uint32_t rx_lsbfirst : 1; uint32_t tx_lsbfirst : 1; uint32_t use_dma : 1; }; int mode; /* 事务相关每笔事务都刷入外设 */ uint32_t tx_bitlen; // 预期最大 TX 长度bit uint32_t rx_bitlen; // 预期最大 RX 长度bit const void *tx_buffer; void *rx_buffer; uint32_t rcv_bitlen; // 上一笔事务的实际长度bit } spi_slave_hal_context_t;值得注意的两处实现细节DMA 描述符类型随总线而变——spi_slave_hal.h 中当SOC_GDMA_TRIG_PERIPH_SPI2_BUS SOC_GDMA_BUS_AHB时用dma_descriptor_align4_t否则用dma_descriptor_align8_tspi_slave_hal_get_rcv_bitlen的文档特别注明即使上一笔事务实际长度超过配置值返回的仍是实际长度spi_slave_hal_dma_need_reset()专门用于判断 ESP32 等平台在下次事务前是否需要复位从端 DMA。从模式 API 序列为spi_slave_hal_init(hal, hal_config)→spi_slave_hal_setup_device(hal)→spi_slave_hal_hw_fifo_reset()/spi_slave_hal_hw_reset()→spi_slave_hal_push_tx_buffer()→spi_slave_hal_set_trans_bitlen()→spi_slave_hal_enable_data_line()→spi_slave_hal_user_start()→spi_slave_hal_usr_is_done()→spi_slave_hal_store_result()→spi_slave_hal_get_rcv_bitlen()并辅以spi_slave_hal_get_intr_status()/clear_intr_status()与 DMA 准备函数spi_slave_hal_hw_prepare_rx()/tx()。从半双工模式Slave HD事件驱动与共享寄存器缓冲Slave HD 是 GPSPI 的扩展能力仅在支持半双从外设的芯片上编译CMakeLists.txt 中由CONFIG_SOC_SPI_SUPPORT_SLAVE_HD_VER2决定是否加入spi_slave_hd_hal.c。spi_slave_hd_hal.h 头部注释完整描述了其用法模型先用spi_slave_hd_hal_init初始化从端配置项spi_slave_hd_hal_config_t包含host_id、dma_enabled、append_modeDMA 追加模式或分段模式、three_wire_mode、片选引脚spics_io_num、SPImode(0-3)以及命令/地址/dummy 各字段位数均为 8 的倍数且至少 8 位事件处理可选地用spi_slave_hd_hal_enable_event_intr使能所需中断基础用法通过spi_slave_hd_hal_check_clear_event检查并清事件SPI_EV_BUF_TX、SPI_EV_BUF_RX、SPI_EV_CMD9、SPI_EV_CMDA等进阶用法用spi_slave_hd_hal_check_disable_event先关事件中断、之后由任务手动spi_slave_hd_hal_invoke_event_intr触发 ISR针对SPI_EV_SEND、SPI_EV_RECVTX DMAspi_slave_hd_hal_txdma发送数据完成后触发SPI_EV_SENDRX DMAspi_slave_hd_hal_rxdma接收完成后触发SPI_EV_RECV用spi_slave_hd_hal_rxdma_seg_get_len获取接收长度共享寄存器缓冲spi_slave_hd_hal_write_buffer写共享缓冲主端读走无论读地址即触发SPI_EV_BUF_TXspi_slave_hd_hal_read_buffer读共享缓冲主端写入无论写地址即触发SPI_EV_BUF_RX。事件位定义在 spi_types.h 的spi_event_t中typedef enum { /* Slave HD Only */ SPI_EV_BUF_TX BIT(0), // 缓冲数据已发给主端 SPI_EV_BUF_RX BIT(1), // 缓冲收到主端数据 SPI_EV_SEND_DMA_READY BIT(2), // TX 数据经 DMA 载入硬件 SPI_EV_SEND BIT(3), // 主端已收到若干数据 SPI_EV_RECV_DMA_READY BIT(4), // RX 缓冲经 DMA 载入硬件 SPI_EV_RECV BIT(5), // 从端已收到若干数据 SPI_EV_CMD9 BIT(6), // 收到主端 CMD9 SPI_EV_CMDA BIT(7), // 收到主端 CMDA /* Common Event */ SPI_EV_TRANS BIT(8), // 一笔事务完成 } spi_event_t;同文件中的spi_command_t还定义了 Slave HD 专属的硬件命令位写/读缓冲、写/读 DMA、段结束、QPI 使能、写结束、内部中断 0/1/2供 LL 层配置。DMA 描述符采用继承扩展结构spi_slave_hd_hal_desc_append_t硬件描述符 用户传入的事务指针arg从而支持追加模式append mode下用spi_slave_hd_hal_txdma_append()/rxdma_append()在不中断 DMA 的前提下挂接新事务并以spi_slave_hd_hal_get_tx_finished_trans()/get_rx_finished_trans()回收已完成事务头文件注释强调这两个 API 依赖事务完成状态的硬件行为只由本调用层修改这一前提若其他代码清了中断 raw 位则行为会错乱。构建集成、依赖与上层调用链CMakeLists.txt 展示了组件的构建逻辑仅当CONFIG_SOC_GPSPI_SUPPORTED时编译源文件各目标芯片目录下的${target}/spi_periph.c加上通用层spi_hal.c、spi_hal_iram.c、spi_slave_hal.c、spi_slave_hal_iram.ciram 版本用于将关键函数放入 IRAM避免 flash 回读对时序敏感路径的影响spi_slave_hd_hal.c再按CONFIG_SOC_SPI_SUPPORT_SLAVE_HD_VER2条件加入依赖声明为REQUIRES soc hal esp_hal_dma、PRIV_REQUIRES esp_hal_gpio与 README 依赖小节一致soc提供芯片寄存器定义hal提供核心硬件抽象工具与宏如 DMA 描述符类型esp_hal_gpio用于 ESP32 上获取 GPIO 矩阵延迟信息对应spi_hal_timing_param_t.use_gpio/input_delay_ns的时序补偿计算Linux 目标下仅注册头文件目录REQUIRES soc hal无外设实现说明本组件面向裸机固件。上层驱动的实际调用链在 esp_driver_spi 的 CMakeLists.txt 中可见REQUIRES esp_pm esp_hal_gpspi esp_driver_dma其 GPSPI 实现文件 spi_common.c、spi_master.c、spi_slave.c 直接引用hal/spi_hal.h与hal/spi_slave_hal.h印证了 READMEprimarily used by ESP-IDF peripheral drivers such asesp_driver_spi的定位应用侧通过spi_master_*/spi_slave_*公共 API 组包事务驱动内部再把事务翻译成本文所述的setup_device/setup_trans/user_startHAL 序列。使用建议与边界说明优先走上层驱动绝大多数场景应使用esp_driver_spispi_master_init/spi_device_add/spi_device_transmit等HAL 层的beta 内部接口双重声明意味着不应在长期维护的应用中直接依赖其函数签名时钟计算前置spi_hal_cal_clock_conf类计算耗时务必在初始化阶段完成并将结果缓存在timing_conf中而非每笔事务重算频率有硬约束SPI 主时钟是 80 MHz 时钟的整数分频见 hints.yml 的提示实际输出是最接近期望值的可用分频高频全双工读取可能受 GPIO 矩阵延迟限制可用spi_hal_get_freq_limit预先判断上限模式支持随芯片而异Slave HD、SCT、as_cs、正片选等特性都由soc_caps.h能力宏与 LL 层#ifdef编译裁剪具体某芯片支持哪些模式以对应芯片目录下 LL 头文件的实际定义为准。综上esp_hal_gpspi以公共 HAL 流程 按芯片裁剪的 LL 寄存器层组织代码把 GPSPI 主模式、从模式与从半双工模式的寄存器细节收敛在芯片目录内为esp_driver_spi提供了跨芯片一致的底层操作序列与时序计算能力理解它的两层结构与三套 HAL API是进一步阅读 ESP-IDF SPI 驱动全栈、或评估在特定芯片上直接操作 GPSPI 硬件的前提。【免费下载链接】esp-idfEspressif IoT Development Framework. Official development framework for Espressif SoCs.项目地址: https://gitcode.com/GitHub_Trending/es/esp-idf创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表