好的!我们用全景图的方式,从头到尾把新的 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示例]
