Skip to content

好的!我们用全景图的方式,从头到尾把新的 DMA 传输方式讲清楚。


全景图:数据从代码到屏幕的完整旅程

┌─────────────────────────────────────────────────────────┐
│                     你的代码                             │
│                                                         │
│  1. 告诉屏幕:我要写哪个区域  → st7789_set_window()     │
│  2. 告诉屏幕:准备接收像素   → st7789_write_cmd(0x2C)  │
│  3. 拉高 DC 引脚             → gpio_set_level(DC, 1)   │
│  4. 准备一个"DMA仓库"        → heap_caps_malloc()       │
│  5. 把颜色数据装进仓库        → for 循环填充            │
│  6. 告诉 DMA:去搬运!        → spi_device_transmit()  │
│  7. 等 DMA 搬完,继续下一趟  → while 循环              │
│  8. 仓库用完,归还            → heap_caps_free()        │
└─────────────────────────────────────────────────────────┘

第一步:告诉屏幕"我要写哪个区域"

你的代码

  └─→ st7789_set_window(0, 0, 239, 319)

            └─→ 发送命令 0x2A(列范围:0~239)
            └─→ 发送命令 0x2B(行范围:0~319)

            屏幕内部记住了:
            ┌──────────────┐
            │ 从(0,0)      │
            │ 到(239,319)  │
            │ 整个屏幕     │
            └──────────────┘

第二步:告诉屏幕"准备接收像素数据"

你的代码

  └─→ st7789_write_cmd(0x2C)

            └─→ 屏幕收到命令:
                "好的,我准备好了,
                 接下来收到的都是像素颜色数据!"

第三步:拉高 DC 引脚

DC 引脚就像一个"翻译牌":

DC = 0(低电平)→ 屏幕理解为:"这是命令"
DC = 1(高电平)→ 屏幕理解为:"这是数据(像素颜色)"

gpio_set_level(lcd_spi_dc, 1)

  └─→ DC 引脚变成高电平
        屏幕:"好,接下来收到的是颜色数据!"

第四步:申请"DMA仓库"

芯片内存示意图:

┌─────────────────────────────────────┐
│         芯片内部内存                 │
│                                     │
│  ┌──────────────────────────────┐   │
│  │  普通内存区域                │   │
│  │  CPU 能用,DMA 不能直接用    │   │
│  └──────────────────────────────┘   │
│                                     │
│  ┌──────────────────────────────┐   │
│  │  DMA 专用内存区域  ← 我们要的│   │
│  │  CPU 能用,DMA 也能直接用 ✓  │   │
│  │                              │   │
│  │  heap_caps_malloc(4096,      │   │
│  │    MALLOC_CAP_DMA) 申请的    │   │
│  │  就是这里的 4096 字节        │   │
│  └──────────────────────────────┘   │
└─────────────────────────────────────┘

为什么是 4096 字节?
  → 4096 字节 = 2048 个像素的数据
  → 就像快递车,一次最多装这么多货
  → 整个屏幕 153600 字节,需要跑约 38 趟

第五步:把颜色数据装进仓库

假设颜色是红色 0xF800:

0xF800 拆成两个字节:
  高字节 hi = 0xF8
  低字节 lo = 0x00

for 循环把仓库填满:

DMA仓库(4096字节):
┌────┬────┬────┬────┬────┬────┬──────┐
│0xF8│0x00│0xF8│0x00│0xF8│0x00│ .... │
└────┴────┴────┴────┴────┴────┴──────┘
  像素1      像素2      像素3    ...2048个像素

第六步 & 第七步:DMA 搬运(循环发送)

总共需要发:153600 字节
每趟搬运:  4096 字节
需要跑:    约 38 趟

第1趟:
  spi_device_transmit()
  ┌──────────┐         ┌─────────┐         ┌──────────┐
  │ DMA仓库  │ ──DMA──→│   SPI   │ ──线──→ │  屏幕    │
  │ 4096字节 │  自动    │  硬件   │         │ 填充了   │
  └──────────┘  搬运   └─────────┘         │ 2048像素 │
                                           └──────────┘
  等待搬完...

第2趟:
  spi_device_transmit()
  ┌──────────┐         ┌─────────┐         ┌──────────┐
  │ DMA仓库  │ ──DMA──→│   SPI   │ ──线──→ │  屏幕    │
  │ 4096字节 │  自动    │  硬件   │         │ 又填了   │
  └──────────┘  搬运   └─────────┘         │ 2048像素 │
                                           └──────────┘
  等待搬完...

... 重复约 38 趟 ...

整个屏幕填满!

第八步:归还仓库

heap_caps_free(buf)

  └─→ 把申请的 4096 字节内存还给系统
        就像快递车卸完货,把车还回去

和原来 CPU 方式的对比

原来(CPU方式):
┌────────────────────────────────────────────────┐
│ CPU 亲自搬运每1个字节,跑 153600 趟             │
│                                                │
│ CPU → 1字节 → SPI → 屏幕                       │
│ CPU → 1字节 → SPI → 屏幕                       │
│ CPU → 1字节 → SPI → 屏幕                       │
│ ... 重复 153600 次 ...                          │
└────────────────────────────────────────────────┘

现在(DMA方式):
┌────────────────────────────────────────────────┐
│ CPU 只需准备好仓库,DMA 自动搬运,跑约 38 趟   │
│                                                │
│ CPU 准备仓库(4096字节)                       │
│   ↓                                            │
│ DMA → 4096字节 → SPI → 屏幕  (第1趟)         │
│ DMA → 4096字节 → SPI → 屏幕  (第2趟)         │
│ ... 重复约 38 次 ...                            │
└────────────────────────────────────────────────┘

代码和全景图的对应关系

c
void lcd_fill_color_dma(uint16_t color, spi_device_handle_t spi_handle)
{
    st7789_set_window(0, 0, 239, 319, spi_handle); // ← 第一步
    st7789_write_cmd(0x2C, spi_handle);            // ← 第二步
    gpio_set_level(lcd_spi_dc, 1);                 // ← 第三步

    int chunk_size = 4096;
    uint8_t *buf = heap_caps_malloc(chunk_size,
                   MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL); // ← 第四步

    uint8_t hi = (color >> 8) & 0xFF;
    uint8_t lo = color & 0xFF;
    for (int i = 0; i < chunk_size; i += 2) {
        buf[i]     = hi;
        buf[i + 1] = lo;
    }                                              // ← 第五步

    int total = 240 * 320 * 2;
    int sent  = 0;
    while (sent < total) {                         // ← 第七步(循环)
        int this_time = total - sent;
        if (this_time > chunk_size) this_time = chunk_size;

        spi_transaction_t t = {
            .tx_buffer = buf,
            .length    = this_time * 8,
        };
        spi_device_transmit(spi_handle, &t);       // ← 第六步(DMA搬运)
        sent += this_time;
    }

    heap_caps_free(buf);                           // ← 第八步
}

这就是完整的全景图!每一行代码都对应上面流程中的一个步骤,没有任何多余的东西。[SPI LCD示例]

觉醒,然后燎原。 © 2026 门主引擎