尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
ZTMR开发板驱动SPI LCD显示图片:从接线到代码的完整实战
二手平台淘来的板子很多时候资料残缺不全能跑通一个完整实例全靠自己折腾。这次分享的就是我手里这块“咸鱼ZTMR”驱动LCD显示图片的全过程从硬件接线、图片数据转换到代码实现和排坑思路一步步说清楚希望能帮到正在捣鼓同类开发板的同学少走弯路。ZTMR这个名字圈子里不陌生很多人是从二手平台收来的板载一颗Cortex-M3内核的MCU搭配OV2640摄像头接口、SD卡槽和一个TFT LCD插座性价比很高。LCD图片显示是嵌入式开发里非常经典的一个功能点往小了说是点阵绘图的基础往大了说可以扩展到电子相册、GUI界面、摄像头画面预览。这篇主要围绕2.4寸SPI接口TFT屏幕做静态图片显示适合刚接触STM32/单片机开发、想把屏幕玩明白的初学者也适合手里刚好有这块板子但找不到完整例程的朋友。1. 项目从哪里来认识这块“咸鱼ZTMR”开发板1.1 板子整体印象这块板子在二手市场流通量不小典型配置是主控MCUOV2640摄像头2.4寸TFT LCDSD卡槽若干传感器接口板子设计紧凑很多时候被拿来当学习套件卖。我收来的这块还带着出厂测试固件上电后屏幕能点亮显示一个静态画面摄像头也能出图说明硬件本身没有大问题。想让它跑自己的图片显示程序第一步就是搞明白屏幕接口和引脚定义。ZTMR的LCD插座通常是14pin或22pin的排针包含电源、地、背光、SPI时钟、SPI数据、复位、命令/数据选择等信号。不同批次板子的引脚顺序可能会变这也是很多人在网上求资料找例程的原因——板子的原理图往往买回来就是缺失的。我这块板子LCD接口实测下来是标准的SPI四线模式SCLK、MOSI、CS、DC另外有RESET复位控制和背光控制脚电源部分需要注意是3.3V还是5V供电接错容易烧屏幕。1.2 为什么选SPI接口的TFT屏而不是并口屏LCD屏幕的接口类型常见有SPI、并行8080/6800、RGB和MIPI等在单片机开发板这个级别SPI接口的TFT屏是性价比和易用性最均衡的选择。为什么这么说SPI接口优势在于引脚占用少GPIO资源紧张的小板子也能驱动而且硬件SPI可以配合DMA刷新率不至于太难看。而并行接口虽然数据传输带宽更高但需要8位或16位并行GPIO布线复杂代码量和时序处理也更多对初学者不太友好。ZTMR板载屏幕选SPI我觉得还有一层原因——它要和摄像头共用MCU资源SPI总线结构简单可以通过片选信号灵活切换方便后续扩展SD卡或者其他SPI设备整体设计上更灵活。实际测试下来SPI模式驱动320x240分辨率的屏幕整屏刷新一帧大约几十毫秒显示静态图片完全够用。1.3 板子背后的“坑”和惊喜买二手开发板最大的不确定性是资料。ZTMR很少有官方完整手册网上流传的多是网友整理的资料包里面散落着出厂例程、引脚定义图和屏幕驱动代码。我这块板子到手后花了点时间对照着网上的引脚定义逐一用万用表测量确认了LCD接口每个引脚的实际连接才敢放心写代码。这一摸排发现了一个惊喜屏幕控制芯片是ST7789V这是一个非常通用的TFT驱动IC资料丰富驱动代码可以参考的资料很多网上随便一搜就有成熟的底层驱动库。注意屏幕驱动IC型号很重要同样是2.4寸SPI屏可能是ST7789、ILI9341、ST7735等不同方案初始化序列完全不同搞错了直接花屏。2. 图片显示的核心原理屏幕是怎么“看到”图片的2.1 像素、RGB颜色和缓冲区的故事LCD屏幕本质是一个像素点矩阵2.4寸屏幕分辨率通常是320x240也就是横向上有320个像素点纵向有240个像素点总共76800个点。怎么让屏幕显示一张图片把图片拆解成一个个像素点每个像素点存储一个颜色值把这些颜色值按顺序发送给LCD控制芯片芯片再把颜色值映射到对应的像素位置。整个过程和我们打印照片很相似打印机扫描每一行把墨点按顺序落在纸上屏幕则是逐行扫描像素点把颜色填进去。颜色值在单片机里怎么表示LCD驱动IC内部用16位RGB565格式存储一个像素的颜色高5位是红色分量中间6位是绿色分量低5位是蓝色分量一共可以表达65536种颜色。为什么用565而不是888因为16位正好是2字节内存占用小读写效率高色彩表现对绝大多数应用场景已经足够。一张320x240的图片RGB565格式的数据大小是320x240x2等于153600字节算下来大概150KB这个数据量对STM32F103这种512KB Flash的芯片来说还能接受。2.2 图片预处理从BMP/JPG到C语言数组电脑上的图片大多是JPG或PNG格式是经过压缩编码的MCU没法直接解码。要让单片机显示图片通常有两种思路一是把图片解码成RGB565后以C数组形式烧录到Flash里适合显示固定图片二是用FATFS读SD卡里的BMP文件适合做电子相册自由更换图片。新手入门建议先做数组显示等搞懂了再拓展SD卡方案。把图片转成C数组需要用到取模工具我这里用的是常见的Image2Lcd参数设置上核心就几项输出格式选“C语言数组”颜色格式选“RGB565”16位色最大宽度和高度设成320和240扫描方式选“水平扫描高位在前”勾选“包含图像头文件”不是必须的我更习惯只导出纯数据数组其中RGB565的字节顺序值得单独说ST7789V这类控制器默认接收的是高字节在前big-endian也就是一个16位颜色值0xF800红色发送顺序是先0xF8再0x00。如果工具默认输出是ABGR或低字节在前就要在代码里做字节交换否则颜色会乱。我的经验是先用一张纯色图片测试字节序红色如果显示成了蓝色那基本就是字节序反了。2.3 图片尺寸和内存的账要算清楚图片转换前先要在电脑上把图片裁剪缩放到320x240像素。这一步用Windows画图就能完成注意缩放时保持纵横比否则图片会变形。缩放后的图片建议另存为24位BMP格式再拿去转换虽然文件变大了但转换工具兼容性最好减少了格式解码出错的可能性。转换生成的C数组大小大约是150KB你要确认自己的单片机Flash够不够。ZTMR主控的Flash通常在512KB左右放一张图片是绰绰有余的但如果要放多张不同图片就要精打细算或者琢磨怎么用外部存储。这也是为什么务实一点的方案是开发调试时用数组真正做产品时上SD卡FATFS。3. 硬件接线与初始化配置细节3.1 引脚连接与实物接线表我没有采用现成的例程引脚定义而是对着自己的板子实测了一遍。先把常用引脚定义整理成表后面写代码直接对应。信号名开发板引脚功能说明LCD_SCLKPA5SPI1时钟LCD_MOSIPA7SPI1主机发送数据LCD_CSPA4片选信号低电平有效LCD_DCPA2数据/命令选择高电平数据低电平命令LCD_RSTPA1复位信号低电平复位LCD_BLPA3背光控制高电平点亮供电方面整个模块用3.3V供电背光LED如果独立供电注意电压电流别超限我用的是共用3.3V实测背光亮度足够。需要提醒的是SPI通信线不建议用跳线飞得老长保持连线短而粗特别是时钟频率提高之后长跳线的寄生电容会影响信号质量。3.2 LCD初始化时序要点解析SPI屏幕驱动的第一步是初始化时序看起来是一堆寄存器配置但实际上顺序有讲究。我拆解一下核心流程硬件复位先把RST引脚拉低至少10毫秒再拉高。这个步骤是为了让控制芯片内部逻辑清零状态回到已知的初始状态。有些人省略这一步或者复位时间太短很大概率后面出现各种诡异问题。退出睡眠模式初始化序列里通常先发送SLPOUT0x01芯片默认是睡眠状态屏幕不工作。退出睡眠后还要等待约120毫秒让内部电路充分稳定。关闭显示发DISP0x28先关闭显示在配置寄存器时不希望屏幕闪动。配置像素格式和显示方向设置COLMOD0x3A为0x05表示16位RGB565设置MADCTL0x36控制扫描方向和RGB/BGR顺序。开启显示发送DISPON0x29屏幕就绪。这个初始化序列是从驱动IC数据手册和大量代码里提炼的公共部分具体寄存器值可以微调但先后顺序最好不要乱。特别是睡眠退出和延时等待图省事的话后期问题排查会很难受。3.3 SPI通信速率的取舍初始化代码里我配置SPI1为主模式数据帧8位时钟极性CPOL0、相位CPHA0也就是SPI Mode 0。这个模式是绝大多数LCD驱动芯片默认支持的。SPI分频系数我用了4在主频72MHz的情况下SPI时钟约18MHz。这个速率在理论范围内但受制于线材长度和质量实测偶尔出现数据错位。后来我把分频系数调整为8时钟降到9MHz显示效果非常稳定。这里想说的是不要盲目追求高速嵌入式系统稳定优先图片显示这种场景9MHz绰绰有余32ms就能刷完153600字节的数据感官上依然流畅。如果后续要上DMASPI_DR寄存器配合DMA可以把CPU解放出来我这次没有用DMA直接轮询发送代码逻辑更清晰也更适合初学者理解。4. 核心代码实现从零写一个图片显示例程4.1 LCD底层驱动函数设计上一节初始化配置完毕接下来要看LCD底层驱动怎么组织。很多网上的例程代码风格比较随意变量命名和函数划分都不够清晰我按自己的习惯重构了一下。底层函数按层次划分// 命令/数据切换 void LCD_DC_CTRL(uint8_t isData) { HAL_GPIO_WritePin(DC_PORT, DC_PIN, isData ? GPIO_PIN_SET : GPIO_PIN_RESET); } // 发送命令 void LCD_Write_Cmd(uint8_t cmd) { LCD_DC_CTRL(0); LCD_CS_CTRL(0); SPI_SendByte(cmd); LCD_CS_CTRL(1); } // 发送数据 void LCD_Write_Data(uint8_t data) { LCD_DC_CTRL(1); LCD_CS_CTRL(0); SPI_SendByte(data); LCD_CS_CTRL(1); } // 批量发送数据 void LCD_Write_Data_Buf(uint8_t *buf, uint16_t len) { LCD_DC_CTRL(1); LCD_CS_CTRL(0); for (uint16_t i 0; i len; i) { SPI_SendByte(buf[i]); } LCD_CS_CTRL(1); }这里最关键的一点是DC引脚电平状态它告诉控制芯片当前字节是命令还是数据。因为LCD的SPI不像SD卡那样有明确的数据包结构而是靠DC线的电平来区分内容类型。底层函数封装好了上层应用就不用关心这些细节只管调用。4.2 设置显示窗口让屏幕知道像素画在哪里要往屏幕指定区域写像素需要先设置“显示窗口”。ST7789V支持通过CASET列地址0x2A和RASET行地址0x2B来划定一个矩形区域之后发送的像素数据会依次填充到这个区域里。比如我要在屏幕左上角显示一张320x240的图片窗口就设成整个屏幕从第0行0列到第239行319列。void LCD_Set_Window(uint16_t x_start, uint16_t y_start, uint16_t x_end, uint16_t y_end) { LCD_Write_Cmd(0x2A); // CASET LCD_Write_Data(x_start 8); LCD_Write_Data(x_start 0xFF); LCD_Write_Data(x_end 8); LCD_Write_Data(x_end 0xFF); LCD_Write_Cmd(0x2B); // RASET LCD_Write_Data(y_start 8); LCD_Write_Data(y_start 0xFF); LCD_Write_Data(y_end 8); LCD_Write_Data(y_end 0xFF); LCD_Write_Cmd(0x2C); // RAMWR写内存 }窗口设置之后紧接着调用RAMWR命令0x2C然后连续发送的像素数据就会像流水一样填进这个窗口。注意坐标范围不要越界比如320x240屏幕上列坐标范围是0到319行坐标范围是0到239一旦超界行为就不可预期了。4.3 显示图片的主逻辑有了窗口设置函数显示一整张图片就简单了设置窗口覆盖整屏然后把图片数组连续发给LCD。void LCD_Show_Image(uint16_t *image, uint16_t width, uint16_t height) { LCD_Set_Window(0, 0, width - 1, height - 1); LCD_Write_Data_Buf((uint8_t *)image, width * height * 2); }这里有个细节image指针是uint16_t类型但底层发送是按字节的所以在调用批量发送函数时要把指针强转为uint8_t*。如果你的取模工具输出的是大端字节序那么内存里的排列就是高字节在前发送顺序正好和处理器字节序无关直接发送没问题如果工具是小端输出这里就要逐字节调换顺序了。完整的调用流程是这样int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_SPI1_Init(); LCD_Init(); LCD_Fill(0, 0, 319, 239, 0x0000); // 先清屏 LCD_Show_Image((uint16_t *)gImage_demo, 320, 240); while(1) { // 可以在这里加其它功能 } }清屏操作其实是调用填充函数往整个屏幕写入统一颜色值。填充函数内部也是设置窗口然后循环写像素和图片显示唯一区别是像素值来自一个常量而不是数组。4.4 提高刷新效率的几个技巧轮询发送150KB数据最朴素的做法是每发一个字节都循环判断发送完成标志。我实测下来代码简单但效率还有优化空间可以批量地先把数据写入发送寄存器再等待。用HAL库的HAL_SPI_Transmit一次发送整个数组效率比逐字节调用高不少底层库本身已经做了状态标志等待处理。另一个优化方向是充分利用SPI的DMA能力。初始化SPI后调用HAL_SPI_Transmit_DMA挂在中断里CPU可以在DMA搬运数据的同时做其他计算刷新一整帧图片时这个优势尤其明显。但初学阶段我建议先跑通轮询版本再迁移到DMA版本。一个直接的理由是轮询版本时序完全可控方便调试排查问题时不会引入DMA传输的额外变量。我把DMA版本的代码也贴出来供参考void LCD_Show_Image_DMA(uint16_t *image, uint16_t width, uint16_t height) { LCD_Set_Window(0, 0, width - 1, height - 1); HAL_SPI_Transmit_DMA(hspi1, (uint8_t *)image, width * height * 2); }使用DMA版本要注意传输完成回调里及时关闭SPI避免和下一次SPI操作冲突。另外你要保证SPI发送数据期间DC引脚一直维持高电平数据模式有些屏翻转DC会导致数据被误判为命令这种错误处理起来比想象的更隐蔽。5. 实操过程中的问题排查与避坑技巧整理5.1 白屏问题排查思路白屏是最常见的问题特征就是背光亮了但屏幕全白。我按优先级列出排查顺序第一步查电源和复位。先确认LCD供电引脚上有没有稳定3.3V电流够不够——如果板子的供电能力不足背光能亮但逻辑电路工作不稳定表现就是白屏。复位信号同样关键如果RST引脚一直处于低电平或者上电后没有正常释放控制芯片根本没法启动。第二步查SPI信号。白屏通常意味着LCD连最基本的初始化命令都没收到。用示波器或者逻辑分析仪抓SPI时钟和数据线上是否有时序活动如果CS和DC在初始化时没有正确拉低拉高数据根本传不进去。第三步查初始化时序顺序。初始化序列里有个常见坑发送SLPOUT退出睡眠后要等待约120毫秒然后再发送后续命令。如果等待时间不够芯片内部还没完全从睡眠模式过渡到正常工作状态后续命令会被忽略屏幕就会一直白屏。提示遇到白屏先用万用表量电压再用逻辑分析仪看波形基本能定位80%的问题不要上来就怀疑代码逻辑。5.2 图像颜色异常偏色、反色和随机色块图片能显示了但颜色不对这是第二个高频问题。最常见的情况是红色和蓝色对调。RGB565的字节序取模工具输出的是大端还是小端直接决定图片颜色。如果发现红色区域显示成蓝色十有八九是字节序反了。解决的办法是修改取模工具的字节序设置或者在代码里做一次高低字节交换二选一即可。另一种情况是整个画面蒙上一层绿色或紫色。这通常是RGB还是BGR通道顺序配置错误。ST7789V的MADCTL寄存器里有一个RGB/BGR位设置成0对应RGB设置成1对应BGR。如果你的驱动IC接线或数据手册定义不同就可能输出错误的颜色。验证方法还是用纯色图片显示纯红、纯绿、纯蓝三张测试图分别看输出什么颜色就能判断通道是否错乱。最后一种不常见但很坑的是随机色块。这多半是SPI速率过高导致数据传输出错。解决办法就是降速把分频系数从4调成8甚至16。嵌入式开发里稳定性和速度的取舍很多时候就是在这种实际测试中得出的经验。5.3 图像显示偏移和扫描方向问题图片显示出来了但位置偏移或者倒过来这是MADCTL寄存器里扫描方向配置的问题。MADCTL控制LCD内部GRAM的读写方向和坐标映射不同厂家的屏幕默认值可能不同。要解决方向问题直接调整MADCTL的MX、MY和MV位就可以实现水平和垂直镜像、行列交换。我的代码里把MADCTL配成0x00默认从左上角开始扫描如果你发现图片是倒着的或镜像的就按组合调整寄存器值。另外要注意窗口设置和图片数组扫描顺序要一致。如果屏幕扫描方向是从右到左而图片数组是按从左到右的像素排列显示效果就会上下左右乱跳。这种情况下要么改窗口设置要么把图片数组按反方向重新生成灵活处理就好。5.4 刷新慢的优化思路静态图片显示还好如果做连续刷新就会明显感觉到闪烁和拖影。这里有几个可以提升体验的手段。通信效率优先。单个字节手动翻转GPIO是效率最低的方式换成连续发送数据的HAL_SPI_Transmit或者直接用DMA效果立竿见影。我实测把分频系数从64改到8刷新时间明显缩短肉眼感知到的变化就很明显。减少不必要的数据量。如果只是局部区域变化就没必要整屏刷新设置窗口只刷新变化区域能省下一多半的SPI传输时间。最后是屏幕方向的优化。MADCTL的扫描方向会影响数据发送顺序合理设置扫描方向可以配合后续的区域刷新逻辑让画面局部更新时更流畅。比如从上到下刷和从左到右刷流畅感会有差异这个需要根据实际应用场景去调。6. 后续还能往哪走电子相册、摄像头预览和GUI扩展图片显示这个功能做通之后可以很自然地延伸出几个方向每个都是独立且实用的项目。电子相册是将图片数据放到SD卡里用FATFS文件系统读取BMP文件再显示到LCD上实现自由切换图片。相比烧录数组SD卡方案几乎不占Flash适合存放几十张图片体验上更像一个真正的产品。摄像头预览正好ZTMR板载OV2640摄像头接口摄像头输出的JPEG数据可以解码后显示到LCD上或者直接用DVP接口输出RGB565数据到屏幕。这个项目能帮你深入理解图像采集、同步信号和显示缓存的概念涉及的知识量比静态图片显示要大不少但一次打通之后对整个嵌入式图像处理链路都会有质的认知提升。再往上就是给屏幕加上简单GUI框架了绘制矩形、圆形、字符处理触控输入这些基础图形操作几乎全是基于像素坐标的绘制函数而绘制函数又建立在显示图片的底层机制之上。所以说LCD图片显示是嵌入式视觉类项目的“开门砖”一点都不夸张。我个人实操下来最大的体会是显示一张图片并不难真正提升能力的是遇到白屏、偏色、花屏时那份排查思路和耐心。拿着万用表测、逻辑分析仪看波形、可疑点一个个排除这个过程比看到图片正常显示更值钱。希望这篇记录能帮你把屏幕点亮更希望你通过这个过程多踩几个坑、多长几次记性那后面做复杂项目时会省下十倍的时间。
RELATED

