SPI总线抽象设计与STM32实现详解

张开发
2026/5/17 16:30:08 15 分钟阅读
SPI总线抽象设计与STM32实现详解
1. SPI总线抽象设计背景与价值在嵌入式开发领域SPISerial Peripheral Interface作为重要的串行通信协议广泛应用于各类外设连接。传统裸机编程方式存在几个显著痛点硬件依赖性强、代码复用率低、移植成本高。我曾参与过多个需要更换MCU平台的项目每次SPI驱动重写都耗费大量时间。通过引入分层抽象设计我们实现了硬件无关性上层应用代码不关心底层是STM32还是GD32的SPI控制器统一接口四种标准操作模式覆盖90%以上的SPI外设需求热插拔支持运行时动态切换硬件SPI和模拟SPI这种设计尤其适合需要快速验证原型的产品开发阶段多平台兼容的嵌入式系统需要同时接入多种SPI外设的复杂应用2. 核心架构设计解析2.1 设备-总线分离模型关键结构体spi_dev_device实现了设备与总线的解耦struct spi_dev_device { void (*spi_cs)(unsigned char state); struct spi_bus_device *spi_bus; };这种设计的精妙之处在于片选控制spi_cs由设备自己管理不同设备可复用同一总线总线操作通过spi_bus指针抽象隐藏具体硬件实现内存占用仅8字节32位系统适合资源受限的MCU环境2.2 四类标准操作接口我们提炼出四种最常用的SPI操作模式spi_send_then_recv典型寄存器读取场景// 读取传感器寄存器值 uint8_t cmd REG_TEMP_VALUE; uint8_t value; spi_send_then_recv(sensor_dev, cmd, 1, value, 1);spi_send_then_send典型寄存器写入场景// 配置ADC采样率 uint8_t cfg[2] {REG_SAMPLE_RATE, 0x08}; spi_send_then_send(adc_dev, cfg, 2, NULL, 0);spi_send_recv全双工传输场景// 同时收发数据如以太网PHY uint8_t tx_buf[64], rx_buf[64]; spi_send_recv(eth_dev, tx_buf, rx_buf, 64);spi_send纯发送场景// 刷新LCD显示 spi_send(lcd_dev, frame_buffer, 320*240*2);实际测试表明这四种API可覆盖90%以上的SPI外设操作需求。特殊时序需求可通过组合这些基本操作实现。3. STM32硬件实现详解3.1 底层驱动实现以STM32F1系列为例关键实现位于spi_bus_xfer函数static int stm32_spi_bus_xfer(struct spi_dev_device *spi_dev, struct spi_dev_message *msg) { SPI_TypeDef *SPIx spi_dev-spi_bus-spi_phy; // 片选控制 if(msg-cs_take) spi_dev-spi_cs(0); // 8位模式传输 while(msg-length--) { uint8_t data msg-send_buf ? *msg-send_buf : 0xFF; while(!(SPIx-SR SPI_SR_TXE)); // 等待发送缓冲区空 SPIx-DR data; while(!(SPIx-SR SPI_SR_RXNE)); // 等待接收完成 data SPIx-DR; if(msg-recv_buf) *msg-recv_buf data; } // 释放片选 if(msg-cs_release) spi_dev-spi_cs(1); return msg-length; }几个关键优化点使用寄存器直接操作替代库函数提升约30%速度动态判断收发缓冲区避免不必要的内存操作支持DMA传输扩展通过判断msg-length阈值3.2 初始化配置要点正确的SPI初始化是稳定通信的基础void stm32f1_spi_init(SPI_TypeDef *SPIx, uint32_t speed) { // 时钟使能 if(SPIx SPI1) RCC-APB2ENR | RCC_APB2ENR_SPI1EN; else if(SPIx SPI2) RCC-APB1ENR | RCC_APB1ENR_SPI2EN; // GPIO配置以SPI1为例 GPIOA-CRL ~(GPIO_CRL_CNF5 | GPIO_CRL_MODE5 | // SCK GPIO_CRL_CNF6 | GPIO_CRL_MODE6 | // MISO GPIO_CRL_CNF7 | GPIO_CRL_MODE7); // MOSI GPIOA-CRL | (GPIO_CRL_CNF5_1 | GPIO_CRL_MODE5_1 | GPIO_CRL_CNF6_0 | GPIO_CRL_MODE6_0 | GPIO_CRL_CNF7_1 | GPIO_CRL_MODE7_1); // SPI参数配置 SPIx-CR1 SPI_CR1_MSTR | SPI_CR1_SPE | speed | SPI_CR1_SSM | SPI_CR1_SSI; // 软件NSS管理 }常见配置问题排查时钟未使能用逻辑分析仪看不到任何波形GPIO模式错误输出模式应为复用推挽输入模式应为浮空输入NSS配置必须启用软件管理模式SSM1, SSI14. EEPROM驱动实战以25AA256为例展示完整驱动实现4.1 设备初始化struct spi_dev_device eeprom_dev; struct spi_bus_device spi1_bus; void eeprom_init(void) { // 硬件初始化 stm32f1_spi_init(SPI1, SPI_BAUDRATEPRESCALER_64); // 片选GPIO配置 GPIOB-CRH ~GPIO_CRH_MODE12; GPIOB-CRH | GPIO_CRH_MODE12_0; // 输出模式最大速度50MHz GPIOB-BSRR GPIO_BSRR_BS12; // 初始高电平 // 设备注册 spi1_bus.spi_phy SPI1; spi1_bus.spi_bus_xfer stm32_spi_bus_xfer; spi1_bus.data_width 8; eeprom_dev.spi_cs eeprom_cs; eeprom_dev.spi_bus spi1_bus; }4.2 读写操作实现页写入函数示例#define PAGE_SIZE 64 void eeprom_write_page(uint16_t addr, uint8_t *data, uint8_t len) { uint8_t cmd[3]; // 地址对齐检查 if(len PAGE_SIZE || (addr % PAGE_SIZE) len PAGE_SIZE) { return; // 错误处理 } // 写使能 cmd[0] WREN; spi_send(eeprom_dev, cmd, 1); // 写入数据 cmd[0] WRITE; cmd[1] addr 8; cmd[2] addr 0xFF; spi_send_then_send(eeprom_dev, cmd, 3, data, len); // 等待写入完成 do { cmd[0] RDSR; spi_send_then_recv(eeprom_dev, cmd, 1, cmd[1], 1); } while(cmd[1] 0x01); }实测发现25系列EEPROM页写入后需要约5ms的编程时间连续写入时必须检查状态寄存器BUSY位。5. 高级应用技巧5.1 多设备总线共享通过不同的片选信号实现总线共享// 设备1Flash存储器 struct spi_dev_device flash_dev { .spi_cs flash_cs, .spi_bus spi1_bus }; // 设备2温湿度传感器 struct spi_dev_device sensor_dev { .spi_cs sensor_cs, .spi_bus spi1_bus };关键注意事项片选GPIO速度应配置为最高速50MHz总线频率需满足所有设备要求取最低值避免在中断上下文操作共享总线5.2 性能优化方案通过预编译选项实现灵活配置#if defined(SPI_DMA_ENABLE) (SPI_DMA_ENABLE 1) #define SPI_XFER spi_dma_xfer #else #define SPI_XFER spi_poll_xfer #endif实测性能对比STM32F10372MHz传输模式1KB数据传输时间CPU占用率轮询模式2.8ms100%DMA模式0.3ms5%6. 移植与调试经验6.1 跨平台移植要点移植到新平台只需实现spi_bus_xfer函数硬件初始化代码片选GPIO控制以GD32移植为例主要差异点// GD32的SPI状态寄存器位定义不同 while(!(SPIx-STAT SPI_STAT_TBE)); // 发送缓冲区空 while(!(SPIx-STAT SPI_STAT_RBNE)); // 接收缓冲区非空6.2 常见问题排查数据错位检查CPOL/CPHA配置是否与外设一致用逻辑分析仪捕获实际波形通信失败确认片选信号有效电平有些设备是低有效有些是高有效检查SPI时钟频率是否超出外设限制数据丢失增加发送/接收完成标志检查在关键位置插入延时特别是上升沿采样模式我在实际项目中总结的调试口诀 时钟先看极性数据要对相位片选注意时序速率逐步上调

更多文章