0.96寸彩屏驱动移植实战:基于ST7735的80x160 IPS TFT显示屏在嵌入式平台上的应用
0.96寸彩屏驱动移植实战基于ST7735的80x160 IPS TFT显示屏在嵌入式平台上的应用最近在做一个需要小尺寸显示的项目手头正好有一块0.96寸的IPS彩屏分辨率是80x160驱动芯片是ST7735。这种小屏在物联网设备、穿戴设备上用得特别多但网上的驱动代码质量参差不齐移植起来经常遇到各种问题。今天我就结合自己实际移植的经验手把手带你把这套驱动移植到你的嵌入式开发板上让你少走弯路。这篇文章适合正在寻找小尺寸彩屏驱动解决方案的嵌入式软件工程师和电子爱好者。我会从硬件引脚连接开始一步步讲解SPI通信配置、底层驱动函数移植最后实现显示字符、汉字和图片的功能。跟着做下来你就能在自己的项目里用上这块性价比很高的显示屏了。1. 硬件准备与引脚连接咱们先来看看这块屏的基本情况。我用的这块是0.96寸IPS TFT彩屏驱动芯片是ST7735分辨率80x160通过SPI接口通信。1.1 屏幕规格参数参数规格工作电压2.8~3.3V工作电流30mA模块尺寸24(H) x 30(V) mm像素大小80(H) x 160(V) RGB驱动芯片ST7735通信协议SPI管脚数量8 Pin2.54mm间距排针注意工作电压一定要在2.8-3.3V之间超过3.3V可能会损坏屏幕1.2 引脚定义与连接这块屏有8个引脚每个引脚的作用如下VCC电源正极接3.3VGND电源地SCLSPI时钟线SCKSDASPI数据输出线MOSIRES复位引脚低电平有效DC数据/命令选择引脚高电平为数据低电平为命令CS片选引脚低电平有效BLK背光控制引脚高电平点亮在实际项目中如果MCU的GPIO引脚不够用有两个引脚可以简化RES可以接到MCU的复位引脚这样MCU复位时屏幕也跟着复位BLK可以直接接3.3V或悬空代价是无法控制背光亮度下面是我在立创开发板上的实际连接方案屏幕引脚开发板引脚GNDGNDVCC3V3SCLP302SDAP207RESP403DCP407CSP408BLKP4092. 驱动代码移植步骤移植驱动不是简单复制粘贴就行需要根据你的硬件平台做适配。我总结了一个五步法跟着做基本不会出错。2.1 获取原始资料首先需要下载屏幕的资料和示例代码。原始资料里提供了百度网盘链接提取码是8888。下载后你会看到这些文件屏幕规格书ZJY096S0800TG01.pdf示例工程代码字库文件2.2 创建工程并添加文件在你的开发环境中新建一个工程然后把下载的驱动文件复制过来。主要需要这几个文件lcd_init.h // LCD初始化相关定义 lcd_init.c // LCD初始化实现 lcd.h // LCD应用层函数声明 lcd.c // LCD应用层函数实现 lcdfont.h // 字库数据提示如果你用的是Keil、IAR或VSCodePlatformIO记得在工程设置里添加这些文件的路径。2.3 修改引脚配置这是最关键的一步需要根据你的实际硬件连接修改引脚定义。打开lcd_init.h文件找到引脚定义部分//-----------------LCD端口定义---------------- #define LCD_RES_PIN BSP_IO_PORT_04_PIN_03 // 复位引脚 #define LCD_DC_PIN BSP_IO_PORT_04_PIN_07 // 数据/命令选择 #define LCD_CS_PIN BSP_IO_PORT_04_PIN_08 // 片选 #define LCD_BLK_PIN BSP_IO_PORT_04_PIN_09 // 背光控制这里的BSP_IO_PORT_04_PIN_03对应的是P403引脚。你需要根据自己开发板的引脚命名规则来修改。比如在STM32上可能会写成GPIO_PIN_3这样的形式。2.4 配置SPI通信ST7735通过SPI接口通信需要配置SPI的时钟、数据位宽等参数。在lcd_init.c的LCD_Init()函数开头你会看到SPI初始化的代码void LCD_Init(void) { fsp_err_t err R_SPI_Open(g_spi0_ctrl, g_spi0.p_cfg); if (err ! FSP_SUCCESS) { printf(\r\nSPI开启错误\r\n); } // ... 后续初始化代码 }这里用的是瑞萨RA系列MCU的FSP库。如果你用的是其他平台比如STM32的HAL库需要改成对应的SPI初始化函数。SPI配置要点时钟极性CPOL通常设为0时钟相位CPHA通常设为0数据位宽为8位时钟频率建议在10-20MHz之间太快可能导致通信不稳定2.5 修改延时函数驱动里用到了毫秒和微秒延时原始代码使用的是瑞萨的延时函数#ifndef delay_ms #define delay_ms(x) R_BSP_SoftwareDelay(x, BSP_DELAY_UNITS_MILLISECONDS) #endif你需要替换成自己平台的延时函数。比如在STM32 HAL库中可以改成#define delay_ms(x) HAL_Delay(x) #define delay_us(x) delay_us_custom(x) // 需要自己实现微秒延时3. 核心驱动代码解析移植完成后咱们来看看这些驱动函数是怎么工作的。理解原理后调试起来会更容易。3.1 屏幕初始化序列LCD_Init()函数里有一长串的寄存器配置这是ST7735芯片的初始化序列。每个命令都有特定作用LCD_WR_REG(0x11); // Sleep out退出睡眠模式 delay_ms(120); // 需要120ms延时 LCD_WR_REG(0xB1); // 设置正常模式下的帧率 LCD_WR_DATA8(0x05); LCD_WR_DATA8(0x3C); LCD_WR_DATA8(0x3C); LCD_WR_REG(0x36); // 设置扫描方向 if(USE_HORIZONTAL0)LCD_WR_DATA8(0x08); else if(USE_HORIZONTAL1)LCD_WR_DATA8(0xC8); else if(USE_HORIZONTAL2)LCD_WR_DATA8(0x78); else LCD_WR_DATA8(0xA8);注意初始化序列中的延时很重要特别是退出睡眠模式后的120ms延时如果太短屏幕可能无法正常显示。3.2 显示方向设置这块屏支持四个方向的显示通过USE_HORIZONTAL宏定义来控制#define USE_HORIZONTAL 2 // 设置横屏或者竖屏显示 0或1为竖屏 2或3为横屏 #if USE_HORIZONTAL0||USE_HORIZONTAL1 #define LCD_W 80 #define LCD_H 160 #else #define LCD_W 160 #define LCD_H 80 #endif0竖屏原点在左上角1竖屏原点在右上角2横屏原点在左上角3横屏原点在左下角实际使用中我发现有些屏的起始坐标需要偏移。在LCD_Address_Set()函数里可以看到这些偏移量if(USE_HORIZONTAL0) { LCD_WR_REG(0x2a); // 列地址设置 LCD_WR_DATA(x126); // X起始地址26 LCD_WR_DATA(x226); // X结束地址26 // ... }这个26的偏移是因为屏幕实际有132列但只用了中间的80列来显示。3.3 基本绘图函数驱动提供了丰富的绘图函数最基础的是画点函数void LCD_DrawPoint(u16 x,u16 y,u16 color) { LCD_Address_Set(x,y,x,y); // 设置光标位置 LCD_WR_DATA(color); // 写入颜色数据 }基于画点函数实现了画线、画矩形、画圆等高级功能。比如画线函数用的是Bresenham算法效率很高void LCD_DrawLine(u16 x1,u16 y1,u16 x2,u16 y2,u16 color) { u16 t; int xerr0,yerr0,delta_x,delta_y,distance; int incx,incy,uRow,uCol; delta_xx2-x1; // 计算坐标增量 delta_yy2-y1; uRowx1; uColy1; // ... Bresenham算法实现 }3.4 显示字符和汉字显示字符需要字库支持驱动里内置了多种尺寸的ASCII字库和汉字字库// 显示一个字符 void LCD_ShowChar(u16 x,u16 y,u8 num,u16 fc,u16 bc,u8 sizey,u8 mode) { u8 temp,sizex,t,m0; u16 i,TypefaceNum; u16 x0x; sizexsizey/2; TypefaceNum(sizex/8((sizex%8)?1:0))*sizey; numnum- ; // 得到偏移后的值 // 根据字号选择不同的字库 if(sizey12)tempascii_1206[num][i]; // 6x12字体 else if(sizey16)tempascii_1608[num][i]; // 8x16字体 else if(sizey24)tempascii_2412[num][i]; // 12x24字体 else if(sizey32)tempascii_3216[num][i]; // 16x32字体 }显示汉字也是类似的原理只是汉字字库更大。驱动支持12x12、16x16、24x24、32x32四种尺寸的汉字。4. 实际应用测试移植完成后咱们写个简单的测试程序验证一下。在app.c的Run()函数中添加测试代码void Run(void) { // 初始化调试串口 UART0_Debug_Init(); printf(\r\n 开始LCD测试 \r\n); float t 0; LCD_Init(); // LCD初始化 LCD_Fill(0, 0, LCD_W, LCD_H, WHITE); // 清屏为白色 while(1) { // 显示汉字 LCD_ShowChinese(40,0,(const char *)中景园电子,RED,WHITE,16,0); // 显示屏幕尺寸 LCD_ShowString(10,20,(const u8 *)LCD_W:,RED,WHITE,16,0); LCD_ShowIntNum(58,20,LCD_W,3,RED,WHITE,16); LCD_ShowString(10,40,(const u8 *)LCD_H:,RED,WHITE,16,0); LCD_ShowIntNum(58,40,LCD_H,3,RED,WHITE,16); // 显示浮点数递增 LCD_ShowFloatNum1(10,60,t,4,RED,WHITE,16); t0.11; // 显示图片需要提前准备好图片数组 // LCD_ShowPicture(100,20,40,40,gImage_1); delay_ms(100); } }编译下载后你应该能看到屏幕上显示中景园电子和屏幕尺寸信息还有一个不断增加的浮点数。5. 常见问题与调试技巧我在移植过程中遇到过不少坑这里分享几个常见问题和解决方法5.1 屏幕白屏或不显示可能原因电源电压不对必须是2.8-3.3V复位时序不对初始化序列有误SPI通信失败解决方法用万用表测量VCC和GND之间的电压检查RESET引脚波形确保有正确的复位脉冲在LCD_Init()函数开头加调试信息确认SPI初始化成功用逻辑分析仪抓取SPI波形看是否有数据发出5.2 显示颜色异常可能原因颜色格式不对ST7735是RGB565格式扫描方向设置错误伽马校正参数有问题解决方法确认颜色值是16位的RGB565格式尝试不同的USE_HORIZONTAL值检查初始化序列中的伽马校正寄存器0xE0和0xE15.3 显示位置偏移可能原因起始坐标偏移量不对显示区域设置错误解决方法调整LCD_Address_Set()函数中的偏移量检查0x2A和0x2B命令设置的显示区域5.4 显示速度慢可能原因SPI时钟频率太低每次画点都设置地址效率低没有使用DMA传输优化建议提高SPI时钟频率但不要超过芯片最大频率批量传输数据时只设置一次地址如果MCU支持使用SPI DMA传输调试小技巧在关键函数前后加GPIO翻转用示波器测量执行时间。比如在LCD_WR_DATA()函数前后翻转一个IO就能知道写一个像素需要多长时间。6. 进阶应用显示图片和动画基本的文字显示搞定后咱们可以玩点更高级的——显示图片和简单动画。6.1 显示图片显示图片需要先把图片转换成数组。可以用Img2Lcd这类工具转换// 40x40像素的图片数组示例 const unsigned char gImage_1[3200] { /* 这里是一长串的RGB565数据 */ }; void Show_Image(void) { // 在(100,20)位置显示40x40的图片 LCD_ShowPicture(100, 20, 40, 40, gImage_1); }图片数组的大小计算宽度 × 高度 × 2字节RGB565。40x40的图片就是40×40×23200字节。6.2 简单动画实现基于画图函数可以实现简单的动画效果void Simple_Animation(void) { u16 x 0; u16 y 0; u16 dx 1; u16 dy 1; while(1) { // 清除上一帧 LCD_Fill(x, y, x10, y10, WHITE); // 更新位置 x dx; y dy; // 边界检测 if(x LCD_W-10 || x 0) dx -dx; if(y LCD_H-10 || y 0) dy -dy; // 绘制新位置 LCD_Fill(x, y, x10, y10, RED); delay_ms(50); } }这个例子实现了一个红色方块在屏幕上弹跳的动画。6.3 优化显示性能当需要刷新大量内容时性能优化很重要局部刷新只更新变化的部分而不是整个屏幕双缓冲在内存中绘制完成后再一次性显示使用硬件加速如果MCU有LCD控制器直接使用硬件加速// 局部刷新示例 void Update_Score(u16 score) { static u16 last_score 0; // 只更新分数区域 LCD_Fill(50, 0, 100, 16, BLACK); // 清除旧分数 LCD_ShowIntNum(50, 0, score, 3, WHITE, BLACK, 16); last_score score; }这块0.96寸的ST7735屏虽然小但功能齐全在资源受限的嵌入式系统中特别实用。移植过程中最关键的是引脚配置和SPI初始化这两个搞定了后面的显示功能就是水到渠成。实际项目中我一般会先把基本的画点、画线函数调通然后再逐步添加更复杂的功能。有个小经验分享如果屏幕显示异常先别急着改代码用逻辑分析仪抓一下SPI波形很多时候是硬件连接或时序问题。另外不同批次的屏幕初始化参数可能略有差异如果遇到问题可以尝试调整初始化序列中的延时和寄存器值。