ST7735S TFT屏幕的SPI驱动与多平台移植实战 1. ST7735S与SPI接口基础认知第一次拿到ST7735S驱动的TFT屏幕时我盯着那排密密麻麻的引脚有点发懵。这种1.44寸的小屏幕在创客圈特别受欢迎但要让它在不同平台上跑起来得先理解它的通信本质。ST7735S实际上是个翻译官它把SPI协议的数字信号转换成屏幕上的彩色像素点。SPI协议就像四个人打麻将SCK时钟线相当于出牌节奏主机说碰的时候从机必须响应MOSI主机输出是主机打出的牌CS片选决定现在谁坐庄DC数据/命令选择则是区分这是指令还是真实数据实测中发现个有趣现象当SPI时钟超过20MHz时我的STM32F103屏幕开始出现雪花点但树莓派4B却能稳定跑到30MHz。这说明不同MCU的SPI控制器性能差异很大移植时时钟配置不能照搬。2. 硬件连接避坑指南去年给51单片机接屏幕时我烧了三块屏才搞明白电压匹配的重要性。ST7735S虽然标称支持3.3V-5V但逻辑电平必须保持一致引脚51单片机接法STM32接法树莓派接法VCC5V直接供电3.3V供电3.3V供电GND共地共地共地SDAP1.0引脚PA7 (SPI1_MOSI)GPIO10 (MOSI)SCKP1.1引脚PA5 (SPI1_SCK)GPIO11 (SCK)CSP1.2引脚PA4 (软件片选)GPIO8 (CE0)DCP1.3引脚PB0GPIO25RESET接RC复位电路接MCU复位引脚接GPIO24特别提醒LED背光引脚一定要串接限流电阻我有个学生直接接5V导致屏幕亮度异常还发烫。建议用220Ω电阻实测电流控制在15mA左右最安全。3. 核心驱动代码解析看官方手册第45页的初始化序列时我发现不同厂商的屏幕初始化参数微妙不同。比如某宝买的屏幕需要额外设置0x36命令的扫描方向// 竖屏模式初始化代码片段 void ST7735_Init(void) { WriteCmd(0x36); WriteData(0xC0); // MY1,MX1,MV0 DelayMs(120); WriteCmd(0x3A); WriteData(0x05); // RGB565格式 // 后续省略其他初始化命令... }移植到STM32时我封装了硬件抽象层(HAL)// 硬件抽象层示例 typedef struct { void (*WriteByte)(uint8_t); void (*SetDC)(uint8_t); void (*SetCS)(uint8_t); void (*Delay)(uint32_t); } ST7735_HAL; // 在STM32上的实现 void STM32_SPI_Write(uint8_t data) { HAL_SPI_Transmit(hspi1, data, 1, 100); } ST7735_HAL stm32_hal { .WriteByte STM32_SPI_Write, .SetDC HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0), .SetCS HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4), .Delay HAL_Delay };4. 多平台移植实战4.1 51单片机上的精简实现在STC89C52上跑SPI需要模拟时序这里有个加速技巧用查表法替代移位操作。实测刷屏速度提升3倍// 51单片机模拟SPI写数据 void SPI_Write(uint8_t dat) { static code uint8_t mask[] {0x80,0x40,0x20,0x10,0x08,0x04,0x02,0x01}; uint8_t i; for(i0; i8; i) { SDA (dat mask[i]) ? 1 : 0; SCK 0; _nop_(); // 插入空指令保证时序 SCK 1; } }4.2 STM32的DMA优化使用STM32CubeMX配置DMA时要特别注意缓冲对齐问题。有次我遇到屏幕显示错乱最后发现是DMA传输宽度没设置对// DMA配置关键参数 hdma_spi1.Init.PeriphDataAlignment DMA_PDATAALIGN_BYTE; hdma_spi1.Init.MemDataAlignment DMA_MDATAALIGN_BYTE; hdma_spi1.Init.Mode DMA_NORMAL; // 非循环模式刷全屏颜色时采用内存到外设的DMA传输比普通SPI快8倍void ST7735_Fill_DMA(uint16_t color) { uint16_t buffer[128]; // 每行像素缓存 ST7735_SetWindow(0, 0, 127, 159); for(int i0; i128; i) buffer[i] color; HAL_SPI_Transmit_DMA(hspi1, (uint8_t*)buffer, sizeof(buffer)); while(HAL_SPI_GetState(hspi1) ! HAL_SPI_STATE_READY); }4.3 树莓派Python驱动树莓派上我用Pythonspidev库发现个隐藏坑点必须设置SPI模式3才能正常通信import spidev spi spidev.SpiDev() spi.open(0, 0) # 使用SPI0 spi.mode 0b11 # CPOL1, CPHA1 spi.max_speed_hz 32000000 # 32MHz def write_data(data): spi.xfer2([0x72] list(data)) # xfer2保持CS有效图形库推荐使用PillowNumPy组合处理图像转换特别高效from PIL import Image import numpy as np def show_image(img_path): img Image.open(img_path).resize((128,160)) rgb565 np.array(img).astype(np.uint16) r (rgb565[...,0] 3).astype(np.uint16) g (rgb565[...,1] 2).astype(np.uint16) b (rgb565[...,2] 3).astype(np.uint16) buffer (r 11) | (g 5) | b spi.xfer2(buffer.tobytes())5. 性能优化技巧上周调试时发现STM32H743刷屏时SPI时钟跑到100MHz会导致数据丢失。通过逻辑分析仪抓包总结出稳定运行的黄金法则时钟分频SPI时钟 ≤ 1/10 MCU主频缓冲策略双缓冲DMA连续传输时序补偿在STM32CubeMX中调整SPI的First Bit设置有个特别实用的调试方法用GPIO引脚实时标记关键代码段执行时间// 在GPIOB1上输出调试信号 #define DEBUG_START() GPIOB-BSRR GPIO_BSRR_BS_1 #define DEBUG_END() GPIOB-BSRR GPIO_BSRR_BR_1 void Refresh_Screen() { DEBUG_START(); // 刷新代码... DEBUG_END(); // 用示波器测量脉冲宽度 }最后分享个血泪教训移植到新平台时务必先用示波器检查SPI波形。有次我花了三天调试最后发现是杜邦线接触不良导致时钟信号畸变。现在我的工作台上常备各种长度的镀金排线信号质量明显改善。