1. STM32与ST7735S的基础认知第一次接触STM32驱动TFT屏时看着密密麻麻的引脚和陌生的寄存器名称我也曾一头雾水。但经过几个项目的实战后我发现只要掌握几个关键点就能轻松驾驭这块1.8寸的ST7735S屏幕。让我们先从硬件连接开始这是所有工作的基础。开发板与屏幕的接线其实很有规律。以常见的STM32F103C8T6开发板为例我们需要连接以下引脚GND接电源地VCC接3.3V电源SCL接PA5SPI时钟线SDA接PA7SPI数据线RES接PB0复位引脚DC接PB1数据/命令选择CS接PA4片选引脚BL接PB10背光控制可选这里有个实用技巧如果暂时不想控制背光BL引脚可以直接接3.3V或者悬空。我在早期项目中经常忘记初始化背光引脚导致屏幕不亮排查了半天才发现问题所在。2. 模拟SPI通信的深度解析2.1 SPI通信的本质很多初学者会疑惑为什么不用硬件SPI而选择模拟其实在资源受限的场景下模拟SPI更加灵活。SPI通信的核心在于四个信号SCLK - 时钟信号由主设备产生MOSI - 主设备输出从设备输入MISO - 从设备输入主设备输出CS - 片选信号ST7735S比较特殊它只需要接收数据而不需要发送所以我们可以省略MISO线这简化了我们的接线工作。2.2 时钟极性与相位这是SPI最让人困惑的部分但理解后会发现很简单。CPOL和CPHA两个参数决定了通信时序CPOL0时钟空闲时为低电平CPOL1时钟空闲时为高电平CPHA0在第一个时钟边沿采样数据CPHA1在第二个时钟边沿采样数据对于ST7735S需要设置为模式0CPOL0CPHA0。这意味着时钟空闲时为低电平数据在上升沿被采样数据在下降沿变化2.3 模拟SPI的代码实现直接操作寄存器是最高效的方式。下面是我的常用宏定义#define CS_H ((CDS_PORT)-ODR | (CS_PIN)) #define SCL_H ((CDS_PORT)-ODR | (SCL_PIN)) #define SDA_H ((CDS_PORT)-ODR | (SDA_PIN)) #define CS_L ((CDS_PORT)-ODR ~(CS_PIN)) #define SCL_L ((CDS_PORT)-ODR ~(SCL_PIN)) #define SDA_L ((CDS_PORT)-ODR ~(SDA_PIN))发送一个字节的函数实现void SPI_SendByte(uint8_t data) { for(uint8_t i0; i8; i) { SCL_L; if(data 0x80) SDA_H; else SDA_L; SCL_H; data 1; } }3. 寄存器配置实战3.1 关键寄存器详解ST7735S有多个重要寄存器这里重点介绍两个最常用的0x36 - 显示方向控制寄存器这个寄存器控制屏幕的显示方向、颜色格式等。它的各个位含义如下MY行地址顺序1从上到下MX列地址顺序1从左到右MV行列交换ML垂直刷新方向RGB颜色格式0RGB1BGRMH水平刷新方向0x2A和0x2B - 行列地址设置这两个寄存器用于设置显示区域的范围0x2A设置列地址X方向0x2B设置行地址Y方向 使用时需要发送4个参数起始地址高字节、起始地址低字节、结束地址高字节、结束地址低字节。3.2 初始化序列正确的初始化是屏幕正常工作的关键。以下是典型的初始化流程void ST7735_Init(void) { // 硬件复位 RST_L; Delay_ms(100); RST_H; Delay_ms(120); // 发送初始化命令序列 ST7735_WriteCmd(0x11); // 退出睡眠模式 Delay_ms(120); ST7735_WriteCmd(0x36); // 设置显示方向 ST7735_WriteData(0xC0); // 我的常用设置 ST7735_WriteCmd(0x3A); // 设置颜色格式 ST7735_WriteData(0x05); // 16位RGB565 // 更多初始化命令... ST7735_WriteCmd(0x29); // 开启显示 }4. 图形显示实战4.1 基本绘图功能4.1.1 画点函数这是所有图形显示的基础void ST7735_DrawPixel(uint16_t x, uint16_t y, uint16_t color) { // 检查坐标范围 if(x ST7735_WIDTH || y ST7735_HEIGHT) return; // 设置显示区域为单个点 ST7735_SetAddressWindow(x, y, x, y); // 发送颜色数据 ST7735_WriteData(color 8); ST7735_WriteData(color 0xFF); }4.1.2 清屏函数清屏实际上是填充整个屏幕void ST7735_FillScreen(uint16_t color) { ST7735_FillRect(0, 0, ST7735_WIDTH, ST7735_HEIGHT, color); }4.1.3 矩形填充这是更通用的区域填充函数void ST7735_FillRect(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color) { // 边界检查 if(x ST7735_WIDTH || y ST7735_HEIGHT) return; if(x w ST7735_WIDTH) w ST7735_WIDTH - x; if(y h ST7735_HEIGHT) h ST7735_HEIGHT - y; // 设置显示区域 ST7735_SetAddressWindow(x, y, x w - 1, y h - 1); // 发送颜色数据 for(uint32_t i 0; i w * h; i) { ST7735_WriteData(color 8); ST7735_WriteData(color 0xFF); } }4.2 高级图形功能4.2.1 画线算法实现Bresenham画线算法void ST7735_DrawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t color) { int dx abs(x1 - x0); int dy abs(y1 - y0); int sx (x0 x1) ? 1 : -1; int sy (y0 y1) ? 1 : -1; int err dx - dy; while(1) { ST7735_DrawPixel(x0, y0, color); if(x0 x1 y0 y1) break; int e2 2 * err; if(e2 -dy) { err - dy; x0 sx; } if(e2 dx) { err dx; y0 sy; } } }4.2.2 圆形绘制中点圆算法实现void ST7735_DrawCircle(uint16_t x0, uint16_t y0, uint16_t r, uint16_t color) { int f 1 - r; int ddF_x 1; int ddF_y -2 * r; int x 0; int y r; ST7735_DrawPixel(x0, y0 r, color); ST7735_DrawPixel(x0, y0 - r, color); ST7735_DrawPixel(x0 r, y0, color); ST7735_DrawPixel(x0 - r, y0, color); while(x y) { if(f 0) { y--; ddF_y 2; f ddF_y; } x; ddF_x 2; f ddF_x; ST7735_DrawPixel(x0 x, y0 y, color); ST7735_DrawPixel(x0 - x, y0 y, color); ST7735_DrawPixel(x0 x, y0 - y, color); ST7735_DrawPixel(x0 - x, y0 - y, color); ST7735_DrawPixel(x0 y, y0 x, color); ST7735_DrawPixel(x0 - y, y0 x, color); ST7735_DrawPixel(x0 y, y0 - x, color); ST7735_DrawPixel(x0 - y, y0 - x, color); } }5. 文字显示技术5.1 ASCII字符显示显示字符需要先获取字模数据。通常我们会使用8x16像素的字模void ST7735_DrawChar(uint16_t x, uint16_t y, char c, uint16_t color, uint16_t bg) { // 获取字符在字模数组中的偏移量 uint32_t index (c - 32) * 16; for(uint8_t i 0; i 16; i) { uint8_t line font8x16[index i]; for(uint8_t j 0; j 8; j) { if(line (0x80 j)) { ST7735_DrawPixel(x j, y i, color); } else if(bg ! color) { ST7735_DrawPixel(x j, y i, bg); } } } }5.2 字符串显示基于字符显示函数可以轻松实现字符串显示void ST7735_DrawString(uint16_t x, uint16_t y, const char *str, uint16_t color, uint16_t bg) { while(*str) { ST7735_DrawChar(x, y, *str, color, bg); x 8; // 自动换行处理 if(x ST7735_WIDTH - 8) { x 0; y 16; } } }5.3 中文显示中文显示相对复杂因为每个汉字通常需要16x16像素void ST7735_DrawChinese(uint16_t x, uint16_t y, uint16_t index, uint16_t color, uint16_t bg) { for(uint8_t i 0; i 16; i) { uint16_t line chinese_font[index * 16 i]; for(uint8_t j 0; j 16; j) { if(line (0x8000 j)) { ST7735_DrawPixel(x j, y i, color); } else if(bg ! color) { ST7735_DrawPixel(x j, y i, bg); } } } }6. 图片显示技术6.1 图片取模显示图片前需要将图片转换为数组。常用的取模软件如Image2Lcd可以将图片转换为C语言数组。选择设置时要注意输出数据类型C语言数组扫描方式水平扫描输出灰度16位真彩色最大宽度和高度不超过屏幕分辨率6.2 图片显示实现有了图片数组后显示图片就很简单了void ST7735_DrawImage(uint16_t x, uint16_t y, uint16_t w, uint16_t h, const uint16_t *img) { ST7735_SetAddressWindow(x, y, x w - 1, y h - 1); for(uint32_t i 0; i w * h; i) { ST7735_WriteData(img[i] 8); ST7735_WriteData(img[i] 0xFF); } }7. 性能优化技巧7.1 批量写入优化单像素写入效率很低我们可以优化为批量写入void ST7735_WritePixels(uint16_t *colors, uint32_t len) { for(uint32_t i 0; i len; i) { ST7735_WriteData(colors[i] 8); ST7735_WriteData(colors[i] 0xFF); } }7.2 双缓冲技术对于动画显示可以使用双缓冲技术在内存中创建一个和屏幕大小相同的缓冲区所有绘图操作先在内存缓冲区中进行完成一帧绘制后一次性将缓冲区内容写入屏幕这能有效避免屏幕闪烁。7.3 DMA传输如果使用硬件SPI可以结合DMA进一步提高传输效率void ST7735_DMA_WritePixels(uint16_t *colors, uint32_t len) { // 配置DMA... // 启动SPI传输... }8. 常见问题排查8.1 屏幕无显示检查电源和背光确认复位信号正常检查SPI时钟和数据线验证初始化序列是否正确8.2 显示颜色异常检查颜色格式设置RGB565确认0x36寄存器的RGB/BGR位检查伽马校正设置8.3 显示位置错乱检查0x2A和0x2B寄存器设置确认显示方向寄存器0x36检查坐标范围是否超出屏幕9. 项目实战建议在实际项目中我建议采用分层架构底层驱动层处理最基础的SPI通信和寄存器操作中间层实现基本图形功能点、线、面等应用层实现具体的UI界面和业务逻辑这种结构便于维护和移植。例如当更换不同型号的屏幕时只需修改底层驱动层即可。10. 进阶功能探索掌握了基础功能后可以尝试实现更复杂的功能触摸屏控制多级菜单系统动画效果自定义字体屏幕截图功能每个功能的实现都会让你对TFT驱动有更深的理解。我在实际项目中发现很多看似复杂的功能拆解后都是基础功能的组合应用。