资讯动态

ESP32-HUB75-MatrixPanel-DMA SD 卡动态 GIF 示例:为 HUB75 点阵屏接入 SD 卡文件系统,按文件播放动画 GIF

发布时间:2026/10/14 17:49:40 来源:尧图企业网站定制
嵌入式硬件开发驱动开发物联网【免费下载链接】ESP32-HUB75-MatrixPanel-DMAAn Adafruit GFX Compatible Library for the ESP32/S2/S3/P4 to drive HUB75 LED matrix panels using DMA for high refresh rates. Supports panel chaining.项目地址https://gitcode.com/gh_mirrors/es/ESP32-HUB75-MatrixPanel-DMA点击查看免费下载本文围绕仓库中的examples/AnimatedGIFPanel_SD示例展开讲解如何在 ESP32 HUB75 点阵屏系统中通过 SPI 接口挂载 SD 卡将 GIF 文件存放在 FAT32 文件系统里由库的 DMA 显示引擎逐帧解码并播放。读完后你将掌握SD 卡与 HUB75 引脚冲突时的引脚重映射方法、基于 Larry Bank 的AnimatedGIF解码器的四个文件回调open/read/seek/draw如何对接 ESP32 的File流、以及 GIF 列表扫描与限时长播放循环的完整实现。示例定位与依赖该示例位于 examples/AnimatedGIFPanel_SD/AnimatedGIFPanel_SD.ino其头部注释说明了它做的事情使用 Larry Bank 的AnimatedGIF库解码 GIF文件通过 Espressif 官方 ArduinoSD库从 SPI 接口的 SD 卡读取。相比同仓库的 LittleFS 版本GIF 写入 FlashSD 卡版本的优势是文件可随时增减、无需重新烧录代价是必须重新分配部分 HUB75 信号引脚。示例包含 4 个文件各自职责清晰文件职责AnimatedGIFPanel_SD.ino引脚定义、SD 卡初始化、HUB75 面板初始化、GIF 列表扫描、播放主循环gif_functions.hppAnimatedGIF库所需的文件打开/读取/寻址/绘制回调直接调用dma_display-drawPixel()sdcard_functions.hpp通用 SD 卡调试工具目录列表、读文件、IO 速度测试gifs/仓库自带的 6 个 64x32 分辨率示例 GIFcartoon、pacmn、matrix-spin 等硬件接线SD 卡 SPI 引脚对照GIF 文件通过 SD 卡 SPI 接口访问.ino 中给出了完整的引脚对照表AnimatedGIFPanel_SD.ino#L27-L40/* * Connect the SD card to the following pins: * * SD Card | ESP32 * D2 - * D3 SS * CMD MOSI * VSS GND * VDD 3.3V * CLK SCK * VSS GND * D0 MISO * D1 - */需要注意的接线细节SS_PIN定义为 GPIO 5#define SS_PIN 5MOSI/MISO/SCK 使用 ESP32 的默认 SPI 引脚23/19/18因此 .ino 中把这三个引脚的宏注释掉了直接交给SD.begin(SS_PIN)使用默认 SPI 总线。SD 卡的VDD必须接 3.3VD1/D2两根数据脚空闲不用。为什么必须重映射 HUB75 引脚这是本示例最关键的技术点。库为 ESP32 平台定义了一套默认 HUB75 引脚见 src/platforms/esp32/esp32-default-pins.hpp#define A_PIN_DEFAULT 23 // - 与 SD 卡的 MOSI 冲突 #define B_PIN_DEFAULT 19 // - 与 SD 卡的 MISO 冲突 #define C_PIN_DEFAULT 5 // - 与 SD 卡的 SS 冲突由于SD库固定占用 GPIO 23MOSI、19MISO、18SCK以及 SS 使用的 GPIO 5与默认的 A/B/C 三个地址选择线完全重叠.ino 中的注释明确写道Default pin mapping used by this library is NOT compatable with the use of the ESP32-Arduino SD card library (there is overlap). As such, some of the pins used for the HUB75 panel need to be shifted.因此示例把 A/B/C 三线重映射到空闲引脚AnimatedGIFPanel_SD.ino#L49-L68/**** HUB75 GPIO mapping ****/ // GPIO 34 are on the ESP32 are input only!! #define A_PIN 33 // remap esp32 library default from 23 to 33 #define B_PIN 32 // remap esp32 library default from 19 to 32 #define C_PIN 22 // remap esp32 library default from 5 to 22注释中同时提醒ESP32 的 GPIO 34 及以上34/35/36/39是纯输入引脚不能选作输出。R1/G1/B1/R2/G2/B2、LAT、OE、CLK 共 9 路信号保持库默认值25/26/27/14/12/13/4/15/16不变E_PIN默认 -1注释指出如果使用 64x64 面板必须改成一个有效引脚。这些宏最终通过HUB75_I2S_CFG结构体的gpio成员生效。该结构体定义在 src/ESP32-HUB75-MatrixPanel-I2S-DMA.h#L262-L355其构造函数的默认参数正是取自上述平台默认引脚struct HUB75_I2S_CFG { uint16_t mx_width; uint16_t mx_height; uint16_t chain_length; struct i2s_pins { int8_t r1, g1, b1, r2, g2, b2, a, b, c, d, e, lat, oe, clk; } gpio; shift_driver driver; // SHIFTREG / FM6126A / MBI5124 等 line_driver line_decoder; // TYPE138 / TYPE595 等 bool double_buff; clk_speed i2sspeed; // 默认 HZ_8M // ... 完整定义见 src/ESP32-HUB75-MatrixPanel-I2S-DMA.h };所以示例中只需覆盖冲突的三个引脚其余引脚自动沿用库默认值mxconfig.gpio.a A_PIN; // 33 mxconfig.gpio.b B_PIN; // 32 mxconfig.gpio.c C_PIN; // 22SD 卡准备工作原文档使用步骤示例的 Readme.md 给出的三步操作必须完整执行Format a SD Card with FAT32 file system (default setting)—— 将 SD 卡格式化为 FAT32 文件系统默认参数。注意必须是 FAT32SD 卡初始化代码依赖标准 FAT 卷信息。Create a directory called gifs—— 在卡根目录创建名为gifs的目录代码中打开路径为SD.open(/gifs)且注释强调大小写敏感case sensitive。Drop your gifs in there. The resolution of the GIFS must match that of the display.—— 将 GIF 文件放入该目录且GIF 分辨率必须与显示屏一致。本示例面板配置为PANEL_RES_X 64 / PANEL_RES_Y 32 / PANEL_CHAIN 1即 64x32仓库自带的 gifs/ 目录下 6 个 GIF 全部是 64x32 尺寸。若放错尺寸绘制回调GIFDraw中虽有iWidth PANEL_RES_X时的截断保护但高度方向并无同样保护显示结果不可预期。代码解析setup() 的四个阶段阶段一SD 卡初始化setup() 开头按官方SD_Test示例的模式挂载 SD 卡AnimatedGIFPanel_SD.ino#L128-L158if(!SD.begin(SS_PIN)){ Serial.println(Card Mount Failed); return; } uint8_t cardType SD.cardType(); // CARD_NONE / CARD_MMC / CARD_SD(SC) / CARD_SDHC uint64_t cardSize SD.cardSize() / (1024 * 1024); Serial.printf(SD Card Size: %lluMB\n, cardSize); Serial.printf(Total space: %lluMB\n, SD.totalBytes() / (1024 * 1024)); Serial.printf(Used space: %lluMB\n, SD.usedBytes() / (1024 * 1024));挂载失败或无卡时直接return不初始化显示屏。SD.begin的完整签名为bool begin(uint8_t ssPinSS, SPIClass spiSPI, uint32_t frequency4000000, const char * mountpoint/sd, uint8_t max_files5, bool format_if_emptyfalse)代码注释中已列出本示例只传 SS 引脚其余走默认值。阶段二HUB75 DMA 面板初始化HUB75_I2S_CFG mxconfig( PANEL_RES_X, // module width 64 PANEL_RES_Y, // module height 32 PANEL_CHAIN // Chain length 1 ); // 仅覆盖与 SD 卡冲突的引脚 mxconfig.gpio.a A_PIN; mxconfig.gpio.b B_PIN; mxconfig.gpio.c C_PIN; dma_display new MatrixPanel_I2S_DMA(mxconfig); if( not dma_display-begin() ) Serial.println(****** !KABOOM! HUB75 memory allocation failed ***********); dma_display-setBrightness8(128); // 0-255 dma_display-clearScreen();MatrixPanel_I2S_DMA构造时以mx_width * chain_length × mx_height初始化其 Adafruit GFX 基类见 src/ESP32-HUB75-MatrixPanel-I2S-DMA.h#L375-L383begin()负责分配 DMA 帧缓冲并启动 I2S 输出setBrightness8()与clearScreen()均为该类公开 APIsrc/ESP32-HUB75-MatrixPanel-I2S-DMA.h#L397、#L487。阶段三扫描 /gifs 目录构建播放列表目录扫描逻辑直接内联在 setup() 中AnimatedGIFPanel_SD.ino#L195-L223File root SD.open(/gifs); if(!root){ Serial.println(Failed to open directory); return; } File file root.openNextFile(); while(file){ if(!file.isDirectory()){ std::string filename /gifs/ std::string(file.name()); GifFiles.push_back( filename ); totalFiles; } file root.openNextFile(); } file.close(); Serial.printf(Found %d GIFs to play., totalFiles);要点只收集非目录条目路径统一拼成/gifs/文件名的绝对 FS 路径存入std::vectorstd::string GifFiles子目录会被静默跳过不递归sdcard_functions.hpp 中的listDir()提供递归遍历能力totalFiles与GifFiles是全局状态供两处共享扫描完成后紧接着设置字节序——这是 AnimatedGIF 解码的必配项// This is important - Set the right endianness. gif.begin(LITTLE_ENDIAN_PIXELS);阶段四loop() 顺序播放全部 GIFvoid loop(){ for(auto elem : GifFiles){ gifPlay( elem.c_str() ); gif.reset(); delay(500); } }逐个顺序播放列表中的 GIF每个 GIF 播完调用gif.reset()复位解码器状态为下一次gif.open做准备并停顿 500ms 后再播下一个。gifPlay()本身带一个防死循环保护AnimatedGIFPanel_SD.ino#L97-L121int gifPlay( const char* gifPath ) { if( ! gif.open( gifPath, GIFOpenFile, GIFCloseFile, GIFReadFile, GIFSeekFile, GIFDraw ) ){ log_n(Could not open gif %s, gifPath ); } int frameDelay 0; int then 0; while (gif.playFrame(true, frameDelay)) { then frameDelay; if( then maxGifDuration ) { // avoid being trapped in infinite GIFs break; } } gif.close(); return then; }其中maxGifDuration 30000msAnimatedGIFPanel_SD.ino#L89由于 GIF 的循环次数可能为无限playFrame会一直返回 true这里通过累计帧延迟超过 30 秒强制 break避免程序卡死在单个 GIF 上。这是把 GIF 播放器移植到 MCU 长驻程序时很实用的工程细节。GIF 解码回调实现gif_functions.hppgif.open()的第二个参数起是一组回调把如何读文件、如何画像素交给用户实现。本示例的 4 个回调全部在 gif_functions.hpp 中文件访问三件套static void * GIFOpenFile(const char *fname, int32_t *pSize) { FSGifFile M5STACK_SD.open(fname); if (FSGifFile) { *pSize FSGifFile.size(); return (void *)FSGifFile; } return NULL; }M5STACK_SD是为兼容 M5Stack 构建定义的别名普通 ESP32 构建下即官方SD实例gif_functions.hpp#L3-L6文件句柄直接返回静态File FSGifFile的地址解码器生命周期内整个文件就这一个句柄GIFReadFile读取数据块其中有一段值得注意的兼容性 workaround// Note: If you read a file all the way to the last byte, seek() stops working if ((pFile-iSize - pFile-iPos) iLen) iBytesRead pFile-iSize - pFile-iPos - 1; // -- ugly work-around即ESP32 的SD库在文件读到最后 1 字节时seek()会失效所以永远保留 1 字节不读以维持GIFSeekFile中f-seek(iPosition)的可用性——GIF 解码器在回读帧索引时依赖随机寻址这一点不能破坏GIFSeekFile用micros()记录了一次寻址耗时默认被log_d注释掉可用于排查 SD 卡 seek 性能。GIFDraw逐行写入 DMA 帧缓冲GIFDraw是解码器每解码完一行的回调它把 8-bit 调色板像素翻译成 RGB565 后直接打到 DMA 帧缓冲gif_functions.hpp#L58-L127void GIFDraw(GIFDRAW *pDraw) { int baseX pDraw-iX; // 帧的 X 偏移GIF 子帧定位 iWidth pDraw-iWidth; if (iWidth PANEL_RES_X) iWidth PANEL_RES_X; // 宽度截断保护 usPalette pDraw-pPalette; y pDraw-iY pDraw-y; // 帧顶 行偏移 当前 y if (pDraw-ucDisposalMethod 2) { // 恢复背景色的处置方式 for (x0; xiWidth; x) if (s[x] pDraw-ucTransparent) s[x] pDraw-ucBackground; } if (pDraw-ucHasTransparency) { // 按连续不透明像素段成批转换并绘制透明像素段整段跳过 // ... dma_display-drawPixel(baseX x xOffset, y, usTemp[xOffset]); // RGB565 } else { // 无透明整行顺序过调色板 for (x0; xiWidth; x) dma_display-drawPixel(baseX x, y, usPalette[*s]); // RGB565 } }实现要点色彩路径pDraw-pPalette由 AnimatedGIF 解码时按gif.begin(LITTLE_ENDIAN_PIXELS)的字节序生成为 RGB565 查找表因此usPalette[c]可直接作为drawPixel的颜色参数无需二次转换透明处理GIF 的透明像素在解码器输出中表现为透明索引值。代码把一行拆成不透明段/透明段交替处理——不透明段先收集进栈缓冲usTemp容量 320大于本示例 64 的宽度再批量绘制透明段则整段跳过避免逐像素判断disposal method 2当帧使用恢复背景色处置方式时先把行内透明索引替换为背景索引并清掉透明标志保证该帧后续按不透明路径处理写入目标dma_display-drawPixel()写的是 I2S DMA 双缓冲帧数据由 src/ESP32-HUB75-MatrixPanel-I2S-DMA.cpp 中的 DMA 引擎持续刷屏解码线程与显示刷新解耦——这正是该库相比逐行 GPIO 驱动 HUB75 的核心价值解码慢一拍也不影响刷新率。使用与验证清单在 Arduino IDE 中安装 ESP32 板支持、AnimatedGIFLarry Bank库把整个AnimatedGIFPanel_SD文件夹作为 sketch 打开依赖同目录的gif_functions.hpp、sdcard_functions.hpp按上文 FAT32 格式化 SD 卡建gifs目录放入与面板分辨率一致本例 64x32的 GIF按引脚对照表接线确认 A/B/C 三线实际接在 33/32/22 而非默认 23/19/5上电后串口115200应依次打印SD 卡类型与容量、/gifs下每个文件的名称与大小、Found N GIFs to play.随后逐个输出Playing: /gifs/xxx.gif若打印Card Mount Failed优先检查 MOSI/MISO/SCK/SS 四根线与 3.3V 供电若面板无显示而 SD 正常检查是否漏改 A/B/C 重映射。已知注意事项DMA 显示开启后 SD 卡访问受限.ino 中有一行注释// SD CARD STOPS WORKING WITH DMA DISPLAY ENABLED...AnimatedGIFPanel_SD.ino#L193作者观察到 I2S DMA 刷屏开启期间 SD 卡 SPI 访问不再可靠因此示例把目录扫描全部放在dma_display-begin()之后立即完成、且仅在 setup 中读目录播放阶段只读单个 GIF 文件。从源码结构看这是一种有意规避的时序安排GIF 尺寸强约束解码器不做缩放分辨率不匹配的 GIF 显示会错乱务必与PANEL_RES_X × PANEL_RES_Y × PANEL_CHAIN匹配E 引脚E_PIN默认 -164x64 面板必须显式分配引脚资源本示例用掉了 33/32/22A/B/C 23/19/18SPI 数据 5SS共 7 个 GPIO如果你的硬件还需要这些脚做其他用途需重新规划 HUB75 的其余默认引脚25/26/27/14/12/13/4/15/16。该示例与 examples/AnimatedGIFPanel_LittleFS 形成对照两者共用同一套AnimatedGIF解码回调思路区别仅在文件后端SPI SD 卡 vs Flash LittleFS引脚冲突重映射这一部分只有 SD 卡版本需要处理。赞分享嵌入式硬件开发驱动开发物联网【免费下载链接】ESP32-HUB75-MatrixPanel-DMAAn Adafruit GFX Compatible Library for the ESP32/S2/S3/P4 to drive HUB75 LED matrix panels using DMA for high refresh rates. Supports panel chaining.项目地址https://gitcode.com/gh_mirrors/es/ESP32-HUB75-MatrixPanel-DMA点击查看免费下载相关推荐ESP32-HUB75-MatrixPanel-DMA 实战用 LittleFS 存储并播放动画 GIF 的完整方案ESP32 HUB75 MatrixPanel DMA 实战用 LittleFS 存储并播放动画 GIF 的完整方案 本文以 examples/Animate嵌入式硬件开发驱动开发物联网ESP32-HUB75-MatrixPanel-DMA轻松打造高性能LED矩阵显示系统ESP32 HUB75 MatrixPanel DMA轻松打造高性能LED矩阵显示系统 你是否曾经想要制作一个炫酷的LED矩阵显示项目却被复杂的驱动和低刷新嵌入式硬件开发驱动开发物联网ESP32-HUB75-MatrixPanel-DMA 使用指南ESP32 HUB75 MatrixPanel DMA 使用指南 项目介绍 ESP32 HUB75 MatrixPanel DMA 是一个专为ESP32系列芯片嵌入式硬件开发驱动开发物联网创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