
一、SPI通信基础SPISerial Peripheral Interface串行外设接口是摩托罗拉公司推出的一种高速、全双工、同步的串行通信总线协议广泛应用于微控制器与各种外设如Flash存储器、SD卡、LCD显示屏、传感器等之间的通信。1.1 SPI通信特点全双工同步通信主从设备可以同时发送和接收数据高速传输通常可达几MHz到几十MHz的时钟频率主从架构一个主设备可控制多个从设备硬件简单仅需4根信号线即可实现通信无寻址机制通过片选信号选择从设备1.2 SPI信号线SPI通信需要4根基本信号线SCKSerial Clock时钟信号由主设备产生MOSIMaster Out Slave In主设备输出从设备输入MISOMaster In Slave Out主设备输入从设备输出NSS/CSSlave Select片选信号低电平有效二、STM32 SPI外设STM32系列微控制器通常集成多个SPI接口如SPI1、SPI2、SPI3等支持主从模式、全双工/半双工通信、多种数据格式和时钟极性/相位配置。2.1 SPI工作模式STM32 SPI支持以下工作模式全双工模式同时发送和接收数据最常用半双工模式同一时刻只能发送或接收只发送模式仅发送数据不接收只接收模式仅接收数据不发送2.2 时钟配置SPI时钟配置涉及两个关键参数CPOLClock Polarity时钟极性CPOL0空闲时SCK为低电平CPOL1空闲时SCK为高电平CPHAClock Phase时钟相位CPHA0数据在第一个时钟边沿采样CPHA1数据在第二个时钟边沿采样CPOL和CPHA的组合形成4种SPI模式Mode 0-3通信双方必须配置相同的模式。三、STM32 SPI编程实战下面以STM32F103系列为例演示如何使用HAL库进行SPI通信。3.1 SPI初始化配置#include stm32f1xx_hal.h SPI_HandleTypeDef hspi1; void SPI1_Init(void) { __HAL_RCC_SPI1_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); // GPIO配置 GPIO_InitTypeDef GPIO_InitStruct {0}; GPIO_InitStruct.Pin GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7; // SCK, MISO, MOSI GPIO_InitStruct.Mode GPIO_MODE_AF_PP; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOA, GPIO_InitStruct); // SPI配置 hspi1.Instance SPI1; hspi1.Init.Mode SPI_MODE_MASTER; hspi1.Init.Direction SPI_DIRECTION_2LINES; hspi1.Init.DataSize SPI_DATASIZE_8BIT; hspi1.Init.CLKPolarity SPI_POLARITY_LOW; // CPOL 0 hspi1.Init.CLKPhase SPI_PHASE_1EDGE; // CPHA 0 (Mode 0) hspi1.Init.NSS SPI_NSS_SOFT; hspi1.Init.BaudRatePrescaler SPI_BAUDRATEPRESCALER_8; // 9MHz (72MHz/8) hspi1.Init.FirstBit SPI_FIRSTBIT_MSB; hspi1.Init.TIMode SPI_TIMODE_DISABLE; hspi1.Init.CRCCalculation SPI_CRCCALCULATION_DISABLE; hspi1.Init.CRCPolynomial 10; HAL_SPI_Init(hspi1); }3.2 SPI数据收发函数// 发送一个字节并接收一个字节 uint8_t SPI_TransmitReceive(uint8_t txData) { uint8_t rxData 0; // 拉低片选软件控制 HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); // 发送并接收数据 HAL_SPI_TransmitReceive(hspi1, txData, rxData, 1, 1000); // 释放片选 HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); return rxData; } // 发送多个字节 void SPI_Transmit(uint8_t *pData, uint16_t Size) { HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); HAL_SPI_Transmit(hspi1, pData, Size, 1000); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); } // 接收多个字节 void SPI_Receive(uint8_t *pData, uint16_t Size) { HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); HAL_SPI_Receive(hspi1, pData, Size, 1000); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); }3.3 与W25Qxx Flash通信示例// W25Qxx指令定义 #define W25X_WriteEnable 0x06 #define W25X_WriteDisable 0x04 #define W25X_ReadStatusReg 0x05 #define W25X_WriteStatusReg 0x01 #define W25X_ReadData 0x03 #define W25X_FastReadData 0x0B #define W25X_PageProgram 0x02 #define W25X_SectorErase 0x20 // 读取Flash ID uint32_t W25Q_ReadID(void) { uint32_t Temp 0; uint8_t cmd[4] {0x90, 0x00, 0x00, 0x00}; // 读取ID指令 HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); HAL_SPI_Transmit(hspi1, cmd, 4, 1000); HAL_SPI_Receive(hspi1, (uint8_t*)Temp, 3, 1000); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); return Temp; } // 读取数据 void W25Q_Read(uint8_t* pBuffer, uint32_t ReadAddr, uint16_t NumByteToRead) { uint8_t cmd[4]; cmd[0] W25X_ReadData; cmd[1] (uint8_t)((ReadAddr 0xFF0000) 16); cmd[2] (uint8_t)((ReadAddr 0xFF00) 8); cmd[3] (uint8_t)(ReadAddr 0xFF); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); HAL_SPI_Transmit(hspi1, cmd, 4, 1000); HAL_SPI_Receive(hspi1, pBuffer, NumByteToRead, 1000); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); }四、特殊使用软件模拟SPI通信当MCU外设不够时可以使用GPIO模拟SPI进行通信对于调试SPI有着极大的灵活性但会带来更高的 CPU 占用和更复杂的时序控制。4.1 引脚初始化配置GPIO引脚为输出模式SCLK、MOSI、SS和输入模式MISO初始化时钟线和片选线为高电平根据SPI模式调整。// 示例代码基于STM32 HAL库 void SPI_GPIO_Init() { GPIO_InitTypeDef GPIO_InitStruct {0}; // 配置SCLK、MOSI、SS为输出 GPIO_InitStruct.Pin SPI_SCLK_PIN | SPI_MOSI_PIN | SPI_SS_PIN; GPIO_InitStruct.Mode GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull GPIO_NOPULL; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(SPI_PORT, GPIO_InitStruct); // 配置MISO为输入 GPIO_InitStruct.Pin SPI_MISO_PIN; GPIO_InitStruct.Mode GPIO_MODE_INPUT; HAL_GPIO_Init(SPI_PORT, GPIO_InitStruct); // 初始状态片选高不选中时钟高模式0 HAL_GPIO_WritePin(SPI_PORT, SPI_SS_PIN, GPIO_PIN_SET); HAL_GPIO_WritePin(SPI_PORT, SPI_SCLK_PIN, GPIO_PIN_SET); }4.2 数据传输函数根据SPI模式如模式0时钟空闲为高数据在上升沿采样实现逐位读写uint8_t SPI_Sim_TransferByte(uint8_t tx_data) { uint8_t rx_data 0; for (uint8_t i 0; i 8; i) { if (tx_data 0x80) { HAL_GPIO_WritePin(SPI_GPIO_PORT, SPI2_MOSI_Pin, GPIO_PIN_SET); } else { HAL_GPIO_WritePin(SPI_GPIO_PORT, SPI2_MOSI_Pin, GPIO_PIN_RESET); } HAL_GPIO_WritePin(SPI_GPIO_PORT, SPI2_CLK_Pin, GPIO_PIN_SET); rx_data 1; if (HAL_GPIO_ReadPin(SPI_GPIO_PORT, SPI2_MISO_Pin) GPIO_PIN_SET) { rx_data | 0x01; } HAL_GPIO_WritePin(SPI_GPIO_PORT, SPI2_CLK_Pin, GPIO_PIN_RESET); tx_data 1; } return rx_data; } uint32_t SCL3300_Sim_Transceive(uint32_t cmd) { uint32_t response 0; uint8_t *cmd_bytes (uint8_t*)cmd; uint8_t *resp_bytes (uint8_t*)response; HAL_GPIO_WritePin(SPI_GPIO_PORT, SPI2_CS_Pin, GPIO_PIN_RESET); for (int i 3; i 0; i--) { resp_bytes[i] SPI_Sim_TransferByte(cmd_bytes[i]); } HAL_GPIO_WritePin(SPI_GPIO_PORT, SPI2_CS_Pin, GPIO_PIN_SET); HAL_Delay(1); return response; }4.3 时序调整时钟频率通过调整延时函数如Delay_us()控制SCLK速度需匹配从设备要求。模式支持修改时钟极性和相位CPOL/CPHA以适配不同SPI模式。4.4 注意事项信号完整性软件模拟SPI速率较低通常1MHz高频应用需硬件SPI。中断处理避免在传输过程中被中断打断可临时关闭中断。多从设备每个从设备需独立片选引脚传输前选中对应设备。4.5 验证方法通过逻辑分析仪或示波器观察SCLK、MOSI/MISO波形确保数据对齐时钟边沿并检查从设备的响应数据。五、SPI通信注意事项5.1 时序匹配主从设备必须配置相同的时钟极性和相位注意时钟频率不能超过从设备的最大支持频率考虑信号传输延迟适当降低时钟频率5.2 片选信号管理片选信号必须在数据传输前拉低传输完成后拉高多从机系统中同一时刻只能有一个片选有效软件片选NSS Soft需要手动控制GPIO5.3 数据对齐STM32支持8位和16位数据格式注意MSB/LSB高位在前/低位在前设置从设备的数据格式必须与主设备匹配5.4 错误处理检查SPI状态标志BUSY、TXE、RXNE等实现超时机制防止通信卡死使用DMA传输时注意缓冲区管理六、SPI与I2C、UART对比特性SPII2CUART通信方式同步、全双工同步、半双工异步、全双工信号线数量4根3CS2根2根TX、RX传输速率高MHz级中KHz-MHz低通常1Mbps寻址方式硬件片选软件地址点对点硬件复杂度简单中等简单适用场景高速外设多设备低速通信设备间通信七、常见问题与调试技巧7.1 通信失败排查步骤检查电源和地线连接是否正常确认时钟极性和相位配置一致使用示波器或逻辑分析仪观察信号波形检查片选信号时序是否正确验证从设备是否处于就绪状态7.2 性能优化建议使用DMA传输减少CPU占用合理设置SPI时钟分频器批量传输数据减少片选切换使用硬件NSS信号提高稳定性7.3 多从机系统设计为每个从设备分配独立的片选引脚考虑总线负载和信号完整性实现从设备状态管理机制注意从设备切换时的时序要求八、总结SPI是STM32与外部设备通信的重要接口具有高速、全双工、硬件简单的优点。掌握SPI通信原理、STM32 SPI外设配置方法以及实际编程技巧对于嵌入式开发至关重要。在实际项目中需要根据具体外设的时序要求合理配置SPI参数并注意信号完整性和错误处理确保通信的稳定可靠。