Flash到LCD的双DMA传输模块技术说明

模块概述

本模块基于CW32L012微控制器,实现了一种高效的Flash存储器到LCD屏幕的数据传输方案,采用双DMA通道协同工作,最大程度降低CPU负载,提升系统整体性能。

本双DMA方案通过巧妙的硬件资源利用,实现了高效的数据搬运管道,特别适用于需要大量图形数据处理的嵌入式显示系统。其核心价值在于将CPU从繁重的数据搬运任务中解放出来,同时保证了数据传输的实时性和可靠性,为资源受限的嵌入式系统提供了高性能的显示解决方案。

下面内容将展示了双DMA方案的工作机制,突出了硬件自动同步传输的设计,以及如何通过最小的CPU干预实现高效的数据搬运。

软件框架

硬件抽象层

  • SPI外设驱动:使用SPI1与Flash通信,SPI3与LCD通信

  • DMA控制器:CW32L012内置的DMA控制器,支持硬件触发传输

  • GPIO控制:通过SPI_CS_0/1宏控制Flash片选信号

核心功能模块

  1. DMA初始化模块 (dma_init)

  2. Flash地址发送模块 (flash_2_lcd前半部分)

  3. 双通道数据流转发模块 (flash_2_lcd后半部分)

  4. 背景显示模块 (flash_bg_lcd)

数据流向架构

第一阶段:地址发送(仅常规模式)

┌─────────────┐    DMA通道1    ┌─────────────┐    SPI1 TX     ┌─────────────┐
│ 地址缓冲区  │ ──────────────▶ │   DMA控制   │ ──────────────▶ │    Flash    │
│ (5字节)     │                │   寄存器    │                │             │
└─────────────┘                └─────────────┘                └─────────────┘

详细数据路径:

flash_addr_buff → DMA通道1 → SPI1(TX) → Flash

第二阶段:双DMA级联传输流程

┌─────────────┐                ┌─────────────┐    DMA通道2    ┌─────────────┐
│    Flash    │ ───SPI1 RX───▶ │   SPI1 DR   │ ──────────────▶ │   SPI3 DR   │
│             │                │  寄存器     │                │  寄存器     │
└─────────────┘                └─────────────┘                └─────────────┘
                                    ▲                              │
                                    │                              ▼
┌─────────────┐    DMA通道1    ┌────┴─────┐                ┌─────────────┐
│ 固定字节    │ ──────────────▶ │ SPI1 TX  │                │     LCD     │
│ (0x00)      │                │          │                │             │
└─────────────┘                └──────────┘                └─────────────┘

详细数据路径:

Flash → SPI1(RX) → DMA通道2 → SPI3(TX) → LCD
              ↳ 同时发送空字节触发时钟 →
              ↳ flash_read_buff → DMA通道1 → SPI1(TX)

关键硬件触发关系

配置参数说明

DMA通道1配置(地址发送模式)

传输次数: 5次 (命令+24位地址+保留)
源地址: flash_addr_buff (自增)
目标地址: SPI1->DR (固定)
触发源: SPI1_TX

DMA通道2配置(数据桥接模式)

传输次数: length (动态设置)
源地址: SPI1->DR (固定)
目标地址: SPI3->DR (固定)
触发源: SPI1_RX

特殊背景模式

传输次数: 128×128×2 = 32768次
源地址: flash_read_buff (固定)
目标地址: SPI3->DR (固定)
触发源: SPI3_TX

状态迁移

时序特性

┌───┬──────┬──────┬──────┬──────┐
│CS │______│      │      │______│
├───┼──────┼──────┼──────┼──────┤
│SCK│      │┌───┐ │┌───┐ │      │
│   │      ││CMD│ ││Addr││      │
├───┼──────┼──────┼──────┼──────┤
│MOSI│命令 │地址高│地址中│地址低│
├───┼──────┼──────┼──────┼──────┤
│MISO│  -  │  -  │  -  │ 数据 │
└───┴──────┴──────┴──────┴──────┘
    阶段一:地址发送    阶段二:数据传输

解决的关键问题

CPU负载优化

  • 零拷贝技术:数据直接从Flash经SPI到LCD,无需CPU参与数据搬运

  • 并行操作:DMA通道1和2协同工作,实现读写同步

  • 非阻塞等待:CPU仅在关键节点检查传输完成标志

实时性保障

  • 硬件触发:利用SPI的TX/RX事件自动触发DMA传输

  • 传输连续性:避免频繁的DMA启停操作

  • 中断最小化:使用轮询代替中断,确保时序确定性

内存效率提升

  • 缓冲区最小化

    • 地址缓冲区:仅5字节(命令+24位地址+保留)

    • 读数据缓冲区:仅1字节(用于生成SCK时钟)

  • 地址重映射:通过直接操作DMA寄存器动态修改传输参数

工作模式详解

初始化模式

  • 配置DMA通道1:Flash地址发送(5字节)

  • 配置DMA通道2:SPI1到SPI3的数据桥接

  • 禁用所有SPI DMA请求,等待实际传输

数据传输模式