相关推荐

VS Code 调试 Apollo Client 断点不命中解决方案

VS Code 调试 Apollo Client 断点不命中解决方案

简介:本资源是一份面向Apollo自动驾驶框架开发者与C调试初学者的VS Code断点调试实战指南,聚焦解决在复杂嵌入式系统项目中难以高效定位逻辑错误、理解多模块调用流程的核心痛点。压缩包共5个文件,含4个关键JSON配置文件(launch.j…

📅 2026/10/15 1:32:41
Python编程题161-170深度拆解:从基础语法到综合实战的进阶之路

Python编程题161-170深度拆解:从基础语法到综合实战的进阶之路

刷过不少练手题,能让我隔一段时间愿意回看的系列不多,Python小屋的编程题算一个。最近集中把161-170这十道题从头到尾做了一遍,一个明显的感受是:题量确实不大,但如果想全部一次通过、又快又稳,并不比做三十…

📅 2026/10/15 1:32:41
条件卷积如何提升实例分割精度与效率

条件卷积如何提升实例分割精度与效率

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/10/15 1:32:41
MORE NEWS

更多资讯

📰

M3U8 文件 BOM 头部标记,看不见字符带来解析失败

、看不见的 BOM 头部带来解析失败 很多新手遇到一类诡异故障:M3U8 内容肉眼看完全没问题,第一行写的就是 #EXTM3U,VLC 播放器播放一切正常,但是浏览器 hls.js 直接解析报错,播放黑屏。 根源就是 UTF‑8 BOM 标记。BO…

📰

随身WiFi突然断网限速?八成是卡被风控了:停机原因自查清单

前排提醒:本文只讨论随身WiFi与流量卡的使用排查研究,素材来自公开资料整理,不构成任何渠道推荐。最迷惑的一种故障:设备灯正常、信号满格、余额也在,就是上不了网。折腾半天设备,最后发现是卡被风控停机了…

📰

Java基础:为什么两个String明明一样,使用==比较却是false?深入理解==和equals()区别

在学习 Java 的过程中, 和 equals() 几乎是每个初学者都会遇到的一个问题。很多刚开始学习 Java 的同学都会记住一句话:String 比较不要使用 ,应该使用 equals()但是为什么?为什么下面这段代码中,两个 String 明明都是…

📰

随身WiFi一到晚高峰就卡?先看懂QCI优先级,别急着怪设备

前排提醒:本文只讨论随身WiFi设备与流量卡的技术原理与使用研究,数据来自公开资料整理,不涉及任何具体渠道推荐。一个高频吐槽:白天测速满血,一到晚高峰就缩水;换台设备、换个卡槽,还是卡。不少…

📰

aigc检测报告怎么看?2026年论文AI率免费检测和付费检测攻略,超标先降AI!

论文写完了,查重率也降下来了,学校又要求提交AIGC检测报告。搜索一下,免费查AI率的网站、知网检测入口、降AI率软件混在一起:到底应该用哪个? 是不是每修改一次论文,都要重新花钱查AI率? 可以…

📰

Flashlight Emoji 插件深度解析:从 `emoji grin` 到 `:rocket:` 的搜索与复制机制

桌面应用插件系统 【免费下载链接】Flashlight The missing Spotlight plugin system 项目地址: https://gitcode.com/gh_mirrors/fl/Flashlight 点击查看 免费下载 导读 本文围绕 Flashlight(macOS Spotlight 插件系统)官方插件目录中的 E…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