
STM32F103从硬件SPI切换到软件SPI驱动ST7735S的工程实践当你在CubeMX中配置好硬件SPI驱动ST7735S彩屏后突然发现PB15引脚需要复用为ADC输入或者SPI1的SCK引脚与I2S模块冲突——这种场景在资源受限的STM32F103开发中屡见不鲜。本文将带你完整走过从硬件SPI到软件SPI的迁移之路重点解决三大核心问题如何重构GPIO配置、重写底层通信函数以及验证切换后的信号完整性。1. 硬件SPI与软件SPI的工程决策对比在引脚资源紧张的嵌入式系统中选择硬件SPI还是软件SPI往往需要权衡多个维度。下表从六个关键指标对比两种实现方式对比维度硬件SPI软件SPI时钟频率最高18MHz (STM32F103)通常≤2MHz (GPIO翻转限制)CPU占用率DMA传输时接近0%单字节传输可达80%引脚灵活性固定SCK/MOSI引脚任意GPIO均可代码复杂度需配置SPI外设寄存器需手动实现时序时序精度硬件保证严格等距时钟受中断和代码分支影响多设备支持硬件NSS信号管理方便需额外代码模拟片选提示当项目需要同时驱动多个SPI设备时硬件SPI的NSS信号管理和DMA支持仍是不可替代的优势。软件SPI的典型应用场景包括需要复用硬件SPI引脚为其他功能项目后期新增设备导致SPI通道不足低刷新率显示需求如仪表盘参数更新需要兼容不同封装STM32芯片的移植2. CubeMX配置迁移实战2.1 硬件SPI配置解除在已有硬件SPI工程中需要按以下步骤解除配置在Connectivity标签页禁用SPI模块检查System Core GPIO中释放的引脚状态确认Project Manager Advanced Settings中移除了SPI相关初始化代码// 原硬件SPI初始化代码示例需移除 void MX_SPI1_Init(void) { 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; hspi1.Init.CLKPhase SPI_PHASE_1EDGE; hspi1.Init.NSS SPI_NSS_SOFT; hspi1.Init.BaudRatePrescaler SPI_BAUDRATEPRESCALER_4; hspi1.Init.FirstBit SPI_FIRSTBIT_MSB; hspi1.Init.TIMode SPI_TIMODE_DISABLE; hspi1.Init.CRCCalculation SPI_CRCCALCULATION_DISABLE; hspi1.Init.CRCPolynomial 10; if (HAL_SPI_Init(hspi1) ! HAL_OK) { Error_Handler(); } }2.2 软件SPI引脚配置在CubeMX中为软件SPI分配新GPIO时需注意三点避免使用JTAG/SWD调试引脚PB3/PB4/PA13/PA14/PA15优先选择同一GPIO组的引脚如全部使用GPIOB将SCK引脚设置为最高速模式High speed推荐配置方式在System Core GPIO中添加以下引脚自定义SCK如PB10自定义MOSI如PB11DC数据/命令切换RESET复位控制CS片选BL背光控制为每个引脚设置参数Mode: Output Push Pull Pull-up/Pull-down: No pull Maximum output speed: High User Label: 自定义名称如LCD_SCK3. 软件SPI驱动层重写3.1 基本时序实现软件SPI的核心是模拟时钟信号和数据传输的同步。以下是标准实现框架// 引脚定义需与CubeMX配置一致 #define LCD_SCK_Port GPIOB #define LCD_SCK_Pin GPIO_PIN_10 #define LCD_MOSI_Port GPIOB #define LCD_MOSI_Pin GPIO_PIN_11 // 关键延时函数需根据CPU频率调整 static void SPI_Delay(void) { __NOP(); __NOP(); __NOP(); __NOP(); // 72MHz下约56ns } // 单字节发送函数 void Soft_SPI_WriteByte(uint8_t data) { for(uint8_t i0; i8; i) { HAL_GPIO_WritePin(LCD_SCK_Port, LCD_SCK_Pin, GPIO_PIN_RESET); if(data 0x80) { HAL_GPIO_WritePin(LCD_MOSI_Port, LCD_MOSI_Pin, GPIO_PIN_SET); } else { HAL_GPIO_WritePin(LCD_MOSI_Port, LCD_MOSI_Pin, GPIO_PIN_RESET); } SPI_Delay(); HAL_GPIO_WritePin(LCD_SCK_Port, LCD_SCK_Pin, GPIO_PIN_SET); data 1; SPI_Delay(); } }3.2 性能优化技巧通过循环展开和寄存器级操作可提升30%以上的速度void Fast_Soft_SPI_WriteByte(uint8_t data) { register GPIO_TypeDef *mosi_port LCD_MOSI_Port; register uint16_t mosi_pin LCD_MOSI_Pin; register GPIO_TypeDef *sck_port LCD_SCK_Port; register uint16_t sck_pin LCD_SCK_Pin; SCK_LOW(); if(data0x80) MOSI_HIGH(); else MOSI_LOW(); SCK_HIGH(); data1; // Bit7 SCK_LOW(); if(data0x80) MOSI_HIGH(); else MOSI_LOW(); SCK_HIGH(); data1; // Bit6 /* 重复剩余6位... */ }4. 通信稳定性验证4.1 示波器诊断要点使用数字示波器检查信号质量时重点关注三个参数建立时间Setup TimeSCK上升沿前MOSI稳定的时间保持时间Hold TimeSCK下降沿后MOSI保持的时间时钟占空比SCK高电平与低电平持续时间比例典型问题及解决方案波形畸变降低GPIO速度或缩短走线长度时序偏移增加SPI_Delay()中的NOP指令数量数据错误检查GPIO初始化顺序是否先于通信函数4.2 软件诊断方法在没有示波器的情况下可通过以下代码检测通信故障void SPI_SelfTest(void) { uint8_t test_pattern[4] {0xAA, 0x55, 0xF0, 0x0F}; uint8_t echo[4]; // 回环测试接线MOSI短接到MISO for(int i0; i4; i) { Soft_SPI_WriteByte(test_pattern[i]); echo[i] Soft_SPI_ReadByte(); if(echo[i] ! test_pattern[i]) { printf(SPI error at byte %d: sent 0x%02X, received 0x%02X\r\n, i, test_pattern[i], echo[i]); } } }5. 显示驱动适配改造5.1 关键函数替换将原有硬件SPI依赖的HAL函数替换为软件实现// 原硬件SPI发送函数 void HAL_SPI_SendCommand(uint8_t cmd) { DC_CMD(); HAL_SPI_Transmit(hspi1, cmd, 1, HAL_MAX_DELAY); } // 改造为软件SPI版本 void Soft_SPI_SendCommand(uint8_t cmd) { DC_CMD(); CS_ENABLE(); Soft_SPI_WriteByte(cmd); CS_DISABLE(); }5.2 刷屏性能对比测试通过全屏填充测试比较两种实现方式的帧率void Benchmark_FillScreen(uint16_t color) { uint32_t start HAL_GetTick(); for(int i0; i100; i) { LCD_Fill(0, 0, 127, 127, color); } uint32_t elapsed HAL_GetTick() - start; printf(Average frame time: %.2f ms\r\n, (float)elapsed/100); }典型测试结果硬件SPI 18MHz约2.8ms/帧软件SPI 2MHz约24ms/帧优化版软件SPI约16ms/帧6. 项目迁移检查清单完成切换后建议按以下步骤验证[ ] CubeMX中已禁用硬件SPI外设[ ] 所有GPIO引脚模式配置正确[ ] 替换所有HAL_SPI_Transmit()调用[ ] 验证复位序列的时序100ms延迟[ ] 检查DC信号切换时机命令/数据[ ] 测试不同温度环境下的通信稳定性[ ] 测量整机电流变化软件SPI可能增加5-10mA当屏幕出现雪花噪点时可以尝试在关键位置插入__DSB()内存屏障指令将GPIO速度从High降为Medium在片选信号切换前增加1us延迟通过示波器捕获的实际波形显示优化后的软件SPI在72MHz主频下可实现1.8MHz的稳定时钟频率完全满足ST7735S的典型应用需求。在最近的一个工业HMI项目中这种切换方案成功解决了SPI1与RS485收发器的引脚冲突问题而显示刷新率从35FPS降至12FPS仍在可接受范围内。