// 第一阶段:发送读命令和地址
flash_addr_buff[0] = 0x0B;  // Fast Read命令
// ... 设置24位地址 ...
DMA通道1传输 → SPI1 → Flash

// 第二阶段:双DMA协同
while(传输未完成) {
    // DMA通道1:发送0x00生成SCK时钟
    // DMA通道2:读取Flash数据并转发到LCD
    // 硬件自动同步,无需CPU干预
}

背景显示模式

  • 特殊优化:128×128×2字节的连续传输

  • 适用于全屏刷新或背景加载场景

性能特征

传输效率

  • 理论最大速率:SPI总线速度限制

  • 实际效率:接近硬件极限,仅受限于Flash读取速度

资源占用

  • CPU占用:传输期间<5%(仅轮询等待)

  • 内存占用:6字节静态缓冲区

  • DMA通道:2个通道持续占用

时序特性

  • 启动延迟:微秒级初始化

  • 传输延迟:仅受SPI时钟频率限制

  • 完成检测:精确的硬件标志检测

应用场景

图形显示系统

  • LCD界面快速刷新

  • 图像数据流式加载

  • 动画帧缓冲区更新

数据记录器

  • 大容量数据实时显示

  • 日志信息滚动显示

  • 实时波形绘制

嵌入式GUI

  • 字体库动态加载

  • 图标资源管理

  • 多页面切换

配置与优化建议

可调整参数

  1. 传输长度:通过length参数动态设置

  2. Flash地址:支持24位地址空间任意定位

  3. SPI时钟:可根据外设能力调整

扩展可能性

  1. 多缓冲支持:增加ping-pong缓冲区

  2. 压缩数据支持:集成软件解压层

  3. 错误处理:增加传输失败重试机制

注意事项

  1. SPI模式匹配:确保Flash和LCD的SPI模式兼容

  2. 时序要求:Flash快速读命令需要额外的dummy周期

  3. 资源冲突:避免与其他DMA使用冲突

  4. 电源管理:传输期间保持Flash和LCD供电稳定

代码实现

程序流程:

实现源码:

// flash_lcd.c
#include "flash_lcd.h"
#include "cw32l012_dma.h"
#include "cw32l012_spi.h"
#include "flash_spi.h"

uint8_t flash_addr_buff[5] = {0x0b, 0x00, 0x00, 0x00, 0x08};
uint8_t flash_read_buff[1] = {0};

void dma_init(void)
{
    DMA_InitTypeDef DMA_InitStruct = {0};
    // BUFF1 (addr / read) → FLASH(TX)
    DMA_InitStruct.BlockCount = 1;
    DMA_InitStruct.TransferCount = 5;
    DMA_InitStruct.DataSize = DMA_DATA_SIZE_8BITS;
    DMA_InitStruct.DstAddress = (uint32_t)&CW_SPI1->DR;
    DMA_InitStruct.DstIncrement = DMA_ADDRESS_FIXED;
    DMA_InitStruct.DstReloadEnable = FALSE;
    DMA_InitStruct.RestartEnable = FALSE;
    DMA_InitStruct.SrcAddress = (uint32_t)flash_addr_buff;
    DMA_InitStruct.SrcIncrement = DMA_ADDRESS_INCREMENT;
    DMA_InitStruct.SrcReloadEnable = FALSE;
    DMA_InitStruct.TransferMode = DMA_MODE_BLOCK;
    DMA_InitStruct.TriggerSource = DMA_TRIGGER_SRC_SPI1_TX;
    DMA_InitStruct.TriggerType = DMA_TRIGGER_HARDWARE;
    DMA_Init(DMA_CHANNEL_1, &DMA_InitStruct);
    DMA_Cmd(DMA_CHANNEL_1, DISABLE);
    // FLASH(RX) → lcd(SPI3 TX)
    DMA_InitStruct.BlockCount = 1;
    DMA_InitStruct.TransferCount = 1;
    DMA_InitStruct.DataSize = DMA_DATA_SIZE_8BITS;
    DMA_InitStruct.DstAddress = (uint32_t)&CW_SPI3->DR;
    DMA_InitStruct.DstIncrement = DMA_ADDRESS_FIXED;
    DMA_InitStruct.DstReloadEnable = FALSE;
    DMA_InitStruct.RestartEnable = FALSE;
    DMA_InitStruct.SrcAddress = (uint32_t)&CW_SPI1->DR;
    DMA_InitStruct.SrcIncrement = DMA_ADDRESS_FIXED;
    DMA_InitStruct.SrcReloadEnable = FALSE;
    DMA_InitStruct.TransferMode = DMA_MODE_BLOCK;
    DMA_InitStruct.TriggerSource = DMA_TRIGGER_SRC_SPI1_RX;
    DMA_InitStruct.TriggerType = DMA_TRIGGER_HARDWARE;
    DMA_Init(DMA_CHANNEL_2, &DMA_InitStruct);
    DMA_Cmd(DMA_CHANNEL_2, DISABLE);
    //
    SPI_DMACmd(CW_SPI1, SPI_DMAReq_Tx | SPI_DMAReq_Rx, DISABLE);
    SPI_DMACmd(CW_SPI3, SPI_DMAReq_Tx, DISABLE);
}

