找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 777|回复: 1
打印 上一主题 下一主题
收起左侧

求助ESP32S3+TFT+DMA 240*320屏幕只显示16*16的一小块

[复制链接]
跳转到指定楼层
楼主
ID:1159408 发表于 2025-8-24 16:09 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
如题:
官方示例也跑不通
下面是TFTESPI配置  
#define SPI_FREQUENCY  27000000
// #define SPI_FREQUENCY  40000000
// #define SPI_FREQUENCY  55000000 // STM32 SPI1 only (SPI2 maximum is 27MHz)
// #define SPI_FREQUENCY  80000000
#define TFT_MISO -1
#define TFT_MOSI 13
#define TFT_SCLK 14
#define TFT_CS   15  // Chip select control pin
#define TFT_DC    18  // Data Command control pin
#define ST7789_DRIVER
#define TFT_RST  -1  // Set TFT_RST to -1 if display RESET is co

// Optional reduced SPI frequency for reading TFT
#define SPI_READ_FREQUENCY  20000000

// The XPT2046 requires a lower SPI clock rate of 2.5MHz so we define that here:
#define SPI_TOUCH_FREQUENCY  2500000

// The ESP32 has 2 free SPI ports i.e. VSPI and HSPI, the VSPI is the default.
// If the VSPI port is in use and pins are not accessible (e.g. TTGO T-Beam)
// then uncomment the following line:
#define USE_HSPI_PORT



官方示例  github/Bodmer/TJpg_Decoder  注释掉DMA宏后正常 但是开启后只显示开头的16*16部分
下图是通过TJpgDec解码后的回调  经过串口打印 x y 确实是以16像素为基准变化的

帮忙瞅瞅
分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏 分享淘帖 顶 踩
回复

使用道具 举报

沙发
ID:1175769 发表于 2026-8-26 12:24 | 只看该作者
“开启DMA后只显示16x16小块”的问题,是ESP32-S3在使用TFT_eSPI和TJpg_Decoder时非常经典的一个坑。结合你的现象(解码回调坐标正常,但屏幕只渲染了第一块),问题大概率出在DMA内存分配或数据块合并逻辑上。
以下是具体的排查和解决步骤:
. 检查DMA内存分配(最常见原因)
ESP32-S3的SPI DMA硬件要求传输的缓冲区必须位于DMA可访问的内存区域。如果使用了普通的malloc或局部数组,DMA将无法正确读取后续数据,导致只传输了第一块就停止或出错。
解决方法:
确保你的图像缓冲区(Buffer)是通过 heap_caps_malloc 分配的,并指定了 MALLOC_CAP_DMA 标志。

. 警惕“碎片化”输出,合并数据块
TJpg_Decoder 默认以 16x16 的 MCU 块为单位输出数据。正如你串口打印看到的那样,每次回调只给 16x16 的数据。如果直接对这 16x16 的数据启动 DMA 传输,DMA 启动的开销极大,且极易引发时序问题。
解决方法:
不要对每个 16x16 块都调用一次 pushImageDMA。你需要在应用层做一个“行合并”逻辑:
将同一水平行的多个 16x16 块(240/16 = 15次)拼接成一个 240x16 的完整行数据。
当凑齐一整行(或更大的块)后,再统一调用一次 tft.pushImageDMA(x, y, 240, 16, buffer)。
这样可以将一帧图像的 DMA 传输次数从 225 次骤降到 15 次,不仅解决显示不全的问题,还能大幅提升帧率。
. 核对 TFT_eSPI 的 User_Setup 配置
你贴出的配置中缺少了分辨率定义,这在某些情况下会导致 DMA 传输越界或截断。
解决方法:
在 User_Setup.h 中明确补全屏幕的宽度和高度。

你遇到的“开启DMA后只显示16x16小块”的问题,是ESP32-S3在使用TFT_eSPI和TJpg_Decoder时非常经典的一个坑。结合你的现象(解码回调坐标正常,但屏幕只渲染了第一块),问题大概率出在DMA内存分配或数据块合并逻辑上。
以下是具体的排查和解决步骤:
. 检查DMA内存分配(最常见原因)
ESP32-S3的SPI DMA硬件要求传输的缓冲区必须位于DMA可访问的内存区域。如果使用了普通的malloc或局部数组,DMA将无法正确读取后续数据,导致只传输了第一块就停止或出错。
解决方法:
确保你的图像缓冲区(Buffer)是通过 heap_caps_malloc 分配的,并指定了 MALLOC_CAP_DMA 标志。
cpp



