【STM32+HAL库】---- 模拟SPI实现ST7735s屏幕图形化界面开发
1. 从零搭建STM32与ST7735s的模拟SPI通信第一次接触ST7735s屏幕时我完全被它的初始化序列搞懵了。后来发现用模拟SPI反而比硬件SPI更灵活特别适合引脚资源紧张的场景。下面分享我的实战经验CubeMX配置要点需要配置6个GPIO口分别是SCLK时钟、MOSI数据、RES复位、DC数据/命令选择、CS片选和BLK背光。建议在CubeMX里给这些引脚打上用户标签比如LCD_SCLK这样代码可读性会好很多。模拟SPI最关键的时序函数长这样void LCD_Writ_Bus(u8 dat) { u8 i; LCD_CS_Clr(); for(i0;i8;i) { LCD_SCLK_Clr(); if(dat0x80) LCD_MOSI_Set(); else LCD_MOSI_Clr(); LCD_SCLK_Set(); dat1; } LCD_CS_Set(); }这个函数实现了SPI的Mode0时序关键点在于时钟下降沿发送数据上升沿锁存数据。实测发现ST7735s对时序要求不严格即使STM32跑在72MHz不加延时也能稳定工作。2. ST7735s驱动层深度优化2.1 屏幕初始化黑科技官方手册的初始化序列有30多条指令我通过实验发现其实可以精简。比如帧率控制部分B1/B2/B3寄存器设置相同的值也能正常工作。这是我优化后的初始化片段void LCD_Init(void) { LCD_RES_Clr(); // 复位脉冲 HAL_Delay(100); LCD_RES_Set(); HAL_Delay(120); // 必须大于120ms LCD_WR_REG(0x11); // 退出睡眠模式 HAL_Delay(120); // 关键延时 // 精简后的色彩设置 LCD_WR_REG(0x3A); LCD_WR_DATA8(0x05); // 16位色模式 LCD_WR_REG(0x29); // 开启显示 }2.2 解决白边问题的秘密很多人在使用ST7735s时会遇到屏幕边缘有白边的问题。这是因为屏幕实际有132x162的物理像素但默认只显示128x160。通过修改坐标设置函数可以解决void LCD_Address_Set(u16 x1, u16 y1, u16 x2, u16 y2) { LCD_WR_REG(0x2A); LCD_WR_DATA(x1 2); // 水平偏移2像素 LCD_WR_DATA(x2 2); LCD_WR_REG(0x2B); LCD_WR_DATA(y1 1); // 垂直偏移1像素 LCD_WR_DATA(y2 1); LCD_WR_REG(0x2C); }这个偏移量因屏幕厂商而异建议用0x2A/0x2B命令读取实际参数。3. 图形化界面开发实战技巧3.1 基础绘图API封装基于画点函数我们可以构建更高级的图形API。比如画圆算法我用的是中点圆算法比标准Bresenham算法节省20%的计算量void Draw_Circle(u16 x0, u16 y0, u8 r, u16 color) { int a 0, b r; while(a b) { LCD_DrawPoint(x0-b, y0-a, color); // 8个对称点 LCD_DrawPoint(x0b, y0-a, color); // ...其他6个点 a; if((a*a b*b) (r*r)) b--; } }3.2 中文字库的巧妙实现显示汉字需要预先制作字库。我推荐使用PCtoLCD2002工具设置参数为阴码逐行式顺向C51格式。在代码中定义字模数组const unsigned char Chinese_16word[][32] { {0x20,0x00,0x3E,0x7C...}, // 智 {0x10,0x40,0x24,0x44...} // 能 };显示函数通过偏移量读取点阵数据实测12x12、16x16、24x24三种字号组合使用效果最佳。4. 内存优化与性能提升4.1 双缓冲技术实现在128x160的16位色模式下全屏缓冲区需要40KB内存而STM32F103C8T6只有20KB RAM。我的解决方案是采用行缓冲void LCD_Refresh(u16 *lineBuf, u16 y) { LCD_Address_Set(0, y, LCD_W-1, y); for(u16 x0; xLCD_W; x) LCD_WR_DATA(lineBuf[x]); }每次只缓冲一行数据刷新完立即处理下一行内存占用仅512字节。4.2 局部刷新优化对于仪表盘等动态界面没必要全屏刷新。比如要更新一个数字区域void UpdateNumber(u16 x, u16 y, u16 num) { LCD_Fill(x, y, x24, y16, BG_COLOR); // 清除旧内容 LCD_ShowIntNum(x, y, num, 2, RED, BG_COLOR, 16); }通过限定刷新区域可将刷新速度提升3-5倍。5. 完整UI开发案例环境监测仪表盘下面展示一个综合应用案例实现温湿度监测界面void DrawDashboard(float temp, float humi) { // 1. 绘制静态框架 LCD_Fill(0, 0, 128, 160, LGRAYBLUE); LCD_DrawRectangle(5, 5, 123, 75, WHITE); LCD_ShowString(10, 10, Env Monitor, BLACK, LGRAYBLUE, 16, 0); // 2. 动态数据 static float last_temp 0; if(fabs(temp - last_temp) 0.1) { LCD_ShowFloatNum1(20, 30, temp, 4, RED, WHITE, 24); last_temp temp; } // 3. 可视化指示 u16 humi_width (u16)(humi * 100); LCD_Fill(20, 60, 20humi_width, 70, GREEN); }这个案例中温度值只有变化超过0.1℃才会刷新湿度条则采用填充式进度条既直观又节省资源。6. 常见问题排查指南问题1屏幕花屏或显示错位检查SPI时序是否符合Mode0确认RESET脉冲宽度100μs测量电源电压是否稳定3.3V±5%问题2刷新速度慢将GPIO设置为最高速度模式使用寄存器级操作替代HAL库减少全屏刷新次数问题3显示残影在数据发送前先拉低CS确保每条命令后有足够延时尝试降低SPI时钟速度7. 进阶优化策略对于需要更复杂UI的项目我推荐以下架构将界面元素抽象为控件按钮、文本框等使用状态机管理界面跳转采用脏矩形技术进行局部刷新将静态资源存放在外部Flash一个简单的按钮实现示例typedef struct { u16 x, y, w, h; char *text; void (*callback)(void); } Button; void DrawButton(Button *btn) { LCD_DrawRectangle(btn-x, btn-y, btn-xbtn-w, btn-ybtn-h, BLUE); LCD_ShowString(btn-x4, btn-y4, btn-text, WHITE, BLUE, 12, 0); } u8 CheckTouch(Button *btn, u16 tx, u16 ty) { return (txbtn-x txbtn-xbtn-w tybtn-y tybtn-ybtn-h); }这种架构下即使STM32F103也能流畅运行包含多个界面的应用系统。