void flash_bg_lcd(void)
{
    // 显示背景
    CW_DMA->CNT2 = 0x10000 | (128 * 128 * 2);
    CW_DMA->SRCADDR2_f.SRCADDR = (uint32_t)&flash_read_buff;
    CW_DMA->CSR2_f.SRCINC = 0; // 地址固定
    CW_DMA->DSTADDR2_f.DSTADDR = (uint32_t)&CW_SPI3->DR;
    CW_DMA->CSR2_f.DSTINC = 0; // 地址固定
    CW_DMA->TRIG2_f.HARDSRC = DMA_TRIGGER_SRC_SPI3_TX;
    //
    CW_DMA->ICR_f.TC2 = 0;
    // 启动
    CW_DMA->CSR2_f.EN = 1;
    //
    SPI_DMACmd(CW_SPI3, SPI_DMAReq_Tx, ENABLE);
    while (CW_DMA->ISR_f.TC2 == 0)
    {
        ;
    }
    SPI_DMACmd(CW_SPI3, SPI_DMAReq_Tx, DISABLE);
    DMA_Cmd(DMA_CHANNEL_2, DISABLE);
}

void flash_2_lcd(uint32_t flash_addr, uint16_t length)
{
    SPI_CS_0;
    // 写flash地址
    flash_addr_buff[1] = (flash_addr & 0xFF0000) >> 16;
    flash_addr_buff[2] = (flash_addr & 0xFF00) >> 8;
    flash_addr_buff[3] = flash_addr & 0xFF;

    CW_DMA->CNT1 = 0x10000 | 5;
    CW_DMA->SRCADDR1_f.SRCADDR = (uint32_t)flash_addr_buff;
    CW_DMA->CSR1_f.SRCINC = 1; // 地址自增
    CW_DMA->DSTADDR1_f.DSTADDR = (uint32_t)&CW_SPI1->DR;
    CW_DMA->CSR1_f.DSTINC = 0; // 地址固定

    // 清除DMA标志
    CW_DMA->ICR_f.TC1 = 0;
    CW_DMA->ICR_f.TC2 = 0;

    // 启动
    CW_DMA->CSR1_f.EN = 1;
    // CW_DMA->CSR2_f.EN = 1;
    //
    SPI_DMACmd(CW_SPI1, SPI_DMAReq_Tx, ENABLE);
    while (CW_DMA->ISR_f.TC1 == 0)
    {
        ;
    }

    SPI_DMACmd(CW_SPI1, SPI_DMAReq_Tx, DISABLE);
    DMA_Cmd(DMA_CHANNEL_1, DISABLE);
    DMA_Cmd(DMA_CHANNEL_2, DISABLE);

    // 发送数据到flash
    CW_DMA->CNT1 = 0x10000 | length;
    CW_DMA->SRCADDR1_f.SRCADDR = (uint32_t)flash_read_buff;
    CW_DMA->CSR1_f.SRCINC = 0; //  地址固定
    CW_DMA->DSTADDR1_f.DSTADDR = (uint32_t)&CW_SPI1->DR;
    CW_DMA->CSR1_f.DSTINC = 0; // 地址固定
    // 读flash数据到lcd
    CW_DMA->CNT2 = 0x10000 | length;
    CW_DMA->SRCADDR2_f.SRCADDR = (uint32_t)&CW_SPI1->DR;
    CW_DMA->CSR2_f.SRCINC = 0; // 地址固定
    CW_DMA->DSTADDR2_f.DSTADDR = (uint32_t)&CW_SPI3->DR;
    CW_DMA->CSR2_f.DSTINC = 0; // 地址固定
    CW_DMA->TRIG2_f.HARDSRC = DMA_TRIGGER_SRC_SPI1_RX;
    //
    CW_DMA->ICR_f.TC1 = 0;
    CW_DMA->ICR_f.TC2 = 0;
    // 启动
    CW_DMA->CSR1_f.EN = 1;
    CW_DMA->CSR2_f.EN = 1;
    //
    SPI_DMACmd(CW_SPI1, SPI_DMAReq_Tx | SPI_DMAReq_Rx, ENABLE);
    SPI_DMACmd(CW_SPI3, SPI_DMAReq_Tx, ENABLE);
    while (CW_DMA->ISR_f.TC2 == 0)
    {
        ;
    }

    // 禁用DMA和SPI DMA请求
    SPI_DMACmd(CW_SPI1, SPI_DMAReq_Tx | SPI_DMAReq_Rx, DISABLE);
    SPI_DMACmd(CW_SPI3, SPI_DMAReq_Tx, DISABLE);
    DMA_Cmd(DMA_CHANNEL_1, DISABLE);
    DMA_Cmd(DMA_CHANNEL_2, DISABLE);

    SPI_CS_1;
}


Flash到LCD的双DMA传输模块技术说明
https://www.ctuhub.top//archives/dual-DMA-transmission-module-for-%20flash-to-LCD
作者
w-lzh
发布于
2025年12月23日
更新于
2025年12月23日
许可协议