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片选信号
核心功能模块
DMA初始化模块 (
dma_init)Flash地址发送模块 (
flash_2_lcd前半部分)双通道数据流转发模块 (
flash_2_lcd后半部分)背景显示模块 (
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_TXDMA通道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
字体库动态加载
图标资源管理
多页面切换
配置与优化建议
可调整参数
传输长度:通过
length参数动态设置Flash地址:支持24位地址空间任意定位
SPI时钟:可根据外设能力调整
扩展可能性
多缓冲支持:增加ping-pong缓冲区
压缩数据支持:集成软件解压层
错误处理:增加传输失败重试机制
注意事项
SPI模式匹配:确保Flash和LCD的SPI模式兼容
时序要求:Flash快速读命令需要额外的dummy周期
资源冲突:避免与其他DMA使用冲突
电源管理:传输期间保持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;
}