从零驱动1.9英寸SPI TFT屏幕:基于ST7735S的嵌入式显示实战指南

发布时间:2026/8/1 14:36:26
从零驱动1.9英寸SPI TFT屏幕:基于ST7735S的嵌入式显示实战指南 1. 项目概述从一块1.9英寸LCD模块说起最近在整理工作室的物料时翻出了一块之前采购的1.9英寸LCD模块。这玩意儿个头不大但接口清晰引脚定义明确一看就是为嵌入式开发准备的。对于很多刚接触单片机或者想给项目加个小屏幕的朋友来说这种小尺寸的LCD模块是个绝佳的起点。它不像手机屏幕那么复杂驱动起来相对简单成本也低但又能实现显示文字、简单图形甚至动画的效果足以满足大多数DIY项目、智能家居终端、便携式仪表或者小型游戏机的显示需求。今天我就以手头这块1.9英寸LCD模块为例和大家深入聊聊如何从零开始驱动它把那些看似枯燥的数据手册参数变成屏幕上实实在在的像素点。这块屏幕的核心通常是一块ST7735S或类似的驱动芯片。这类芯片属于SPI接口的TFT LCD控制器以其性价比高、指令集成熟、社区资源丰富而著称。你可能会问为什么是SPI接口简单来说SPI串行外设接口用很少的几根线通常4根SCLK时钟、MOSI主出从入、CS片选、DC数据/命令选择就能实现高速数据传输极大地节省了单片机宝贵的IO口资源这对于引脚数量有限的微控制器比如常见的STM32、ESP8266/ESP32、Arduino系列来说至关重要。接下来我们就一步步拆解看看如何让这块小屏幕“活”起来。2. 模块核心解析与驱动芯片探秘2.1 硬件接口与引脚定义详解拿到模块第一件事就是看背面或者查阅资料搞清楚引脚定义。一块典型的1.9英寸分辨率常为170x320或160x128具体看型号SPI TFT模块其引脚排列通常如下VCC电源正极常见工作电压为3.3V。务必注意有些模块兼容5V逻辑电平但液晶面板本身仍是3.3V供电直接接5V可能会烧毁。最稳妥的方法是先确认数据手册或者用万用表测量板上是否有LDO低压差线性稳压器。如果有则VCC可接5VLDO会输出3.3V给屏幕如果没有强烈建议使用3.3V供电。GND电源地。CS片选信号Chip Select。低电平时模块才会响应SPI总线上的数据。当总线上挂载多个SPI设备时通过拉高或拉低此引脚来选择与哪个设备通信。RESET复位引脚Reset。低电平有效用于对驱动芯片进行硬件复位。初始化序列中一个正确的复位脉冲是必不可少的。DC或标记为A0、RS数据/命令选择引脚Data/Command。这是控制数据传输性质的关键。当DC为高电平时SPI总线上传送的是要显示的颜色数据像素值当DC为低电平时传送的是给ST7735S芯片的配置命令如设置扫描方向、开显示等。SCLKSPI时钟线Serial Clock由主设备单片机产生。MOSI或标记为SDA主设备输出从设备输入线Master Out Slave In。单片机通过这根线向屏幕发送命令或数据。BL或LED背光控制。通常高电平点亮背光。有些模块可能直接接VCC即上电常亮有些则引出可控引脚方便程序控制背光开关以实现省电。注意部分模块可能还有MISO引脚但ST7735S这类纯显示驱动芯片通常不需要向主机回传数据所以该引脚可能悬空或不连接。确认你的模块是否支持触摸功能如果支持触摸控制器如XPT2046会占用额外的SPI或I2C接口需另行接线。2.2 驱动芯片ST7735S工作机制浅析理解了引脚我们再来看看核心——ST7735S。你可以把它想象成一个拥有内置内存GRAM图形显示数据存储器的“像素管家”。我们的单片机并不需要实时刷新每一个像素而是通过SPI接口将一整帧画面的像素数据对于65K色每个像素点用2字节表示写入到这片GRAM中。之后ST7735S会自己以固定的时序从GRAM中读取数据并转换成对应的模拟电压驱动液晶分子偏转从而显示出图像。这个过程涉及几个关键概念扫描方向ST7735S支持设置GRAM的更新方向如从左到右、从上到下或旋转90度、180度等。这决定了你写入像素数据的顺序如何映射到物理屏幕上。初始化时正确设置扫描方向可以避免图像颠倒或旋转的问题。颜色格式最常见的是RGB565格式即用16位2字节表示一个像素高5位是红色中间6位是绿色低5位是蓝色。ST7735S也支持RGB444等其他格式但RGB565在色彩丰富度和数据量之间取得了良好平衡被广泛使用。窗口设置你可以通过命令指定一个矩形的“窗口”后续的像素数据写入操作将只影响这个窗口区域内的GRAM。这非常有用比如你只想更新屏幕的一部分区域如状态栏而不是刷新整屏可以显著提高效率。3. 软件驱动从底层时序到高层API3.1 底层SPI通信与GPIO模拟驱动屏幕的第一步是建立通信。如果你的单片机硬件带有SPI外设配置并使用它是最佳选择效率高且不占用CPU。以STM32的HAL库为例初始化SPI后发送数据的核心函数可能只是一个HAL_SPI_Transmit()调用。但对于没有硬件SPI或者想彻底理解时序的朋友用GPIO口模拟SPI俗称“软件SPI”也是一个很好的学习过程。关键在于精确控制SCLK和MOSI的时序。下面是一个用GPIO模拟SPI发送一个字节8位数据的示例思路/** * brief 使用软件SPI发送一个字节数据 * param data: 要发送的字节数据 */ void SPI_WriteByte(uint8_t data) { uint8_t i; // 循环8次从最高位(bit7)开始发送 for(i 0; i 8; i) { // 将SCLK拉低准备数据变化 LCD_SCLK_Clr(); // 判断当前要发送的位是1还是0并设置MOSI引脚电平 if(data 0x80) { LCD_MOSI_Set(); // 发送1 } else { LCD_MOSI_Clr(); // 发送0 } // 延时一小段时间根据屏幕数据手册要求通常很短 Delay_us(1); // 将SCLK拉高在上升沿或根据芯片定义是下降沿锁存数据 LCD_SCLK_Set(); Delay_us(1); // 数据左移准备发送下一位 data 1; } // 发送完成后确保SCLK处于空闲状态通常为高 LCD_SCLK_Set(); }实操心得软件SPI的延时Delay_us(1)不是固定的它需要根据屏幕数据手册中SPI接口的时序要求来调整主要是满足SCLK高低电平的最小脉宽以及数据建立和保持时间。如果速度太快屏幕可能无法正确识别数据如果太慢则刷新率会很低。在资源允许的情况下优先使用硬件SPI。3.2 命令与数据发送函数封装有了字节发送函数我们就可以封装两个更高级的函数写命令和写数据。这是与ST7735S对话的基础。/** * brief 向LCD写入一个命令 * param cmd: 命令字节 */ void LCD_Write_Cmd(uint8_t cmd) { LCD_DC_Clr(); // DC引脚拉低表示接下来发送的是命令 LCD_CS_Clr(); // 片选拉低选中设备 SPI_WriteByte(cmd); // 发送命令字节 LCD_CS_Set(); // 片选拉高结束传输 } /** * brief 向LCD写入一个数据字节 * param data: 数据字节 */ void LCD_Write_Data(uint8_t data) { LCD_DC_Set(); // DC引脚拉高表示接下来发送的是数据 LCD_CS_Clr(); // 片选拉低选中设备 SPI_WriteByte(data); // 发送数据字节 LCD_CS_Set(); // 片选拉高结束传输 }对于需要多个参数的命令例如设置坐标范围命令0x2A后面跟4个字节的起始和结束坐标我们会连续调用多次LCD_Write_Data()。3.3 初始化序列唤醒屏幕的“密码”ST7735S上电后处于一个未知状态需要一系列特定的命令和参数进行初始化才能正常工作。这个序列通常包含执行硬件复位拉低RESET引脚至少10ms再拉高。发送软件复位命令0x01。退出睡眠模式命令0x11并等待至少120ms。设置颜色格式如0x3A命令后面跟0x05表示RGB565。设置显示扫描方向0x36命令。设置像素显示窗口0x2A和0x2B命令。打开显示0x29命令。这个初始化序列因屏幕厂商、具体型号甚至批次可能略有不同。最可靠的方法是找到屏幕供应商提供的示例代码或数据手册中的初始化代码部分直接使用。网上通用的驱动库如Adafruit_ST7735通常内置了多种常见屏幕的初始化序列可以通过定义不同的型号宏来选择。4. 图形绘制基础与高级功能实现4.1 画点、画线与填充矩形驱动初始化成功后最基本的操作就是画一个点。这需要三个步骤设置要操作的像素坐标通过0x2A和0x2B命令设置列地址和行地址窗口将窗口设置为单个点。发送写GRAM命令0x2C。连续发送两个字节代表该像素点的RGB565颜色值。基于画点函数我们可以构建更复杂的图形函数画线使用布雷森汉姆直线算法高效计算出一条线段上所有需要绘制的像素点。填充矩形设置一个矩形区域的窗口然后连续发送width * height * 2个字节的颜色数据。这是清屏用单一颜色填充整个屏幕和绘制色块的基础。/** * brief 在指定位置画一个点 * param x: 横坐标 (0 ~ width-1) * param y: 纵坐标 (0 ~ height-1) * param color: RGB565颜色值 */ void LCD_DrawPoint(uint16_t x, uint16_t y, uint16_t color) { // 1. 设置窗口为单个点 LCD_SetWindow(x, y, x, y); // 2. 发送写GRAM命令 LCD_Write_Cmd(0x2C); // 3. 发送颜色数据的高字节和低字节 LCD_Write_Data(color 8); // 先发送高8位 LCD_Write_Data(color 0xFF); // 再发送低8位 } /** * brief 填充一个矩形区域 * param x0, y0: 矩形左上角坐标 * param x1, y1: 矩形右下角坐标 * param color: 填充颜色 */ void LCD_Fill(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t color) { uint32_t pixel_count (uint32_t)(x1 - x0 1) * (y1 - y0 1); // 1. 设置窗口为整个矩形区域 LCD_SetWindow(x0, y0, x1, y1); // 2. 发送写GRAM命令 LCD_Write_Cmd(0x2C); // 3. 循环发送颜色数据填充整个窗口 LCD_DC_Set(); LCD_CS_Clr(); for(uint32_t i0; i pixel_count; i) { SPI_WriteByte(color 8); SPI_WriteByte(color 0xFF); } LCD_CS_Set(); }注意事项在填充矩形或大量连续像素时像上面LCD_Fill函数那样在循环前一次性拉低CS循环内只发送数据最后再拉高CS比每发送两个字节就操作一次CS要快得多。这是优化SPI传输效率的一个小技巧。4.2 字符与位图显示显示文字是大多数项目的刚需。我们通常使用点阵字库。以16x16像素的汉字为例每个汉字需要32个字节16行 * 16列 / 8位每字节的数据来描述其点阵。显示过程就是按行按列判断每个比特位是1还是0是1则画前景色点是0则画背景色点或跳过。显示位图BMP的原理类似但数据来源是图像数组。通常我们需要先将图片如PNG、JPG通过工具如Img2Lcd、LCD Image Converter转换成C语言数组数组内容就是每个像素的RGB565值。显示时只需按顺序将数组数据写入屏幕GRAM即可。这里有一个关键点图像数组的存储顺序必须与屏幕的扫描方向匹配。如果你的屏幕初始化设置为从左到右、从上到下扫描而你的图像数组是按行优先存储的那么显示就是正常的。否则可能会出现图像被旋转或镜像的情况这时需要调整数组顺序或修改扫描方向设置。4.3 双缓冲与动画优化当你尝试制作流畅的动画时可能会发现直接在前台缓冲区即屏幕GRAM上绘图会有严重的闪烁或撕裂现象。这是因为你在绘制新一帧图像时屏幕正在读取并显示GRAM中未完成的、新旧混合的数据。双缓冲技术是解决这个问题的经典方案在单片机的内存中开辟一块和屏幕GRAM一样大小的缓冲区frame_buffer。所有的绘图操作画点、画线、渲染字符都先在这个内存缓冲区中进行。当一帧图像在内存缓冲区中完全绘制好后通过一次快速的、连续的数据传输DMA方式最佳将整个缓冲区的内容一次性拷贝到屏幕的GRAM中。由于整帧数据的切换非常快人眼几乎感知不到从而获得平滑的动画效果。当然这需要单片机有足够的内存对于170x320的16位色屏幕一帧缓冲区需要约108.8KB内存这对于一些RAM有限的单片机如STM32F103C8T6只有20KB可能是个挑战。此时可以考虑降低颜色深度如改用256色、缩小屏幕有效区域或使用外置RAM。5. 实战进阶项目集成与性能调优5.1 与实时操作系统RTOS结合在复杂的嵌入式项目中显示刷新可能只是众多任务之一。使用RTOS如FreeRTOS可以很好地管理这些并发任务。你可以创建一个专有的“显示任务”它等待一个消息队列或信号量。当其他任务如传感器数据采集、网络通信需要更新界面时它们不直接操作屏幕而是向“显示任务”发送一个包含更新信息的消息。“显示任务”接收到消息后统一在内存缓冲区中执行绘图操作最后刷新到屏幕。这种架构解耦了业务逻辑和显示驱动使代码更清晰也避免了多任务同时访问显示资源导致的冲突。同时可以将耗时的整屏刷新操作放在低优先级的任务中或者使用DMA传输确保高优先级的任务如电机控制不会被阻塞。5.2 DMA传输大幅提升刷新率对于带有DMA控制器的单片机使用DMA来搬运显示数据到SPI外设是释放CPU压力的终极武器。以STM32为例你可以配置SPI工作在发送模式并关联一个DMA通道。当需要刷新屏幕或填充大块区域时你只需要设置好源地址内存中的颜色数据数组、目标地址SPI数据寄存器、数据长度然后启动DMA传输。之后CPU就可以去处理其他事情DMA控制器会在后台自动完成所有数据的搬运和发送效率极高。实测中对于同样的屏幕使用软件SPI全屏刷新可能需要上百毫秒使用硬件SPI可能缩短到几十毫秒而使用“硬件SPI DMA”的组合可能只需要几毫秒帧率提升非常明显。5.3 低功耗设计与背光控制对于电池供电的便携设备屏幕是耗电大户。除了在软件上尽量减少刷新频率如只有数据变化时才刷新特定区域硬件上也可以做优化背光控制如果屏幕的BL引脚是独立可控的在设备休眠或不需要看屏幕时彻底关闭背光可以节省大量电能。可以使用PWM信号控制BL引脚实现亮度调节。睡眠模式ST7735S芯片本身支持睡眠模式0x10命令。在长时间不更新显示时可以发送睡眠命令将驱动芯片内部电路功耗降到最低。需要显示时再发送唤醒命令0x11并等待一段稳定时间。注意进入睡眠模式后GRAM内容可能会丢失唤醒后需要重新初始化并绘制界面。6. 常见问题排查与调试心得驱动一块LCD屏幕的过程就是与各种奇怪现象斗争的过程。下面记录了一些典型问题及排查思路现象可能原因排查步骤与解决方案屏幕全白/全黑/花屏无任何显示1. 电源问题电压不对或电流不足。2. 复位序列不正确。3. 初始化命令序列错误或遗漏。4. 背光未开启。1. 用万用表测量VCC和GND之间电压是否为稳定的3.3V。检查电源能否提供足够电流通常需100mA以上。2. 用示波器或逻辑分析仪抓取RESET引脚波形确保有低电平脉冲10ms。3.逐行核对初始化代码与官方示例或数据手册对比。特别注意命令后的延时是否足够如退出睡眠后的120ms。4. 检查BL引脚尝试直接接VCC看是否亮起。显示内容错位、颠倒或镜像扫描方向0x36命令设置错误。查阅数据手册中0x36命令的参数定义MY, MX, MV等位尝试不同的参数组合0x00, 0x40, 0x80, 0xC0等直到显示方向正确。注意修改后画点函数的坐标映射逻辑可能也需要调整。颜色显示错误如红色变蓝色颜色格式0x3A命令设置错误。确认初始化序列中设置颜色格式的命令参数。对于RGB565通常是0x05或0x06。同时检查发送像素数据时高低字节顺序是否正确是RGB高位在前还是BGR高位在前。SPI通信失败屏幕无反应1. SPI时序时钟极性、相位不匹配。2. 引脚连接错误或虚焊。3.CS或DC引脚控制逻辑错误。1. ST7735S通常工作在SPI Mode 0CPOL0 CPHA0或Mode 3。检查单片机SPI配置是否一致。2. 用逻辑分析仪连接SCLK,MOSI,CS,DC观察上电初始化阶段是否有正确的波形。这是最直接的调试手段。3. 确保在发送每个命令或数据帧前后正确控制了CS和DC的电平。刷新速度慢动画卡顿1. 使用软件SPI且延时过长。2. 绘图算法效率低如全屏刷新画点。3. 没有使用DMA。1. 尝试减少软件SPI的延时或在资源允许时切换到硬件SPI。2. 优化图形函数多用矩形填充代替逐点绘制使用内存缓冲区局部刷新策略。3. 启用硬件SPI的DMA传输功能。显示有拖影或残影1. 刷新率过高液晶分子响应跟不上。2. 电源纹波过大。1. 适当降低刷新频率或在帧之间增加微小延时。2. 在屏幕的电源引脚附近增加一个10-100uF的电解电容并联一个0.1uF的陶瓷电容进行滤波。调试心得逻辑分析仪是你的好朋友一个几十块钱的简易逻辑分析仪能让你直观地看到SPI线上的每一位数据、每一个命令是排查通信问题无可替代的工具。对比分析仪抓取的数据和你代码中期望发送的数据能快速定位问题。从简单到复杂不要一开始就试图显示复杂图形。写一个简单的测试程序初始化后只用LCD_Fill函数将屏幕填充成红、绿、蓝三种纯色。如果这个能成功说明电源、复位、基本通信和初始化序列都是对的然后再逐步测试画点、画线、显示字符。善用现有驱动库除非是为了深入学习否则在项目初期直接使用成熟的驱动库如Arduino的Adafruit_ST7735或STM32的TFT_eSPI可以避免很多底层麻烦。先让屏幕跑起来再根据需要去研究和修改底层。注意屏幕型号差异同样是1.9英寸不同厂家、不同批次的屏幕其初始化序列、偏移量offset、分辨率可能都有细微差别。购买时尽量索要资料网上找驱动代码时也要注意型号匹配。