
ESP-IDF SPI Master 驱动完全指南总线初始化、事务模型、线宽模式与高速传输调优【免费下载链接】esp-idfEspressif IoT Development Framework. Official development framework for Espressif SoCs.项目地址: https://gitcode.com/GitHub_Trending/es/esp-idfSPI Master 驱动是 ESP-IDF 中控制 Espressif SoC 通用 SPIGP-SPI外设作为主机Master发起总线通信的程序。本文以 ESP-IDF 官方文档docs/en/api-reference/peripherals/spi_master.rst为骨架结合 spi_master.h、spi_common.h、spi_master.c 等源码及examples/peripherals/spi_master示例系统讲解 SPI 术语、五阶段事务模型、中断/轮询两种事务类型、Dual/Quad/Octal 线宽模式、总线获取、GPIO Matrix 与 IO_MUX 的引脚选择以及影响传输速度的三大因素与时序调优方案。读完本文你将能够独立完成 SPI 总线的初始化、设备挂载、全双工/半双工事务编排并根据从设备时序特性正确配置频率、dummy 位与输入延迟写出可稳定高速运行的 SPI 驱动代码。基本概念与术语SPI Master 驱动控制的是 {IDF_TARGET_NAME} 的 GP-SPI 外设。需要特别注意的是SPI1 并不是 GP-SPI虽然驱动也支持在 SPI1 总线上运行但存在较多限制属于高级开发者功能详见后文在 SPI1 总线上使用驱动一节。更多硬件信息可参考对应芯片的Technical Reference Manual SPI Controller章节。驱动文档中常用术语定义如下术语定义Host芯片内部的 SPI 控制器外设在总线上发起 SPI 传输并充当 SPI MasterDeviceSPI 从设备。一条 SPI 总线上可挂接一个或多个 Device所有 Device 共享 MOSI、MISO、SCLK 信号但只有当 Host 拉低assert某个 Device 独立的 CS 线时该 Device 才在总线上活跃Bus连接到一个 Host 的所有 Device 共用的信号总线一般包含 MISO、MOSI、SCLK、一条或多条 CS 线可选 QUADWP 与 QUADHD 线Device 之间除各自独立的 CS 线外共用其余信号线也可通过菊花链方式共享一条 CS 线MOSIMaster Out, Slave In又名 DHost 向 Device 发送数据在 Octal/OPI 模式下为 data0 信号MISOMaster In, Slave Out又名 QDevice 向 Host 发送数据在 Octal/OPI 模式下为 data1 信号SCLK串行时钟由 Host 产生、用于同步每一位数据的传输CS片选Host 通过它选择总线上参与收发的单个或多个DeviceQUADWP写保护信号用于 4-bitqio/qout事务在 Octal/OPI 模式下为 data2 信号QUADHDHold 信号用于 4-bitqio/qout事务在 Octal/OPI 模式下为 data3 信号DATA4~DATA7Octal/OPI 模式下的 data4~data7 信号Assertion / De-assertion激活一条信号线 / 将信号线恢复到非激活空闲状态TransactionHost 拉低 CS 线、与 Device 收发数据、再释放 CS 线的完整过程。事务是原子的不会被其他事务打断Launch Edge源寄存器将信号发射到线路上的时钟沿Latch Edge目的寄存器锁存信号的时钟沿驱动特性与线程安全SPI Master 驱动负责协调 Host 与 Device 之间的通信支持以下特性多线程环境驱动基于 FreeRTOS 队列与中断实现可运行于多任务环境DMA 透明处理读写数据时驱动自动处理 DMA 传输无需用户直接操作 DMA 描述符时分复用同一信号总线上的不同 Device 事务由驱动自动进行时间片分派time-division multiplexing其底层实现是总线锁bus lock机制。从 spi_master.c 源码可以看到每个设备在注册时都会调用spi_bus_lock_register_dev()在总线锁上注册一个设备锁句柄spi_bus_lock_dev_handle_tspi_common.c 中则在芯片启动阶段通过构造函数为 SPI0/1 主总线初始化了全局总线锁g_main_spi_bus_lock。线程安全警告驱动允许在同一 SPI 总线上挂接多个 Device共享同一个 SPI 外设。只要每个 Device 只被一个任务访问驱动就是线程安全的但如果多个任务同时访问同一个 SPI Device驱动不是线程安全的。此时建议采用以下两种方案之一重构应用使每个 SPI 外设同一时刻只被单个任务访问可通过 spi_bus_config_t 的isr_cpu_id成员将 SPI 中断ISR注册到与 SPI 相关任务相同的 CPU 核上以保证线程安全使用 FreeRTOS 的xSemaphoreCreateMutex为共享 Device 添加互斥锁。SPI 事务的五阶段模型一条 SPI 总线事务由以下五个阶段组成任意阶段都可以跳过阶段描述Command主机向总线写入命令0~16 bitAddress主机在总线上传输地址0~64 bitESP32 为 0~64 bit其余芯片默认 0~32 bitDummy可配置阶段用于满足时序要求Write主机向 Device 发送数据数据紧跟可选的 Command/Address 阶段之后在电气层面与之无法区分ReadDevice 向主机发送数据事务的属性由三类配置结构共同决定总线配置结构 spi_bus_config_t、设备配置结构 spi_device_interface_config_t 和事务配置结构 spi_transaction_t。全双工与半双工SPI Host 可以发送全双工事务此时 Read 与 Write 阶段同时发生。全双工事务的总长度由以下成员之和决定spi_device_interface_config_t::command_bitsspi_device_interface_config_t::address_bitsspi_transaction_t::length而spi_transaction_t::rxlength仅决定接收进缓冲区的数据长度。在半双工事务中Read 与 Write 阶段不会同时发生一次只有一个方向。Write 与 Read 阶段的长度分别由spi_transaction_t::length和spi_transaction_t::rxlength决定。可选的各阶段Command/Address 阶段可选并非每个 SPI Device 都需要命令和/或地址。当设备的command_bits和/或address_bits被置为 0 时对应的 Command/Address 阶段不会出现。Read/Write 阶段可选并非每次事务都必须同时读写。当spi_transaction_t::rx_buffer为NULL且未设置SPI_TRANS_USE_RXDATA标志时Read 阶段被跳过当tx_buffer为NULL且未设置SPI_TRANS_USE_TXDATA时Write 阶段被跳过。驱动支持中断事务与轮询事务两种类型程序员可以为每个 Device 单独选择事务类型。如果某个 Device 需要同时使用两种事务类型请参考后文混合事务注意事项。中断事务Interrupt Transactions中断事务会阻塞事务例程直到事务完成从而允许 CPU 运行其他任务。应用任务可以一次性排队多个事务驱动在中断服务例程ISR中自动逐个处理任务在其他事务完成前可以切换到其他流程。对应接口为spi_device_queue_trans()将事务排入队列异步执行spi_device_get_trans_result()查询等待排队事务的完成结果spi_device_transmit()等价于queue_transget_trans_result的同步组合。轮询事务Polling Transactions轮询事务不使用中断例程持续轮询 SPI Host 的状态位直到事务结束。所有使用中断事务的任务可能被队列阻塞需要等待 ISR 运行两次才能完成一次事务而轮询事务省去了队列处理与上下文切换的时间事务持续时间更短缺点是事务进行期间 CPU 被占满。spi_device_polling_end()例程在事务结束时需要至少 1 µs 的开销来解除其他任务的阻塞。强烈建议使用spi_device_acquire_bus()与spi_device_release_bus()包裹一系列轮询事务以避免该开销详见总线获取一节。对应接口为spi_device_polling_transmit()发送一次轮询事务并等待完成spi_device_polling_start()/spi_device_polling_end()分步执行便于在两次轮询事务之间插入其他操作。事务线宽模式Transaction Line Mode通过设置 spi_transaction_t 结构体flags成员中的事务标志Transaction Flag可以让数据以 1 线、2 线、4 线甚至 8 线宽度传输对应的总线 IO 能力则通过 spi_bus_config_t 的flags成员中总线 IO 设置标志Bus IO Setting Flag声明。各模式的具体配置如下表模式名称命令线宽地址线宽数据线宽事务标志总线 IO 设置标志标准 SPI11100Dual Output112SPI_TRANS_MODE_DIOSPICOMMON_BUSFLAG_DUALDual I/O122SPI_TRANS_MODE_DIOSPI_TRANS_MULTILINE_ADDRSPICOMMON_BUSFLAG_DUALQuad Output114SPI_TRANS_MODE_QIOSPICOMMON_BUSFLAG_QUADQuad I/O144SPI_TRANS_MODE_QIOSPI_TRANS_MULTILINE_ADDRSPICOMMON_BUSFLAG_QUADOctal Output支持 Octal 的芯片118SPI_TRANS_MODE_OCTSPICOMMON_BUSFLAG_OCTALOPI支持 Octal 的芯片888SPI_TRANS_MODE_OCTSPI_TRANS_MULTILINE_ADDRSPI_TRANS_MULTILINE_CMDSPICOMMON_BUSFLAG_OCTAL说明并非所有芯片都支持 Octal/OPI 模式取决于SOC_SPI_SUPPORT_OCT能力宏。Dual/Quad 模式要求总线初始化时配置对应的 WP/HD 引脚Octal 模式还要求配置 IO4~IO7 引脚这些引脚在 spi_common.h 中对应quadwp_io_num、quadhd_io_num、data4_io_num~data7_io_num成员。从源码可以看到SPICOMMON_BUSFLAG_DUAL、SPICOMMON_BUSFLAG_QUAD、SPICOMMON_BUSFLAG_OCTAL之间存在逐级包含关系QUAD DUAL|WPHDOCTAL QUAD|IO4_IO7。Command 与 Address 阶段详解在 Command 和 Address 阶段事务结构体中的spi_transaction_t::cmd和spi_transaction_t::addr会被发送到总线此时不会读取任何数据。Command/Address 阶段的默认长度通过调用spi_bus_add_device()时传入的 spi_device_interface_config_t 设置如果spi_transaction_t::flags中没有设置SPI_TRANS_VARIABLE_CMD与SPI_TRANS_VARIABLE_ADDR驱动会在设备初始化时自动将阶段长度设为默认值。如果需要可变的 Command/Address 长度应声明结构体 spi_transaction_ext_t在base的flags中设置SPI_TRANS_VARIABLE_CMD和/或SPI_TRANS_VARIABLE_ADDR其余按常规配置然后各阶段长度即为spi_transaction_ext_t::command_bits和spi_transaction_ext_t::address_bits。该结构体还提供了dummy_bits成员配合SPI_TRANS_VARIABLE_DUMMY使用可让 dummy 阶段长度也可变。从源码看spi_transaction_ext_t的结构是typedef struct { struct spi_transaction_t base; // 事务数据spi_transaction_t 指针可直接转换为 spi_transaction_ext_t uint8_t command_bits; // 本次事务的命令长度bit uint8_t address_bits; // 本次事务的地址长度bit uint8_t dummy_bits; // 本次事务的 dummy 长度bit } spi_transaction_ext_t;如果需要 Command/Address 阶段与数据阶段使用相同的线宽需要在spi_transaction_t::flags中设置SPI_TRANS_MULTILINE_CMD和/或SPI_TRANS_MULTILINE_ADDR注意源码中SPI_TRANS_MULTILINE_ADDR与SPI_TRANS_MODE_DIOQIO_ADDR是同一个宏值均为(14)。这也正是上表 Dual I/O、Quad I/O、OPI 模式的做法。Write 与 Read 阶段缓冲区要求正常情况下收发数据读写自spi_transaction_t::rx_buffer与tx_buffer指向的内存块。如果启用了 DMA缓冲区必须满足分配在 DMA 能力内部内存中MALLOC_CAP_DMA32 位对齐起始地址位于 32 位边界长度为 4 字节的整数倍。不满足上述要求时驱动会因临时缓冲区的分配与拷贝而影响事务效率。驱动还提供了辅助函数spi_bus_dma_memory_alloc()在spi_bus_initialize()之后调用可自动处理缓存与硬件对齐要求见 spi_common.h。如果使用多于一条数据线传输请务必在spi_device_interface_config_t::flags中设置SPI_DEVICE_HALFDUPLEX标志并按事务线宽模式一节配置事务标志。注意在不支持SOC_SPI_HD_BOTH_INOUT_SUPPORTED的芯片上半双工事务不能同时包含 Read 和 Write 阶段请改用全双工模式在 ESP32 上使用 DMA 时也不支持同时含 Read 与 Write 阶段的半双工事务详见已知问题。总线获取Bus Acquiring有时你希望独占并连续发送 SPI 事务使耗时尽可能短。此时可以使用总线获取功能在总线被释放前其他 Device 的事务无论轮询还是中断都会被挂起。使用spi_device_acquire_bus()获取总线、spi_device_release_bus()释放总线。注意spi_device_acquire_bus()的wait参数目前必须设置为portMAX_DELAY否则返回ESP_ERR_INVALID_ARG见 spi_master.h 源码注释。睡眠保持Sleep Retention支持该特性的芯片由SOC_SPI_SUPPORT_SLEEP_RETENTION决定可以在进入**浅睡眠light sleep**前保留 SPI 寄存器上下文唤醒后自动恢复因此无需在浅睡眠后重新初始化 SPI 驱动。通过设置SPICOMMON_BUSFLAG_SLP_ALLOW_PD标志即可启用它允许系统在浅睡眠期间关闭 SPI 电源并保存寄存器上下文以额外内存开销换取更低功耗该标志定义于 spi_common.h值为(19)。驱动使用流程标准使用步骤初始化 SPI 总线调用spi_bus_initialize(host_id, buscfg, dma_chan)在 spi_bus_config_t 中设置正确的 IO 引脚不需要的信号设为-1。DMA 通道参数SPI_DMA_DISABLED关闭 DMA事务长度受限、SPI_DMA_CH_AUTO让驱动自动分配 DMA 通道推荐、ESP32 上还可显式选择SPI_DMA_CH1/SPI_DMA_CH2。注册设备调用spi_bus_add_device(host_id, devcfg, handle)将 Device 挂到总线上通过dev_config配置设备需要的任何时序参数并获得用于后续事务的设备句柄spi_device_handle_t。ESP32 上所有 SPI Master 外设都有 3 个 CS 引脚因此一个 Host 最多可控制 3 个设备。填充并发送事务填写一个或多个 spi_transaction_t然后选择中断或轮询方式发送中断事务可调用spi_device_queue_trans()排队全部事务稍后用spi_device_get_trans_result()查询结果或调用spi_device_transmit()同步处理所有请求。轮询事务调用spi_device_polling_transmit()发送若需在事务之间插入操作用spi_device_polling_start()spi_device_polling_end()。可选背靠背事务发送前调用spi_device_acquire_bus()发送完调用spi_device_release_bus()。可选移除设备以设备句柄为参数调用spi_bus_remove_device()。可选释放总线确保总线上没有设备后调用spi_bus_free()。参考示例代码SPI Master 驱动的示例位于 examples/peripherals/spi_master 目录。以hd_eeprom为例spi_eeprom_main.c其初始化总线与挂载设备的典型代码为spi_bus_config_t buscfg { .miso_io_num PIN_NUM_MISO, .mosi_io_num PIN_NUM_MOSI, .sclk_io_num PIN_NUM_CLK, .quadwp_io_num -1, .quadhd_io_num -1, .max_transfer_sz 32, }; ret spi_bus_initialize(EEPROM_HOST, buscfg, SPI_DMA_CH_AUTO); ESP_ERROR_CHECK(ret); spi_device_interface_config_t devcfg { .command_bits 10, // EEPROM 指令 10 bit .clock_speed_hz EEPROM_CLK_FREQ,// 3.3V 供电下 EEPROM 最高 1MHz .mode 0, // SPI mode 0 .spics_io_num -1, // CS 由软件控制GPIO 手动拉低/拉高 .queue_size 1, .flags SPI_DEVICE_HALFDUPLEX | SPI_DEVICE_POSITIVE_CS, .pre_cb cs_high, .post_cb cs_low, .input_delay_ns EEPROM_INPUT_DELAY_NS, // EEPROM 输出滞后 SPI 时钟半拍 }; ret spi_bus_add_device(EEPROM_HOST, devcfg, ctx-spi);这个示例非常有代表性它使用半双工模式读写 AT93C46D EEPROM通过pre_cb/post_cb回调在事务开始前/结束后手动控制 CS 电平来满足芯片的忙检测时序并演示了SPI_DEVICE_HALFDUPLEX、SPI_DEVICE_POSITIVE_CS与input_delay_ns的配合使用见 spi_eeprom.c。另一个示例lcd演示了如何用 SPI Master 驱动在 LCD 上显示动画借助 DMA 实现渲染与刷屏并行并利用 SPI 事务钩子函数pre_cb/post_cb驱动 D/C 信号电平见 spi_master_example_main.c。高级事务特性PSRAM 上的 DMA 事务在支持SOC_PSRAM_DMA_CAPABLE的芯片上GPSPI Master 可以通过为事务添加SPI_TRANS_DMA_USE_PSRAM标志让 DMA 直接从 PSRAM 收发数据而无需额外的内部拷贝从而节省内存。需要注意该特性与 MSPI 总线共享带宽总线频率 × 总线位宽因此 GPSPI 传输带宽必须小于 PSRAM 带宽否则可能丢失传输数据。事务结束后可通过返回值或SPI_TRANS_DMA_RX_FAILRX 溢出与SPI_TRANS_DMA_TX_FAILTX 下溢标志判断传输是否出错若事务返回ESP_ERR_INVALID_STATE说明事务失败。不超过 32 位的数据事务当事务数据大小不超过 32 位时为数据分配缓冲区并不划算数据可以直接存放在事务结构体中发送数据使用spi_transaction_t::tx_data成员并设置SPI_TRANS_USE_TXDATA标志接收数据使用spi_transaction_t::rx_data成员并设置SPI_TRANS_USE_RXDATA标志。两种情况下都不要去触碰tx_buffer或rx_buffer成员因为它们在内存中与tx_data/rx_data共用同一块存储位置源码中二者位于同一个 union 内。非 uint8_t 整数的传输SPI Host 按字节逐字节读写内存。默认情况下数据**高位在前MSB first**发送LSB first 只在极少数情况下使用。如果需要发送少于 8 位的值位应按照 MSB first 方式写入内存。例如需要发送0b000105 bit应写入一个uint8_t变量读取长度设为 5 bit。Device 实际仍会收到 8 位含 3 个随机位因此读取必须正确进行。并非所有芯片都支持任意位长的数据传输发送或接收不支持的位长将返回ESP_ERR_NOT_SUPPORTED错误。支持情况如下表YES 表示支持任意位长NO 表示仅支持整字节即 8 的倍数方向模式ESP32ESP32-S2ESP32-S3/C2/C3/C6ESP32-H2/P4/C5/C61TXDMAYESYES(bit_len % 8) ! 1YESTX无 DMAYESYES(bit_len % 8) ! 1YESRXDMANONOYESYESRX无 DMAYESNOYESYES如果确实需要使用不支持的位长有两种替代方案组合使用spi_transaction_t::cmd/addr与数据阶段缺点是 Command 和 Address 阶段无法从从设备接收数据用两段受支持的传输组合实现例如用27实现 9 bit 传输同时保持 CS 线有效缺点是两段传输之间存在最小时间间隔见事务持续时间整体传输速率更低。此外{IDF_TARGET_NAME} 是小端little-endian芯片uint16_t/uint32_t变量的最低有效字节存放在最小地址因此内存中存放uint16_t时bits [7:0] 先发送然后是 bits [15:8]。对于数据大小不同于uint8_t数组的情况可使用以下宏将数据转换为 SPI 驱动可直接发送的格式定义于 spi_common.hSPI_SWAP_DATA_TX(DATA, LEN)转换待发送数据例如uint16_t data SPI_SWAP_DATA_TX(0x145, 9);后将tx_buffer指向dataSPI_SWAP_DATA_RX(DATA, LEN)转换接收数据例如uint16_t data SPI_SWAP_DATA_RX(*(uint32_t*)t-rx_data, 15);。两个宏的实质是利用__builtin_bswap32进行字节序重排发送时左移到 MSB、接收时右移到 LSB。混合事务注意事项为降低编码复杂度建议对同一个 Device 只发送一种类型的事务中断或轮询。不过驱动仍允许交替发送中断事务与轮询事务需遵守以下规则轮询事务只能在所有轮询与中断事务完成后发起未完成的轮询事务会阻塞其他事务因此spi_device_polling_start()之后不要忘记调用spi_device_polling_end()以便让其他事务或其他 Device 使用总线如果轮询期间无需切换任务可直接用spi_device_polling_transmit()一次性完成进行中的轮询事务会被 ISR 操作打扰以容纳中断事务。调用spi_device_polling_start()前务必确保发往 ISR 的所有中断事务都已结束可反复调用spi_device_get_trans_result()直到所有事务返回为更好地控制函数调用顺序混合事务应只在单个任务内发送。在 SPI1 总线上使用驱动ESP32虽然总线锁特性使 SPI Master 驱动可以在 SPI1 总线上运行但这仍然需要大量特殊处理属于高级开发者功能。在 SPI1 总线上使用驱动需要解决两个问题问题 1驱动操作 SPI1 总线时代码和数据必须位于内部内存。SPI1 总线由设备与 flash/PSRAM 的数据代码缓存共享。当其他驱动占用 SPI1 总线时缓存会被禁用因此在以下时间段内无法从 flash/PSRAM 取数据代码显式获取spi_device_acquire_bus()与spi_device_release_bus()之间隐式获取spi_device_polling_start()与spi_device_polling_end()之间或spi_device_polling_transmit()内部。上述时间内所有其他任务和大部分 ISR 都会被禁用当前任务使用的应用代码与数据必须放在内部内存DRAM 或 IRAM或已在 ROM 中。访问外部内存flash 代码、flash 中的 const 数据、PSRAM 中的静态/堆数据将触发Cache disabled but cached memory region accessed异常。将函数放入 IRAM 的方法// 方法 1IRAM_ATTR 属性需包含 esp_attr.h IRAM_ATTR void foo(void) { }注意函数被内联时会跟随调用者的段属性不生效可能需要NOLINE_ATTR编译器还可能把部分代码转换为 const data 中的查找表因此noflash_text并不绝对安全。方法 2 是在linker.lf中使用noflash放置详见 linker-script-generation 文档。编译优化等级可能影响内联或查找表转换等编译器行为。将数据放入 DRAM 的方法// 方法 1DRAM_ATTR 属性需包含 esp_attr.h DRAM_ATTR int g_foo 3;或同样在linker.lf中使用noflash放置。可参考示例 examples/peripherals/spi_master/hd_eeprom该示例在 ESP32 上可通过 Kconfig 选项改用 SPI1 引脚。GPIO Matrix 与 IO_MUX大部分芯片的外设信号都有专用的 IO_MUX 引脚直达连接信号也可以通过更灵活的 GPIO matrix 路由到任意可用引脚。只要有一个信号经由 GPIO matrix 路由所有信号都会经由 GPIO matrix 路由。ESP32 上的差异GPIO matrix 在带来布线灵活性的同时也有两个缺点增大了 MISO 信号的输入延迟使 MISO 建立时间违例更可能发生若 SPI 需要高速运行应使用专用 IO_MUX 引脚经 GPIO matrix 的信号时钟频率最高只能到 40 MHz而使用 IO_MUX 引脚可达 80 MHz。ESP32 的 SPI 总线 IO_MUX 引脚如下表CS0 只能由总线上第一个挂载的 Device 使用引脚名SPI2GPIO 号SPI3GPIO 号CS0155SCLK1418MISO1219MOSI1323QUADWP222QUADHD421其他芯片对于其他芯片当 SPI Host 频率为 40 MHz 或更低时经 GPIO matrix 路由与经 IO_MUX 路由行为相同。SPI2 的 IO_MUX 引脚因芯片而异例如ESP32-S2/S3 的 CS010、SCLK12、MOSI11、MISO13、WP14、HD9ESP32-C3 的 CS010、SCLK6、MOSI7、MISO2、WP5、HD4ESP32-C6 的 CS016、SCLK6、MOSI7、MISO2、WP5、HD4ESP32-H2 的 CS01、SCLK4、MOSI5、MISO0、WP2、HD3ESP32-P4 的 CS07、SCLK9、MOSI8、MISO10、WP11、HD6。具体数值请以芯片数据手册为准。可以在总线配置中通过SPICOMMON_BUSFLAG_IOMUX_PINS/SPICOMMON_BUSFLAG_GPIO_PINS标志显式声明期望的引脚路由方式spi_common.h 中SPICOMMON_BUSFLAG_NATIVE_PINS与SPICOMMON_BUSFLAG_IOMUX_PINS等价。传输速度考量有三个因素限制传输速度事务间隔Transaction intervalSPI 时钟频率SPI clock frequencySPI 函数包括回调的缓存未命中Cache miss对于大事务决定传输速度的主要参数是时钟频率对于多个小事务传输速度主要由事务间隔长度决定。事务持续时间事务持续时间包括设置 SPI 外设寄存器、将数据拷贝到 FIFO 或建立 DMA 链接以及 SPI 事务本身的时间。中断事务会附加额外开销用于容纳 FreeRTOS 队列的成本以及任务与 ISR 之间切换的时间。中断事务进行期间 CPU 可以切换到其他任务节省 CPU 时间但增加事务持续时间轮询事务不阻塞任务在事务进行期间允许轮询。若启用 DMA建立链表linked list每个事务约需 2 µs主机传输时自动从链表读取数据若未启用 DMACPU 必须自行向/从 FIFO 逐字节读写通常快于 2 µs但事务长度被限制为读写各 64 字节。每字节数据的典型事务持续时间各芯片略有差异下表为 ESP32 数值中断事务 DMA约 28 µs中断事务 CPU约 25 µs轮询事务 DMA约 10 µs轮询事务 CPU约 8 µs注意这些数据是在启用CONFIG_SPI_MASTER_ISR_IN_IRAM的条件下测试的SPI 事务相关代码位于内部内存。若关闭该选项例如为优化内部内存事务持续时间可能受影响。SPI 时钟频率GPSPI 外设的时钟源可通过spi_device_interface_config_t::clock_source选择参考spi_clock_source_t枚举。默认情况下驱动将时钟源设为SPI_CLK_SRC_DEFAULT通常代表 GPSPI 支持的时钟源中频率最高者其值因芯片而异。设备实际时钟频率不一定恰好等于设定值驱动会将其重新计算为最接近的硬件兼容数值且不超过所选时钟源频率。可通过spi_device_get_actual_freq()查询驱动计算出的实际频率。另外SPI 外设只有整数分频器因此实际频率可能与期望频率略有偏差——spi_master.h 提供了一组便捷频率宏例如SPI_MASTER_FREQ_8M80MHz/10、SPI_MASTER_FREQ_26M80MHz/3、SPI_MASTER_FREQ_40M80MHz/2、SPI_MASTER_FREQ_80M80MHz/1等。设备时钟频率可在传输过程中通过设置spi_transaction_t::override_freq_hz改变该操作会对设备当前及后续传输使用新频率。如果期望频率无法实现驱动会打印警告并继续使用之前的频率传输。Write/Read 阶段的理论最大传输速度计算公式如下其他阶段 Command/Address/Dummy 的计算类似Write/Read 阶段线宽速度Bps1 线SPI Frequency / 82 线SPI Frequency / 44 线SPI Frequency / 28 线支持 Octal 的芯片SPI Frequency缓存未命中Cache Missing默认配置只把 ISR 放入 IRAM。其他 SPI 相关函数包括驱动本身和回调可能遭遇缓存未命中需要等待代码从 flash 读出。选择CONFIG_SPI_MASTER_IN_IRAM可将整个 SPI 驱动放入 IRAM并把所有回调及其被调函数放入 IRAM 以避免缓存未命中。注意SPI 驱动实现基于 FreeRTOS API要使用CONFIG_SPI_MASTER_IN_IRAM需同时启用CONFIG_FREERTOS_IN_IRAM。该选项默认关闭启用后会连带选中SPI_MASTER_ISR_IN_IRAM与ESP_SPI_BUS_LOCK_FUNCS_IN_IRAM见 components/esp_driver_spi/Kconfig。CONFIG_SPI_MASTER_ISR_IN_IRAM默认开启它依赖CONFIG_HEAP_PLACE_FUNCTION_INTO_FLASH关闭因为 SPI Master 会通过 heap 组件 API 分配 DMA 内存段缓冲区而 heap 函数必须位于 IRAM。对于中断事务n 字节单次事务的总开销约为208n/Fspi[MHz] [µs]因此传输速度为n/(208n/Fspi)。以 8 MHz 时钟为例频率MHz事务间隔µs事务长度字节总时间µs总速度KBps82512638.5825833242.48251641490.28256489719.1825128153836.6当事务长度较短时事务间隔成本占比很高。如果可能尽量把多个短事务合并为一个大事务以获得更高传输速度。另外默认情况下 flash 操作期间 ISR 会被禁用。若要 flash 操作期间继续发送事务需启用CONFIG_SPI_MASTER_ISR_IN_IRAM并在spi_bus_config_t::intr_flags中设置ESP_INTR_FLAG_IRAM。此时 flash 操作前已排队的所有事务会由 ISR 并行处理。还需注意每个 Device 的回调及其被调函数应放在 IRAM 中否则回调会因缓存未命中而崩溃。时序考量ESP32如下图所示SCLK 发射沿之后、信号被内部寄存器锁存之前MISO 线上存在延迟。因此 MISO 引脚建立时间是 SPI 时钟速度的限制因素当延迟过长时建立余量setup slack 0说明建立时序要求被违反读取可能出错。最大允许频率取决于spi_device_interface_config_t::input_delay_ns——SCLK 一个时钟周期开始后MISO 总线上的最大数据有效时间是否使用 IO_MUX 引脚或 GPIO matrix。使用 GPIO matrix 时与现有input delay相比最大允许频率降至约 33%~77%。要保持更高频率必须使用 IO_MUX 引脚或dummy bit workaround。可通过spi_get_freq_limit()获取主机的最大读取频率。Dummy bit workaround虚拟位变通方案可以在 Read 阶段开始前插入 dummy 时钟期间 Host 不读数据。Device 仍会看到 dummy 时钟并发出数据但 Host 直到 Read 阶段才读取。这弥补了 Host 所需的 MISO 建立时间使 Host 可以在更高频率下读取。理想情况下若 Device 足够快、输入延迟小于一个 APB 时钟周期12.5 ns则不同条件下 Host 可读取或读写的最大频率如下频率上限MHzGPIO Matrix频率上限MHzIO_MUX 引脚驱动使用的 Dummy 位备注26.680否40--是半双工不允许 DMA如果 Host 只写数据可通过在spi_device_interface_config_t::flags中设置SPI_DEVICE_NO_DUMMY位禁用dummy bit workaround与频率检查禁用后即使使用 GPIO matrix输出频率也可达 80 MHz。input_delay_ns设为 0 时 SPI Master 驱动仍可工作但设置准确的值有助于计算全双工事务的频率上限用 dummy 位正确补偿半双工事务的时序。可通过从设备规格书的 AC 特性章节统计近似最大数据有效时间或用示波器/逻辑分析仪实测。实际 PCB 布局与过载会增加输入延迟——非最优布线或总线上的负载电容很可能使输入延迟超过规格书数值或浮空测量值。一些典型延迟值从设备位于不同物理芯片时测得设备输入延迟ns理想设备0使用 IO_MUX 的 ESP32 从机50使用 GPIO_MATRIX 的 ESP32 从机75MISO 路径延迟有效时间由从机的input delay加上主机的GPIO matrix 延迟组成。该延迟决定上述全双工传输的频率上限超过后全双工传输将不如使用 dummy 位的半双工事务。频率上限计算公式Freq limit [MHz] 80 / (floor(MISO delay[ns]/12.5) 1)下图展示频率上限与输入延迟的关系若主机使用 GPIO matrix应在 MISO 延迟上额外加上两个 APB 时钟周期。不同输入延迟对应的频率上限主机为 IO_MUX0 ns 延迟输入延迟nsMISO 路径延迟ns频率上限MHz0080505016757511.43主机为 GPIO_MATRIX25 ns 延迟输入延迟nsMISO 路径延迟ns频率上限MHz02526.67507511.43751008.89已知问题ESP32半双工事务与 DMA 不兼容当同时使用 Write 与 Read 阶段时。若必须使用此类事务可选以下替代方案改用全双工事务禁用 DMA将总线初始化函数的最后一个参数设为 0ret spi_bus_initialize(SPI3_HOST, buscfg, 0);——但这会限制收发数据不超过 64 字节尝试用 command 与 address 字段替代 Write 阶段。全双工事务与 dummy bit workaround 不兼容因此频率受限见时序考量。spi_device_interface_config_t::dummy_bits与spi_transaction_ext_t::dummy_bits在 SPI Read 和 Write 阶段同时启用时不可用无论全双工还是半双工模式。cs_ena_pretrans与全双工事务的 Command/Address 阶段不兼容。启用 DMA 时RX 缓冲区应为字对齐起始地址位于 32 位边界长度为 4 字节的整数倍否则 DMA 可能覆盖未对齐部分的数据。此外ESP32 若时钟频率过高部分函数的使用可能受限见时序考量ESP32-H2v1.2 及以后版本还支持通过spi_device_interface_config_t::sample_point配置 Sample Phase 0/1 两种接收采样模式默认使用 sample phase 0在标准 SPI 时序下高频时采样更稳定。应用示例总览examples/peripherals/spi_master/hd_eeprom演示在 {IDF_TARGET_NAME} 上使用 SPI Master 半双工模式读写 AT93C46D EEPROM8-bit 模式覆盖了中断/轮询等待、CS 软件控制与input_delay_ns配置等关键技巧examples/peripherals/spi_master/lcd演示使用 SPI Master 驱动在 LCD 上显示动画借助 DMA 实现渲染与刷屏并行并利用 SPI 事务钩子函数驱动 D/C 信号电平。完整 API 细节可进一步查阅 spi_master.h设备配置、事务结构、SPI_DEVICE_/SPI_TRANS_标志与 spi_common.h总线配置、SPICOMMON_BUSFLAG_* 标志、频率宏与数据交换宏以及驱动源文件 spi_master.c 与 spi_common.c。【免费下载链接】esp-idfEspressif IoT Development Framework. Official development framework for Espressif SoCs.项目地址: https://gitcode.com/GitHub_Trending/es/esp-idf创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考