// 分配一块足够大的DMA内存,例如用于存放一整行或更大的数据块
uint16_t *dma_buffer = (uint16_t *)heap_caps_malloc(240 * 16 * sizeof(uint16_t), MALLOC_CAP_DMA);
. 警惕“碎片化”输出,合并数据块
TJpg_Decoder 默认以 16x16 的 MCU 块为单位输出数据。正如你串口打印看到的那样,每次回调只给 16x16 的数据。如果直接对这 16x16 的数据启动 DMA 传输,DMA 启动的开销极大,且极易引发时序问题。
解决方法:
不要对每个 16x16 块都调用一次 pushImageDMA。你需要在应用层做一个“行合并”逻辑:
将同一水平行的多个 16x16 块(240/16 = 15次)拼接成一个 240x16 的完整行数据。
当凑齐一整行(或更大的块)后,再统一调用一次 tft.pushImageDMA(x, y, 240, 16, buffer)。
这样可以将一帧图像的 DMA 传输次数从 225 次骤降到 15 次,不仅解决显示不全的问题,还能大幅提升帧率。
. 核对 TFT_eSPI 的 User_Setup 配置
你贴出的配置中缺少了分辨率定义,这在某些情况下会导致 DMA 传输越界或截断。
解决方法:
在 User_Setup.h 中明确补全屏幕的宽度和高度:
cpp
#define TFT_WIDTH  240
#define TFT_HEIGHT 320
同时,确认你的 TFT_DC 引脚(GPIO 18)没有与 ESP32-S3 的 PSRAM/Flash 引脚(如 GPIO 26-32)或摄像头引脚冲突。
. 关于显示偏移的额外提醒
虽然你目前的问题是只显示一小块,但如果后续修复 DMA 后发现画面错位、左侧不刷新或底部有花纹,这是因为 ST7789 内部 GRAM 是 240x320,但实际可视区域可能存在偏移。届时可以使用以下代码进行窗口偏移补偿。

下面这段代码的核心逻辑是:在内存中维护一个 240x16 的 DMA 安全缓冲区。每次 TJpg_Decoder 吐出 16x16 的碎片时,我们只把它复制(memcpy)到缓冲区对应的列位置,并不立即刷新屏幕。当凑齐 15 次碎片(即一整行 240 像素)后,再统一调用一次 pushImageDMA。
#include <TJpg_Decoder.h>#include <TFT_eSPI.h>// 1. 定义屏幕尺寸和块大小#define SCREEN_WIDTH  240#define SCREEN_HEIGHT 320#define MCU_BLOCK     16// 2. 声明全局的 DMA 安全缓冲区// 注意:必须使用 heap_caps_malloc 分配,否则 ESP32-S3 的 SPI DMA 会罢工uint16_t *dma_line_buffer = nullptr;// 3. 修改后的 TJpg 解码回调函数bool tft_output(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t *bitmap) {    // 防止越界保护    if (y >= SCREEN_HEIGHT) return 0;    // 计算当前行(16像素高)在 dma_line_buffer 中的起始位置    // 因为 w 固定是 16,所以每次复制的像素数是 16    uint16_t *dest_ptr = dma_line_buffer + x;    // 将 16x16 的碎片拷贝到行缓冲区的对应列位置    for (uint16_t j = 0; j < h; j++) {        memcpy(dest_ptr, bitmap + (j * w), w * sizeof(uint16_t));        dest_ptr += SCREEN_WIDTH; // 指针跳到缓冲区的下一行    }    // 判断是否凑齐了一整行(x 走到 240-16=224 时,说明这一行的 15 个块都拷贝完了)    // 注意:TJpg 的 x 坐标是 0, 16, 32... 224    if (x + w >= SCREEN_WIDTH) {        // 计算当前行缓冲区的绝对 Y 坐标        uint16_t abs_y = y;                 // 将拼好的 240x16 整行数据通过 DMA 推送到屏幕        // 注意:如果你的屏幕有偏移量,记得在这里加上 tft.setAddrWindow 或修改 x/y        tft.pushImageDMA(0, abs_y, SCREEN_WIDTH, MCU_BLOCK, dma_line_buffer);    }    return 1; // 必须返回 1,告诉解码器继续解码下一块}void setup() {    Serial.begin(115200);    tft.init();    tft.setRotation(0); // 根据你的屏幕调整旋转方向    tft.fillScreen(TFT_BLACK);    // 初始化 DMA 安全内存    dma_line_buffer = (uint16_t *)heap_caps_malloc(SCREEN_WIDTH * MCU_BLOCK * sizeof(uint16_t), MALLOC_CAP_DMA);    if (!dma_line_buffer) {        Serial.println("Failed to allocate DMA memory!");        while (1);    }    // 初始化 TJpg_Decoder    TJpgDec.setJpgScale(1);    TJpgDec.setCallback(tft_output);        // 测试解码    // TJpgDec.drawJpg(0, 0, your_jpg_array, your_jpg_size);}void loop() {    // ...}

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

快速回复 返回顶部 返回列表