STM32F103引脚资源告急?软件SPI驱动ST7735s屏幕的实战优化指南

当硬件SPI引脚被其他关键外设占用时,开发者常陷入两难境地。我曾在一个智能家居控制器项目中遇到类似困境——PA5/PA6/PA7引脚已被RFID模块占用,而TSSOP20封装的STM32F103C8T6需要同时驱动ST7735s屏幕。通过反复试验,我发现软件SPI不仅能解决问题,经过优化后甚至能达到接近硬件SPI的显示效果。本文将分享从CubeMX配置到性能调优的全套解决方案。

1. 硬件SPI与软件SPI的深度对比

在资源受限的嵌入式系统中,引脚分配如同拼图游戏。硬件SPI虽然高效,但其引脚固定性常成为系统设计的阿喀琉斯之踵。通过实际测试对比,两种方式的差异远比参数表呈现的更为复杂:

对比维度 硬件SPI 优化后的软件SPI
最大时钟频率 18MHz (PCLK2=72MHz时) 2.1MHz (CMSIS-DAP实测)
CPU占用率 <5% 15-40% (取决于优化等级)
引脚灵活性 固定三线制 任意GPIO组合
中断响应延迟 无显著影响 需规避关键时序段
代码复杂度 自动生成初始化代码 需手动实现位操作

实测发现:当软件SPI时钟间隔控制在480ns以上时,ST7735s的显示稳定性最佳。过快的时钟会导致色彩失真,这与屏幕控制器内部采样保持电路特性有关。

硬件SPI的优势在于其专用外设结构:

  • 自动生成时钟信号
  • 硬件级FIFO缓冲
  • DMA兼容设计

而软件SPI的灵活之处体现在:

  • 可动态调整时钟极性/相位
  • 支持非标准电压电平(如1.8V屏)
  • 便于实现多设备分时复用

2. CubeMX的差异化配置实战

2.1 硬件SPI标准配置流程

在CubeMX中启用硬件SPI只需三步:

  1. 在"Connectivity"标签启用SPI1/SPI2
  2. 配置参数(CPOL/CPHA、波特率分频)
  3. 生成代码后直接调用HAL_SPI_Transmit()
// 典型硬件SPI发送函数
HAL_SPI_Transmit(&hspi1, pData, Size, Timeout);

2.2 软件SPI的GPIO艺术

软件SPI需要更精细的GPIO配置,以PB13(SCK)、PB14(MISO)、PB15(MOSI)为例:

  1. 引脚模式设置

    • 所有引脚设为GPIO输出模式
    • 输出电平初始化为高(根据CPOL调整)
    • 输出速度选择"High"(确保信号边沿陡峭)
  2. CubeMX配置技巧

    • 为每个SPI引脚添加用户标签(如"SW_SCK")
    • 启用GPIO时钟后添加自定义初始化代码
// 软件SPI初始化示例
void SW_SPI_Init(void) {
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    
    // SCK引脚配置
    GPIO_InitStruct.Pin = GPIO_PIN_13;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
    
    // MOSI引脚类似配置...
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_13, GPIO_PIN_SET);
}
  1. 时序优化关键点
    • 使用寄存器级操作提升速度(如 GPIOB->BSRR
    • 插入NOP指令微调延时
    • 根据屏幕规格调整建立/保持时间

3. 软件SPI驱动代码的极致优化

3.1 基础位操作实现

最简软件SPI发送函数包含以下步骤:

void SW_SPI_SendByte(uint8_t data) {
    for(int i=0; i<8; i++) {
        HAL_GPIO_WritePin(GPIOB, GPIO_PIN_15, (data & 0x80) ? GPIO_PIN_SET : GPIO_PIN_RESET);
        HAL_GPIO_WritePin(GPIOB, GPIO_PIN_13, GPIO_PIN_RESET);
        data <<= 1;
        // 插入延时确保满足tSU/tHD
        for(volatile int j=0; j<2; j++);
        HAL_GPIO_WritePin(GPIOB, GPIO_PIN_13, GPIO_PIN_SET);
    }
}

3.2 性能提升五重奏

通过以下优化手段,我的测试项目帧率从12FPS提升到26FPS:

  1. 寄存器级操作 : 替换HAL库函数为直接寄存器访问:

    #define SCK_LOW()  (GPIOB->BRR = GPIO_PIN_13)
    #define SCK_HIGH() (GPIOB->BSRR = GPIO_PIN_13)
    
  2. 循环展开技术 : 消除循环判断开销:

    void SW_SPI_SendByte_Fast(uint8_t data) {
        MOSI(data & 0x80); SCK_TOGGLE(); data <<= 1;
        MOSI(data & 0x80); SCK_TOGGLE(); data <<= 1;
        // ...重复8次
    }
    
  3. 预取颜色数据 : 将16位色值拆分为高低字节数组:

    uint8_t color_hi[128], color_lo[128];
    
  4. 双缓冲机制 : 使用DMA搬运数据到显存时准备下一帧

  5. 指令重排 : 调整指令顺序避免流水线阻塞

3.3 ST7735s的特调参数

针对ST7735s的优化参数表:

参数 推荐值 说明
时钟空闲电平 High 匹配ST7735s的CPOL=1
数据采样边沿 下降沿 CPHA=1
最小时钟周期 480ns 对应约2.08MHz
复位脉冲宽度 10μs 确保可靠复位
命令/数据切换延时 50ns 满足tRDDI时序要求

4. 性能实测与实战建议

4.1 基准测试数据对比

在72MHz系统时钟下测得:

测试项 硬件SPI 优化前软件SPI 优化后软件SPI
全屏填充时间 8.2ms 32.5ms 14.7ms
文本渲染FPS 48 12 26
CPU占用率(60FPS) 6% 78% 35%
功耗增量 4.2mA 9.8mA 6.5mA

4.2 场景化选型建议

根据项目需求选择方案:

  • 工业控制面板 : 优先硬件SPI,确保高刷新率下的实时性

  • 智能家居终端 : 采用优化软件SPI,平衡引脚资源与性能

  • 穿戴设备 : 使用软件SPI+动态刷新控制降低功耗

4.3 避坑指南

在三个实际项目中积累的经验教训:

  1. GPIO速度陷阱 : 发现将GPIO设为"Low"速度反而提升稳定性,因为过快的边沿导致信号振铃

  2. 中断冲突 : 屏幕刷新期间禁用USB中断,避免显示撕裂

  3. 电源噪声 : 软件SPI高频切换时,添加0.1μF去耦电容改善显示噪点

// 中断安全版发送函数
void SW_SPI_SendByte_Safe(uint8_t data) {
    uint32_t primask = __get_PRIMASK();
    __disable_irq();
    // ...发送操作...
    __set_PRIMASK(primask);
}

通过将GPIO操作转换为内联函数、使用查表法预计算像素位置、采用区域刷新替代全屏更新等技巧,最终在保持显示效果的前提下,使CPU占用率降低40%。在最近的一次产品迭代中,这套优化方案成功帮助团队在引脚资源紧张的情况下实现了双屏显示功能。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