ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

ESP8266 MicroPython ST7735 TFT屏硬件SPI驱动与刷新提速指南

ESP8266 MicroPython ST7735 TFT屏硬件SPI驱动与刷新提速指南 简介面向嵌入式开发者的ESP8266与MicroPython联合开发资源包围绕ST7735驱动芯片的TFT屏幕展开核心目标是解决模拟SPI方式刷新过慢、直接切换硬件SPI后速度依旧不理想的问题。作者从画点、铺色、显示字符串等基础绘图函数入手逐项重写优化底层数据写入与传输方式让屏幕刷新速度获得明显提升。资源中特别提醒MicroPython环境下ESP8266设有两个硬件SPI其中一个被Flash占用无法供用户使用因此实际可用的硬件SPI只有一个若改用软件SPI则需注意spi.write函数必须传入bytes类型且SPI相位和极性要依据芯片数据手册时序图正确配置。压缩包为zip格式大小仅4KB共3个py文件涵盖LCD屏幕驱动、功能加载与主程序入口代码精简、结构清晰便于移植到其他工程。目前已有535人浏览学习适合正在调试TFT显示、希望提高屏幕刷新效率的嵌入式开发爱好者参考。 如果你在ESP8266上用MicroPython做过TFT屏幕显示大概率经历过这样一个场景屏幕能亮但全屏刷个图就像在慢放一行一行往下爬肉眼能清楚看到扫描过程。稍微复杂一点的界面刷一次等得人心烦。这个问题多半不是代码逻辑有毛病而是数据传输通路选错了。把单片机引脚当成人肉SPI时钟用Python一句一句去翻转GPIO速度当然上不去。想让ESP8266配合ST7735这类彩屏跑出比较体面的刷新效果正确做法是走硬件SPI——让芯片内部的移位寄存器帮你去发时钟和数据CPU只需要把数据塞给外设就行速度完全不在一个量级。这篇内容我按自己实际调这块屏幕的经验来写从硬件SPI和软件SPI的差异讲起把接线、固件准备、驱动文件、初始化参数和各类坑一次说完。适合刚入手ESP8266开发、准备给项目配一块小巧TFT屏的朋友参考也适合已经在用软件SPI刷屏、觉得卡顿想换硬件SPI提速的开发者。1. 为什么非要用硬件SPI软件GPIO刷屏是真的难受1.1 从一次全屏刷新说起先算一笔账。ST7735常见的分辨率是128x160颜色深度一般是16bit也就是每个像素占用2字节。一屏完整数据量是128x160x2 40960字节约40KB。如果是软件SPI每发送一个字节流程是拉低时钟、在MOSI上设置电平、拉高时钟、再重复8次。在MicroPython的解释环境下每次GPIO翻转差不多要1~3us加上循环判断、函数调用开销发一个字节的耗时能走到30~50us甚至更慢。按50us算40KB就是40960x50us约2秒。就算优化一点也差不多要1秒以上。这个速度意味着你写个简单的动画刷新一次至少一秒界面必然肉眼可见地卡顿。硬件SPI就不一样了。时钟信号由外设电路产生MicroPython只需要把数据块交给SPI外设它会自动按设定的波特率同步输出。以20MHz为例理论上发完40KB只需要16.4ms是软件方案一个量级以上的提升。实际项目里因为还要发命令、切换DC电平、处理绘图开销全屏刷新在几十毫秒级别是能达到的。我的板子上实测20MHz硬件SPI下执行一次整屏填充大约在40~80ms跟软件的“秒级”完全是两种体验。1.2 ESP8266硬件SPI的引脚和一些认知误区ESP8266在MicroPython环境下的用户可操作硬件SPI只有一个就是machine.SPI(1)引脚是固定的三根SCK在GPIO14、MOSI在GPIO13、MISO在GPIO12。和某些单片机不一样ESP8266的硬件SPI引脚不能随意映射想在代码里写“SCKGPIO5”这种操作在硬件SPI下行不通。这里有个容易误会的点很多人一听“硬件SPI”怕引脚太死板下意识还是去用软件SPI。实际上ST7735这块屏本身只需要SPI的写方向读取操作基本用不上所以MISO那个脚不接任何设备也无所谓。但MicroPython构造硬件SPI对象时有些固件版本还是要求你指定miso引脚那就把GPIO12填进去让这个脚悬空或做普通IO处理即可不影响写屏幕。还有一个误区是觉得硬件SPI一定比软件SPI难配置。梳理清楚了就知道两者在MicroPython里的API差别不大硬件SPI只是额外限制了一组固定引脚换来的是几十倍的性能提升。用屏幕这种数据吞吐量大的设备选硬件SPI是必须的而不是可选项。2. ST7735模块接线与供电细节2.1 引脚对照与选脚避坑市面上的ST7735模块虽然卖家不同但接口基本是统一的常见引脚有VCC、GND、CS、RESET、A0有些标DC、SDAMOSI、SCK、LED背光。对照ESP8266接线可以这样规划ST7735模块引脚ESP8266引脚说明VCC3.3V模块供电不要接5VGNDGND共地SCKGPIO14硬件SPI时钟SDAGPIO13硬件SPI MOSICSGPIO5片选普通GPIO即可A0/DCGPIO4数据/命令选择普通GPIORESETGPIO16复位普通GPIO即可LED3.3V背光可常亮或GPIO控制选GPIO的时候有讲究。ESP8266启动时会检查几个特殊引脚的状态GPIO0、GPIO2上电必须为高GPIO15上电必须为低否则会进入错误的启动模式。如果把这些特殊引脚用来接外部模块并且模块在上电瞬间把电平拉反就有可能导致板子启动异常。稳妥起见CS、DC、RST这类信号引脚尽量挑GPIO4、GPIO5、GPIO16这种没有额外启动约束的脚位。我自己喜欢用GPIO5做CS、GPIO4做DC、GPIO16做RST跑了好几个项目都很稳定。MISO那根线再次强调SPI构造时需要指定GPIO12但硬件上可以不连。如果你买的模块引出了MISO留着悬空或者接到GPIO12都行代码里只写屏不读屏完全无所谓。2.2 供电与干扰问题ST7735模块上的VCC丝印虽然是5V兼容的样子但很多小模块板载稳压和电平转换并不完善习惯上还是按3.3V供电最稳。背光LED如果直接接3.3V模块自带限流电阻常亮没问题如果想让背光可以软件控制就接一个GPIO但要注意GPIO驱动能力有限最好通过一个三极管或MOS管去控制背光的开关。供电这块经常有人忽略却最容易出花屏和闪屏的毛病。ESP8266在开启WiFi后电流尖峰很猛瞬间可能冲到几百毫安如果和TFT屏共用一颗小功率LDO很容易在WiFi收发瞬间把3.3V电压拉低屏幕就会随机花屏甚至重启。我的习惯是在电源输入端并联一颗100uF到470uF的电解电容再在屏的VCC脚附近加一颗0.1uF陶瓷电容。别小看这两颗电容能把很多“莫名其妙花屏”的问题挡在发生之前。3. 烧录固件并准备驱动文件3.1 ESP8266的MicroPython固件烧录如果板子还是出厂自带的AT固件第一步是烧录MicroPython。去MicroPython官网下载ESP8266对应的固件bin文件注意选择匹配你板子Flash容量的版本常规NodeMCU一般是4MB。烧录工具用esptool先完整擦除Flash再写入固件esptool.py --port COM3 erase_flash esptool.py --port COM3 --baud 460800 write_flash -fm dio -fs 4MB 0x0 esp8266-20220618-v1.19.1.binWindows的串口是COMxLinux下一般是/dev/ttyUSB0改成自己实际看到的端口即可。先擦除再写入这个顺序别跳如果旧固件残留MicroPython启动后文件系统可能挂载失败出现莫名其妙的重启循环。烧录完可以用PuTTY或Thonny连接REPL看到提示符说明固件已经正常跑起来了。3.2 上传ST7735驱动和main.pyMicroPython本身没有内置ST7735驱动需要把驱动文件放到设备上。社区里常见的做法是下载一份现成的st7735.py放到ESP8266的文件系统然后在main.py里import使用。上传工具有很多最省事的是Thonny图形界面里直接右键上传文件对新手非常友好。也可以用ampy走命令行ampy --port COM3 put st7735.py ampy --port COM3 put main.py拿到驱动文件后别急着运行先打开源码扫一眼构造函数。ST7735驱动的开源版本很多接口细节有差异有的版本初始化时直接在驱动内部创建SPI对象有的版本要求你从外面传一个SPI实例进来。标题这里强调“使用硬件SPI”所以我们要用的是后一种方式外部创建machine.SPI(1)再把SPI对象传给显示设备类。如果下载的驱动是内部创建SPI的写法需要微调一下把驱动里创建SPI那段代码替换为接收外部传入的SPI参数。很多人在这一步卡住其实把源码打开对比几行就看明白了。4. 核心代码与参数调优4.1 初始化硬件SPI和显示屏下面这段是典型的外部传入SPI实例的写法细节可能因驱动版本略有不同但思路一致from machine import Pin, SPI import st7735 spi SPI(1, baudrate20000000, polarity0, phase0, bits8, firstbitSPI.MSB, sckPin(14), mosiPin(13), misoPin(12)) cs Pin(5, Pin.OUT) dc Pin(4, Pin.OUT) rst Pin(16, Pin.OUT) cs.value(1) tft st7735.ST7735(spi, cs, dc, rst, rotation0, offset(0, 0), bgrTrue, invertTrue) tft.init() tft.fill(0xFFFF)baudrate20000000就是20MHz。先别一上来就追求极限这个速率能保证大部分模块稳定工作。等整屏显示正常了再试着改到40MHz如果出现花屏、条纹就降回去。网上有一种说法是ST7735数据手册里写的最大写周期约15MHz20MHz已经算超频实际很多模块配合杜邦线短距离连接也能跑但不要因此忽略线材质量。杜邦线太长、接触不良都会影响高速SPI的波形。代码中polarity0, phase0对应SPI Mode 0ST7735一般工作在Mode 0。firstbitSPI.MSB表示高位先出这是SPI设备的常规要求。如果你的固件不支持firstbit这个参数去掉即可不影响默认行为。cs、dc、rst三个引脚初始化成输出后记得把CS先拉高避免初始化前SPI总线上有其他干扰信号被屏幕误解析成命令。rst在init()内部会自己拉低再拉高完成复位外部不需要额外处理。4.2 几个最影响显示效果的参数驱动初始化里最折磨人的三个参数是rotation、bgr、invert。这三者配置不对屏幕能亮但显示效果千奇百怪。rotation控制旋转方向取值范围通常是0到3对应0度、90度、180度、270度。改了旋转方向后如果屏幕显示区域偏移了还需要配合offset参数调整起点位置。很多模块不是标准的128x160全屏可视区域比如有些80x160或128x128的屏安装方向不同坐标偏移也不同。出现显示内容不在屏幕中心的时候就是offset该派上用场的时候。bgr控制RGB还是BGR颜色顺序。这个参数错了最典型的表现是红色和蓝色互换。比如你画一个红色矩形屏幕显示出来是蓝色。遇到这种状况把bgr从True改成False或者反过来问题立刻解决。invert是颜色反相。有的ST7735面板需要开启反色否则显示效果像相片底片一样整个世界都是反色的。这个参数没有绝对标准跟具体面板的初始化序列有关最好的方法就是写一个纯白填充和纯红填充的测试程序多试几次True/False选显示正常的那组。最后还有一个隐性参数是MADCTL也就是ST7735寄存器0x36控制扫描方向、RGB/BGR顺序和行列交换。rotation和bgr从根本上说都是在帮你设置这个寄存器。有些驱动把这两个参数封装好了而有的驱动需要你直接改源码里的MADCTL值原理明白后看到相关代码就不会懵。4.3 刷新性能优化思路显示大面积图形时绘制和刷屏要配合好。ST7735控制芯片内部自带GRAM我们可以一次性把所有像素数据推给屏幕让控制器自己管理刷新。问题在ESP8266的内存很紧张128x160x2的完整帧缓冲需要40KB而MicroPython环境能稳定使用的大块连续内存没那么多直接分配很容易MemoryError。实际项目里我一般用“行缓冲分块刷新”的方式不需要一整个40KB的buffer先绘制一小块区域到内存然后立刻通过SPI刷出去再绘制下一块。比如用bytearray(128*2)先准备一行数据绘制完一行就推一行。这样做内存占用小效果上也几乎看不出差别。如果是显示静态界面可以算好一个区域用fill_rect配合局部刷新比整屏重绘高效得多。如果确实需要整屏缓冲那么在程序启动早期、文件系统加载完但还没开启WiFi时分配成功率会高一些。分配之前先执行gc.collect()整理堆内存减少碎片。分配完就不要再频繁创建大对象否则后期内存碎片会越来越严重。5. 踩坑实录与排查清单5.1 常见问题速查表把这段时间用ST7735碰到的典型问题整理成了一张表排查的时候对照着看能省很多时间现象大概率原因解决办法全屏白屏复位时序不对、SPI速率太高、供电不足检查RST引脚接线降低baudrate电源并电容全屏花屏、横条纹电源干扰或SPI速度过高加强滤波电容逐步降低SPI速率测试颜色红蓝互换bgr参数不对切换bgr的True/False显示像底片反色invert参数不对切换invert的True/False内容偏移/显示不全offset或rotation不对修改offset坐标测试不同rotation背光不亮但屏幕有显示背光引脚没接对LED引脚接3.3V或确认GPIO控制电平上电后板子反复重启Flash固件没擦干净重新完整erase_flash再烧录刷新时WiFi偶尔掉线SPI中断与WiFi争抢资源降低SPI速率减少整屏刷新频率5.2 几个容易被忽略的坑第一个坑是RST复位等待时间。有些驱动初始化序列里Sleep Out之后只等待几十毫秒个别ST7735面板需要更长时间才能稳定。我的做法是在init()里的0x11命令之后主动加一个time.sleep_ms(200)宁可多等一下也不要让屏幕带着半睡状态就开始收数据能明显减少白屏概率。第二个坑是GPIO初始状态。CS、DC、RST在初始化SPI之前就应该明确设置成输出并给到正确电平。尤其CS如果不小心悬空或默认拉低上电瞬间芯片可能把GPIO上的噪声当成片选信号导致屏幕吃进一堆垃圾命令。养成习惯cs Pin(5, Pin.OUT); cs.value(1)初始化屏幕之前CS保持高需要通信时才拉低。第三个坑是SPI速率并非越高越好。我测试过40MHz下刷新速度确实更快但MicroPython在推大块数据时会长时间占用CPU和SPI外设此时如果WiFi正在收发数据两边可能互相拖累表现为屏幕撕帧、WiFi偶发断连。很多场景下20MHz的稳定性比40MHz的极限速度更重要。如果你做的是传感器数据展示这种低频刷新场景20MHz完全够用。第四个坑是绘图坐标和屏幕物理方向的组合。常见的小屏幕安装方向五花八门有的屏接口在左边有的在右边有的排线朝下。rotation参数调好后务必用英文和中文混合文本、矩形框、实心圆各画一遍确认上下左右没有镜像或偏移再写正式界面代码。一次调好后面省心很多。我自己在实际项目中还习惯把显示刷新和业务逻辑分离WiFi联网、传感器采集走一个流程屏幕刷新走另一个流程用标志位控制避免两者在同一时间片里抢CPU。这样即使SPI偶发耗时较长也不会导致整个系统卡死。这个思路在ESP8266这种资源受限的板子上尤其重要屏幕只是显示终端不能让它拖垮主逻辑。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进