ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

ESP8266/ESP32多屏驱动方案:共享总线加分时片选实现MAX7219点阵屏控制

2026/9/24 4:27:49 拓冰建站 浏览量
ESP8266/ESP32多屏驱动方案:共享总线加分时片选实现MAX7219点阵屏控制 1. 从一块屏到多块屏Tobers Multidisplay 到底解决了什么问题如果你玩过 ESP8266 或者 ESP32大概率经历过这样一个阶段板子连上 WiFi 了串口能打印数据了然后就想给它加一块显示屏让数据能直观地“跳出来”。我最早做环境监测小项目的时候用的就是一块 0.96 寸 OLED显示温湿度还行但一旦想同时看时间、IP 地址、传感器数值和状态提示屏幕就不够用了。换大屏吧成本上去了接线也麻烦用 TFT 彩屏吧刷新率和内存又吃紧ESP8266 那点 RAM 根本扛不住复杂界面。后来接触到 MAX7219 驱动的 LED 点阵模块一块 8x8 的模块几块钱四块级联就能拼成 8x32 的长条屏显示滚动文字非常合适。但问题又来了一块屏只能显示一行内容我想让时间、温度、状态各占一行怎么办最直接的想法就是挂多块 MAX7219 模块每块独立控制。可实际接线的时候你会发现ESP8266 的 GPIO 数量本来就紧张如果每块屏都单独占用三个引脚DIN、CLK、CS挂三块屏就要九个引脚再加上传感器和其他外设根本不够分。Tobers Multidisplay 这个项目核心思路就是解决“多块显示屏共用少量引脚”的问题。它通过硬件上共享时钟和数据线、软件上分时选通片选的方式让 ESP8266 或 ESP32 用三根线就能驱动多块独立的 MAX7219 点阵屏。你可以把它理解成一条“数据总线”所有屏幕都挂在上面同一时刻只有一块屏的片选被拉低接收属于自己的数据。这个方案的好处非常明显引脚占用从 3N 降到 3N 块屏的布线复杂度大幅下降代码层面也只需要维护一套 SPI 输出逻辑针对不同屏幕切换片选即可。这个项目适合谁呢如果你正在用 ESP8266 或 ESP32 做以下事情它会非常有用多参数环境监测站需要同时显示温度、湿度、气压和网络状态桌面时钟加消息提醒一块屏显示时间一块屏滚动显示天气或通知工业设备状态面板用多块点阵屏分别指示不同工位的运行状态。甚至你只是想用点阵屏拼一个自定义的图案墙这个多屏驱动框架也能直接拿来用。我实测下来ESP8266 驱动三块 8x8 点阵屏刷新率完全够用滚动文字没有明显卡顿。ESP32 就更不用说了性能绰绰有余还能同时跑 WiFi 和蓝牙。下面我就把这个项目的设计思路、硬件接线、代码实现和踩过的坑完整地拆一遍。2. 整体设计思路与方案选型为什么是共享总线加分时片选2.1 多屏驱动的三种常见方案对比在决定用共享总线加分时片选之前我其实试过另外两种方案各有各的问题。这里先做一个横向对比方便你根据自己项目的情况做选择。方案引脚占用硬件复杂度软件复杂度刷新率适用场景独立 SPI 接口3N高每块屏单独走线低每块屏独立初始化高屏幕数量少、引脚充裕软件模拟 SPI 分时复用3低共享数据时钟中需要手动切换片选中引脚紧张、屏幕数量 2-4 块硬件 SPI 加片选扩展3log2(N)中需要译码器中高需要译码逻辑高屏幕数量多、对刷新率要求高Tobers Multidisplay 采用的是第二种方案的变体数据线和时钟线共享片选线独立但通过软件分时控制。为什么不用硬件 SPI 加译码器因为对于大多数 DIY 项目来说2 到 4 块屏已经够用了加一个 74HC138 译码器虽然能进一步省引脚但增加了硬件成本和接线复杂度而且 ESP8266 的硬件 SPI 引脚是固定的灵活性反而受限。2.2 为什么 MAX7219 适合做多屏级联MAX7219 这颗芯片本身就是为了驱动多位 LED 数码管或点阵屏设计的它内部有 8x8 的显示缓存支持级联。级联的方式很简单第一块的 DOUT 接第二块的 DIN时钟和片选全部并联。这样理论上你可以级联任意多块数据从第一块依次往后传。但级联有一个限制所有屏幕显示的内容是连续排列的你没法单独控制某一块屏的刷新而不影响其他屏。比如我想让第一块屏显示静态图标第二块屏滚动文字级联模式下就比较难做因为每次刷新都要把整条链的数据全部重发一遍。Tobers Multidisplay 的做法是“物理上共享总线逻辑上独立寻址”每块屏的片选单独控制想刷新哪块就选通哪块其他屏保持不动。这样既省了引脚又保留了独立控制的灵活性。2.3 引脚分配与电气特性考量ESP8266 的可用 GPIO 本来就不多排除掉串口、Flash 占用的引脚真正能自由使用的也就那么几个。我一般这样分配DIN数据接 GPIO13硬件 SPI 的 MOSICLK时钟接 GPIO14硬件 SPI 的 SCKCS1、CS2、CS3分别接 GPIO15、GPIO4、GPIO5这样三块屏一共占用五个引脚其中两个是硬件 SPI 专用三个是普通 GPIO 做片选。如果你用 ESP32引脚就宽裕多了可以随便选但建议还是把 DIN 和 CLK 放在硬件 SPI 引脚上这样可以用 DMA 传输刷新率更高。电气特性方面需要注意一点MAX7219 的工作电压是 5V但它的逻辑电平输入在高电平时最低要求 3.5V。ESP8266 和 ESP32 的 GPIO 输出高电平是 3.3V直接连的话有些批次的 MAX7219 可能识别不稳定。我实测下来大部分模块能正常工作但如果你遇到数据错乱或者屏幕闪烁可以在 DIN、CLK、CS 线上各串一个 1N4148 二极管做电平抬升或者直接用一块 74HCT245 做电平转换。这个坑后面还会详细说。3. 核心细节解析与实操要点从接线到驱动的关键环节3.1 硬件接线共享总线怎么接才稳先明确一点共享的是 DIN 和 CLK不是 CS。所有 MAX7219 模块的 DIN 并联在一起接到 ESP8266 的 GPIO13所有 CLK 并联接到 GPIO14每块模块的 CS 单独接到一个 GPIO。VCC 和 GND 全部并联接 5V 电源。这里有一个非常容易踩的坑MAX7219 模块的 CS 引脚在模块上通常标的是 “CS” 或 “LOAD”有些模块标的是 “SS”。不管标什么功能是一样的低电平有效数据在时钟上升沿移入CS 上升沿锁存。接线的时候一定要确认每块模块的 CS 是独立控制的不能并联。还有一个细节DIN 和 CLK 的走线长度。如果屏幕数量多、走线长信号反射会导致数据错乱。我建议 DIN 和 CLK 的线尽量短如果必须拉长可以在末端加一个 100 欧姆的终端电阻到地。实测在 30 厘米以内不加电阻也没问题超过 50 厘米就建议加上。电源方面MAX7219 每块模块全亮的时候电流大概在 100 到 150 毫安三块屏就是 300 到 450 毫安。ESP8266 的 3.3V 输出肯定不够用必须外接 5V 电源。我一般用一块 LM2596 降压模块从 12V 降到 5V或者直接用 5V 2A 的电源适配器。注意 ESP8266 的 VCC 也要接 5V 到它的 VIN 引脚板载稳压器会降到 3.3V 给芯片用。3.2 软件架构如何实现分时选通软件层面的核心逻辑其实不复杂但有几个关键点必须处理好。我用 Arduino IDE 开发基于 LedControl 库做了改造。LedControl 库本身支持多块 MAX7219但它的多设备支持是通过多个 CS 引脚实现的底层还是分时选通。我把它精简了一下去掉了不必要的东西让代码更轻量。核心函数就三个initDisplay、setLed和refreshDisplay。initDisplay负责初始化每块屏包括设置扫描范围、亮度、关闭测试模式。setLed把要显示的数据写入内存缓冲区不直接操作硬件。refreshDisplay根据当前需要刷新的屏幕编号拉低对应的 CS把缓冲区数据发出去再拉高 CS。这里有一个性能优化的点不要每次刷新都重新初始化 MAX7219。初始化只需要在开机时做一次之后只发显示数据就行。我见过有人在 loop 里反复调用shutdown和init结果屏幕闪烁严重刷新率也上不去。另一个关键点是缓冲区管理。每块屏需要 8 个字节的缓冲区对应 8 行。三块屏就是 24 字节对 ESP8266 来说完全没压力。但如果你要做滚动效果缓冲区就要多留一些空间因为滚动需要预计算下一帧的数据。我的做法是每块屏分配 16 字节前 8 字节是当前显示后 8 字节是下一帧滚动的时候直接切换指针避免频繁的内存拷贝。3.3 刷新率与视觉暂留的平衡多屏分时刷新的一个潜在问题是刷新率下降。假设单块屏刷新一次需要 1 毫秒三块屏轮流刷一遍就是 3 毫秒刷新率约 333Hz远超人眼视觉暂留的 24Hz所以看起来是完全连续的。但如果你在每块屏之间加了延时或者 SPI 时钟设得太低刷新率就会掉下来。我实测的数据ESP8266 在 80MHz 主频下SPI 时钟设为 4MHz单块屏发送 8 字节数据大约需要 20 微秒加上片选切换和函数调用开销单块屏刷新一次约 50 微秒。三块屏轮流刷一遍 150 微秒刷新率约 6.6kHz完全无压力。即使你把 SPI 时钟降到 1MHz刷新率也有 1.6kHz肉眼绝对看不出闪烁。但如果你用软件模拟 SPI情况就不一样了。软件 SPI 每翻转一次引脚都要消耗 CPU 周期ESP8266 在 80MHz 下软件 SPI 时钟大概只能跑到 1MHz 左右而且占用大量 CPU 时间。所以我的建议是能用硬件 SPI 就用硬件 SPIESP8266 的 GPIO13 和 GPIO14 就是硬件 SPI 引脚直接调用SPI.transfer就行。4. 实操过程与核心环节实现从零搭建三屏显示系统4.1 材料准备与接线清单先列一下我这次实操用到的材料ESP8266 NodeMCU 开发板一块或者 ESP32 DevKit 也行MAX7219 8x8 点阵模块三块杜邦线若干建议用母对母和公对母搭配5V 2A 电源适配器一个面包板一块可选直接焊接更稳接线步骤如下三块 MAX7219 模块的 VCC 全部并联接 5V 电源正极三块模块的 GND 全部并联接 5V 电源负极同时和 ESP8266 的 GND 共地三块模块的 DIN 全部并联接 ESP8266 的 GPIO13D7三块模块的 CLK 全部并联接 ESP8266 的 GPIO14D5第一块模块的 CS 接 GPIO15D8第二块接 GPIO4D2第三块接 GPIO5D1ESP8266 的 VIN 接 5V 电源正极GND 接电源负极注意ESP8266 的 GPIO15 在启动时必须为低电平否则会进入错误的启动模式。如果你把 CS 接在 GPIO15 上确保 MAX7219 的 CS 在上电时不会被拉高。MAX7219 的 CS 内部有下拉吗没有。所以最好在 GPIO15 和 CS 之间加一个 10k 欧姆的下拉电阻到地保证启动时 CS 为低。或者干脆换一个引脚比如 GPIO16 或者 GPIO12。我一开始就是踩了这个坑板子一直启动不了查了半天才发现是 GPIO15 的问题。4.2 代码实现精简版多屏驱动下面是我实际使用的代码框架基于 Arduino IDE 和 ESP8266 核心库。代码不长但每一行都有用。#include SPI.h // 定义片选引脚 const uint8_t CS_PINS[] {15, 4, 5}; const uint8_t NUM_DISPLAYS 3; // 每块屏的显示缓冲区8行每行8位 uint8_t displayBuffer[NUM_DISPLAYS][8]; // MAX7219 寄存器地址 const uint8_t REG_DECODE 0x09; const uint8_t REG_INTENSITY 0x0A; const uint8_t REG_SCANLIMIT 0x0B; const uint8_t REG_SHUTDOWN 0x0C; const uint8_t REG_DISPTEST 0x0F; void writeRegister(uint8_t displayIndex, uint8_t reg, uint8_t data) { digitalWrite(CS_PINS[displayIndex], LOW); SPI.transfer(reg); SPI.transfer(data); digitalWrite(CS_PINS[displayIndex], HIGH); } void initDisplay(uint8_t displayIndex) { writeRegister(displayIndex, REG_SHUTDOWN, 0x00); // 关闭显示 writeRegister(displayIndex, REG_DISPTEST, 0x00); // 退出测试模式 writeRegister(displayIndex, REG_DECODE, 0x00); // 不译码直接操作点阵 writeRegister(displayIndex, REG_SCANLIMIT, 0x07); // 扫描全部8行 writeRegister(displayIndex, REG_INTENSITY, 0x08); // 亮度中等 writeRegister(displayIndex, REG_SHUTDOWN, 0x01); // 开启显示 } void refreshDisplay(uint8_t displayIndex) { for (uint8_t row 0; row 8; row) { writeRegister(displayIndex, row 1, displayBuffer[displayIndex][row]); } } void setup() { SPI.begin(); SPI.setFrequency(4000000); // 4MHz for (uint8_t i 0; i NUM_DISPLAYS; i) { pinMode(CS_PINS[i], OUTPUT); digitalWrite(CS_PINS[i], HIGH); initDisplay(i); } // 初始化缓冲区为全灭 memset(displayBuffer, 0, sizeof(displayBuffer)); } void loop() { // 示例第一块屏显示一个笑脸第二块屏显示心形第三块屏滚动文字 // 这里只演示刷新逻辑 for (uint8_t i 0; i NUM_DISPLAYS; i) { refreshDisplay(i); } delay(10); }这段代码的核心就是writeRegister函数它负责选通指定的屏幕发送寄存器地址和数据然后锁存。initDisplay在开机时调用一次refreshDisplay在需要更新显示时调用。注意SPI.setFrequency(4000000)这一行4MHz 是 MAX7219 的推荐上限再高可能会不稳定。4.3 显示内容设计三块屏各司其职有了驱动框架接下来就是设计每块屏显示什么。我这次做的示例是一个环境监测面板第一块屏显示一个静态的温度计图标表示当前显示的是温度数据第二块屏显示温度数值用点阵字模拼出来比如 “23.5”第三块屏滚动显示网络状态和 IP 地址点阵字模的生成可以用在线工具比如 “LED Matrix Editor” 或者 “Dot Matrix Font Generator”。生成的字模是 8x8 的十六进制数组直接填入displayBuffer就行。滚动效果的做法是把要显示的字符串转换成点阵序列每次刷新时整体左移一位从右侧补入新的列数据。这个逻辑不复杂但要注意缓冲区边界别越界访问。我实测下来三块屏同时刷新ESP8266 的 CPU 占用率不到 5%剩余性能完全够跑 WiFi 和传感器读取。如果你用 ESP32还可以把刷新逻辑放在单独的任务里用 FreeRTOS 做多线程主循环只负责更新数据显示刷新完全独立。5. 常见问题与排查技巧实录那些年我踩过的坑5.1 屏幕不亮或只亮第一块这是最常见的问题原因通常有三个片选引脚接错了、初始化顺序不对、电源功率不够。排查步骤很简单先用万用表量一下每块屏的 VCC 和 GND 之间是不是 5V如果电压明显偏低说明电源功率不够换一个 2A 以上的电源。然后检查 CS 引脚用万用表量一下在初始化的时候 CS 有没有被拉低。如果硬件没问题那就是代码里初始化顺序的问题确保每块屏都调用了initDisplay而且initDisplay里先发SHUTDOWN再发其他寄存器。我遇到过一次第一块屏正常第二块和第三块完全不亮。查了半天发现是第二块和第三块的 DIN 没有并联到总线上而是各自悬空了。共享总线的时候DIN 和 CLK 必须全部并联少接一根都不行。5.2 显示内容错乱或闪烁显示错乱通常是 SPI 时钟太快或者接线太长导致的。先把SPI.setFrequency降到 1MHz 试试如果问题消失说明是信号完整性问题。可以在 DIN 和 CLK 线上串一个 100 欧姆的电阻或者在末端加一个 100 欧姆的终端电阻到地。另外确保 DIN 和 CLK 的走线尽量短不要和电源线捆在一起避免耦合干扰。闪烁的另一个可能原因是刷新率太低。如果你在loop里加了delay(100)之类的延时刷新率就掉到 10Hz 了肉眼会明显感觉到闪烁。把延时去掉或者用millis()做非阻塞定时保证刷新率在 100Hz 以上。5.3 ESP8266 启动失败或反复重启这个问题我在 4.1 节提到过GPIO15 在启动时必须为低电平。如果你把 CS 接在 GPIO15 上而 MAX7219 的 CS 在上电瞬间被拉高ESP8266 就会进入错误的启动模式表现为串口一直打印乱码或者反复重启。解决办法有两个换一个引脚或者在 GPIO15 和 CS 之间加一个 10k 欧姆的下拉电阻。我后来直接把 CS 换到了 GPIO16问题就消失了。还有一个可能的原因是电源电流不够。ESP8266 在启动瞬间电流会冲到 300 毫安以上如果电源响应速度慢电压会瞬间跌落导致芯片复位。在电源输出端并一个 470 微法的电解电容能有效缓解这个问题。5.4 常见问题速查表现象可能原因排查方法解决方案屏幕完全不亮电源未接通万用表量 VCC 和 GND检查电源线和电源功率只有第一块屏亮DIN 或 CLK 未并联检查总线接线确保所有模块 DIN 和 CLK 并联显示内容错乱SPI 时钟太快降低 SPI 频率设为 1MHz 测试屏幕闪烁刷新率太低检查 loop 中的延时去掉延时用非阻塞定时ESP8266 反复重启GPIO15 启动电平错误检查 GPIO15 上电电平换引脚或加下拉电阻亮度不均匀电源压降量每块屏的 VCC 电压加粗电源线或就近供电5.5 独家避坑技巧第一个技巧在每块 MAX7219 模块的 VCC 和 GND 之间并一个 100 微法的电容越靠近模块越好。这个电容能吸收电源纹波防止屏幕在刷新时出现随机亮点。我一开始没加三块屏同时刷新的时候偶尔会闪一下加了电容之后就完全稳定了。第二个技巧如果你用 ESP32可以把 DIN 和 CLK 映射到任意 GPIO通过SPI.begin(sck, miso, mosi, ss)指定引脚。这样布线更灵活不用受硬件 SPI 固定引脚的限制。但注意ESP32 的硬件 SPI 只有两组映射的时候要选对。第三个技巧做滚动文字的时候不要每次刷新都重新计算整个字符串的点阵数据。把字符串预先转换成点阵数组滚动的时候只移动指针这样 CPU 占用率能降低一个数量级。我实测过预计算之后ESP8266 刷新三块屏的 CPU 占用从 15% 降到了 3% 以下。6. 扩展玩法与性能优化让多屏系统更实用6.1 用 ESP32 的双核优势做异步刷新ESP32 有两个核心你可以把显示刷新放在一个核心上传感器读取和网络通信放在另一个核心上。Arduino IDE 里可以用xTaskCreatePinnedToCore创建任务把刷新任务固定到核心 0主循环跑在核心 1。这样即使网络通信偶尔阻塞显示也不会卡顿。我实测下来双核方案下即使 WiFi 正在传输大量数据点阵屏的滚动依然丝滑。6.2 多屏内容联动与动画效果三块屏可以独立控制也可以联动做动画。比如做一个“进度条”效果第一块屏显示进度条的前 8 列第二块屏显示中间 8 列第三块屏显示最后 8 列三块屏拼起来就是一个 24 列的长进度条。实现方法是把 24 列的数据拆成三段分别写入三块屏的缓冲区然后同时刷新。这个效果在展示下载进度或者传感器数值变化趋势的时候非常直观。6.3 功耗优化与亮度自动调节如果你做的是电池供电的项目功耗就需要考虑。MAX7219 在显示全亮的时候电流不小可以通过降低亮度来省电。REG_INTENSITY寄存器可以设置 0 到 15 的亮度等级我一般设为 4 到 8 之间白天看得清晚上不刺眼。另外可以在无人观看的时候自动关闭显示用 PIR 传感器检测有人靠近再点亮。ESP8266 的深度睡眠模式配合定时唤醒可以把整体功耗降到毫安级别。6.4 从三块屏扩展到更多屏如果你需要驱动超过四块屏GPIO 就不够用了。这时候可以用 74HC138 译码器用三个 GPIO 控制八个片选理论上可以驱动八块屏。再往上可以用 74HC595 移位寄存器扩展片选级联多片 595 就能控制任意多块屏。但说实话对于大多数 DIY 项目四块屏已经足够展示信息了再多的话布线复杂度和调试难度都会大幅上升。我个人在实际操作中的体会是Tobers Multidisplay 这个方案最大的价值不是省了那几个引脚而是提供了一种“模块化”的思维每块屏是独立的显示单元你可以按需增减代码框架不用大改。这种灵活性在项目迭代的时候特别有用今天加一块屏显示新数据明天减一块屏做小体积版本都只是改一下NUM_DISPLAYS和引脚数组的事。最后再分享一个小技巧把片选引脚定义成数组初始化的时候用for循环遍历这样增减屏幕只需要改数组长度代码其他部分完全不用动。